Spring的事務管理簡化了傳統的事務管理流程,提升了開發效率。可是首先先要了解Spring的數據庫編程。java
數據庫編程是互聯網編程的基礎,Spring框架爲開發者提供了JDBC模板模式,即jdbcTemplate,它能夠簡化許多代碼,但在實際應用中jdbcTemplate使用並不常見,在大多數時候都採用Spring結合MyBatis進行開發。在這裏,只講述Spring的jdbcTemplate開發。mysql
本節Spring數據庫編程主要使用的是SpringJDBC模塊的core和DataSource包,core是JDBC的核心包,包括經常使用的JdbcTemplate類,DataSource是訪問數據源的工具類包。若是要使用SpringJDBC操做數據庫,須要進行配置,配置以下:spring
<!--配置數據源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--Mysql驅動--> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <!--鏈接的url--> <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/> <!--用戶名密碼的配置--> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
在上述示例中,配置JDBC模板須要將dataSource注入到jdbcTemplate,而在數據訪問層(Dao)中須要使用jdbcTemplate時也須要將jdbcTemplate注入到對應的bean中。sql
@Repository("testDao") public class TestDaoImpl implements TestDao { @Autowired //按照類型注入 private JdbcTemplate jdbcTemplate;
public int update(String sql,Object args[])
:該方法能夠對數據表進行增長、修改、刪除。使用args[]設置參數,函數返回的是更新的行數。示例以下:數據庫
String insertSQL="insert into user values(NULL,?,?)"; Onject param[] = {"chencheng","m"}; jdbcTemplate.update(insertSQL,param);
public List<T> query(String sql,RowMapper<T> rowmapper,Object args[])
:該方法能夠對數據表進行查詢操做,rowMapper將結果集映射到用戶自定義的類中(前提是類的屬性名與字段名相同)。示例以下:express
jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--指定須要掃描的包--> <context:component-scan base-package="com.ch5"/> <!--配置數據源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--Mysql驅動--> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <!--鏈接的url--> <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/> <!--用戶名密碼的配置--> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
package com.ch5; public class User { private Integer id; private String name; private double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
package com.ch5.dao; import com.ch5.User; import java.util.List; public interface TestDao { public int update(String sql,Object[] param); public List<User> query(String sql,Object[] param); }
package com.ch5.dao.Impl; import com.ch5.User; import com.ch5.dao.TestDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; @Repository("testDao") public class TestDaoImpl implements TestDao { @Autowired //按照類型注入 private JdbcTemplate jdbcTemplate; @Override public int update(String sql, Object[] param) { return jdbcTemplate.update(sql,param); } @Override public List<User> query(String sql, Object[] param) { return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param); } }
package com.ch5.Test; import com.ch5.User; import com.ch5.dao.TestDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.List; public class JdbcTemplateTest { public static void main(String[] args) { ApplicationContext appCo = new ClassPathXmlApplicationContext("appliationContext.xml"); TestDao testDao=(TestDao)appCo.getBean("testDao"); String insertSql="insert into account values(null,?,?)"; Object param[] = {"chencheng",1050.0}; testDao.update(insertSql,param); String selectSql="select * from account"; List<User> list=testDao.query(selectSql,null); for (User user : list) { System.out.println(user); } } }
在代碼中顯式的調用beginTransaction
、commit
、rollback
等與事務處理相關的方法,這就是編程式事務管理,當只有少數事務操做時,編程式事務管理才比較適合。編程
package com.itheima.utils; /** * 和事務管理相關的工具類,它包含了,開啓事務,提交事務,回滾事務和釋放鏈接 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 開啓事務 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事務 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滾事務 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 釋放鏈接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//還回鏈接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } }
<!-- 配置事務管理器--> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..)"/> <aop:aspect id="txAdvice" ref="txManager"> <!--配置前置事務,開啓事務--> <aop:before method="beginTransaction" pointcut-ref="pt1"/> <!--配置後置事務,提交事務--> <aop:after-returning method="commit" pointcut-ref="pt1"/> <!--配置異常事務,回滾事務--> <aop:after-throwing method="rollback" pointcut-ref="pt1"/> <!--配置最終事務,釋放鏈接--> <aop:after method="release" pointcut-ref="pt1"/> </aop:aspect> </aop:config>
基於底層API的編程式事務管理就是根據PlatformTransactionManager
,TransactionDefinition
和TeansactionStatus
等幾個核心接口,經過編程的方式進行事務管理,下面經過一個實例描述底層API的事務管理實現:api
<!--配置事務管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
package com.ch5.dao.Impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Repository; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; @Repository("codeTransaction") public class CodeTransaction { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private DataSourceTransactionManager transactionManager; public String testTransaction(){ //默認事務定義 TransactionDefinition definition=new DefaultTransactionDefinition(); //開啓事務 TransactionStatus transactionStatus = transactionManager.getTransaction(definition); String message="執行成功,沒有回滾"; try{ String sql = "delete * from account"; String insertSql = "insert into account values(?,?,?)"; Object param[] = {"1","chenheng",2000}; jdbcTemplate.update(sql); //id重複,所以發生錯誤。 jdbcTemplate.update(insertSql,param); jdbcTemplate.update(insertSql,param); //提交事務 transactionManager.commit(transactionStatus); }catch (Exception e){ //出現異常,回滾 transactionManager.rollback(transactionStatus); message="事務回滾"; e.printStackTrace(); } return message; } }
package com.ch5.Test; import com.ch5.dao.Impl.CodeTransaction; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TransactionMangagerTest { public static void main(String[] args) { ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml"); CodeTransaction codeTransaction = (CodeTransaction)appCo.getBean("codeTransaction"); String result = codeTransaction.testTransaction(); System.out.println(result); } }
TransactionTemplate
的編程式事務管理事務處理的代碼散落在業務邏輯代碼中,破壞了原有代碼的條理性,而且每個事務都會有相似的啓動事務,提交以及回滾事務的代碼。app
TransactionTemplate
的excute
方法有一個TransactionCallback
接口類型的參數,該接口定義了一個DoInTransaction
的方法,一般以匿名內部類的方式實現TransactionCallback
接口,並在其doInTransaction
方法中寫業務邏輯代碼。在這裏可使用默認的事務提交和回滾規則,在業務代碼中不須要顯式調用任何事務處理的API,doInTransaction
方法有一個TransactionStatus
類型的參數,能夠在方法的任何位置調用該參數的setRollbackOnly
方法將事務標識爲回滾,以執行事務回滾。框架
根據默認規則,若是在執行回調方法的過程當中拋出未檢查異常,或者顯式調用了setRollbackOnly
方法,則回滾事務;若是事務執行完成或者拋出了checked類型的異常,則提交事務。
基於TransactionTemplate
的編程式事務管理的步驟以下:
springframwork
提供的org,springframework,transaction.support.TransactionTemplate
類爲事務管理器添加事務模板。完整的配置文件以下:<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--指定須要掃描的包--> <context:component-scan base-package="com.ch5"/> <!--配置數據源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--Mysql驅動--> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <!--鏈接的url--> <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/> <!--用戶名密碼的配置--> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置事務管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--爲事務管理器txManager建立transactionTemplate--> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
TransactionTemplateDao
package com.ch5; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; @Repository("transactionTemplateDao") public class TransactionTemplateDao { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private TransactionTemplate transactionTemplate; String message = ""; public String TransactionTemplateTest(){ //以你命好內部類的方式實現TransactionCallback接口。使用默認的事務規則。 transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { String insertSql = "insert into account values(?,?,?)"; Object param[] = {9,"chen",5000.0}; try{ jdbcTemplate.update(insertSql,param); jdbcTemplate.update(insertSql,param); message="執行成功,未回滾"; }catch (Exception e){ message="事務回滾"; } return message; } }); return message; } }
TransactionTemplateDaoTest
package com.ch5.Test; import com.ch5.TransactionTemplateDao; import com.ch5.dao.Impl.CodeTransaction; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TransactionTemplateDaoTest { public static void main(String[] args) { ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml"); TransactionTemplateDao transactionTemplateDao = appCo.getBean("transactionTemplateDao", TransactionTemplateDao.class); String result = transactionTemplateDao.TransactionTemplateTest(); System.out.println(result); } }
Spring的聲明式事務管理是經過AOP技術實現的事務管理,其本質是對方法先後攔截,而後再目標方法開始以前建立一個事務,在執行完成後提交或回滾事務。
與編程式事務管理相比較,聲明式事務惟一不足的地方是最細粒度只能做用到方法級別,沒法作到像編程式事務管理那樣能夠做用到代碼塊級別,但即使有這樣的須要,能夠經過變通方法進行解決。例如能夠將要進行事務處理的代碼塊單獨封裝爲方法。
Spring聲明式事務管理能夠經過兩種方式實現,一是基於XML方式,二是基於@Transactional
註解的方式
基於XML方式的聲明式事務管理是經過在配置文件中配置事務規則的相關聲明來實現的。Spring提供了tx命名空間來配置事務管理,提供了<tx:advice>
元素來配置事務的通知,在配置<tx:advice>
時通常要指定id和transaction-manager屬性,其中id是配置文件的惟一標識。transaction-manager指定了事務管理器。另外還須要配置<tx:attributes>
子元素,該子元素可配置多個<tx:method>
子元素決定執行事務的細節。
在<tx:advice>
元素配置了事務的加強處理後就能夠經過編寫AOP配置讓Spring自動對目標對象生成代理,下面經過實例演示XML方式讓Spring實現聲明式事務管理。爲了體現事務管理的流程,建立Dao、Service、Controller3層實現。
package statment.dao; public interface TestDao { public int save(String sql,Object param[]); public int delete(String sql,Object param[]); }
package statment.dao.Impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import statment.dao.TestDao; @Repository("testDao") public class TestDaoImpl implements TestDao { @Autowired private JdbcTemplate jdbcTemplate; public int save(String sql, Object[] param) { return jdbcTemplate.update(sql,param); } public int delete(String sql, Object[] param) { return jdbcTemplate.update(sql,param); } }
package statment.Service; public interface TestService { public void test(); }
package statment.Service.Impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import statment.Service.TestService; import statment.dao.TestDao; @Service("testService") public class TestServiceImpl implements TestService { @Autowired private TestDao testDao; public void test() { String deleteSql="delete from account"; String saveSql="insert into account values(?,?,?)"; Object param[] = {1,"shitji",5000}; testDao.delete(deleteSql,null); testDao.save(saveSql,param); } }
package statment.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import statment.Service.TestService; @Controller public class StatementController { @Autowired private TestService testService; public void test(){ testService.test(); } }
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="statment"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="myAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPonintCut" expression="execution(* statment.Service.*.*(..))"/> <aop:advisor advice-ref="myAdvice" pointcut-ref="txPonintCut"/> </aop:config> </beans>
package statment.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import statment.controller.StatementController; public class XMLTest { public static void main(String[] args) { ApplicationContext appCo=new ClassPathXmlApplicationContext("bean.xml"); StatementController controller = appCo.getBean("statementController", StatementController.class); controller.test(); } }
package statment.Service.Impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import statment.Service.TestService; import statment.dao.TestDao; @Service("testService") @Transactional public class TestServiceImpl implements TestService { @Autowired private TestDao testDao; public void test() { String deleteSql="delete from account"; String saveSql="insert into account values(?,?,?)"; Object param[] = {1,"shitji",5000}; testDao.delete(deleteSql,null); testDao.save(saveSql,param); } }
加入
@Transactional
,就能夠指定這個類須要受到Spring的事務管理,注意該註解只針對public修飾的方法添加。
聲明式事務處理的流程是:
若是開發者在代碼邏輯中加入try...catch
語句,Spring不能在聲明式事務處理中正常執行事務的回滾。緣由是Spring只在發生未被捕獲的RuntimeException
時纔會回滾事務。所以須要處理這種問題。
在基於XML方式的聲明式事務管理捕獲異常,須要補充兩個步驟:
<tx:method name="*" rollback-for="java.lang.Exception"/>
throw new RuntimeException();
@Transactional(rollbackFor = {Exception.class})
throw new RuntimeException();