從JDBC到ORM的事務實現


一、JDBC

早期SUN公司想編寫一套可以連接天下所有數據庫的API,但是當他們剛剛開始時就發現這是不可完成的任務,因為各個廠商的數據庫服務器差異太大了。后來SUN開始與數據庫廠商們討論,最終得出的結論是,由SUN提供一套訪問數據庫的規范(就是一組接口),並提供連接數據庫的協議標准,然后各個數據庫廠商會遵循SUN的規范提供一套訪問自己公司的數據庫服務器的API出現。SUN提供的規范命名為JDBC,而各個廠商提供的,遵循了JDBC規范的,可以訪問自己數據庫的API被稱之為驅動。

數據庫連接池:

C3P0

DBCP-- Apache CommonPool

Druid

Hikari

二、ORM

Hibernate

Hibernate 是一個開源的對象關系映射框架,它對JDBC 進行了非常輕量級的對象封裝,它將 POJO 與數據庫表建立映射關系,是一個全自動的 orm 框架,hibernate 可以自動生成 SQL 語句,自動執行,使得 Java 程序員可以使用面向對象的思維來操縱數據庫。Hibernate 里需要定義實體類和 hbm 映射關系文件(IDE 一般有工具生成)。Hibernate 里可以使用 HQL、Criteria、Native SQL三種方式操作數據庫。也可以作為 JPA 適配實現,使用 JPA 接口操作。

Mybatis

MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis避免了幾乎所有的JDBC 代碼和手動設置參數以及獲取結果集。Mybatis 可以使用簡單的XML或注解來配置和映射原生信息,將接口和 Java的POJOs(Plain Old Java Objects,普通的Java對象)映射成數據庫中的記錄

Mybatis與Hibernate的區別?

Hibernate是全自動,Mybatis是半自動。

Mybatis

  • 優點:原生SQL(XML語法),直觀,容易優化
  • 缺點:繁瑣,可以用Mybatis-generator、Mybatis-Plus之類的插件彌補

Hibernate

  • 優點:簡單場景不用寫SQL(HQL、Cretiria、SQL)
  • 缺點:不好優化sql,對DBA不友好

Spring管理事務

我們先看看JDBC如何操作事務?

   public void updatePrice() throws SQLException {
        try {
            Connection conn = getConnection();
            //關閉自動提交
            conn.setAutoCommit(false);
            String sql = "update goods set price =? where id=? ";
            PreparedStatement ptmt = conn.prepareStatement(sql); 
            ptmt.setDouble(1, 3500);
            ptmt.setString(2, "1");
            //執行
            ptmt.execute();
            //提交事務
            conn.commit();
        } catch (Exception e) {
            //回滾事務
           conn.rollback();
           e.printStackTrace();
        }
    }

再看看Spring是如何無侵入的進行事務管理的?

實現原理:事務管理器+AOP

源碼分析Spring事務實現過程

示例代碼:
我在goodsService.updatePrice方法上加了事務注解。

 @RequestMapping("/updateprice")
    public String updateprice(Double price,Integer age){
        goodsService.updatePrice(1,price);
        int i=10/0;
        userService.updateAge(1,age);
        return "sucess";
    }
  1. 請求進入controller,調用goodsService的時候,調用的實際上是goodsService的代理對象

  2. 到代理類的方法中org.springframework.aop.framework.ReflectiveMethodInvocation#proceed

  3. 進入事務攔截器的方法

    里面有個TransactionInterceptor

  4. TransactionInterceptor方法里面進行了事務管理

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				//執行目標方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				//回滾
				exception
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			//提交事務
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
		
		...

上述是簡單場景的事務處理,如果是多個service方法,並且都加了@Transactional注解,那事務怎么算呢?那就需要學習Spring里的事務傳播了。

public Class ServiceA{
    
    @Transactional
    void methodA(){
        //....    
    }
}


public Class ServiceB{
    
    @Transactional
    void methodB(){
        //....    
    }
}

七種事務的傳播行為:

  • PROPAGATION_REQUIRED (默認) 表示當前方法必須在一個具有事務的上下文中運行,如有客戶端有事務在進行,那么被調用端將在該事務中運行,否則的話重新開啟一個事務。

  • PROPAGATION_SUPPORTS 表示當前方法不必須在一個具有事務的上下文中運行,如:ServiceA.methodA()調用ServiceB.methodB(),如果methodA方法上有事務,那么methodB加入他的事務,如果methodA上沒有事務,那么methodB也不開事務

  • PROPAGATION_MANDATORY 必須被開啟事務的方法調用,否則報錯

  • PROPAGATION_REQUIRES_NEW 強制自己開啟一個新的事務,如果一個事務已經存在,那么將這個事務掛起.如ServiceA.methodA()調用ServiceB.methodB(),methodB()上的傳播級別是PROPAGATION_REQUIRES_NEW的話,那么如果methodA報錯,不影響methodB的事務,如果methodB報錯,那么methodA是可以選擇是回滾或者提交的,就看你是否將methodB報的錯誤拋出還是try catch了.

  • PROPAGATION_NOT_SUPPORTED 總是非事務的執行,並且掛起任何事務.就是如果methodA方法執行到methodB這里了,methodA的事務就被掛起,然后methodB非事務的執行,然后等methodB方法運行結束,methodA的事務再繼續.這個的好處就是methodB報錯了不會讓methodA回滾.

  • PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常

  • PROPAGATION_NESTED 表示如果當前方法正有一個事務在運行中,則該方法應該運行在一個嵌套事務中 ,被嵌
    套的事務可以獨立於被封裝的事務中進行提交或者回滾。如果封裝事務存在,並且外層事務拋出異常回滾,那么內層事務必須回滾,反之,內層事務並不影響外層事務。如果封裝事務不存在,則同propagation. required的一樣

事務失效的幾個原因:

  1. spring的事務注解@Transactional只能放在public修飾的方法上才起作用,如果放在其他非public(private,protected)方法上,雖然不報錯,但是事務不起作用
  2. 如果采用spring+springmvc,則context:component-scan重復掃描問題可能會引起事務失敗。如果spring和mvc的配置文件中都掃描了service層,那么事務就會失效。
    原因:因為按照spring配置文件的加載順序來講,先加載springmvc配置文件,再加載spring配置文件,我們的事物一般都在srping配置文件中進行配置,如果此時在加載srpingMVC配置文件的時候,把servlce也給注冊了,但是此時事物還沒加載,也就導致后面的事物無法成功注入到service中。所以把對service的掃描放在spring配置文件中或是其他配置文件中。
  3. 如使用mysql且引擎是MyISAM,則事務會不起作用,原因是MyISAM不支持事務,可以改成InnoDB引擎
  4. @Transactional注解開啟配置,必須放到listener里加載,如果放到DispatcherServlet的配置里,事務也是不起作用的。
  5. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional注解,只能當你設置了基於接口的代理時它才生效。因為注解是 不能繼承 的,這就意味着如果正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝。
  6. 在業務代碼中如果拋出RuntimeException異常,事務回滾;但是拋出Exception,事務不回滾;默認對RuntimeException回滾
  7. 如果在加有事務的方法內,使用了try...catch..語句塊對異常進行了捕獲,而catch語句塊沒有throw new RuntimeExecption異常,事務也不會回滾
  8. 在類A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法級別的事務,在方法a里面 調用了方法b,方法b里面的事務不會生效。原因是在同一個類之中,方法互相調用,切面無效,而不僅僅是事務。這里事務之所以無效,是因為spring的事務是通過aop實現的。

代碼示例:
可以看出這個this,並不是代理對象,事務也就不能生效了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM