博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis-Plus进阶和使用实践
阅读量:4298 次
发布时间:2019-05-27

本文共 19287 字,大约阅读时间需要 64 分钟。

文章目录

MyBatis-Plus进阶和使用实践

一、前言

  1. 环境说明
  • 操作系统:Windows 10 专业版
  • Mybatis-Plus 版本:3.4.2
  • JDK 版本:1.8
  • 开发工具:IntelliJ IDEA 2020.2.2
  1. 简介
  • MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

    特性:

  1. 无侵入、消耗小、强大的 CRUD 操作
  2. 支持 Lambda 形式调用,支持多种数据库
  3. 支持主键自动生成,支持 ActiveRecord 模式
  4. 支持自定义全局通用操作,支持关键词自动转义
  5. 内置代码生成器,内置分页插件,内置性能分析插件
  6. 内置全局拦截插件,内置 SQL 注入剥离器

MyBatis-Plus 官网:

MyBatis-Plus 示例工程:

MyBatis 官网:

Maven 仓库:

MyBatis-Plus入门和使用实践:

二、正文

1.创建项目

1)引入依赖

  • pom.xml 引入依赖 mybatis-plus、lombok、spring-boot
spring-boot-starter-parent
org.springframework.boot
2.1.3.RELEASE
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
true
com.baomidou
mybatis-plus-boot-starter
3.4.2
com.baomidou
mybatis-plus
3.4.2
mysql
mysql-connector-java
junit
junit
4.12
test

2)SpringBoot配置

  • resources 文件夹创建 application.yml
# mysql 连接配置spring:  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/mp-advanced-db?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8    username: root    password: 123456# 输入日志,查看 sql 语句logging:  level:    root: warn    org.example.dao: trace  pattern:    console: '%p%m%n'
  • App.java 启动 spring-boot
package org.example;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * spring-boot 启动入口 * */@SpringBootApplication@MapperScan({
"org.example.dao"})public class App {
public static void main( String[] args ) {
SpringApplication.run(App.class, args); }}

3)测试数据

  • sql 脚本
#创建用户表CREATE TABLE user (    id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',    name VARCHAR(30) DEFAULT NULL COMMENT '姓名',    age INT(11) DEFAULT NULL COMMENT '年龄',    email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',    manager_id BIGINT(20) DEFAULT NULL COMMENT '上级领导id',    create_time DATETIME DEFAULT NULL COMMENT '创建时间',	update_time DATETIME DEFAULT NULL COMMENT '修改时间',	version INT(11) DEFAULT '1' COMMENT '版本',	deleted INT(1) DEFAULT '0' COMMENT '逻辑删除标识(0.未删除,1.已删除)',    CONSTRAINT manager_fk FOREIGN KEY (manager_id)        REFERENCES user (id))  ENGINE=INNODB CHARSET=UTF8;#初始化数据:INSERT INTO user (id, name, age, email, manager_id	, create_time)VALUES (1087982257332887553, '大boss', 40, 'boss@baomidou.com', NULL		, '2019-01-11 14:20:20'),	(1088248166370832385, '王天风', 25, 'wtf@baomidou.com', 1087982257332887553		, '2019-02-05 11:12:22'),	(1088250446457389058, '李艺伟', 28, 'lyw@baomidou.com', 1088248166370832385		, '2019-02-14 08:31:16'),	(1094590409767661570, '张雨琪', 31, 'zjq@baomidou.com', 1088248166370832385		, '2019-01-14 09:15:15'),	(1094592041087729666, '刘红雨', 32, 'lhm@baomidou.com', 1088248166370832385		, '2019-01-14 09:48:16');
  • 创建 entity/User.java
package org.example.entity;import com.baomidou.mybatisplus.extension.activerecord.Model;import lombok.Data;import java.time.LocalDateTime;/*** * 用户实体类 * */@Datapublic class User extends Model {
// 用户 ID private Long id; // 姓名 private String name; // 年龄 private Integer age; // 邮箱 private String email; // 上级领导 ID private Long managerId; // 创建时间 private LocalDateTime createTime; // 更新时间 private LocalDateTime updateTime; // 版本 private Integer version; // 逻辑删除标识(0.未删除,1.已删除) private Integer deleted;}
  • 创建 dao/UserMapper.java (接口)
package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.example.entity.User;public interface UserMapper extends BaseMapper
{
}

4)测试例子

  • 测试类:AppTest.java
package org.example;import org.example.dao.UserMapper;import org.example.entity.User;import org.junit.Assert;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTestpublic class AppTest {
@Autowired private UserMapper userMapper; /** * 查询列表 * */ @Test public void select() {
List
users = userMapper.selectList(null); // 判断预期值与实际值是否相同 // Assert.assertEquals(5, users.size()); // 循环输出 users.forEach(System.err::println); }}

2.逻辑删除

逻辑删除-MP官网:

使用场景: 1.保留业务数据; 2.主外键关联,不方便删除

  • application.yml 配置逻辑删除默认值
# mybatis-plusmybatis-plus:  # 全局主键配置  global-config:    db-config:      # 逻辑删除:标记为正常的值      logic-not-delete-value: 0      # 逻辑删除:标记为删除的值      logic-delete-value: 1
  • entity/User.java

@TableLogic :标记逻辑删除属性

@TableField(select = false) :查询时排除掉当前属性

public class User {
...... // 逻辑删除标识(0.未删除,1.已删除) @TableLogic @TableField(select = false) private Integer deleted;}
  • 测试类:AppTest.java
@Testpublic void deleteById(){
Long userid = 1088250446457389058L; // 根据 ID 删除数据 Integer num = userMapper.deleteById(userid); System.out.println("受影响行数:"+num); // 查询删除结果 List
users = userMapper.selectList(null); users.forEach(System.err::println);}

结果:

1.删除方法,最终执行的是 update 语句

2.查询方法,最终执行的查询 SQL 中添加了逻辑删除的属性,只查询未删除的数据

注意事项:

  1. 执行修改时,会自动添加逻辑删除属性,作为其中的一个条件
  2. 自定义 SQL 不会自动添加逻辑删除属性(解决:1.删除标识加到 Wrapper 中;2.删除标识加到 SQL 中)

3.自动填充

自动填充-MP官网:

使用场景: 自动填充创建时间和修改时间

  • entity/User.java

@TableField(fill = FieldFill.INSERT) :执行新增操作时,进行填充

@TableField(fill = FieldFill.UPDATE) :执行修改操作时,进行填充

public class User {
...... // 创建时间 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; // 更新时间 @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime;}
  • 创建 componect/MyMetaObjectHandler.java

实现:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口

package org.example.componect;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.time.LocalDateTime;@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {
@Override public void insertFill(MetaObject metaObject) {
// 当属性存在时,在执行自动填充操作 boolean hasSetter = metaObject.hasSetter("createTime"); // 当属性未设置值时,在执行自动填充操作 Object val = getFieldValByName("createTime", metaObject); if(hasSetter && val == null){
// 属性存在,且未设置值 setFieldValByName("createTime", LocalDateTime.now(), metaObject); } } @Override public void updateFill(MetaObject metaObject) {
// 当属性存在时,在执行自动填充操作 boolean hasSetter = metaObject.hasSetter("updateTime"); // 当属性未设置值时,在执行自动填充操作 Object val = getFieldValByName("updateTime", metaObject); if(hasSetter && val == null){
// 属性存在,且未设置值 setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } }}
  • 测试类:AppTest.java
@Testpublic void insert(){
User user = new User(); user.setName("李小龙"); user.setAge(22); user.setEmail("lxl@163.com"); user.setManagerId(1088248166370832385L); // 插入数据 int num = userMapper.insert(user); System.err.println("影响记录数据:"+num); System.err.println("主键:"+user.getId());}@Testpublic void updateById(){
User user = new User(); user.setId(1360237954211033089L); user.setAge(23); // 更新数据 int num = userMapper.updateById(user); System.err.println("影响记录数据:"+num);}

4.乐观锁

乐观锁-维基百科:

乐观锁-MP官网:

使用场景: 防止更新冲突

  • config/MybatisPlusConfig.java
package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件 return interceptor; }}
  • entity/User.java
public class User {
...... // 版本 @Version private Integer version;}
  • 测试类:AppTest.java
@Testpublic void updateById(){
// 查询数据,获取当前版本 Long userid = 1360237954211033089L; User user = userMapper.selectById(userid); // 更新数据 user.setAge(23); int num = userMapper.updateById(user); System.err.println("影响记录数据:"+num);}

5.性能分析

执行 SQL 分析-MP官网:

p6spy-GitHub:

使用场景: 输出每条 SQL 语句执行时间

注意: 该插件有性能损耗,不建议生产环境使用。

建议: 开发环境和测试环境配置 p6spy 驱动;生产环境配置正常的数据库连接驱动

  • pom.xml
p6spy
p6spy
3.9.1
  • resources/application.yml

数据源配置修改:driver-class-name 和 url

spring:  datasource:    # driver-class-name: com.mysql.cj.jdbc.Driver    driver-class-name: com.p6spy.engine.spy.P6SpyDriver    # url: jdbc:mysql://localhost:3306 ......    url: jdbc:p6spy:mysql://localhost:3306 ......    ......
  • 创建 resources/spy.properties
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory#3.2.1以下使用或者不配置#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory# 自定义日志打印logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger#日志输出到控制台appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger# 使用日志系统记录 sql#appender=com.p6spy.engine.spy.appender.Slf4JLogger# 设置 p6spy driver 代理deregisterdrivers=true# 取消JDBC URL前缀useprefix=true# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.excludecategories=info,debug,result,commit,resultset# 日期格式dateformat=yyyy-MM-dd HH:mm:ss# 实际驱动可多个#driverlist=org.h2.Driver# 是否开启慢SQL记录outagedetection=true# 慢SQL记录标准 2 秒outagedetectioninterval=2# 输出到日志文件# logfile=spylog.log
  • 测试类:AppTest.java
@Testpublic void select(){
List
users = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}

6.多租户

多租户技术-维基百科:

多租户-MP官网:

使用场景: 多用户的环境下共享相同的数据库,例如:当前用户只能查询自己创建的数据

  • config/MybatisPlusConfig.java
package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;import java.util.Arrays;import java.util.List;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 多租户 sql 解析器 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override public String getTenantIdColumn() {
return "manager_id"; // 实现多租户的数据库表字段名 } @Override public Expression getTenantId() {
// 一般从 session 或 配置文件中获取 Long managerId = 1088248166370832385L; return new LongValue(managerId); // 传入的多租户值 } @Override public boolean ignoreTable(String tableName) {
List
list = Arrays.asList("rols", "power"); return list.contains(tableName); // return false;// 表示当前表需要添加多租户过滤;如果不需要,判断表名后,返回 true 即可 } })); // 分页插件 // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false // PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); // interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; }}
  • 测试类:AppTest.java

实际情况:CURD 都会生效

方法级取消多租户信息:Mapper 方法上添加注解 @InterceptorIgnore(tenantLine = "true")

@Testpublic void select(){
List
users = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}

7.动态表

动态表-MP官网:

  • 使用场景: 不同的表,表结构相同(数据量大);例如:日志表,根据月份进行拆分

  • config/MybatisPlusConfig.java

package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;import java.util.HashMap;import java.util.Random;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 动态表(这里需要先创建 user_2018 和 user_2019 两张表) DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); HashMap
map = new HashMap
(2) {
{
put("user", (sql, tableName) -> {
String year = "_2018"; int random = new Random().nextInt(10); if (random % 2 == 1) {
year = "_2019"; } return tableName + year; }); }}; dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; }}
  • 测试类:AppTest.java

方法级取消动态表名替换:Mapper 方法上添加注解 @InterceptorIgnore(dynamicTableName= "true")

@Testpublic void select(){
List
users = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}

8.SQL注入器

SQL 注入器-MP官网:

1)自定义

  • 使用场景: 自定义通用方法;例如:BaseMapper 的方法

    步骤:

  1. 创建自定义方法的类
  2. 创建注入器
  3. 将自定义方法添加到 Mapper 中
  • 创建 method/DeleteAllMethod.java

继承 com.baomidou.mybatisplus.core.injector.AbstractMethod

package org.example.method;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.metadata.TableInfo;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlSource;public class DeleteAllMethod extends AbstractMethod {
@Override public MappedStatement injectMappedStatement(Class
mapperClass, Class
modelClass, TableInfo tableInfo) {
// 定义 sql 语句 String sql = "delete from " + tableInfo.getTableName(); // 定义方法名 String method = "deleteAll"; // sql 源 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); // delete 方法 return addDeleteMappedStatement(mapperClass, method, sqlSource); }}
  • 创建 injector/MySqlInjector.java
package org.example.injector;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;import org.example.method.DeleteAllMethod;import java.util.List;@Componentpublic class MySqlInjector extends DefaultSqlInjector {
@Override public List
getMethodList(Class
mapperClass) {
List
methods = super.getMethodList(mapperClass); // 获取之前已添加的方法类 methods.add(new DeleteAllMethod()); // 添加自定义的方法类 return methods; }}
  • 创建 dao/MyMapper.java

继承 com.baomidou.mybatisplus.core.mapper.BaseMapper

package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface MyMapper
extends BaseMapper
{ /** * 删除所有数据 * @return 受影响行数 * */ public int deleteAll();}
  • dao/UserMapper.java

继承 MyMapper

package org.example.dao;import com.baomidou.mybatisplus.core.conditions.Wrapper;import com.baomidou.mybatisplus.core.toolkit.Constants;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import org.example.entity.User;import java.util.List;public interface UserMapper extends MyMapper
{
......}
  • 测试类:AppTest.java

方法级取消动态表名替换:Mapper 方法上添加注解 @InterceptorIgnore(dynamicTableName= "true")

@Testpublic void deleteAll(){
Integer num = userMapper.deleteAll(); System.out.println("受影响行数:"+num);}

2)MP选装件

选装件-MP官网:

  • MP 写好的方法,但是并没有添加到 BaseMapper 中;需要手动在 injector/MySqlInjector.java 中添加

选装件目录:com.baomidou.mybatisplus.extension.injector.methods

类名 说明
InsertBatchSomeColumn 批量插入,可排除某些字段
LogicDeleteByIdWithFill 根据 id 逻辑删除数据,并带字段填充功能;例如:逻辑删除时,修改操作人字段
AlwaysUpdateSomeColumnById 根据 id 更新固定的某些字段

InsertBatchSomeColumn 为例:

  • injector/MySqlInjector.java
package org.example.injector;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;import org.example.method.DeleteAllMethod;import java.util.List;@Componentpublic class MySqlInjector extends DefaultSqlInjector {
@Override public List
getMethodList(Class
mapperClass) {
List
methods = super.getMethodList(mapperClass); // 获取之前已添加的方法类 methods.add(new DeleteAllMethod()); // 添加自定义的方法类 methods.add(new InsertBatchSomeColumn(t->!t.isLogicDelete())); // 不是逻辑删除的字段都包含在内 return methods; }}
  • dao/MyMapper.java

继承 com.baomidou.mybatisplus.core.mapper.BaseMapper

package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import java.util.List;public interface MyMapper
extends BaseMapper
{ /** * 删除所有数据 * @return 受影响行数 * */ public int deleteAll(); /** * 批量插入 * @return 受影响行数 * */ public int insertBatchSomeColumn(List
list);}
  • dao/UserMapper.java

继承 MyMapper

  • 测试类:AppTest.java

方法级取消动态表名替换:Mapper 方法上添加注解 @InterceptorIgnore(dynamicTableName= "true")

@Testpublic void insertBatchSomeColumn(){
User user = new User(); user.setName("李小龙"); user.setAge(22); user.setEmail("lxl@163.com"); user.setManagerId(1088248166370832385L); User user2 = new User(); user2.setName("李二龙"); user2.setAge(25); user2.setEmail("lel@163.com"); user2.setManagerId(1088248166370832385L); List
users = Arrays.asList(user, user2); // 插入数据 int num = userMapper.insertBatchSomeColumn(users); System.err.println("影响记录数据:"+num); System.err.println("主键:"+user.getId());}

转载地址:http://qznws.baihongyu.com/

你可能感兴趣的文章
VKCUP 2012 B Taxi 个人认为一道比较好的题目(虽然已经被否决),还是来发下代码吧
查看>>
HDU上一道最小生成树模板题的练习
查看>>
CCNU第五周数学练习的一道水题
查看>>
CCNU第五周赛前练习:不可摸数
查看>>
安卓7.1源码——制作OTA包 (平台msm8909)
查看>>
安卓7.1源码——改屏幕旋转(横屏) (平台msm8909)
查看>>
安卓7.1系统源码 添加wifi预置信息 (平台msm8909)
查看>>
安卓7.1系统源码 屏蔽sim卡的提示 (平台msm8909)
查看>>
安卓7.1系统源码 屏蔽系统home键 (平台msm8909)
查看>>
安卓7.1系统源码 获取framework.jar包 (平台msm8909)
查看>>
安卓7.1系统源码 默认开root的权限 (平台msm8909)
查看>>
安卓7.1系统源码 wifi提示无法访问互联网
查看>>
安卓7.1关闭userdebug版本的串口调试 同时也可以打开user版本的串口调试
查看>>
STM32F40实现 按键密码锁、按键控制LED 、串口选择菜单 、串口控制LED开关 、串口控制蜂鸣器开关及响度 、串口控制蜂鸣器播放音乐 、按键控制LED的亮度
查看>>
内核源码树内核编译错误
查看>>
开发板网线连接虚拟机,虚拟机ens33,没有ip地址(s5pv210)
查看>>
学习笔记:内核编译测试自己编写的驱动之环境搭建
查看>>
内核链表学生管理系统,strcpy函数实现将数组指针赋值给数组
查看>>
linux移植jpeg库和常见错误
查看>>
ES入门(一)Elasticsearch简介:基本概念
查看>>