關於加@Transactional註解的方法之間呼叫,事務是否生效的問題
阿新 • • 發佈:2019-01-09
之前面試被問過這個問題,回答基本靠猜,在此記錄一下事務方法呼叫的問題。
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.LogicalConnectionImpl.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和事務的原始碼,應該能搞得更清楚。