Spring @Transaction配置示例及發生不回滾原因深度剖析


背景

最近在公司做的一個項目,用的是SpringMVC框架,數據庫用的是MySql,剛開始並沒有加入事務,後因業務需要必須事務處理。


問題的產生和解決

使用事務,直接問百度,我選擇的是註解的方式。


在配置文件中配置事務管理器和驅動:

<tx:annotation-driven transaction-manager="transactionManager"/>
      
       <bean
              id="transactionManager"
              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource">
                     <ref bean="dataSource"/>
              </property>
       </bean>


然後直接在service層加註解


package com.my.service.impl;

import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.my.constants.ServiceException;
import com.my.dao.TestDao;
import com.my.service.TestService;

@Service
public class TestServiceImpl implements TestService{

    @Autowired
    private TestDao testDao;
    
    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { ServiceException.class })
    public int insertUser(String userName) throws ServiceException {
        int id = 0;
        try {
            id = testDao.insertUser(userName);
        } catch (SQLException e) {
            throw new ServiceException();
        }
        return id;
    } 
}


自然地,rollback的異常要和service拋出的異常一樣纔會回滾。

然後自認爲代碼肯定沒有問題,可是多次debug後到數據庫取看都沒有回滾,於是就直接在代碼中加入錯誤代碼int i = 5/0,並把exception的捕獲修改一下


package com.my.service.impl;

import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.my.constants.ServiceException;
import com.my.dao.TestDao;
import com.my.service.TestService;

@Service
public class TestServiceImpl implements TestService{

    @Autowired
    private TestDao testDao;
    
    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { ServiceException.class })
    public int insertUser(String userName) throws ServiceException {
        int id = 0;
        try {
            id = testDao.insertUser(userName);
			int i = 5/0;
        } catch (Exception e) {
            throw new ServiceException();
        }
        return id;
    }
    
}


用上面的代碼多次調試,始終沒有回滾。

然後自然想到,可能Dao層有問題,然後去看Dao層代碼,似乎真的有問題:


package com.my.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import com.my.dao.TestDao;

@Repository
public class TestDaoImpl implements TestDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insertUser(final String userName) throws SQLException {
        final String sql = "INSERT INTO USER(NAME) VALUES(?);";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(new PreparedStatementCreator() {
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = jdbcTem-plate.getDataSource().getConnection().prepareStatement(sql);
                ps.setString(1, userName);

                return ps;
            }
        }, keyHolder);

        return keyHolder.getKey().intValue();
    }
}


錯誤可能就在代碼黃色塊。於是debug進去,看到Connectioncon中的autoCommit屬性是false的,顯然是被service層的事務管理到的,而jdbcTemplate.getDataSource().getConnection()是到鏈接池重新獲取的連接,這個連接顯然沒有被事務管理,它的autoCommit屬性顯然是true,所以這使得service層事務沒有回滾,改起來很簡單,直接把代碼中的黃色塊改成PreparedStatement ps = con.prepareStatement(sql);就可以了。


總結

 

遇到Springmvc事務不能回滾,解決的步驟:

1.  檢查配置文件裏面有沒有加入事務管理配置和驅動;

2.  檢查數據庫是否支持事務(例如MySql4.0 支持事務,Engine:InnoDB);

3.  檢查代碼塊是否拋出異常,且事務的rollback的異常是拋出異常或者是拋出異常的父類;

4.  檢查事務覆蓋的代碼塊中的所有Connection是否都被這個事務覆蓋(debug檢查所有connection的autoCommit屬性是不是被事務改成了false)。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章