开始怀疑Spring框架的jdbc事务处理方式

目前我做了个小小的试验,证明了Spring的jdbc事务的问题。我的环境:
使用jakarta commons dbcp 的 org.apache.commons.dbcp.BasicDataSource,作为DataSource.
xml中的配置如下:


<bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name=
"driverClassName" value="com.mysql.jdbc.Driver" />
<property name=
"url" value="jdbc:mysql://localhost/TransactionTest" />
<property name=
"username" value="zc" />
<property name=
"password" value="zc" />
<property name=
"defaultAutoCommit" value="false" />
</bean>

my sql 数据库TransactionTest中有2个表,一个account, 一个accountprofile,分别弄了2个dao


<bean id="accountdao"
class=
"test.AccountDaoImp" >
<property name=
"dataSource" ref="datasource" />
</bean>

<bean id=
"accountprofiledao"
class=
"test.AccountProfileDaoImp" >
<property name=
"dataSource" ref="datasource" />
</bean>

事务管理器配置:


<bean id="dbTransactionManager"
class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name=
"dataSource" ref="datasource" />
</bean>

业务门面:


<bean id="accountservice_target"
class=
"test.AccountService" >
<property name=
"accountDao" ref="accountdao" />
<property name=
"accountProfileDao" ref="accountprofiledao" />
</bean>

最后配置Spring的事务代理(其实就是利用了aop):


<bean id="accountservice" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" >
<property name=
"transactionManager" ref="dbTransactionManager" />
<property name=
"target" ref="accountservice_target" />
<property name=
"transactionAttributes" >
<props>
<prop key=
"register*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

我的业务门面实现有个方法:


public void registerAccount(Account a, AccountProfile ap){
accountDao.insert(a);
accountProfileDao.insert(ap);
}

分别调用2个dao的insert()方法,Account, AccountProfile是值对象。

测试代码:


public static void main(String[] args) {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("beans.xml");

AccountService service = (AccountService) context.getBean(
"accountservice");

Account a = new Account();
a.setId(
"U001");
a.setName(
"zc");
a.setPhone(
"123");

AccountProfile ap = new AccountProfile();
ap.setName(a.getName());
// "zc"
ap.setPassword(
"abcdef");

service.registerAccount(a,ap);

a = new Account();
a.setId(
"U002");
a.setName(
"zc");
a.setPhone(
"123");

ap = new AccountProfile();
ap.setName(a.getName());
// "zc"
ap.setPassword(
"12345");

service.registerAccount(a,ap);

context.close();

}

由于表accountprofile 的name 字段是唯一的,所以第2次registerAccount()要出现异常。

也就是在第2次执行registerAccount()时


{
accountDao.insert(a);
accountProfileDao.insert(ap); // 这要抛出异常
}

但前面的 accountDao.insert(a) 无法回滚呢?

数据库结果:account有2条记录,id不同,但name相同, accountprofile有1条记录.

数据库不一致, 事务没起到作用。


Spring的事务有2种方式:
1。编程的方式,使用TransactionTemplate的execute方法。
2。AOP方式,通过配置来启动事务。

我上面的方法是采用AOP方式。

还有,我试验做过,如果使用相同的DataSource,调用它的getConnection(),那么获取的Connection(连接池封装了的)的hashCode都不一样,也就是说Connection是不一样。注射给 2个dao以及TransactionManager的DataSource是同意个DataSource(单列),单他们在使用DataSource的时候可能使用的Connection是不同的,所以无法实现事务混滚, 不知道是不是这样?

有点郁闷。

如果你不是MySQL 5.0,你需要指定Mysql的事务机制是INNO DB。

你在JBoss下试验看看。

Spring只提供使用JTA JMS等方便使用方式,但是不提供这些服务,必须由容器提供,你需要使用JBoss.

拜托先确保数据库的事务能力好伐?

我也做了个类似的例子, 只用jdbc 和 spring , 没有用hibernate和任何应用服务器....
结果也是控制不了transaction, 不过从log4j的输出看,
的确该rollback的exception是有rollback, 该commit的exception也是有commit. 貌似spring rollback和commit的connection 和我用来做DB操作的connection是两个实例 .... ()

[org.springframework.transaction.interceptor.TransactionInterceptor] - Completing transaction for <b>[dream.service.JdbcService.insertTTRange] after exception: dream.exception.ToRollbackException
[org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on dream.exception.ToRollbackException
[org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] - Winning rollback rule is: RollbackRuleAttribute with pattern [ToRollbackException]
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Triggering beforeCompletion synchronization
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction rollback
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Rolling back JDBC transaction on Connection </b>[jdbc:microsoft:sqlserver://AspenServer:1433;NETADDRESS=000000000000;HOSTPROCESS=0;SENDSTRINGPARAMETERSASUNICODE=true;LOGINTIMEOUT=0;DATABASENAME=Test;PROGRAMNAME=;SELECTMETHOD=direct;WSID=, UserName=sa, SQLServer]
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Triggering afterCompletion synchronization
[org.springframework.transaction.support.TransactionSynchronizationManager] - Clearing transaction synchronization
[org.springframework.transaction.support.TransactionSynchronizationManager] - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@1dacccc] for key [org.apache.commons.dbcp.BasicDataSource@e41bc3] from thread [main]
[org.springframework.jdbc.datasource.DataSourceTransactionManager] - Releasing JDBC Connection [jdbc:microsoft:sqlserver://AspenServer:1433;NETADDRESS=000000000000;HOSTPROCESS=0;SENDSTRINGPARAMETERSASUNICODE=true;LOGINTIMEOUT=0;DATABASENAME=Test;PROGRAMNAME=;SELECTMETHOD=direct;WSID=, UserName=sa, SQLServer] after transaction
[org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource

[该贴被0000ps于2008-01-29 18:13修改过]

Spring引入显式事务表面上给使用者带来可配置方便,太实际上引入了复杂性,Spring+Hibernate必须强制配置事务,否则无法运行,这些从一个侧面看事务和线程一样不是那么好驾驭的,这也是EJB将事务封装到容器一个原因之一,Spring打破这个盒子,曾经被一些盲目跟从者叫好,同时他们必须面对潘多拉盒子打开的后果。

现在Grails又试图将Spring+Hibernate及其它打开的潘多拉盒子冒出的魔鬼一起再封装到一个新的盒子里..

唉,,分久必和 合久必分。螺旋式上升?
[该贴被banq于2008-01-30 17:14修改过]

sorry, 我收回我说的..
spring在纯jdbc也可以控制事务,
不过dataSource要以注入的方式获取..

不然spring最后commit 或rollback的connection, 和数据操作的connection是不同的object, 这样就会造成spring没有在控制transaction的假象

public static void main(String[] args) { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("beans.xml"); AccountService service = (AccountService) context.getBean("accountservice"); Account a = new Account(); a.setId("U001"); a.setName("zc"); a.setPhone("123"); AccountProfile ap = new AccountProfile(); ap.setName(a.getName()); // "zc" ap.setPassword("abcdef"); service.registerAccount(a,ap); a = new Account(); a.setId("U002"); a.setName("zc"); a.setPhone("123"); ap = new AccountProfile(); ap.setName(a.getName()); // "zc" ap.setPassword("12345"); service.registerAccount(a,ap); context.close(); }

楼主说的是两个事务,service.registerAccount(a,ap)执行了两次.这里是两个分开的事务.第一次执行当然没问题,事务成功.第二次会因为因为主键冲突出错,当然你不能期望第一次的数据能回滚.
如果你这样测试就能满足你的要求了:
public void registerAccount(Account a,AccountProfile ap){
accountDao.insert(a);
accountProfileDao.insert(ap);

accountDao.insert(a);
accountProfileDao.insert(ap);

}
那么一条数据都不会插入成功.