Spring的事務管理
Spring的事務管理簡化了傳統的事務管理流程,提高了開發效率。但是首先先要了解Spring的數據庫編程。
Spring的數據庫編程
數據庫編程是互聯網編程的基礎,Spring框架為開發者提供了JDBC模板模式,即jdbcTemplate,它可以簡化許多代碼,但在實際應用中jdbcTemplate使用並不常見,在大多數時候都采用Spring結合MyBatis進行開發。在這里,只講述Spring的jdbcTemplate開發。
SpringJDBC的配置
本節Spring數據庫編程主要使用的是SpringJDBC模塊的core和DataSource包,core是JDBC的核心包,包括常用的JdbcTemplate類,DataSource是訪問數據源的工具類包。如果要使用SpringJDBC操作數據庫,需要進行配置,配置如下:
<!--配置數據源-->
<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中。
@Repository("testDao")
public class TestDaoImpl implements TestDao {
@Autowired //按照類型注入
private JdbcTemplate 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將結果集映射到用戶自定義的類中(前提是類的屬性名與字段名相同)。示例如下:
jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);
具體的實現步驟
- 創建並編輯配置文件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: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 +
'}';
}
}
- 創建數據庫訪問層TestDao和TestDaoImpl
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);
}
}
- 編寫測試類JdbcTemplateTest
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等與事務處理相關的方法,這就是編程式事務管理,當只有少數事務操作時,編程式事務管理才比較適合。
基於XML的AOP實現事務控制
- 編寫事務管理的類
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();
}
}
}
- 配置aop
<!-- 配置事務管理器-->
<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的編程式事務管理
基於底層API的編程式事務管理就是根據PlatformTransactionManager,TransactionDefinition和TeansactionStatus等幾個核心接口,通過編程的方式進行事務管理,下面通過一個實例描述底層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的編程式事務管理
事務處理的代碼散落在業務邏輯代碼中,破壞了原有代碼的條理性,並且每一個事務都會有類似的啟動事務,提交以及回滾事務的代碼。
TransactionTemplate的excute方法有一個TransactionCallback接口類型的參數,該接口定義了一個DoInTransaction的方法,通常以匿名內部類的方式實現TransactionCallback接口,並在其doInTransaction方法中寫業務邏輯代碼。在這里可以使用默認的事務提交和回滾規則,在業務代碼中不需要顯式調用任何事務處理的API,doInTransaction方法有一個TransactionStatus類型的參數,可以在方法的任何位置調用該參數的setRollbackOnly方法將事務標識為回滾,以執行事務回滾。
根據默認規則,如果在執行回調方法的過程中拋出未檢查異常,或者顯式調用了setRollbackOnly方法,則回滾事務;如果事務執行完成或者拋出了checked類型的異常,則提交事務。
基於TransactionTemplate的編程式事務管理的步驟如下:
- 為事務管理添加事務模板:在基於底層的API開發的applicationContext.xml配置文件上使用
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方式的聲明式事務管理
基於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層實現。
- 創建Dao接口和實現類
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);
}
}
- 創建Service接口和實現類
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);
}
}
- 創建Controller類
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();
}
}
- 編寫配置文件bean.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: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修飾的方法添加。
在事務處理中捕獲異常
聲明式事務處理的流程是:
- Spring根據配置完成事務定義,設置事務屬性。
- 執行開發者的代碼邏輯。
- 如果開發者的代碼產生異常並且滿足事務回滾的配置條件,則事務回滾,否則提交事務。
- 事務資源釋放。
如果開發者在代碼邏輯中加入try...catch語句,Spring不能在聲明式事務處理中正常執行事務的回滾。原因是Spring只在發生未被捕獲的RuntimeException時才會回滾事務。因此需要處理這種問題。
基於XML方式的聲明式事務管理中捕獲異常
在基於XML方式的聲明式事務管理捕獲異常,需要補充兩個步驟:
- 修改聲明事務的配置
<tx:method name="*" rollback-for="java.lang.Exception"/>
- 在catch語句中添加
throw new RuntimeException();
基於注解方式的聲明式事務管理中捕獲異常
- 修改注解內容,添加rollbackFor屬性
@Transactional(rollbackFor = {Exception.class})
- 在catch語句中添加
throw new RuntimeException();
