【MP】MybatisPlus教程
mybatis-plus最精细入门到高级教程
文章目录
- 一、
- 二、使用步骤
- 1.创建mapper接口
- 2.mp的api
- 3.设置表的映射规则
- 4. 设置主键的生成策略
- 5. 设置字段和列名的驼峰映射
- 6. 开启日志
- 7. mp的分页查询
- 8. mp的service层接口
- 9. 代码生成器
- 10. mp的自动填充功能
- 11. mp的逻辑删除功能
- 12. mp的乐观锁插件
- 13. mybatis-plus的插件顺序
一、
1.mp依赖
2.数据库配置
二、使用步骤
1.创建mapper接口
有了mp之后,我们不用自己写mapper层中的那些单表操作方法,只需要写一个接口继承BaseMapper接口即可
/*我们需要写一个接口继承BaseMapper即可,你这个mapper是要操作哪个表,
你就在BaseMapper的泛型里指定这个表对应的实体类;
比如我现在这个mapper是要操作user表,我们写一个UserMapper继承BaseMapper,泛型写User就好;*/
public UserMapper extends BaseMapper<User>{
}
2.mp的api
1. selectList(Wrapper queryWrapper) 条件查询/查询所有
1. selectList(Wrapper<User> queryWrapper) List<User> 批量查询/按条件批量查询
注意点1:Wrapper<User>是一个条件构造器,让我们可以按照条件查询;如果我们传入一个null就表示是查询整张表
注意点2:Wrapper<User>中的泛型由你注入的哪个mapper决定,假如你注入的是UserMapper,而UserMapper又是继承的BaseMapper<User>,那么当你在使用userMapper.selectList(Wrapper<User> queryWrapper)时,Wrapper<User>泛型就默认是User了;
注意点3:我们可以传一个null进去 ,那么此时selectList(null)就是查询User这整张表的数据了;
2. insert() 插入数据
3. 删除数据
3.1 按照条件删除
delete(Wrapper<User> queryWrapper) int //返回值int
3.2 批量删除
deleteBatchIds(Collection<? extends Serialization> idList) int //返回值int
//案例
private UserMapper userMapper;
List<Integer> ids = new ArrayList<>();
ids.add(5);
ids.add(6);
ids.add(7);
int i = userMapper.deleteBatchIds(ids);
System.out.println(i)
3.3 通过id删除
deleteById(Serialization id) int //返回值int
int i = userMapper.deleteById(1);//按照id删除
3.4 通过Map删除
通过Map删除:什么意思呢?其实就是按照条件删除
deleteByMap(Map<String,Object> columnMap) int //返回值int
private UserMapper userMapper;
//这个方法需要一个Map,所以你必须先创建一个map
Map<String,Object> map = new HashMap<>();
map.put("name","许海");
map.put("age",28);
/* 注意:这里Map的泛型一定是<String,Object>,因为你要指定字段名,字段名肯定是String,后面的是字段的值,各个字段有不同的类型,所以要用Object */
int i = userMapper.deleteByMap(map);//意思就是将name等于许海,并且年龄等于28的user删掉;
4. 修改数据
修改操作有两个api:
4.1. 根据id修改
这样写就能达到一个根据id修改的目的:你必须要传一个user对象,并且这个user对象的id是有值的,否则修改不了;
updateById(User entity)
User user = new User();
user.setId(1L);
userr.setAge(18);
userMapper.updateById(user);
但是这种方式就不太灵活,因为只能更新user对象里id对应的那一行的数据,假如我要实现UPDATE user SET age = 99 WHERE id > 1这样一条语句,上面的语句就满足不了了,所以需要按照条件修改;
并且每次我都要new一个user出来,也很麻烦
4.2. 按照条件修改
update(User entity, Wrapper<User> updateWrapper) int
//如果把Wrapper<User> updateWrapper设置为null,就是没有条件
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.gt("id",1);
wrapper.set("age",99); //这里不能用wrapper.eq("age",99,因为eq是用来构建where条件的,我们这里是需要修改,要用set
questionMapper.update(null,wrapper); //这里本来要传一个User实体类对象,但是可以设置为null;
5. 条件构造器Wrapper
AbstractWrapper中提供了很多用于构造where条件的方法;
QueryWrapper,UpdateWrapper都继承于AbstractWrapper;
QueryWrapper额外提供了select方法
UpdateWrapper额外提供了set方法
6. AbstractWrapper
6.1 gt,lt,eq
sql语句如下:
SELECT
id,user_name,password,name,age,address
FROM
user
Where
age > 18 AND address = '重庆'
假如要用Wrapper实现上面的sql语句应该怎样书写呢?
@Autowired
private UserMapper userMapper;
@Test
public void testWrapper01(){
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age",18);
wrapper.eq("address","重庆");
userMapper.selectList(wrapper)//这里为什么要采用selectList方法呢?因为通过条件查询,很可能查出来是多个结果,所以要用selectList(Wrapper<User> queryWrapper);
}
6.2 select; 一定要注意,select跟selectList不是平级的,它跟eq,lt,gt这些是平级的
select有三种重载形式:
第一种:
select(String... columns)
现在有一个User表,里面有很多字段,我只想查询出user_name,password这两个字段,其他字段不查,就可以用select的第一种重载形式: select(String… columns)
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.select("user_name","password");
List<User> list = userMapper.selectList(wrapper );
第二种:
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
同样是这个User表,但是这个User表的字段实在是太多,我又不想把所有字段都查出来,我只想查除了age字段外的所有字段,如果是用上面的第一种重载形式select(String… columns) ,那么这么多字段我们很难写;所以可以用第二种重载形式;
QueryWrapper<User> wrapper = new QueryWrapper<User>();
# 首先要指定User.class,告诉Mp是操作哪一个类; 然后还要new Predicate<TableFieldInfo>要采用匿名内部类的写法,MP将User表的所有属性封装到了TableFieldInfo对象中,所以Predicate这个函数式接口的泛型是TableFieldInfo;
# !"age".equals(TableFieldInfo.getColunm()) 这段代码中,MP会将User表的所有字段拿出来跟"age"做对比,如果字段名不等于"age",test方法就返回为true,意思就是:只要是User表中名称不等于"age"的字段,我都给你查出来;
wrapper.select(User.class,tableFieldInfo->!"age".equals(TableFieldInfo.getColunm()));
List<User> list = userMapper.selectList(wrapper );
第三种:
select(Predicate<TableFieldInfo> predicate)
第三种跟第二种其实是一样的,都是达到过滤字段的目的,只不过入参少了一个User.class,就是不用在入参处执行domain类的字节码对象了;但是你必须在new QueryWrapper()时传一个User对象进去,否则mp也不知道你到底是操作哪个表,因为MP会根据User.class找到@TableName,然后再找到数据库中对应的表;
QueryWrapper<User> wrapper = new QueryWrapper<>(new User()); //这里就多写一个new User()
wrapper.select(tableFieldInfo->!"age".equals(TableFieldInfo.getColunm())); //这里就少写一个User.class
List<User> list = userMapper.selectList(wrapper );
7. lambda条件构造器
用旧的条件构造器存在的问题:
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age",18);
wrapper.eq("address","重庆");
userMapper.selectList(wrapper );
以上面这段代码为例,里面的"age","address"都是我们自己手写的字符串,我们手写是很可能出错的,并且只有在运行时才会报错,编译时是不会报错的;但是只要我们使用lambda条件构造器,如果出现这种问题,编译时就会报错,就更方便;
//使用lambda条件构造器,这里就要用LambdaQueryWrapper,同理还有LambdaUpdateWrapper;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
//这里就不再是使用wrapper.gt("age",18)了
//这里的gt里面的参数就变成了一个函数式接口,所以可以用这种方法引用的方式来写,getAge是要省略小括号的
//这里也一样表示age>18
wrapper.gt(User::getAge,18);
wrapper.eq(User::getAddress,"重庆");
userMapper.selectList(wrapper);
8. 自定义方法,自定义SQL语句
背景:mp的BaseMapper中已经为我们提供了很多单表操作的方法,但是在实际开发中,如果我们遇到了一个很复杂的场景,需要写一个很复杂的sql,有可能它提供的这些方法就不够用了,我们就需要自定义方法,自定义SQL;
如何操作?---->在UserMapper中添加我们自定义的方法
8.1 第一步:在mapper接口中添加自定义方法;
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findMyUser(Long id); //这个方法写的很简单,我只是用来当一个例子,你把它看做一个复杂场景下的方法即可;
}
8.2 第二步:在yml中指定mapper.xml的位置;
mybatis-plus:
mapper-location:classpath*:/mapper/**/*.xml
8.3 第三步:在resources目录下创建mapper.xml,
在UserMapper接口中,鼠标放到UserMapper上alt+回车,选择构造xml,然后我们再选择存放xml文件的位置,它就会自动帮我们创建好xml文件,但前提是我们有安装mybatis x插件;
然后再选中方法名,alt+回车,在xml映射文件中生成对应语句;
然后我们自己再像以前用mybatis那样,自己写sql就行了;
9. 自定义方法后如何结合Wrapper进行使用?
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findMyUser(Long id);
//入参就要用Wrapper<User> wrapper,并且要加注解:@Param(Constans.Wrapper),Constans.Wrapper是一个常量ew,ew代表wrapper对象;
User findMyUserByWrapper(@Param(Constans.Wrapper) Wrapper<User> wrapper);
}
然后你的xml就要这么写,
注意1:要用美元大括号,不能用警号大括号;
注意2:里面要用固定的ew.customSqlSegment,mp将你拼接好的条件全都封装到ew对象的customSqlSegment属性中去了;
注意3:加了${ew.customSqlSegment},后面就不用我们自己写where等等这些条件了,我们就可以用eq(),lt(),gt()等等这些方法来添加条件了;
<select id="findMyUserByWrapper" resultType="com.xuhai.domain.User">
select * from user ${ew.customSqlSegment}
</select>
最后你自定义的findMyUserByWrapper方法,就跟mp中自带的selectList差不多了,只不过你自定义的这个方法是返回的User对象;
写法如下
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age",18);
wrapper.eq("name","许海")
User user = userMapper.findMyUserByWrapper(wrapper );
3.设置表的映射规则
另外还需要注意:在实际的开发中,数据库表名跟实体类名很可能是不一致的,比如你的实体类是User,但是表名却是tb_user;此时,如果你还是写一个UserMapper继承BaseMapper,BaseMapper的泛型写User,那么MP就会默认到你的数据库中去找一个名为User的表,但是你数据库表名是tb_user,很显然是找不到的,所以就会报下图中的错:表user不存在;
3.1 单独设置表名 @TableName
为了解决表名跟实体类名不一致的问题:MP提供了一个注解@TableName
@TableName(“tb_user”)
public class User{
…
}
通过@TableName(“tb_user”)注解指定后,MP首先会通过BaseMapper中的泛型User找到User这个实体类,然后找到@TableName(“tb_user”),它会以这个注解里面的tb_user为表名到数据库中去查找;
3.2全局设置表名前缀
全局配置表名前缀的用途是:上面我们一个个的在实体类上加@TableName指定表名显得太麻烦了,当你的表们都是以同一个前缀开头时,你就可以在yaml中将这个前缀配置进去,以后MP在操作表时,就会自动把前缀加到BaseMapper里指定的泛型前面作为表名,在公司中表名为了规范命名,很可能是加了统一的前缀的,这个配置就有用处了;但是我个人感觉用处不大,不如我自己一个个单独设置来的准确;
4. 设置主键的生成策略
4.1 MP的默认主键生成策略
主键的生成策略有好几种:
- 比如如果你的主键是整数类型,直接就可以使用最普通的主键自增策略,但是这种方式在分布式的环境下会存在问题;
- 还可以使用基于UUID的主键生成策略,生成的就是随机的UUID,且是字符串类型,但是使用UUID作为主键也会有问题,因为数据库的索引需要进行排序才能提交查询效率,但是UUID是随机的,没有大小,就无法排序,所以使用UUID作为主键时,索引就会有问题,也不推荐使用;
- 在分布式的环境下,还可以使用基于雪花算法生成的自增id,这个比较好用,在分布式下表现良好,但是它生成出来id都比较长;如果你的项目是分布式,推荐使用这一种;
值得注意的是:MP中的主键自增策略,模式就是基于雪花算法生成的自增id;
看下面的代码作为验证:
//mapper接口
public interface UserMapper extends BaseMapper<User>{
}
//实体类User
@TableName("tb_user")
public class User{
/*在private Long id上面没有加@TableId(type = IdType.Auto)的话,
MP就是采用的默认的雪花算法生成id */
private Long id;
private String username;
private String password;
private String name;
private Integer age;
private Integer address;
}
//测试类
@SpringBootTest
public class test{
@Autowired
private UserMapper userMapper;
@Test
public void testInsert(){
User user = new User();
int i = userMapper.insert(user );
System.out.println(i);
}
}
在上面这段代码中,我们调用了MP API中的insert方法往tb_user表中插入了一条数据,当MP发现你插入进来的user是没有指定id时,它就会使用默认的雪花算法给我们生成一个id,而这个id非常长;就像下图所示;
4.2 单独设置主键自增策略
4.2.1@TableId(type = IdType.Auto)
如果我们的项目不是分布式,我们就想使用普通的主键自增策略,该如何操作呢?
@TableName("tb_user")
public class User{
@TableId(type = IdType.Auto) //这样设置后,主键就是普通自增;
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer address;
}
要注意:@TableId(type = IdType.Auto) 要依赖于数据库的自增,如果你数据库表的id没有设置成自增,那么你在实体类上加type = IdType.Auto也是无效的;
4.2.2 @TableId(type = IdType.None)
此时就是采用的默认的主键生成策略—即雪花算法生成;
4.3 全局设置主键生成策略
在上面的案例中,我们需要给每个表的主键都设置主键生成策略,这显得非常麻烦;
所以我们可以通过yaml配置的方式,设置一个全局的主键生成策略,这样设置后,我整个项目的主键生成策略都会使用我们yaml配置好的策略来;
即:下面的id-type:auto 这样指定后,全局就会使用普通的主键自增策略;
mybatis-plus:
global-config:
db-config:
# id生成策略,auto为数据库自增
id-type: auto
5. 设置字段和列名的驼峰映射
5.1注意事项
注意:在MP中,是默认开启了字段和列名的驼峰映射的;
@TableName("tb_user")
public class User{
@TableId(type = IdType.Auto) //这样设置后,主键就是普通自增;
private Long id;
private String userName; <<<<<<<-------------------------看这里
}
以实体类中的属性userName为例,MP根据BaseMapper中的泛型User找到User类后—》再根据@TableName找到表"tb_user"—》再得到User类的属性,根据驼峰映射,将userName映射成user_name字段; 当MP要去执行sql时,就会以user_name为字段去数据库中操作;
所以此时就必须要求你的数据库表中也必须有user_name这个字段;
如果你把数据库中的user_name改成了username,那么当你再去执行关于userName属性的sql时,就会报错"Unknow column ‘user_name’ in ‘field list’ "
5.2 关闭默认驼峰映射
在上面的情况中,你的数据库字段是username,实体类属性又是userName,此时MP映射就会出现问题,但是如果你公司中这些数据库表又设计好了的,你无法对表属性做修改,那么此时你就只有关闭MP的驼峰映射了;
mybatis-plus:
configuration:
#fasle就表示不开启自动驼峰映射,默认是true
map-underscore-to-camel-case: false
关闭驼峰映射后,MP就会拿实体类属性userName去拼接sql,你数据库字段是username,这两个大小写不一样没有关系,因为在写sql时实际上是不用考虑大小写的,所以能够解决上面的问题;
5.3 @TableField(“address_str”) 设置字段与属性之间的映射关系
当你的类属性是address,表字段是address_str,此时这两个单词都不同,是不能映射过去的;这会导致从数据库中查出字段address_str的数据了,没有办法把数据传递给实体类的address属性;
所以要通过MP来进行设置
@TableName("tb_user")
public class User{
@TableId(type = IdType.Auto)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
@TableField("address_str") //经过这样设置后,类属性address跟表字段addressStr就产生映射了
private Integer address;
}
加了@TableField(“address_str”)注解后,等到要拼接sql时,MP就会用address_str代替address去拼接sql,这样才不会报错;
6. 开启日志
如果你想看到MP执行的每句sql记录,你就可以开启日志,MP已经提供好了日志功能,实际上它的日志功能就是写了很多不同的实现类,我们采用不同的实现类,可以达到不同的日志效果;
比如最简单的StdOutImpl就是每次在执行sql后将sql语句打印在控制台;
mybatis-plus:
configuration:
# 加上这句配置后,才每次执行sql时,MP都会在控制台输出执行的sql语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
以下是MP的所有日志功能,我们可以根据自己的业务需求选择不同的日志实现类;
7. mp的分页查询
7.1 添加分页插件
如果要使用mp的分页功能,需要添加插件,其实这个插件就是个配置类:
这个配置类里面有两个配置,根据你的mp版本保留一个就可以了;
@Configuration
public class MybatisPlusConfig {
//如果你的mp是3.4.0以前的版本,你要用这种写法;
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setOverflow(false);
paginationInterceptor.setLimit(500);
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
//如果你的mp是3.4.0及以后的版本,你要用这种写法;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
7.2 分页查询的方法:selectPage
7.2.1 selectPage入参解析
selectPage(IPage<T> page, Wrapper<T> queryWrapper) IPage<T> , 返回值是IPage<T>
第一个参数IPage<T> page是用来定义你要分页查询的信息:比如每页显示多少条,第几页等;
IPage<T>这是一个mp提供的接口;
第二个参数queryWrapper是用来定义分页查询的条件;
7.2.2 如何查看一个接口的实现类?
IPage接口:我们要调用selectPage方法,肯定不能传一个接口进去,我们肯定要传IPage接口的实现类对象进去才合适;
我们该如何查看一个接口的实现类有哪些呢? 左键选中这个接口,然后右键—>选择Diagrams—>再选择show Diagrams Popup,Diagrams是图解的意思,我们就可以查看到当前这个接口的继承关系,注意,这一步不会显示它的实现类;
然后我们就能看到这样一个页面:我们能看到它实现了序列化这一个接口;
我们再左键点击选中IPage这个接口,右键选择show implementations,就能够查看到IPage这个接口的所有实现类了;
最终,我们能看到这个接口只有两个实现类;那我们在创建实现类时到底用哪一个呢?实际上用上面这个Page类就可以了;
7.2.3 使用selectPage方法
IPage<User> page = new Page<>();
//设置每页条数,这里就是每页只有2条数据
page.setSize(2);
//设置查询第几页,这里就是查询第一页
page.setCurrent(1);
IPage<User> page1 = userMapper.selectPage(page,null);//设置为null就表示没有查询条件
System.out.println(page.getRecords()); //获取查询出来的User集合
System.out.println(page.getCurrent());//获取当前页数
System.out.println(page.getTotal());//获取总条数
注意:实际上这里返回值page1对象就是上面new出来的page对象,它们的地址值都是一样的,不过page对象在经过selectPage方法后,mp把查出来的User集合,当前页,总条数 封装到了page对象的records属性,current属性,total属性中;所以我们一般在调用selectPage方法时,我们都不会用变量接收它;有page对象就够了;
7.3 多表分页查询
上面的分页查询并不是适应所有情况,当我们要进行联表查询再分页时,上面的分页查询就失效了;
举个例子,现在有一个需求,我们要去查询订单表,要求除了把订单表中的字段查出来,还要把每个订单的下单用户的用户也查出来;
因为要操作新的订单表,所以我们要新建一个OrderMapper,同时由于mp的BaseMapper中只提供了单表查询的方法,所以要实现联表查询,我们只能自定义方法;
public interface OrderMapper extends BaseMapper<Order>{
List<Order> findAllOrders(); //这就是我们自定义的查询所有订单,并且把user_name也查出来的方法;
}
<select id="findAllOrders" resultType="com.xuhai.domain.Order">
select o.* , u.user_name from t_order o left join t_user u on o.user_id = u.id
//或者写成select o.* , u.user_name from t_order o , t_user u where o.user_id = u.id 也可以
</select>
我们可以看到上面两部分实际上是没有做分页的,因为SQL语句里根本没有limit关键字;
所以要进一步调整;如何根据mp的习惯对上面的方法进行改造呢?
我们知道单表分页查询中有一个很关键的接口IPage,
只要我们给findAllOrders方法传入Page<Order> page作为入参,并且让方法的返回值类型是IPage<Order>,mp就会自动帮我们把查出来的数据封装到IPage<Order>返回值中,然后我们再根据getRecords(),getTotal(),getCurrent()来取就可以了;
那为什么不以List<Order>作为返回值呢?因为我们做分页时,不仅仅只是要一个Order的集合,还需要总条数与当前所处页数,前端才能做分页;
所以
List<Order> findAllOrders();就要被改造为下面这样:
public interface OrderMapper extends BaseMapper<Order>{
IPage<Order> findAllOrders(Page<Order> page); //这就是我们自定义的查询所有订单,并且把user_name也查出来的方法;
}
然后xml中的sql就不用管了,不需要给sql加上limit,mp会自动在sql后面加上limit
8. mp的service层接口
8.1 mp的service层写法
写一个接口继承IService接口,泛型填对应的实体类,IService中已经封装好了常用方法;
public interface UserService extends IService<User>{
]
再写一个类继承ServiceImpl类,ServiceImpl的第一个泛型是UserMapper,第二个泛型是实体类User,实现UserService;
@Service
public Class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{
]
8.2 ServiceImpl的api
查询所有:
List<T> list();
List<User> list = userService.list();
System.out.println(list);这样就查询出了user表中的所有记录;
按照条件查询:
List<T> list(Wrapper<T> queryWrapper);
插入:
批量插入:
Page<User> page = new Page<>();
page.setSize(2);
page.setCurrent(2);
userService.page(page); //注意,mapper层中是调用selectPage方法,service层是调用page方法;
System.out.println(page.getRecords());
8.3 自定义Service层的方法
同样的,实际开发过程中,IService中提供的方法并不能满足我们的要求,所以我们需要自己定义一些更加复杂的方法;
如何自定义Service层方法呢?实际上只需要在UserService中自己加方法就可以了,你想怎么加就怎么加;
比如现在我往UserService接口中添加了一个自定义的test方法,返回值是User;
public interface UserService extends IService<User>{
User test();
]
然后需要在UserServiceImpl 类中重写test方法;
并且假如我需要在UserServiceImpl 中用到userMapper这个对象,我不需要再通过@Autowired注入,mp自带的ServiceImpl类中已经封装好了一个getBaseMapper()方法,我们直接调用它,就能获取到userMapper对象;
我们自己写的UserServiceImpl 继承了ServiceImpl,所以可以直接用方法名调;
@Service
public Class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{
@Autowired
private OrderMapper orderMapper; //假如UserServiceImpl类中还要用到其他Mapper,就没有办法了,只能自己注入;因为只有ServiceImpl<UserMapper,User>泛型中的UserMapper可以通过getBaseMapper获取到UserMapper对象;
@Override
public User test(){
UserMapper userMapper = getBaseMapper();// 那这里mp怎么就知道返回值是UserMapper呢? 这是因为上面ServiceImpl的第一个泛型指定了是UserMapper,所以mp知道;
List<Order> list = orderMapper.selectList(null);
return null;
}
]
9. 代码生成器
mp提供了代码生成器,让我们可以一键生成实体类,Service,Controller等全套代码;使用方式如下:
9.1 添加依赖
<!--mybatisplus代码生成器的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--模板引擎的依赖-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
9.2 创建模板类
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
10. mp的自动填充功能
实际项目中的表,有很多像更新时间,创建时间,创建人,更新人这些字段,我们可以利用@TableField的fill属性来设置字段的自动填充,以便我们能更方便的更新字段;
第一步:加注解
比如我现在有一个Order的实体类,它有一个创建时间,更新时间
public class Order{
@TableField(fill = FieldFill.INSERT) //表示只有在执行插入操作的时候,mp都会对这个字段自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) //表示在执行插入或者更新操作的时候,mp都会对这个字段自动填充
private LocalDateTime updateTime;
}
上面这一步只是告诉mp,以上两个字段需要自动填充,以及在什么时候自动填充;但是mp还不知道到底怎么去填充,所以需要下一步:
第二步:创建一个类实现MetaObjectHandler,也就是自定义填充处理器,告诉mp填充规则;
@Component //最后还要将填充规则类写到注册成bean
public class MyMetaObjectHandler implements MetaObjectHandler{
@Override //这个方法的意思,就是在执行官插入操作时,要执行的填充规则
public void insertFill(MetaObject metaObject){
//这里填充规则基本上是一个固定写法,setFieldValByName意思是根据属性名称来设置属性值,这个方法前面用this.
//入参第一个参数:表示属性名,你要自动填充个哪一个属性,你这里就填哪一个属性名的名称;
//第二个参数:是你要把要更新的属性,填充成什么值;这里就是填充的一个当前时间;
//第三个参数:这个参数就是固定写法,就固定写metaObject就可以了;
//最后,由于updateTime,createTime在插入时都要自动填充,所以insertFill方法中要把两个都写进去
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
@Override //执行更新操作时,要执行的填充规则
public void updateFill(MetaObject metaObject){
//只有updateTime在执行更新操作时要自动填充,所以这里只写了updateTime的自动填充规则;
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}
经过上面的操作后,我们以后在插入或者更新操作时,mp就会自动帮我们更新时间了;
11. mp的逻辑删除功能
11.1 什么叫逻辑删除?
逻辑删除其实就是假删除,在今天的时代,我们基本是不会删除一条数据的,一般我们会加一个字段delete_log,delete_log为0表示这个行数据没有被删除,1表示这个行数据被删除了;这就是逻辑删除,不是真正的删除;
但是要做逻辑删除时,有一些比较不方便的地方,比如:我每次去查询时,肯定要加一个条件where delete_log = 0,因为我们只想只想查询出没有被删除的数据;另外在删除的时候,我执行的就不是delete,而是update操作;
如果让我们自己去写,虽然简单但是麻烦且没必要;
所以mp就已经帮我们提供好了这方面的功能;
11.2 mp中如何配置逻辑删除?
在yaml中配置即可
mybatis-plus:
golbal-config:
db-config:
logic-delete-field:delete_log #这里填全局逻辑删除的实体字段名,也就是说你在表中是用哪一个字段来表示逻辑删除的
logic-delete-value:1 #逻辑已删除的值(默认是1)
logic-not-delete-value:0 #逻辑未删除的值(默认是0)
另外需要注意:
如果你的mybatis-plus是在3.3.0以前,除了上面的yaml配置,你还需要在对应的属性上加上@TableLogic注解,也就是说你需要在对应实体类中的delete_log这个属性上加@TableLogic注解;
如果是在3.3.0及以后,就只需要配置yaml即可;
11.3 测试逻辑删除是否生效
先测试删除:
orderMapper.deleteById(12L);
我执行了上面这段代码后,我们查看下面控制台的输出日志,能看到mp实际上执行的是update语句,如果我们没有设置逻辑删除,那么mp就会执行delete语句;
再测试查询: 如果逻辑删除设置成功,那么我们在执行查询操作时,就不会把delete_log等于1的行查询出来;
orderMapper.selectList(null).stream().forEach(System.out::println);
然后我们能看到,mp在执行sql时,自动帮我们在最后面加上了 where delete_log = 0;显然设置已经生效;
12. mp的乐观锁插件
并发情况下,在操作数据库表时,我们需要保证对数据的操作不冲突,使用乐观锁就是解决这一问题的方式之一;
乐观锁其实就是在表中加入一个version字段,实际项目中这么多表,要让我们自己每个表都去维护version字段,这就显然比较麻烦;
mp就提供好了这个功能,这就是mp的乐观锁功能;
(这里有一点问题,我们项目中这么多表,我们还是需要手动给每个表都加上version字段的,mp没办法给我们加)
12.1 添加配置类
@Configuration
public class MybatisPlusConfig{
//如果你的mybatis-plus是在3.4.0版本以前就用这个
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimsticLockerInterceptor();
}
//如果你的mybatis-plus是在3.4.0版本及以后就用这个
@Bean
public MybatisPlusInterceptor optimisticLockerInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//前面的给mybatis-plus添加分页功能也是这么添加的,下面这句代码就是给mybatis-plus加分页功能;
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//这句代码就是给mybatis-plus添加乐观锁功能;
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInterceptor());
//注意:要先添加分页插件,再添加乐观锁插件,否则可能跟出现bug
return mybatisPlusInterceptor;
}
}
所以总结一下:在3.4.0版本以后的mybatis-plus中,分页功能,乐观锁功能是可以写在一个@Bean的方法中的,一个配置类就能搞定,不需要单独再写一个配置类;
在3.4.0版本以前的,分页功能,乐观锁功能可以写到同一个配置类中,都要用@Bean标注,但是要写成两个方法,因为它们的返回值不同;
12.2 添加@Version注解
给实体类的version属性加上@Version注解;
以order类为例;
public class Order{
//此处省略其他属性
@Version
public Integer version; version的类型是integer比较合适,因为从乐观锁的过程来看,它的值就是0或者其他正整数;
//此处省略其他属性
}
完成上面的操作后,mp的乐观锁操作即配置完成
12.3 测试乐观锁功能是否生效
。。。。这里省略
13. mybatis-plus的插件顺序
首先需要知道如何给mybatis-plus添加插件?
只需要将MybatisPlusInterceptor对象注册成bean,然后用MybatisPlusInterceptor对象调用addInnerInterceptor方法,将插件对象传入该方法中,最后返回添加好插件的MybatisPlusInterceptor对象即可;
但是需要注意:我们可能会给mybatis-plus添加多个插件,如果插件之间的顺序出错,可能会有意想不到的bug,建议使用以下的属性添加插件;
我也看看不懂这个顺序是什么意思,但是就目前的案例而言,你在添加分页插件与乐观锁插件时,你最好先添加分页插件,再添加乐观锁插件,否则可能出bug;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)