1. 程式人生 > >@Transactional註解工作原理

@Transactional註解工作原理

一、配置好Bean例項
首先配置好DataSource和DataSourceTransactionManager這兩個類的Bean例項


二、添加註解
在測試類上新增@Transactional註解


三、工作原理
執行配置@Transactional註解的測試類的時候,具體會發生如下步驟
1)事務開始時,通過AOP機制,生成一個代理connection物件,並將其放入DataSource例項的某個與DataSourceTransactionManager相關的某處容器中。在接下來的整個事務中,客戶程式碼都應該使用該connection連線資料庫,執行所有資料庫命令[不使用該connection連線資料庫執行的資料庫命令,在本事務回滾的時候得不到回滾]
2)事務結束時,回滾在第1步驟中得到的代理connection物件上執行的資料庫命令,然後關閉該代理connection物件


根據上面所述,我們所使用的客戶程式碼應該具有如下能力:
1)每次執行資料庫命令的時候
如果在事務的上下文環境中,那麼不直接建立新的connection物件,而是嘗試從DataSource例項的某個與DataSourceTransactionManager相關的某處容器中獲取connection物件;在非事務的上下文環境中,直接建立新的connection物件
2)每次執行完資料庫命令的時候
如果在事務的上下文環境中,那麼不直接關閉connection物件,因為在整個事務中都需要使用該connection物件,而只是釋放本次資料庫命令對該connection物件的持有;在非事務的上下文環境中,直接關閉該connection物件


在org.springframework.jdbc.core.JdbcTemplate中的如下程式碼:
org.springframework.jdbc.datasource.DataSourceUtils.getConnection(getDataSource());
具有1)中所述能力


在org.springframework.jdbc.core.JdbcTemplate中的如下程式碼:
DataSourceUtils.releaseConnection(connection, getDataSource());
就有2)所述能力


因此,我們的客戶程式碼或者可以使用JdbcTemplate(它使用DataSourceUtils.getConnection(getDataSource())和DataSourceUtils.releaseConnection(connection, getDataSource())),或者可以直接使用DataSourceUtils.getConnection(getDataSource())和DataSourceUtils.releaseConnection(connection, getDataSource());


四、舉例

現在有如下一個測試類:

import com.dslztx.service.FooterService;
import com.dslztx.service.FooterServiceTest;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;


import javax.sql.DataSource;






@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@Transactional
@TransactionConfiguration(defaultRollback = true)
public class FooterServiceTest {
    private final Logger logger = Logger.getLogger(FooterServiceTest.class);
    
    @Autowired
    protected DataSource dataSource;


    protected JdbcTemplate template;


    @Autowired
    FooterService footerService;


    @Before
    public void init() {
        template = new JdbcTemplate(dataSource);


        String sql1 = "INSERT INTO `Footer` (`ID`,`name`) VALUES (4992,'hello');";
        template.update(sql1);


        String sql2 = "INSERT INTO `Footer` (`ID`,`name`) VALUES (4995,'world');";
        template.update(sql2);
    }   


    @Test
    public void existTest() {
        try {
            String footer1 = "footer1";
            Assert.assertTrue(footerService.exist(footer1));


            String footer2 = "do not exist";
            Assert.assertFalse(footerService.exist(footer2));
        } catch (Exception e) {
            logger.error("", e);
            Assert.fail();
        }
    }


    @Test
    public void insertTest() {
        try {
            String footer = "footer";
            String id = "1000";
            footerService.insert(footer, id);
        } catch (Exception e) {
            logger.error("", e);
            Assert.fail();
        }
    }
}


執行這個測試類的具體過程如下:
1)第1個事務
i、事務開始,生成一個代理connection物件,並將其放入DataSource例項的某個與DataSourceTransactionManager相關的某處容器中
ii、執行@Before註解標記的init方法和@Test標記的existTest方法,整個過程中都從DataSource例項中獲取第1步驟中得到的代理connection物件,使用該connection物件連線資料庫執行資料庫命令
iii、事務結束,回滾在第1步驟中得到的代理connection物件上執行的資料庫命令,然後關閉該代理connection物件


2)第2個事務
i、事務開始,生成一個代理connection物件,並將其放入DataSource例項的某個與DataSourceTransactionManager相關的某處容器中
ii、執行@Before註解標記的init方法和@Test標記的insertTest方法,整個過程中都從DataSource例項中獲取第1步驟中得到的代理connection物件,使用該connection物件連線資料庫執行資料庫命令
iii、事務結束,回滾在第1步驟中得到的代理connection物件上執行的資料庫命令,然後關閉該代理connection物件