欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis一對(duì)多關(guān)系映射方式小結(jié)

 更新時(shí)間:2025年10月11日 09:52:44   作者:Iris761  
在實(shí)際開發(fā)中經(jīng)常會(huì)有一對(duì)多的表關(guān)系,因此掌握使用MyBatis處理一對(duì)多關(guān)系映射方式時(shí)很有必要的,本文就來介紹一下MyBatis一對(duì)多關(guān)系映射方式,感興趣的可以了解一下

1. MyBatis N+1 問題詳解

1.1 什么是N+1問題

N+1問題是指執(zhí)行1次主查詢獲取N條主記錄,然后對(duì)每條主記錄再執(zhí)行1次關(guān)聯(lián)查詢,總共執(zhí)行 1 + N 次查詢的性能問題。

1.2 示例場(chǎng)景

假設(shè)有:部門表(department)和員工表(employee),一個(gè)部門有多個(gè)員工。

產(chǎn)生N+1問題的代碼如下:

<!-- 1. 先查詢所有部門 -->
<select id="selectAllDepartments" resultMap="DepartmentResultMap">
    SELECT id, name FROM department
</select>

<!-- 2. 為每個(gè)部門查詢員工 -->
<select id="selectEmployeesByDeptId" resultType="Employee">
    SELECT id, name FROM employee WHERE dept_id = #{deptId}
</select>

<!-- 3. 結(jié)果映射中使用嵌套查詢 -->
<resultMap id="DepartmentResultMap" type="Department">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="employees" column="id" 
                ofType="Employee" select="selectEmployeesByDeptId"/>
</resultMap>

執(zhí)行過程:

List<Department> departments = departmentMapper.selectAllDepartments();
// 實(shí)際執(zhí)行的SQL:
// 1. SELECT id, name FROM department; (假設(shè)返回3個(gè)部門)
// 2. SELECT id, name FROM employee WHERE dept_id = 1;
// 3. SELECT id, name FROM employee WHERE dept_id = 2;
// 4. SELECT id, name FROM employee WHERE dept_id = 3;
// 總共執(zhí)行了 1 + 3 = 4 次查詢

1.3 產(chǎn)生問題

  1. 性能低下:查詢次數(shù)隨數(shù)據(jù)量線性增長
  2. 數(shù)據(jù)庫壓力大:頻繁建立數(shù)據(jù)庫連接
  3. 響應(yīng)時(shí)間長:網(wǎng)絡(luò)往返次數(shù)多

接下來講解解決1+N問題的方式

2. 使用JOIN 查詢 + Collection 映射

2.1 實(shí)體類定義

// 部門實(shí)體
@Getter
@Setter
public class Department {
    private Long id;
    private String name;
    private List<Employee> employees; // 一對(duì)多關(guān)系
}

// 員工實(shí)體
@Getter
@Setter
public class Employee {
    private Long id;
    private String name;
    private String position;
    private Long deptId;
}

2.2 XML配置

由于 JOIN 查詢有些情況會(huì)產(chǎn)生重復(fù)的部門數(shù)據(jù),MyBatis 會(huì)自動(dòng)處理這種重復(fù),但需要使用<id>標(biāo)簽指定好主鍵:

<!-- 使用 JOIN 查詢一次性獲取所有數(shù)據(jù) -->
<resultMap id="DepartmentWithEmployeesMap" type="Department">
    <id property="id" column="dept_id"/><!-- 重要配置 -->
    <result property="name" column="dept_name"/>
    
    <!-- 使用 collection 映射一對(duì)多關(guān)系 -->
    <collection property="employees" ofType="Employee" javaType="java.util.ArrayList">
        <id property="id" column="emp_id"/><!-- 重要配置 -->
        <result property="name" column="emp_name"/>
        <result property="position" column="position"/>
        <result property="deptId" column="dept_id"/>
    </collection>
</resultMap>

<select id="selectDepartmentWithEmployees" resultMap="DepartmentWithEmployeesMap">
    SELECT 
        d.id as dept_id,
        d.name as dept_name,
        e.id as emp_id,
        e.name as emp_name,
        e.position,
        e.dept_id
    FROM department d
    LEFT JOIN employee e ON d.id = e.dept_id
    WHERE d.id = #{id}
</select>

<!-- 查詢多個(gè)部門及其員工 -->
<select id="selectAllDepartmentsWithEmployees" resultMap="DepartmentWithEmployeesMap">
    SELECT 
        d.id as dept_id,
        d.name as dept_name,
        e.id as emp_id,
        e.name as emp_name,
        e.position,
        e.dept_id
    FROM department d
    LEFT JOIN employee e ON d.id = e.dept_id
    ORDER BY d.id, e.id
</select>

2.3 使用實(shí)例

@Service
public class DepartmentService {
    
    @Autowired
    private DepartmentMapper departmentMapper;
    
    // 一次性獲取部門及其所有員工,避免N+1問題
    public Department getDepartmentWithEmployees(Long deptId) {
        return departmentMapper.selectDepartmentWithEmployees(deptId);
    }
    
    // 獲取所有部門及其員工
    public List<Department> getAllDepartmentsWithEmployees() {
        return departmentMapper.selectAllDepartmentsWithEmployees();
    }
    
    // 業(yè)務(wù)方法:統(tǒng)計(jì)各部門員工數(shù)量
    public Map<String, Integer> getEmployeeCountByDepartment() {
        List<Department> departments = departmentMapper.selectAllDepartmentsWithEmployees();
        
        return departments.stream()
            .collect(Collectors.toMap(
                Department::getName,
                dept -> dept.getEmployees() != null ? dept.getEmployees().size() : 0
            ));
    }
}

2.4 復(fù)雜場(chǎng)景(多層嵌套)

假設(shè)有一個(gè)多層嵌套的復(fù)雜場(chǎng)景,表關(guān)系如下:

// 公司實(shí)體
public class Company {
    private Long id;
    private String name;
    private List<Department> departments; // 一對(duì)多:公司有多個(gè)部門
}

// 部門實(shí)體
public class Department {
    private Long id;
    private String name;
    private Long companyId;           // 所屬公司ID
    private List<Employee> employees; // 一對(duì)多:部門有多個(gè)員工
    private List<Project> projects;   // 一對(duì)多:部門有多個(gè)項(xiàng)目
}

// 員工實(shí)體
public class Employee {
    private Long id;
    private String name;
    private String position;      // 新增字段
    private Long deptId;         // 所屬部門ID
    private List<Skill> skills;  // 多對(duì)多:員工有多個(gè)技能
}

// 項(xiàng)目實(shí)體
public class Project {
    private Long id;
    private String name;
    private Long deptId;         // 所屬部門ID
    private Date startDate;      // 新增字段
    private Date endDate;        // 新增字段
}

// 技能實(shí)體
public class Skill {
    private Long id;
    private String name;
    private String category;     // 新增字段:技能分類
}

// 員工技能關(guān)聯(lián)實(shí)體(多對(duì)多中間表)
public class EmployeeSkill {
    private Long id;
    private Long employeeId;
    private Long skillId;
    private Integer proficiency; // 熟練程度
}

對(duì)應(yīng)的Mapper映射如下:

<!-- 更新后的結(jié)果映射,包含所有字段 -->
<resultMap id="CompanyResultMap" type="Company">
    <id property="id" column="company_id"/>
    <result property="name" column="company_name"/>
    <collection property="departments" ofType="Department" resultMap="DepartmentResultMap"/>
</resultMap>

<resultMap id="DepartmentResultMap" type="Department">
    <id property="id" column="dept_id"/>
    <result property="name" column="dept_name"/>
    <result property="companyId" column="company_id"/>
    <collection property="employees" ofType="Employee" resultMap="EmployeeResultMap"/>
    <collection property="projects" ofType="Project" resultMap="ProjectResultMap"/>
</resultMap>

<resultMap id="EmployeeResultMap" type="Employee">
    <id property="id" column="emp_id"/>
    <result property="name" column="emp_name"/>
    <result property="position" column="position"/>
    <result property="deptId" column="dept_id"/>
    <collection property="skills" ofType="Skill" resultMap="SkillResultMap"/>
</resultMap>

<resultMap id="ProjectResultMap" type="Project">
    <id property="id" column="project_id"/>
    <result property="name" column="project_name"/>
    <result property="deptId" column="dept_id"/>
    <result property="startDate" column="start_date"/>
    <result property="endDate" column="end_date"/>
</resultMap>

<resultMap id="SkillResultMap" type="Skill">
    <id property="id" column="skill_id"/>
    <result property="name" column="skill_name"/>
    <result property="category" column="category"/>
</resultMap>

<!-- 更新后的查詢SQL,包含所有字段 -->
<select id="selectCompanyWithDetails" resultMap="CompanyResultMap">
    SELECT 
        c.id as company_id, 
        c.name as company_name,
        d.id as dept_id, 
        d.name as dept_name,
        d.company_id,
        e.id as emp_id, 
        e.name as emp_name,
        e.position,
        e.dept_id,
        p.id as project_id, 
        p.name as project_name,
        p.dept_id,
        p.start_date,
        p.end_date,
        s.id as skill_id, 
        s.name as skill_name,
        s.category
    FROM company c
    LEFT JOIN department d ON c.id = d.company_id
    LEFT JOIN employee e ON d.id = e.dept_id
    LEFT JOIN project p ON d.id = p.dept_id
    LEFT JOIN employee_skill es ON e.id = es.employee_id
    LEFT JOIN skill s ON es.skill_id = s.id
    WHERE c.id = #{id}
</select>

3. 分次查詢+Stream處理

還有一種方式是通過分次(次數(shù)為關(guān)聯(lián)表的個(gè)數(shù))查詢關(guān)聯(lián)表后,再使用Stream流組裝數(shù)據(jù),下面是通過分次查詢+Stream處理查詢公司詳情信息的方法:

@Service
public class CompanyService {
    
    public Company getCompanyWithDetails(Long companyId) {
        // 1. 查詢公司
        Company company = companyMapper.selectById(companyId);
        if (company == null) return null;
        
        // 2. 查詢部門
        List<Department> departments = departmentMapper.selectByCompanyId(companyId);
        
        // 3. 查詢員工(批量查詢避免N+1)
        List<Long> deptIds = departments.stream()
            .map(Department::getId)
            .collect(Collectors.toList());
        List<Employee> employees = employeeMapper.selectByDeptIds(deptIds);
        
        // 4. 使用Stream組裝數(shù)據(jù)
        Map<Long, List<Employee>> employeeMap = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDeptId));
        
        departments.forEach(dept -> 
            dept.setEmployees(employeeMap.getOrDefault(dept.getId(), new ArrayList<>()))
        );
        
        company.setDepartments(departments);
        return company;
    }
}

4. 性能對(duì)比

同樣的業(yè)務(wù)下(查詢公司詳情信息)他們的性能對(duì)比表如下:

方面JOIN+Collection分次查詢+Stream
數(shù)據(jù)庫查詢次數(shù)1次3次
網(wǎng)絡(luò)開銷中等
數(shù)據(jù)庫壓力單次復(fù)雜查詢多次簡單查詢
內(nèi)存占用可能有重復(fù)數(shù)據(jù)數(shù)據(jù)更緊湊
響應(yīng)時(shí)間穩(wěn)定但可能較長可能更快(并行查詢)

總結(jié):建議在管理后臺(tái)數(shù)據(jù)展示這樣的小數(shù)據(jù)量場(chǎng)景使用Join+Collection方案,在API接口大數(shù)據(jù)量這樣較大數(shù)據(jù)量使用分次查詢+Stream方案。

到此這篇關(guān)于MyBatis一對(duì)多關(guān)系映射方式小結(jié)的文章就介紹到這了,更多相關(guān)MyBatis一對(duì)多關(guān)系映射內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA實(shí)現(xiàn)PDF轉(zhuǎn)HTML文檔的示例代碼

    JAVA實(shí)現(xiàn)PDF轉(zhuǎn)HTML文檔的示例代碼

    本文是基于PDF文檔轉(zhuǎn)PNG圖片,然后進(jìn)行圖片拼接,拼接后的圖片轉(zhuǎn)為base64字符串,然后拼接html文檔寫入html文件實(shí)現(xiàn)PDF文檔轉(zhuǎn)HTML文檔,感興趣的可以了解一下
    2021-05-05
  • Java 字符串連接的性能問題分析

    Java 字符串連接的性能問題分析

    這篇文章主要介紹了Java 字符串連接的性能問題分析的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • java獲取各種路徑的基本方法

    java獲取各種路徑的基本方法

    這篇文章主要為大家詳細(xì)介紹了java獲取各種路徑的基本方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java 括號(hào)匹配問題案例詳解

    Java 括號(hào)匹配問題案例詳解

    這篇文章主要介紹了Java 括號(hào)匹配問題案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • java微信開發(fā)API第二步 獲取和回復(fù)消息

    java微信開發(fā)API第二步 獲取和回復(fù)消息

    這篇文章主要為大家詳細(xì)介紹了java微信開發(fā)API第二步,獲取消息和回復(fù)消息,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Java函數(shù)式接口Supplier接口實(shí)例詳解

    Java函數(shù)式接口Supplier接口實(shí)例詳解

    這篇文章主要介紹了Java函數(shù)式接口Supplier接口實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • 詳解OAuth2 Token 一定要放在請(qǐng)求頭中嗎

    詳解OAuth2 Token 一定要放在請(qǐng)求頭中嗎

    這篇文章主要介紹了詳解OAuth2 Token 一定要放在請(qǐng)求頭中嗎,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Springboot項(xiàng)目中kaptcha驗(yàn)證碼的使用方式

    Springboot項(xiàng)目中kaptcha驗(yàn)證碼的使用方式

    這篇文章主要介紹了Springboot項(xiàng)目中kaptcha驗(yàn)證碼的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • maven搭建spring項(xiàng)目(圖文教程)

    maven搭建spring項(xiàng)目(圖文教程)

    下面小編就為大家?guī)硪黄猰aven搭建spring項(xiàng)目(圖文教程)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • 深度解析Java 21中虛擬線程的工作原理與實(shí)際應(yīng)用

    深度解析Java 21中虛擬線程的工作原理與實(shí)際應(yīng)用

    Java 21的發(fā)布標(biāo)志著Java并發(fā)編程的一個(gè)重要里程碑,本文將深入探討虛擬線程的工作原理,優(yōu)勢(shì)以及在實(shí)際項(xiàng)目中的應(yīng)用,文中的示例代碼講解詳細(xì),有需要的小伙伴可以了解下
    2025-09-09

最新評(píng)論