# 开发参考
# 批量操作
使用批量插入可有效提高开发时数据库操作效率
# 批量插入
使用Mapper时,在BaseMapper提供batchInsert方法实现批量插入,使用的是单SQL批量插入模式,需要注入使用时不在单次插入过多,因为过长的SQL可能有编译效率问题,过大的报文有可能超过数据库的限制。
服务类继承BaseServiceImpl实现BaseService时可获得以下两个批量插入能力:
- batchAdd方法。batchAdd是对BaseMapp中batchInsert的包装,所以使用时要注意同batchInsert的事项。
- batchSave方法。batchSave是使用Mybatis的BatchExecutor批量执行sql的模式,效率高且不会有长SQL问题。
User user1 = new User(1L, 'zhangsan');
User user2 = new User(2L, 'lisi');
userMapper.batchInsert(Arrays.asList(user1, user2));
userService.batchSave(Arrays.asList(user1, user2));
# 批量更新
服务类继承BaseServiceImpl实现BaseService时可获得以下两个批量更新能力:
- batchUpdateById。将一组实体以各自的id为匹配条件进行更新。
- batchUpdateByLambdaCriteria。将一组实体以各自的动态条件进行匹配更新。
List<User, Consumer<LambdaUpdatePredicate<User>>>> updates = new ArrayList<>();
User lockUser = new User();
lockUser.setStatus(1);
User normalUser = new User();
normalUser.setStatus(0);
updates.add(Pair.of(normalUser, p -> p.gte(User::getCreateTime, LocalDateTime.parse("2021-01-01T00:00:00"))));
updates.add(Pair.of(lockUser, p -> p.lt(User::getCreateTime, LocalDateTime.parse("2021-01-01T00:00:00"))));
userService.batchUpdateByLambdaCriteria(updates);
# 联表查询
在说明联表查询前,先说明在实体中是如何定义引用属性的,完全遵循JPA的声明规则。
public class Dept {
@SnowflakeId // insert数据时自动设置snowFlakeId值
private Long id;
private String deptName;
@OneToMany(mappedBy = "dept") // 在User实体类中已有JoinColumn声明联结规则,所以这里只要使用mappedBy指定声明联结规则的属性名即可。
private List<User> users;
}
public class User {
@Id
private Long id;
@Contains // 这里可以在example查询时使用LIKE查询
private String userName;
@Contains
private String name;
private Long deptId;
@ManyToOne
@JoinColumn(name = "dept_id", referencedColumnName="id")
private Dept dept; // 关联表实体
}
同表引用也是可以的
public class Menu {
@Id
private Long id;
private menuName;
private Long parentId;
@OneToMany(mappedBy = "parent")
private List<Menu> children;
@ManyToOne
@JoinColumn(name = "parent_id", referencedColumnName="id")
private Menu parent;
}
假设中间表“角色-菜单”表为
CREATE TABLE role_menu_rel (
menu_id BIGINT,
role_id BIGINT,
)
public class Role {
@Id
private Long id;
private String roleCode;
private String roleName;
// 多对多引用
@ManyToMany
@JoinTable(name = "role_menu_rel", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "menu_id", referencedColumnName = "id"))
private List<Menu> menus;
}
# 使用引用实体的属性作为查询条件
只可以使用直接引用的实体的属性作为查询条件
// 查询“财务部”的用户信息
userMapper.findByCriteria(p -> p.eq("deptDeptName", "财务部"));
使用引用属性对应用的实体类的属性作查询条件时,使用当前实体引用属性的名称+关联实体的属性名作为条件。
例如上面使用Dept实体中的deptName作为查询条件,Dept在User中的属性为dept,所以查询条件时条件名为deptDeptName,注意驼峰格式。
# 使用引用实体的属性作为查询项
只可以查询直接引用的实体的属性
// 查询User的所有属性 + Dept的deptName
List<User> users = userMapper.findByCriteria(p -> p.select("*,deptDeptName"));
// 查询User的所有属性 + Dept的所有属性
List<User> users = userMapper.findByCriteria(p -> p.select("*,dept*"));
// 查询User的部分属性 + Dept的deptName
List<User> users = userMapper.findByCriteria(p -> p.select("id, userName,deptDeptName"));
// 查询菜单及其下级菜单
Menu menu = menuMapper.findByCriteria(p -> p.select("*, childdren*").eq("id", 1L));
// 查询菜单及其上级
Menu menu = menuMapper.findByCriteria(p -> p.select("*, parent*").eq("id", 1L));
// 查询角色及其菜单
Role role = roleMapper.findByCriteria(p -> p.select("*, parent*").eq("id", 1L));
如果查询当前实体的所有属性,是不需要select()指定的。
# 使用example条件快速查询
example是以实体类实例作为查询条件的查询,当实例属性不为空时,即作为查询条件,常用场景就是使用实体类接收查询参数进行查询。
通过声明,可以获得等值、区间、包含、模糊的查询能力,以下通过“用户”实体类进行演示
@FetchRef(refAttrs="dept") // 声明Example查询时,同时查询引用表
public class User {
@Id
private Long id;
@Contains // 这里可以在example查询时使用LIKE查询
private String userName;
@Contains
private String name;
private Long deptId;
// 0:正常,1:锁定,2:注销
@AttributeOptions(exampleQuery = @ExampleQuery(inKeyName = "params.statusIn")) // 这里声明当传送了params.statusIn值时启用IN查询
private Integer status;
@AttributeOptions(exampleQuery = @ExampleQuery(startKeyName = "params.createTimeBegin", endKeyName = "params.createTimeEnd")) // 这里声明当传送了params.createTimeBegin或params.createTimeEnd值时启用区间查询
private LocalDateTime createTime;
@ManyToOne
@JoinColumn(name = "dept_id", referencedColumnName="id")
private Dept dept; // 关联表实体
@Transient // @Transient声明这不是一个表字段
private Map<String, Object> params;
public Map<String, Object> getParams() {
return params == null ? (params = new HashMap<>()) : params;
}
}
// 这是一个常用查询场景
@GetMapping("/list")
public List<User> getUserList(User exmaple, Integer pageNum , Integer pageSize) {
return userService.getByExample(exmaple, new PageRequest(pageNum, pageSize), Sort.desc("createTime"));
}
传值示例
-- example值为:User{name=张三}
SELECT ... WHERE name LIKE CONCAT('%', ?, '%')
-- example值为:User{params={createTimeBegin=2021-01-01, createTimeEnd=2022-12-31}}
SELECT ... WHERE name create_time >= ? AND create_time <= ?
-- example值为:User{status=0}
SELECT ... WHERE status = ?
-- example值为:User{params={statusIn=0, 1}}
SELECT ... WHERE status IN (?,?)
-- 以上的查询字段
SELECT a.id _a_id, a.user_name _a_user_ame, a.name _a_name, ..., b.id _b_id, b.dept_name _b_dept_name, ... FROM user a INNER JOIN dept b ON (a.dept_id = b.id) WHERE ...
example查询中不想查询关联表怎么做?
@FetchRef(refAttrs="dept") 等同于 @EntityOptions(fetchRefs={@FetchRef(group = "default", refAttrs="dept")},defualt组在example查询中不指定分组时的默认取值
所以如果不想默认情况下查询关联表,声明时指定分组即可,例如:@FetchRef(group="fetchDept", refAttrs="dept")
这样只有指定使用分组时才查关联表
userService.getByExample(exmaple, new PageRequest(pageNum, pageSize), Sort.desc("createTime"), "fetchDept")
# 分页的支持方式
分页底层使用PageHelper插件完成分页操作,所以最简单的操作就是直接使用PageHelper.startPage(...)进行分页。
直接使用PageHelper.startPage(...)是有风险的,因为需要紧跟后面的查询,如果在查询前有拦截器等切面进行其它查询操作时,就被误用了,所以这里是建议使用mybatis-milu提供的分页传参方式。
# 在NamingQuery中分页
interface UserMapper extends BaseMapper<Long, User> {
@NamingQuery
private findByCreateTimeGreaterThan(LocalDateTime createTime, Pageable page); // 不需要写SQL
@NamingQuery
private Optional<User> findByUserName(String userName); // 唯一值查询,表达式,不需要写SQL
}
List<User> users = userMapper.findByCreateTimeGreaterThan(LocalDateTime.parse("2021-01-01T00:00:00"), new PageRequest(1, 10));
// SELECT ... WHERE create_time > ? LIMIT ?
# 在Criteria查询中分页
// Criteria有多种传参方式
userMapper.findByCriateria(p -> p.limit(1, 10));
userMapper.findByCriateria(p -> p.limit(1, 10, false)); // 设定不统计总行数
userMapper.findByCriateria(p -> p.limit(new PageRequest(1, 10));
userMapper.findByCriateria(p -> p.limitOffset(0, 10)); // 偏移量方式
# 在Example查询中分页
常用就是传Pageable的实例
userService.getByExample(exmaple, new PageRequest(pageNum, pageSize), Sort.desc("createTime"));
如果实体类实现了Pageable,则可以使用实体类设置分页参数进行传参
public class User extends PageRequest { // PageRequest是Pageable的实现类
@Id
private Long id;
}
User example = new User();
example.setPageNum(1);
example.setPageSize(10);
userService.getByExample(exmaple);