假设Service有两个A和B涉及到调用关系(ServiceA中方法内部调用ServiceB中的方法):
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) 可以有以下两种处理方式:
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 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
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包
日志相关的jar
数据库相关的jar
测试包
添加配置文件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中进行注册
第二步: 注册事务模板类(在事务模板类中还可以注入事务的隔离级别以及事务的传播行为等信息)
第三步: 在业务层注入模板类(由模板类管理事务)
第四步: 在业务层代码上使用模板
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);
}
});
}
手动编码方式的缺点: 代码量增加,代码有侵入性。
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 配置内容如下:
测试代码如下 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中注入事务模板后 执行时 代码回滚 查询表如下:
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模板是否存在,如果不存在就会自动创建模板并完成初始化操作。
第二步: 创建业务层代理对象
PROPAGATION_REQUIRED
第三步: 编写测试类
@Autowired
@Qualifier("accountServiceProxy")
private AccountService accountService;
prop格式: PROPAGATION,ISOLATION,readOnly,-Exception,+Exception
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 具体配置如下:
PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException
新建测试类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);
}
}
由于配置了一个除法异常提交事务的属性 所以当执行到异常时就会提交事务
第三步: 注册事务管理器
第四步:定义增强(事务管理)
第五步: 定义AOP的配置(切点和通知的组合)
第六步: 编写测试类
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);
}
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);
}
}
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 配置如下:
新建测试类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中被屏蔽的异常 测试如下
第二步: 注解事务
第三步: 在Service上使用注解@Transactional
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 具体内容如下:
新建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);
}
}
运行