從零開始手寫 mybatis(四)- mybatis 事務管理機制詳解

2020-09-04 老馬嘯西風

前景回顧

第一節 從零開始手寫 mybatis(一)MVP 版本 中我們實現了一個最基本的可以運行的 mybatis。

第二節 從零開始手寫 mybatis(二)mybatis interceptor 插件機制詳解

第三節 從零開始手寫 mybatis(三)jdbc pool 從零實現資料庫連接池

本節我們一起來學習一下 mybatis 中的事務管理。



mybatis 中的事務管理

mybatis 事務有兩種使用方式:

  1. 使用JDBC的事務管理機制:即使用 java.Sql.Connection對象完成對事務的提交,回滾和關閉操作。
  2. 使用MANAGED的事務管理機制:mybatis本身不會去實現事務管理的相關操作,而是交個外部容器來管理事務。當與spring整合使用後,一般使用spring來管理事務。

事務工廠 TransactionFactory

接口定義

這個是對事務的一個工廠,接口如下:

public interface TransactionFactory { /** * Sets transaction factory custom properties. * @param props */ void setProperties(Properties props); /** * Creates a {@link Transaction} out of an existing connection. * @param conn Existing database connection * @return Transaction * @since 3.1.0 */ Transaction newTransaction(Connection conn); /** * Creates a {@link Transaction} out of a datasource. * @param dataSource DataSource to take the connection from * @param level Desired isolation level * @param autoCommit Desired autocommit * @return Transaction * @since 3.1.0 */ Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);}

主要就是如何根據一個 DataSource 創建一個 Transaction。

實際上整體感覺意義不大。

最核心的還是要看一下 Transaction 的實現。

Transaction 接口

public interface Transaction { /** * Retrieve inner database connection * @return DataBase connection * @throws SQLException */ Connection getConnection() throws SQLException; /** * Commit inner database connection. * @throws SQLException */ void commit() throws SQLException; /** * Rollback inner database connection. * @throws SQLException */ void rollback() throws SQLException; /** * Close inner database connection. * @throws SQLException */ void close() throws SQLException; /** * Get transaction timeout if set * @throws SQLException */ Integer getTimeout() throws SQLException;}

這裡最核心的實際上只有 commit() 和 rollback(),其他的都是可以忽略的。

針對 getTimeout() 我們就可以為 mybatis 提供一個操作的超時機制。

JdbcTransaction 實現

基於 jdbc 機制的一些處理。

public class JdbcTransaction implements Transaction { private static final Log log = LogFactory.getLog(JdbcTransaction.class); protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommmit; public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommmit = desiredAutoCommit; } public JdbcTransaction(Connection connection) { this.connection = connection; } @Override public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; } @Override public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug(&34; + connection + &34;); } connection.commit(); } } @Override public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug(&34; + connection + &34;); } connection.rollback(); } } @Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug(&34; + connection + &34;); } connection.close(); } } protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug(&34; + desiredAutoCommit + &34; + connection + &34;); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // Only a very poorly implemented driver would fail here, // and there&34;Error configuring AutoCommit. &34;Your driver may not support getAutoCommit() or setAutoCommit(). &34;Requested setting: &34;. Cause: &34;Resetting autocommit to true on JDBC Connection [&34;]&34;Error resetting autocommit to true &34;before closing the connection. Cause: &34;Opening JDBC Connection&34;Closing JDBC Connection [&34;]&34;Opening JDBC Connection"); } this.connection = this.dataSource.getConnection(); if (this.level != null) { this.connection.setTransactionIsolation(this.level.getLevel()); } } @Override public Integer getTimeout() throws SQLException { return null; }}

作用

ManagedTransaction對事務的commit和rollback交給了容器去管理,自己本身並沒有做任何處理。

mybatis 的使用方式

如果Mybatis是單獨運行的,沒有其他框架管理,此時mybatis內部會對下段代碼實現。

con.setAutoCommit(false);//此處命令通知資料庫,從此刻開始從當前Connection通道推送而來的//SQL語句屬於同一個業務中這些SQL語句在資料庫中應該保存到同一個//Transaction中.這個Transaction的行為(commit,rollback)由當前Connection管理.try{ //推送sql語句命令……..; con.commit();//通知Transaction提交.}catch(SQLException ex){ con.rollback();//通知Transaction回滾.}

整體來說這種寫法比較原始,我們可以將本來交給 connection 處理的事務,統一調整為使用事務管理器處理。

spring 整合

當然針對 mybatis,大部分都是單個語句的執行。

用於使用 connection 時,實際上得到的是 mybatis 事務管理器封裝之後的 connection。

實際上 spring 的整合,可能適用性更強一些。

個人實現

看完了 mybatis 的實現原理之後,我們的實現就變得非常簡單。

我們可以簡化上面的一些實現,保留核心的部分即可。

接口定義

我們只保留核心的 3 個接口。

/** * 事務管理 */public interface Transaction { /** * Retrieve inner database connection * @return DataBase connection */ Connection getConnection(); /** * Commit inner database connection. */ void commit(); /** * Rollback inner database connection. */ void rollback();}

ManageTransaction

這個實現,我們的 commit 和 rollback 什麼都不做。

/** * 事務管理 * * @since 0.0.18 */public class ManageTransaction implements Transaction { /** * 數據信息 * @since 0.0.18 */ private final DataSource dataSource; /** * 隔離級別 * @since 0.0.18 */ private final TransactionIsolationLevel isolationLevel; /** * 連接信息 * @since 0.0.18 */ private Connection connection; public ManageTransaction(DataSource dataSource, TransactionIsolationLevel isolationLevel) { this.dataSource = dataSource; this.isolationLevel = isolationLevel; } public ManageTransaction(DataSource dataSource) { this(dataSource, TransactionIsolationLevel.READ_COMMITTED); } @Override public Connection getConnection() { try { if(this.connection == null) { Connection connection = dataSource.getConnection(); connection.setTransactionIsolation(isolationLevel.getLevel()); this.connection = connection; } return connection; } catch (SQLException throwables) { throw new MybatisException(throwables); } } @Override public void commit() { //nothing } @Override public void rollback() { //nothing }}

JdbcTransaction.java

這裡和上面的相比較,多出了 commit 和 rollback 的邏輯處理。

package com.github.houbb.mybatis.transaction.impl;import com.github.houbb.mybatis.constant.enums.TransactionIsolationLevel;import com.github.houbb.mybatis.exception.MybatisException;import com.github.houbb.mybatis.transaction.Transaction;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;/** * 事務管理 * * @since 0.0.18 */public class JdbcTransaction implements Transaction { /** * 數據信息 * @since 0.0.18 */ private final DataSource dataSource; /** * 隔離級別 * @since 0.0.18 */ private final TransactionIsolationLevel isolationLevel; /** * 自動提交 * @since 0.0.18 */ private final boolean autoCommit; /** * 連接信息 * @since 0.0.18 */ private Connection connection; public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel isolationLevel, boolean autoCommit) { this.dataSource = dataSource; this.isolationLevel = isolationLevel; this.autoCommit = autoCommit; } public JdbcTransaction(DataSource dataSource) { this(dataSource, TransactionIsolationLevel.READ_COMMITTED, true); } @Override public Connection getConnection(){ try { if(this.connection == null) { Connection connection = dataSource.getConnection(); connection.setTransactionIsolation(isolationLevel.getLevel()); connection.setAutoCommit(autoCommit); this.connection = connection; } return connection; } catch (SQLException throwables) { throw new MybatisException(throwables); } } @Override public void commit() { try { //非自動提交,才執行 commit 操作 if(connection != null && !this.autoCommit) { connection.commit(); } } catch (SQLException throwables) { throw new MybatisException(throwables); } } @Override public void rollback() { try { //非自動提交,才執行 commit 操作 if(connection != null && !this.autoCommit) { connection.rollback(); } } catch (SQLException throwables) { throw new MybatisException(throwables); } }}

相關焦點

  • 從零開始手寫 mybatis(二)mybatis interceptor 插件機制詳解
    前景回顧第一節 從零開始手寫 mybatis(一)MVP 版本 中我們實現了一個最基本的可以運行的 mybatis。常言道,萬事開頭難,然後中間難。mybatis 的插件機制是 mybatis 除卻動態代理之外的第二大靈魂。
  • 從零開始手寫 mybatis (三)jdbc pool 從零實現資料庫連接池
    前景回顧第一節 從零開始手寫 mybatis(一)MVP 版本 中我們實現了一個最基本的可以運行的 mybatis。第二節 從零開始手寫 mybatis(二)mybatis interceptor 插件機制詳解本節我們一起來看一下如何實現一個資料庫連接池。為什麼需要連接池?資料庫連接的創建是非常耗時的一個操作,在高並發的場景,如果每次對於資料庫的訪問都重新創建的話,成本太高。
  • 從零開始手寫 mybatis(一)MVP 版本
    (這是官網解釋)MyBatis 運行原理手寫 mybatis其實整體流程就是這麼簡單,我們來一起實現一個簡單版本的 mybatis。創作目的(1)深入學習 mybatis 的原理一千個讀者就有一千個哈姆雷特,一千個作者就有一千個莎士比亞。
  • 手寫mybatis框架-增加緩存&事務功能
    增加代碼詳解緩存 com.simple.ibatis.cache緩存接口-Cache事務功能com.simple.ibatis.transaction事務接口-Transaction/** * @Author xiabing * @Desc 增加事務功能
  • Mybatis工作流程及其原理
    而mybatis僅有基本的欄位映射,對象數據以及對象實際關係仍然需要通過手寫sql來實現和管理。(2)hibernate資料庫移植性遠大於mybatishibernate通過它強大的映射結構和hql語言,大大降低了對象與資料庫(Oracle、MySQL等)的耦合性,而mybatis由於需要手寫sql,因此與資料庫的耦合性直接取決於程式設計師寫sql的方法,如果sql不具通用性而用了很多某資料庫特性的sql語句的話,移植性也會隨之降低很多,成本很高。
  • 網易工程師3分鐘教你實現Spring+Mybatis事務管理
    前言在我們的工程中,一般使用Spring + MyBatis 的模式來實現資料庫的事務操作。MyBatis作為DAO層來執行具體的SQL,spring進行事務的管理。那麼Mybatis怎麼保證執行的SQL是在Spring事務的上下文中?這裡的關鍵就是Spring管理事務和MyBatis執行SQL使用的是同一個Connection。接下來就分析Spring是怎麼保證它們使用同一個Connection的。
  • 詳解mybatis和Mybatis-Plus區別
    -- mybatis mybatis-plus mybatis-spring mvc --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>${mybatis-plus.version
  • 通關 MyBatis 實戰 (下篇)
    各位志同道合的朋友們大家好,我是一個一直在一線網際網路踩坑十餘年的編碼愛好者,現在將我們的各種經驗以及架構實戰分享出來,如果大家喜歡,就關注我,一起將技術學深學透,我會每一篇分享結束都會預告下一專題MyBatis 最初的設計是基於 XML 配置文件的,但隨著 Java 的發展(Java
  • mybatis環境搭建
    如果使用 Maven 來構建項目,則需將下面的 dependency 代碼置於 pom.xml 文件中:3.3.2 xml 基礎配置MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置( settings )和屬性( properties )信息。
  • 一、Mybatis基本應用
    並且使用反射與動態代理機制,將代碼實現了通用性,讓開發人員把精力專注在核心的業務代碼實現上。持久層框架:專注於解決數據持久化的框架(mybatis、hibernate、spring jdbc)表現層框架:專注於解決與用戶交互的框架(struts2、spring mvc)全棧框架: 能在各層都給出解決方案的框架(spring)SSM =  spring+ springmvc + mybatis(學習這個框架)二、Mybatis簡介原始jdbc
  • Mybatis緩存詳解
    緩存在計算機裡面,任何信息都有源頭,緩存一般指源頭信息讀取後,放在內存或者其他讀取較快的地方,下次讀取相同信息不去源頭查詢而是直接從內存(或者能快速存取的硬體)讀取。這樣可以減少硬體使用,提高讀取速度。
  • mybatis-plus思維導圖,讓mybatis-plus不再難懂
    但mybatis有個讓我比較頭疼的一個問題是sql工作量很大,尤其是欄位多的時候。雖然說單表的增刪改查操作可以通過mybatis generator工具來生成(或者自己寫模板工具生成),但項目開發的過程中總免不了要新添加新欄位,這些工具就幫不了我了,我得把新欄位寫到原來的所有增刪改查的sql中。這是個痛苦的過程,特別是當你重複了很多次之後。
  • MyBatis:基本應用
    GitHub 地址:https://github.com/mybatis/mybatis-3/ORM 思想ORM(Object Relational Mapping)對象關係映射O(對象模型):實體對象,即我們在程序中根據資料庫表結構建立的一個個實體
  • 從零開始學SpringBoot之MyBatis-註解
    本節概述:(1) 關於mybatis(2) 註解思想(3) 新建項目並添加依賴項包(4) 創建啟動類App.java(5) 編寫實體類演示(6) 寫映射接口DemoMapper(7) 編寫服務類DemoService(8) 寫控制類DemoController(9) 配置資料庫連接池(10) 測試讓我們來看看這個部分:(1) 關於mybatis
  • Mybatis的SqlSession創建過程詳解
    前面mybatis的初始化過程分析完成,接下來是第二步SqlSession的創建。 創建過程總覽 SqlSession創建過程如下圖:
  • mybatis快速入門
    Mybatis通過xml或註解的方式將要執行的各種statement(statement、preparedStatemnt)配置起來,並通過java對象和statement中的sql進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。
  • Springboot整合Mybatis(含代碼生成器)
    ) lettuce: pool: max-active: 8 # 連接池最大連接數(使用負值表示沒有限制) max-wait: -1 # 連接池最大阻塞等待時間(使用負值表示沒有限制) max-idle: 8 # 連接池中的最大空閒連接 min-idle
  • mybatis的Configuration詳解
    上一篇介紹了mybatis中SqlSessionFactory的創建過程,今天來學習它默認實現中的唯一屬性Configuration。回顧還是最開始的mybatis源碼環境中的測試代碼如下圖:更加完整的說明在官方文檔上:https://mybatis.org/mybatis-3/zh/configuration.html。去掉zh可以查看英文版本。
  • 如何讓 Mybatis 自動生成代碼,提高開發效率
    來自:cnblogs.com/homejim/p/9782403.html | 責編:樂樂  圖 / 圖蟲  往日回顧:  正文  在使用mybatis過程中, 當手寫JavaBean和XML\src\main\java">javaClientGenerator>  context>generatorConfiguration>  需要改一些內容:  本地資料庫驅動程序jar包的全路徑(必須要改)。
  • SpringBoot+Mybatis多模塊(module)項目搭建教程
    一、前言最近公司項目準備開始重構,框架選定為SpringBoot+mybatis,本篇主要記錄了在IDEA中搭建SpringBoot多模塊項目的過程。及lombok依賴dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter