Spring的事務(wù)管理難點剖析

1.DAO和事務(wù)管理的牽絆

事務(wù)管理的目的是保證數(shù)據(jù)操作的事務(wù)性(原子性、一致性、隔離性、持久性,即所謂的ACID),脫離了事務(wù)性,DAO照樣可以順利地進行數(shù)據(jù)操作

1.1.JDBC訪問數(shù)據(jù)庫

文檔中用到的代碼項目地址

public class UserJdbcWithoutTransManagerService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void addScore(String userName,int toAdd){
        String sql="update t_user u set u.score=u.score+? where user_name=?";
        jdbcTemplate.update(sql,toAdd,userName);
    }

    public static void main(String[] args) {
        ApplicationContext ctx=new ClassPathXmlApplicationContext("jdbcWithoutTx.xml");
        UserJdbcWithoutTransManagerService userJdbcWithoutTransManagerService= (UserJdbcWithoutTransManagerService) ctx.getBean("userService");
        JdbcTemplate jdbcTemplate= (JdbcTemplate) ctx.getBean("jdbcTemplate");
        BasicDataSource dataSource= (BasicDataSource) jdbcTemplate.getDataSource();
        //檢查數(shù)據(jù)源autoCommit的設(shè)置
        System.out.println("autoCommit:"+dataSource.getDefaultAutoCommit());
        //插入一條記錄,初始分數(shù)為10
        jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");
        //調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加到20分
        userJdbcWithoutTransManagerService.addScore("tom",20);
        //查看此時用戶的分數(shù)
        int score=jdbcTemplate.queryForObject("select score from t_user where user_name='tom'",Integer.class);
        System.out.println("score:"+score);
        jdbcTemplate.execute("delete from t_user where user_name='tom'");
    }
}

 <context:component-scan base-package="com"/>
   <context:property-placeholder
            location="classpath:jdbc.properties" ignore-unresolvable="true"/>

    <!--apache.commons.dbcp.BasicDataSource-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${jdbc.driverClassName}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>

    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="userService" class="com.servcie.UserJdbcWithoutTransManagerService"/>

在jdbcWithoutTx.xml中沒有配置任何事務(wù)管理器,但是數(shù)據(jù)已經(jīng)成功持久化到數(shù)據(jù)庫中。在默認情況下,dataSource數(shù)據(jù)源的autoCommit被設(shè)置為ture,這也意味著所有通過JdbcTemplate執(zhí)行的語句馬上提交,沒有事務(wù)。如果將dataSource的defaultAutoCommit設(shè)置為false,再次運行UserJdbcWithoutTransManagerService,將拋出錯誤,原因是新增及更改數(shù)據(jù)庫的操作都沒有提交到數(shù)據(jù)庫,所以查詢語句無法從數(shù)據(jù)庫中查詢匹配大記錄而發(fā)生異常

對于強調(diào)讀速度的應(yīng)用,數(shù)據(jù)庫本身可能就不支持事務(wù),如使用MyISAM引擎的MySQL數(shù)據(jù)庫。這時,無須在Spring應(yīng)用中配置事務(wù)管理器,因為即使配置了,也沒有用處

1.2.Hibernate訪問數(shù)據(jù)庫

對于Hibernate來說,情況就比較復(fù)雜了。因為Hibernate的事務(wù)管理擁有其自身的意義,它和Hibernate一級緩存存在密切的關(guān)系:在強調(diào)Session的save、update等方法時,Hibernate并不直接向數(shù)據(jù)庫發(fā)送SQL語句,只在提交事務(wù)或flush一級緩存時才真正向數(shù)據(jù)庫發(fā)送SQL。所以,即使底層數(shù)據(jù)庫不支持事務(wù),Hibernate的事務(wù)管理也是有一定好處的,不會對數(shù)據(jù)庫操作的效率造成負面的影響。所以,如果使用Hiberbate數(shù)據(jù)訪問技術(shù),則沒有理由不配置HibernateTransactionManager事務(wù)管理器

但是,如果不使用Hibernate事務(wù)管理器,Spring就會采取默認的事務(wù)管理器策略搜索(PROPAGATION_REQUIRED,readOnly)。如果有修改操作是不允許的,就會拋出異常

文檔中用到的代碼項目地址

@Service("hiberService")
public class UserHibernateWithoutTransManagerService {
    
    private HibernateTemplate hibernateTemplate;

    @Autowired
    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public void addScore(String userName,int toAdd){
        User user = hibernateTemplate.get(User.class,userName);
        user.setScore(user.getScore()+toAdd);
        //以下語句取消注釋后,由于默認事務(wù)管理器不支持數(shù)據(jù)更改將報異常
        //通過Hibernate操作數(shù)據(jù)庫
        hibernateTemplate.update(user);
        
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/withouttx/hiber/hiberWithoutTx.xml");
        UserHibernateWithoutTransManagerService service = (UserHibernateWithoutTransManagerService)ctx.getBean("hiberService");

        JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");
        BasicDataSource basicDataSource = (BasicDataSource)jdbcTemplate.getDataSource();

        //①檢查數(shù)據(jù)源autoCommit的設(shè)置
        System.out.println("autoCommit:"+ basicDataSource.getDefaultAutoCommit());

        //②插入一條記錄,初始分數(shù)為10
        jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");

        //③調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
        service.addScore("tom",20);
        
        //④查看此時用戶的分數(shù)
        int score = jdbcTemplate.queryForObject("SELECT score FROM t_user WHERE user_name ='tom'", Integer.class);
        System.out.println("score:"+score);
        jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
    }
}

<context:component-scan base-package="com.smart.withouttx.hiber"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"/>

    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource">
        <property name="annotatedClasses">
            <list>
                <value>smart.User</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="hibernateTemplate"
          class="org.springframework.orm.hibernate4.HibernateTemplate"
          p:sessionFactory-ref="sessionFactory"/>
    <bean id="userService" class="smart.withouttx.jdbc.UserJdbcWithoutTransManagerService"/>
    <bean id="hiberService" class="smart.withouttx.hiber.UserHibernateWithoutTransManagerService"/>

2.應(yīng)用分層的迷惑

任何項目不一定必須按照web,service和dao三層來進行分層開發(fā),對于一個簡單的項目來說,往往只有一些對數(shù)據(jù)庫增、刪、改、查的操作,此時,過分強調(diào)“面向接口編程”,除了會帶來更多的類文件,并不會有什么好處。

3.事務(wù)方法嵌套調(diào)用的迷茫

3.1.Spring事務(wù)傳播機制回顧

Spring事務(wù)的一個被訛傳很廣的說法是:一個事務(wù)方法不應(yīng)該調(diào)用另一個事務(wù)方法,否則會產(chǎn)生兩個事務(wù)。結(jié)果造成開發(fā)人員在設(shè)計事務(wù)方法時束手束腳

Spring對事務(wù)控制的支持統(tǒng)一在TransactionDefinition類中描述,該類有以下幾個重要的接口方法

  • int getPropagationBehavior():事務(wù)的傳播級別
  • int getIsolationLevel():事務(wù)的隔離級別
  • int getTimeout():事務(wù)的過期時間
  • boolean isReadOnly():事務(wù)的讀/寫特性

除了事務(wù)的傳播行為,對于事務(wù)的其它特性,Spring是借助底層資源的功能來完成的,Spring無非是充當(dāng)了一個代理的角色。但是事務(wù)的傳播行為卻是Spring憑借自身的框架提供的功能,是Spring提供給開發(fā)者最珍貴的禮物。

所謂事務(wù)傳播行為,就是多個事務(wù)方法相互調(diào)用時,事務(wù)如何在這些方法間傳播。Spring支持以下7種事務(wù)傳播行為:

  • PROPAGATION_REQUIRED:如果當(dāng)前沒有事務(wù),就新建一個事務(wù);如果已經(jīng)存在一個事務(wù),就加入到這個事務(wù)中。這是最常見的選擇
  • PROPAGATION_SUPPORTS:支持當(dāng)前事務(wù)。如果沒有事務(wù),就以非事務(wù)方式執(zhí)行
  • PROPAGATION_MANDATORY:使用當(dāng)前事務(wù)。如果沒有當(dāng)前事務(wù),就拋出異常
  • PROPAGATION_REQUIRES_NEW:新建事務(wù)。如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起
  • PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式執(zhí)行操作。如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起
  • PROPAGATION_NEVER:以非事務(wù)方式執(zhí)行。如果當(dāng)前存在事務(wù),就拋出異常
  • PROPAGATION_NESTED:如果當(dāng)前存在事務(wù),就在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒有事務(wù),就執(zhí)行與PROPAGATION_REQUIRED類似的操作

spring默認的事務(wù)傳播行為是PROPAGATION_REQUIRED,它適合絕大多數(shù)場景,如果多個Service.methodX均工作在事務(wù)環(huán)境下(均被spring事務(wù)增強),且程序中存在調(diào)用鏈Service1.method1()->Service2.method2()->Service3.method3(),那么這3個服務(wù)類的3個方法通過Spring的事務(wù)傳播機制都工作在同一個事務(wù)中

3.2相互嵌套的服務(wù)方法

文檔中用到的代碼項目地址

@Service("userService")
public class UserService extends BaseService {

    private JdbcTemplate jdbcTemplate;

    private ScoreService scoreService;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Autowired
    public void setScoreService(ScoreService scoreService) {
        this.scoreService = scoreService;
    }

    public void logon(String userName) {
        System.out.println("before userService.updateLastLogonTime...");
        updateLastLogonTime(userName);
        System.out.println("after userService.updateLastLogonTime...");
        
        System.out.println("before scoreService.addScore...");
        scoreService.addScore(userName, 20);
        System.out.println("after scoreService.addScore...");

    }

    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/nestcall/applicatonContext.xml");
        UserService service = (UserService) ctx.getBean("userService");

        JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource();
        //插入一條記錄,初始分數(shù)為10
        jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");

        //調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
        System.out.println("before userService.logon method...");
        service.logon("tom");
        System.out.println("after userService.logon method...");

        jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
    }
}


@Service("scoreUserService")
public class ScoreService extends BaseService{

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void addScore(String userName, int toAdd) {
        String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
        jdbcTemplate.update(sql, toAdd, userName);
    }
}


4.多線程的困惑

4.1.Spring通過單實例化Bean簡化多線程問題

由于Spring的事務(wù)管理器是通過線程相關(guān)的ThreadLocal來保存數(shù)據(jù)訪問基礎(chǔ)設(shè)施(Connection實例)的,再結(jié)合Ioc和AOP實現(xiàn)高級聲明式事務(wù)的功能,所以Spring的事務(wù)天然地和線程有著千絲萬縷的聯(lián)系

一個類能夠以單實例的方式運行的前提是“無狀態(tài)”,即一個類不能擁有狀態(tài)化的成員變量。在傳統(tǒng)的編程中,DAO必須持有一個Connection,而Connection就是狀態(tài)化的對象.所以傳統(tǒng)的DAO不能做成單實例的,每次要用時都必須創(chuàng)建一個新的實例。傳統(tǒng)的Service由于內(nèi)部包含了若干有狀態(tài)的DAO成員變量,所以其本身也是有狀態(tài)的

在Spring中,DAO和Service都以單實例的方式存在。Spring通過ThreadLocal將有狀態(tài)的變量(如Connection等)本地線程化,達到另一個層面上的“線程無關(guān)”,從而實現(xiàn)線程安全。Spring不遺余力的將有狀態(tài)的對象無狀態(tài)化,就是要達到單實例化Bean的目的

4.2.啟動獨立線程調(diào)用事務(wù)方法

文檔中用到的代碼項目地址

@Service("userService")
public class UserService extends BaseService {
    private JdbcTemplate jdbcTemplate;
    private ScoreService scoreService;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Autowired
    public void setScoreService(ScoreService scoreService) {
        this.scoreService = scoreService;
    }

    public void logon(String userName) {
        System.out.println("before userService.updateLastLogonTime method...");
        updateLastLogonTime(userName);
        System.out.println("after userService.updateLastLogonTime method...");

//      scoreService.addScore(userName, 20);
        Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一個新線程運行
        myThread.start();
    }

    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
    }

    private class MyThread extends Thread {
        private ScoreService scoreService;
        private String userName;
        private int toAdd;
        private MyThread(ScoreService scoreService, String userName, int toAdd) {
            this.scoreService = scoreService;
            this.userName = userName;
            this.toAdd = toAdd;
        }

        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("before scoreService.addScor method...");
            scoreService.addScore(userName, toAdd);
            System.out.println("after scoreService.addScor method...");
        }
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/multithread/applicatonContext.xml");
        UserService service = (UserService) ctx.getBean("userService");

        JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        //插入一條記錄,初始分數(shù)為10
        jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10," + System.currentTimeMillis() + ")");


        //調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
        System.out.println("before userService.logon method...");
        service.logon("tom");
        System.out.println("after userService.logon method...");
        jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



在相同線程中進行相互嵌套使用的事務(wù)方法工作在相同的事務(wù)中,如果這些相互嵌套調(diào)用的方法工作在不同的線程中,則不同線程下的事務(wù)方法工作在獨立的事務(wù)中。

5.聯(lián)合軍中作戰(zhàn)的混亂

5.1.Spring事務(wù)管理器的應(yīng)對

Spring抽象的DAO體系兼容多種數(shù)據(jù)訪問技術(shù),比如Hibernate是一個非常優(yōu)秀的ORM實現(xiàn)方案,但對底層SQL的控制不太方便,而MyBatis則通過模板化技術(shù)讓用戶方便地控制SQL,但沒有Hibernate那樣高的開發(fā)效率;自由度最高的就是直接使用Spring JDBC了,
但是代碼是比較繁復(fù)的

Spring提供的事務(wù)管理能力能夠很好應(yīng)對ORM框架和JDBC聯(lián)合,由于ORM框架的會話是對后者連接的封裝,Spring會“足夠智能地”在同一事務(wù)線程中讓前者的會話封裝后者的連接。因此,只要采用前者的事務(wù)管理器就行了。

序號 混合數(shù)據(jù)訪問技術(shù)框架 事務(wù)管理器
1 Hibernate+Spring JDBC或MyBatis org.springframework.orm.hibernateX.HibernateTransactionManager
2 JPA+Spring JDBC或MyBatis org.springframework.orm.jpa.JpaTransactionManager
3 JDO+Spring JDBC或MyBatis org.springframework.orm.jdo.JdoTransactionManager
5.2.Hibernate+Spring JDBC混合框架的事務(wù)管理

下面展示ORM框架+JDBC框架的情況
文檔中用到的代碼項目地址

@Service("userService")
public class UserService extends BaseService {

    private HibernateTemplate hibernateTemplate;
    private ScoreService scoreService;

    @Autowired
    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    @Autowired
    public void setScoreService(ScoreService scoreService) {
        this.scoreService = scoreService;
    }

    public void logon(String userName) {
        //1、通過Hibernate技術(shù)訪問數(shù)據(jù)
        System.out.println("before userService.updateLastLogonTime()..");
        updateLastLogonTime(userName);
        System.out.println("end userService.updateLastLogonTime()..");
        //2、通過JDBC技術(shù)訪問數(shù)據(jù)
        System.out.println("before scoreService.addScore()..");
        scoreService.addScore(userName, 20);
        System.out.println("end scoreService.addScore()..");
    }

    public void updateLastLogonTime(String userName) {
        User user = hibernateTemplate.get(User.class,userName);
        user.setLastLogonTime(System.currentTimeMillis());
        hibernateTemplate.update(user);
        //將hibernate一級緩存中的內(nèi)容刷新到數(shù)據(jù)
        hibernateTemplate.flush();//③請看下文的分析
    }

     public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/mixdao/applicationContext.xml");
        UserService service = (UserService) ctx.getBean("userService");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        //插入一條記錄,初始分數(shù)為10
        jdbcTemplate.execute("INSERT INTO t_user(user_name,password,score,last_logon_time) VALUES('tom','123456',10,"+System.currentTimeMillis()+")");

        //調(diào)用工作在無事務(wù)環(huán)境下的服務(wù)類方法,將分數(shù)添加20分
        System.out.println("before userService.logon()..");
        service.logon("tom");
        System.out.println("after userService.logon()..");
         
        int score = jdbcTemplate.queryForObject("SELECT score FROM t_user WHERE user_name ='tom'", Integer.class);
        System.out.println("score:"+score);
        jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'");
    }
}
 <context:component-scan base-package="com.smart.mixdao"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"/>

    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource">
        <property name="annotatedClasses">
            <list>
                <value>com.smart.User</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="hibernateTemplate"
          class="org.springframework.orm.hibernate4.HibernateTemplate"
          p:sessionFactory-ref="sessionFactory"/>

<!--使用Hibernate事務(wù)管理器-->
    <bean id="hiberManager"
          class="org.springframework.orm.hibernate4.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>

<!--使用UserServie及ScoreService共用方法都擁有事務(wù)-->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="serviceJdbcMethod"
                      expression="within(com.smart.mixdao.BaseService+)"/>
        <aop:advisor pointcut-ref="serviceJdbcMethod"
                     advice-ref="hiberAdvice"/>
    </aop:config>
    <tx:advice id="hiberAdvice" transaction-manager="hiberManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    

在1處使用Hibernate技術(shù)操作數(shù)據(jù)庫,而在2處調(diào)用addScore()方法,該方法內(nèi)部使用Spring JDBC技術(shù)操作數(shù)據(jù)庫。3處顯示調(diào)用了flush方法(),將Session中的緩存同步到數(shù)據(jù)庫中(馬上向數(shù)據(jù)庫發(fā)送一條更新記錄的SQL語句)。之所以要顯示執(zhí)行flush()方法,是因為在默認情況下,Hibernate對數(shù)據(jù)的更新只記錄在一級緩存中,要等到事務(wù)提交后者顯示調(diào)用flush()方法時才會將一級緩存中的數(shù)據(jù)同步到數(shù)據(jù)庫中。

6.特殊方法成漏網(wǎng)之魚

6.1.哪些方法補不能實施Spring AOP事務(wù)

由于Spring事務(wù)管理是基于接口代理或動態(tài)字節(jié)碼技術(shù),通過AOP實施事務(wù)增強的。對于接口動態(tài)代理的AOP事務(wù)增強來說,由于接口的方法都必須是public的,這就要求實現(xiàn)類的實現(xiàn)方法也必須是public的,同時不能使用static修飾符?;贑GLib字節(jié)碼動態(tài)代理方案是通過擴展增強類,動態(tài)創(chuàng)建其子類的方式進行AOP增強織入的。由于使用final、static、private修飾符的方法都不能被子類覆蓋,相應(yīng)地這些方法將無法實施AOP增強。

6.2.事務(wù)增強遺漏實例

文檔中用到的代碼項目地址

@Service("userService")
public class UserService implements UserServiceInterface{
   //1、private方法因訪問權(quán)限的限制,無法被子類覆蓋
    private void method1() {
        System.out.println("in method1");
    }
   //2、final方法無法被子類覆蓋
    public final void method2() {
        System.out.println("in method2");
    }
   //3、static 是類級別的方法,無法被子類覆蓋
    public static void method3() {
        System.out.println("in method3");
    }
   //4、public 方法可以被子類覆蓋,因此可以被動態(tài)字節(jié)碼增強
    public void method4() {
        System.out.println("in method4");
    }
   //5、final方法不能被子類覆蓋
    public final void method5() {
        System.out.println("in method5");
    }
   //6、protected方法可以被子類覆蓋,因此可以被動態(tài)字節(jié)碼增強
    protected void method6(){
        System.out.println("in method6");
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/special/applicationContext.xml");
        UserService service = (UserService) ctx.getBean("userService");

        System.out.println("before method1");
        service.method1();
        System.out.println("after method1");

        System.out.println("before method2");
        service.method2();
        System.out.println("after method2");

        System.out.println("before method3");
        service.method3();
        System.out.println("after method3");

        System.out.println("before method4");
        service.method4();
        System.out.println("after method4");

        System.out.println("before method5");
        service.method5();
        System.out.println("after method5");

        System.out.println("before method6");
        service.method6();
        System.out.println("after method6");

        //基于接口的動態(tài)代理
//        UserServiceInterface service = (UserServiceInterface) ctx.getBean("userService");
//        System.out.println("before method4");
//        service.method4();
//        System.out.println("after method4");
//
//        System.out.println("before method5");
//        service.method5();
//        System.out.println("after method5");
    }
}



這些不能被Spring事務(wù)增強的特殊方法并非就不工作在事務(wù)環(huán)境中,只要他們被外層的事務(wù)方法調(diào)用了,由于Spring事務(wù)管理的傳播級別,內(nèi)部方法也可以工作工作在外部方法所啟動的事務(wù)上下文中。這些方法不能啟動事務(wù)增強,是指這些方法不能啟動事務(wù),但是外層方法的事務(wù)上下文依舊可以順利地傳播到這些方法中。換句話說,這些無法啟動事務(wù)的方法被無事務(wù)的上下文方法調(diào)用,則他們就工作在無事務(wù)的上下文中;反之,如果被有事務(wù)上下文的方法調(diào)用,則它們就工作在事務(wù)上下文中

7、數(shù)據(jù)連接泄漏

7.1、底層連接資源的訪問問題

獲取被Spring管控的數(shù)據(jù)連接,Spring提供了兩種方法:其一是使用數(shù)據(jù)資源獲取工具類;其二是對數(shù)據(jù)源(或衍生品,如Hibernate的SessionFactory)進行代理

7.2.Spring JDBC數(shù)據(jù)連接泄漏

文檔中用到的代碼項目地址

@Service("jdbcUserService")
public class JdbcUserService {
    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public void logon(String userName) {
        try {
            Connection conn = jdbcTemplate.getDataSource().getConnection();
//            Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
            Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public static void asynchrLogon(JdbcUserService userService, String userName) {
        UserServiceRunner runner = new UserServiceRunner(userService, userName);
        runner.start();
    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void reportConn(BasicDataSource basicDataSource) {
        System.out.println("連接數(shù)[active:idle]-[" +
                       basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
    }

    private static class UserServiceRunner extends Thread {
        private JdbcUserService userService;
        private String userName;

        public UserServiceRunner(JdbcUserService userService, String userName) {
            this.userService = userService;
            this.userName = userName;
        }

        public void run() {
            userService.logon(userName);
        }
    }


    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/connleak/applicatonContext.xml");
        JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");

        BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
        JdbcUserService.reportConn(basicDataSource);
        
        JdbcUserService.asynchrLogon(userService, "tom");
        JdbcUserService.sleep(500);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.sleep(2000);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.asynchrLogon(userService, "john");
        JdbcUserService.sleep(500);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.sleep(2000);
        JdbcUserService.reportConn(basicDataSource);

    }
}
7.3.事務(wù)環(huán)境下通過DataSourceUtils獲取數(shù)據(jù)連接

Spring 提供了一個能從當(dāng)前事務(wù)上下文中獲取綁定的數(shù)據(jù)連接的工具類,即DataSourceUtils。Spring強調(diào)必須使用DataSourceUtils獲取數(shù)據(jù)連接,Spring的JdbcTemplate內(nèi)部也是通過DataSourceUtils來獲取連接的。DataSourceUtils提供了若干獲取和釋放數(shù)據(jù)連接的靜態(tài)方法,

  • static Connection doGetConnection(DataSource dataSource):首先嘗試從事務(wù)上
    下文中獲取連接,失敗后再從數(shù)據(jù)源獲取連接
  • static Connection getConnection(DataSource dataSource):和doGetConnection()
    方法的功能一樣,實際上,其內(nèi)部就是通過調(diào)用doGetConnection()方法獲取連接的
  • static void doReleaseConnection(Connection conn,DataSource dataSource):釋
    放連接,放回連接池中
  • static void releaseConnection(Connection conn,DataSource dataSource):和doR
    eleaseConnection()方法的功能一樣,實際上,其內(nèi)部就是通過調(diào)用doReleaseConnection()方法獲取連接的
    文檔中用到的代碼項目地址
@Service("jdbcUserService")
public class JdbcUserService {
    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public void logon(String userName) {
        try {
//            Connection conn = jdbcTemplate.getDataSource().getConnection();
            Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
            Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public static void asynchrLogon(JdbcUserService userService, String userName) {
        UserServiceRunner runner = new UserServiceRunner(userService, userName);
        runner.start();
    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void reportConn(BasicDataSource basicDataSource) {
        System.out.println("連接數(shù)[active:idle]-[" +
                       basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
    }

    private static class UserServiceRunner extends Thread {
        private JdbcUserService userService;
        private String userName;

        public UserServiceRunner(JdbcUserService userService, String userName) {
            this.userService = userService;
            this.userName = userName;
        }

        public void run() {
            userService.logon(userName);
        }
    }


    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/connleak/applicatonContext.xml");
        JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");

        BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
        JdbcUserService.reportConn(basicDataSource);
        
        JdbcUserService.asynchrLogon(userService, "tom");
        JdbcUserService.sleep(500);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.sleep(2000);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.asynchrLogon(userService, "john");
        JdbcUserService.sleep(500);
        JdbcUserService.reportConn(basicDataSource);


        JdbcUserService.sleep(2000);
        JdbcUserService.reportConn(basicDataSource);

    }
}
7.4.無事務(wù)環(huán)境下通過DataSourceUtils獲取數(shù)據(jù)連接

如果DataSourceUtils在沒有事務(wù)上下文的方法中使用getConnection()方法連接,那么依然會造成數(shù)據(jù)連接泄漏,需要按照下面所示顯示釋放掉連接
文檔中用到的代碼項目地址

 @Transactional
    public void logon(String userName) {
        Connection conn =null;
        try {
//            Connection conn = jdbcTemplate.getDataSource().getConnection();
            conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
            Thread.sleep(1000);//②模擬程序代碼的執(zhí)行時間
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //顯示使用DataSourceUtils釋放連接
            DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
        }

    }
7.5.JdbcTemplate如何做到對連接泄漏的免疫

通過分析JdbcTemplate的代碼,可以清楚的看到它開放的每個數(shù)據(jù)庫操作的方法:首先使用DataSourceUtils獲取連接,然后在方法返回之前使用DataSourceUtils釋放連接
文檔中用到的代碼項目地址

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");
        //獲取連接
        Connection con = DataSourceUtils.getConnection(this.getDataSource());
        Statement stmt = null;

        Object var7;
        try {
            Connection conToUse = con;
            if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }

            stmt = conToUse.createStatement();
            this.applyStatementSettings(stmt);
            Statement stmtToUse = stmt;
            if (this.nativeJdbcExtractor != null) {
                stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
            }

            T result = action.doInStatement(stmtToUse);
            this.handleWarnings(stmt);
            var7 = result;
        } catch (SQLException var11) {
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, this.getDataSource());
            con = null;
            throw this.getExceptionTranslator().translate("StatementCallback", getSql(action), var11);
        } finally {
            JdbcUtils.closeStatement(stmt);
            //顯示的關(guān)閉Jdbc連接
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }

        return var7;
    }
7.6.使用TransactionAwareDataSourceProxy

如果不得以要顯示獲取數(shù)據(jù)連接,除了可以使用DataSourceUtils獲取事務(wù)上下文綁定的連接,還可以通過TransactionAwareDataSourceProxy對數(shù)據(jù)源進行代理。數(shù)據(jù)源對象被代理后就具有事務(wù)上下文感知的能力,通過代理數(shù)據(jù)源的getConnection()方法獲取連接和使用DataSourceUtils.getConnection()方法獲取連接是一樣的
文檔中用到的代碼項目地址

<context:component-scan base-package="smart.connleak"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${jdbc.driverClassName}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>
    
    <!--<bean id="dataSource"
        class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
        <property name="targetDataSource">
            <bean class="org.apache.commons.dbcp.BasicDataSource"
                  destroy-method="close"
                  p:driverClassName="${jdbc.driverClassName}"
                  p:url="${jdbc.url}"
                  p:username="${jdbc.username}"
                  p:password="${jdbc.password}"/>
        </property>
    </bean> -->   
    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
        p:targetDataSource-ref="originDataSource"/>


    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>         
    <tx:annotation-driven/>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 5.Spring的事務(wù) 通常情況下,J2EE有2種事務(wù)管理方式:全局事務(wù)和本地事務(wù),2種事務(wù)都比較明顯的缺陷。 全...
    FTOLsXD閱讀 1,577評論 0 8
  • 很多人喜歡這篇文章,特此同步過來 由淺入深談?wù)搒pring事務(wù) 前言 這篇其實也要歸納到《常識》系列中,但這重點又...
    碼農(nóng)戲碼閱讀 4,923評論 2 59
  • 這是某公司2017前端實習(xí)招聘的一道編程測試題——用JS實現(xiàn)一個對話框,水平垂直居中,有半透明遮罩層效果。自己研究...
    剪影Boy閱讀 1,874評論 1 6

友情鏈接更多精彩內(nèi)容