上篇文章寫到了編程式事務的使用TransactionTemplate類的實現,TransactionTemplate類里的execute方法需要TransactionCallback接口實現類做參數,其接口的定義中只包含一個doInTransaction方法用于執行事務操作,上面的代碼中使用匿名類的方式定義TransactionCallback接口實現,并且在doInTransaction()方法中定義事務處理代碼。
第五章 聲明式事務管理
我們使用最多的還是Spring聲明式事務管理,其實所有Spring事務管理都是基于AOP來實現,而其中的聲明式事務才是真正體現AOP全部優點的最佳應用,記得我以前參加面試一說到AOP我就提及聲明式事務管理,哎,那個時候對AOP的理解也就僅限于此,太菜了。Spring聲明式事務不涉及組件的依賴關系,使用它時候不需要編寫任何代碼,很大程度的節省了工作量,提高了工作效率。下面我在我的框架里面加入聲明式事務,首先修改applicationContext.xml配置文件,代碼如下:
<? xml version="1.0" encoding="UTF-8" ?> < beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd" > <!-- 掃描該路徑下的spring組件 --> < context:component-scan base-package ="cn.com.sharpxiajun" /> <!-- 讀取資源文件 --> < bean id ="propertyConfigurer" class ="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > < property name ="locations" > < list > < value > classpath:conf/constants.properties </ value > </ list > </ property > </ bean > <!-- 配置數據源 --> <!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${db.driverClass}"/> <property name="jdbcUrl" value="${db.jdbcUrl}"/> <property name="user" value="${db.user}"/> <property name="password" value="${db.password}"/> </bean> --> < bean id ="myDataSource" class ="org.apache.commons.dbcp.BasicDataSource" destroy-method ="close" > < property name ="driverClassName" value ="${db.driverClass}" /> < property name ="url" value ="${db.jdbcUrl}" /> < property name ="username" value ="${db.user}" /> < property name ="password" value ="${db.password}" /> </ bean > < bean id ="sqlMapClient" class ="org.springframework.orm.ibatis.SqlMapClientFactoryBean" > < property name ="configLocation" > < value > classpath:conf/SqlMapConfig.xml </ value > </ property > < property name ="dataSource" ref ="myDataSource" /> </ bean > < bean id ="sqlMapClientTemplate" class ="org.springframework.orm.ibatis.SqlMapClientTemplate" > < property name ="sqlMapClient" > < ref local ="sqlMapClient" /> </ property > </ bean > < bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > < property name ="dataSource" > < ref local ="myDataSource" /> </ property > </ bean > <!-- 聲明式事務 --> < tx:advice id ="txAdvice" transaction-manager ="transactionManager" > < tx:attributes > < tx:method name ="get*" read-only ="true" propagation ="REQUIRED" /> < tx:method name ="query*" read-only ="true" propagation ="REQUIRED" /> < tx:method name ="find*" read-only ="true" propagation ="REQUIRED" /> < tx:method name ="list*" read-only ="true" propagation ="REQUIRED" /> < tx:method name ="search*" read-only ="true" propagation ="REQUIRED" /> < tx:method name ="add*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> < tx:method name ="insert*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> < tx:method name ="del*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> < tx:method name ="save*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> < tx:method name ="update*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> < tx:method name ="modify*" propagation ="REQUIRED" isolation ="READ_COMMITTED" /> </ tx:attributes > </ tx:advice > < bean id ="transactionTemplate" class ="org.springframework.transaction.support.TransactionTemplate" > < property name ="transactionManager" > < ref bean ="transactionManager" /> </ property > < property name ="propagationBehaviorName" > < value > PROPAGATION_REQUIRED </ value > </ property > </ bean > <!-- 將我自己定義的攔截器生成bean --> < bean id ="methodServiceAdvisor" class ="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor" /> < aop:config > <!-- 配置規則,滿足以下規則的將攔截,第一個*表示所有返回類型,第二個表示service包下的所有class,第三個表示所有方法 --> < aop:pointcut id ="baseServiceMethods" expression ="execution(* cn.com.sharpxiajun.service.*.*(..))" /> <!-- 聲明式事務 --> < aop:advisor advice-ref ="txAdvice" pointcut-ref ="baseServiceMethods" /> <!-- 符合上面規則的攔截器都會調用到methodServiceAdvisor --> < aop:advisor advice-ref ="methodServiceAdvisor" pointcut-ref ="baseServiceMethods" /> </ aop:config > </ beans >
這里面添加了<tx:advice id="txAdvice" transaction-manager="transactionManager">,根據方法前綴不同綁定不同的事務策略,然后在<aop:config>里面添加<aop:advisor advice-ref="txAdvice" pointcut-ref="baseServiceMethods" />,那么service包下面類里的方法都會綁定相應的事務管理。在javaEE工程里面到底是DAO層綁定事務還是在Service層綁定事務?這個問題以前有同事問過我,我當時回答是都應該綁定,現在我會說最好只在Service層綁定事務。DAO層在我的理解里應該是數據庫操作的映射,那是針對數據的原子操作,而Service層則是把這些各種數據庫操作封裝成一個業務操作單元,所以Service層的含義更符合事務本質,所以事務最好綁定在Service層。DAO層再綁定事務沒必要,而且多余,甚至還會操作不必要的錯誤。
接下來我修改USERS.xml映射文件,代碼如下:
<? xml version="1.0" encoding="UTF-8" ?> <! DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" > < sqlMap namespace ="USERS" > < select id ="queryUserList" parameterClass ="java.util.Map" resultClass ="java.util.HashMap" > select t.username,t.password,t.enabled from users t </ select > <!-- 新增用戶信息 --> < insert id ="addUsers" parameterClass ="java.util.Map" > insert into users < dynamic prepend ="(" > < isNotNull prepend ="," property ="username" > username </ isNotNull > < isNotNull prepend ="," property ="password" > password </ isNotNull > < isNotNull prepend ="," property ="enabled" > enabled </ isNotNull > ) </ dynamic > values < dynamic prepend ="(" > < isNotNull prepend ="," property ="username" > #username:VARCHAR# </ isNotNull > < isNotNull prepend ="," property ="password" > #password:VARCHAR# </ isNotNull > < isNotNull prepend ="," property ="enabled" > #enabled:NUMBER# </ isNotNull > ) </ dynamic > </ insert > </ sqlMap >
修改cn.com.sharpxiajun.dao包下的接口UsersDao,代碼如下:
package cn.com.sharpxiajun.dao; import java.util.List; import java.util.Map; public interface UsersDao { public static final String QUERY_USERS_SQL = "USERS.queryUserList"; public static final String ADD_USERS_SQL = "USERS.addUsers"; public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception; public Object addUsers(Map<String, Object> map) throws Exception; }
修改cn.com.sharpxiajun.dao.impl包下面UsersDaoImpl類,代碼如下:
package cn.com.sharpxiajun.dao.impl; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.orm.ibatis.SqlMapClientTemplate; import org.springframework.stereotype.Repository; import cn.com.sharpxiajun.dao.UsersDao; @SuppressWarnings("unchecked") @Scope("prototype") @Repository("usersDao") public class UsersDaoImpl implements UsersDao { @Autowired @Qualifier("sqlMapClientTemplate") private SqlMapClientTemplate sqlMapClientTemplate = null ; public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception { return sqlMapClientTemplate.queryForList(QUERY_USERS_SQL, map); } @Override public Object addUsers(Map<String, Object> map) throws Exception { return sqlMapClientTemplate.insert(ADD_USERS_SQL, map); } }
修改cn.com.sharpxiajun.service包下接口UsersService,代碼如下:
package cn.com.sharpxiajun.service; import java.util.List; import java.util.Map; public interface UsersService { public List<Map<String, Object>> queryUsersList(Map<String, Object> map) throws Exception; public Object addUsers(Map<String, Object> map) throws Exception; }
修改cn.com.sharpxiajun.service.impl包下類UsersServiceImpl,代碼如下:
package cn.com.sharpxiajun.service.impl; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import cn.com.sharpxiajun.dao.UsersDao; import cn.com.sharpxiajun.service.UsersService; @SuppressWarnings("unchecked") @Scope("prototype") @Service("userService") public class UsersServiceImpl implements UsersService { @Autowired @Qualifier("usersDao") private UsersDao usersDao = null ; @Override public List<Map<String, Object>> queryUsersList(Map<String, Object> map) throws Exception { return usersDao.queryUserList(map); } @Override public Object addUsers(Map<String, Object> map) throws Exception { return usersDao.addUsers(map); } }
修改cn.com.sharpxiajun.junittest.service包下測試類UsersServiceImplTest,代碼如下:
package cn.com.sharpxiajun.junittest.service; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import cn.com.sharpxiajun.service.UsersService; import cn.com.sharpxiajun.service.impl.UsersServiceImpl; @RunWith(SpringJUnit4ClassRunner. class ) @ContextConfiguration(locations={"classpath:conf/applicationContext.xml"}) @TransactionConfiguration(defaultRollback = false ) public class UsersServiceImplTest extends AbstractTransactionalJUnit4SpringContextTests { private final static Log log = LogFactory.getLog(UsersServiceImpl. class ); @Autowired private UsersService usersService = null ; public UsersServiceImplTest() { System.out.println("初始化測試類...."); } @Before public void setUp() throws Exception { System.out.println("測試開始...."); } @After public void tearDown() throws Exception { System.out.println("測試結束!!"); } @Test public void testQueryUserList() { Map<String, Object> map = new HashMap<String, Object>(); map.put("username", "sharpxiajun"); try { List<Map<String, Object>> list = usersService.queryUsersList(map); log.info(list); } catch (Exception e) { e.printStackTrace(); } } @Test public void testAddUsers() { Map<String, Object> map = new HashMap<String, Object>(); map.put("username", "xiajun"); map.put("password", "xiajun"); map.put("enabled", 1); try { Object obj = usersService.addUsers(map); log.info("testAddUsers:" + obj); } catch (Exception e) { e.printStackTrace(); } } }
運行測試類,結果如下:
數據新增了,但是沒有體現事務的作用,我前面寫事務示例代碼也沒有考慮到如何表現事務的作用,為了體現事務作用,我在USERS.xml配置文件里添加新方法,代碼如下:
<!-- 修改用戶信息 --> < update id ="updateUsers" parameterClass ="java.util.Map" > update users set errinfo = 'error' </ update >
修改cn.com.sharpxiajun.dao包下的UsersDao接口,代碼如下:
package cn.com.sharpxiajun.dao; import java.util.List; import java.util.Map; public interface UsersDao { public static final String QUERY_USERS_SQL = "USERS.queryUserList"; public static final String ADD_USERS_SQL = "USERS.addUsers"; public static final String UPDATE_USERS_SQL = "USERS.updateUsers"; public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception; public Object addUsers(Map<String, Object> map) throws Exception; public Object updateUsers(Map<String, Object> map) throws Exception; }
修改cn.com.sharpxiajun.dao.impl包下的UsersDaoImpl類,代碼如下:
package cn.com.sharpxiajun.dao.impl; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.orm.ibatis.SqlMapClientTemplate; import org.springframework.stereotype.Repository; import cn.com.sharpxiajun.dao.UsersDao; @SuppressWarnings("unchecked") @Scope("prototype") @Repository("usersDao") public class UsersDaoImpl implements UsersDao { @Autowired @Qualifier("sqlMapClientTemplate") private SqlMapClientTemplate sqlMapClientTemplate = null ; public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception { return sqlMapClientTemplate.queryForList(QUERY_USERS_SQL, map); } @Override public Object addUsers(Map<String, Object> map) throws Exception { return sqlMapClientTemplate.insert(ADD_USERS_SQL, map); } @Override public Object updateUsers(Map<String, Object> map) throws Exception { return sqlMapClientTemplate.update(UPDATE_USERS_SQL, map); } }
在cn.com.sharpxiajun.service包的UsersService接口里添加方法:
public void updateUsers(Map<String, Object> map) throws Exception;
在cn.com.sharpxiajun.service.impl下的UsersServiceImpl類里實現該方法,代碼如下:
@Override public void updateUsers(Map<String, Object> map) throws Exception { usersDao.addUsers(map); usersDao.updateUsers(map); }
從上面代碼可以看出USERS.xml的update方法是不能被執行,程序一定會報出異常,如果方法綁定了事務,那么usersDao.addUsers(map);操作會被回滾掉,也就是新增操作不會成功,下面我在cn.com.sharpxiajun.junittest.service包下的UsersServiceImplTest類里添加新的測試方法,代碼如下:
@Test public void testUpdateUsers() { Map<String, Object> map = new HashMap<String, Object>(); map.put("username", "sharp"); map.put("password", "sharp"); map.put("enabled", 1); try { usersService.updateUsers(map); } catch (Exception e) { e.printStackTrace(); } }
(注意:最好把測試新增方法的代碼注釋掉)
運行測試類,控制臺會打印出下面異常:
2011-10-25 23:10:59 SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase] org.springframework.jdbc.BadSqlGrammarException: SqlMapClient operation; bad SQL grammar []; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml. --- The error occurred while applying a parameter map. --- Check the USERS.updateUsers-InlineParameterMap. --- Check the statement (update failed). --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:233) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:203) at org.springframework.orm.ibatis.SqlMapClientTemplate.update(SqlMapClientTemplate.java:378) at cn.com.sharpxiajun.dao.impl.UsersDaoImpl.updateUsers(UsersDaoImpl.java:36) at cn.com.sharpxiajun.service.impl.UsersServiceImpl.updateUsers(UsersServiceImpl.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at cn.com.sharpxiajun.common.aop.MethodServiceAdvisor.invoke(MethodServiceAdvisor.java:31) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) at $Proxy18.updateUsers(Unknown Source) at cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testUpdateUsers(UsersServiceImplTest.java:103) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml. --- The error occurred while applying a parameter map. --- Check the USERS.updateUsers-InlineParameterMap. --- Check the statement (update failed). --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list' at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:107) at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java:457) at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java:90) at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:380) at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:1) at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:200) ... 49 more Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list' at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at com.mysql.jdbc.Util.handleNewInstance(Util.java:406) at com.mysql.jdbc.Util.getInstance(Util.java:381) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1031) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2543) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737) at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:998) at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:169) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.ibatis.common.jdbc.logging.PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:62) at $Proxy21.execute(Unknown Source) at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java:80) at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java:216) at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:94) ... 54 more 測試結束!! 2011-10-25 23:10:59 TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener@6798eb] to process 'after' execution for test: method [ public void cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testUpdateUsers()], instance [cn.com.sharpxiajun.junittest.service.UsersServiceImplTest@168989e], exception [ null ] org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717) at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:516) at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:291) at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:184) at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:406) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:90) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
結果如下:
沒有新的記錄被添加,事務被成功綁定了~~~。
那么如果我們寫在Service里面的方法沒有用applicationContext.xml里面設置的前綴,效果會如何了?
我在cn.com.sharpxiajun.service包的UsersService接口下添加新方法,代碼如下:
public void noTransactionMethod(Map<String, Object> map) throws Exception;
在cn.com.sharpxiajun.service.impl包里UsersServiceImpl類實現該方法,代碼如下:
@Override public void noTransactionMethod(Map<String, Object> map) throws Exception { usersDao.addUsers(map); usersDao.updateUsers(map); }
最后在cn.com.sharpxiajun.junittest.service包下,修改UsersServiceImplTest測試類,代碼如下:
// @Test // public void testAddUsers() // { // Map<String, Object> map = new HashMap<String, Object>(); // map.put("username", "xiajun"); // map.put("password", "xiajun"); // map.put("enabled", 1); // // try { // Object obj = usersService.addUsers(map); // log.info("testAddUsers:" + obj); // } catch (Exception e) { // e.printStackTrace(); // } // } @Test public void testNoTransactionMethod() { Map<String, Object> map = new HashMap<String, Object>(); map.put("username", "sharp"); map.put("password", "sharp"); map.put("enabled", 1); try { usersService.noTransactionMethod(map); } catch (Exception e) { e.printStackTrace(); } } // @Test // public void testUpdateUsers() // { // Map<String, Object> map = new HashMap<String, Object>(); // map.put("username", "sharp"); // map.put("password", "sharp"); // map.put("enabled", 1); // // try { // usersService.updateUsers(map); // } catch (Exception e) { // e.printStackTrace(); // } // }
運行測試類,控制臺打印出的異常是:
2011 - 10 - 25 23 : 28 : 00 SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase] org.springframework.jdbc.BadSqlGrammarException: SqlMapClient operation; bad SQL grammar []; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml. --- The error occurred while applying a parameter map. --- Check the USERS.updateUsers-InlineParameterMap. --- Check the statement (update failed). --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ' errinfo ' in ' field list ' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java: 233 ) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java: 72 ) at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java: 203 ) at org.springframework.orm.ibatis.SqlMapClientTemplate.update(SqlMapClientTemplate.java: 378 ) at cn.com.sharpxiajun.dao.impl.UsersDaoImpl.updateUsers(UsersDaoImpl.java: 36 ) at cn.com.sharpxiajun.service.impl.UsersServiceImpl.noTransactionMethod(UsersServiceImpl.java: 38 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 39 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 25 ) at java.lang.reflect.Method.invoke(Method.java: 597 ) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java: 309 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java: 183 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 150 ) at cn.com.sharpxiajun.common.aop.MethodServiceAdvisor.invoke(MethodServiceAdvisor.java: 31 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 172 ) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java: 110 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 172 ) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java: 89 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java: 172 ) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java: 202 ) at $Proxy18.noTransactionMethod(Unknown Source) at cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testNoTransactionMethod(UsersServiceImplTest.java: 88 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 39 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 25 ) at java.lang.reflect.Method.invoke(Method.java: 597 ) at org.junit.runners.model.FrameworkMethod$ 1 .runReflectiveCall(FrameworkMethod.java: 44 ) at org.junit. internal .runners.model.ReflectiveCallable.run(ReflectiveCallable.java: 15 ) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java: 41 ) at org.junit. internal .runners.statements.InvokeMethod.evaluate(InvokeMethod.java: 20 ) at org.junit. internal .runners.statements.RunBefores.evaluate(RunBefores.java: 28 ) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java: 74 ) at org.junit. internal .runners.statements.RunAfters.evaluate(RunAfters.java: 31 ) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java: 82 ) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java: 72 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java: 240 ) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java: 50 ) at org.junit.runners.ParentRunner$ 3 .run(ParentRunner.java: 193 ) at org.junit.runners.ParentRunner$ 1 .schedule(ParentRunner.java: 52 ) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java: 191 ) at org.junit.runners.ParentRunner.access$ 000 (ParentRunner.java: 42 ) at org.junit.runners.ParentRunner$ 2 .evaluate(ParentRunner.java: 184 ) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java: 61 ) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java: 70 ) at org.junit.runners.ParentRunner.run(ParentRunner.java: 236 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java: 180 ) at org.eclipse.jdt. internal .junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java: 49 ) at org.eclipse.jdt. internal .junit.runner.TestExecution.run(TestExecution.java: 38 ) at org.eclipse.jdt. internal .junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java: 467 ) at org.eclipse.jdt. internal .junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java: 683 ) at org.eclipse.jdt. internal .junit.runner.RemoteTestRunner.run(RemoteTestRunner.java: 390 ) at org.eclipse.jdt. internal .junit.runner.RemoteTestRunner.main(RemoteTestRunner.java: 197 ) Caused by: com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml. --- The error occurred while applying a parameter map. --- Check the USERS.updateUsers-InlineParameterMap. --- Check the statement (update failed). --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ' errinfo ' in ' field list ' at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java: 107 ) at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java: 457 ) at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java: 90 ) at org.springframework.orm.ibatis.SqlMapClientTemplate$ 9 .doInSqlMapClient(SqlMapClientTemplate.java: 380 ) at org.springframework.orm.ibatis.SqlMapClientTemplate$ 9 .doInSqlMapClient(SqlMapClientTemplate.java: 1 ) at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java: 200 ) ... 49 more Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ' errinfo ' in ' field list ' at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java: 39 ) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java: 27 ) at java.lang.reflect.Constructor.newInstance(Constructor.java: 513 ) at com.mysql.jdbc.Util.handleNewInstance(Util.java: 406 ) at com.mysql.jdbc.Util.getInstance(Util.java: 381 ) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java: 1031 ) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java: 957 ) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java: 3376 ) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java: 3308 ) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java: 1837 ) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java: 1961 ) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java: 2543 ) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java: 1737 ) at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java: 998 ) at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java: 169 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 39 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 25 ) at java.lang.reflect.Method.invoke(Method.java: 597 ) at com.ibatis.common.jdbc.logging.PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java: 62 ) at $Proxy21.execute(Unknown Source) at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java: 80 ) at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java: 216 ) at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java: 94 ) ... 54 more
我們發現這里面少了事務回滾的異常,數據庫查詢結果如下:
有記錄被新增了~~~
第六章 通過注解綁定事務管理
聲明式事務咋看一下還是十分簡單的,但是Spring引入注解后還有更加簡單的配置,請看修改后新的applicationContext.xml配置文件:
<? xml version="1.0" encoding="UTF-8" ?> < beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd" > <!-- 掃描該路徑下的spring組件 --> < context:component-scan base-package ="cn.com.sharpxiajun" /> <!-- 讀取資源文件 --> < bean id ="propertyConfigurer" class ="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > < property name ="locations" > < list > < value > classpath:conf/constants.properties </ value > </ list > </ property > </ bean > <!-- 配置數據源 --> <!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${db.driverClass}"/> <property name="jdbcUrl" value="${db.jdbcUrl}"/> <property name="user" value="${db.user}"/> <property name="password" value="${db.password}"/> </bean> --> < bean id ="myDataSource" class ="org.apache.commons.dbcp.BasicDataSource" destroy-method ="close" > < property name ="driverClassName" value ="${db.driverClass}" /> < property name ="url" value ="${db.jdbcUrl}" /> < property name ="username" value ="${db.user}" /> < property name ="password" value ="${db.password}" /> </ bean > < bean id ="sqlMapClient" class ="org.springframework.orm.ibatis.SqlMapClientFactoryBean" > < property name ="configLocation" > < value > classpath:conf/SqlMapConfig.xml </ value > </ property > < property name ="dataSource" ref ="myDataSource" /> </ bean > < bean id ="sqlMapClientTemplate" class ="org.springframework.orm.ibatis.SqlMapClientTemplate" > < property name ="sqlMapClient" > < ref local ="sqlMapClient" /> </ property > </ bean > < bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > < property name ="dataSource" > < ref local ="myDataSource" /> </ property > </ bean > <!-- 聲明式事務 --> <!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true" propagation="REQUIRED"/> <tx:method name="query*" read-only="true" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="REQUIRED"/> <tx:method name="list*" read-only="true" propagation="REQUIRED"/> <tx:method name="search*" read-only="true" propagation="REQUIRED"/> <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="del*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED"/> </tx:attributes> </tx:advice> --> < bean id ="transactionTemplate" class ="org.springframework.transaction.support.TransactionTemplate" > < property name ="transactionManager" > < ref bean ="transactionManager" /> </ property > < property name ="propagationBehaviorName" > < value > PROPAGATION_REQUIRED </ value > </ property > </ bean > <!-- 將我自己定義的攔截器生成bean --> < bean id ="methodServiceAdvisor" class ="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor" /> < aop:config > <!-- 配置規則,滿足以下規則的將攔截,第一個*表示所有返回類型,第二個表示service包下的所有class,第三個表示所有方法 --> < aop:pointcut id ="baseServiceMethods" expression ="execution(* cn.com.sharpxiajun.service.*.*(..))" /> <!-- 聲明式事務 --> <!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="baseServiceMethods" /> --> <!-- 符合上面規則的攔截器都會調用到methodServiceAdvisor --> < aop:advisor advice-ref ="methodServiceAdvisor" pointcut-ref ="baseServiceMethods" /> </ aop:config > < tx:annotation-driven transaction-manager ="transactionManager" /> </ beans >
配置文件里我把原來的聲明式配置內容注釋掉了,然后加入了<tx:annotation-driven transaction-manager="transactionManager"/>。
然后在cn.com.sharpxiajun.service.impl包下的UsersServiceImpl類加入@Transactional注解,代碼如下:
@SuppressWarnings("unchecked") @Scope("prototype") @Transactional @Service("userService") public class UsersServiceImpl implements UsersService { ......................................................
將數據庫里的數據都清除掉,運行測試類UsersServiceImplTest,測試service下的noTransactionMethod方法和updateUsers方法,結果顯示數據都沒有新增成功,這就說明事務綁定是成功了,這個簡單吧。
總結下了:事務很重要,我希望我這兩篇博客能給童鞋們一點幫助。
下一篇文章將把struts2框架引入到我寫的框架。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
