# 无SQL关联数据绑定

diboot-core支持通过注解实现数据字典关联、从表Entity/VO的(1-1)及其集合(1-n)关联,从表字段(1-1)及字段集合(1-n)关联等实现。 通过重构查询方式 (拆解关联查询,程序中Join) ,简化开发、提高性能

# 如何开始

您可以使用diboot-devtools一键生成表的各层代码及关联数据绑定。 如需初次体验绑定功能,需要准备表相对应的Entity实体类(推荐继承自diboot的BaseEntity)、service接口及实现(继承自diboot的BaseService或mybatis-plus的IService)、mapper(继承自diboot的BaseCrudMapper或mybatis-plus的BaseMapper)等, 完成之后可以创建一个VO类来体验绑定功能,如:

import com.diboot.core.binding.annotation.*;
import com.example.demo.entity.User;
@Data
public class UserVO extends User {
    private static final long serialVersionUID = 5476618743424368148L;
    // 通过注解绑定员工的工作经历关联数据
    @BindEntityList(entity=WorkExperience.class, condition="this.id=user_id")
    private List<WorkExperience> workExperienceList;
}
1
2
3
4
5
6
7
8
9

WARNING

  • @BindDict注解需要依赖dictionary表,初次启动时diboot-core-starter会自动安装。 如果只依赖diboot-core,可以手动创建该表并实现DictionaryServiceExtProvider接口,以使用字典绑定功能。
  • 推荐实践:Bind注解请写在独立的类中(如VO),不要跟Entity类混合在一起,难以维护。

# 绑定关联数据的调用方式

  1. 通过在VO类中添加相关字段,以及对应的关联数据绑定注解,来定义我们的绑定类型和需要得到的结果以及额外的条件等信息;
  2. 注解添加后,调用Binder类中的相关方法(*bindRelations)执行这个绑定关系,目前提供了两种方式适配不同场景:

# 1. 自动转型并绑定关联

该调用会先自动转型为vo类,并自动查询抓取VO中的声明的@Bind*绑定注解的关联数据,赋值给vo对象,如:

// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
// 转型并绑定关联
List<UserVO> voList = Binder.convertAndBindRelations(userList, UserVO.class);
1
2
3
4
  • 如果在绑定前已经拿到VO类型,不需要转型直接绑定,可以调用:
//List<MyUserVO> voList = ...; 
Binder.bindRelations(voList);
1
2

# 2. 通过BaseService接口实现绑定

BaseService类中的getViewObject*接口已实现了关联数据的绑定逻辑,可以直接调用,如:

// 绑定单个VO对象
xxService.getViewObject(id, xxVO.class)
// 绑定多个VO集合
xxService.getViewObjectList()
1
2
3
4

# 绑定关联数据字典

TIP

当表中的字段为数据字典类型的值时,可使用数据字典关联来绑定表字段与数据字典的关联关系。
通过@BindDict注解,数据字典关联时无需写大量java代码和SQL关联查询,即可快速转换值字典value值为label。

  • 使用@BindDict注解时需传两个参数,分别是type和field。
  • type表示关联的数据字典类型(dictionary表的type字段值);
  • field表示关联的取值字段。
  • 示例如下:
@BindDict(type="USER_STATUS", field = "status")
private String statusLabel;
1
2

关于数据字典的更多介绍,看这里

# 绑定关联数据表

  • 数据表关联按照关联表的方式上可分为单数据表直接关联中间表关联这两种,中间表关联是一种通过中间表进行“"1-1"或"多-多"的关联处理方案。
  • 按照得到结果的形式可分为绑定关联表中对应字段(集合)绑定关联表实体(集合) 这两种关联方式,前者得到关联表中的目标字段(集合),后者得到关联表的整个实体(集合)。
  • 支持对关联查询添加附加条件。
  • diboot关联的实现是拆解关联查询为单表查询,可以更高效利用数据库缓存和索引,降低死锁概率,提高性能。

# 绑定从表Entity实体

绑定单个实体(或实体对应的VO)使用**@BindEntity**注解进行处理,将得到关联表对应的单个实体。

  • 使用@BindEntity注解时需指定两个参数:entity和condition。
    • entity 表示关联实体类;
    • condition 表示关联SQL条件(当前对象表中的列添加'this.'前缀,写在比较条件的左侧,被绑定Entity表的列在右侧,如果有中间表的条件写在中间)。
    • deepBind 是否深度绑定,默认false。当绑定Entity对象为VO(类定义中还有Bind注解),开启deepBind=true,可以实现对被绑定VO的注解的再次绑定。
  • 主表1-1直接关联从表,绑定从表Entity,注解示例如下:
@BindEntity(entity = Department.class, condition="this.department_id=id")
private Department department;
1
2
  • 主表1-1通过中间表间接关联从表,绑定从表Entity,注解示例如下(condition不同):
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id")
private Organization organization;
1
2
  • deepBind 深度绑定注解示例如下:
@BindEntity(entity = Department.class, condition="this.department_id=id", deepBind=true)
private DepartmentVO departmentVO;
1
2

# 绑定从表Entity实体列表

绑定实体(或实体对应的VO)列表使用**@BindEntityList**注解进行处理,将得到关联表对应的实体列表。

  • @BindEntityList多数注解参数与@BindEntity相同。

    • 附加参数
      • orderBy 对绑定结果集合的排序标识,用法同列表页查询的orderBy排序,具体格式为'列名:排序方式,列名:排序方式',示例如下:
        • orderBy="sort_id" //按sort_id升序(默认)
        • orderBy="sort_id:ASC" //按sort_id升序
        • orderBy="sort_id:DESC" //按sort_id降序
        • orderBy="type:ASC,id:DESC" //先按type升序,type相同按id降序
      • splitBy 分隔符,用于拆解拼接存储的多个id值(since v2.3.1
  • 主表1-n直接关联从表,绑定从表的Entity列表,注解示例如下:

// 关联其他表(main_table_id为Task中存储当前对象id的字段)
@BindEntityList(entity = Task.class, condition="this.id=main_table_id")
private List<Task> taskList;

// 关联自身,实现加载子级
@BindEntityList(entity = Department.class, condition = "this.id=parent_id")
private List<Department> children;
1
2
3
4
5
6
7
  • 主表1-n通过中间表关联从表,绑定从表的Entity列表,结果按code升序排序。示例如下:
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id", orderBy="code")
private List<Role> roleList;
1
2
  • 主表1-n主表分隔符拼接存储多个从表ID,绑定从表的Entity列表,注解示例如下:(since v2.3.1
// 当前表task_ids字段中以分隔符拼接存储了Task对象的id,如:10001,10002,10003
@BindEntityList(entity = Task.class, condition="this.task_ids=id", splitBy=",")
private List<Task> taskList;
1
2
3

# 绑定从表字段

绑定字段使用**@BindField**注解进行处理,将得到关联表的目标字段的值。

  • @BindField 注解需三个参数,分别是entity、field、condition。

    • entity表示关联实体;
    • field表示关联表字段名;
    • condition表示关联条件,用法同BindEntity*
  • 主表1-1直接关联从表,绑定从表字段值,注解示例如下:

@BindField(entity=Department.class, field="name", condition="this.department_id=id AND parent_id>=0")
private String deptName;
1
2
  • 主表1-1通过中间表关联从表的级联关联,注解示例如下:
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
1
2

# 绑定从表字段列表

  • since v2.1.1

绑定实体列表使用**@BindFieldList**注解进行处理,将得到关联表对应的实体列表。

  • @BindFieldList多数注解参数与@BindField相同。

  • 主表1-n直接关联从表字段集合,注解示例如下:

// 关联其他表(main_table_id为Task中存储当前对象id的字段)
@BindFieldList(entity = Task.class, field="name", condition="this.id=main_table_id")
private List<String> taskTitleList;
1
2
3
  • 主表1-n通过中间表关联从表,绑定从表的Entity某字段的列表,示例如下:
@BindFieldList(entity = Role.class, field="code", condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<String> roleCodes;
1
2
  • 主表1-n主表分隔符拼接存储多个从表ID,绑定从表的Entity某字段的列表,注解示例如下:(since v2.3.1
// 当前表task_ids字段中以分隔符拼接存储了Task对象的id,如:10001,10002,10003
@BindFieldList(entity = Task.class, field="task_title", condition="this.task_ids=id", splitBy=",")
private List<String> taskTitleList;
1
2
3

# 绑定从表子项count计数

  • since v2.6.0

@BindCount注解将绑定关联子项的count汇总计数数字结果

  • 使用@BindCount注解时需指定两个参数:entity和condition。
    • entity 表示关联实体类;
    • condition 表示关联SQL条件(当前对象表中的列添加'this.'前缀,写在比较条件的左侧,被绑定Entity表的列在右侧,如果有中间表的条件写在中间)。
  • 主表对应的列表页面需要显示从表子项的汇总计数,注解示例如下:
public class DepartmentVO extends Department {
    //...
    // 关联绑定各部门下的员工数量
    @BindCount(entity = User.class, condition="this.id=department_id")
    private Integer deptUserCount;
}
1
2
3
4
5
6

# 复杂绑定

  • since v2.1.2

绑定操作中的条件等同于SQL中的JOIN ON,diboot可以支持类似如下的复杂场景:

  • 支持多列的关联数据绑定
// 示例:通过 utype+uid 关联中间表的 user_type+user_id ,绑定目标对象Role
@BindEntityList(entity = Role.class, condition = "this.utype=user_role.user_type AND this.uid=user_role.user_id AND user_role.role_id=id")
private List<Role> roles;
1
2
3