MyBatis 結(jié)果映射的幾種實現(xiàn)方式
一、 什么是結(jié)果映射? ??
在我們的 Java 應(yīng)用程序中,數(shù)據(jù)通常以對象(JavaBeans/POJOs)的形式存在。然而,關(guān)系型數(shù)據(jù)庫是以行和列的形式存儲數(shù)據(jù)。當我們從數(shù)據(jù)庫查詢數(shù)據(jù)時,得到的是一個包含多行多列的結(jié)果集(ResultSet)。
結(jié)果映射(Result Mapping) 就是 MyBatis 中定義如何將數(shù)據(jù)庫查詢結(jié)果集中的列(Columns)映射到 Java 對象的屬性(Properties)上的規(guī)則和過程。它是解決數(shù)據(jù)庫中心的數(shù)據(jù)表示(表結(jié)構(gòu))與應(yīng)用程序中心的對象模型(類結(jié)構(gòu))之間“阻抗失配”問題的核心機制。
簡單來說: 告訴 MyBatis,數(shù)據(jù)庫查出來的 user_id
這一列的值,應(yīng)該賦給 User
這個 Java 類的 userId
這個屬性。
二、 為何需要結(jié)果映射? ??
- 命名差異:數(shù)據(jù)庫列名常用下劃線命名法(
snake_case
,如order_id
),而 Java 屬性常用駝峰命名法(camelCase
,如orderId
)。需要轉(zhuǎn)換。 - 類型轉(zhuǎn)換 ??:數(shù)據(jù)庫類型(如
VARCHAR
,TIMESTAMP
,NUMBER
)需要轉(zhuǎn)換為相應(yīng)的 Java 類型(如String
,java.util.Date
,Integer
,BigDecimal
)。 - 復(fù)雜關(guān)系:實際應(yīng)用中,數(shù)據(jù)往往不是扁平的。一個對象可能包含另一個對象(一對一/多對一),或者包含一個對象集合(一對多)。需要機制來處理這些關(guān)聯(lián)。
- 顯式控制 ??:有時自動映射無法滿足需求,需要更精細地控制映射過程,例如列名和屬性名差異很大,或需要使用自定義的類型處理器(TypeHandler)。
三、 如何映射?(基礎(chǔ))?
MyBatis 提供了多種方式進行結(jié)果映射:
1、 別名映射 ??
這是最直接的方式,在 SQL 語句中使用 AS
關(guān)鍵字為查詢的列指定別名,讓別名與 Java 對象的屬性名完全一致。
Java Bean (User.java
)
public class User { private Integer userId; private String userName; private String userEmail; // getters and setters... }
Mapper XML (UserMapper.xml
)
<select id="findUserById" resultType="com.yourcompany.domain.User"> SELECT user_id AS userId, -- 使用 AS 將列名映射到屬性名 user_name AS userName, email AS userEmail -- 列名和屬性名不一致時必須用 AS FROM users WHERE user_id = #{id} </select>
- 優(yōu)點:非常明確,SQL 本身就定義了映射關(guān)系。
- 缺點:SQL 語句會變得冗長,尤其當字段很多時。SQL 摻雜了部分映射邏輯。
2、 駝峰命名自動映射??
這是 MyBatis 提供的一個便捷功能。開啟后,MyBatis 會自動嘗試將下劃線命名法的列(column_name
)映射到駝峰命名法的屬性(columnName
)。
Java Bean (User.java
) - 同上
Mapper XML (UserMapper.xml
)
<select id="findUserById" resultType="com.yourcompany.domain.User"> SELECT user_id, -- 無需 AS user_name, email -- 如果屬性名是 email,也能自動映射 FROM users WHERE user_id = #{id} </select>
(注意:如果 Java 屬性名不是標準的駝峰轉(zhuǎn)換,駝峰映射也無法自動處理)
如何開啟?
MyBatis 配置文件 (mybatis-config.xml
):
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
Spring Boot 配置文件 (application.properties
或 application.yml
):
# application.properties mybatis.configuration.map-underscore-to-camel-case=true
# application.yml mybatis: configuration: map-underscore-to-camel-case: true
通過 Java 配置 (如 Spring):
@Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // ... 其他配置 org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setMapUnderscoreToCamelCase(true); // 開啟駝峰映射 factoryBean.setConfiguration(configuration); // ... 其他配置 return factoryBean.getObject(); }
- 優(yōu)點:保持 SQL 簡潔 ?,配置一次全局生效,是目前推薦的主流簡單映射方式。
- 缺點:需要配置開啟;對于非標準駝峰映射的場景無能為力。
3、 resultMap 顯式映射 ???
當自動映射(包括駝峰)無法滿足需求,或者需要處理復(fù)雜類型、關(guān)聯(lián)查詢時,resultMap
是最強大、最靈活的武器。它允許你顯式地、精確地定義列與屬性之間的映射關(guān)系。
Java Bean (User.java
) - 同上
Mapper XML (UserMapper.xml
)
定義 <resultMap>
:
<resultMap id="userResultMap" type="com.yourcompany.domain.User"> <!-- <id> 標簽用于映射主鍵字段,有助于 MyBatis 性能優(yōu)化 --> <id property="userId" column="user_id" /> <!-- <result> 標簽用于映射普通字段 --> <result property="userName" column="user_name"/> <result property="userEmail" column="email"/> <!-- 可以指定 javaType 和 jdbcType,通常 MyBatis 能自動推斷 --> <!-- <result property="status" column="user_status" javaType="java.lang.Integer" jdbcType="INTEGER"/> --> </resultMap>
在 <select>
標簽中使用 resultMap
屬性引用:
<select id="findUserById" resultMap="userResultMap"> SELECT user_id, user_name, email FROM users WHERE user_id = #{id} </select>
<resultMap>
屬性:id
: 在當前 Mapper XML 命名空間內(nèi)的唯一標識符。type
: 映射的目標 Java 類的完全限定名或 MyBatis 配置的別名。
子標簽
<id>
和<result>
屬性:property
: Java 對象的屬性名。column
: SQL 查詢結(jié)果集中的列名(或別名)。javaType
: Java 屬性的完整類名或別名(通??墒÷裕?/li>jdbcType
: 數(shù)據(jù)庫列的 JDBC 類型(枚舉org.apache.ibatis.type.JdbcType
)。typeHandler
: 指定自定義的類型處理器。
優(yōu)點:控制力最強 ??!能處理各種復(fù)雜映射場景,是實現(xiàn)高級映射的基礎(chǔ)。代碼更清晰,SQL 保持純凈。
缺點:配置相對繁瑣,增加了 XML 的代碼量。
四、 高級結(jié)果映射 (處理關(guān)聯(lián)關(guān)系) ??
當查詢結(jié)果需要映射到包含其他對象或?qū)ο蠹系膹?fù)雜 Java 對象時,就需要使用 resultMap
的高級特性:<association>
和 <collection>
。
場景設(shè)定:
- 實體類:
Department.java
: 包含id
,name
屬性。Employee.java
: 包含id
,name
,email
,department
(Department 對象),departmentId
(Integer)。
- 數(shù)據(jù)庫表:
departments
(id PK, dept_name)employees
(id PK, emp_name, emp_email, dept_id FK references departments.id)
1、<association>(處理 “有一個” 關(guān)系 - To-One) ????
用于映射一個對象中包含的另一個對象,通常對應(yīng)數(shù)據(jù)庫中的 一對一 或 多對一 關(guān)系。
兩種主要實現(xiàn)方式:
1. 嵌套 Select 查詢 (Nested Select)
- 思路: 先查詢主對象(Employee),然后根據(jù)外鍵(
dept_id
),再執(zhí)行一個單獨的 SQL 查詢獲取關(guān)聯(lián)對象(Department)。 - Mapper XML:
<select id="findDepartmentById" resultType="com.yourcompany.domain.Department">...</select> <resultMap id="employeeWithDeptSelectMap" type="com.yourcompany.domain.Employee"> <id property="id" column="id"/> <result property="name" column="emp_name"/> <!-- association: 映射 department 屬性 --> <association property="department" javaType="com.yourcompany.domain.Department" column="dept_id" select="findDepartmentById" /> <!-- column: 傳遞給 select 查詢的參數(shù)列 --> <!-- select: 指定用于查詢關(guān)聯(lián)對象的 select 語句 ID --> <!-- fetchType="lazy": 可配置懶加載 --> </resultMap> <select id="findEmployeeByIdWithDeptSelect" resultMap="employeeWithDeptSelectMap">...</select>
- 優(yōu)點: 簡單直觀,SQL 語句分離,支持懶加載。
- 缺點: N+1 查詢問題!性能可能很差 ??,通常不推薦使用 ?。
2. 嵌套結(jié)果映射 (Nested Result Map) ?
- 思路: 使用 SQL 的
JOIN
操作一次性查詢出所有需要的數(shù)據(jù),然后在resultMap
中定義嵌套結(jié)構(gòu)來映射連接后的結(jié)果。 - Mapper XML:
<resultMap id="employeeWithDeptNestedMap" type="com.yourcompany.domain.Employee"> <id property="id" column="emp_id"/> <result property="name" column="emp_name"/> <result property="departmentId" column="dept_id"/> <!-- association: 映射 department 屬性 --> <association property="department" javaType="com.yourcompany.domain.Department"> <!-- 嵌套定義 Department 的映射規(guī)則 --> <id property="id" column="dept_id"/> <result property="name" column="dept_name"/> </association> </resultMap> <select id="findEmployeeByIdWithDeptNested" resultMap="employeeWithDeptNestedMap"> SELECT e.id AS emp_id, e.emp_name, e.emp_email, e.dept_id, d.id AS dept_id, d.dept_name -- 查詢關(guān)聯(lián)表的列,注意別名 FROM employees e LEFT JOIN departments d ON e.dept_id = d.id WHERE e.id = #{empId} </select>
- 優(yōu)點: 性能好 ??,只需執(zhí)行一次 SQL 查詢。結(jié)構(gòu)清晰。這是處理關(guān)聯(lián)關(guān)系推薦的方式 ?。
- 缺點: SQL 語句可能因 JOIN 變得復(fù)雜;不支持懶加載(但通常性能好就不需要了)。
2、<collection>(處理 “有很多” 關(guān)系 - To-Many) ????
用于映射一個對象中包含的對象集合,通常對應(yīng)數(shù)據(jù)庫中的 一對多 關(guān)系。
同樣有兩種實現(xiàn)方式:
1. 嵌套 Select 查詢 (Nested Select)
- 思路: 先查詢主對象(Department),然后根據(jù)主鍵(
id
),再執(zhí)行一個單獨的 SQL 查詢獲取關(guān)聯(lián)的對象集合(List)。 - Java Bean (
Department.java
): (需要有List<Employee> employees;
屬性) - Mapper XML:
<select id="findEmployeesByDeptId" resultType="com.yourcompany.domain.Employee">...</select> <resultMap id="deptWithEmployeesSelectMap" type="com.yourcompany.domain.Department"> <id property="id" column="id"/> <result property="name" column="dept_name"/> <!-- collection: 映射 employees 集合屬性 --> <collection property="employees" ofType="com.yourcompany.domain.Employee" column="id" select="findEmployeesByDeptId" /> <!-- column: 傳遞給 select 查詢的參數(shù)列 --> <!-- select: 指定用于查詢關(guān)聯(lián)集合的 select 語句 ID --> <!-- ofType: 指定集合中元素的類型 --> <!-- fetchType="lazy": 支持懶加載 --> </resultMap> <select id="findDeptByIdWithEmployeesSelect" resultMap="deptWithEmployeesSelectMap">...</select>
- 優(yōu)點: 簡單直觀,SQL 分離,支持懶加載。
- 缺點: N+1 查詢問題!性能問題嚴重 ??,通常不推薦使用 ?。
2. 嵌套結(jié)果映射 (Nested Result Map) ?
- 思路: 使用 SQL 的
LEFT JOIN
一次性查詢出部門及其所有員工的信息。MyBatis 的resultMap
機制能智能地將重復(fù)的主對象信息合并,并將關(guān)聯(lián)的從對象信息收集到集合中。 - Mapper XML:
<resultMap id="employeeBaseMap" type="com.yourcompany.domain.Employee">...</resultMap> <resultMap id="deptWithEmployeesNestedMap" type="com.yourcompany.domain.Department"> <id property="id" column="dept_id"/> <!-- 部門 ID 是主鍵 --> <result property="name" column="dept_name"/> <!-- collection: 映射 employees 集合屬性 --> <collection property="employees" ofType="com.yourcompany.domain.Employee" resultMap="employeeBaseMap" /> <!-- ofType: 指定集合元素類型 --> <!-- resultMap: 引用 Employee resultMap 來映射集合中的每個對象 --> <!-- 或者可以直接在 collection 內(nèi)部定義映射規(guī)則 --> </resultMap> <select id="findDeptByIdWithEmployeesNested" resultMap="deptWithEmployeesNestedMap"> SELECT d.id AS dept_id, d.dept_name, e.id AS emp_id, e.emp_name, e.emp_email FROM departments d LEFT JOIN employees e ON d.id = e.dept_id WHERE d.id = #{deptId} </select>
- 優(yōu)點: 性能好 ??,只需一次 SQL 查詢。MyBatis 自動處理結(jié)果聚合。映射邏輯集中。這是處理一對多關(guān)系推薦的方式 ?。
- 缺點: SQL 語句可能較復(fù)雜;返回的數(shù)據(jù)量可能較大(主表信息會重復(fù))。
五、 選擇哪種映射方式? ??
- 簡單場景(列名與屬性名一致或符合駝峰規(guī)則):優(yōu)先使用駝峰自動映射 ??。
- 列名與屬性名不一致/不規(guī)則:少數(shù)幾個字段用 SQL 別名
AS
??;較多或想保持 SQL 純凈,使用resultMap
???。 - 需要類型轉(zhuǎn)換/自定義 TypeHandler:必須使用
resultMap
??。 - 處理一對一/多對一/一對多關(guān)聯(lián)關(guān)系:必須使用
resultMap
的<association>
或<collection>
??。強烈推薦使用 嵌套結(jié)果映射 (Nested Result Map) 方式(基于 JOIN)?,避免 N+1 問題。只有在明確需要懶加載且能接受其潛在性能影響時,才考慮嵌套 Select。
六、 總結(jié) ??
MyBatis 的結(jié)果映射機制是其強大功能的核心之一。從簡單的別名、駝峰自動映射到強大的 resultMap
(包括處理復(fù)雜關(guān)聯(lián)的 <association>
和 <collection>
),它提供了靈活多樣的手段來連接數(shù)據(jù)庫表結(jié)構(gòu)和 Java 對象模型。理解并熟練運用這些映射方式,特別是掌握 resultMap
的高級用法并優(yōu)先選擇嵌套結(jié)果映射來處理關(guān)聯(lián),對于編寫高效、可維護的 MyBatis 應(yīng)用至關(guān)重要。
到此這篇關(guān)于MyBatis 結(jié)果映射的幾種實現(xiàn)方式的文章就介紹到這了,更多相關(guān)MyBatis 結(jié)果映射內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MyBatis延遲加載、關(guān)聯(lián)查詢與結(jié)果映射的實現(xiàn)原理解析
- 深度分析MybatisPlus查詢結(jié)果映射失敗@TableField失效解決辦法
- MyBatis 結(jié)果映射的兩種方式
- MyBatis結(jié)果映射(ResultMap)的使用
- MyBatis動態(tài)SQL、模糊查詢與結(jié)果映射操作過程
- Java中MyBatis的結(jié)果映射詳解
- MyBatis中的SQL映射文件配置結(jié)果映射的操作指南
- 關(guān)于MyBatis結(jié)果映射的實例總結(jié)
- 基于mybatis查詢結(jié)果映射不到對象的處理
相關(guān)文章
以Json形式的數(shù)據(jù)格式實現(xiàn)JMeter參數(shù)化
本文以小項目學院管理系統(tǒng)為例,給大家分享以Json形式的數(shù)據(jù)格式實現(xiàn)JMeter參數(shù)化的相關(guān)知識,包括添加元件操作步驟及使用用戶參數(shù)組件實現(xiàn)參數(shù)化的方法,感興趣的朋友跟隨小編一起看看吧2021-05-05Javaweb監(jiān)聽器實例之統(tǒng)計在線人數(shù)
這篇文章主要為大家詳細介紹了Javaweb監(jiān)聽器實例之統(tǒng)計在線人數(shù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11Spring中為bean指定InitMethod和DestroyMethod的執(zhí)行方法
在Spring中,那些組成應(yīng)用程序的主體及由Spring IoC容器所管理的對象,被稱之為bean,接下來通過本文給大家介紹Spring中為bean指定InitMethod和DestroyMethod的執(zhí)行方法,感興趣的朋友一起看看吧2021-11-11Java日期毫秒值和常見日期時間格式相互轉(zhuǎn)換方法
這篇文章主要給大家介紹了關(guān)于Java日期毫秒值和常見日期時間格式相互轉(zhuǎn)換的相關(guān)資料,在Java的日常開發(fā)中,會隨時遇到需要對時間處理的情況,文中給出了詳細的示例代碼,需要的朋友可以參考下2023-07-07