跳到主要内容

基于 Mybatis Plus 的数据持久化 API 总结

· 阅读需 9 分钟
程序员老熊

盘古开发框架 数据持久化 API 依赖 MyBatis Plus 提供。在 MyBatis 的基础上提供了强大的内置通用 Mapper CRUD 操作、支持 Lambda 表达式、内置屏蔽不通数据库方言差异的分页插件、自动填充字段值、多种主键自动生成策略、逻辑删除、乐观锁插件等。

快速 QA:可以支持兼容哪些数据库类型?
与 MyBatis Plus 一致。支持所有 JDBC 标准数据库。分页方言兼容如下数据库: mysql、oracle、db2、h2、hsql、sqlite、postgresql、sqlserver、phoenix、gauss、clickhouse、sybase、oceanbase、firebird、cubrid、goldilocks、csiidb、达梦数据库、虚谷数据库、人大金仓数据库、南大通用(华库)数据库、南大通用数据库、神通数据库、瀚高数据库。

安装相关盘古模块

<parent>
<groupId>com.gitee.pulanos.pangu</groupId>
<artifactId>pangu-parent</artifactId>
<version>latest.version.xxx</version>
<relativePath/>
</parent>

本地配置

为便于理解,本文基于本地配置的方式编写。若改为标准的Nacos配置中心模式,请参阅:配置中心章节。

spring.application.name=pangu-examples-crud

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/pangu-examples?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=root123456
mybatis-plus.mapperLocations=classpath*:/mapper/**/*.xml
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl
# 分页SQL方言数据库类型标识(缺省:自动识别)
pangu.jdbc.db-type=mysql

logging.level.root=INFO
logging.level.com.gitee.pulanos.pangu=INFO
logging.level.com.gitee.pulanos.pangu.showcases.crud.dao=debug

生成持久化所需的模版代码

基于实践经验,我们建议使用盘古代码生成器,而不是使用 Mybatis Plus 官方生成器。关于生成代码相关内容,请参阅 《代码生成器插件》 章节,这里不再赘述。(针对每一个数据表会生成一个 *Mapper.java 文件和一个 *Entity.java 文件)

基本数据持久化操作

这里只是给出几个简单典型的操作代码,更多内容请参阅范例或阅读 Mybatis Plus 官方文档

新增

UserEntity userEntity = new UserEntity();
userEntity.setName("XC").setAge(18).setUserType("1");
int row = userMapper.insert(userEntity);
log.info("成功插入{}条数据。{}", row, userEntity);

修改

//方式1
userMapper.updateById(new UserEntity().setId(1L).setName("XC2"));

//方式2
LambdaUpdateWrapper<UserEntity> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(UserEntity::getAge, 100);
updateWrapper.eq(UserEntity::getId, 2L);
userMapper.update(null, updateWrapper);
//方式2简写
userMapper.update(null, Wrappers.<UserEntity>lambdaUpdate().set(UserEntity::getName, "XC2").eq(UserEntity::getId, 3L));

//方式3
UserEntity userEntity = new UserEntity();
userEntity.setName("XC2");
userMapper.update(userEntity, Wrappers.<UserEntity>lambdaUpdate().eq(UserEntity::getId, 4L));

删除

//方式1
userMapper.deleteById(1000L);

//方式2
userMapper.deleteBatchIds(Arrays.asList(1000L, 1001L));

//方式3
userMapper.delete(Wrappers.<UserEntity>lambdaQuery().ge(UserEntity::getAge, 150));
userMapper.delete(Wrappers.lambdaQuery(UserEntity.class).ge(UserEntity::getAge, 150));

//方式4
userMapper.deleteById(new UserEntity().setId(2000L));

简单查询

//方式1
UserEntity userEntity = userMapper.selectById(1L);

//方式2
UserEntity userEntity1 = userMapper.selectOne(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getId, 1L));

//方式3 (需要注意对传入 ID 集合为非空判断,否则生成的 SQL in() 语法将报错)
if(CollUtil.isNotEmpty(userIds)){
userEntities = userMapper.selectBatchIds(userIds);
}

//方式4
LambdaQueryWrapper<UserEntity> lambdaQueryWrapper = Wrappers.lambdaQuery();
//动态组合查询条件的简便写法
lambdaQueryWrapper.between(ObjectUtil.isNotEmpty(age), UserEntity::getAge, 1, age);
lambdaQueryWrapper.eq(UserEntity::getUserType, "1");
lambdaQueryWrapper.orderByDesc(UserEntity::getId);
List<UserEntity> userEntities1 = userMapper.selectList(lambdaQueryWrapper);

//方式5
LambdaQueryWrapper<UserRoleEntity> lambdaQueryWrapper = Wrappers.lambdaQuery();
lambdaQueryWrapper.eq(UserRoleEntity::getUserId, userId).orderByDesc(UserRoleEntity::getRoleId);
List<UserRoleEntity> userRoleEntities = userRoleMapper.selectList(lambdaQueryWrapper);
List<Long> roleIds = userRoleEntities.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList());

//方式6
List<Map<String, Object>> userMaps = userMapper.selectMaps(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getUserType, "1"));

//方式7 count 查询
Long cnt = userMapper.selectCount(Wrappers.<UserEntity>lambdaQuery().le(UserEntity::getGmtCreate, DateUtil.date()));

//方式8 group 查询
QueryWrapper<UserEntity> queryWrapper1 = Wrappers.query();
queryWrapper1.select("age, count(id) as cnt").groupBy("age");
List<Map<String, Object>> mapList = userMapper.selectMaps(queryWrapper1);

//方式9 or 查询
if (ObjectUtil.isNotEmpty(keyword)){
lambdaQueryWrapper.and(w -> w.like( UserEntity::getName, keyword).or().like(UserEntity::getUserName, keyword));
}

//方式10 指定查询字段
LambdaQueryWrapper<UserEntity> lambdaQueryWrapper = Wrappers.lambdaQuery();
lambdaQueryWrapper.select(UserEntity::getId, UserEntity::getUserName);
lambdaQueryWrapper.eq(...);

//方式11 排除查询字段
lambdaQueryWrapper.select(UserEntity.class, info -> !info.getColumn().equals("password") && !info.getColumn().equals("password2"));

分页查询

盘古框架已经做了 Mybatis Plus 分页插件的自动装配工作,直接使用即可。无需额外配置。

基于 Mapper API 的分页查询

public void aPageQuery(){
log.info("MyBatisPlus API 分页查询数据...");
Page page = new Page<UserEntity>(1,3);
userMapper.selectPage(page, Wrappers.<UserEntity>lambdaQuery().ge(UserEntity::getAge, 10).orderByAsc(UserEntity::getId));
Console.log("总数:{}", page.getTotal());
List<UserEntity> userEntities = page.getRecords();
userEntities.forEach(System.out::println);
}

基于手工映射 SQL 的分页查询

继续阅读本文后面的 《手工编写SQL映射》 章节可以找到。

一个完整的分页查询例子

// 分页入参对象
public class RoleIn extends Page {
private String name;
}

// Controller
@GetMapping("/list")
public Result<PageResult<RoleEntity>> list(RoleIn roleIn) {
PageResult<RoleEntity> pageResult = roleService.list(roleIn);
return Result.success(pageResult);
}

// Service实现
public PageResult<RoleEntity> list(RoleIn roleIn) {
Page<RoleEntity> page = PagingUtil.createPage(roleIn);
LambdaQueryWrapper<RoleEntity> lambdaQueryWrapper = Wrappers.lambdaQuery();
lambdaQueryWrapper.like(ObjectUtil.isNotEmpty(roleIn.getName()), RoleEntity::getName, roleIn.getName());
roleMapper.selectPage(page, lambdaQueryWrapper);
return PagingUtil.getPageResult(page);
}

手工编写SQL映射

提示

一般来说在日常开发中我们是不需要再编写SQL代码的,Mapper API已经能满足我们的需求了。但对于一些特殊需求,我们也可以手工写SQL映射的方式来处理。如下范例演示了手工SQL查询的操作,新增、修改、删除也是同理。

public void bPageQuery(){
log.info("自定义SQL映射分页查询数据...");
Page page = new Page<UserEntity>(1,3);
Map<String, Object> params = Maps.newHashMap();
params.put("userType", "1");
List<UserEntity> userEntities = crudMapper.listUsersByPage(page, params);
Console.log("总数:{}", page.getTotal());
userEntities.forEach(System.out::println);
}

public void cPageQuery(){
log.info("自定义SQL映射分页查询数据...");
Page page = new Page<Map<String, Object>>(1,3);
String userType = "1";
List<Map<String, Object>> userMaps = crudMapper.listUserMapsByPage(page, userType);
Console.log("总数:{}", page.getTotal());
userMaps.forEach(System.out::println);
}

public void bSelect() {
log.info("自定义SQL映射查询数据...");
Map<String, Object> params = Maps.newHashMap();
params.put("userType", "1");
List<UserEntity> userEntities = crudMapper.listUsersByMap(params);
userEntities.forEach(System.out::println);
}
src/main/java/com/gitee/pulanos/pangu/showcases/crud/dao/mapper/CrudMapper.java
/**
* 提示:自定义sql手工映射大部分时候为复杂的多表联合查询的SQL,单表操作都应该统一使用mybatis plus的API
*/
@Mapper
public interface CrudMapper {

List<UserEntity> listUsersByPage(Page<UserEntity> page, Map<String, Object> param);

List<Map<String, Object>> listUserMapsByPage(Page<Map<String, Object>> page, String userType);

List<UserEntity> listUsersByMap(Map<String, Object> param);

}
src/main/resources/mapper/CrudMapper.xml
<mapper namespace="com.gitee.pulanos.pangu.showcases.crud.dao.mapper.CrudMapper">
<!-- 分页查询 -->
<select id="listUsersByPage" resultType="com.gitee.pulanos.pangu.showcases.crud.dao.entity.UserEntity">
select id,name,age,user_type,gmt_create,gmt_update from user
<where>
<if test="param.userType!=null and param.userType!=''">
user_type = #{param.userType}
</if>
</where>
ORDER BY id
</select>

<!-- 分页查询 -->
<select id="listUserMapsByPage" resultType="java.util.Map">
select age, count(id) as userCnt from user
<where>
<if test="userType!=null and userType!=''">
user_type = #{userType}
</if>
</where>
GROUP BY age
ORDER BY age DESC
</select>

<!-- 普通查询 -->
<select id="listUsersByMap" resultType="com.gitee.pulanos.pangu.showcases.crud.dao.entity.UserEntity">
select id,name,age,user_type,gmt_create,gmt_update from user
<where>
<if test="userType!=null and userType!=''">
user_type = #{userType}
</if>
</where>
</select>
</mapper>

高级特性

事务

单一数据源事务,直接使用 Spring 事务相关注解即可( @EnableTransactionManagement@Transactional )。对于分布式事务请参阅 《分布式事务》 章节。

逻辑删除

盘古框架借助 Mybatis Plus 对逻辑删除提供了非常友好的支持。通过如下简单配置即可实现从物理删除到逻辑删除在 CRUD 上面的无感体验。

# 指定逻辑删除的标识字段
mybatis-plus.global-config.db-config.logic-delete-field=deleted
# 删除前的缺省值
mybatis-plus.global-config.db-config.logic-not-delete-value=0
# 删除后的值
mybatis-plus.global-config.db-config.logic-delete-value=id

配置说明

  • 逻辑删除字段 deleted 建议通过数据库设置默认值 0。
  • 删除后的值建议不用 1,这会带来某字段需要唯一索引时的尴尬问题。因此建议将删除后的值更新为当前记录的 ID 字段值,如上配置所示。这时如果表中某字段需要建立唯一索引,则可以和 deleted 字段一起创建联合唯一索引。
  • 查询和更新操作,框架会自动拼接逻辑删除字段作为条件。(AND deleted = 0)

本文相关范例源码