盘古开发框架 数据持久化 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
- 基础模块
- JDBC 模块
<parent>
<groupId>com.gitee.pulanos.pangu</groupId>
<artifactId>pangu-parent</artifactId>
<version>latest.version.xxx</version>
<relativePath/>
</parent>
<dependency>
<groupId>com.gitee.pulanos.pangu</groupId>
<artifactId>pangu-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.gitee.pulanos.pangu</groupId>
<artifactId>pangu-framework-jdbc-spring-boot-starter</artifactId>
</dependency>
本地配置
为便于理解,本文基于本地配置的方式编写。若改为标准的Nacos配置中心模式,请参阅:配置中心章节。
- application.properties
- application-dev.properties
spring.profiles.active=${spring.profiles.active:dev}
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)
本文相关范例源码
- pangu-examples-crud:数据持久化参考范例