一起来学SpringBoot(十五)MybatisPlus的整合

MyBatis-Plus(简称 MP)是一个MyBatis的增强工具 ,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本篇文章介绍的是与springboot的整合。

img

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 支持关键词自动转义:支持数据库关键词(order、key……)自动转义,还可自定义关键词

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

    不要忘记依赖

    1
    2
    3
    4
    5
    6
    <!-- mp -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.3</version>
    </dependency>

代码生成器

不多说这个非常爽,运行下能生成基本结构。我这里稍微加了点注释,写好表名直接运行就ok了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.maoxs.generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;

/**
* @author fulin
* @since 2018-09-12
*/
public class MysqlGenerator {

public static void Generator(String[] tableName) {

String projectPath = System.getProperty("user.dir");

//============================== 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(projectPath + "/springboot-mybatisplus/src/main/java")
.setActiveRecord(true)// 是否支持 AR
.setAuthor("fulin") //设置作者名字
.setFileOverride(true) //文件覆盖(全新文件)
.setIdType(IdType.AUTO)//主键策略
.setBaseResultMap(true) //SQL 映射文件
.setBaseColumnList(true)//SQL 片段
.setOpen(false);
//============================== 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MARIADB)
.setUrl("jdbc:mariadb://localhost:3306/test")
.setDriverName("org.mariadb.jdbc.Driver")
.setUsername("root")
//.setSchemaName("public")
.setPassword("123456");
//==============================包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.maoxs")//配置父包路径
.setModuleName("base")//配置业务包路径
.setMapper("mapper")
.setEntity("entity")
.setService("service")
.setController("controller");
//.setServiceImpl("service.impl"); 会自动生成 impl,可以不设定
//============================== 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称
return projectPath + "/springboot-mybatisplus/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
//============================== 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel)//设置命名规则 underline_to_camel 底线变驼峰
.setColumnNaming(NamingStrategy.underline_to_camel)//设置设置列命名 underline_to_camel 底线变驼峰
//.setSuperEntityClass("com.maoxs.pojo")//设置继承类
//.setSuperControllerClass("com.maoxs.controller")//设置继承类
.setEntityLombokModel(true)//是否加入lombok
.setInclude(tableName)//设置表名
//.setSuperEntityColumns("id") //设置超级超级列
.setControllerMappingHyphenStyle(true)//设置controller映射联字符
.setTablePrefix(pc.getModuleName() + "_");//表的前缀
//============================== 生成配置
AutoGenerator mpg = new AutoGenerator();
mpg.setCfg(cfg)
.setTemplate(new TemplateConfig().setXml(null))
.setGlobalConfig(gc)
.setDataSource(dsc)
.setPackageInfo(pc)
.setStrategy(strategy)
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
public static void main(String[] args) {
Generator(new String[]{"x", "xx"});
}
}

这只是生成代码的工作,然后呢为了确保你生成的Mapper接口可以扫描到,还需要这样的一个配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.maoxs.conf;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author fulin
* @since 2018-09-10
*/
@Configuration
@MapperScan("com.maoxs.base.mapper")
public class MybatisPlusConfig {

}

@MapperScan 是用来扫描你mapper所在的包,多个包的话英文逗号隔开即可。剩下的大多数MybatisPlus的配置基本都是在这里配置。

crud

这边就阐述什么表名什么的,可以自己尝试创建,这里只提到怎么使用,重要的配置我会贴出.

这边呢就贴出一个简单的增删改查的测试类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.maoxs.crud;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.maoxs.SpringbootMybatisplusApplicationTests;
import com.maoxs.base.entity.User;
import com.maoxs.base.mapper.UserMapper;
import org.junit.Assert;
import org.junit.Test;

import javax.annotation.Resource;

public class CrudTest extends SpringbootMybatisplusApplicationTests {
@Resource
private UserMapper userMapper;

@Test
public void Insert() {
User user = new User();
user.setAge(17);
user.setName("我是付林");
int num = userMapper.insert(user);
Assert.assertTrue(num > 0);
System.out.println("插入成功Id为" + user.getId());
}

@Test
public void Delete() {
Assert.assertTrue(userMapper.deleteById(14L) > 0);
Assert.assertTrue(userMapper.delete(new QueryWrapper<User>().lambda().eq(User::getName, "我是付林")) > 0);
}

@Test
public void Update() {
Assert.assertTrue(userMapper.updateById(new User().setId(1L).setAge(100).setName("我是付林")) > 0);
Assert.assertTrue(userMapper.update(new User(),
new UpdateWrapper<User>().lambda().set(User::getAge, 2).eq(User::getId, 2)
) > 0);
}

@Test
public void Select() {
Assert.assertEquals("cfulin", userMapper.selectById(3L).getName());
User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getId, 2));
Assert.assertEquals("付林1", user.getName());
Assert.assertTrue(2 == user.getAge());
}
}

这里呢说明一下Wrapper 这可是个好玩的东西,相当于QBC那样的动态sql拼接,这里mybatisplus是3.0以上的版本,算是一个大版本,这里呢条件的拼接呢都使用了 lambda 方式。如果不想使用,比如new QueryWrapper().lambda().eq(User::getId, 2) 可以写为new QueryWrapper().eq(“id”, 2)

AR(ActiveRecord)

在web后台开发领域,经常会遇到应用程序操作数据库的场景,目前市面上存在的数据库包括mysql、postgresql、oracle、sqlite等,为了屏蔽不同数据库的差异,产生了ORM(Object-Relational-Mapping),在实现层面,又分为DataMapper和ActiveRecord两种。mybatis对其也有支持

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.maoxs.crud;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.maoxs.SpringbootMybatisplusApplicationTests;
import com.maoxs.base.entity.User;
import org.junit.Test;

/**
* @program: plus
* @description: Ar测试
* @author: fulin
* @create: 2018-09-24 12:13
**/
public class ArTest extends SpringbootMybatisplusApplicationTests {
@Test
public void ARInsert() {
User user = new User();
user.setName("fulinlin");
user.setAge(20);
//字段不为空插入
user.insert();
//ID为空插入,否则为更新
user.insertOrUpdate();
}

@Test
public void ARUpdate() {
User user = new User();
user.setName("快乐猫");
user.setAge(24);
user.setId(3L);
//ID 修改
user.updateById();
//条件修改
user.update(new UpdateWrapper<User>().lambda().eq(User::getId, "5"));
}


@Test
public void ARSelect() {
User user = new User();
user.setId(4L);
//setId
user.selectById();
//直接键入 Id
user.selectById(24);
//条件
user.selectCount(new QueryWrapper<User>().lambda().eq(User::getId, 0));
//查询所有
user.selectAll();
//查询总记录数
user.selectList(new QueryWrapper<User>().lambda().eq(User::getName, "fulinlin"));
//查询一个
user.selectOne(new QueryWrapper());
//分页
user.selectPage(new Page<>(1, 2), new QueryWrapper<>());
}


@Test
public void ARDelete() {
//删除不存在的数据 在逻辑上也是成功的,返回结果 true
User user = new User();
user.setId(4L);
user.deleteById();
user.deleteById(31);
//条件删除
user.delete(new QueryWrapper<User>().lambda().eq(User::getId, 3L));
}
}

分页

MybatisPlus也提供了一个分页插件,使用起来及其方便,这里说明两种情况下的分页,一种是自带的,一种是自定义的。不过呢做这些之前你要先引入这个配置。

1
2
3
4
5
6
7
8
/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}

普通分页

普通分页需要传递一个page 的对象,里面说明你当前页和分页的大小,用起来也是非常的舒服。

1
2
3
4
5
6
@Test
public void Page() {
Page<User> page = new Page<>(1, 5);
IPage<User> userIPage = userMapper.selectPage(page, null);
System.out.println(Collections.unmodifiableCollection(userIPage.getRecords()));
}

自定义分页

如果在某些情况下需要自己定义分页,那怎么办呢

首先呢在mapper接口中定义一个方法

1
2
3
4
5
6
7
8
9
10

/**
* 3.x 的 page 可以进行取值,多个入参记得加上注解
* 自定义 page 类必须放在入参第一位
* 返回值可以用 IPage<T> 接收 也可以使用入参的 MyPage<T> 接收
* todo 目前使用注解会报错,写在 xml 里就没事
* @param myPage 自定义 page
* @return 分页数据
*/
MyPage<User> mySelectPage(@Param("pg") MyPage<User> myPage);

然后呢你需要在xml中写一个select做绑定

1
2
3
<select id="mySelectPage" resultType="com.maoxs.base.entity.User">
select * from user where name like '%' #{pg.selectStr} '%'and age > #{pg.selectInt}
</select>

这里呢,把用到的MyPage贴出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.maoxs.model;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @author fulin
* @since 2018-09-10
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class MyPage<T> extends Page<T> {
private static final long serialVersionUID = 5194933845448697148L;

private Integer selectInt;
private String selectStr;

public MyPage(long current, long size) {
super(current, size);
}
}

怎么使用呢?贴一个测试类

1
2
3
4
5
6
@Test
public void MyPage() {
MyPage<User> myPage = new MyPage<User>(1, 5).setSelectInt(10).setSelectStr("fulin");
MyPage<User> userMyPage = userMapper.mySelectPage(myPage);
System.out.println(Collections.unmodifiableCollection(userMyPage.getRecords()));
}

逻辑删除

有时候在做业务的时候,由于涉及到数据的安全性和方便以后的维护,删除一般都是假删除,当然了mybatisplus也提供的非常简便的做法,如果想使用mybatisplus的逻辑删除,必须要注入LogicSqlInjector 这个类

1
2
3
4
5
6
7
8
/**
* 逻辑删除
* @return
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}

然后呢需要在实体类的属性中加入@TableLogic 注解

1
2
@TableLogic
private Integer isDelete;

这里有两种配置,一中是局部一中是全局.

局部配置呢就在注解中设置

1
2
@TableLogic(value = "0",delval = "1")
private Integer isDelete;

全局呢就是在yml中配置

1
2
3
4
5
6
7
mybatis-plus:
global-config:
banner: false
db-config:
logic-delete-value: 1 #删除后的状态 默认值1
logic-not-delete-value: 0 #逻辑前的值 默认值0
mapper-locations: classpath:/mapper/base/*Mapper.xml

然后呢你进行删除的时候,这个加入注解的字段就会为你设置的制定逻辑删除的状态,并且,如果你设置了删除值,查询和修改都不会在对删除的数据进行操作。

1
2
3
4
5
@Test
public void testLogicDeleteBatchIds() {
userMapper.deleteBatchIds(Arrays.asList(4, 5, 6));
userMapper.deleteById(2L);
}

公共字段填充

对于一些公用的字段,比如插入时间,更新时间等等。这些字段,mybatisplus对其也有非常简化的做法.

想使用填充呢必须先在实体类中的字段上添加注解@TableField

1
2
3
4
5
6
7
//----------------- 公共字段填充注解
// ==> DEFAULT 默认不处理
// ==> INSERT 插入填充字段
// ==> UPDATE 更新填充字段
// ==> INSERT_UPDATE 插入/更新填充字段
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime createDate;

然后呢定义一个公用字段的填充类就行了,但是这个类必须实现MetaObjectHandler 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.maoxs.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.maoxs.util.DateTimeUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

/**
* 填充器
* @author fulin
* @since 2018-09-12
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createDate", DateTimeUtils.getCurrentLocalDateTime());
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("createDate",DateTimeUtils.getCurrentLocalDateTime(), metaObject);
}
}

然后呢你的插入和更新就可以自动填充字段了

1
2
3
4
5
6
7
8
9
10
11
@Test
public void updateFill() {
userMapper.update(new User(), new UpdateWrapper<User>().lambda().set(User::getName, "付林").eq(User::getId, 4));
}
@Test
public void installFill() {
User user = new User();
user.setName("fulinlin");
user.setAge(12);
userMapper.insert(user);
}

序列

Sequence是数据库系统的特性,有的数据库实现了Sequence,有的则没有。比如Oracle、DB2、PostgreSQL数据库实现Sequence,MySQL、SQL Server、Sybase等数据库没有Sequence。那在mybatisplus里怎么设置呢?

mybatisplus 目前支持DB2KeyGenerator 、OracleKeyGenerator 、PostgreKeyGenerator 、H2KeyGenerator

想使用呢就需要把这些Generator 注入比如注入H2KeyGenerator

1
2
3
4
5
6
7
8
9
/**
* sequence主键,需要配置一个主键生成器
* 配合实体类注解 @KeySequence@TableId type=INPUT
* @return
*/
@Bean
public H2KeyGenerator h2KeyGenerator() {
return new H2KeyGenerator();
}

然后呢实体类中的@TableId 的 type的IdType 必须设置为INPUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.maoxs.base.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @author fulin
* @since 2018-09-24
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@KeySequence("SEQ_USER")
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
//----------------- 公共字段填充注解
// ==> AUTO 数据库ID自增
// ==> NONE 默认状态默认为ID_WORKER 全局唯一ID为空自动填充
// ==> INPUT 用户输入配合序列
// ==> ID_WORKER 分布式高效有序ID生产黑科技 全局唯一ID 为空自动填充
// ==> ID_WORKER_STR 分布式高效字符串ID生产黑科技 全局唯一ID 为空自动填充
// ==> UUID 这个大家都懂 全局唯一ID 为空自动填充
@TableId(value = "id", type = IdType.INPUT)
private Long id;
....
}

测试下看看就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testInsert() {
User user = new User();
user.setAge(18);
user.setName("sequence");
userMapper.insert(user);
Long id1 = user.getId();
System.out.println(id1);
Assert.assertTrue("sequence start with 1000", id1 >= 1000);
user = new User();
user.setAge(19);
user.setName("sequence2");
userMapper.insert(user);
Long id2 = user.getId();
Assert.assertTrue("squence increment by 1", id2 - id1 == 1);
}

枚举类型

通常呢我们在做业务的时候,喜欢用枚举定义常量,这样方便我们维护和管理。比如数据库中的一些字段是以 数字来定义状态的,这时候呢不做好注释,会给交接人员和新上手项目的人带来一定的不便,这时候枚举就是解决这种问题的好办法。同样呢,mybatisplus 对枚举有很好的支持。

想使用枚举呢,必须要让mybatisplus扫描到你的枚举类

1
2
mybatis-plus:
typeEnumsPackage: com.maoxs.enums

紧接着我们就来定义一个枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.maoxs.enums;
import com.baomidou.mybatisplus.core.enums.IEnum;

public enum UserTypeEnum {
Default(1, "普通用户"),
VIP(2, "会员"),
SUPER_VIP(3, "超级会员");
private int value;
private String desc;

UserTypeEnum(final int value, final String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return value;
}
}

除了这种定义扫描之外呢还可以实现IEnum 接口,申明自动注入为通用枚举转换处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.maoxs.enums;

import com.baomidou.mybatisplus.core.enums.IEnum;

public enum UserTypeEnum implements IEnum<Integer> {
Default(1, "普通用户"),
VIP(2, "会员"),
SUPER_VIP(3, "超级会员");
private int value;
private String desc;

UserTypeEnum(final int value, final String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return value;
}
}

在实体类中呢直接吧枚举当做类型即可

1
private UserTypeEnum UserType;

然后我们测试下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.maoxs.crud;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.maoxs.SpringbootMybatisplusApplicationTests;
import com.maoxs.base.entity.User;
import com.maoxs.base.mapper.UserMapper;
import com.maoxs.enums.UserTypeEnum;
import org.junit.Test;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

/**
* @program: plus
* @description: 枚举测试
* @author: fulin
* @create: 2018-09-24 13:00
**/
public class EnumTest extends SpringbootMybatisplusApplicationTests {
@Resource
private UserMapper userMapper;

@Test
public void insert() {
User user = new User();
user.setAge(11);
user.setName("快乐鱼");
user.setUserType(UserTypeEnum.Default);
userMapper.insert(user);
}

@Test
public void select() {
List<User> users = userMapper.selectList(new QueryWrapper<User>().eq("user_type", UserTypeEnum.Default));
System.out.println(users.get(0).getUserType().getValue());
}
}

这里需要注意的是,枚举类型不能运用lambda 来片接Wrapper

自定义全局操作

有时候我们想定义自己的全局方法,当然mybatisplus也提供了扩展,首先呢,定义一个类继承AbstractMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.maoxs.methods;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;

/**
* 删除全部
* @author fulin
* 2018/9/11 20:29.
*/
public class DeleteAll extends AbstractMethod {

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
/* 执行 SQL ,动态 SQL 参考类 SqlMethod */
String sql = "delete from " + tableInfo.getTableName();
/* mapper 接口方法名一致 */
String method = "deleteAll";
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
}
}

这里呢注入了一个deleteAll 的方法,用来删除表中所有的记录。然后呢,需要自定义sql注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.maoxs.injector;

import java.util.List;

import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.samples.injector.methods.DeleteAll;

/**
* 自定义Sql注入
* @author fulin
* 2018/8/11 20:23.
*/
@Component
public class MySqlInjector extends DefaultSqlInjector {

@Override
public List<AbstractMethod> getMethodList() {
List<AbstractMethod> methodList = super.getMethodList();
//增加自定义方法
methodList.add(new DeleteAll());
return methodList;
}
}

接下来怎么使用呢?只需要在接口中 定义

1
2
3
4
5
6
7
8
9
10
11
package com.maoxs.base.mapper;

import org.apache.ibatis.annotations.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.samples.injector.entity.Student;

@Mapper
public interface UserMapper extends BaseMapper<User> {
void deleteAll();
}

然后呢,贴个测试

1
2
3
4
@Test
public void test(){
userMapper.deleteAll();
}

乐观锁

当你在进行数据库操作的时候,当要更新一条记录的时候,希望这条记录没有被别人更新.这种情况经常用到。

一般乐观锁实现有这几种

  • 取出记录时,获取当前version

  • 更新时,带上这个version

  • 执行更新时, set version = newVersion where version = oldVersion

  • 如果version不对,就更新失败

但是在mybatisplus中乐观锁就很好处理,配置只需要两部,首先呢就是要注入OptimisticLockerInterceptor

1
2
3
4
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

注解字段中一定要加注解

1
2
@Version
private Integer version;

特别说明:

  • 支持的数据类型只有:int、Integer、long、Long、Date、Timestamp、LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

给个测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 乐观锁插件
*/
@Test
public void optimisticLockerTest() {
User user = new User();
user.setAge(11);
user.setName("hahha");
user.setVersion(1);
//AR 模式没有乐观锁形式
//employee.updateById();
//MP 模式才具有乐观锁模式,version修改
employeeMapper.updateById(employee);
}

这里说明下AR模式还没有乐观锁形势

多租户

可以这样理解,许多组织都将使用同一个应用程序;他们必须能够允许自己的用户访问应用程序,但是应用程序必须只允许每个组织自己的成员访问其组织的数据。

在查询的时候呢肯定要对其进行必要的筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 	 /**
* 多租户属于 SQL 解析部分,依赖 MP 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
/*
* 【测试多租户】 SQL 解析处理拦截器<br>
* 这里固定写成住户 1 实际情况你可以从cookie读取,因此数据看不到 【 麻花藤 】 这条记录( 注意观察 * SQL )<br>
*/
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId() {
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";//租户 ID
}
@Override
public boolean doTableFilter(String tableName) {
// 这里可以判断是否过滤表
/*if ("user".equals(tableName)) {
return true;
}*/
return false;
}
});

sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
// paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
// @Override
// public boolean doFilter(MetaObject metaObject) {
// MappedStatement ms = PluginUtils.getMappedStatement(metaObject);
// // 过滤自定义查询此时无租户信息约束【 麻花藤 】出现
// if ("com.maoxs.base.UserMapper.selectListBySQL".equals(ms.getId())) {
// return true;
// }
// return false;
// }
// });
return paginationInterceptor;
}

相关 SQL 解析如多租户可通过 @SqlParser(filter=true) 排除 SQL 解析,注意!!全局配置 sqlParserCache 设置为 true 才生效。yml配置如下

1
2
3
4
# 开启 SQL 解析缓存注解生效
mybatis-plus:
global-config:
sql-parser-cache: true

性能分析插件

这个插件呢就是 性能分析拦截器,用于输出每条 SQL 语句及其执行时间,只需要配置下就可以了,不建议生产使用

1
2
3
4
5
@Bean
public PerformanceInterceptor performanceInterceptor(){
//启用性能分析插件
return new PerformanceInterceptor();
}

需要注意下PerformanceInterceptor里面有两个参数

  • 参数:maxTime SQL 执行最大时长,超过自动停止运行,有助于发现问题。
  • 参数:format SQL SQL是否格式化,默认false。

Sql执行分析插件

这玩意就是吧一些具体的数据显示出来,适合开发使用。

1
2
3
4
5
6
7
8
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}

本博文是基于springboot2.x 如果有什么不对的请在下方留言。

相关连接:

个人博客地址 : www.fulinlin.com

csdn博客地址:https://blog.csdn.net/qq_32867467

集合源码地址 : https://gitee.com/Maoxs/springboot-test

交流群:826953936

个人qq: 1670245232

注:如果不对联系本宝宝及时改正~~