JDBC、MyBatis 、MyBatis-Plus面試總結(一)

以下為你整理了一些 MyBatis 和 MyBatis-Plus 中 mapper.xml 相關的常見面試問題及答案:

基礎概念類

問題 1:什么是 mapper.xml 文件,它在 MyBatis 中有什么作用?

答案mapper.xml 文件是 MyBatis 中用于定義 SQL 語句的配置文件。它將 SQL 語句與 Java 代碼分離,使得 SQL 語句的管理和維護更加方便。在 mapper.xml 中可以定義各種 SQL 操作,如查詢、插入、更新和刪除等,通過映射關系將 SQL 執行結果映射到 Java 對象上。

問題 2:MyBatis-Plus 中為什么還需要 mapper.xml 文件?

答案:雖然 MyBatis-Plus 提供了很多內置的 CRUD 方法,可以減少編寫 SQL 語句的工作量,但在以下情況下仍然需要使用 mapper.xml 文件:

  • 復雜 SQL 場景:當需要編寫復雜的 SQL 語句,如多表關聯查詢、復雜的嵌套查詢、自定義的 SQL 邏輯等,MyBatis-Plus 內置方法無法滿足需求時,就需要在 mapper.xml 中編寫自定義 SQL。
  • 性能優化:對于一些性能敏感的查詢,開發人員可以在 mapper.xml 中手動優化 SQL 語句,以達到更好的性能。

配置與使用類

問題 3:如何在 mapper.xml 中定義一個簡單的查詢語句?

答案:以下是一個簡單的查詢示例,假設我們有一個 User 表,對應的 Java 實體類為 User

<mapper namespace="com.example.mapper.UserMapper"><select id="selectUserById" resultType="com.example.entity.User">SELECT * FROM user WHERE id = #{id}</select>
</mapper>

在上述代碼中,namespace 指定了該 mapper.xml 文件對應的 Mapper 接口的全限定名,select 標簽用于定義查詢語句,id 是該查詢方法的唯一標識,resultType 指定了查詢結果的映射類型。

問題 4:mapper.xml 中的 #{}${} 有什么區別?

答案

  • #{}:是預編譯處理,MyBatis 在處理 #{} 時,會將 SQL 中的 #{} 替換為 ? 占位符,然后使用 PreparedStatement 進行參數設置,這樣可以有效防止 SQL 注入攻擊。例如:SELECT * FROM user WHERE id = #{id}
  • ${}:是字符串替換,MyBatis 在處理 ${} 時,會直接將 ${} 中的內容替換為傳入的參數值,這種方式存在 SQL 注入風險。通常用于動態表名、動態列名等場景。例如:SELECT * FROM ${tableName}
問題 5:如何在 mapper.xml 中實現動態 SQL?

答案:MyBatis 提供了多種標簽來實現動態 SQL,常見的有 <if><choose><when><otherwise><where><set><foreach> 等。
以下是一個使用 <if><where> 標簽實現動態查詢的示例:

<select id="selectUserByCondition" resultType="com.example.entity.User">SELECT * FROM user<where><if test="username != null and username != ''">AND username = #{username}</if><if test="age != null">AND age = #{age}</if></where>
</select>

在上述代碼中,<where> 標簽會自動處理 SQL 語句中的 ANDOR 關鍵字,避免出現多余的 ANDOR<if> 標簽用于根據條件判斷是否拼接相應的 SQL 片段。

高級特性類

問題 6:mapper.xml 中如何實現關聯查詢和結果映射?

答案:可以使用 <resultMap> 標簽來實現關聯查詢和結果映射。以下是一個簡單的示例,假設我們有 UserOrder 兩個表,一個用戶可以有多個訂單:

<mapper namespace="com.example.mapper.UserMapper"><resultMap id="UserResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><result property="username" column="username"/><collection property="orders" ofType="com.example.entity.Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/></collection></resultMap><select id="selectUserWithOrders" resultMap="UserResultMap">SELECT u.id AS user_id, u.username, o.id AS order_id, o.order_noFROM user uLEFT JOIN orders o ON u.id = o.user_id</select>
</mapper>

在上述代碼中,<resultMap> 標簽定義了查詢結果的映射規則,<id> 標簽用于映射主鍵,<result> 標簽用于映射普通字段,<collection> 標簽用于處理一對多的關聯關系。

問題 7:如何在 mapper.xml 中使用存儲過程?

答案:可以使用 <select><call> 標簽來調用存儲過程。以下是一個調用存儲過程的示例:

<mapper namespace="com.example.mapper.UserMapper"><select id="callProcedure" resultType="com.example.entity.User">{call get_user_info(#{id, mode=IN, jdbcType=INTEGER})}</select>
</mapper>

在上述代碼中,{call get_user_info(...)} 表示調用名為 get_user_info 的存儲過程,#{id, mode=IN, jdbcType=INTEGER} 表示傳入一個輸入參數 id

性能與優化類

問題 8:如何優化 mapper.xml 中的 SQL 語句以提高性能?

答案:可以從以下幾個方面進行優化:

  • 避免全表掃描:盡量使用索引,在 SQL 語句中避免使用 SELECT *,只查詢需要的字段。
  • 合理使用分頁:對于大數據量的查詢,使用分頁查詢可以減少數據傳輸量,提高查詢性能。
  • 優化動態 SQL:避免在動態 SQL 中使用過多的條件判斷,減少不必要的 SQL 拼接。
  • 批量操作:對于插入、更新和刪除操作,盡量使用批量操作,減少與數據庫的交互次數。
問題 9:在 mapper.xml 中,如何處理大結果集以避免內存溢出?

答案:可以使用流式查詢來處理大結果集。在 MyBatis 中,可以通過設置 fetchSize 屬性來實現流式查詢。例如:

<select id="selectLargeResult" resultType="com.example.entity.User" fetchSize="100">SELECT * FROM user
</select>

在上述代碼中,fetchSize="100" 表示每次從數據庫中獲取 100 條記錄,避免一次性將所有記錄加載到內存中。

緩存相關問題

問題 10:MyBatis 中 mapper.xml 如何配置一級緩存和二級緩存?它們有什么區別?

答案

  • 一級緩存配置:MyBatis 的一級緩存是默認開啟的,不需要在 mapper.xml 中進行額外配置。一級緩存是基于 SqlSession 的,同一個 SqlSession 中執行相同的 SQL 查詢時,會優先從緩存中獲取結果,而不會再次執行 SQL 語句。
  • 二級緩存配置:在 mapper.xml 中配置二級緩存,需要添加 <cache> 標簽,示例如下:
<mapper namespace="com.example.mapper.UserMapper"><cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/><!-- 其他 SQL 語句 -->
</mapper>

其中,eviction 表示緩存的回收策略(如 LRU 最近最少使用),flushInterval 表示緩存刷新間隔,size 表示緩存對象的最大數量,readOnly 表示緩存是否只讀。

區別

  • 作用域不同:一級緩存的作用域是 SqlSession,當 SqlSession 關閉時,一級緩存會被清空;二級緩存的作用域是 Mapper 接口,多個 SqlSession 可以共享同一個 Mapper 接口的二級緩存。
  • 緩存范圍不同:一級緩存只對同一個 SqlSession 內的相同查詢有效;二級緩存對多個 SqlSession 內的相同查詢都有效。
問題 11:如何在 mapper.xml 中手動控制緩存刷新?

答案:可以在 mapper.xml 中使用 <flushCache> 屬性來手動控制緩存刷新。在 <select><insert><update><delete> 標簽中都可以使用該屬性。例如:

<update id="updateUser" flushCache="true">UPDATE user SET username = #{username} WHERE id = #{id}
</update>

flushCache="true" 時,執行該 SQL 語句后會清空一級緩存和二級緩存。

映射與類型轉換問題

問題 12:在 mapper.xml 中,如何處理數據庫字段類型與 Java 對象屬性類型不匹配的情況?

答案

  • 使用 typeHandler:MyBatis 提供了 TypeHandler 接口來處理類型轉換。可以自定義 TypeHandler 類,實現該接口并重寫相應的方法,然后在 mapper.xml 中使用 typeHandler 屬性指定自定義的類型處理器。例如:
<resultMap id="UserResultMap" type="com.example.entity.User"><result property="createTime" column="create_time" typeHandler="com.example.handler.DateTypeHandler"/>
</resultMap>
  • 使用 <sql> 標簽進行類型轉換:在 SQL 語句中使用數據庫的類型轉換函數,如 MySQL 中的 CAST 函數。例如:
<select id="selectUser" resultType="com.example.entity.User">SELECT CAST(age AS SIGNED) AS age FROM user
</select>
問題 13:mapper.xml<resultMap><association><collection> 標簽在處理關聯關系時有什么高級用法?

答案

  • <association> 高級用法:除了基本的一對一關聯映射,還可以使用 fetchType 屬性來控制關聯對象的加載方式,有 eager(立即加載)和 lazy(延遲加載)兩種模式。例如:
<resultMap id="UserResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><association property="department" javaType="com.example.entity.Department" fetchType="lazy"><id property="id" column="dept_id"/><result property="deptName" column="dept_name"/></association>
</resultMap>
  • <collection> 高級用法:同樣可以使用 fetchType 屬性控制集合的加載方式,還可以使用 columnPrefix 屬性為關聯查詢的列添加前綴,避免列名沖突。例如:
<resultMap id="UserResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><collection property="orders" ofType="com.example.entity.Order" fetchType="lazy" columnPrefix="order_"><id property="id" column="id"/><result property="orderNo" column="order_no"/></collection>
</resultMap>

性能監控與調優問題

問題 14:如何在 mapper.xml 層面監控 SQL 執行性能?

答案

  • 使用日志框架:配置日志框架(如 Log4j、SLF4J 等),將 MyBatis 的日志級別設置為 DEBUG,這樣可以在日志中看到每條 SQL 語句的執行情況,包括執行時間、傳入的參數等。例如,在 log4j.properties 中配置:
log4j.logger.com.example.mapper=DEBUG
  • 使用性能監控工具:可以使用一些第三方性能監控工具,如 P6Spy,它可以攔截和記錄 SQL 語句的執行時間、參數等信息,方便進行性能分析。
問題 15:在 mapper.xml 中,如何優化批量插入操作的性能?

答案

  • 使用 <foreach> 標簽:在 mapper.xml 中使用 <foreach> 標簽將多個插入語句合并為一個批量插入語句。例如:
<insert id="batchInsertUsers">INSERT INTO user (username, age) VALUES<foreach collection="users" item="user" separator=",">(#{user.username}, #{user.age})</foreach>
</insert>
  • 調整數據庫連接池參數:適當增加數據庫連接池的最大連接數、最小空閑連接數等參數,以提高并發插入的性能。
  • 關閉自動提交:在代碼中關閉自動提交,手動控制事務的提交,減少事務開銷。例如:
SqlSession sqlSession = sqlSessionFactory.openSession(false);
try {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);userMapper.batchInsertUsers(users);sqlSession.commit();
} catch (Exception e) {sqlSession.rollback();
} finally {sqlSession.close();
}

以下是一些關于MyBatis和MyBatis Plus中mapper.xml的其他面試問題及答案:

動態SQL相關問題

問題16:MyBatis的mapper.xml<if><choose><when><otherwise>標簽在動態SQL中有什么作用?如何使用?

答案

  • <if>標簽:用于根據條件判斷是否包含某個SQL片段。例如:
<select id="selectUsers" resultMap="UserResultMap">SELECT * FROM user<where><if test="username!= null">AND username = #{username}</if><if test="age!= null">AND age = #{age}</if></where>
</select>

上述代碼根據usernameage是否為null來動態添加查詢條件。

  • <choose><when><otherwise>標簽:類似于Java中的switch語句,``是主標簽,是條件分支,`是默認分支。例如:
<select id="selectUsersByCondition" resultMap="UserResultMap">SELECT * FROM user<where><choose><when test="condition == 1">AND age &gt; 18</when><when test="condition == 2">AND age &lt;= 18</when><otherwise>AND gender = '男'</otherwise></choose></where>
</select>

根據condition的值來選擇不同的查詢條件,如果condition既不等于1也不等于2,則使用otherwise中的條件。

問題17:<foreach>標簽有哪些屬性?在批量操作中如何使用?

答案

  • <foreach>標簽的屬性
    • collection:指定要遍歷的集合或數組。
    • item:表示集合中每個元素的變量名。
    • separator:元素之間的分隔符。
    • open:在開始處添加的字符串。
    • close:在結束處添加的字符串。
  • 批量操作示例 - 批量刪除
<delete id="batchDeleteUsers">DELETE FROM user WHERE id IN<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>
</delete>

在上述代碼中,ids是一個包含用戶ID的集合,通過<foreach>標簽將集合中的ID拼接成IN子句中的參數列表,實現批量刪除操作。

多表關聯查詢問題

問題18:在mapper.xml中如何進行復雜的多表聯合查詢?

答案:可以使用JOIN關鍵字進行多表聯合查詢,并通過resultMap來映射結果。例如,查詢用戶及其所屬部門,以及該部門下的所有員工:

<resultMap id="UserDeptEmpResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><result property="username" column="username"/><association property="department" javaType="com.example.entity.Department"><id property="id" column="dept_id"/><result property="deptName" column="dept_name"/><collection property="employees" ofType="com.example.entity.User"><id property="id" column="emp_id"/><result property="username" column="emp_username"/></collection></association>
</resultMap><select id="selectUserDeptEmp" resultMap="UserDeptEmpResultMap">SELECTu.id AS user_id,u.username,d.id AS dept_id,d.dept_name,e.id AS emp_id,e.username AS emp_usernameFROMuser uJOINdepartment d ON u.dept_id = d.idLEFT JOINuser e ON d.id = e.dept_id
</select>

上述代碼中,通過JOINLEFT JOIN實現了多表關聯查詢,并使用resultMap對復雜的結果進行了映射。

問題19:在多表關聯查詢時,如何處理關聯字段的別名沖突問題?

答案:可以在SQL查詢中為關聯字段指定不同的別名來避免沖突。例如:

<select id="selectUserAndOrders" resultMap="UserOrdersResultMap">SELECTu.id AS user_id,u.username,o.id AS order_id,o.order_no,o.order_amountFROMuser uJOINorders o ON u.id = o.user_id
</select>

resultMap中使用對應的別名進行映射:

<resultMap id="UserOrdersResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><result property="username" column="username"/><collection property="orders" ofType="com.example.entity.Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/><result property="orderAmount" column="order_amount"/></collection>
</resultMap>

MyBatis Plus特有的mapper.xml問題

問題20:MyBatis Plus中mapper.xml與MyBatis的mapper.xml有什么區別和聯系?

答案

  • 聯系:MyBatis Plus的mapper.xml在本質上和MyBatis的mapper.xml作用是一樣的,都是用于編寫SQL語句和配置映射關系等,并且在很多用法上是相似的,比如都可以使用resultMap、動態SQL標簽等。
  • 區別
    • MyBatis Plus增強功能:MyBatis Plus的mapper.xml可以配合其提供的通用Mapper等功能,減少大量的基礎SQL語句編寫。例如,不需要編寫簡單的SELECTINSERTUPDATEDELETE語句,通用Mapper已經提供了默認實現。
    • 內置方法不同:MyBatis Plus的mapper.xml如果要使用其特有的方法,需要遵循其特定的命名規范和配置方式。比如selectPage方法用于分頁查詢,在mapper.xml中的配置和普通MyBatis的分頁查詢配置有所不同,它會結合Page對象等進行分頁操作。
問題21:在MyBatis Plus的mapper.xml中如何實現自定義分頁查詢?

答案:首先在mapper.xml中編寫查詢語句,使用${ew.customSqlSegment}來接收MyBatis Plus自動生成的分頁相關的SQL片段。例如:

<select id="selectUsersByConditionWithPage" resultMap="UserResultMap">SELECT * FROM user<where><!-- 自定義查詢條件 --><if test="username!= null">AND username = #{username}</if></where>${ew.customSqlSegment}
</select>

在Java代碼中,使用Page對象和QueryWrapper來進行分頁查詢:

Page<User> page = new Page<>(1, 10);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("gender", "女");
userMapper.selectUsersByConditionWithPage(page, queryWrapper);

事務與并發控制問題

問題 22:在 mapper.xml 相關操作中,如何保證事務的一致性?

答案

  • 基于 Spring 管理事務:在 Spring 與 MyBatis 整合的項目中,通常使用 Spring 的聲明式事務管理。通過在服務層方法上添加 @Transactional 注解,Spring 會自動管理事務的開啟、提交和回滾。例如:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic void updateUserAndOrder(User user, Order order) {userMapper.updateUser(user);userMapper.insertOrder(order);}
}
  • 手動控制事務(較少使用):在不使用 Spring 管理事務時,可通過 SqlSession 手動控制事務。例如:
SqlSession sqlSession = sqlSessionFactory.openSession(false);
try {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);userMapper.updateUser(user);userMapper.insertOrder(order);sqlSession.commit();
} catch (Exception e) {sqlSession.rollback();
} finally {sqlSession.close();
}
問題 23:在高并發場景下,mapper.xml 中的 SQL 操作可能會出現哪些問題?如何解決?

答案

  • 可能出現的問題
    • 數據不一致:多個事務同時對同一數據進行讀寫操作,可能導致數據不一致,如臟讀、不可重復讀、幻讀等問題。
    • 死鎖:多個事務相互等待對方釋放鎖,導致程序無法繼續執行。
  • 解決方法
    • 使用合適的事務隔離級別:在 Spring 的 @Transactional 注解中設置合適的隔離級別,如 @Transactional(isolation = Isolation.READ_COMMITTED) 可避免臟讀問題。
    • 優化 SQL 語句:減少鎖的持有時間,避免長時間占用鎖資源。例如,避免在事務中執行復雜的查詢或耗時操作。
    • 使用悲觀鎖或樂觀鎖:悲觀鎖通過 SELECT ... FOR UPDATE 語句在查詢時加鎖,確保數據在事務處理期間不被其他事務修改;樂觀鎖通過在表中添加版本號字段,在更新數據時檢查版本號是否一致。

代碼維護與擴展性問題

問題 24:如何提高 mapper.xml 文件的可維護性和擴展性?

答案

  • 合理命名mapper.xml 中的 id 要具有清晰的含義,能夠準確反映該 SQL 操作的功能。例如,selectUserById 表示根據 ID 查詢用戶。
  • 模塊化設計:將常用的 SQL 片段提取到 <sql> 標簽中,通過 <include> 標簽引用,提高代碼的復用性。例如:
<sql id="userColumns">id, username, age, gender
</sql><select id="selectUserById" resultType="com.example.entity.User">SELECT <include refid="userColumns"/> FROM user WHERE id = #{id}
</select>
  • 注釋說明:在 mapper.xml 中添加詳細的注釋,解釋 SQL 語句的功能、參數含義和返回值等信息,方便后續開發人員理解和維護。
問題 25:當業務需求變更時,如何修改 mapper.xml 中的 SQL 語句以最小化對現有代碼的影響?

答案

  • 遵循開閉原則:盡量通過擴展而不是修改現有代碼來滿足新的需求。例如,當需要添加新的查詢條件時,可以使用動態 SQL 標簽 <if> 來擴展查詢條件,而不是直接修改原有的 SQL 語句。
  • 版本控制:使用版本控制系統(如 Git)對 mapper.xml 文件進行管理,方便回溯和比較不同版本的修改。在修改之前,先創建新的分支,確保修改不會影響到主分支的穩定性。
  • 測試驅動開發:在修改 mapper.xml 中的 SQL 語句后,編寫相應的單元測試和集成測試,確保修改后的代碼仍然能夠正常工作,并且不會引入新的問題。

性能調優深入問題

問題 26:在 mapper.xml 中,如何利用數據庫索引來優化查詢性能?

答案

  • 合理設計查詢條件:確保 SQL 語句中的查詢條件能夠使用到數據庫的索引。例如,在 WHERE 子句中使用索引列進行過濾,避免使用函數或表達式對索引列進行操作,因為這可能會導致索引失效。
<!-- 正確使用索引 -->
<select id="selectUserByUsername" resultType="com.example.entity.User">SELECT * FROM user WHERE username = #{username}
</select><!-- 錯誤示例,可能導致索引失效 -->
<select id="selectUserByUsernameWrong" resultType="com.example.entity.User">SELECT * FROM user WHERE UPPER(username) = UPPER(#{username})
</select>
  • 復合索引的使用:如果查詢條件涉及多個列,可以考慮創建復合索引。在編寫 SQL 語句時,按照復合索引的列順序進行條件過濾,以充分利用復合索引的性能優勢。
問題 27:對于 mapper.xml 中的復雜 SQL 查詢,如何進行性能分析和優化?

答案

  • 性能分析
    • 數據庫自帶工具:使用數據庫的性能分析工具,如 MySQL 的 EXPLAIN 關鍵字,分析 SQL 語句的執行計劃,了解查詢的索引使用情況、掃描行數等信息。
    • 日志記錄:通過日志記錄 SQL 語句的執行時間,找出執行時間較長的 SQL 語句。
  • 優化方法
    • 索引優化:根據性能分析結果,添加或修改索引,提高查詢效率。
    • SQL 重構:將復雜的 SQL 查詢拆分成多個簡單的查詢,或者使用存儲過程來優化邏輯。
    • 緩存使用:對于一些不經常變化的數據,可以使用緩存來減少數據庫查詢次數。

與其他框架集成問題

問題 28:當 MyBatis 的 mapper.xml 與 Spring Boot 集成時,有哪些常見的配置和注意事項?

答案

  • 常見配置
    • 依賴添加:在 pom.xml 中添加 MyBatis 和 MyBatis - Spring - Boot - Starter 依賴,確保項目能正常使用 MyBatis 功能。
    <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>x.x.x</version>
    </dependency>
    
    • 配置文件設置:在 application.propertiesapplication.yml 中配置數據庫連接信息和 mapper.xml 文件的位置。
    spring.datasource.url=jdbc:mysql://localhost:3306/your_database
    spring.datasource.username=your_username
    spring.datasource.password=your_password
    mybatis.mapper-locations=classpath:mapper/*.xml
    
  • 注意事項
    • Mapper 接口掃描:確保 Spring Boot 能掃描到 Mapper 接口,可以使用 @MapperScan 注解指定 Mapper 接口所在的包。
    @SpringBootApplication
    @MapperScan("com.example.mapper")
    public class YourApplication {public static void main(String[] args) {SpringApplication.run(YourApplication.class, args);}
    }
    
    • 事務管理:Spring Boot 集成 MyBatis 時,要注意事務的配置和使用,確保數據操作的一致性。
問題 29:如果要將 mapper.xml 與 Redis 集成實現緩存,該如何操作?

答案

  • 添加依賴:在項目中添加 Redis 相關依賴,如 Spring Data Redis。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置 Redis:在 application.propertiesapplication.yml 中配置 Redis 連接信息。
spring.redis.host=localhost
spring.redis.port=6379
  • 實現緩存邏輯:在服務層實現緩存邏輯,先從 Redis 中獲取數據,如果緩存中不存在,則執行 mapper.xml 中的 SQL 查詢,并將結果存入 Redis。
@Service
public class UserService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate UserMapper userMapper;public User getUserById(Long id) {String key = "user:" + id;User user = (User) redisTemplate.opsForValue().get(key);if (user == null) {user = userMapper.selectUserById(id);if (user != null) {redisTemplate.opsForValue().set(key, user);}}return user;}
}

代碼規范與最佳實踐問題

問題 30:在編寫 mapper.xml 時,有哪些代碼規范和最佳實踐?

答案

  • 代碼規范
    • 命名規范mapper.xml 文件的命名應與對應的 Mapper 接口名稱保持一致,id 屬性命名要清晰反映 SQL 操作的功能,如 selectUserByUsernameupdateUserInfo 等。
    • 注釋規范:為每個 SQL 語句添加詳細的注釋,包括功能描述、參數說明、返回值說明等,方便后續維護。
    • 格式規范:保持 SQL 語句的格式整齊,合理使用縮進和換行,提高代碼的可讀性。
  • 最佳實踐
    • 避免硬編碼:盡量使用參數化查詢,避免在 SQL 語句中硬編碼常量,防止 SQL 注入攻擊。
    • 復用 SQL 片段:將常用的 SQL 片段提取到 <sql> 標簽中,通過 <include> 標簽復用,減少代碼冗余。
    • 性能優化:編寫高效的 SQL 語句,合理使用索引,避免全表掃描,提高查詢性能。
問題 31:如何對 mapper.xml 中的 SQL 語句進行安全審查?

答案

  • 防止 SQL 注入
    • 使用參數化查詢:確保在 mapper.xml 中使用 #{} 占位符進行參數傳遞,MyBatis 會自動處理參數的預編譯,防止 SQL 注入。
    • 輸入驗證:在服務層對用戶輸入進行嚴格的驗證和過濾,確保輸入的數據符合預期。
  • 權限審查
    • 檢查 SQL 操作權限:確保 SQL 語句所涉及的數據庫操作在用戶或系統的權限范圍內,避免越權操作。
  • 代碼審查
    • 人工審查:組織開發人員對 mapper.xml 中的 SQL 語句進行人工審查,檢查是否存在潛在的安全風險。
    • 工具輔助:使用靜態代碼分析工具,如 SonarQube 等,對 mapper.xml 文件進行掃描,發現潛在的安全問題。

異常處理與調試問題

問題 32:在使用 mapper.xml 時,可能會遇到哪些異常?如何進行調試和解決?

答案

  • 常見異常
    • SQLSyntaxErrorException:SQL 語法錯誤,通常是由于 SQL 語句編寫錯誤導致的,如關鍵字拼寫錯誤、表名或列名錯誤等。
    • SQLException:數據庫連接異常、數據類型不匹配等問題都可能引發該異常。
    • MapperException:MyBatis 映射異常,如 resultMap 配置錯誤、id 重復等。
  • 調試和解決方法
    • 日志調試:開啟 MyBatis 的詳細日志,通過日志信息查看具體的 SQL 語句和錯誤堆棧,定位問題所在。
    • 數據庫客戶端測試:將 mapper.xml 中的 SQL 語句復制到數據庫客戶端中執行,檢查是否能正常運行,排查 SQL 語法問題。
    • 代碼審查:仔細檢查 mapper.xml 文件的配置,確保 resultMapparameterType 等配置正確。
問題 33:當 mapper.xml 中的 SQL 執行超時,應該如何處理?

答案

  • 優化 SQL 語句
    • 索引優化:檢查 SQL 語句是否使用了合適的索引,通過添加或修改索引來提高查詢效率。
    • 查詢優化:避免復雜的嵌套查詢和全表掃描,將復雜查詢拆分成多個簡單查詢。
  • 調整超時設置
    • 數據庫層面:在數據庫中調整連接超時和查詢超時的參數設置。
    • 應用層面:在 MyBatis 配置中設置超時時間,例如在 DataSource 配置中設置 connectionTimeoutqueryTimeout
@Configuration
public class DataSourceConfig {@Beanpublic DataSource dataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/your_database");dataSource.setUsername("your_username");dataSource.setPassword("your_password");dataSource.setConnectionTimeout(30000); // 連接超時時間 30 秒dataSource.setIdleTimeout(600000); // 空閑連接超時時間 10 分鐘dataSource.setMaxLifetime(1800000); // 最大連接生命周期 30 分鐘dataSource.setMaximumPoolSize(10); // 最大連接池大小return dataSource;}
}

分布式環境相關問題

問題 34:在分布式系統中,mapper.xml 里的 SQL 操作如何保證數據一致性?

答案

  • 使用分布式事務框架:如 Seata 等,Seata 提供了 AT、TCC、SAGA 等多種分布式事務模式。在服務層使用 Seata 的注解進行事務管理,例如在調用多個涉及 mapper.xml 中 SQL 操作的服務方法時,使用 @GlobalTransactional 注解開啟全局事務。
@Service
public class DistributedService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate OrderMapper orderMapper;@GlobalTransactionalpublic void createUserAndOrder(User user, Order order) {userMapper.insertUser(user);orderMapper.insertOrder(order);}
}
  • 消息隊列與最終一致性:借助消息隊列(如 Kafka、RabbitMQ)實現最終一致性。當一個 SQL 操作完成后,發送消息到消息隊列,其他服務監聽消息并執行相應的 SQL 操作。如果操作失敗,可通過重試機制或人工干預來保證最終數據一致。
  • 冪等性設計:確保 mapper.xml 中的 SQL 操作具有冪等性,即多次執行相同操作產生的結果與執行一次的結果相同。例如,在更新操作中使用 UPDATE ... WHERE ... 語句,通過唯一標識進行更新,避免重復更新導致數據不一致。
問題 35:在分布式緩存場景下,mapper.xml 相關的 SQL 查詢如何與緩存配合以提升性能?

答案

  • 緩存穿透處理:當查詢的數據在緩存中不存在時,為避免大量請求直接穿透到數據庫,可在緩存中設置空值或使用布隆過濾器。在 mapper.xml 查詢時,先檢查緩存,如果緩存為空,進行布隆過濾器判斷,若可能存在則查詢數據庫并更新緩存。
public User getUserById(Long id) {String key = "user:" + id;User user = (User) redisTemplate.opsForValue().get(key);if (user == null) {if (bloomFilter.mightContain(id)) {user = userMapper.selectUserById(id);if (user != null) {redisTemplate.opsForValue().set(key, user);} else {redisTemplate.opsForValue().set(key, null, 60, TimeUnit.SECONDS); // 設置空值緩存,避免緩存穿透}}}return user;
}
  • 緩存更新策略:當 mapper.xml 中的 SQL 操作對數據進行更新時,需要更新緩存。可以采用先更新數據庫,再刪除緩存的策略,利用緩存失效機制保證下次查詢時獲取最新數據。
@Transactional
public void updateUser(User user) {userMapper.updateUser(user);String key = "user:" + user.getId();redisTemplate.delete(key);
}
  • 緩存預熱:在系統啟動時,將常用的查詢結果預先加載到緩存中,減少首次查詢時的數據庫壓力。可以通過定時任務或手動觸發的方式,執行 mapper.xml 中的查詢語句并將結果存入緩存。

動態數據源問題

問題 36:在使用動態數據源的情況下,mapper.xml 如何適配不同的數據源?

答案

  • 動態數據源配置:使用 Spring 的 AbstractRoutingDataSource 實現動態數據源切換。在配置類中定義多個數據源,并根據業務邏輯動態切換數據源。
@Configuration
public class DynamicDataSourceConfig {@Beanpublic DataSource dynamicDataSource() {DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("dataSource1", dataSource1());targetDataSources.put("dataSource2", dataSource2());dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource1());dynamicRoutingDataSource.setTargetDataSources(targetDataSources);return dynamicRoutingDataSource;}@Beanpublic DataSource dataSource1() {// 配置數據源 1}@Beanpublic DataSource dataSource2() {// 配置數據源 2}
}
  • mapper.xml 通用配置mapper.xml 中的 SQL 語句應盡量保持通用性,避免使用特定數據庫的語法。如果不同數據源使用的數據庫類型不同,可通過動態 SQL 標簽根據數據源類型進行適配。
<select id="selectUser" resultType="com.example.entity.User"><choose><when test="@com.example.util.DataSourceContextHolder@getDataSource() == 'dataSource1'">-- 數據源 1 的特定 SQL 語句</when><when test="@com.example.util.DataSourceContextHolder@getDataSource() == 'dataSource2'">-- 數據源 2 的特定 SQL 語句</when><otherwise>-- 默認 SQL 語句</otherwise></choose>
</select>
問題 37:如何在 mapper.xml 中實現對不同數據庫方言的支持?

答案

  • 動態 SQL 結合方言判斷:在 mapper.xml 中使用動態 SQL 標簽結合方言判斷來編寫不同數據庫的 SQL 語句。可以通過配置文件或上下文信息獲取當前使用的數據庫方言。
<select id="selectUserCount" resultType="int"><choose><when test="@com.example.util.DbDialectUtil@isMysql()">SELECT COUNT(*) FROM user</when><when test="@com.example.util.DbDialectUtil@isOracle()">SELECT COUNT(*) FROM user_table</when><otherwise>-- 默認 SQL 語句</otherwise></choose>
</select>
  • 抽象 SQL 片段:將不同數據庫中通用的 SQL 邏輯提取到 <sql> 標簽中,然后在具體的 SQL 語句中通過 <include> 標簽引用,減少重復代碼。對于不同數據庫的差異部分,單獨處理。
<sql id="userColumns">id, username, age
</sql><select id="selectUser" resultType="com.example.entity.User"><choose><when test="@com.example.util.DbDialectUtil@isMysql()">SELECT <include refid="userColumns"/> FROM user</when><when test="@com.example.util.DbDialectUtil@isOracle()">SELECT <include refid="userColumns"/> FROM user_table</when><otherwise>-- 默認 SQL 語句</otherwise></choose>
</select>

代碼生成與自動化問題

問題 38:如何使用代碼生成工具自動生成 mapper.xml 文件?

答案

  • MyBatis Generator:這是 MyBatis 官方提供的代碼生成工具。
    • 配置生成器:創建一個 generatorConfig.xml 配置文件,指定數據庫連接信息、生成的表名、生成文件的路徑等。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration><context id="DB2Tables" targetRuntime="MyBatis3"><commentGenerator><property name="suppressDate" value="true"/><property name="suppressAllComments" value="true"/></commentGenerator><jdbcConnection driverClass="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/your_database"userId="your_username"password="your_password"></jdbcConnection><javaTypeResolver><property name="forceBigDecimals" value="false"/></javaTypeResolver><javaModelGenerator targetPackage="com.example.entity"targetProject="src/main/java"><property name="enableSubPackages" value="true"/><property name="trimStrings" value="true"/></javaModelGenerator><sqlMapGenerator targetPackage="com.example.mapper"targetProject="src/main/resources"><property name="enableSubPackages" value="true"/></sqlMapGenerator><javaClientGenerator type="XMLMAPPER"targetPackage="com.example.mapper"targetProject="src/main/java"><property name="enableSubPackages" value="true"/></javaClientGenerator><table tableName="user"/></context>
</generatorConfiguration>
- **運行生成器**:在 Java 代碼中運行生成器,執行代碼生成操作。
public class Generator {public static void main(String[] args) throws Exception {List<String> warnings = new ArrayList<>();boolean overwrite = true;File configFile = new File("generatorConfig.xml");ConfigurationParser cp = new ConfigurationParser(warnings);Configuration config = cp.parseConfiguration(configFile);DefaultShellCallback callback = new DefaultShellCallback(overwrite);MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);myBatisGenerator.generate(null);}
}
  • MyBatis-Plus Generator:MyBatis-Plus 提供的代碼生成器,使用更便捷。
    • 添加依賴:在 pom.xml 中添加 MyBatis-Plus Generator 依賴。
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>x.x.x</version>
</dependency>
- **編寫生成代碼**:通過 Java 代碼配置生成器并執行生成操作。
public class CodeGenerator {public static void main(String[] args) {FastAutoGenerator.create("jdbc:mysql://localhost:3306/your_database", "your_username", "your_password").globalConfig(builder -> {builder.author("your_name") // 設置作者.outputDir("src/main/java"); // 指定輸出目錄}).packageConfig(builder -> {builder.parent("com.example") // 設置父包名.moduleName("module") // 設置模塊名.pathInfo(Collections.singletonMap(OutputFile.xml, "src/main/resources/mapper")); // 設置mapperXml生成路徑}).strategyConfig(builder -> {builder.addInclude("user") // 設置需要生成的表名.addTablePrefix("t_", "c_"); // 設置過濾表前綴}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板.execute();}
}
問題 39:自動生成的 mapper.xml 文件如何進行定制化修改和擴展?

答案

  • 備份與版本控制:在進行定制化修改之前,先對自動生成的 mapper.xml 文件進行備份,并使用版本控制系統(如 Git)進行管理,方便后續回溯和比較修改。
  • 避免直接修改生成模板:盡量不要直接修改代碼生成工具的模板文件,因為這樣可能會影響后續的代碼生成。可以在生成的基礎上進行手動修改和擴展。
  • 添加自定義 SQL 語句:在 mapper.xml 中添加自定義的 SQL 語句,如復雜的查詢、存儲過程調用等。同時,要注意保持命名規范和代碼格式的一致性。
<mapper namespace="com.example.mapper.UserMapper"><!-- 自動生成的 SQL 語句 --><select id="selectUserById" resultType="com.example.entity.User">SELECT * FROM user WHERE id = #{id}</select><!-- 自定義 SQL 語句 --><select id="selectUserByUsernameAndAge" resultType="com.example.entity.User">SELECT * FROM user WHERE username = #{username} AND age = #{age}</select>
</mapper>
  • 擴展映射關系:如果需要擴展映射關系,可以在 resultMap 中添加新的映射字段或修改現有的映射規則。
<resultMap id="UserResultMap" type="com.example.entity.User"><id property="id" column="id"/><result property="username" column="username"/><result property="age" column="age"/><!-- 擴展映射字段 --><result property="email" column="email"/>
</resultMap>

日志與審計相關問題

問題 40:如何在 mapper.xml 相關操作中記錄詳細的日志,以便進行審計和問題排查?

答案

  • MyBatis 日志配置:MyBatis 本身支持多種日志框架,如 Log4j、Logback、SLF4J 等。可以在 MyBatis 配置文件或 Spring Boot 的配置文件中配置日志級別為 DEBUG,這樣會輸出 SQL 語句及其參數。
    • 使用 Log4j 示例:在 log4j.properties 中添加以下配置:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
log4j.logger.com.example.mapper=DEBUG
- **使用 Spring Boot 配置**:在 `application.properties` 中配置:
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 自定義日志記錄:可以在服務層或攔截器中自定義日志記錄邏輯,記錄 SQL 操作的執行時間、影響行數等信息。例如,創建一個自定義的攔截器:
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class SqlLogInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(SqlLogInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();Object result = invocation.proceed();long endTime = System.currentTimeMillis();long executionTime = endTime - startTime;MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];String sqlId = mappedStatement.getId();logger.info("SQL ID: {}, Execution Time: {} ms, Result: {}", sqlId, executionTime, result);return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 可用于設置攔截器的屬性}
}

然后在 MyBatis 配置中注冊該攔截器:

@Configuration
public class MyBatisConfig {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@PostConstructpublic void addInterceptor() {sqlSessionFactory.getConfiguration().addInterceptor(new SqlLogInterceptor());}
}
問題 41:對于 mapper.xml 中的敏感數據操作,如何進行安全審計?

答案

  • 日志記錄:對涉及敏感數據的 SQL 操作(如查詢、修改、刪除用戶的身份證號、密碼等)進行詳細的日志記錄,包括操作時間、操作人員、操作的 SQL 語句、影響的記錄等信息。可以結合上述的日志配置和自定義攔截器來實現。
  • 權限控制:在系統層面設置嚴格的權限控制,只有具有相應權限的用戶才能執行涉及敏感數據的 SQL 操作。可以使用 Spring Security 等框架來實現權限管理。
  • 審計表記錄:創建專門的審計表,記錄敏感數據操作的相關信息。在執行敏感數據操作時,同時向審計表中插入一條記錄。例如:
<insert id="insertAuditLog">INSERT INTO audit_log (operation_time, operator, sql_statement, affected_rows)VALUES (#{operationTime}, #{operator}, #{sqlStatement}, #{affectedRows})
</insert>

在服務層調用該插入語句:

@Service
public class SensitiveDataService {@Autowiredprivate AuditLogMapper auditLogMapper;@Autowiredprivate SensitiveDataMapper sensitiveDataMapper;public void updateSensitiveData(SensitiveData data) {String sqlStatement = "UPDATE sensitive_data SET ...";int affectedRows = sensitiveDataMapper.updateSensitiveData(data);AuditLog auditLog = new AuditLog();auditLog.setOperationTime(new Date());auditLog.setOperator("user1");auditLog.setSqlStatement(sqlStatement);auditLog.setAffectedRows(affectedRows);auditLogMapper.insertAuditLog(auditLog);}
}

數據遷移與兼容性問題

問題 42:當數據庫進行升級或遷移時,mapper.xml 中的 SQL 語句需要做哪些調整?

答案

  • 數據庫語法差異:不同版本的數據庫或不同類型的數據庫(如從 MySQL 遷移到 PostgreSQL)可能存在語法差異。需要檢查 mapper.xml 中的 SQL 語句,將不兼容的語法進行修改。例如,MySQL 中的 LIMIT 關鍵字在 PostgreSQL 中也可以使用,但函數的使用方式可能有所不同。
<!-- MySQL 查詢分頁 -->
<select id="selectUsersByPage" resultType="com.example.entity.User">SELECT * FROM user LIMIT #{offset}, #{limit}
</select><!-- PostgreSQL 查詢分頁 -->
<select id="selectUsersByPage" resultType="com.example.entity.User">SELECT * FROM user OFFSET #{offset} LIMIT #{limit}
</select>
  • 表結構變化:如果數據庫升級或遷移后表結構發生了變化,如字段名更改、字段類型變更、新增或刪除字段等,需要相應地修改 mapper.xml 中的 SQL 語句和映射關系。例如,原表中的 user_name 字段改為 username,則需要修改 SQL 語句和 resultMap
<resultMap id="UserResultMap" type="com.example.entity.User"><!-- 修改前 --><!-- <result property="username" column="user_name"/> --><!-- 修改后 --><result property="username" column="username"/>
</resultMap><select id="selectUserById" resultType="com.example.entity.User"><!-- 修改前 --><!-- SELECT user_name FROM user WHERE id = #{id} --><!-- 修改后 -->SELECT username FROM user WHERE id = #{id}
</select>
  • 存儲過程和函數變化:如果使用了數據庫的存儲過程和函數,需要檢查它們在新數據庫中的定義是否發生了變化,如有變化則需要修改 mapper.xml 中調用這些存儲過程和函數的 SQL 語句。
問題 43:如何確保 mapper.xml 中的 SQL 語句在不同數據庫版本之間的兼容性?

答案

  • 使用標準 SQL 語法:盡量使用標準的 SQL 語法編寫 mapper.xml 中的 SQL 語句,避免使用特定數據庫版本的專有語法和函數。例如,使用 WHERE 子句進行條件過濾,而不是依賴特定數據庫的過濾方式。
  • 動態 SQL 適配:利用 MyBatis 的動態 SQL 標簽,根據不同的數據庫版本或類型,動態生成不同的 SQL 語句。可以通過配置文件或上下文信息獲取當前數據庫的相關信息,然后在 mapper.xml 中進行判斷。
<select id="selectUserCount" resultType="int"><choose><when test="@com.example.util.DbVersionUtil@isOldVersion()">-- 舊版本數據庫的 SQL 語句</when><otherwise>-- 新版本數據庫的 SQL 語句</otherwise></choose>
</select>
  • 測試與驗證:在不同版本的數據庫上進行充分的測試,確保 mapper.xml 中的 SQL 語句能夠正常執行。可以使用測試框架(如 JUnit)編寫單元測試和集成測試,模擬不同數據庫環境下的操作。

代碼優化與重構問題

問題 44:如何對已有的 mapper.xml 文件進行性能優化和代碼重構?

答案

  • 性能優化
    • 索引優化:分析 SQL 語句,確保查詢條件能夠使用到數據庫的索引。可以通過數據庫的 EXPLAIN 工具查看查詢的執行計劃,根據結果添加或調整索引。
    • 減少全表掃描:避免在 WHERE 子句中使用函數或表達式對索引列進行操作,防止索引失效。盡量使用覆蓋索引,減少數據庫的 I/O 操作。
    • 批量操作:對于插入、更新和刪除操作,使用批量操作代替單條記錄的操作,減少與數據庫的交互次數。例如,使用 <foreach> 標簽實現批量插入。
  • 代碼重構
    • 提取公共 SQL 片段:將重復使用的 SQL 片段提取到 <sql> 標簽中,通過 <include> 標簽引用,提高代碼的復用性。
    • 優化動態 SQL:檢查動態 SQL 標簽的使用,避免過多的嵌套和復雜的條件判斷。可以將復雜的動態 SQL 拆分成多個簡單的 SQL 語句,提高代碼的可讀性。
    • 遵循命名規范:統一 mapper.xmlid<sql> 標簽的命名,使其具有清晰的含義,方便后續維護。
問題 45:當 mapper.xml 文件變得非常龐大時,如何進行拆分和管理?

答案

  • 按功能模塊拆分:根據業務功能將 mapper.xml 文件拆分成多個小文件。例如,將用戶相關的 SQL 語句放在 UserMapper.xml 中,訂單相關的 SQL 語句放在 OrderMapper.xml 中。
  • 按操作類型拆分:將查詢、插入、更新、刪除等不同類型的 SQL 操作分別放在不同的 mapper.xml 文件中。例如,UserQueryMapper.xml 存放用戶查詢相關的 SQL 語句,UserUpdateMapper.xml 存放用戶更新相關的 SQL 語句。
  • 使用 <package> 配置:在 MyBatis 配置文件中使用 <package> 標簽指定 mapper.xml 文件所在的包,讓 MyBatis 自動掃描和加載這些文件。
<mappers><package name="com.example.mapper"/>
</mappers>
  • 版本控制與管理:使用版本控制系統(如 Git)對拆分后的 mapper.xml 文件進行管理,方便團隊協作和代碼的版本追溯。同時,可以為每個文件添加詳細的注釋,說明其功能和用途。

MyBatis、MyBatis-Plus和JDBC之間存在一定的關系,它們的底層原理也各有特點,具體如下:

三者關系

  • JDBC:Java Database Connectivity(Java數據庫連接),是Java語言中用于與各種數據庫進行交互的標準API。它提供了一套通用的接口,讓Java程序能夠連接到數據庫、執行SQL語句、處理結果集等。MyBatis和MyBatis-Plus都是構建在JDBC之上的框架,它們最終都要通過JDBC來實現與數據庫的交互。
  • MyBatis:是一個持久層框架,它對JDBC進行了封裝,簡化了JDBC操作數據庫的代碼,使開發者可以更專注于SQL語句的編寫和業務邏輯的實現。MyBatis通過配置文件或注解等方式來管理SQL語句,并提供了對象關系映射(ORM)等功能,將數據庫中的數據映射為Java對象。
  • MyBatis-Plus:是在MyBatis基礎上進行擴展的增強工具,它在MyBatis的基礎上封裝了更多的通用操作,如通用的CRUD(增刪改查)方法等,進一步簡化了開發流程,提高了開發效率。MyBatis-Plus并不改變MyBatis的底層原理,而是在其基礎上提供了更多的便捷功能和特性。

底層原理

  • JDBC
    • 連接管理:通過DriverManager類來管理數據庫驅動,加載數據庫驅動程序后,使用DriverManager.getConnection()方法建立與數據庫的連接,這個過程會根據傳入的數據庫URL、用戶名和密碼等信息與數據庫服務器進行通信,建立起物理連接。
    • SQL執行:獲取連接后,創建StatementPreparedStatement對象來執行SQL語句。Statement用于執行靜態SQL,PreparedStatement用于執行預編譯的SQL,它可以防止SQL注入攻擊,并且在多次執行相同SQL但參數不同的情況下性能更好。
    • 結果處理:執行SQL語句后,通過ResultSet對象來處理查詢結果,它提供了一系列方法來獲取結果集中的數據,如getInt()getString()等,根據列的數據類型獲取相應的值。
  • MyBatis
    • 配置解析:首先讀取配置文件(如mybatis-config.xml),解析其中的數據源配置、映射文件路徑等信息,創建Configuration對象來存儲這些配置信息。
    • SQL映射:通過映射文件(如.xml文件或注解)將SQL語句與Java方法進行關聯,在執行Java方法時,MyBatis會根據映射關系找到對應的SQL語句。
    • 執行流程:當調用MyBatis的方法時,會創建SqlSession對象,它是MyBatis與數據庫交互的核心對象。SqlSession通過Executor執行器來執行SQL語句,Executor會根據配置和SQL語句生成StatementHandlerParameterHandlerResultSetHandler等對象,分別負責處理SQL語句的創建、參數設置和結果集處理。
    • ORM實現:利用反射機制,將結果集中的數據映射為Java對象,根據配置中的映射關系,將數據庫列名與Java對象的屬性進行匹配,通過調用對象的setter方法將數據設置到對象中。
  • MyBatis-Plus
    • 通用Mapper原理:基于MyBatis的插件機制,通過攔截器攔截MyBatis的SQL執行過程,在運行時根據實體類的信息和方法調用動態生成SQL語句。例如,對于通用的查詢方法,它會根據實體類的字段信息生成相應的SELECT語句,無需開發者手動編寫大量重復的SQL。
    • 條件構造器原理:通過鏈式調用的方式構建查詢條件,在底層將用戶設置的條件轉換為SQL中的WHERE子句條件。它利用了Java的函數式編程和反射等技術,對實體類的字段進行操作,生成準確的查詢條件。
    • 代碼生成原理:根據數據庫表結構和用戶配置的模板,利用代碼生成工具(如Freemarker、Velocity等)生成Java實體類、Mapper接口、Mapper XML文件等代碼,減少了開發者手動編寫基礎代碼的工作量。

如何在MyBatis中使用注解配置SQL語句?

在MyBatis中,除了使用XML文件配置SQL語句外,還可以使用注解來配置,這樣可以減少XML文件的使用,使代碼更加簡潔。以下為你詳細介紹如何在MyBatis中使用注解配置SQL語句:

1. 環境準備

首先要確保項目中已經添加了MyBatis的依賴。如果你使用的是Maven項目,可以在pom.xml中添加如下依賴:

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version>
</dependency>

2. 定義實體類

定義一個簡單的Java實體類,例如User類:

public class User {private Integer id;private String username;private String password;// 構造方法、getter和setter方法public User() {}public User(Integer id, String username, String password) {this.id = id;this.username = username;this.password = password;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
}

3. 定義Mapper接口并使用注解配置SQL

在Mapper接口中使用MyBatis提供的注解來配置SQL語句。常見的注解有@Select@Insert@Update@Delete

import org.apache.ibatis.annotations.*;import java.util.List;public interface UserMapper {// 查詢所有用戶@Select("SELECT * FROM user")List<User> getAllUsers();// 根據ID查詢用戶@Select("SELECT * FROM user WHERE id = #{id}")User getUserById(int id);// 插入用戶@Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")@Options(useGeneratedKeys = true, keyProperty = "id")int insertUser(User user);// 更新用戶@Update("UPDATE user SET username = #{username}, password = #{password} WHERE id = #{id}")int updateUser(User user);// 刪除用戶@Delete("DELETE FROM user WHERE id = #{id}")int deleteUser(int id);
}
注解說明:
  • @Select:用于定義查詢語句。
  • @Insert:用于定義插入語句。@Options(useGeneratedKeys = true, keyProperty = "id")表示使用數據庫自動生成的主鍵,并將生成的主鍵值設置到User對象的id屬性中。
  • @Update:用于定義更新語句。
  • @Delete:用于定義刪除語句。

4. 配置MyBatis

創建MyBatis的配置文件mybatis-config.xml,并在其中配置數據源和Mapper接口:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/your_database"/><property name="username" value="your_username"/><property name="password" value="your_password"/></dataSource></environment></environments><mappers><mapper class="com.example.mapper.UserMapper"/></mappers>
</configuration>

5. 測試代碼

編寫測試代碼來驗證注解配置的SQL語句是否正常工作:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;
import java.util.List;public class Main {public static void main(String[] args) throws Exception {// 加載MyBatis配置文件String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 獲取SqlSessiontry (SqlSession session = sqlSessionFactory.openSession()) {UserMapper userMapper = session.getMapper(UserMapper.class);// 查詢所有用戶List<User> users = userMapper.getAllUsers();System.out.println("所有用戶: " + users);// 根據ID查詢用戶User user = userMapper.getUserById(1);System.out.println("ID為1的用戶: " + user);// 插入用戶User newUser = new User(null, "testUser", "testPassword");int rows = userMapper.insertUser(newUser);System.out.println("插入用戶影響的行數: " + rows);System.out.println("新插入用戶的ID: " + newUser.getId());// 更新用戶newUser.setUsername("updatedUser");rows = userMapper.updateUser(newUser);System.out.println("更新用戶影響的行數: " + rows);// 刪除用戶rows = userMapper.deleteUser(newUser.getId());System.out.println("刪除用戶影響的行數: " + rows);session.commit();}}
}

其他復雜注解使用

動態SQL注解

MyBatis還提供了@SelectProvider@InsertProvider@UpdateProvider@DeleteProvider注解,用于實現動態SQL。以下是一個使用@SelectProvider的示例:

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;import java.util.List;public interface UserMapper {@SelectProvider(type = UserSqlProvider.class, method = "getUsersByCondition")List<User> getUsersByCondition(@Param("username") String username, @Param("password") String password);class UserSqlProvider {public String getUsersByCondition(String username, String password) {StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1 = 1");if (username != null && !username.isEmpty()) {sql.append(" AND username = #{username}");}if (password != null && !password.isEmpty()) {sql.append(" AND password = #{password}");}return sql.toString();}}
}

通過上述步驟,你就可以在MyBatis中使用注解來配置SQL語句了。注解配置適用于簡單的SQL場景,而對于復雜的SQL,使用XML配置可能會更加清晰和易于維護。

MyBatis-Plus和MyBatis的注解有哪些區別?

MyBatis-Plus 是基于 MyBatis 開發的增強工具,它在保留 MyBatis 注解的基礎上,新增了一些注解以提供更便捷的功能。以下為你詳細介紹兩者注解的區別:

共同使用的基礎注解

MyBatis-Plus 完全兼容 MyBatis 的基礎注解,這些注解在兩者中功能和使用方式基本一致:

  • @Select:用于定義查詢語句。
// MyBatis 和 MyBatis-Plus 中使用方式相同
import org.apache.ibatis.annotations.Select;
public interface UserMapper {@Select("SELECT * FROM user WHERE id = #{id}")User selectUserById(Long id);
}
  • @Insert:用于定義插入語句。
import org.apache.ibatis.annotations.Insert;
public interface UserMapper {@Insert("INSERT INTO user (username, age) VALUES (#{username}, #{age})")int insertUser(User user);
}
  • @Update:用于定義更新語句。
import org.apache.ibatis.annotations.Update;
public interface UserMapper {@Update("UPDATE user SET username = #{username} WHERE id = #{id}")int updateUser(User user);
}
  • @Delete:用于定義刪除語句。
import org.apache.ibatis.annotations.Delete;
public interface UserMapper {@Delete("DELETE FROM user WHERE id = #{id}")int deleteUserById(Long id);
}

MyBatis-Plus 新增的注解

實體類相關注解
  • @TableName:用于指定實體類對應的數據庫表名。當實體類名與數據庫表名不一致時,可以使用該注解進行映射。
import com.baomidou.mybatisplus.annotation.TableName;@TableName("t_user")
public class User {// 類的屬性和方法
}
  • @TableId:用于指定實體類中哪個屬性為主鍵,并且可以設置主鍵的生成策略。
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.IdType;public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;// 其他屬性和方法
}
  • @TableField:用于指定實體類屬性與數據庫表字段的映射關系,還可以設置字段的一些特性,如是否為數據庫表字段、字段的填充策略等。
import com.baomidou.mybatisplus.annotation.TableField;public class User {private String username;@TableField(value = "user_age", fill = FieldFill.INSERT)private Integer age;// 其他屬性和方法
}
  • @TableLogic:用于實現邏輯刪除功能,標記實體類中的某個字段為邏輯刪除字段。
import com.baomidou.mybatisplus.annotation.TableLogic;public class User {private Long id;@TableLogicprivate Integer deleted;// 其他屬性和方法
}
代碼生成相關注解
  • @Mapper:MyBatis-Plus 建議在 Mapper 接口上添加該注解,這樣可以讓 Spring 自動掃描并將其注冊為 Bean。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {// 可以自定義擴展方法
}

注解使用場景差異

  • MyBatis:其注解更側重于手動編寫 SQL 語句,適用于需要精細控制 SQL 邏輯、處理復雜業務需求的場景。開發人員需要自己編寫完整的 SQL 語句,對 SQL 有較高的掌控度。
  • MyBatis-Plus:注解主要用于簡化開發流程,提供了大量的通用 CRUD 操作,減少了手動編寫 SQL 的工作量。對于簡單的增刪改查操作,使用 MyBatis-Plus 的注解和通用方法可以快速實現;而對于復雜的業務邏輯,仍然可以結合 MyBatis 的注解手動編寫 SQL。

如何在MyBatis中使用注解配置一對多和多對多的關聯查詢?

在MyBatis中,可以使用注解來配置一對多和多對多的關聯查詢。以下分別介紹這兩種關聯查詢的實現方式:

一對多關聯查詢

1. 數據庫表結構和實體類定義

假設我們有兩個表:user(用戶表)和order(訂單表),一個用戶可以有多個訂單,這是典型的一對多關系。

  • 數據庫表結構
-- 用戶表
CREATE TABLE user (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50)
);-- 訂單表
CREATE TABLE `order` (id INT PRIMARY KEY AUTO_INCREMENT,order_number VARCHAR(50),user_id INT,FOREIGN KEY (user_id) REFERENCES user(id)
);
  • 實體類定義
import java.util.List;// 用戶實體類
public class User {private Integer id;private String username;private List<Order> orders;// 構造方法、getter和setter方法public User() {}public User(Integer id, String username) {this.id = id;this.username = username;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public List<Order> getOrders() {return orders;}public void setOrders(List<Order> orders) {this.orders = orders;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", orders=" + orders +'}';}
}// 訂單實體類
public class Order {private Integer id;private String orderNumber;private Integer userId;// 構造方法、getter和setter方法public Order() {}public Order(Integer id, String orderNumber, Integer userId) {this.id = id;this.orderNumber = orderNumber;this.userId = userId;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getOrderNumber() {return orderNumber;}public void setOrderNumber(String orderNumber) {this.orderNumber = orderNumber;}public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}@Overridepublic String toString() {return "Order{" +"id=" + id +", orderNumber='" + orderNumber + '\'' +", userId=" + userId +'}';}
}
2. Mapper 接口定義及注解配置
import org.apache.ibatis.annotations.*;import java.util.List;public interface UserMapper {@Results({@Result(property = "id", column = "id"),@Result(property = "username", column = "username"),@Result(property = "orders", column = "id", many = @Many(select = "com.example.mapper.OrderMapper.getOrdersByUserId"))})@Select("SELECT * FROM user")List<User> getAllUsers();
}public interface OrderMapper {@Select("SELECT * FROM `order` WHERE user_id = #{userId}")List<Order> getOrdersByUserId(Integer userId);
}
注解解釋
  • @Results:用于定義結果映射關系。
  • @Result:具體的映射規則,property 表示實體類的屬性名,column 表示數據庫表的列名。
  • @Many:用于處理一對多關系,select 屬性指定調用的另一個 Mapper 方法來查詢關聯的訂單數據。

多對多關聯查詢

1. 數據庫表結構和實體類定義

假設我們有三個表:student(學生表)、course(課程表)和student_course(學生 - 課程關聯表),一個學生可以選擇多門課程,一門課程也可以被多個學生選擇,這是典型的多對多關系。

  • 數據庫表結構
-- 學生表
CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50)
);-- 課程表
CREATE TABLE course (id INT PRIMARY KEY AUTO_INCREMENT,course_name VARCHAR(50)
);-- 學生 - 課程關聯表
CREATE TABLE student_course (student_id INT,course_id INT,PRIMARY KEY (student_id, course_id),FOREIGN KEY (student_id) REFERENCES student(id),FOREIGN KEY (course_id) REFERENCES course(id)
);
  • 實體類定義
import java.util.List;// 學生實體類
public class Student {private Integer id;private String name;private List<Course> courses;// 構造方法、getter和setter方法public Student() {}public Student(Integer id, String name) {this.id = id;this.name = name;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<Course> getCourses() {return courses;}public void setCourses(List<Course> courses) {this.courses = courses;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", courses=" + courses +'}';}
}// 課程實體類
public class Course {private Integer id;private String courseName;// 構造方法、getter和setter方法public Course() {}public Course(Integer id, String courseName) {this.id = id;this.courseName = courseName;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getCourseName() {return courseName;}public void setCourseName(String courseName) {this.courseName = courseName;}@Overridepublic String toString() {return "Course{" +"id=" + id +", courseName='" + courseName + '\'' +'}';}
}
2. Mapper 接口定義及注解配置
import org.apache.ibatis.annotations.*;import java.util.List;public interface StudentMapper {@Results({@Result(property = "id", column = "id"),@Result(property = "name", column = "name"),@Result(property = "courses", column = "id", many = @Many(select = "com.example.mapper.CourseMapper.getCoursesByStudentId"))})@Select("SELECT * FROM student")List<Student> getAllStudents();
}public interface CourseMapper {@Select("SELECT c.* FROM course c " +"JOIN student_course sc ON c.id = sc.course_id " +"WHERE sc.student_id = #{studentId}")List<Course> getCoursesByStudentId(Integer studentId);
}
注解解釋

同樣使用 @Results@Result@Many 注解來處理多對多關系,@Many 中的 select 屬性指定調用的另一個 Mapper 方法來查詢關聯的課程數據。

測試代碼

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;
import java.util.List;public class Main {public static void main(String[] args) throws Exception {// 加載MyBatis配置文件String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 獲取SqlSessiontry (SqlSession session = sqlSessionFactory.openSession()) {// 一對多查詢測試UserMapper userMapper = session.getMapper(UserMapper.class);List<User> users = userMapper.getAllUsers();System.out.println("一對多查詢結果:");for (User user : users) {System.out.println(user);}// 多對多查詢測試StudentMapper studentMapper = session.getMapper(StudentMapper.class);List<Student> students = studentMapper.getAllStudents();System.out.println("多對多查詢結果:");for (Student student : students) {System.out.println(student);}}}
}

通過以上步驟,你可以在 MyBatis 中使用注解配置一對多和多對多的關聯查詢。

MyBatis-Plus的注解在使用時有哪些需要注意的地方?

1. 實體類注解
  • @TableName
    • 當實體類名與數據庫表名不一致時,使用該注解指定表名。要確保表名正確,包括大小寫(某些數據庫區分大小寫)。
    • 若表名有特殊字符(如 -),需要使用反引號(`)包裹。
  • @TableId
    • 要正確設置主鍵生成策略(IdType),如 IdType.AUTO 適用于自增主鍵,IdType.INPUT 表示手動輸入主鍵值等。
    • 確保 value 屬性指定的列名與數據庫中主鍵列名一致。
  • @TableField
    • exist 屬性設置為 false 時,該屬性不會參與 SQL 語句的生成,常用于實體類中不需要映射到數據庫字段的屬性。
    • fill 屬性用于設置字段的自動填充策略,使用時要確保配置了相應的自動填充處理器。
2. 邏輯刪除注解 @TableLogic
- 要在數據庫中設計好邏輯刪除字段(通常為 `int` 類型),并設置好默認值和邏輯刪除值。
- 配置好全局的邏輯刪除字段值,否則可能導致查詢結果不符合預期。
3. 其他注解
- 在使用自定義 SQL 注解(如 `@SqlParser`)時,要了解其作用和使用場景,避免濫用導致 SQL 執行異常。

二、JDBC、MyBatis、MyBatis-Plus 實現 CURD

1. JDBC 實現 CURD
import java.sql.*;
import java.util.ArrayList;
import java.util.List;// 假設數據庫中有一個 user 表,包含 id, name, age 字段
class User {private int id;private String name;private int age;public User() {}public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}// getters and setterspublic int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{id=" + id + ", name='" + name + "', age=" + age + "}";}
}public class JdbcExample {private static final String URL = "jdbc:mysql://localhost:3306/test";private static final String USER = "root";private static final String PASSWORD = "password";public static void main(String[] args) {// 創建createUser(new User(0, "John", 25));// 查詢List<User> users = readUsers();for (User user : users) {System.out.println(user);}// 更新updateUser(new User(1, "UpdatedJohn", 26));// 刪除deleteUser(1);}public static void createUser(User user) {String sql = "INSERT INTO user (name, age) VALUES (?, ?)";try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, user.getName());pstmt.setInt(2, user.getAge());pstmt.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}public static List<User> readUsers() {List<User> users = new ArrayList<>();String sql = "SELECT * FROM user";try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)) {while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");int age = rs.getInt("age");users.add(new User(id, name, age));}} catch (SQLException e) {e.printStackTrace();}return users;}public static void updateUser(User user) {String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, user.getName());pstmt.setInt(2, user.getAge());pstmt.setInt(3, user.getId());pstmt.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}public static void deleteUser(int id) {String sql = "DELETE FROM user WHERE id = ?";try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setInt(1, id);pstmt.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}
}
2. MyBatis 實現 CURD
實體類
public class User {private int id;private String name;private int age;// getters and setterspublic int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{id=" + id + ", name='" + name + "', age=" + age + "}";}
}
Mapper 接口
public interface UserMapper {void insertUser(User user);List<User> selectAllUsers();void updateUser(User user);void deleteUser(int id);
}
Mapper XML 文件(UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><insert id="insertUser" parameterType="com.example.entity.User">INSERT INTO user (name, age) VALUES (#{name}, #{age})</insert><select id="selectAllUsers" resultType="com.example.entity.User">SELECT * FROM user</select><update id="updateUser" parameterType="com.example.entity.User">UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}</update><delete id="deleteUser" parameterType="int">DELETE FROM user WHERE id = #{id}</delete>
</mapper>
測試代碼
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;
import java.util.List;public class MyBatisExample {public static void main(String[] args) throws Exception {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper userMapper = session.getMapper(UserMapper.class);// 創建User user = new User(0, "John", 25);userMapper.insertUser(user);// 查詢List<User> users = userMapper.selectAllUsers();for (User u : users) {System.out.println(u);}// 更新user.setName("UpdatedJohn");user.setAge(26);userMapper.updateUser(user);// 刪除userMapper.deleteUser(user.getId());session.commit();}}
}
3. MyBatis-Plus 實現 CURD
實體類
import com.baomidou.mybatisplus.annotation.TableName;@TableName("user")
public class User {private int id;private String name;private int age;// getters and setterspublic int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{id=" + id + ", name='" + name + "', age=" + age + "}";}
}
Mapper 接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface UserMapper extends BaseMapper<User> {
}
測試代碼
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;import java.util.List;@SpringBootApplication
public class MyBatisPlusExample implements CommandLineRunner {@Autowiredprivate UserMapper userMapper;public static void main(String[] args) {SpringApplication.run(MyBatisPlusExample.class, args);}@Overridepublic void run(String... args) throws Exception {// 創建User user = new User();user.setName("John");user.setAge(25);userMapper.insert(user);// 查詢QueryWrapper<User> queryWrapper = new QueryWrapper<>();List<User> users = userMapper.selectList(queryWrapper);for (User u : users) {System.out.println(u);}// 更新user.setName("UpdatedJohn");user.setAge(26);userMapper.updateById(user);// 刪除userMapper.deleteById(user.getId());}
}

三、MyBatis 實現一對多查詢

數據庫表結構

假設存在 user 表和 order 表,一個用戶可以有多個訂單,表結構如下:

-- 用戶表
CREATE TABLE user (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50)
);-- 訂單表
CREATE TABLE `order` (id INT PRIMARY KEY AUTO_INCREMENT,order_number VARCHAR(50),user_id INT,FOREIGN KEY (user_id) REFERENCES user(id)
);
實體類
import java.util.List;public class User {private int id;private String name;private List<Order> orders;// getters and setterspublic int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<Order> getOrders() {return orders;}public void setOrders(List<Order> orders) {this.orders = orders;}@Overridepublic String toString() {return "User{id=" + id + ", name='" + name + "', orders=" + orders + "}";}
}public class Order {private int id;private String orderNumber;private int userId;// getters and setterspublic int getId() {return id;}public void setId(int id) {this.id = id;}public String getOrderNumber() {return orderNumber;}public void setOrderNumber(String orderNumber) {this.orderNumber = orderNumber;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}@Overridepublic String toString() {return "Order{id=" + id + ", orderNumber='" + orderNumber + "', userId=" + userId + "}";}
}
Mapper 接口
public interface UserMapper {List<User> selectAllUsersWithOrders();
}
Mapper XML 文件(UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><resultMap id="UserWithOrdersResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><result property="name" column="user_name"/><collection property="orders" ofType="com.example.entity.Order"><id property="id" column="order_id"/><result property="orderNumber" column="order_number"/><result property="userId" column="user_id"/></collection></resultMap><select id="selectAllUsersWithOrders" resultMap="UserWithOrdersResultMap">SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id, o.order_numberFROM user uLEFT JOIN `order` o ON u.id = o.user_id</select>
</mapper>
測試代碼
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;
import java.util.List;public class OneToManyExample {public static void main(String[] args) throws Exception {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper userMapper = session.getMapper(UserMapper.class);List<User> users = userMapper.selectAllUsersWithOrders();for (User user : users) {System.out.println(user);}}}
}

以上代碼展示了 JDBC、MyBatis、MyBatis-Plus 實現 CURD 以及 MyBatis 實現一對多查詢的詳細過程。注意,運行代碼前需要確保數據庫連接配置正確,并且添加相應的依賴(如 MyBatis、MyBatis-Plus、JDBC 驅動等)。

MyBatis-Plus 實現一對多

以下將詳細介紹如何使用 MyBatis-Plus 結合 mapper.xml 實現一對多查詢,仍以用戶(User)和訂單(Order)的一對多關系為例。

1. 數據庫表結構

-- 用戶表
CREATE TABLE user (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50)
);-- 訂單表
CREATE TABLE `order` (id INT PRIMARY KEY AUTO_INCREMENT,order_number VARCHAR(50),user_id INT,FOREIGN KEY (user_id) REFERENCES user(id)
);

2. 實體類定義

// User.java
public class User {private Integer id;private String username;private List<Order> orders;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public List<Order> getOrders() {return orders;}public void setOrders(List<Order> orders) {this.orders = orders;}
}// Order.java
public class Order {private Integer id;private String orderNumber;private Integer userId;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getOrderNumber() {return orderNumber;}public void setOrderNumber(String orderNumber) {this.orderNumber = orderNumber;}public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}
}

3. Mapper 接口定義

import com.baomidou.mybatisplus.core.mapper.BaseMapper;// UserMapper.java
public interface UserMapper extends BaseMapper<User> {/*** 查詢所有用戶及其關聯的訂單* @return 用戶列表*/List<User> getAllUsersWithOrders();
}

4. Mapper XML 文件(UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><!-- 定義結果映射 --><resultMap id="UserWithOrdersResultMap" type="com.example.entity.User"><id property="id" column="user_id"/><result property="username" column="username"/><!-- 一對多關聯,使用 collection 標簽 --><collection property="orders" ofType="com.example.entity.Order"><id property="id" column="order_id"/><result property="orderNumber" column="order_number"/><result property="userId" column="user_id"/></collection></resultMap><!-- 查詢所有用戶及其關聯的訂單 --><select id="getAllUsersWithOrders" resultMap="UserWithOrdersResultMap">SELECT u.id AS user_id,u.username,o.id AS order_id,o.order_numberFROM user uLEFT JOIN `order` o ON u.id = o.user_id</select>
</mapper>

5. Service 層實現

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;// UserService.java
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getUsersWithOrders() {return userMapper.getAllUsersWithOrders();}
}

6. 測試代碼

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;import java.util.List;// Application.java
@SpringBootApplication
public class Application implements CommandLineRunner {@Autowiredprivate UserService userService;public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Overridepublic void run(String... args) throws Exception {List<User> users = userService.getUsersWithOrders();for (User user : users) {System.out.println("User: " + user.getUsername());List<Order> orders = user.getOrders();if (orders != null) {for (Order order : orders) {System.out.println("  Order: " + order.getOrderNumber());}}}}
}

代碼解釋

  • 實體類User 類中包含一個 List<Order> 類型的 orders 屬性,用于存儲該用戶關聯的所有訂單。
  • Mapper 接口UserMapper 繼承自 BaseMapper<User>,并定義了 getAllUsersWithOrders 方法用于查詢所有用戶及其關聯的訂單。
  • Mapper XML 文件
    • resultMap 標簽定義了結果映射規則,id 標簽用于映射主鍵,result 標簽用于映射普通字段。
    • collection 標簽用于處理一對多關系,ofType 屬性指定集合中元素的類型。
    • select 語句使用 LEFT JOIN 關聯 user 表和 order 表,確保即使某個用戶沒有訂單,該用戶信息也會被查詢出來。
  • Service 層UserService 中的 getUsersWithOrders 方法調用 UserMappergetAllUsersWithOrders 方法獲取用戶及其關聯的訂單信息。
  • 測試代碼:在 Application 類中調用 UserServicegetUsersWithOrders 方法,并打印查詢結果。

通過以上步驟,你可以使用 MyBatis-Plus 結合 mapper.xml 實現一對多查詢。

JDBC、MyBatis、MyBatis-Plus是Java開發中常用的數據庫操作技術,以下是它們的架構圖、底層原理和底層實現的相關介紹:

JDBC

  • 架構圖
    • JDBC架構主要包括Java應用程序、JDBC API、JDBC驅動管理器、JDBC驅動程序和數據庫。Java應用程序通過JDBC API與JDBC驅動管理器交互,驅動管理器根據不同的數據庫類型選擇合適的JDBC驅動程序,然后通過驅動程序與數據庫建立連接并執行SQL操作。
  • 底層原理
    • 基于Java的接口和類實現對各種數據庫的統一操作。它定義了一系列標準的接口,如ConnectionStatementResultSet等,這些接口提供了與數據庫交互的方法。不同數據庫廠商提供各自的JDBC驅動實現這些接口,使得Java程序能夠以統一的方式操作不同類型的數據庫。
  • 底層實現
    • 當Java程序加載JDBC驅動時,會通過Class.forName()方法將驅動類加載到內存中。驅動類實現了java.sql.Driver接口,在加載過程中會向DriverManager注冊自己。DriverManager負責管理和維護已注冊的驅動程序列表。當需要建立數據庫連接時,DriverManager會根據連接字符串中的數據庫標識,選擇合適的驅動程序來建立連接。在連接建立后,通過StatementPreparedStatement接口來執行SQL語句,將SQL語句發送到數據庫服務器,數據庫服務器執行完后將結果以ResultSet的形式返回給Java程序。

MyBatis

  • 架構圖
    • MyBatis的架構主要涉及應用程序、SQL映射配置文件(XML或注解)、MyBatis核心組件(如SqlSessionFactory、SqlSession等)、JDBC和數據庫。應用程序通過MyBatis核心組件與數據庫交互,核心組件根據配置文件中的信息來構建SQL語句并執行。
  • 底層原理
    • 核心是將SQL語句與Java對象進行映射。通過配置文件或注解定義SQL語句以及參數和結果的映射關系,在運行時,MyBatis根據這些配置信息,將Java方法的參數轉換為SQL語句中的參數值,執行SQL后再將結果集映射為Java對象返回給應用程序。
  • 底層實現
    • 利用Java的反射機制和動態代理技術。在啟動時,MyBatis會解析配置文件或掃描注解,將SQL語句、參數映射、結果映射等信息加載到內存中,構建Configuration對象。通過SqlSessionFactory創建SqlSessionSqlSession是與數據庫交互的核心接口。當執行SQL操作時,MyBatis會根據配置信息創建StatementHandlerParameterHandlerResultSetHandler等對象來處理SQL語句的執行、參數設置和結果處理。對于接口的映射,MyBatis使用動態代理為接口生成代理對象,在代理對象中攔截方法調用,根據方法名和參數等信息構建并執行SQL語句。

MyBatis-Plus

  • 架構圖
    • MyBatis-Plus在MyBatis的基礎上進行了擴展,架構上增加了代碼生成器、分頁插件、性能分析插件等組件。應用程序通過MyBatis-Plus提供的增強API與數據庫交互,這些API在MyBatis核心組件的基礎上進行了封裝和擴展。
  • 底層原理
    • 基于MyBatis框架,對MyBatis的功能進行增強和擴展。它提供了通用的CRUD操作接口和方法,通過約定大于配置的方式,減少了開發人員編寫SQL語句的工作量。同時,支持自動生成代碼、分頁插件、邏輯刪除等功能。
  • 底層實現
    • 繼承了MyBatis的底層實現機制,通過自定義插件和攔截器來實現功能擴展。例如,在啟動時,MyBatis-Plus的代碼生成器可以根據數據庫表結構和配置信息自動生成實體類、Mapper接口、Mapper XML等文件。分頁插件通過攔截StatementHandler,在SQL語句執行前對其進行改寫,添加分頁相關的邏輯。對于通用的CRUD操作,MyBatis-Plus利用MyBatis的ConfigurationSqlSession等核心對象,根據實體類的信息和方法調用,自動構建SQL語句并執行。

JDBC、MyBatis、MyBatis-Plus 中的事務分別是什么?怎么實現的給出代碼或者sql腳本? 底層原理是什么?

1. JDBC 中的事務

事務概念

在 JDBC 中,事務是一組不可分割的數據庫操作序列,這些操作要么全部成功執行并持久化到數據庫(提交),要么在出現錯誤時全部撤銷(回滾),以此保證數據的一致性和完整性。

實現代碼
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;public class JDBCTransactionExample {private static final String URL = "jdbc:mysql://localhost:3306/your_database";private static final String USER = "your_username";private static final String PASSWORD = "your_password";public static void main(String[] args) {Connection connection = null;Statement statement = null;try {// 加載驅動Class.forName("com.mysql.cj.jdbc.Driver");// 獲取連接connection = DriverManager.getConnection(URL, USER, PASSWORD);// 關閉自動提交,開啟事務connection.setAutoCommit(false);statement = connection.createStatement();// 執行一系列 SQL 操作String sql1 = "INSERT INTO account (id, balance) VALUES (1, 1000)";statement.executeUpdate(sql1);String sql2 = "UPDATE account SET balance = balance - 500 WHERE id = 1";statement.executeUpdate(sql2);// 提交事務connection.commit();System.out.println("事務提交成功");} catch (Exception e) {// 出現異常,回滾事務if (connection != null) {try {connection.rollback();System.out.println("事務回滾成功");} catch (SQLException ex) {ex.printStackTrace();}}e.printStackTrace();} finally {// 關閉資源try {if (statement != null) statement.close();if (connection != null) connection.close();} catch (SQLException e) {e.printStackTrace();}}}
}
底層原理

JDBC 事務的底層原理基于數據庫本身的事務管理機制。當調用 connection.setAutoCommit(false) 時,JDBC 驅動會通知數據庫服務器開始一個新的事務。之后執行的 SQL 語句都在這個事務的上下文中進行。當調用 connection.commit() 時,JDBC 驅動會向數據庫服務器發送提交事務的指令,數據庫服務器將事務中所有操作的結果持久化到磁盤;當調用 connection.rollback() 時,數據庫服務器會撤銷事務中已經執行的操作。

2. MyBatis 中的事務

事務概念

MyBatis 中的事務和 JDBC 事務本質相同,是為了確保一組數據庫操作的原子性、一致性、隔離性和持久性(ACID 特性)。

實現代碼

以下是使用 MyBatis 手動控制事務的示例:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;public class MyBatisTransactionExample {public static void main(String[] args) {SqlSession sqlSession = null;try {// 加載 MyBatis 配置文件String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 獲取 SqlSession,關閉自動提交,開啟事務sqlSession = sqlSessionFactory.openSession(false);// 執行數據庫操作,假設存在一個 UserMapper 接口和對應的方法// UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// userMapper.insertUser(new User());// userMapper.updateUser(new User());// 提交事務sqlSession.commit();System.out.println("事務提交成功");} catch (IOException e) {e.printStackTrace();} catch (Exception e) {// 出現異常,回滾事務if (sqlSession != null) {sqlSession.rollback();System.out.println("事務回滾成功");}e.printStackTrace();} finally {// 關閉 SqlSessionif (sqlSession != null) {sqlSession.close();}}}
}
底層原理

MyBatis 事務底層依賴于 JDBC 事務。MyBatis 的 SqlSession 持有一個 Connection 對象,當調用 sqlSessionFactory.openSession(false) 時,會將 Connection 的自動提交模式關閉,開啟一個新的事務。在事務執行過程中,MyBatis 通過 StatementHandlerParameterHandler 等組件將 SQL 語句發送到數據庫執行。當調用 sqlSession.commit()sqlSession.rollback() 時,實際上是調用 Connectioncommit()rollback() 方法來提交或回滾事務。

3. MyBatis-Plus 中的事務

事務概念

MyBatis-Plus 是基于 MyBatis 的增強工具,其事務概念和 MyBatis 一致,用于保證數據庫操作的一致性和完整性。

實現代碼

通常結合 Spring Boot 和 Spring 的事務管理,使用 @Transactional 注解來實現事務控制。

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic void doTransaction() {// 執行數據庫操作User user = new User();user.setName("John");userMapper.insert(user);// 模擬異常int result = 1 / 0;QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", "John");List<User> users = userMapper.selectList(queryWrapper);for (User u : users) {u.setName("Updated John");userMapper.updateById(u);}}
}
底層原理

MyBatis-Plus 借助 Spring 的事務管理機制,通過 Spring AOP(面向切面編程)實現事務控制。當方法上添加了 @Transactional 注解時,Spring 在方法執行前會創建一個事務上下文,獲取 DataSourceTransactionManager 管理的 Connection,并將其綁定到當前線程。在方法執行過程中,MyBatis-Plus 的數據庫操作會在這個事務上下文中進行。如果方法正常執行完畢,Spring 的事務管理器會調用 Connectioncommit() 方法提交事務;如果方法拋出異常,事務管理器會根據異常類型和 @Transactional 注解的配置,調用 Connectionrollback() 方法回滾事務。

JDBC、MyBatis、MyBatis-Plus怎么保證冪等性

你想問的可能是“JDBC、MyBatis、MyBatis - Plus 怎么保證冪等性”。冪等性是指一個操作無論執行多少次,所產生的影響都和執行一次的影響相同。下面分別介紹這三種技術保證冪等性的方法:

JDBC 保證冪等性

1. 唯一索引

在數據庫表中創建唯一索引,例如在插入操作時,如果存在唯一索引,重復插入相同的數據會導致數據庫拋出唯一約束沖突異常,從而避免重復插入。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class JDBCIdempotencyExample {private static final String URL = "jdbc:mysql://localhost:3306/your_database";private static final String USER = "your_username";private static final String PASSWORD = "your_password";public static void main(String[] args) {try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {// 假設表中有一個唯一索引在 username 字段上String sql = "INSERT INTO users (username, email) VALUES (?, ?)";try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {preparedStatement.setString(1, "unique_user");preparedStatement.setString(2, "user@example.com");try {preparedStatement.executeUpdate();System.out.println("插入成功");} catch (SQLException e) {if (e.getSQLState().equals("23000")) {System.out.println("由于唯一約束,插入失敗,數據可能已存在");} else {e.printStackTrace();}}}} catch (SQLException e) {e.printStackTrace();}}
}
2. 查詢 - 插入模式

在執行插入操作之前,先查詢數據是否已經存在,如果存在則不進行插入操作。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class JDBCQueryInsertExample {private static final String URL = "jdbc:mysql://localhost:3306/your_database";private static final String USER = "your_username";private static final String PASSWORD = "your_password";public static void main(String[] args) {try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {// 查詢數據是否存在String querySql = "SELECT id FROM users WHERE username = ?";try (PreparedStatement queryStatement = connection.prepareStatement(querySql)) {queryStatement.setString(1, "unique_user");try (ResultSet resultSet = queryStatement.executeQuery()) {if (!resultSet.next()) {// 數據不存在,進行插入操作String insertSql = "INSERT INTO users (username, email) VALUES (?, ?)";try (PreparedStatement insertStatement = connection.prepareStatement(insertSql)) {insertStatement.setString(1, "unique_user");insertStatement.setString(2, "user@example.com");insertStatement.executeUpdate();System.out.println("插入成功");}} else {System.out.println("數據已存在,不進行插入");}}}} catch (SQLException e) {e.printStackTrace();}}
}

MyBatis 保證冪等性

1. 使用唯一索引和異常處理

和 JDBC 類似,利用數據庫的唯一索引,在 MyBatis 的 Mapper 接口方法中捕獲唯一約束沖突異常進行處理。

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class MyBatisIdempotencyExample {public static void main(String[] args) {String resource = "mybatis-config.xml";try (InputStream inputStream = org.apache.ibatis.io.Resources.getResourceAsStream(resource)) {SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = new User();user.setUsername("unique_user");user.setEmail("user@example.com");try {userMapper.insertUser(user);sqlSession.commit();System.out.println("插入成功");} catch (PersistenceException e) {if (e.getCause() instanceof java.sql.SQLIntegrityConstraintViolationException) {System.out.println("由于唯一約束,插入失敗,數據可能已存在");} else {e.printStackTrace();}}}} catch (Exception e) {e.printStackTrace();}}
}
2. 查詢 - 插入邏輯

在 Service 層實現查詢 - 插入的邏輯。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public void insertUserIfNotExists(User user) {User existingUser = userMapper.selectUserByUsername(user.getUsername());if (existingUser == null) {userMapper.insertUser(user);}}
}

MyBatis - Plus 保證冪等性

1. 利用唯一索引和異常處理

MyBatis - Plus 基于 MyBatis,同樣可以利用數據庫的唯一索引,在調用 MyBatis - Plus 提供的插入方法時捕獲唯一約束沖突異常。

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public void insertUser(User user) {try {userMapper.insert(user);} catch (MybatisPlusException e) {if (e.getCause() instanceof java.sql.SQLIntegrityConstraintViolationException) {System.out.println("由于唯一約束,插入失敗,數據可能已存在");} else {e.printStackTrace();}}}
}
2. 使用 saveOrUpdate 方法

MyBatis - Plus 提供了 saveOrUpdate 方法,該方法會根據實體的主鍵是否存在來決定是執行插入還是更新操作,一定程度上保證了冪等性。

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {public void saveOrUpdateUser(User user) {this.saveOrUpdate(user);}
}
3. 分布式鎖和令牌機制(適用于高并發場景)

在高并發場景下,可以使用分布式鎖(如 Redis 分布式鎖)或令牌機制來保證操作的冪等性。例如,在執行操作前,先從 Redis 中獲取一個唯一的令牌,操作完成后將令牌存儲在 Redis 中,后續再次請求時先檢查令牌是否已存在,如果存在則不執行操作。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.UUID;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisTemplate<String, String> redisTemplate;public void insertUserWithIdempotency(User user) {String token = UUID.randomUUID().toString();if (redisTemplate.opsForValue().setIfAbsent(token, "processed")) {try {userMapper.insert(user);} finally {redisTemplate.delete(token);}} else {System.out.println("操作已執行,避免重復操作");}}
}

以上就是 JDBC、MyBatis、MyBatis - Plus 保證冪等性的常見方法,不同的場景可以選擇合適的方式來確保操作的冪等性。

JDBC、MyBatis 和 MyBatis - Plus 的事務操作本質上是基于數據庫事務

是的,你的理解是正確的。JDBC、MyBatis 和 MyBatis - Plus 的事務操作本質上是基于數據庫事務的,若數據庫本身不支持事務,這些框架即便有操作事務的方法和功能,也無法保證事務的特性。下面為你詳細解釋:

本質依賴數據庫事務

  • JDBC:作為 Java 與數據庫交互的基礎 API,JDBC 本身并沒有實現事務機制,而是通過調用數據庫驅動提供的方法來管理事務。例如,Connection 類中的 setAutoCommitcommitrollback 方法,實際上是委托給數據庫驅動與數據庫進行交互,依賴數據庫自身的事務處理能力來實現事務的開啟、提交和回滾。
  • MyBatis:MyBatis 是一個持久層框架,它在 JDBC 的基礎上進行了封裝和抽象。MyBatis 的事務管理器(如 JdbcTransactionManagedTransaction)底層還是借助 JDBC 與數據庫進行通信,依賴數據庫的事務功能來保證數據操作的一致性和完整性。
  • MyBatis - Plus:MyBatis - Plus 是 MyBatis 的增強工具,它繼承了 MyBatis 的事務管理機制。在使用 Spring 集成時,雖然通過 @Transactional 注解等方式簡化了事務的使用,但本質上依然依賴數據庫的事務支持,只是將事務管理的配置和使用進行了封裝。

數據庫不支持事務時的情況

  • 無法保證原子性:事務的原子性要求一組操作要么全部成功,要么全部失敗。如果數據庫不支持事務,當執行一系列操作時,可能會出現部分操作成功,部分操作失敗的情況,無法實現原子性。例如,在一個轉賬操作中,從一個賬戶扣款成功,但向另一個賬戶存款失敗,由于沒有事務的支持,無法回滾扣款操作,導致數據不一致。
  • 無法保證一致性:一致性是指事務執行前后數據庫的狀態保持一致。沒有數據庫事務的支持,在并發操作或異常情況下,數據可能會處于不一致的狀態。例如,多個線程同時對同一數據進行修改,由于沒有事務的隔離機制,可能會導致數據的錯誤更新。
  • 無法保證隔離性:隔離性是指多個事務之間相互隔離,互不干擾。數據庫不支持事務時,無法提供不同的隔離級別來控制事務之間的可見性,可能會出現臟讀、不可重復讀、幻讀等問題。
  • 無法保證持久性:持久性要求事務一旦提交,其對數據庫的修改就永久保存。若數據庫不支持事務,在系統崩潰或出現其他異常時,已經執行的操作可能會丟失,無法保證數據的持久性。

綜上所述,JDBC、MyBatis 和 MyBatis - Plus 的事務操作依賴于數據庫的事務支持,數據庫的事務功能是實現這些框架事務特性的基礎。

JDBC、MyBatis 和 MyBatis - Plus 怎么實現邏輯刪除?

邏輯刪除是指在數據庫中并不真正刪除數據,而是通過設置一個標志位來表示該數據已被刪除,這樣可以避免物理刪除帶來的數據丟失風險,方便后續的數據恢復和審計。以下分別介紹 JDBC、MyBatis 和 MyBatis - Plus 實現邏輯刪除的方法:

JDBC 實現邏輯刪除

1. 數據庫表設計

在需要進行邏輯刪除的表中添加一個邏輯刪除標志字段,例如 is_deleted,通常使用 0 表示未刪除,1 表示已刪除。

CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50),is_deleted TINYINT(1) DEFAULT 0
);
2. Java 代碼實現

在執行刪除操作時,將 is_deleted 字段更新為 1,而不是真正刪除記錄。在查詢數據時,過濾掉 is_deleted1 的記錄。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class JDBCLogicDeleteExample {private static final String URL = "jdbc:mysql://localhost:3306/your_database";private static final String USER = "your_username";private static final String PASSWORD = "your_password";public static void main(String[] args) {// 邏輯刪除操作try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {String deleteSql = "UPDATE users SET is_deleted = 1 WHERE id = ?";try (PreparedStatement preparedStatement = connection.prepareStatement(deleteSql)) {preparedStatement.setInt(1, 1);preparedStatement.executeUpdate();System.out.println("邏輯刪除成功");}} catch (SQLException e) {e.printStackTrace();}// 查詢未刪除的數據try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {String selectSql = "SELECT * FROM users WHERE is_deleted = 0";try (PreparedStatement preparedStatement = connection.prepareStatement(selectSql);ResultSet resultSet = preparedStatement.executeQuery()) {while (resultSet.next()) {System.out.println("ID: " + resultSet.getInt("id") + ", Username: " + resultSet.getString("username"));}}} catch (SQLException e) {e.printStackTrace();}}
}

MyBatis 實現邏輯刪除

1. 數據庫表設計

同樣在表中添加 is_deleted 字段。

CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50),is_deleted TINYINT(1) DEFAULT 0
);
2. 實體類定義

在實體類中添加 isDeleted 屬性,并使用 @TableLogic 注解(如果使用 MyBatis - Plus 注解支持)或在 SQL 中手動處理。

import lombok.Data;@Data
public class User {private Integer id;private String username;private Integer isDeleted;
}
3. Mapper 接口和 XML 文件

在 Mapper 接口中定義邏輯刪除和查詢方法,在 XML 文件中編寫相應的 SQL 語句。

import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {int logicDeleteById(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><update id="logicDeleteById">UPDATE usersSET is_deleted = 1WHERE id = #{id}</update><select id="selectAllUsers" resultType="com.example.entity.User">SELECT * FROM users WHERE is_deleted = 0</select>
</mapper>

MyBatis - Plus 實現邏輯刪除

1. 數據庫表設計

添加 is_deleted 字段。

CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50),is_deleted TINYINT(1) DEFAULT 0
);
2. 實體類定義

使用 @TableLogic 注解標記邏輯刪除字段。

import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;@Data
public class User {private Integer id;private String username;@TableLogicprivate Integer isDeleted;
}
3. 配置邏輯刪除規則

在配置類中配置邏輯刪除的規則。

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyBatisPlusConfig {@Beanpublic GlobalConfig globalConfig() {GlobalConfig globalConfig = new GlobalConfig();GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();dbConfig.setLogicDeleteField("is_deleted");dbConfig.setLogicNotDeleteValue("0");dbConfig.setLogicDeleteValue("1");globalConfig.setDbConfig(dbConfig);return globalConfig;}
}
4. 使用 MyBatis - Plus 進行邏輯刪除和查詢
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;@Service
public class UserService extends ServiceImpl<UserMapper, User> {public void logicDeleteUser(Integer id) {this.removeById(id); // 調用 MyBatis - Plus 的 removeById 方法進行邏輯刪除}public void selectAllUsers() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();List<User> users = this.list(queryWrapper); // 查詢未刪除的數據for (User user : users) {System.out.println("ID: " + user.getId() + ", Username: " + user.getUsername());}}
}

以上就是 JDBC、MyBatis 和 MyBatis - Plus 實現邏輯刪除的方法,其中 MyBatis - Plus 提供了更便捷的方式來實現邏輯刪除,減少了開發人員的工作量。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/71607.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/71607.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/71607.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

GCC RISCV 后端 -- GCC Passes 注釋

在前面文章提到&#xff0c;當GCC 前端完成對C源代碼解析完成后&#xff0c;就會使用 處理過程&#xff08;Passes&#xff09;機制&#xff0c;通過一系列的處理過程&#xff0c;將 GENERIC IR 表示的C程序 轉步轉換成 目標機器的匯編語言。過程描述如下圖所示&#xff1a; 此…

基于Python實現的智能旅游推薦系統(Django)

基于Python實現的智能旅游推薦系統(Django) 開發語言:Python 數據庫&#xff1a;MySQL所用到的知識&#xff1a;Django框架工具&#xff1a;pycharm、Navicat 系統功能實現 總體設計 系統實現 系統首頁模塊 統首頁頁面主要包括首頁&#xff0c;旅游資訊&#xff0c;景點信息…

鴻蒙全棧開發 D2

課程目標 掌握ArkTS基礎語法與核心概念理解聲明式UI開發范式能獨立開發簡單鴻蒙應用組件建立規范的代碼編寫習慣 第一部分&#xff1a;初識ArkTS 1.1 語言全景認知 #mermaid-svg-V5mnjQN3DAHkfoBo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size…

【YashanDB認證】yashandb23.3.1 個人版單機部署安裝實踐

YCA報名鏈接如下: YashanDB|崖山數據庫系統YashanDB學習中心-YCA認證詳情 目前免費 主要參考文檔&#xff1a; 單機&#xff08;主備&#xff09;部署 | YashanDB Doc 另外還參考摩天輪文章&#xff1a; YashanDB 23.2.9.101 企業版安裝步驟搶先看&#xff01; - 墨天輪 …

【藍橋杯】每天一題,理解邏輯(3/90)【Leetcode 快樂數】

閑話系列&#xff1a;每日一題&#xff0c;禿頭有我&#xff0c;Hello&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;,我是IF‘Maxue&#xff0c;歡迎大佬們來參觀我寫的藍橋杯系列&#xff0c;我好久沒有更新博客了&#xff0c;因為up豬我寒假用自己的勞動換了…

爬蟲Incapsula reese84加密案例:Etihad航空

聲明: 該文章為學習使用,嚴禁用于商業用途和非法用途,違者后果自負,由此產生的一切后果均與作者無關 一、找出需要加密的參數 1.js運行 atob(‘aHR0cHM6Ly93d3cuZXRpaGFkLmNvbS96aC1jbi8=’) 拿到網址,F12打開調試工具,隨便搜索航班,切換到network搜索一個時間點可以找…

緩存雪崩 緩存擊穿 緩存穿透

1. redis使用場景-緩存-緩存穿透 在實際開發中&#xff0c;Redis 被廣泛應用于緩存&#xff0c;以提高系統性能和響應速度。然而&#xff0c;在使用緩存時&#xff0c;需要注意一些問題&#xff0c;其中 緩存穿透 是一個常見且需要重點關注的場景。 什么是緩存穿透 ● 緩存穿…

【YOLOv12改進trick】多尺度大核注意力機制MLKA模塊引入YOLOv12,實現多尺度目標檢測漲點,含創新點Python代碼,方便發論文

??改進模塊??:多尺度大核注意力機制(MLKA) ??解決問題??:MLKA模塊結合多尺度、門控機制和空間注意力,顯著增強卷積網絡的模型表示能力。 ??改進優勢??:超分辨的MLKA模塊對小目標和模糊目標漲點很明顯 ??適用場景??:小目標檢測、模糊目標檢測等 ??思路…

better-sqlite3之exec方法

在 better-sqlite3 中&#xff0c;.exec() 方法用于執行包含多個 SQL 語句的字符串。與預編譯語句相比&#xff0c;這種方法性能較差且安全性較低&#xff0c;但有時它是必要的&#xff0c;特別是當你需要從外部文件&#xff08;如 SQL 腳本&#xff09;中執行多個 SQL 語句時。…

電路基礎:【1】PN結二極管制作電橋點亮LED燈

第一章&#xff1a;PN結二極管制作電橋點亮LED燈 文章目錄 第一章&#xff1a;PN結二極管制作電橋點亮LED燈前言一、電路原理二、電路圖與元器件1.電路圖 做實驗總結 前言 在本章中&#xff0c;我們將探討如何通過PN結二極管制作電橋電路&#xff0c;并利用該電路點亮LED燈。L…

XHR請求解密:抓取動態生成數據的方法

在如今動態頁面大行其道的時代&#xff0c;傳統的靜態頁面爬蟲已無法滿足數據采集需求。尤其是在目標網站通過XHR&#xff08;XMLHttpRequest&#xff09;動態加載數據的情況下&#xff0c;如何精準解密XHR請求、捕獲動態生成的數據成為關鍵技術難題。本文將深入剖析XHR請求解密…

機器學習數學基礎:42.AMOS 結構方程模型(SEM)分析的系統流程

該流程圖完整呈現了 AMOS 結構方程模型&#xff08;SEM&#xff09;分析的系統流程&#xff0c;具體步驟及內涵如下&#xff1a; 1. 模型設定 基于理論基礎或研究假設&#xff0c;構建結構方程模型的初始框架&#xff0c;明確潛變量與顯變量的關系、測量模型&#xff08;因子…

以太網通訊

接口開發筆記-WebApi-CSDN博客 以太網常用通訊協議 1、modbus tcp using EasyModbus; using System;class Program {static void Main(string[] args){// 創建Modbus客戶端實例ModbusClient modbusClient new ModbusClient("192.168.1.100"); // IP地址modbusCli…

Arcgis中添加腳本工具箱

文章目錄 準備資料1、打開arcmap2、找到目錄窗口3、復制粘貼工具箱的路徑4、添加或者確認python腳本路徑準備資料 (1)工具箱 (2)python腳本 1、打開arcmap 2、找到目錄窗口 3、復制粘貼工具箱的路徑 4、添加或者確認python腳本路徑 腳本上右鍵屬性(注意:腳本內容和路徑…

TDengine SQL查詢語法

簡介 TDengine 中的查詢 SQL 基本遵循 MYSQL 的查詢語法&#xff0c;大部分查詢都是通過超級表按時間維度進行的各種查詢。 TDengine 時序數據庫以時間為主索引列進行數據組織排序及存儲&#xff0c;同時按存儲塊做了預計算&#xff0c;所以在無普通列過濾的 SQL 查詢語句中聚…

Apache nifi demo 實驗

Apache nifi 是個數據流系統&#xff0c;可以通過配置 自定義的流程來實現數據的轉換。 比如可以配置一個流程&#xff0c;讀取數據庫里的數據&#xff0c;再轉換&#xff0c;最后保存到本地文件。 這樣可以來實現一些數據轉換的操作&#xff0c;而不用特地編寫程序來導入導出。…

javascript一些原生方法記錄

Element.scrollIntoView() Element 接口的 scrollIntoView() 方法會滾動元素的父容器&#xff0c;使被調用 scrollIntoView() 的元素對用戶可見。 structuredClone() 方法 Window 接口的 structuredClone() 方法使用結構化克隆算法將給定的值進行深拷貝。

記一次ScopeSentry搭建

介紹 Scope Sentry是一款具有資產測繪、子域名枚舉、信息泄露檢測、漏洞掃描、目錄掃描、子域名接管、爬蟲、頁面監控功能的工具&#xff0c;通過構建多個節點&#xff0c;自由選擇節點運行掃描任務。當出現新漏洞時可以快速排查關注資產是否存在相關組件。 目前功能 插件系…

Spring提供的SPEL表達式

SPEL 1. 概述 SpEL是Spring框架中用于表達式語言的一種方式。它類似于其他編程語言中的表達式語言&#xff0c;用于在運行時計算值或執行特定任務。 SpEL提供了一種簡單且強大的方式來訪問和操作對象的屬性、調用對象的方法&#xff0c;以及實現運算、條件判斷等操作。它可以…

【Azure 架構師學習筆記】- Azure Databricks (14) -- 搭建Medallion Architecture part 2

本文屬于【Azure 架構師學習筆記】系列。 本文屬于【Azure Databricks】系列。 接上文 【Azure 架構師學習筆記】- Azure Databricks (13) – 搭建Medallion Architecture part 1 前言 上文搭建了ADB 與外部的交互部分&#xff0c;本篇搭建ADB 內部配置來滿足medallion 架構。…