MyBatis持久層實現
package com.example.usermanagement.mapper;import com.example.usermanagement.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;/*** 用戶Mapper接口* @Mapper: 標識這是MyBatis的Mapper接口*/
@Mapper
public interface UserMapper {// 插入用戶int insert(User user);// 根據ID刪除用戶int deleteById(Long id);// 更新用戶信息int update(User user);// 根據ID查詢用戶User selectById(Long id);// 根據用戶名查詢用戶User selectByUsername(String username);// 查詢所有用戶List<User> selectAll();// 分頁查詢用戶List<User> selectByPage(@Param("offset") Integer offset,@Param("limit") Integer limit);// 統計用戶總數int count();/*** 根據郵箱查詢用戶* @param email 郵箱地址* @return 用戶信息*/User selectByEmail(String email);List<User> searchByUsername(@Param("username") String username,@Param("offset") int offset,@Param("limit") int limit);int countByUsername(@Param("username") String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.usermanagement.mapper.UserMapper"><!-- 結果映射:定義數據庫字段與實體類屬性的映射關系 --><resultMap id="BaseResultMap" type="com.example.usermanagement.entity.User"><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="email" property="email"/><result column="phone" property="phone"/><result column="status" property="status"/><result column="score" property="score"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/></resultMap><!-- 基礎字段列表 --><sql id="Base_Column_List">id, username, password, email, phone, status, score, create_time, update_time</sql><!-- 插入用戶 --><!-- 原來的寫法 --><insert id="insert" parameterType="com.example.usermanagement.entity.User"useGeneratedKeys="true" keyProperty="id">INSERT INTO user (username, password, email, phone, status, score)VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})</insert><!-- 根據ID刪除 --><delete id="deleteById" parameterType="Long">DELETE FROM user WHERE id = #{id}</delete><!-- 更新用戶 --><update id="update" parameterType="com.example.usermanagement.entity.User">UPDATE user<set><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="email != null">email = #{email},</if><if test="phone != null">phone = #{phone},</if><if test="status != null">status = #{status},</if><if test="score != null">score = #{score},</if></set>WHERE id = #{id}</update><!-- 根據ID查詢 --><select id="selectById" parameterType="Long" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE id = #{id}</select><!-- 根據用戶名查詢 --><select id="selectByUsername" parameterType="String" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE username = #{username}</select><!-- 查詢所有用戶 --><select id="selectAll" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESC</select><!-- 分頁查詢 --><select id="selectByPage" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESCLIMIT #{offset}, #{limit}</select><!-- 統計總數 --><select id="count" resultType="int">SELECT COUNT(*) FROM user</select><!-- 根據郵箱查詢用戶 --><select id="selectByEmail" parameterType="String" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE email = #{email}</select><!-- 根據用戶名搜索(模糊查詢) --><select id="searchByUsername" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List" />FROM userWHERE username LIKE CONCAT('%', #{username}, '%')ORDER BY id DESCLIMIT #{offset}, #{limit}</select><!-- 統計搜索結果數量 --><select id="countByUsername" resultType="int">SELECT COUNT(*)FROM userWHERE username LIKE CONCAT('%', #{username}, '%')</select></mapper>
1. Mapper接口設計分析
1.1 接口聲明與注解
@Mapper
public interface UserMapper {// 方法定義
}
@Mapper注解詳解:
- MyBatis標識:告訴Spring這是MyBatis的Mapper接口
- 自動代理:Spring自動創建接口的實現類
- 依賴注入:可以被@Autowired注入到Service中
- 類型安全:編譯時檢查方法簽名
接口vs實現類:
// 傳統DAO實現
public class UserDaoImpl implements UserDao {// 需要手寫JDBC代碼
}// MyBatis Mapper
public interface UserMapper {// 只需要定義方法簽名,XML中寫SQL
}
1.2 方法命名規范
// 查詢類方法
User selectById(Long id);
User selectByUsername(String username);
List<User> selectAll();// 插入類方法
int insert(User user);// 更新類方法
int update(User user);// 刪除類方法
int deleteById(Long id);// 統計類方法
int count();
命名約定分析:
- select:查詢操作,返回實體或集合
- insert:插入操作,返回影響行數
- update:更新操作,返回影響行數
- delete:刪除操作,返回影響行數
- count:統計操作,返回數量
1.3 參數傳遞設計
// 單個參數(MyBatis自動處理)
User selectById(Long id);// 多個參數(使用@Param注解)
List<User> selectByPage(@Param("offset") Integer offset,@Param("limit") Integer limit);// 復雜對象參數
int insert(User user);
@Param注解作用:
- 參數命名:在XML中可以通過名稱引用參數
- 多參數支持:避免MyBatis的參數0、參數1命名
- 可讀性增強:XML中的參數名更有意義
2. XML映射文件結構解析
2.1 文件頭聲明
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
作用說明:
- XML聲明:指定版本和編碼
- DTD約束:定義XML文件的結構規范
- MyBatis驗證:確保XML語法正確
2.2 命名空間配置
<mapper namespace="com.example.usermanagement.mapper.UserMapper">
namespace重要性:
- 接口綁定:必須與Mapper接口全限定名一致
- 方法映射:XML中的SQL語句與接口方法一一對應
- 避免沖突:不同Mapper的同名方法不會沖突
3. 結果映射深度解析
3.1 ResultMap配置
<resultMap id="BaseResultMap" type="com.example.usermanagement.entity.User"><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="email" property="email"/><result column="phone" property="phone"/><result column="status" property="status"/><result column="score" property="score"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/>
</resultMap>
ResultMap核心作用:
字段映射:
- 數據庫字段 → Java屬性
create_time
→createTime
(下劃線轉駝峰)update_time
→updateTime
標簽區別:
<id>
:主鍵字段,MyBatis優化處理<result>
:普通字段映射
類型映射:
type="com.example.usermanagement.entity.User"
- 指定返回的Java對象類型
- MyBatis自動創建對象并設置屬性
3.2 字段列表復用
<sql id="Base_Column_List">id, username, password, email, phone, status, score, create_time, update_time
</sql>
設計優勢:
- DRY原則:避免重復定義字段列表
- 維護性:字段變更只需修改一處
- 可讀性:SQL語句更簡潔
使用方式:
<select id="selectById" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE id = #{id}
</select>
4. SQL語句詳細分析
4.1 插入語句設計
<insert id="insert" parameterType="com.example.usermanagement.entity.User"useGeneratedKeys="true" keyProperty="id">INSERT INTO user (username, password, email, phone, status, score)VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})
</insert>
核心配置解析:
useGeneratedKeys=“true”:
- 自動生成主鍵:數據庫自增ID
- 回寫主鍵:插入后ID自動設置到對象
- 便于后續操作:可以直接使用生成的ID
keyProperty=“id”:
- 指定主鍵屬性:告訴MyBatis將生成的主鍵值設置給哪個屬性
- 對象更新:插入后User對象的id字段會被自動設置
參數綁定:
#{username}, #{password}, #{email}
- 預編譯SQL:防止SQL注入
- 類型轉換:自動處理Java類型到數據庫類型的轉換
- 空值處理:null值自動處理
使用效果:
User user = new User();
user.setUsername("testuser");
user.setPassword("123456");userMapper.insert(user);
// 插入后,user.getId() 會自動獲得生成的主鍵值
System.out.println("生成的ID: " + user.getId());
4.2 動態更新語句
<update id="update" parameterType="com.example.usermanagement.entity.User">UPDATE user<set><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="email != null">email = #{email},</if><if test="phone != null">phone = #{phone},</if><if test="status != null">status = #{status},</if><if test="score != null">score = #{score},</if></set>WHERE id = #{id}
</update>
動態SQL優勢:
<set>
標簽作用:
- 智能組裝:自動處理SET子句
- 逗號處理:自動去除末尾多余的逗號
- 條件更新:只更新有值的字段
<if>
條件判斷:
<if test="username != null">username = #{username},</if>
- 空值檢查:只有非null字段才會被更新
- 靈活更新:支持部分字段更新
- 避免覆蓋:不會將現有數據置為null
生成的SQL示例:
-- 如果只更新用戶名和郵箱
UPDATE user SET username = ?, email = ? WHERE id = ?-- 如果只更新狀態
UPDATE user SET status = ? WHERE id = ?
4.3 分頁查詢實現
<select id="selectByPage" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESCLIMIT #{offset}, #{limit}
</select>
MySQL分頁語法:
- LIMIT offset, limit:MySQL特有語法
- offset:跳過的記錄數
- limit:返回的記錄數
分頁計算:
// 第1頁,每頁10條:LIMIT 0, 10
// 第2頁,每頁10條:LIMIT 10, 10
// 第3頁,每頁10條:LIMIT 20, 10
Integer offset = (pageNum - 1) * pageSize;
排序策略:
ORDER BY id DESC
- ID倒序:最新數據在前
- 穩定排序:確保分頁結果一致性
4.4 模糊搜索實現
<select id="searchByUsername" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE username LIKE CONCAT('%', #{username}, '%')ORDER BY id DESCLIMIT #{offset}, #{limit}
</select>
LIKE查詢分析:
CONCAT函數:
- 字符串拼接:MySQL的CONCAT函數
- 防止SQL注入:參數化查詢
- 跨數據庫:不同數據庫有不同語法
不同數據庫的寫法:
<!-- MySQL -->
WHERE username LIKE CONCAT('%', #{username}, '%')<!-- Oracle -->
WHERE username LIKE '%' || #{username} || '%'<!-- SQL Server -->
WHERE username LIKE '%' + #{username} + '%'
性能考慮:
-- 前置通配符影響索引
WHERE username LIKE '%zhang%' -- 不能使用索引-- 后置通配符可以使用索引
WHERE username LIKE 'zhang%' -- 可以使用索引
4.5 統計查詢
<select id="count" resultType="int">SELECT COUNT(*) FROM user
</select><select id="countByUsername" resultType="int">SELECT COUNT(*)FROM userWHERE username LIKE CONCAT('%', #{username}, '%')
</select>
resultType vs resultMap:
- resultType=“int”:直接返回基本類型
- resultMap:返回復雜對象類型
- 自動轉換:MyBatis自動處理類型轉換
5. 參數傳遞機制
5.1 單參數傳遞
User selectById(Long id);
<select id="selectById" parameterType="Long" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE id = #{id}
</select>
單參數特點:
- 自動識別:MyBatis自動識別參數類型
- 直接引用:XML中直接使用
#{參數名}
- parameterType可選:通常可以省略
5.2 多參數傳遞
List<User> selectByPage(@Param("offset") Integer offset,@Param("limit") Integer limit);
<select id="selectByPage" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESCLIMIT #{offset}, #{limit}
</select>
@Param注解必要性:
// 不使用@Param(不推薦)
List<User> selectByPage(Integer offset, Integer limit);
// XML中需要使用:#{param1}, #{param2} 或 #{0}, #{1}// 使用@Param(推薦)
List<User> selectByPage(@Param("offset") Integer offset,@Param("limit") Integer limit);
// XML中可以使用:#{offset}, #{limit}
5.3 對象參數傳遞
int insert(User user);
<insert id="insert" parameterType="com.example.usermanagement.entity.User">INSERT INTO user (username, password, email, phone, status, score)VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})
</insert>
對象屬性訪問:
- 直接訪問:
#{username}
等價于user.getUsername()
- 嵌套對象:
#{address.city}
訪問嵌套屬性 - 類型安全:編譯時檢查屬性是否存在
6. MyBatis配置優化
6.1 application.yml中的MyBatis配置
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.example.usermanagement.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置說明:
- mapper-locations:XML文件位置
- type-aliases-package:實體類包路徑
- map-underscore-to-camel-case:自動駝峰轉換
- log-impl:SQL執行日志
6.2 自動駝峰轉換效果
// 數據庫字段 → Java屬性
create_time → createTime
update_time → updateTime
user_name → userName
開啟前后對比:
<!-- 未開啟駝峰轉換 -->
<resultMap id="UserResultMap" type="User"><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/>
</resultMap><!-- 開啟駝峰轉換后 -->
<!-- 可以省略ResultMap,MyBatis自動映射 -->
<select id="selectById" resultType="User">SELECT * FROM user WHERE id = #{id}
</select>
7. 性能優化要點
7.1 索引使用建議
-- 為常用查詢字段建索引
CREATE INDEX idx_username ON user(username);
CREATE INDEX idx_email ON user(email);
CREATE INDEX idx_status ON user(status);-- 復合索引
CREATE INDEX idx_status_create_time ON user(status, create_time);
7.2 分頁性能優化
<!-- 大數據量分頁優化 -->
<select id="selectByPageOptimized" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM user WHERE id > #{lastId}ORDER BY idLIMIT #{limit}
</select>
游標分頁 vs 傳統分頁:
-- 傳統分頁(深分頁性能差)
SELECT * FROM user ORDER BY id LIMIT 100000, 10;-- 游標分頁(性能穩定)
SELECT * FROM user WHERE id > 100000 ORDER BY id LIMIT 10;
7.3 批量操作支持
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">INSERT INTO user (username, password, email)VALUES<foreach collection="list" item="user" separator=",">(#{user.username}, #{user.password}, #{user.email})</foreach>
</insert><!-- 批量刪除 -->
<delete id="batchDelete" parameterType="list">DELETE FROM user WHERE id IN<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
8. 常見問題與解決方案
8.1 SQL注入防護
<!-- 安全的參數綁定 -->
WHERE username = #{username} <!-- 推薦:預編譯SQL --><!-- 危險的字符串拼接 -->
WHERE username = '${username}' <!-- 不推薦:SQL注入風險 -->
#{}與${}區別:
- #{}:預編譯參數,防SQL注入
- ${}:字符串替換,有注入風險
8.2 空值處理
<update id="update">UPDATE user<set><if test="username != null and username != ''">username = #{username},</if><if test="email != null and email != ''">email = #{email},</if></set>WHERE id = #{id}
</update>
8.3 數據庫兼容性
<!-- MySQL -->
<select id="selectByPage" resultMap="BaseResultMap">SELECT * FROM user LIMIT #{offset}, #{limit}
</select><!-- Oracle -->
<select id="selectByPage" resultMap="BaseResultMap">SELECT * FROM (SELECT ROWNUM rn, t.* FROM user t WHERE ROWNUM <= #{offset} + #{limit}) WHERE rn > #{offset}
</select>
9. 高級特性應用
9.1 動態SQL復雜示例
<select id="searchUsers" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM user<where><if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="email != null and email != ''">AND email = #{email}</if><if test="status != null">AND status = #{status}</if><if test="minScore != null">AND score >= #{minScore}</if><if test="maxScore != null">AND score <= #{maxScore}</if></where>ORDER BY <choose><when test="sortBy == 'score'">score DESC</when><when test="sortBy == 'createTime'">create_time DESC</when><otherwise>id DESC</otherwise></choose>
</select>
9.2 結果集嵌套
<!-- 一對多關聯查詢 -->
<resultMap id="UserWithOrdersMap" type="User"><id column="id" property="id"/><result column="username" property="username"/><collection property="orders" ofType="Order"><id column="order_id" property="id"/><result column="order_amount" property="amount"/></collection>
</resultMap>
- 接口簡潔:只需定義方法簽名
- SQL分離:業務邏輯與SQL解耦
- 類型安全:編譯時檢查
- 動態SQL:靈活的條件查詢
- 結果映射:自動對象轉換
- 參數綁定:防SQL注入
-
預編譯SQL:性能優化
-
結果緩存:二級緩存支持
-
批量操作:減少數據庫交互
-
分頁查詢:大數據量處理
-
XML配置:SQL變更無需重編譯
-
代碼復用:SQL片段共享
-
規范統一:標準化的CRUD操作
-
易于測試:接口便于Mock測試