Mybatis-Plus笔记

Mybatis-Plus笔记

  • Mybatis-Plus笔记
    • 一、框架简介
    • 二、原理结构
    • 三、通用接口
      • 3.1 BaseMapper接口
      • 3.2 IService接口
    • 四、常见注解
      • 4.1 @TableName
      • 4.2 @TableId
      • 4.3 @TableField
      • 4.4 @TableLogic
    • 五、条件构造器
      • 5.1 QueryWrapper
      • 5.2 UpdateWrapper
      • 5.3 LambdaQueryWrapper
      • 5.4 LambdaUpdateWrapper
    • 六、分页插件
      • 6.1 添加配置类
      • 6.2 测试
      • 6.3 自定义分页
    • 七、乐观锁
      • 7.1 场景
      • 7.2 乐观锁实现流程
    • 八、通用枚举


一、框架简介

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。mybatis-plus官方文档

二、原理结构

Mybatis-Plus原理:扫描实体类Entity,通过反射机制分析出需要操作的表名、字段名(实体类类名 >> 表名,实体类属性名 >> 字段名),生成相应的CRUD及其他常用的sql语句,注入到mybatis容器中。
Mybatis-Plus笔记_第1张图片

三、通用接口

3.1 BaseMapper接口

MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:

public interface BaseMapper<T> extends Mapper<T> {
	// 插入一条记录
	int insert(T entity);
	// 根据主键删除
	int deleteById(Serializable id);
	// 根据自定义条件删除 
	int deleteByMap(@Param("cm") Map<String, Object> columnMap);

	...
}

需要使用时只需要继承该接口,即可获得BaseMapper所有封装好的CRUD等方法。注意BaseMapper 泛型中必须指定实体类。

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

3.2 IService接口

MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑。封装方法介绍

通用 IService接口,进一步封装 CRUD 采用 get 查询单行、remove 删除、list 查询集合、page 分页,前缀命名方式区分 Mapper 层避免混淆。

使用时流程如下:注意实现类继承的ServiceImpl,T>,第一个为继承的mapper接口,第二个需指定为实体类。

// UserService继承IService模板提供的基础功能
public interface UserService extends IService<User> {
}
// 实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}

四、常见注解

4.1 @TableName

@TableName可以用来设置当前实体类映射的表名。详细介绍
当数据库所有表有统一的前缀 t_ 时,可以通过在配置文件中设置全局的表名前缀进行处理。

mybatis-plus:
  configuration:
   # 开启sql日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
    # 设置实体类映射数据库表,表名的统一前缀
      table-prefix: t_

4.2 @TableId

Mybatis-Plus默认会将id作为主键,当映射表不存在id字段或者实体类中不存在id属性时,会报错。
@TableId会将当前属性对应的字段作为主键。详细介绍
@TableId中有两个属性:value、type。

属性 类型 必须指定 默认值 描述
value String " " 主键字段名
type Enum IdType.NONE (无状态) 指定主键类型

当实体类主键属性名(id)和数据库表所映射的字段名(u_id)不一致时,可以通过value属性设置。

@Data
@TableName("t_user")
public class User {
	// 指定映射t_user表中的u_id主键字段
    @TableId("u_id")
    private Integer id;
    private String name;
}

默认生成主键的策略:IdType.NONE,未设置类型。修改生成策略用type属性,注意确保映射数据库表主键字段也要设置为自增。

@Data
@TableName("t_user")
public class User {
	// 指定映射t_user表中的u_id主键字段,设置生成的id策略为自增。
    @TableId(value = "u_id",type = IdType.AUTO )
    private Integer id;
    private String name;
}

全局统一配置主键生成策略

mybatis-plus:
  global-config:
    db-config:
    # 设置实体类映射数据库表,表名的统一前缀
      table-prefix: t_
      // 设置全局主键生成策略为:自增
      id-type: auto

4.3 @TableField

@TableField可以指定(非主键)普通属性对应的字段名。详细介绍
注意:@TableField中的exist属性可以指定是否为数据库表字段。

4.4 @TableLogic

@TableLogic表示逻辑删除,常用于数据恢复。需要指定映射表中存在 is_deleted字段,类型Int,默认值为0(0表示未删除,1表示已经逻辑删除)。当加上该注解后,所有的删除操作就变成了修改操作,且所有的查询操作都会加上逻辑是否删除的判断。

@TableLogic(value= " 原值 ",delval= " 改值 " )

@Data
@TableName("t_user")
public class User {
	// 指定映射t_user表中的u_id主键字段,设置生成的id策略为自增。
    @TableId(value = "u_id",type = IdType.AUTO )
    private Integer id;
    private String name;
    // 表示逻辑删除字段
    @TableLogic(value="0",delval="1")
    private Integer isDeleted;
}

五、条件构造器

AbstractWrapper的方法详解

  • Wrapper:条件构造抽象类,最顶端父类。
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件。
      • QueryWrapper : Entity 对象封装操作类,不是用lambda语法。
      • UpdateWrapper : Update 条件封装,用于Entity对象更新操作。
      • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
        • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper。
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper。
          Mybatis-Plus笔记_第2张图片

5.1 QueryWrapper

查询姓李、年龄在20到30之间、邮箱不为空的用户信息。注意:条件构造器方法中的column指的是数据库表的字段名,且可以使用链式结构,默认条件之间用 and 连接。

  @Autowired
  private UserMapper userMapper;
  @Test
  void test01() {
      // 查询姓李、年龄在20到30之间、邮箱不为空的用户信息。
      QueryWrapper<User> queryWrapper = new QueryWrapper<>();
      queryWrapper.likeRight("u_name","李")
              .between("age",20,30)
              .isNotNull("email");
      List<User> userList = userMapper.selectList(queryWrapper);
      for (User user : userList) {
          System.out.println(user);
      }
  }

查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序。

  @Autowired
  private UserMapper userMapper;
  @Test
  void test02(){
  	  // 查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序。
      QueryWrapper<User> queryWrapper = new QueryWrapper<>();
      queryWrapper.orderByDesc("age")
              .orderByAsc("id");
      for (User user : userMapper.selectList(queryWrapper)) {
          System.out.println(user);
      }
  }

删除邮箱为空的用户信息。

  @Autowired
  private UserMapper userMapper;
   @Test
   void test03(){
   		// 删除邮箱为空的用户信息。
       QueryWrapper<User> queryWrapper = new QueryWrapper<>();
       queryWrapper.isNotNull("email");
       int delete = userMapper.delete(queryWrapper);
       System.out.println(delete);
   }

将(年龄大于20并且名字中包含“小”字的)或者邮箱为null的用户信息修改。

  @Autowired
  private UserMapper userMapper;
  @Test
  void test04(){
      // 将年龄大于20并且名字中包含“小”字的或者邮箱为null的用户信息修改。
      QueryWrapper<User> queryWrapper = new QueryWrapper<>();
      queryWrapper.gt("age",20)
              .like("u_name","小")
              // 默认and连接,这里需要改为or 
              .or()
              .isNotNull("email");
      User user = new User();
      user.setName("李白");
      user.setEmail("[email protected]");
      // 第一个参数用来标明需要修改的内容,第二个参数用来标明参与修改的条件。
      int update = userMapper.update(user, queryWrapper);
      System.out.println(update);
  }

将(年龄大于20或者邮箱为空)并且名字中含有“小”的用户信息修改。注意优先级问题,可以上一题类比。

  @Autowired
  private UserMapper userMapper;
   @Test
   void test05(){
   		// 将(年龄大于20或者邮箱为空)并且名字中含有“小”的用户信息修改。
       QueryWrapper<User> queryWrapper = new QueryWrapper<>();
       queryWrapper.like("u_name","小")
       			// lambda中的条件会优先执行。
               .and(i->i.gt("age",20).or().isNotNull("email"));
       User user = new User();
       user.setName("李白");
       user.setEmail("[email protected]");
       int update = userMapper.update(user, queryWrapper);
       System.out.println(update);
   }

自定义需要查询的字段,并封装Map。

  @Autowired
  private UserMapper userMapper;
   @Test
   void test06(){
       QueryWrapper<User> queryWrapper = new QueryWrapper<>();
       // 只需要查询u_name、age、email字段
       queryWrapper.select("u_name", "age", "email");
       // 通过selectMaps可以封装查询的条件。
       List<Map<String, Object>> list = userMapper.selectMaps(queryWrapper);
       for (Map<String, Object> selectMap : list) {
           System.out.println(selectMap);
       }
   }

子查询语句。完整sql:select * from t_user where uid in (select uid from t_user where uid <= 100)

  @Test
  void test07(){
      QueryWrapper<User> queryWrapper = new QueryWrapper<>();
      // 子查询语句
      queryWrapper.inSql("uid","select uid from t_user where uid <= 100");
      List<User> userList = userMapper.selectList(queryWrapper);
      for (User user : userList) {
          System.out.println(user);
      }
  }

condition条件用例:
可以避免使用 if 语句判断,简单的 if 条件可以直接写在 queryWrapper 属性中。
查出名字带有“a”,年龄在ageBegin和ageEnd区间的用户信息。

    @Test
    void test09(){
        String username = "a";
        Integer ageBegin = null;
        Integer ageEnd = 30;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username),"u_name","a")
                .ge(ageBegin != null,"age",ageBegin)
                .le(ageEnd != null,"age",ageEnd);
        List<User> userList = userMapper.selectList(queryWrapper);
        for (User user : userList) {
            System.out.println(user);
        }
    }

5.2 UpdateWrapper

将(年龄大于20或者邮箱为空)并且名字中含有“小”的用户信息修改,注意与queryWrapper类比。

    @Test
    void test08(){
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("u_name","小")
                .and(i->i.gt("age",20).isNotNull("email"));
        UpdateWrapper<User> set = updateWrapper.set("u_name", "李白")
                .set("email", "[email protected]");
        System.out.println(set);
    }

只更新一个属性,把名字为小军的用户年龄更新为18,其他的属性保持不变。

 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
 updateWrapper.eq("name","小军").set("age",18);
 userMapper.update(null,updateWrapper);

5.3 LambdaQueryWrapper

LambdaQueryWrapper和QueryWrapper的区别在与
首先在结构关系上,LambdaQueryWrapper和LambdaUpdateWrapper继承自AbstractLambdaWrapper,而QueryWrapper和UpdateWrapper继承自AbstractWrapper。
其次在调用方法传参中,QueryWrapper和UpdateWrapper需要传入的是数据表中的字段名(column),是string类型。而LambdaQueryWrapper和LambdaUpdateWrapper则通过Lambda表达式的形式指定实体类中的属性名,是一个函数式的接口。这样设计的初衷在于使用Lambda表达式可以避免字段名写错或者对应不上数据库表的情况。


查出名字带有“a”,年龄在ageBegin和ageEnd区间的用户信息,使用LambdaQueryWrapper。

    @Test
    // 查出名字带有“a”,年龄在ageBegin和ageEnd区间的用户信息,使用LambdaQueryWrapper。
    void test10(){
        String username = "a";
        Integer ageBegin = null;
        Integer ageEnd = 30;
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isBlank(username),User::getName,username)
                .ge(ageBegin != null,User::getAge,ageBegin)
                .le(ageEnd != null,User::getAge,ageEnd);
        for (User user : userMapper.selectList(queryWrapper)) {
            System.out.println(user);
        }
    }

5.4 LambdaUpdateWrapper

将(年龄大于20或者邮箱为空)并且名字中含有“小”的用户信息修改,使用LambdaUpdateWrapper。

    @Test
    void test11(){
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.like(User::getName,"小")
                .and(i->i.gt(User::getAge,20).isNotNull(User::getEmail));
        LambdaUpdateWrapper<User> set = updateWrapper.set(User::getName, "李白")
                .set(User::getEmail, "[email protected]");
        System.out.println(set);
    }

六、分页插件

6.1 添加配置类

@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

6.2 测试

@Test 
public void testPage(){ 
	//设置分页参数 
	Page<User> page = new Page<>(1, 5); 
	userMapper.selectPage(page, null); 
	//获取分页数据 
	List<User> list = page.getRecords(); 
	list.forEach(System.out::println); 
	System.out.println("当前页:"+page.getCurrent()); 
	System.out.println("每页显示的条数:"+page.getSize()); 
	System.out.println("总记录数:"+page.getTotal()); 
	System.out.println("总页数:"+page.getPages()); 
	System.out.println("是否有上一页:"+page.hasPrevious()); 
	System.out.println("是否有下一页:"+page.hasNext()); 
}

测试结果:

User(id=1, name=Jone, age=18, email=test1@baomidou.com, isDeleted=0) User(id=2,
name=Jack, age=20, email=test2@baomidou.com, isDeleted=0) User(id=3, name=Tom,
age=28, email=test3@baomidou.com, isDeleted=0) User(id=4, name=Sandy, age=21,
email=test4@baomidou.com, isDeleted=0) User(id=5, name=Billie, age=24, email=test5@ba
omidou.com, isDeleted=0) 
当前页:1 
每页显示的条数:5 
总记录数:17 
总页数:4 
是否有上一页:false 
是否有下一页:true

6.3 自定义分页

UserMapper中定义接口方法

/**
* 根据年龄查询用户列表,分页显示 
* @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位 
* @param age 年龄 * @return 
**/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);

UserMapper.xml中编写SQL

<!--SQL片段,记录基础字段--> 
<sql id="BaseColumns">id,username,age,email</sql> 

<!--IPage<User> selectPageVo(Page<User> page, Integer age);--> 
<select id="selectPageVo" resultType="User"> 
	SELECT <include refid="BaseColumns"></include> FROM t_user WHERE age > # {age} 
</select>

测试

@Test 
public void testSelectPageVo(){ 
	//设置分页参数 
	Page<User> page = new Page<>(1, 5); 
	userMapper.selectPageVo(page, 20); 
	//获取分页数据 
	List<User> list = page.getRecords(); 
	list.forEach(System.out::println); 
	System.out.println("当前页:"+page.getCurrent()); 
	System.out.println("每页显示的条数:"+page.getSize()); 
	System.out.println("总记录数:"+page.getTotal()); 
	System.out.println("总页数:"+page.getPages()); 
	System.out.println("是否有上一页:"+page.hasPrevious()); 
	System.out.println("是否有下一页:"+page.hasNext()); 
}

结果

User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=null) User(id=4,
name=Sandy, age=21, email=test4@baomidou.com, isDeleted=null) User(id=5, name=Billie,
age=24, email=test5@baomidou.com, isDeleted=null) User(id=8, name=ybc1, age=21,
email=null, isDeleted=null) User(id=9, name=ybc2, age=22, email=null, isDeleted=null) 
当前页:1 
每页显示的条数:5 
总记录数:12 
总页数:3 
是否有上一页:false 
是否有下一页:true

七、乐观锁

7.1 场景

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。


上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。

7.2 乐观锁实现流程

乐观锁实现流程主要是通过在数据库中添加version字段,每次取出记录时都会获取当前的version,更新时version+1,如果where语句中的版本号不对,则更新失败。
1、首先在实体类上添加注解。

package com.atguigu.mybatisplus.entity; 
import com.baomidou.mybatisplus.annotation.Version; 
import lombok.Data; 

@Data 
public class Product { 
	private Long id; 
	private String name; 
	private Integer price; 
	// 乐观锁注解
	@Version 
	private Integer version; 
}

2、在配置类中添加乐观锁配置

@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 添加乐观锁插件
         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

八、通用枚举

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现。

1、@EnumValue 标识存储到数据库中的属性值。

package com.atguigu.mp.enums; 
import com.baomidou.mybatisplus.annotation.EnumValue; 
import lombok.Getter; 

@Getter 
public enum SexEnum { 
	MALE(1, "男"), FEMALE(2, "女"); 
	// 标识存储到数据库中的值
	@EnumValue 
	private Integer sex; 
	private String sexName; 
	
	SexEnum(Integer sex, String sexName) { 
		this.sex = sex; 
		this.sexName = sexName;
	} 
}

2、配置扫描通用枚举。

mybatis-plus: 
	configuration: 
	# 配置MyBatis日志 
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 
	global-config: 
		db-config: 
			# 配置MyBatis-Plus操作表的默认前缀 
			table-prefix: t_ 
			# 配置MyBatis-Plus的主键策略 
			id-type: auto 
		# 配置扫描通用枚举 
		type-enums-package: com.hupei.mybatisplus.enums

3、测试。

@Test 
public void testSexEnum(){ 
	User user = new User(); 
	user.setName("Enum"); 
	user.setAge(20); 
	//设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库 
	user.setSex(SexEnum.MALE); 
	//INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? ) 
	//Parameters: Enum(String), 20(Integer), 1(Integer) 
	userMapper.insert(user); 
}

你可能感兴趣的:(framework,mybatis,java,mysql)