# 开发参考

# 批量操作

使用批量插入可有效提高开发时数据库操作效率

# 批量插入

使用Mapper时,在BaseMapper提供batchInsert方法实现批量插入,使用的是单SQL批量插入模式,需要注入使用时不在单次插入过多,因为过长的SQL可能有编译效率问题,过大的报文有可能超过数据库的限制。

服务类继承BaseServiceImpl实现BaseService时可获得以下两个批量插入能力:

  1. batchAdd方法。batchAdd是对BaseMapp中batchInsert的包装,所以使用时要注意同batchInsert的事项。
  2. 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时可获得以下两个批量更新能力:

  1. batchUpdateById。将一组实体以各自的id为匹配条件进行更新。
  2. 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);