MyBatis Plus 快速入门笔记

499 阅读8分钟

如果你用过通用Mapper,那上手MyBatis Plus会很轻松

视频地址:www.bilibili.com/video/av532…

1 快速入门

1.1 快速开始

  1. 导入Maven依赖
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.2</version>
</dependency>
  1. 定义Mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface UserMapper extends BaseMapper<User> {
	
}
  1. 使用
@Test
void saveUser() {
	User user = new User();
    user.setId(5L);
    ...
    userMapper.insert(user);
}

@Test
void getById() {
	User user = userMapper.selectById(5L);
}

@Test
void getByIds() {
	List<User> users = userMapper.selectBatchIds(List.of(1L, 2L));
}

@Test
void updateById() {
    User user = new User();
    user.setId(5L);
    user.setBalance(600);
    userMapper.updateById(user);
}

@Test
void deleteById() {
	userMapper.deleteById(5L);
}

如果在启动类上加了@MapperScan,Mapper接口上就不用加@Mapper

不加@MapperScan,就需要加@Mapper

blog.csdn.net/weixin_4400…

1.2 常见注解

MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息

在没有使用注解的情况下,会默认使用如下规则映射到数据表上:

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰转下划线作为表的字段名
  • 根据变量类型推断数据库字段类型

@TableName

表名注解,标识实体类对应的表

@TableId

实体类主键字段

@TableField

字段注解(非主键)

1.3 常见配置

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如

mybatis-plus:
	type-aliases-package: com,itheima,mp,domain.po # 别名描包
	mapper-locations: "classpatht:/mapper/**/*,xml" # Mopper.xml文件地址,默认值
	global-config:
        db-config:
        id-type: auto # id为自增长
        update-strategy: not_null # 更新策略: 只更新非空字段
    configuration:
		map-underscore-to-camel-case: true # 是否开下划线和驼的映射
		cache-enabled: false # 是否开启二级缓存

2 核心功能

2.1 条件构造器

MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求

按条件查询:

QueryWrapper<User> wrapper = new QueryWrapper<User>()
    .select("id", "username", "info", "balance")
    .like("username", "o")
    .ge("balance", 1000);

List<User> users = userMapper.selectList(wrapper);

按条件更新:

User user = new User();
user.setBalance(2000);

QueryWrapper<User> wrapper = new QueryWrapper<User>()
    .eq("username", "jack"));

List<User> users = userMapper.update(user, wrapper);

特殊Set更新:

UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
    .setSql("balance = balance - 200")
    .in("id", List.of(1L, 2L, 4L));
userMapper.update(null, wrapper);

使用Lambda作为字段名:

Querywrapper<User> wrapper = new QueryWrapper<>();
wrapper.Lambda()
    .select(User::getId, User::getusername, User::getInfo, User::getBalance)
    .like(User::getusername, "o")
    .ge(User::getBalance, 1000);
List<User> users = userMapper.selectList(wrapper);

2.2 自定义SQL

可以使用Wrapper构建复杂查询,并作为参数传递给XML中作为SQL的条件使用,然后自定义剩下的SQL。

  1. 基于Wrapper构建Where语句
// 1.构建查询条件 where name like "%o%”AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<User>()
    .Like("username" ,"o")
    .ge("balance"1000);
// 2.查询数据
List<User> users = userMapper.findByCustom(wrapper);
  1. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
List<User> findByCustom(@Param("ew") QueryWrapper<User> wrapper);
  1. 自定义SQL,并使用Wrapper条件
<select id="findByCustom" resultType="user">
    SELECT * FROM user S{ew.customSqlSegment}
</select>

如果查询条件对应的表在SQL中有别名,可以通过${ew.customSqlSegment("a")}添加别名

案例:将id在指定范围的用户(例如1、2、4)的余额扣减指定值,不允许在service层出现SQL。

传统SQL写法:

<select id="updateBalanceByIds" resultType="com.itheima.mp.domain.po.User">
    UPDATE user
    SET balance = balance - #{amount}
    WHERE id
    <foreach collection="ids" separator="," item="id" open="IN (" close=")">
    	#{id}
    </foreach>
</select>

Wrapper写法:

List<Long> ids = List.of(1L, 2L, 4L);
// 1.定义条件
Querywrapper<User>wrapper = new Querywrapper<User>()
    .in("id", ids);
// 2.执行更新
userMapper.updateBalanceByWrapper(1000, wrapper);
public interface UserMapper extends BaseMapper<User> {
    @Update("UPDATE user SET balance = balance - #(amount) ${ew.customSqlSegment}")
    void updateBalanceByWrapper(@Param("amount") int amount, @Paren("ew") Querywrapper<User> wrapper);

案例:查询出所有收货地址在北京的并且用户id在指定范围的用户(例如1、2、4)的用户

传统SQL写法:

<select id="queryUserByIdAndAddr" resultType="com,itheima,mp,domain.po,User">
    SELECT *
    FROM user u
    INNER JOIN address a ON u.id = a.user_id
    WHERE u.id
    <foreach collection="ids" separator="," item="id" open="IN (" close=")">
             #[id}
    </foreach>
    AND a.city = #{city}
</select>

Wrapper写法:

List<Long> ids = List.of(1L, 2L, 4L);
Strng city = "北京";

// 1.定义条件
Querywrapper<User>wrapper = new Querywrapper<User>()
    .in("u.id", ids)
    .eq("a.city", city);

// 2.执行更新
userMapper.updateBalanceByWrapper(1000, wrapper);
<select id="queryUsersByWrapper" resultType="com.itheima.mp.domain.po.User">
    SELECT U.*
    FROM user u
    INNER JOIN address a on u.id = a.user_id
    ${ew.customSqlSegment}
</select>

2.3 IService

MyBatisPlus提供了一个IService接口,包含了常用CRUD、批量CRUD、分页操作,并提供了一个默认实现类。

为了满足编译检查,并能增加自定义方法的同时不用实现这些默认方法:

  • 自定义Service接口继承自IService

    public UserService extends IService<User> {}
    
  • 自定义Service实现先继承ServiceImpl,再实现自定义Service接口:

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    }
    

批量新增

userService.saveBatch(list);

在MySQL中,必须在JDBC连接字符串中添加&rewriteBatchedStatements=true,MySQL内部才会自动将批量提交的sql重写为insert多值插入再执行。(3.1.13以后的mysql连接驱动都支持该配置)

当然也可以使用BaseMapper中的InsertBatchSomeColumn批量插入(不走executeBatch批量提交sql,所以不受上述参数影响)。

Lambda查询

需求:基于IService中的lambdaQuery()实现一个方法,满足下列需求:

  • 查询名字为Rose的用户

    User user = userService.lambdaQuery()
    	.eq(User::getUsername, "Rose")
        .one();
    
  • 查询名字中包含o的用户

    List<User> list = userService.lambdaQuery()
        .like(User::getUsername, "o")
        .list()
    
  • 统计名字中包含o的用户的数量

    Long count = userService.lambdaQuery()
        .like(User::getUsername, "o")
        .count();
    
  • 定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空

    • 如果username参数不为空,则采用模糊查询
    • 如果status参数不为空,则采用精确匹配
    • 如果minBalance参数不为空,则余额必须大于minBalance
    • 如果maxBalance参数不为空,则余额必须小于maxBalance
    @Test
    void testQueryUsers() {
        List<User> list = queryUsers("o", 1, null, null);
        list.forEach(System.out::println);
    }
    public List<User> queryUsers(String username, Integer status, Long min, Long max){
        return userService.lambdaQuery()
            .like(username != null, ser::getUsername, username)
            .eq(status != null, User::getstatus, status)
            .gt(min != null, User::getBalance, min)
            .lt(max != null, User::getBalance, max)
            .list();
    }
    

Lambda更新

需求:基于IService中的lambdaUpdate()方法实现一个更新方法,满足下列需求:

  • 参数为balance、id、username
  • id或username至少一个不为空,根据id或username精确匹配用户
  • 将匹配到的用户余额修改为balance
  • 如果balance为0,则将用户status修改为冻结状态(2)
public void updateBalance(Long balance, Long id, String username) {
    // update user set balance = ? status = 2 WHERE id = ? AND username = ?
    userService.lambdaUpdate()
        .set(User::getBalance, balance)
        .set(balance == 0, User::getStatus, 2)
        .eq(id != null, User::getId, id)
        .eq( username != null, User::getUsername, username)
        .update();
}

2.4 静态工具

MyBatisPlus提供了Db工具类,提供了与IService中相同的所有静态方法,区别在于需要额外传入一个实体类Class。可以避免出现Service循环依赖注入的情况。

List<User> user = Db.lambdaQuery(User.class)
    .like(username != null, ser::getUsername, username)
        .eq(status != null, User::getstatus, status)
        .gt(min != null, User::getBalance, min)
        .lt(max != null, User::getBalance, max)
        .list();

3 拓展功能

3.1 代码生成

安装Idea插件MyBatisPlus

Idea菜单会多出一个Other菜单

  • Config Database:配置数据库连接
  • Code Generator:配置生成类

3.2 逻辑删除

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。

在application.yml中全局配置

mybatis-plus:
    global-config:
        db-config:
        logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
        logic-delete-value: 1 # 逻辑已删除值(默认为 1)
        Logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

3.3 枚举处理器

实体类字段类型可以设置为枚举类

public class User {
    ...
 	private UserStatus status;       
}

枚举类中,要在需要写入数据库的值字段上添加@EnumValue注解

@Getter
public enum UserStatus {
    NORMAL(1, "正常"),FREEZE(2, "陈结");
    @EnumValue
	private final int value;
    private final String desc;
    
	UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}

在application.yml配置中加上MybatisEnumTypeHandler

注意:从 3.5.2 开始无需配置

baomidou.com/pages/8390a…

mybatis-plus:
	configuration:
		default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

将枚举值传给前端的方法(转换枚举类成字符串还是枚举值):baomidou.com/pages/8390a…

3.4 JSON处理器

实体类字段类型可以设置为另一个,需要:

  • 在字段上增加@TableField(typeHandler = JacksonTypeHandler.class)注解指定Json处理器。
  • 在实体类上,将@TableName的属性autoResultMap指定为true(否则无法映射其他类的查询结果)
@TableName(value = "user", autoResultMap = true)
public class User {
    ...
    @TableField(typeHandler = JacksonTypeHandler.class)
 	private UserInfo info;       
}

3.5 配置加密

MyBatisPlus从3.3.2版本开始提供了一个基于AES算法的加密工具,帮助我们对配置中的敏感信息做加密处理。

@Test
void contextLoads() {
    // 1.生成16位随机 AES 秘钥
    String randomKey = AES.generateRandomKey();
    System.out.printn("randomKey = " + randomKey);
    // 2.利用密钥对用户名加密
    String username = AES.encrypt("root", randomKey);
    System.out.println("username = " + username);
    // 3.利用密钥对密码加密
    String password = AES,encrypt("MySQL123",randomKey);
    System.out.println("password = " + password);
}

在application.yaml文件中使用刚刚生成的秘钥,代替明文的用户名和密码

spring:
	datasource:
		url: jdbc:mysql://127.0.0.1:3306/mp
		driver-class-name: com.mysql.cj.jdbc.Driver
		username: mpw:QWWVnk10al3258x5rVhaeQ==
		password: mpw:EUFmeH3cNAzdRGdOQcabWg==

在项目启动的时候,添加AES的秘钥,这样MyBatisPlus就可以解密数据了

// Jar包启动参数,arguments
--mpw.key=d1104d7c3b616fob

单元测试时,可以通过@SpringBootTest来指定秘钥

@SpringBootTest(args = "--mpw.key=6234633a66fb399f")
class UserServiceTest {
	@Autowired
	UserService userService;
}

Idea中可以通过启动配置中的Program arguments来配置

4 插件功能

4.1 分页查询

MyBatisPlus基于MyBatis的Interceptor实现了一个基础拦截器,并在内部保存了MyBatisPlus的内置拦截器的集合

序号拦截器描述
1TenantLineInnerInterceptor多租户插件
2DynamicTableNameInnerInterceptor动态表名插件
3PaginationInnerInterceptor分页插件
4OptimisticLockerInnerInterceptor乐观锁插件
5IllegalSQLInnerInterceptorSQL性能规范插件,检测并拦截垃圾SQL
6BlockAttackInnerInterceptor防止全表更新和删除的插件

首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件

@Configuration
public class MybatisConfig [
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 1初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 2,添加分页插件
        PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        pageInterceptor.setMaxLimit(1000L); // 设置分页上限
        interceptor.addInnerInterceptor(pageInterceptor);
        return interceptor;
    }
}
  • setMaxLimit:设置分页上限

  • setOverflow:如果查到最后一页,则自动回到第一页

接着,就可以使用分页的API了

@Test
void testPageQuery() {
    // 1.查询
    int pageNo = 1, pageSize = 5;
    // 1.1.分页参数
    Page<User> page = Page.of(pageNo, pageSize);
    // 1.2.排序参数,通过OrderItem来指定
    page.addOrder(new OrderItem("balance", false));
    // 1.3.分页查询
    Page<User> p = userService.page(page);
    // 2.总条数
    System.out.println("total = " + p.getTotal());
    // 3.总页数
    System.out.println("pages = " + .getPages());
    // 4.分页数据
    List<User> records = p.getRecords();
    records.forEach(System.out::println);
}

查询结果的Page和查询参数的Page是同一个对象