1. 程式人生 > >關於加@Transactional註解的方法之間呼叫,事務是否生效的問題

關於加@Transactional註解的方法之間呼叫,事務是否生效的問題

之前面試被問過這個問題,回答基本靠猜,在此記錄一下事務方法呼叫的問題。

1. 不同類之間的方法呼叫,如類A的方法a()呼叫類B的方法b(),這種情況事務是正常起作用的。只要方法a()或b()配置了事務,執行中就會開啟事務,產生代理。

若兩個方法都配置了事務,兩個事務具體以何種方式傳播,取決於設定的事務傳播特性。

2. 同一個類內方法呼叫:重點來了,同一個類內的方法呼叫就沒那麼簡單了,假定類A的方法a()呼叫方法b()

  • 同一類內方法呼叫,無論被呼叫的b()方法是否配置了事務,此事務在被呼叫時都將不生效。

看一個例子:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    
    public int insertUser(User user) {
        userDao.insertUser(user);
        // 呼叫同類方法
        this.selectUser(user.getId());
        return 1;
        
    }
    @Transactional
    public String selectUser(int id) {
        throw new RuntimeException();
        //return userDao.selectUser(id);
    }

    public int updateUser(User user) {
        return userDao.updateUser(0, user);
    }

}

service層中,insertUser()方法沒有配置事務,selectUser()配置了事務,在insertUser()中呼叫selectUser()時,檢視日誌如下:

[DEBUG][2018-02-22 11:00:32] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) drop table if exists user 
    Hibernate: drop table if exists user
[DEBUG][2018-02-22 11:00:32] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) create table user (id integer not null auto_increment, createdDate datetime, email varchar(255), name varchar(20) not null, password varchar(255), phone varchar(255), updatedDate datetime, primary key (id)) 
    Hibernate: create table user (id integer not null auto_increment, createdDate datetime, email varchar(255), name varchar(20) not null, password varchar(255), phone varchar(255), updatedDate datetime, primary key (id))
[INFO][2018-02-22 11:00:32] org.hibernate.tool.hbm2ddl.SchemaExport.execute(SchemaExport.java:406) HHH000230: Schema export complete 
    [DEBUG][2018-02-22 11:00:32] org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:1073) Checking 0 named HQL queries 
    [DEBUG][2018-02-22 11:00:32] org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:1096) Checking 0 named SQL queries 
    [DEBUG][2018-02-22 11:00:32] org.hibernate.stat.internal.StatisticsInitiator.initiateServiceInternal(StatisticsInitiator.java:110) Statistics initialized [enabled=false] 
    [INFO][2018-02-22 11:00:33] org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:360) Using DataSource [com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge15x9t1g0fr98s704x1|72d1ad2e, dataSourceName -> 1hge15x9t1g0fr98s704x1|72d1ad2e ]] of Hibernate SessionFactory for HibernateTransactionManager 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:228) Executing identity-insert immediately 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) insert into user (createdDate, email, name, password, phone, updatedDate) values (?, ?, ?, ?, ?, ?) 
    Hibernate: insert into user (createdDate, email, name, password, phone, updatedDate) values (?, ?, ?, ?, ?, ?)
[DEBUG][2018-02-22 11:00:33] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:212) Obtaining JDBC connection 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:218) Obtained JDBC connection 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity(IdentifierGeneratorHelper.java:93) Natively generated identity: 1 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:267) parse() - HQL: select name from com.wcl.pojo.User u where u.id = ? 
    [DEBUG][2018-02-22 11:00:33] org.hibernate.hql.internal.ast.QueryTranslatorImpl.showHqlAst(QueryTranslatorImpl.java:285) --- HQL AST ---

可見沒有開啟事務,因此selectUser()的事務配置沒有生效,拋異常後也不會回滾。

另一個例子:方法a()配置了事務,此時b()的事務雖然不生效,但a()的事務生效,對於b()中丟擲的異常也會回滾。

[DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) drop table if exists user 
    Hibernate: drop table if exists user
[DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) create table user (id integer not null auto_increment, createdDate datetime, email varchar(255), name varchar(20) not null, password varchar(255), phone varchar(255), updatedDate datetime, primary key (id)) 
    Hibernate: create table user (id integer not null auto_increment, createdDate datetime, email varchar(255), name varchar(20) not null, password varchar(255), phone varchar(255), updatedDate datetime, primary key (id))
[INFO][2018-02-22 11:08:50] org.hibernate.tool.hbm2ddl.SchemaExport.execute(SchemaExport.java:406) HHH000230: Schema export complete 
    [DEBUG][2018-02-22 11:08:50] org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:1073) Checking 0 named HQL queries 
    [DEBUG][2018-02-22 11:08:50] org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:1096) Checking 0 named SQL queries 
    [DEBUG][2018-02-22 11:08:50] org.hibernate.stat.internal.StatisticsInitiator.initiateServiceInternal(StatisticsInitiator.java:110) Statistics initialized [enabled=false] 
    [INFO][2018-02-22 11:08:50] org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:360) Using DataSource [com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge15x9t1g0qf4c3bcmx8|399f45b1, dataSourceName -> 1hge15x9t1g0qf4c3bcmx8|399f45b1 ]] of Hibernate SessionFactory for HibernateTransactionManager 
    [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:212) Obtaining JDBC connection 
    [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.inter
nal.LogicalCo
nnectionImpl.obtainConnection(LogicalConnectionImpl.java:218) Obtained JDBC connection [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:158) begin [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:69) initial autocommit status: true [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:71) disabling autocommit [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:228) Executing identity-insert immediately [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:104) insert into user (createdDate, email, name, password, phone, updatedDate) values (?, ?, ?, ?, ?, ?) Hibernate: insert into user (createdDate, email, name, password, phone, updatedDate) values (?, ?, ?, ?, ?, ?) [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:212) Obtaining JDBC connection [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:218) Obtained JDBC connection [DEBUG][2018-02-22 11:08:50] org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity(IdentifierGeneratorHelper.java:93) Natively generated identity: 1 [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:203) rolling back [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doRollback(JdbcTransaction.java:164) rolled JDBC Connection [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.releaseManagedConnection(JdbcTransaction.java:126) re-enabling autocommit [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.releaseConnection(LogicalConnectionImpl.java:232) Releasing JDBC connection [DEBUG][2018-02-22 11:08:50] org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.releaseConnection(LogicalConnectionImpl.java:250) Released JDBC connection Exception in thread "main" java.lang.RuntimeException at com.wcl.service.UserServiceImpl.selectUser(UserServiceImpl.java:26) at com.wcl.service.UserServiceImpl.insertUser(UserServiceImpl.java:20)

3. 不生效的原因?

個人理解,當從類外呼叫方法a()時,從spring容器獲取到的serviceImpl物件實際是包裝好的proxy物件,因此呼叫a()方法的物件是動態代理物件。而在類內部a()呼叫b()的過程中,實質執行的程式碼是this.b(),此處this物件是實際的serviceImpl物件而不是本該生成的代理物件,因此直接呼叫了b()方法。

aop原理跟事務一樣,往大里說是動態代理,往小裡說是反射機制。我又測試了兩個方法,分別加上aop增強通知,類內呼叫的效果跟事務是一樣的。這裡最好研究一下spring aop和事務的原始碼,應該能搞得更清楚。