# 开发参考
# 批量操作
使用批量插入可有效提高开发时数据库操作效率
# 批量插入
使用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")
# exists 子查询
查询条件支持子查询,使用exists子查询不需要预先在实体类在声明实体关联
userMapper.findByCriteria(p -> {
p.exists(Exists.of(DeptMapper.class).join(Dept::getId, User::getDeptId).lambdaCriteria(ep -> {
ep.eq(Dept::getDeptName, "XX部门");
}))
});
userMapper.findByCriteria(p -> {
p.exists(Exists.of(DeptMapper.class).join("id", "deptId").criteria(ep -> {
ep.eq("deptName", "XX部门");
}))
});
com.yuehuanghun.mybatis.milu.criteria.Exists<Entity>中泛型Entity是指实体类
Exists.of(Class<? extends BaseMapper<Entity, ?>> mapperClass)中mapperClass是Exists查询条件的实体对应的Mapper类
Exists.join(String attrName, String referenceAttrName)中attrName是Exists查询实体的属性,referenceAttrName是主查询实体的属性
Exists查询的查询条件有两种模式,lambda模式与普通模式,分别是lambdaCriteria(Consumer<LambdaQueryPredicate<Entity>> predicate)和criteria(Consumer<QueryPredicate> predicate)
# 自定义条件
实现com.yuehuanghun.mybatis.milu.criteria.Condition接口,在动态查询中使用条件的实例
参考:com.yuehuanghun.mybatis.milu.criteria.ext.*.conds 下的类
因为SQL模板缓存的原因,类非单例的情况下,需要重写hashCode及equals方法
# 自定义查询
默认情况下,查询仅允许为实体的属性,如果自定义查询列(如函数查询列),则需要实现Select接口,在查询中Select的实例
参考:com.yuehuanghun.mybatis.milu.criteria.ext.*.select
因为SQL模板缓存的原因,类非单例的情况下,需要重写hashCode及equals方法
# 函数值插入更新
一般情况下,字段在插入/更新时的值都是一个具体值,如果在插入/更新时使用数据库的函数,可以如此使用
public class User {
@SnowflakeId
private Long id;
@FuncColumn(expression = "AES_DECRYPT(FROM_BASE64(${idNo}),'123456')", upsertExp = "TO_BASE64(AES_ENCRYPT(${value},'123456'))")
private String idNo;
}
其中expression是查询的表达式,upsertExp是插入/更新时的值表达式。
其中固定使用${value}使用对应属性的值
运行时SQL
INSERT INTO `student` ( `id`, `id_no`) VALUES( ?, TO_BASE64(AES_ENCRYPT(?,'123456')) )