一、SQL 映射文件核心元素
MyBatis 映射文件的頂級元素(按定義順序):
cache
:命名空間的緩存配置。cache-ref
:引用其他命名空間的緩存。resultMap
:自定義結果集映射。sql
:可重用的 SQL 片段。insert
、update
、delete
:數據操作語句。select
:查詢語句。
二、參數傳遞與處理
1. 單參數
-基礎類型/字符串:
<select id="selectUser" resultType="User" parameterType="int">SELECT * FROM user WHERE id = #{id}
</select>
-POJO 對象:
<insert id="insertUser" parameterType="User">INSERT INTO user (name, email) VALUES (#{name}, #{email})
</insert>
2. 多參數
- 默認 param1
, param2
(不推薦):
<select id="selectUser" resultType="User">SELECT * FROM user WHERE id = #{param1} AND name = #{param2}
</select>
- @Param
注解(推薦):
User selectUser(@Param("id") int id, @Param("name") String name);
<select id="selectUser" resultType="User">SELECT * FROM user WHERE id = #{id} AND name = #{name}
</select>
3. 復雜參數
- Map 類型:
<select id="selectUserByMap" resultType="User" parameterType="map">SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>
- 混合參數(POJO + @Param
):
List<User> selectUsers(@Param("role") String role, User user);
<select id="selectUsers" resultType="User">SELECT * FROM user WHERE role = #{role} AND age = #{user.age}
</select>
三、主鍵生成與回填
1. 自增主鍵(如 MySQL)
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">INSERT INTO user (name, email) VALUES (#{name}, #{email})
</insert>
useGeneratedKeys="true"
:啟用 JDBC 的自動生成主鍵。keyProperty="id"
:將生成的主鍵賦值給對象的id
屬性。
2. 非自增主鍵(如 Oracle)
<insert id="insertUser"><selectKey keyProperty="id" resultType="int" order="BEFORE">SELECT MAX(id) + 1 FROM user</selectKey>INSERT INTO user (id, name) VALUES (#{id}, #{name})
</insert>
order="BEFORE"
:先執行selectKey
生成主鍵,再插入數據。
四、結果映射(resultMap
)
1. 基礎映射
<resultMap id="userResultMap" type="User"><id property="id" column="user_id" /><result property="name" column="user_name" />
</resultMap>
2. 關聯對象(一對一)
<resultMap id="userWithRoleMap" type="User"><association property="role" javaType="Role"><id property="roleId" column="role_id" /><result property="roleName" column="role_name" /></association>
</resultMap>
3. 集合映射(一對多)
<resultMap id="userWithOrdersMap" type="User"><collection property="orders" ofType="Order"><id property="orderId" column="order_id" /><result property="orderNo" column="order_no" /></collection>
</resultMap>
五、動態 SQL
1. 條件查詢(<if>
+ <where>
)
<select id="selectUser" resultType="User">SELECT * FROM user<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
2. 循環遍歷(<foreach>
)
<select id="selectUsersByIds" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
1. 參數是 List
當方法參數直接傳遞一個 List
時,MyBatis 默認將其封裝為 Map
,鍵為 list
。
示例代碼:
// DAO 方法
List<User> selectUsersByIds(@Param("ids") List<Integer> ids);
<!-- XML 映射 -->
<select id="selectUsersByIds" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
關鍵點:
collection="ids"
:對應@Param("ids")
注解的參數名。item="id"
:遍歷的每個元素變量名。open="("
和close=")"
:包裹生成的 SQL 片段。separator=","
:元素之間的分隔符。
2. 參數是 Set
Set
的處理方式與 List
類似,MyBatis 會自動將其轉換為 List
處理。
示例代碼:
// DAO 方法
List<User> selectUsersByNames(@Param("names") Set<String> names);
<!-- XML 映射 -->
<select id="selectUsersByNames" resultType="User">SELECT * FROM user WHERE name IN<foreach collection="names" item="name" open="(" separator="," close=")">#{name}</foreach>
</select>
3. 參數是數組
當直接傳遞數組時,collection
屬性需指定為 array
。
示例代碼:
// DAO 方法
List<User> selectUsersByIds(int[] ids);
<!-- XML 映射 -->
<select id="selectUsersByIds" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
4. 參數是 Map
中的集合
如果參數是 Map
,需通過 @Param
指定鍵名。
示例代碼:
// DAO 方法
List<User> selectUsers(@Param("data") Map<String, Object> data);
<!-- XML 映射 -->
<select id="selectUsers" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="data.ids" item="id" open="(" separator="," close=")">#{id}</foreach>AND name IN<foreach collection="data.names" item="name" open="(" separator="," close=")">#{name}</foreach>
</select>
5. 批量插入示例
使用 <foreach>
實現批量插入:
// DAO 方法
void batchInsertUsers(@Param("users") List<User> users);
<!-- XML 映射 -->
<insert id="batchInsertUsers">INSERT INTO user (name, email) VALUES<foreach collection="users" item="user" separator=",">(#{user.name}, #{user.email})</foreach>
</insert>
6. 遍歷 Map
類型
當集合元素是 Map
時,index
和 item
分別代表鍵和值。
示例代碼:
// DAO 方法
void insertUserRoles(@Param("roles") Map<Integer, String> roles);
<!-- XML 映射 -->
<insert id="insertUserRoles">INSERT INTO role (user_id, role_name) VALUES<foreach collection="roles" index="userId" item="roleName" separator=",">(#{userId}, #{roleName})</foreach>
</insert>
7.關鍵注意事項
-
參數類型匹配
- 單參數集合需通過
@Param
顯式命名。 - 多參數需用
@Param
避免混淆。
- 單參數集合需通過
-
安全與性能
- 始終使用
#{}
占位符(防止 SQL 注入)。 - 批量操作時,注意數據庫的 SQL 語句長度限制。
- 始終使用
-
動態 SQL 靈活性
- 結合
<if>
標簽實現條件遍歷:<foreach collection="ids" item="id"><if test="id != null">#{id}</if> </foreach>
- 結合
8.總結
參數類型 | collection 值 | 示例場景 |
---|---|---|
List | @Param 指定的名稱 | IN 查詢、批量插入 |
Set | @Param 指定的名稱 | 去重后的 IN 查詢 |
數組 | array | 原生數組參數的 IN 查詢 |
Map | map.key 或 @Param | 復雜參數組合的動態查詢 |
通過合理使用 <foreach>
,可以高效處理集合類參數,簡化批量操作和動態 SQL 的編寫。
3. 分支選擇(<choose>
)
<select id="selectUserByChoose" resultType="User">SELECT * FROM user<where><choose><when test="id != null">id = #{id}</when><otherwise>status = 1</otherwise></choose></where>
</select>
六、高級特性
1. 分步查詢與延遲加載
- 分步查詢:
<resultMap id="catMap" type="Cat"><association property="owner" select="selectOwnerById" column="owner_id" /> </resultMap>
- 延遲加載配置:
<settings><setting name="lazyLoadingEnabled" value="true" /><setting name="aggressiveLazyLoading" value="false" /> </settings>
2. SQL 片段復用(<sql>
)
<sql id="userColumns">id, name, email</sql>
<select id="selectUser" resultType="User">SELECT <include refid="userColumns" /> FROM user
</select>
七、特殊處理
1. #{}
與 ${}
以下是 #{}
和 ${}
的核心區別及使用場景總結,結合動態表名/字段的示例說明:
1. #{}
與 ${}
的核心區別
特性 | #{} | ${} |
---|---|---|
原理 | 預編譯(PreparedStatement) | 字符串直接拼接(SQL 注入風險) |
安全性 | 防 SQL 注入(推薦) | 不安全(需嚴格校驗輸入) |
適用場景 | 參數值(如 WHERE 條件) | 動態表名、列名、排序字段等 |
示例 | WHERE id = #{id} | ORDER BY ${columnName} |
2. 動態表名與字段的示例
場景 1:動態表名(如多租戶系統)
<!-- 根據傳入的表名查詢數據 -->
<select id="selectByDynamicTable" resultType="User">SELECT * FROM ${tableName} WHERE id = #{id}
</select>
- 調用方式:
List<User> users = userDao.selectByDynamicTable("user_2023", 1001);
- 生成 SQL:
SELECT * FROM user_2023 WHERE id = ?
場景 2:動態排序字段
<!-- 根據傳入的排序字段動態排序 -->
<select id="selectUsersOrderBy" resultType="User">SELECT * FROM user ORDER BY ${orderByColumn} ${sortDirection}
</select>
- 調用方式:
List<User> users = userDao.selectUsersOrderBy("age", "DESC");
- 生成 SQL:
SELECT * FROM user ORDER BY age DESC
場景 3:動態列名(如選擇特定字段)
<!-- 選擇動態列 -->
<select id="selectDynamicColumns" resultType="map">SELECT ${columns} FROM user WHERE id = #{id}
</select>
- 調用方式:
Map<String, Object> result = userDao.selectDynamicColumns("name, email", 1001);
- 生成 SQL:
SELECT name, email FROM user WHERE id = ?
3. 安全注意事項
-
風險場景:如果用戶輸入未經校驗,直接使用
${}
可能導致 SQL 注入。// 危險示例:用戶輸入惡意表名 String tableName = "user; DROP TABLE user; --"; userDao.selectByDynamicTable(tableName, 1001);
生成 SQL:
SELECT * FROM user; DROP TABLE user; -- WHERE id = ?
-
防御措施:
- 白名單校驗:限制動態值的范圍。
// 只允許特定表名 if (!Arrays.asList("user", "employee").contains(tableName)) {throw new IllegalArgumentException("非法表名"); }
- 轉義特殊字符:過濾或轉義輸入中的特殊符號(如
'
,;
)。
- 白名單校驗:限制動態值的范圍。
4. 總結
場景 | 占位符 | 示例 | 安全性 |
---|---|---|---|
參數值(如 id ) | #{} | WHERE id = #{id} | 安全(推薦) |
動態表名 | ${} | FROM ${tableName} | 需校驗輸入(風險) |
動態列名/排序字段 | ${} | ORDER BY ${column} | 需校驗輸入(風險) |
原則:
- 優先使用
#{}
,確保安全性。 - 僅在必要時使用
${}
,并嚴格校驗輸入值。
2. 返回類型
1. 單對象:resultType="User"
- 作用:將單條數據庫記錄映射到一個 Java 對象(如
User
)。 - 規則:
- 數據庫列名需與 Java 對象屬性名一致(或通過別名匹配),否則需使用
resultMap
。 - 若查詢結果為多條記錄,會拋出異常(
TooManyResultsException
)。
- 數據庫列名需與 Java 對象屬性名一致(或通過別名匹配),否則需使用
- 示例:
<select id="selectUserById" resultType="User">SELECT * FROM user WHERE id = #{id} </select>
- 返回類型:
User
對象。 - 若未查詢到數據,返回
null
。
- 返回類型:
2. 集合:resultType="User"
- 作用:將多條數據庫記錄映射為
List<User>
。 - 規則:
- MyBatis 自動將多行結果封裝為
List
,無需額外配置。 - 若查詢結果為空,返回空列表(非
null
)。
- MyBatis 自動將多行結果封裝為
- 示例:
<select id="selectAllUsers" resultType="User">SELECT * FROM user </select>
- 返回類型:
List<User>
。 - 即使結果為空,返回
Collections.emptyList()
。
- 返回類型:
3. Map 類型
3.1 直接返回 Map
(resultType="map"
)
- 作用:將單條記錄映射為
Map<String, Object>
,鍵為列名,值為對應數據。 - 示例:
<select id="selectUserAsMap" resultType="map">SELECT id, name FROM user WHERE id = #{id} </select>
- 返回類型:
Map<String, Object>
,如{"id": 1, "name": "Alice"}
。
- 返回類型:
3.2 使用 @MapKey
注解返回 Map<K, V>
- 作用:將多條記錄映射為
Map<K, V>
,其中:- 鍵(K):由
@MapKey
指定的屬性值(如id
)。 - 值(V):對應的 Java 對象。
- 鍵(K):由
- 規則:
- 需在 DAO 接口方法上添加
@MapKey("屬性名")
。 - 查詢結果中指定的屬性值必須唯一,否則會覆蓋或拋出異常。
- 需在 DAO 接口方法上添加
- 示例:
@MapKey("id") Map<Integer, User> selectAllUsersAsMap();
<select id="selectAllUsersAsMap" resultType="User">SELECT * FROM user </select>
- 返回類型:
Map<Integer, User>
,鍵為User
的id
,值為對應的User
對象。
- 返回類型:
4.關鍵區別與注意事項
類型 | resultType 值 | 返回值類型 | 適用場景 |
---|---|---|---|
單對象 | User | User | 查詢單條記錄 |
集合 | User | List<User> | 查詢多條記錄 |
單條記錄的 Map | map | Map<String, Object> | 需要靈活訪問列名/值的場景 |
多條記錄的 Map | User + @MapKey | Map<K, User> | 以特定屬性為鍵,對象為值的映射 |
5.常見問題
-
字段名與屬性名不一致
- 使用
resultMap
或 SQL 別名(AS
)解決:<select id="selectUser" resultType="User">SELECT user_id AS id, user_name AS name FROM user </select>
- 使用
-
集合返回類型為
null
- MyBatis 默認返回空集合(
Collections.emptyList()
),而非null
。
- MyBatis 默認返回空集合(
-
@MapKey
的唯一性- 若指定的鍵屬性(如
id
)存在重復值,MyBatis 會保留最后一個匹配的對象,可能導致數據丟失。
- 若指定的鍵屬性(如
6.總結
- 單對象:直接使用
resultType="User"
。 - 集合:同樣使用
resultType="User"
,MyBatis 自動封裝為List
。 - Map:
- 單條記錄:
resultType="map"
。 - 多條記錄:結合
@MapKey
注解,指定鍵屬性。
- 單條記錄:
合理選擇 resultType
可簡化結果映射,提升開發效率。
八、總結
場景 | 解決方案 | 示例 |
---|---|---|
自增主鍵 | useGeneratedKeys="true" + keyProperty="id" | 插入后自動填充 id |
多參數查詢 | @Param 注解 | #{id} + #{name} |
字段名與屬性名映射 | resultMap | <result property="userName" column="user_name" /> |
動態條件查詢 | <if> + <where> | 按條件拼接 WHERE 子句 |
批量操作 | <foreach> | IN (1, 2, 3) |