事务:


事务:是逻辑上一组操作,要么全都成功,要么全都失败。


事务特性:ACID


  • 原子性: 事务不可分割。
  • 一致性: 事务执行的前后,数据完整性保持一致。
  • 隔离性: 一个事务执行的时候,不应该受到其他事务的干扰。
  • 持久性: 一旦结束,数据就永久的保存到了数据库。


如果不考虑隔离性,会出现以下问题:


  • 脏读: 一个事务读到了另一个事务未提交的数据。
  • 不可重复读: 一个事务读到了另一个事务已经提交的数据(update) 导致一个事务中多次查询结果不一致。
  • 虚读: 一个事务读到了另一个事务已经提交的数据(insert) 到最后一个事务多次查询结果不一致。


事务的隔离级别:


  • 未提交读(Read uncommitted): 以上情况都有可能发生。
  • 已提交读(Read committed): 避免脏读,但是不可重复读、虚读有可能发生。
  • 可重复读(Read Repeatable): 避免脏读,不可重复读,但是虚读有可能发生。
  • 串行的(Serizable): 避免以上所有情况。


Spring提供的事务管理的API


分层开发中:事务处在Service层


Spring事务管理高层抽象主要包括3个接口:


  • PlatformTransactionManager 事务管理器
  • TransactionDefinition
  • TransactionStatus 事务具体运行状态


如下图所示:


java中手动开启事务工具类 java事务管理的几种方式_数据库


PlatformTransactionManager: 平台事务管理器


  • commit(TransactionStatus status)
  • getTransaction(TransactionDefinition definition)
  • rollback(TransactionStatus status)


TransactionDefinition: 事务定义


  • ISOLATION_XXX: 事务隔离级别。
  • PROPAGATION_XXX: 事务的传播行为(不是JDBC中特有的,为了解决实际开发的问题)。


TransactionStatus: 事务状态


  • 是否有保存点
  • 是否是一个新的事务
  • 事务是否已经提交


关系: PlatformTransactionManager通过TransactionDefinition设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由TransactionStatus记录。


API详解:


PlatformTransactionManager


Spring为不同的持久化框架提供了不同PlatformTransactionManager接口的实现


java中手动开启事务工具类 java事务管理的几种方式_数据库_02


TransactionDefinition(定义事务的隔离级别和传播行为)


事务的隔离级别: 分为4种


如下图所示:


java中手动开启事务工具类 java事务管理的几种方式_java中手动开启事务工具类_03


脏读: 一个事务读取到了另一个事务改写但还未提交的数据,如果这些数据回滚,则读取到的数据是无效的。


不可重复读:在同一个事务中,多次读取同一数据返回的结果有所不同。换句话说,后续读取可以读取到另一事务已经提交的更新数据。相反," 可重复读 " 在同一事务中多次读取数据时能够保证所读取的数据是一样的,也就是后续读取不能读取到另一事务已经提交的更新数据。


幻读: 即虚读,一个事务读取了几行记录后,另一个事务插入一些记录,欢度就发生了。在后续的查询中,第一个事务就会发现原来没有的记录。


事务的传播行为(七种)


事务的传播行为:(不是JDBC事务管理的范畴,是用来解决开发实际的问题)传播行为主要用来解决业务层之间调用的事务关系。一般事务是开启在Service层的,但是在开发场景中会出现一个业务层调用另一个业务层进行实现,如果这两个业务层都存在事务管理,这是就需要通过定义事务的传播行为来解决业务层之间调用的事务关系了。


java中手动开启事务工具类 java事务管理的几种方式_数据库_04




假设Service有两个A和B涉及到调用关系(ServiceA中方法内部调用ServiceB中的方法):

  • PROPAGATION_REQUIRED:支持当前的事务,如果不存在就创建一个。如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务(保证A和B在同一个事务中)。
  • PROPAGATION_SUPPORTS:支持当前事务,如果不存在就不使用事务。A、B:如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务。
  • PROPAGATION_MANDATORY:支持当前的事务,如果不存在就会抛出异常。A、B: 如果A有事务,B使用A的事务,如果A没有事务则抛出异常。
  • PROPAGATION_REQUIRES_NEW:如果有事务存在,则挂起当前事务,创建一个新的事务。A、B: 如果A有事务,B将A的事务挂起,重新创建一个新的事务(保证A和B不在同一个事务中)。
  • PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,如果有事务存在,挂起当前的事务。A、B:非事务的方式运行,A有事务就会挂起当前的事务。
  • PROPAGATION_NEVER:以非事务的方式运行,如果有事务存在则抛出异常。
  • PROPAGATION_NESTED:如果当前事务存在,则嵌套事务执行。它基于SavePoint(保存点)技术。A、B: A有事务,A执行事务之后的内容保存到SavePoint,若B事务有异常,由自己设置事务提交还是回滚。


特别注意:事务的传播行为是由被调用的一方的事务传播特性来决定的。


常用的事务传播行为只有三种(非常重要):


PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED


详解事务的传播行为:


对于PROPAGATION_REQUIRES_NEW行为如下代码存在调用关系


ServiceA {
    /**  
     * 事务属性配置为 PROPAGATION_REQUIRED  
     */  
    void methodA() {   
        ServiceB.methodB();   
    }   
}   
  
ServiceB {
    /**  
     * 事务属性配置为 PROPAGATION_REQUIRES_NEW  
     */    
    void methodB() {   
    }  
}


这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了,所谓的挂起就是暂停methodA中事务的执行,等待ServiceB#methodB中事务执行完毕 再继续执行。不管ServiceB#methodB是执行成功提交还是失败回滚,都丝毫不影响ServiceA#methodA事务的执行和提交。不管ServiceA#methodA是成功执行还是失败回滚也不会影响ServiceB#methodB的提交和执行(也就是说保证两个事务互不影响)

而PROPAGATION_NESTED嵌套事务如下所示:

ServiceA {
    /**  
     * 事务属性配置为 PROPAGATION_REQUIRED  
     */  
    void methodA() {   
        ServiceB.methodB();   
    }  
}   
  
ServiceB {
    /**  
     * 事务属性配置为 PROPAGATION_NESTED  
     */    
    void methodB() {   
    }   
}


现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 嵌套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

1. 改写 ServiceA 如下


ServiceA {   
       
    /**  
     * 事务属性配置为 PROPAGATION_REQUIRED  
     */  
    void methodA() {   
        try {   
            ServiceB.methodB();   
        } catch (SomeException) {   
            // 执行其他业务, 如 ServiceC.methodC();   
        }   
    }   
  
}


这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点

2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException ).

嵌套事务与新事务的区别:



PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 committed 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.

另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

Spring中的事务管理方式:

Spring的事务管理分为两类:

编程式事务管理:手动编写代码完成事务管理。在实际开发中很少使用,通过TransactionTemplate手动管理事务。

声明式事务管理:不需要手动编写代码,通过配置实现。开发中推荐使用(代码的侵入性最小)。Spring的声明式事务是通过AOP实现的。

事务操作环境的搭建


CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
INSERT INTO `account` VALUES ('3', 'ccc', '1000');

创建一个WEB项目

导入相应的jar包

开发Spring必须的jar包

  • spring-beans-3.2.0.RELEASE.jar
  • spring-context-3.2.0.RELEASE.jar
  • spring-core-3.2.0.RELEASE.jar
  • spring-expression-3.2.0.RELEASE.jar

日志相关的jar

  • com.springsource.org.apache.commons.logging-1.1.1.jar
  • com.springsource.org.apache.log4j-1.2.15.jar

数据库相关的jar

  • spring-jdbc-3.2.0.RELEASE.jar (支持JDBC的包)
  • spring-tx-3.2.0.RELEASE.jar (涉及事务的包)
  • com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar (c3p0连接池的包)
  • mysql-connector-java-5.0.4-bin.jar (数据库驱动)

测试包

  • spring-test-3.2.0.RELEASE.jar (spring整合Junit4的测试包)

添加配置文件applicationContextX.xml、jdbc.properties、log4j.properties

jdbc.properties

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql:///spring3_day03
jdbc.user = root
jdbc.password =root

log4j.properties

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout

创建类

AccountService、AccountDao以及实现类

在Spring中进行注册

<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao"/>
	</bean>
	
	<!-- 持久层类 -->
	<bean id="accountDao" class="spring3.transaction.demo1X.AccountDaoImpl">
		<!-- 注入连接池的对象,通过连接池对象创建模板. -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

方式一: 手动编码的方式完成事务管理


需要事务管理器: 真正管理事务对象


第一步: 在Spring配置文件中注册事务管理器


<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 需要注入连接池,通过连接池获得连接 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

第二步: 注册事务模板类(在事务模板类中还可以注入事务的隔离级别以及事务的传播行为等信息)


<!-- 事务管理的模板 -->
	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
		<property name="transactionManager" ref="transactionManager"/>
	</bean>

第三步: 在业务层注入模板类(由模板类管理事务)


<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo1.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao"/>
		<!-- 在业务层注入事务的管理模板 -->
		<property name="transactionTemplate" ref="transactionTemplate"/>
	</bean>


第四步: 在业务层代码上使用模板

public void transfer(final String from, final String to, final Double money) {
		transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			@Override
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				accountDao.out(from, money);
				int d = 1 / 0;
				accountDao.in(to, money);
			}
		});
	}


手动编码方式的缺点: 代码量增加,代码有侵入性。

手动编码实现的具体代码如下(在demo1包下新建类):


AccountDao


package spring3.transaction.demo1;

public interface AccountDao {
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money);
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money);
}


AccountDaoImpl

package spring3.transaction.demo1;

import java.util.jar.Attributes.Name;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao  {
    
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money){
		String sql = "update account set money = money - ? where name = ?";
		this.getJdbcTemplate().update(sql, money,from);
	}
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money){
		String sql = "update account set money = money + ? where name = ?";
		this.getJdbcTemplate().update(sql, money,to);
	}

}


AccountService

package spring3.transaction.demo1;

public interface AccountService {
	/**
	 * 转账的方法
	 * @param from:从哪转出
	 * @param to:转入给谁
	 * @param money:转账的金额
	 */
	public void transfer(String from, String to, Double money);
}


AccountServiceImpl

package spring3.transaction.demo1;

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class AccountServiceImpl implements AccountService {
	private AccountDao accountDao;
	private TransactionTemplate transactionTemplate;

	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		this.transactionTemplate = transactionTemplate;
	}

	/**
	 * 转账的方法
	 * 
	 * @param from:从哪转出
	 * @param to:转入给谁
	 * @param money:转账的金额
	 */
	public void transfer(final String from, final String to, final Double money) {
		transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			@Override
			protected void doInTransactionWithoutResult(TransactionStatus arg0) {
				accountDao.out(from, money);
				int d=1/0;
				accountDao.in(to, money);
			}
		});
	}

}


在src下新建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"
	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">

	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置c3p0连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.user}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo1.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao" />
		<!-- 在业务层注入事务的管理模板 -->
		<property name="transactionTemplate" ref="transactionTemplate" />
	</bean>

	<!-- 持久层类 -->
	<!-- 注入DataSource数据源 JdbcDaoSuppor会自动检测JdbcTemplate是否设置,如果没有则自动设置 -->
	<bean id="accountDao" class="spring3.transaction.demo1.AccountDaoImpl">
		<!-- 注入连接池对象,通过连接池对象创建JDBC模板 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 事务管理模板 -->
	<bean id="transactionTemplate"
		class="org.springframework.transaction.support.TransactionTemplate">
		<!-- 注入事务管理器 必须注入的属性: 事务管理器-->
		<property name="transactionManager" ref="transactionManager" />
		<property name="isolationLevelName" value="ISOLATION_DEFAULT" />
		<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
	</bean>

	<!-- 配置事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 需要注入连接池,通过连接池获取连接 -->
		<property name="dataSource" ref="dataSource" />
	</bean>
</beans>


测试代码如下 SpringTest1

package spring3.transaction.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest1 {
	@Autowired
	@Qualifier("accountService")
	private AccountService accountService;

	@Test
	public void demo1() {
		// 完成转账操作
		accountService.transfer("aaa", "bbb", 100d);
	}
}


发现往Service中注入事务模板后 执行时 代码回滚 查询表如下:

java中手动开启事务工具类 java事务管理的几种方式_bc_05


屏蔽掉service实现类中的异常代码(int d=1/0;)后运行

java中手动开启事务工具类 java事务管理的几种方式_数据库_06


成功转账 如果不屏蔽异常 且不使用事务模板 则异常上面的代码能执行 出现了异常就会抛出 无法执行下面的转入代码,就会造成一方的钱被扣掉了,另一方还没收到钱。

注意事项:


在Service实现类中需要注入TransactionTemplate(事务模板)TransactionTemplate依赖于DataSourceTransactionManager(事务管理器),而DataSourceTransactionManager依赖于DataSource构造


使用的是JdbcTemplate模板,因此Dao要继承自JdbcDaoSupport类 ,注入属性时只需要往Dao中注入DataSource即可,会自动创建JdbcTemplate模板


查看JdbcDaoSupport的源码如下:


public abstract class JdbcDaoSupport extends DaoSupport {

	private JdbcTemplate jdbcTemplate;


	/**
	 * Set the JDBC DataSource to be used by this DAO.
	 */
	public final void setDataSource(DataSource dataSource) {
		if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
			this.jdbcTemplate = createJdbcTemplate(dataSource);
			initTemplateConfig();
		}
	}
......
}

可以发现向继承自JdbcDaoSupport类的Dao注入DataSource时 会判断当前Dao的JdbcTemplate模板是否存在,如果不存在就会自动创建模板并完成初始化操作。

方式二: 声明式事务管理(原始方式)


使用原始的TransactionProxyFactoryBean


再导入AOP相关的jar包: com.springsource.org.aopalliance-1.0.0.jar、spring-aop-3.2.0.RELEASE.jar


新建包demo2,开发步骤如下:


第一步: 注册平台事务管理器


<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入连接池 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

第二步: 创建业务层代理对象

<!-- 配置生成代理对象 -->
	<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<!-- 目标对象 -->
		<property name="target" ref="accountService"/>
		<!-- 注入事务管理器 -->
		<property name="transactionManager" ref="transactionManager"/>
		<!-- 事务的属性设置 -->
		<property name="transactionAttributes">
			<props>
				<prop key="transfer">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>

第三步: 编写测试类

千万注意: 注入的是代理对象


@Autowired
@Qualifier("accountServiceProxy")
private AccountService accountService;

prop格式: PROPAGATION,ISOLATION,readOnly,-Exception,+Exception


顺序:传播行为,隔离级别,事务是否只读,发生哪些异常可以回滚事务(默认所有异常都进行回滚),发生了哪些异常不会滚进行提交事务


示例代码如下:(demo2下的)


AccountDao


package spring3.transaction.demo2;

public interface AccountDao {
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money);
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money);
}

AccountDaoImpl

package spring3.transaction.demo2;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao  {
    
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money){
		String sql = "update account set money = money - ? where name = ?";
		this.getJdbcTemplate().update(sql, money,from);
	}
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money){
		String sql = "update account set money = money + ? where name = ?";
		this.getJdbcTemplate().update(sql, money,to);
	}

}

AccountService

package spring3.transaction.demo2;

public interface AccountService {
	/**
	 * 转账的方法
	 * @param from:从哪转出
	 * @param to:转入给谁
	 * @param money:转账的金额
	 */
	public void transfer(String from, String to, Double money);
}

AccountServiceImpl

package spring3.transaction.demo2;

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;
	
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	/**
	 * 转账的方法
	 * 
	 * @param from  :从哪转出
	 * @param to  :转入的人
	 * @param money:转账金额
	 */
	public void transfer(String from, String to, Double money) {
		accountDao.out(from, money);
		int d=1/0;
		accountDao.in(to, money);
	}

}

在src下新建applicationContext2.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"
	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">

	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置c3p0连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.user}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo2.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao" />
	</bean>

	<!-- 持久层类 -->
	<bean id="accountDao" class="spring3.transaction.demo2.AccountDaoImpl">
		<!-- 注入连接池对象,通过连接池对象创建模板 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入连接池 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 配置生成代理对象 -->
	<bean id="accountServiceProxy"
		class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<!-- 目标对象 -->
		<property name="target" ref="accountService" />
		<!-- 注入事务管理器对象 -->
		<property name="transactionManager" ref="transactionManager" />
		<!-- 事务的属性设置 -->
		<property name="transactionAttributes">
			<props>
				<!-- * 表示对目标对象的中的所有方法进行增强 -->
				<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop>
			</props>
		</property>
	</bean>

</beans>

新建测试类SpringTest2

package spring3.transaction.demo2;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringTest2 {
	@Autowired
	@Qualifier("accountServiceProxy")
	private AccountService accountService;

	@Test
	public void demo1() {
		accountService.transfer("aaa", "bbb", 100d);
	}
}

由于配置了一个除法异常提交事务的属性 所以当执行到异常时就会提交事务

java中手动开启事务工具类 java事务管理的几种方式_数据库_07



方式三:声明式事务管理(自动代理:基于切面Aspect********)

开发步骤如下:新建package demo3

第一步:导入相应的jar包


需要在以上的基础上再导入AspectJ相关Jar包,AspectJ依赖于Spring Aop,所以除了需要导入aop和aop联盟的包之外,再导入Aspect的包(spring-aspects-3.2.0.RELEASE.jar和 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar)


第二步: 引入相应约束(aop和tx的约束)


<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.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">

第三步: 注册事务管理器

<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>

第四步:定义增强(事务管理)

<!-- 定义一个增强 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<!-- 增强(事务)的属性的配置 -->
		<tx:attributes>
			<!-- 
				isolation:DEFAULT	:事务的隔离级别.
				propagation			:事务的传播行为.
				read-only			:false.不是只读
				timeout				:-1
				no-rollback-for		:发生哪些异常不回滚
				rollback-for		:发生哪些异常回滚事务
			 -->
			<tx:method name="transfer"/>
		</tx:attributes>
	</tx:advice>

第五步: 定义AOP的配置(切点和通知的组合)

<!-- aop配置定义切面和切点的信息 -->
	<aop:config>
		<!-- 定义切点:哪些类的哪些方法应用增强 -->
		<aop:pointcut expression="execution(* spring3.transaction.demo3.AccountService+.*(..))" id="mypointcut"/>
		<!-- 定义切面: -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut"/>
	</aop:config>

第六步: 编写测试类

注入Service对象,不需要再注入代理对象(生成这个类的时候已经是代理对象)


具体代码如下所示:


AccountDao


package spring3.transaction.demo3;

public interface AccountDao {
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money);
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money);
}


AccountDaoImpl


package spring3.transaction.demo3;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao  {
    
	/**
	 * 转出的方法
	 * @param from	:转出的人
	 * @param money	:要转账的金额
	 */
	public void out(String from, Double money){
		String sql = "update account set money = money - ? where name = ?";
		this.getJdbcTemplate().update(sql, money,from);
	}
	/**
	 * 转入的方法
	 * @param to	:转入的人
	 * @param money	:要转账的金额
	 */
	public void in(String to, Double money){
		String sql = "update account set money = money + ? where name = ?";
		this.getJdbcTemplate().update(sql, money,to);
	}

}


AccountService


package spring3.transaction.demo3;

public interface AccountService {
	/**
	 * 转账的方法
	 * @param from:从哪转出
	 * @param to:转入给谁
	 * @param money:转账的金额
	 */
	public void transfer(String from, String to, Double money);
}

AccountServiceImpl

package spring3.transaction.demo3;

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;

	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	/**
	 * 转账的方法
	 * 
	 * @param from
	 *            :从哪转出
	 * @param to
	 *            :转入的人
	 * @param money:转账金额
	 */
	public void transfer(String from, String to, Double money) {
		accountDao.out(from, money);
		// int d=1/0;
		accountDao.in(to, money);
	}

}

在src下新建配置文件applicationContext3.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.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:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置c3p0连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.user}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo3.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao" />
	</bean>

	<!-- 持久层类 -->
	<bean id="accountDao" class="spring3.transaction.demo3.AccountDaoImpl">
		<!-- 注入连接池对象,通过连接池对象创建模板 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 定义一个增强 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<!-- 增强事务的属性配置 -->
 		<!-- 
			isolation:DEFAULT	:事务的隔离级别.
			propagation			:事务的传播行为.
			read-only			:false.不是只读
			timeout				:-1
			no-rollback-for		:发生哪些异常不回滚
			rollback-for		:发生哪些异常回滚事务
		 -->
		<tx:attributes>
			<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"
				read-only="false" />
		</tx:attributes>
	</tx:advice>

	<!-- aop配置定义切面和切点信息 -->
	<aop:config>
		<!-- 定义切点: 哪些类的哪些方法应用增强 -->
		<aop:pointcut
			expression="execution(* spring3.transaction.demo3.AccountService+.*(..))"
			id="myPointcut" />
		<!-- 定义切面 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />
	</aop:config>

</beans>

新建测试类SpringTest3

package spring3.transaction.demo3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
/**
 * 声明式事务的使用:基于切面的
 * @author liuxun
 *
 */
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringTest3 {
	@Autowired
	@Qualifier("accountService")
	AccountService accountService;

	@Test
	public void demo1() {
		accountService.transfer("aaa", "bbb", 100d);
	}

}

打开AccountServiceImpl中被屏蔽的异常 测试如下

java中手动开启事务工具类 java事务管理的几种方式_数据库_08


把异常代码屏蔽后运行如下


java中手动开启事务工具类 java事务管理的几种方式_java中手动开启事务工具类_09


方式四:基于注解的事务管理


开发步骤:


第一步: 事务管理器


<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>

第二步: 注解事务

<!-- 开启注解的事务管理 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>

第三步: 在Service上使用注解@Transactional

具体代码如下:


再新建一个包demo4,新建AccountDao、AccountDaoImpl、AccountService 代码和demo3相同


AccountServiceImpl


package spring3.transaction.demo4;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, readOnly = false)
public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;

	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	/**
	 * 转账的方法
	 * 
	 * @param from
	 *            :从哪转出
	 * @param to
	 *            :转入的人
	 * @param money:转账金额
	 */
	public void transfer(String from, String to, Double money) {
		accountDao.out(from, money);
		// int d=1/0;
		accountDao.in(to, money);
	}

}

在src下新建applicationContext4.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.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:property-placeholder location="classpath:jdbc.properties" />

	<!-- 配置c3p0连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.user}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 业务层类 -->
	<bean id="accountService" class="spring3.transaction.demo4.AccountServiceImpl">
		<!-- 在业务层注入Dao -->
		<property name="accountDao" ref="accountDao" />
	</bean>

	<!-- 持久层类 -->
	<bean id="accountDao" class="spring3.transaction.demo4.AccountDaoImpl">
		<!-- 注入连接池对象,通过连接池对象创建模板 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 开启注解的事务管理 -->
	<!-- 若不配置 "transaction-manager"属性,默认查找名称为transactionManager的事务管理器Bean -->
	<tx:annotation-driven transaction-manager="transactionManager" />

</beans>

新建SpringTest4测试类

package spring3.transaction.demo4;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
/**
 * 声明式事务的使用:基于切面的
 * @author liuxun
 *
 */
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class SpringTest4 {
	@Autowired
	@Qualifier("accountService")
	AccountService accountService;

	@Test
	public void demo1() {
		accountService.transfer("aaa", "bbb", 100d);
	}

}

运行

java中手动开启事务工具类 java事务管理的几种方式_bc_10