【MP】MybatisPlus教程

文章目录


一、

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的默认主键生成策略

主键的生成策略有好几种:

  1. 比如如果你的主键是整数类型,直接就可以使用最普通的主键自增策略,但是这种方式在分布式的环境下会存在问题;
  2. 还可以使用基于UUID的主键生成策略,生成的就是随机的UUID,且是字符串类型,但是使用UUID作为主键也会有问题,因为数据库的索引需要进行排序才能提交查询效率,但是UUID是随机的,没有大小,就无法排序,所以使用UUID作为主键时,索引就会有问题,也不推荐使用;
  3. 在分布式的环境下,还可以使用基于雪花算法生成的自增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;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值