MyBatis Plus的使用


 

mybatis plus简介

mybatis的缺点

  • 未提供通用curd实现,不管多简单的crud都要自行写sql,影响开发效率
  • 未集成分页之类的常用功能,但mybatis支持插件 扩展性不错,可借助第三方插件实现

mybatis plus 在 mybatis 的基础上进行了大量的扩展,旨在简化 mybatis 开发

  • 优点:提供了通用mapper、service,简化了单表操作,集成了分页、 乐观锁等常用功能,可以提高 mybatis 开发效率。
  • 缺点:将很多持久层的批量操作方法封装在通用service中,对service层侵入严重。

官网:https://baomidou.com
github:https://github.com/baomidou/mybatis-plus

 

springboot整合mybatis plus

依赖
<!-- 已经包含了mybatis的依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <!-- 默认是mysql 8.x的驱动,可以用<version>指定mysql 5.x的驱动 -->
    <scope>runtime</scope>
</dependency>

 

yaml
mybatis-plus:
  #指定xml映射文件位置,多个时逗号分隔
  mapper-locations: classpath*:mapper/**.xml
  #实体类别名,有多个包时以英文逗号或分号隔开
  type-aliases-package: com.chy.mall.po
  configuration:
    #(字段名)下划线自动转驼峰,mp默认为true,mybatis默认为false
    map-underscore-to-camel-case: true
    #日志打印sql语句,常用的实现类:org.apache.ibatis.logging.slf4j.Slf4jImpl、org.apache.ibatis.logging.stdout.StdOutImpl
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  #全局默认配置
  global-config:
    db-config:
      #主键策略,默认为ASSIGN_ID通过雪花算法自动生成(支持数值、String)。@TableId未指定type时自动使用此处的全局配置。
      #常用的值:AUTO数据库自增、INPUT开发自行设置主键字段值。数据库自增、雪花算法生成 这些自动分配主键的,insert后会自动将主键值回填到po中
      id-type: AUTO
      #表名前缀,持久层实体类上未使用 @TableName 显式指定表名时,会自动转换为snake写法,并拼接指定前缀,eg. UserInfo => t_user_info
      #table-prefix: t_

 

引导类
@MapperScan(basePackages = { "com.chy.mall.mapper"})

 

持久层实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_user")  //显示指定表名
public class UserPo {

    //指定主键字段。value指定主键字段名称,缺省时默认取变量名的snake写法;type指定主键策略,缺省时默认使用全局配置
    @TableId(value = "user_id", type = IdType.AUTO)
    private Integer id;

    //显示指定对应的数据库字段。缺省时默认取变量名的snake写法(单词全小写,下划线连接)
    @TableField("user_name")
    private String userName;

    private String password;

    private String tel;

    //不映射此字段。缺省时默认会映射到数据表
    @TableField(exist = false)
    private boolean isNull;

}

 

dao层继承通用mapper
//泛型指定对应的po类
public interface UserMapper extends BaseMapper<UserPo> {

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.chy.mall.mapper.UserMapper">

</mapper>

 

service层继承通用service
//泛型指定对应的po类
public interface UserService extends IService<UserPo> {

}
@Service  //泛型指定对应的dao类、po类
public class UserServiceImpl extends ServiceImpl<UserMapper, UserPo> implements UserService {

}

 

通用mapper、service常用方法

通用mapper

userMapper.insert(userPo);
userMapper.updateById(userPo);
userMapper.deleteById(id);
userMapper.deleteBatchIds(ids);

//查询
UserPo userPo = userMapper.selectById(id);
List<UserPo> userPos = userMapper.selectBatchIds(ids);
Integer count = userMapper.selectCount(new QueryWrapper<>(userPo));
List<UserPo> userPos = userMapper.selectList(new QueryWrapper<>(userPo));
UserPo userPo1 = userMapper.selectOne(new QueryWrapper<>(userPo));

 

通用service

UserPo userPo = userService.getById(id);
//如果结果是多条,会直接抛异常
UserPo userPo = userService.getOne(new QueryWrapper<>(userPo));

List<UserPo> userPos = userService.list();
List<UserPo> userPos = userService.listByIds(ids);
List<UserPo> userPos = userService.list(new QueryWrapper<>(userPo));

int count = userService.count();
int count = userService.count(new QueryWrapper<>(userPo));


userService.save(userPo);
userService.updateById(userPo);
userService.saveOrUpdate(userPo);
userService.removeById(id);
userService.removeByIds(ids);


//批量操作,可指定单个批次处理数量,缺省时默认1000
userService.saveBatch(userPos, 1000);
userService.updateBatchById(userPos, 1000);
userService.saveOrUpdateBatch(userPos, 1000);

userService.saveBatch(userPos);
userService.updateBatchById(userPos);
userService.saveOrUpdateBatch(userPos);
  • 通用mapper:封装原子层面的dao操作,方法对应单条sql、没加事务。
  • 通用service:提供更高层面的封装,提供了批量添加/更新的方法。这也是 mybatis plus备受诟病的一点,把持久层的批量操作封装在service层,对service层侵入严重。mybatis plus 给通用service中批量添加/更新以及 saveOrUpdate() 方法上都标注了 @Transactional(rollbackFor = Exception.class),在事务中使用这些方法时,需要注意事务的传播行为。

 

typeHandler 自定义类型转换

除了可以在xml映射文件中使用mybatis的指定方式,也可以在po中使用mybatis plus提供的 @TableField 注解指定,insert、update时只要入参是对应的po,select时只要返回值是对应的po、po集合,mybatis plus就会自动拦截 应用指定的 typeHandler

@TableField(typeHandler = EncryptionTypeHandler.class)
private String password;

@TableField(typeHandler = EncryptionTypeHandler.class)
private String tel;

 

QueryWrapper、UpdateWrapper

QueryWrapper、UpdateWrapper 都是 AbstractWrapper 的子类,用于拼接sql语句。二者都可以拼接where条件,其中 UpdateWrapper 提供了set()方法,可以拼接update的set子句。习惯上:查用QueryWrapper,改UpdateWrapper ,删二者均可。

//可以直接new创建实例
QueryWrapper<UserPo> queryWrapper = new QueryWrapper<>();
UpdateWrapper<UserPo> updateWrapper = new UpdateWrapper<>();
QueryWrapper<UserPo> queryWrapper = new QueryWrapper<>(userPo);
UpdateWrapper<UserPo> updateWrapper = new UpdateWrapper<>(userPo);

//也可以通过Wrappers的静态方法创建实例
QueryWrapper<UserPo> queryWrapper = Wrappers.query();
UpdateWrapper<UserPo> updateWrapper = Wrappers.update();
//po参数会作为where条件拼接到sql中
QueryWrapper<UserPo> queryWrapper = new QueryWrapper<>(userPo)
        //也可以拼接其它where条件
        .eq("source", "xxx")
        .like("url", "%xxx%")
        .notLike("url", "%xxx%")
        .lt("create_time", "2010-01-01 00:00:00")
        .ge("create_time", "2020-01-01 00:00:00")
        .between("create_time", "2010-01-01 00:00:00", "2020-01-01 00:00:00")
        .in("status", 0, 1, 2)
        .notIn("status", 0, 1, 2)
        .isNull("source")
        .isNotNull("source")
        //拼接and、or,会把作为整体放到小括号()中
        .and(queryWrapper -> queryWrapper.like("username", "%xxx%").eq("status", 1))
		.or(queryWrapper -> queryWrapper.like("username", "%xxx%").eq("status", 1))
        //均可指定条件,满足条件时才拼接到sql语句中
        .eq(StringUtils.isNotBlank(username), "username", username)
        //可以拼接排序、分组
        .orderByDesc("id")
        .groupBy("status");

List<UserPo> userPos = userMapper.selectList(queryWrapper);
List<UserPo> userPos = userService.list(queryWrapper);
//po参数会作为where条件拼接到sql中
UpdateWrapper<UserPo> updateWrapper = new UpdateWrapper<>(userPo)
        //可以拼接其它where条件
        .eq("status", 1)
        //拼接set子句
        .set("statue", 2)
        //可以指定拼接条件,满足条件是才拼接
        .set(StringUtils.isNotBlank(username), "username", username);

userService.update(updateWrapper);

//po也可以直接作为update()参数,同样会被拼接到where中作为条件
userMapper.update(userPo, updateWrapper);
userService.update(userPo, updateWrapper);

 

说明
1、po参数作为where条件时,值不为null的字段(只判断是否为null,不判断是否为空串、空格串),才会以 = 等号形式拼接到where条件中。示例

  • 【/user/list?id=1】前端传递了id(不为null)、没传username(为null),拼接的sql是【where id=1】,
  • 【/user/list?id=1&username=】前端传递了id(不为null)、username(空串,不为null,也会被拼接到sql中),拼接的sql是 【where id=1 and username=‘’】
     

2、QueryWrapper、UpdateWrapper偏重量级,尽量减少使用

  • eq()、like()、notIn()、groupBy()、set()… 大量方法调用会拉低性能;
  • 字符串形式的数据表字段名散落在各层中,代码改动时不好查看受影响处,不好维护;
  • 使用字符串类型的值拼接sql,有sql注入的风险,即使要拼接sql,也不能直接使用系统外部传入的值。

对数据库记录的操作统一维护在dao层,数据库字段名统一维护在xml映射文件中。对于mb提供的通用mapper、service,避免使用eq()、like() 之类的方法拼接sql,尽量使用 new QueryWrapper<>(po)、updateById() 之类的通用方法,如果dao层没有现成合适的通用方法,或者sql比较复杂,可以在dao层自行抽取 listByQo(qo) 之类的自定义通用方法。

 

FieldStrategy 字段的插入、更新策略

com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig

private FieldStrategy insertStrategy = FieldStrategy.NOT_NULL;
private FieldStrategy updateStrategy = FieldStrategy.NOT_NULL;

mp默认的字段插入、更新策略是 NOT_NULL,只把值是 not null 的字段拼接到 insert、update 语句中,值是 null 字段不拼接进去。


插入:null 值拼不拼接到 insert 中,DB处理结果都一样,没影响。

  • DB字段没 not null 约束,拼不拼接到 insert 中保存的都是 null 值;
  • 有 not null default xxx,拼不拼接到 insert 中保存的都是默认值;
  • 只有 not null 没 default,null 值拼不拼接到 insert 中都是报错。

更新:不会更新为 null 值,非必需字段的置为 null 值不生效。

可能为 null 的非必填字段,如果DB原来有值,现在用户清空、删除了表单中此字段的值,提交给后端,mp默认更新策略是 NOT_NULL,不会把 值为 null 的字段拼接到 udpate 语句中,这个 null 值字段也就不会更新到 DB。


常见的 3 种解决方案

1、数据库实体类中,在要更新 null 值的字段上单独指定 updateStrategy (推荐)

@TableField(updateStrategy = FieldStrategy.IGNORED)
private Date birthday;

@TableField(updateStrategy = FieldStrategy.IGNORED)
private Integer height;

 

2、yml 中在 mp 的全局配置中指定更新策略(禁止)

mybatis-plus:
  global-config:
    db-config:
      update-strategy: ignored

全局配置影响范围大,更新操作 如果没给 PO 的某些字段赋值,null 值会直接覆盖掉这些字段原来的值,风险大,禁止这样做。
 

3、mp 的 update() 搭配 UpdateWrapper 进行更新,显式给要置为 null 的字段赋 null 值。(太麻烦,不推荐)

 

mp常用插件

配置插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //分页插件。单一数据库类型尽量指定DbType,避免每次分页时都根据数据库连接url推导
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    //乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

/**
 * 使用分页插件时,需要setUseDeprecatedExecutor(false),防止一、二级缓存出现问题
 * 后续useDeprecatedExecutor属性会随旧版本分页插件一起移除
 */
@Bean
public ConfigurationCustomizer configurationCustomizer() {
    return configuration -> configuration.setUseDeprecatedExecutor(false);
}

mp插件是以拦截器形式实现的,避免使用太多插件,防止影响性能。

 

分页插件 PaginationInnerInterceptor
//当前页码(从1开始,缺省默认为1)、每页记录数(缺省默认为10)、是否统计满足的记录总数(缺省默认为true)
//为true查询时先select count()查询满足条件的记录总数,填充total、pages字段,再查询满足条件的分页记录list,会多查询一次
Page<UserPo> userPage = new Page<>(1, 10);
// Page<UserPo> userPage = new Page<>(1, 10, true);


//执行分页查询,数据会自动填充到page实例中。没有查询条件时可缺省QueryWrapper
userMapper.selectPage(userPage, new QueryWrapper<>(userPo));
// userMapper.selectPage(userPage);
// Page<UserPo> userPage = userMapper.selectPage(new Page<>(1, 10), new QueryWrapper<>(userPo));


//满足条件的总记录数、总页数
long total = userPage.getTotal();
long pages = userPage.getPages();
List<UserPo> userPos = userPage.getRecords();

通用service分页和通用mapper分页差不多,只不过方法名是page()。

 

mybatis plus的分页 page必须要作为(第一个)参数传入,对dao层查询方法侵入大,对自定义通用查询加分页时,需要单独写一个分页查询,不像 pagehelper 对dao层代码零侵入,没 pagehelper 方便,mybatis plus官方不推荐使用 pagehelper 的分页

public interface UserDao extends BaseMapper<UserPo> {
	
	List<UserPo> listByQo(UserQo qo);
	
	Page<UserPo> page(Page<UserPo> page, @Param("qo") UserQo qo);
	
}

 

我个人更推荐使用 page helper 分页。注意:同时使用 mybatis plus、pagehelper 会存在依赖冲突,需要排除pagehelper中的mybatis、mybatis-spring

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.3</version>
    <!-- 解决与mybatis plus的依赖冲突 -->
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>

 

乐观锁插件 OptimisticLockerInnerInterceptor
//po version字段上标注@Version,推荐使用Integer、Long类型
@Version
private Long version;
UserPo oldUserPo = userService.getById(id);
UserPo updatePo = UserPo.builder()
        .id(id)
        .username(username)
        //update时需要携带原来的版本号
        .version(oldUserPo.getVersion())
        .build();
        
//版本号插件只支持 updateById(id)、update(entity,wrapper) 方法,其中update(entity,wrapper)的wrapper不能复用
//set version=旧版本号+1 where version=旧版本号,会自动更新version、将newVersion回填到updatePo中,版本号不匹配时受影响记录数为0,返回false
boolean result = userService.updateById(updatePo);
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值