MybatisPlus multi-table join query supports one-to-one, one-to-many, and many-to-many queries

MybatisPlus multi-table join query supports one-to-one, one-to-many, and many-to-many queries 

 

1. Preface

(1) Background content

The most common selection component of the DAO layer in the software application technology architecture is MyBatis. Friends who are familiar with MyBatis know how beautiful MyBatis was once, using XML files to solve complex database access problems. Today, the former dragon slayer has become a dragon, and the database access technology based on XML files has become bloated and complicated, and the maintenance difficulty has skyrocketed.

MybatisPlus encapsulates common database access, which greatly reduces the dependence on XML files for accessing databases, and developers get a maximum relief from bloated XML files.

MybatisPlus does not officially provide 连接查询a general solution for multiple tables, but join queries are quite common needs. There are two requirements to solve the connection query, one is to continue to use MyBatis to provide an XML file solution; the other is the solution provided in this article.

In fact, the author strongly recommends completely saying goodbye to accessing databases through XML, and constantly exploring new, more friendly and natural solutions, and now sharing the latest research results of MybatisPlus technology.

(2) Scenario description

To illustrate the relationship of join queries, students, courses and their relationships are used as examples here.

(3) Preliminary preparation

This part requires readers to master the following: Lambda expressions, especially method references; functional interfaces; streaming operations, etc., otherwise it will be difficult to understand.

For the mapping relationship between entity classes and Vo, the author creatively introduces special constructors and makes reasonable use of inheritance relationships, which greatly facilitates developers to complete the conversion of entity classes to Vo.

Null pointer exceptions are ignored and not handled, and are implemented with the help of the [Optional] class. For details, go to [Java8 New Features].

2. One-to-one query

The most typical application scenario of one-to-one query is to idreplace with name, such as userIdreplace with userName.

(1) Query a single record

Querying a single record means that the return value is only one record, usually with a unique index as the condition to return the query result.

1. Sample code

/**
 * 查询单个学生信息(一个学生对应一个部门)
 */
public UserVo getOneUser(Integer userId) {
    LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class)
        .eq(User::getUserId, userId);
    // 先查询用户信息
    User user = userMapper.selectOne(wrapper);
    // 转化为Vo
    UserVo userVo = Optional.ofNullable(user).map(UserVo::new).orElse(null);
    // 从其它表查询信息再封装到Vo
    Optional.ofNullable(userVo).ifPresent(this::addDetpNameInfo);
    return userVo;
}

Supplementary table information

/**
 * 补充部门名称信息
 */
private void addDetpNameInfo(UserVo userVo) {
    LambdaQueryWrapper<Dept> wrapper = Wrappers.lambdaQuery(Dept.class)
        .eq(Dept::getDeptId, userVo.getDeptId());
    Dept dept = deptMapper.selectOne(wrapper);
    Optional.ofNullable(dept).ifPresent(e -> userVo.setDeptName(e.getDeptName()));
}

2. Theoretical analysis

Querying a single entity is divided into two steps: query the main table data according to the conditions (need to handle null pointer exception); encapsulate Vo and query the data of the auxiliary table.

The query result (VO) has only one record, and the database needs to be queried twice, with a time complexity of O(1).

(2) Query multiple records

Querying multiple records means that the query result is a list, usually a query result conditioned on a common index.

1. Sample code

/**
 * 批量查询学生信息(一个学生对应一个部门)
 */
public List<UserVo> getUserByList() {
    // 先查询用户信息(表现形式为列表)
    List<User> user = userMapper.selectList(Wrappers.emptyWrapper());
    List<UserVo> userVos = user.stream().map(UserVo::new).collect(toList());
    // 此步骤可以有多个
    addDeptNameInfo(userVos);
    return userVos;
}

Supplementary information

private void addDeptNameInfo(List<UserVo> userVos) {
    // 提取用户userId,方便批量查询
    Set<Integer> deptIds = userVos.stream().map(User::getDeptId).collect(toSet());
    // 根据deptId查询deptName(查询前,先做非空判断)
    List<Dept> dept = deptMapper.selectList(Wrappers.lambdaQuery(Dept.class).in(Dept::getDeptId, deptIds));
    // 构造映射关系,方便匹配deptId与deptName
    Map<Integer, String> hashMap = dept.stream().collect(toMap(Dept::getDeptId, Dept::getDeptName));
    // 封装Vo,并添加到集合中(关键内容)
    userVos.forEach(e -> e.setDeptName(hashMap.get(e.getDeptId())));
}

2. Theoretical analysis

First query idthe included list records, extract from the result set idand convert them into batch query statements, then access the database, and parse out the result set from the second call name.

The query result (VO) has multiple records, but only calls the database twice, with a time complexity of O(1).

(3) Query multiple records (pagination)

The idea of ​​paging query entities is similar to that of querying lists, with additional one-step paging generic conversions.

1. Sample code

/**
 * 分页查询学生信息(一个学生对应一个部门)
 */
public IPage<UserVo> getUserByPage(Page<User> page) {
    // 先查询用户信息
    IPage<User> xUserPage = userMapper.selectPage(page, Wrappers.emptyWrapper());
    // 初始化Vo
    IPage<UserVo> userVoPage = xUserPage.convert(UserVo::new);
    if (userVoPage.getRecords().size() > 0) {
        addDeptNameInfo(userVoPage);
    }
    return userVoPage;
}

Inquiry for supplementary information

private void addDeptNameInfo(IPage<UserVo> userVoPage) {
    // 提取用户userId,方便批量查询
    Set<Integer> deptIds = userVoPage.getRecords().stream().map(User::getDeptId).collect(toSet());
    // 根据deptId查询deptName
    List<Dept> dept = deptMapper.selectList(Wrappers.lambdaQuery(Dept.class).in(Dept::getDeptId, deptIds));
    // 构造映射关系,方便匹配deptId与deptName
    Map<Integer, String> hashMap = dept.stream().collect(toMap(Dept::getDeptId, Dept::getDeptName));
    // 将查询补充的信息添加到Vo中
    userVoPage.convert(e -> e.setDeptName(hashMap.get(e.getDeptId())));
}

IPageThe methods in the interface convertcan be modified on the original instance.

2. Theoretical analysis

First query idthe included list records, extract from the result set idand convert them into batch query statements, then access the database, and parse out the result set from the second call name.

The query result (VO) has multiple records, but only calls the database twice, with a time complexity of O(1).

Three, one-to-many query

The most common scenario of one-to-many query is to query the student information contained in a department. Since one department corresponds to multiple students, and each student corresponds to a department, it is called one-to-many query.

(1) Query a single record

1. Sample code

/**
 * 查询单个部门(其中一个部门有多个用户)
 */
public DeptVo getOneDept(Integer deptId) {
    // 查询部门基础信息
    LambdaQueryWrapper<Dept> wrapper = Wrappers.lambdaQuery(Dept.class).eq(Dept::getDeptId, deptId);
    DeptVo deptVo = Optional.ofNullable(deptMapper.selectOne(wrapper)).map(DeptVo::new).orElse(null);
    Optional.ofNullable(deptVo).ifPresent(this::addUserInfo);
    return deptVo;
}

Supplementary additional information

private void addUserInfo(DeptVo deptVo) {
    // 根据部门deptId查询学生列表
    LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).eq(User::getDeptId, deptVo.getDeptId());
    List<User> users = userMapper.selectList(wrapper);
    deptVo.setUsers(users);
}

2. Theoretical analysis

The whole process is divided into two stages: query the specified department information through the primary key in the department table, query student information through the department ID foreign key in the student table, and combine the results to form a return value (Vo).

The whole process of one-to-many query of a single record requires at most two database queries, the number of queries is constant, and the query time complexity is O(1).

(2) Query multiple records

1. Sample code

/**
 * 查询多个部门(其中一个部门有多个用户)
 */
public List<DeptVo> getDeptByList() {
    // 按条件查询部门信息
    List<Dept> deptList = deptMapper.selectList(Wrappers.emptyWrapper());
    List<DeptVo> deptVos = deptList.stream().map(DeptVo::new).collect(toList());
    if (deptVos.size() > 0) {
        addUserInfo(deptVos);
    }
    return deptVos;
}

Supplementary additional information

private void addUserInfo(List<DeptVo> deptVos) {
    // 准备deptId方便批量查询用户信息
    Set<Integer> deptIds = deptVos.stream().map(Dept::getDeptId).collect(toSet());
    // 用批量deptId查询用户信息
    List<User> users = userMapper.selectList(Wrappers.lambdaQuery(User.class).in(User::getDeptId, deptIds));
    // 重点:将用户按照deptId分组
    Map<Integer, List<User>> hashMap = users.stream().collect(groupingBy(User::getDeptId));
    // 合并结果,构造Vo,添加集合列表
    deptVos.forEach(e -> e.setUsers(hashMap.get(e.getDeptId())));
}

2. Theoretical analysis

The whole process is divided into three stages: query several records from the department table through the common index; convert the department ID into a batch query to query the student record from the student table; group the student records with the department ID as the unit, and combine the results, Convert to Vo.

One-to-many query of multiple records requires two database queries, the number of queries is constant, and the query time complexity is O(1).

(3) Query multiple records (pagination)

1. Sample code

/**
 * 分页查询部门信息(其中一个部门有多个用户)
 */
public IPage<DeptVo> getDeptByPage(Page<Dept> page) {
    // 按条件查询部门信息
    IPage<Dept> xDeptPage = deptMapper.selectPage(page, Wrappers.emptyWrapper());
    IPage<DeptVo> deptVoPage = xDeptPage.convert(DeptVo::new);
    if (deptVoPage.getRecords().size() > 0) {
        addUserInfo(deptVoPage);
    }
    return deptVoPage;
}

Inquiry for supplementary information

private void addUserInfo(IPage<DeptVo> deptVoPage) {
    // 准备deptId方便批量查询用户信息
    Set<Integer> deptIds = deptVoPage.getRecords().stream().map(Dept::getDeptId).collect(toSet());
    LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).in(User::getDeptId, deptIds);
    // 用批量deptId查询用户信息
    List<User> users = userMapper.selectList(wrapper);
    // 重点:将用户按照deptId分组
    Map<Integer, List<User>> hashMap = users.stream().collect(groupingBy(User::getDeptId));
    // 合并结果,构造Vo,添加集合列表
    deptVoPage.convert(e -> e.setUsers(hashMap.get(e.getDeptId())));
}

2. Theoretical analysis

The whole process is divided into three stages: query several records from the department table through the common index; convert the department ID into a batch query to query the student record from the student table; group the student records with the department ID as the unit, and combine the results, Convert to Vo.

One-to-many query of multiple records requires two database queries, the number of queries is constant, and the query time complexity is O(1).

Four, many-to-many query

MybatisPlus implementing many-to-many query is a very challenging task and the most difficult part of join query.

Replacing time with space and using streaming operations to solve many-to-many query problems.

Compared with the one-to-many query, the many-to-many query adds streaming grouping operations and batch HashMap values.

(1) Query a single record

Querying a single record generally refers to querying a record in a matching table through two query conditions.

1. Sample code

public StudentVo getStudent(Integer stuId) {
    // 通过主键查询学生信息
    StudentVo studentVo = ConvertUtils.convertObj(getById(stuId), StudentVo::new);
    LambdaQueryWrapper<StuSubRelation> wrapper = Wrappers.lambdaQuery(StuSubRelation.class).eq(StuSubRelation::getStuId, stuId);
    // 查询匹配关系
    List<StuSubRelation> stuSubRelations = stuSubRelationMapper.selectList(wrapper);
    Set<Integer> subIds = stuSubRelations.stream().map(StuSubRelation::getSubId).collect(toSet());
    if (studentVo != null && subIds.size() > 0) {
        List<Subject> subList = subjectMapper.selectList(Wrappers.lambdaQuery(Subject.class).in(Subject::getId, subIds));
        List<SubjectBo> subBoList = ConvertUtils.convertList(subList, SubjectBo::new);
        HashBasedTable<Integer, Integer, Integer> table = getHashBasedTable(stuSubRelations);
        subBoList.forEach(e -> e.setScore(table.get(stuId, e.getId())));
        studentVo.setSubList(subBoList);
    }
    return studentVo;
}

2. Theoretical analysis

Many-to-many single record query accesses the database at most 3 times, first query student information, then query student and course matching information, and finally query course score information, the query time complexity is O(1).

(2) Query multiple records

1. Sample code

public List<StudentVo> getStudentList() {
    // 通过主键查询学生信息
    List<StudentVo> studentVoList = ConvertUtils.convertList(list(), StudentVo::new);
    // 批量查询学生ID
    Set<Integer> stuIds = studentVoList.stream().map(Student::getId).collect(toSet());
    LambdaQueryWrapper<StuSubRelation> wrapper = Wrappers.lambdaQuery(StuSubRelation.class).in(StuSubRelation::getStuId, stuIds);
    List<StuSubRelation> stuSubRelations = stuSubRelationMapper.selectList(wrapper);
    // 批量查询课程ID
    Set<Integer> subIds = stuSubRelations.stream().map(StuSubRelation::getSubId).collect(toSet());
    if (stuIds.size() > 0 && subIds.size() > 0) {
        HashBasedTable<Integer, Integer, Integer> table = getHashBasedTable(stuSubRelations);
        List<Subject> subList = subjectMapper.selectList(Wrappers.lambdaQuery(Subject.class).in(Subject::getId, subIds));
        List<SubjectBo> subjectBoList = ConvertUtils.convertList(subList, SubjectBo::new);
        Map<Integer, List<Integer>> map = stuSubRelations.stream().collect(groupingBy(StuSubRelation::getStuId, mapping(StuSubRelation::getSubId, toList())));
        for (StudentVo studentVo : studentVoList) {
            // 获取课程列表
            List<SubjectBo> list = ListUtils.select(subjectBoList, e -> emptyIfNull(map.get(studentVo.getId())).contains(e.getId()));
            // 填充分数
            list.forEach(e -> e.setScore(table.get(studentVo.getId(), e.getId())));
            studentVo.setSubList(list);
        }
    }
    return studentVoList;
}

2. Theoretical analysis

The many-to-many N record query uses batch query, so the maximum number of accesses to the database is 3 times. First, the student information is queried, then the student and course matching information is queried, and finally the course score information is queried. The query time complexity is O(1).

(3) Query multiple records (pagination)

1. Sample code

public IPage<StudentVo> getStudentPage(IPage<Student> page) {
    // 通过主键查询学生信息
    IPage<StudentVo> studentVoPage = ConvertUtils.convertPage(page(page), StudentVo::new);
    // 批量查询学生ID
    Set<Integer> stuIds = studentVoPage.getRecords().stream().map(Student::getId).collect(toSet());
    LambdaQueryWrapper<StuSubRelation> wrapper = Wrappers.lambdaQuery(StuSubRelation.class).in(StuSubRelation::getStuId, stuIds);
    // 通过学生ID查询课程分数
    List<StuSubRelation> stuSubRelations = stuSubRelationMapper.selectList(wrapper);
    // 批量查询课程ID
    Set<Integer> subIds = stuSubRelations.stream().map(StuSubRelation::getSubId).collect(toSet());
    if (stuIds.size() > 0 && subIds.size() > 0) {
        HashBasedTable<Integer, Integer, Integer> table = getHashBasedTable(stuSubRelations);
        // 学生ID查询课程ID组
        Map<Integer, List<Integer>> map = stuSubRelations.stream().collect(groupingBy(StuSubRelation::getStuId, mapping(StuSubRelation::getSubId, toList())));

        List<Subject> subList = subjectMapper.selectList(Wrappers.lambdaQuery(Subject.class).in(Subject::getId, subIds));
        List<SubjectBo> subBoList = ConvertUtils.convertList(subList, SubjectBo::new);
        for (StudentVo studentVo : studentVoPage.getRecords()) {
            List<SubjectBo> list = ListUtils.select(subBoList, e -> emptyIfNull(map.get(studentVo.getId())).contains(e.getId()));
            list.forEach(e -> e.setScore(table.get(studentVo.getId(), e.getId())));
            studentVo.setSubList(list);
        }
    }
    return studentVoPage;
}

2. Theoretical analysis

The many-to-many paging query of N records uses batch query, so the maximum number of accesses to the database is 3 times. First, the student information is queried, then the student and course matching information is queried, and finally the course score information is queried. The query time complexity is O(1).

V. Summary and expansion

(1) Summary

Through the above analysis, MybatisPlus can be used to solve the 一对一, 一对多, and query in the multi-table join 多对多query.

  • The above code is compact, making full use of the IDE's support for lambda expressions, and checking the code during compilation.
  • The business logic is clear, and the maintainability and modifiability advantages are obvious.
  • A query needs to access the database at most twice, the time complexity is o(1)primary key query or index query, and the query efficiency is high.

(2) Expansion

MybatisPlus can solve the problem of single table query very well, and at the same time, it can solve the problem of connection query with the help of the encapsulation of single table query.

This solution not only solves the connection query problem, but also has the following expansions:

  • When the amount of data is large, it still has stable query efficiency

When the amount of data reaches millions, the traditional single-table query by index has faced challenges, and the performance of ordinary multi-table join query decreases exponentially with the increase of the amount of data.

By transforming the join query into a primary key (index) query, the query performance is equivalent to a single-table query.

  • Use with L2 cache to further improve query efficiency

When all queries are converted into single-table-based queries, the second-level cache can be safely introduced. The single-table addition, deletion, modification, and query operations of the second-level cache are adaptively linked, which solves the problem of dirty data in the second-level cache.

Guess you like

Origin blog.csdn.net/m0_62396648/article/details/124437188