事務的管理有幾種方式, 使用註解, 聲明式配置等等.java
首先,看一下單一數據源的事務配置:mysql
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- 掃描註解Bean --> <context:component-scan base-package="com.tx"> </context:component-scan> <tx:annotation-driven proxy-target-class="true" transaction-manager="txManager" /> <context:property-placeholder location="classpath:jdbc.properties" /> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="txManager" /> <property name="transactionAttributes"> <props> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> <prop key="clear*">PROPAGATION_REQUIRED</prop> <prop key="restore*">PROPAGATION_REQUIRED</prop> <prop key="replace*">PROPAGATION_REQUIRED</prop> <prop key="process*">PROPAGATION_REQUIRED</prop> <prop key="execute*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 設置代理類 --> <bean id="bdf.transaction.beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="proxyTargetClass" value="true"></property> <property name="beanNames" value="*PR,*BO,*Service,*Dao" /> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> <!-- 設置事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 基本屬性 url、user、password <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> 配置初始化大小、最小、最大 <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="20" /> 配置獲取鏈接等待超時的時間 <property name="maxWait" value="60000" /> 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 <property name="timeBetweenEvictionRunsMillis" value="60000" /> 配置一個鏈接在池中最小生存的時間,單位是毫秒 <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT * from user" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> 打開PSCache,而且指定每一個鏈接上PSCache的大小 <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> 配置監控統計攔截的filters <property name="filters" value="stat" /> </bean> --> <!-- 配置數據源,c3p0鏈接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> </beans>
其中transactionInterceptor中配置的攔截方法就是須要事務管理的, 知足命名條件的方法都會被事務管理所攔截.spring
測試:sql
/* Dao代碼 */ public class JdbcTestDao { @Resource(name="jdbctemplate") JdbcTemplate jdbcTemplate; /** * 在配置文件中使用了聲明式的事務管理,<prop key="execute*">PROPAGATION_REQUIRED</prop> * 因此這個事務是會被攔截的事務 */ public void execute(){ jdbcTemplate.update("UPDATE user set username = 'fff1' where id=4"); jdbcTemplate.update("UPDATE user1 set username = 'eee1' where id=4"); int i = 1/0; } /** * 這個雖然不是在配置文件中須要被攔截的事務,可是有註解聲明 * 因此這個事務是會被攔截的事務 */ @Transactional public void notExtcuteWithTx(){ jdbcTemplate.update("UPDATE user set username = 'ggg' where id=4"); jdbcTemplate.update("UPDATE user1 set username = 'hhh' where id=4"); int i = 1/0; } /** * 不會被攔截的事務 */ public void notExtcute(){ jdbcTemplate.update("UPDATE user set username = 'iii' where id=4"); jdbcTemplate.update("UPDATE user1 set username = 'jjjj' where id=4"); int i = 1/0; } }
/* jUnit代碼 */ public class JdbcTestDaoTest { ApplicationContext context; @Before public void before() { context = new ClassPathXmlApplicationContext("spring-jdbc.xml"); } @Test public void execute() { JdbcTestDao dao = context.getBean(JdbcTestDao.class); dao.execute(); } @Test public void notExtcuteWithTx() { JdbcTestDao dao = context.getBean(JdbcTestDao.class); dao.notExtcuteWithTx(); } @Test public void notExtcute() { JdbcTestDao dao = context.getBean(JdbcTestDao.class); dao.notExtcute(); } }
分別單元測試三個方法, 能夠獲得正確的結果, 當拋出異常的時候, 事務都會回滾.數據庫
在spring中, 提供了不少事務管理的接口, DataSourceTransactionManager, JpaTransactionManager, JtaTransactionManager等等. 其中JtaTransactionManager是專門用來管理多數據源, 提供了對分佈式事務的支持.oracle
<!-- JTA分佈式事務配置 --> <?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- 掃描註解Bean --> <context:component-scan base-package="com.tx"> </context:component-scan> <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" /> <bean id="jdbctemplate1" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource1" /> </bean> <bean id="jdbctemplate2" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource2" /> </bean> <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql1" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="url">jdbc:mysql://localhost:3306/chatroom?useUnicode=true&characterEncoding=utf8 </prop> <prop key="user">root</prop> <prop key="password">123456</prop> <!-- <prop key="initialSize">1</prop> <prop key="minIdle">1</prop> <prop key="maxActive">20</prop> --> </props> </property> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="borrowConnectionTimeout" value="30" /> <property name="testQuery" value="select 1" /> <property name="maintenanceInterval" value="60" /> </bean> <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql2" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="url">jdbc:mysql://localhost:3306/cms?useUnicode=true&characterEncoding=utf8 </prop> <prop key="user">root</prop> <prop key="password">123456</prop> <!-- <prop key="initialSize">1</prop> <prop key="minIdle">1</prop> <prop key="maxActive">20</prop> --> </props> </property> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="borrowConnectionTimeout" value="30" /> <property name="testQuery" value="select 1" /> <property name="maintenanceInterval" value="60" /> </bean> <!-- 設置事務管理器 --> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"> <bean class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="true" /> </bean> </property> <property name="userTransaction"> <bean class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> </property> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> <prop key="clear*">PROPAGATION_REQUIRED</prop> <prop key="restore*">PROPAGATION_REQUIRED</prop> <prop key="replace*">PROPAGATION_REQUIRED</prop> <prop key="process*">PROPAGATION_REQUIRED</prop> <prop key="execute*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 設置代理類 --> <bean id="transactionBeanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="proxyTargetClass" value="true"></property> <property name="beanNames" value="*PR,*BO,*Service,*Dao" /> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> </beans>
這裏,使用的都是mysql, 只是數據庫不一樣, 也能夠使用多種數據庫,可替換爲oracle等.app
測試:分佈式
/* Dao代碼 */ @Component public class JtaTestDao implements ApplicationContextAware ,InitializingBean{ private ApplicationContext context; public void execute() { JdbcTemplate chatroom = context.getBean("jdbctemplate1", JdbcTemplate.class); JdbcTemplate cms = context.getBean("jdbctemplate2", JdbcTemplate.class); chatroom.update("UPDATE user set username = 'lzxfgw' where id=4"); cms.update("INSERT INTO c_user (id, login_name, password) values (100, 'fgw', '123')"); } //@PostConstruct //實現ApplicationContextAware接口, 能夠在初始化的時候注入applicationContext public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } @Override public void afterPropertiesSet() throws Exception { } }
/* jUnit代碼 */ public class JtaTestDaoTest { ApplicationContext context; @Test public void test() { JtaTestDao t = context.getBean(JtaTestDao.class); t.execute(); } @Before public void before() { context = new ClassPathXmlApplicationContext("spring-jta.xml"); } }
運行jUnit第一次是沒有問題的, 修改一下dao代碼中的username再次運行,會拋出主鍵重複的異常, 能夠看到數據庫中的第一條update語句的username也沒有改變, 說明事務在拋出異常以後正常回滾了.ide