mybatis-plus
一、简单介绍
直接查看官网来进行查阅资料。
https://baomidou.com/guide/
继续向下,可以看到对应的信息
二、快速入门进行操作
2.1、准备阶段
查看官方文档的快速入门操作,但是根据表设计来说,必须要添加必要的字段。version、create_time、update_time、deleted
create table `user` (
`id` bigint (20) comment '主键',
`name` varchar (90) comment '用户姓名',
`age` int (11) comment '用户年龄',
`email` varchar (150) comment '用户邮箱',
`version` int (11) default 1 comment '乐观锁',
`create_time` datetime comment '记录创建时间',
`update_time` datetime comment '记录的更新时间',
`deleted` int (11) default 0 comment '物理删除还是逻辑删除'
);
参考官方文档:创建springboot项目创建项目,创建完成之后,需要需要添加坐标。我这里还需要导入的是mysql驱动作坐标和lombok以及生成代码引擎
<!--一定要注意版本问题-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
// mysql驱动类用于连接mysql
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
// mybatisplus生成器坐标需要导入
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.0</version>
</dependency>
// Lombok用于给我们的pojo类来自动生成方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
// 需要使用到引擎来进行驱动
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.1</version>
</dependency>
既然使用到了MySQL来创建表,那么必须要在配置文件中进行配置了
2.2、配置文件
# 配置数据库源
# mysql的配置信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://数据库所在主机的ip地址:3306/mybatis_plus? &characterEncoding=utf8&useSSL=false&useUnicode=true
username: root
password: 密码
# 重要的信息!!!!!因为在没有写SQL语句的时候也是具备的,那么可以查看到SQL语句是怎么进行书写的。
# 配置日志信息,打印在控制台,想看下对应的SQL是如何来进行拼写的。
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.2、代码生成
接下来不需要再跟自己去建立controller、service、mapper等等信息了,而是根据代码生成器来自动进行生成。
参考官方文档中的代码生成器代码来进行操作。根据我的习惯,我会去掉一些,然后加上一些。然后作为以后自己操作的一份模板。
代码未生成之前的目录结构:
//需要构建一个 代码自动生成器 对象
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
//配置策略
//1、全局配置
GlobalConfig gc = new GlobalConfig();
// 自动找到当前项目的文职
String projectPath = System.getProperty("user.dir");
// 自动生成后的代码放在这个目录下
gc.setOutputDir(projectPath + "/src/main/java");
// 作者设置成自己的名字
gc.setAuthor("lg");
// 代码生成之后,是否需要打开资源管理器目录,进行显示。这里是false
gc.setOpen(false);
// 生成后的代码是否要将原来的进行覆盖,这里选择为false
gc.setFileOverride(false); //是否覆盖
gc.setServiceName("%sService"); //去Service的I前缀,同时会自动生成实现类
// 主键自增策略,选择这种操作来进行执行
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
mpg.setGlobalConfig(gc);
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
// 指定数据库,后面指定的是数据库的信息配置,现在使用这个即可
dsc.setUrl("jdbc:mysql://放置数据库的主机的ip地址:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
// 数据库驱动。现在mysql8都使用这种并且还是向下兼容,即使是mysql5.x的版本也可以来进行使用
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("账号");
dsc.setPassword("密码");
// 数据库类型!
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3、包的配置
PackageConfig pc = new PackageConfig();
// 创建后的代码放置的目录
pc.setModuleName("guang");
// 这里是将创建后的目录guang放置在com下面
pc.setParent("com");
// 实体类的位置。com.guang.entity
pc.setEntity("entity");
// mapper接口放置的目录。com.guang.mapper
pc.setMapper("mapper");
// service放置的目录。com.guang.service
pc.setService("service");
// controller放置的目录。com.guang.controller
pc.setController("controller");
mpg.setPackageInfo(pc);
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
// 对应的是哪张表,这里使用的是单表,如果要同时创建多张表,使用下面这种方式
// strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setInclude("user"); //设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 需要在maven坐标中引入lombok坐标
strategy.setEntityLombokModel(true); //自动lombok
// 逻辑删除字段设置。这里需要进行额外说明。会在代码中使用到的时候进行讲解
strategy.setLogicDeleteFieldName("deleted");
//自动填充配置。在进行添加和更新的时候,记录的添加时间和更新时间不需要我们手动的用代码来进行操作了
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
// 以下这两个类需要自己手动的来记性创建
// 这里设置了之后,所有代码生成的controller类都会自动的去继承这个BaseController类
strategy.setSuperControllerClass("com.guang.controller.BaseController");
// 这里设置了之后,所有的实体类都会默认的继承这个实体类。
strategy.setSuperEntityClass("com.guang.entity.BaseEntity");
//乐观锁的配置。之前上面提到的数据库必备字段全部设置完成
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute(); //执行代码构造器
代码生成后的目录
在mapper目录下的接口上加上@Reposity或者是@Mapper注解,代表的是mapper层的对象,注入到容器中去。
自此,我们就可以在此基础之上进行编码了。
2.4、代码生成之后需要做的事情
因为我前面配置了几个额外的字段:create_time、update_time、version和deleted。但是在进行操作的时候,我让代码来为我对这些字段来进行操作,而不需要我手动的来进行操作。
2.4、创建时间和更新时间的操作
去官网查看下自动填充功能,因为我现在要做的事情就是我在写代码的时候不想为了这两个字段手动操作,我让代码给我的字段来进行填充值。
原理:
实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
# 这一步已经做到了
注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置!
那么写个自定的类来实现这个接口,并且重写其中的方法
// 将这个类添加到容器中去
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时候需要进行更新时间
* @param metaObject:元数据对象
*/
@Override
public void insertFill(MetaObject metaObject) {
// 根据名字来设置字段以及对应的值
System.out.println("-------------每次插入的时候都会执行这个方法------------");
this.setFieldValByName("crete_time",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
/**
* 更新的时候需要更新时间。对于更新来说,插入时间需要保持不变,但是更新时间是可以进行改变的
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
System.out.println("-------------------每次更新的时候都将会走这个方法---------------------");
this.setFieldValByName("update_time",new Date(),metaObject);
}
}
2.5、为version配置
version代表的是数据库中的乐观锁。查看官方文档中的介绍:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
1、取出记录时,获取当前version
2、更新时,带上这个version
3、执行更新时, set version = newVersion where version = oldVersion
4、如果version不对,就更新失败
那么既然使用了mybatis-plus之后,这种代码为我们自动的来进行生成的。
使用方法
字段上加上@Version
注解,这个代码生成的时候已经自动进行生成了!
@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
不能复用!!!
乐观锁存在的意义就是在多线程环境下,放置对一条记录进行操作。操作完成之后,造成了错误的记录。那么在这种情况下,只有一个对象能够操作成功,并且给另外一个对象提示,进行报错处理。这种方式推荐使用。
既然创建时间和更新时间进行配置了,那么乐观锁也需要去进行配置。官方文档说明:
需要创建OptimisticLockerInnerInterceptor的bean来进行配置,但是官方文档写的不对,根本找不到这个。查阅资料发现,正确的应该是OptimisticLockerInterceptor,也就是把其中的inner给去掉了。
创建组件,也就是配置类来进行使用。
@Configuration
public class MyOptimisticLockerInterceptor {
/**
* 使用默认的即可
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInnerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
2.6为逻辑删除进行配置
参考官方文档说明
说明:
只对自动注入的sql起效:
- 插入: 不作限制
- 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
) - 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
使用方法:
在springboot的配置文件中进行声明:
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
所以配置文件中完成的配置信息是:
# mysql的配置信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://安装了MySQL的主机IP:3306/数据库名?&characterEncoding=utf8&useSSL=false&useUnicode=true
username: root
password: root
# 配置日志信息,打印在控制台,想看下对应的SQL是如何来进行拼写的。
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
那么还需要在上面创建的配置类中来进行配置
@Configuration
// 这个别忘记了!因为我的mapper接口是配置在这个目录下的
@MapperScan("com.guang.mapper")
public class MyOptimisticLockerInterceptor {
/**
* 使用默认的即可
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInnerInterceptor(){
return new OptimisticLockerInterceptor();
}
/**
* ISqlInjector是一个接口,查看实现类。
* @return
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
准备过程结束
三、编码阶段
3.1、插入操作
此时数据库中没有任何的数据,所以这里手动的进行插入操作。
我之前想的是直接将官网中的数据进行插入,但是发现,创建时间和更新时间都没有生成。转头想了想,我是在代码中进行配置的,手动在MySQL中进行插入,但是代码中配置的对添加操作和更新操作的操作没有更新。所以这里采用手动更新。
对于单表的CRUD,mybatis-plus已经帮助我们写好了。具体的可以在mapper层继承的接口中可以查看到,所以我们直接进行测试即可!
对于泛型,我将会在之后来进行更新。
在springboot自带的测试类中添加一条记录:
@Test
public void testInsert(){
User user = new User();
// id我在进行配置的时候,采用的是全局唯一ID。其实也是可以来进行配置的
// 默认的是使用全局唯一ID,有兴趣可以去测试下
// 如果使用的是MySQL自增策略,那么需要注意的地方有两个。1、数据库自增字段进行勾选;2、将字段进行勾选
user.setName("Jone");
user.setAge(18);
user.setEmail("11111@qq.com");
userMapper.insert(user);
// 这里不需要像mybatis进行手动的提交了,只需要自动提交即可
}
查看控制台日志显示:
这里显示的是否已经成功以及对应的SQL语句!!!!!这里的SQL语句是mybatis-plus自动进行生成的!
我们利用这个可看到下面几个更加精准的操作。
查看数据库中的数据:
可以看到配置的create_time数据字段成功了!
那么接下来测试下update_time和version和deleted来测试下,分别进行更新和删除以及更新操作
3.2、更新操作
@Test
public void testUpdate(){
User user = new User();
user.setId(1375802277113163777L);
user.setName("tom");
user.setEmail("2222222");
// 这个方法传递的是更新方法。
// 注意更新的时候使用的是
int i = userMapper.updateById(user);
System.out.println(i);
}
查看控制台日志信息
这里只有更新时间,并没有插入的时间。
可以看到SQL语句后面拼接的是deleted=0,是将并没有进行删除的数据查询了出来。
可以看到更新时间是不是更新了,这里可以看到,那么去数据库中查看创建时间和更新时间
数据库中信息显示:
可以看到创建这条记录的时间没有更新,只有更新时间进行了更新。乐观锁和逻辑删除并没有改变。
补充乐观锁操作
可以看到乐观锁的使用范围。多个线程对同一条数据来进行操作的时候。
利用java代码来模拟多线程操作场景
@Test
public void testVersion(){
// 模拟线程1
User user1 = userMapper.selectById(1375802277113163777L);
System.out.println(user1);
user1.setName("1111");
user1.setAge(1111);
user1.setEmail("1111@qq.com");
// 模拟线程2
User user2 = userMapper.selectById(1375802277113163777L);
System.out.println(user2);
user2.setName("2222");
user2.setAge(2222);
user2.setEmail("2222@qq.com");
// 总结:在查询的时候每个线程都会得到当前的version对应的值
// 在进行更新的时候,都会将自己和数据库最新版本的version对应的值来进行对比。如果相同,更新成功;如果不同,那么更新失败
// 这里先进行更新,那么会将数据库中的version=1修改为version=2
int i = userMapper.updateById(user1);
System.out.println(i);
// 当这里进行更新的时候,当前user2中的版本号是version=1,但是数据库中的已经是version=2了,那么version不相等,所以更新失败
int y = userMapper.updateById(user2);
System.out.println(y);
// 那么线程2争抢失败,最终数据库中更新的是user1设置的值
}
控制台显示:
太长!无法进行显示,这里不在进行补充了!
数据库显示:
可以看到这里更新的和我分析的是一样的。
3.3、删除操作
这里是为了测试逻辑删除操作
@Test
public void testDelete(){
int i = userMapper.deleteById(1375802277113163777L);
System.out.println(i);
}
查看控制台日志输出信息:
有没有发现了什么!!!
这里执行的不是delete操作,而是更新操作!!!!!!
那么是否是真的删除了?
进行查询操作
@Test
public void testSelect(){
User user = userMapper.selectById(1375802277113163777L);
System.out.println(user);
}
控制台显示:
没有数据显示
数据库显示:
数据库中是可以查看到数据!!!!!!!!
说明了查询的时候携带的是deleted=0去进行查询的,而逻辑删除后,这个deleted的字段的值设置成了1,自然也就无法来进行查询!
这点是非常有意思的。
3.4、查询操作
无论是在数据库中还是mybatis以及mybatis-plus中,查询操作都是非常丰富的。mybatis-plus给我们生成的都是单表中的查询,那么复杂的还需要自己来进行书写SQL语句进行拼接!
查询的话直接参考官方文档。比较简单。主要针对的是对wrapper的封装和操作。
3.5、分页进行查询
直接参考官方文档
//Spring boot方式
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
// 旧版
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
那么还是配置在我自定义的config里面去
@Configuration
@MapperScan("com.guang.mapper")
public class MyOptimisticLockerInterceptor {
/**
* 使用默认的即可
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInnerInterceptor(){
return new OptimisticLockerInterceptor();
}
/**
* ISqlInjector是一个接口,查看实现类。
* @return
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
/**
* 分页插件直接进行返回,不用其他的配置
* @return
*/
@Bean
public PaginationInterceptor mybatisPlusInterceptor() {
PaginationInterceptor interceptor = new PaginationInterceptor ();
return interceptor;
}
}
测试代码:
/**
* 测试分页的使用
*/
@Test
public void testPage(){
Page<User> userPage = new Page<>();
// 第2页
userPage.setCurrent(2L);
// 每页显示两条数据
userPage.setSize(2);
IPage<User> userIPage = userMapper.selectPage(userPage, null);
for (User record : userIPage.getRecords()) {
System.out.println(record);
}
}
控制台输出:
可以查看输出语句和对应的SQL语句
总结:
mybatis-plus帮助我们做了很多我们不需要去做的事情,让我们更加注重于业务逻辑的编写,减少简单代码的编写,而不是浪费时间在简单SQL编写的工作上。