Spring框架学习

文章40 |   阅读 20031 |   点赞0

来源:https://blog.csdn.net/yerenyuan_pku/category_6457009.html

Spring入门第九讲——Spring的事务管理

x33g5p2x  于2021-12-18 转载在 其他  
字(18.7k)|赞(0)|评价(0)|浏览(574)

事务的回顾

什么是事务?

事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

事务的特性

不考虑隔离性会引发的安全性问题

如果如果不考虑事务的隔离性,那么就会引发如下安全性问题。

  • 脏读:一个事务读取到了另一个事务的未提交数据;
  • 不可重复读:一个事务读取到了另一个事务提交的数据(主要是指update操作),会导致两次读取的结果不一致;
  • 虚读(也叫幻读):一个事务读取到了另一个事务提交的数据(主要是指insert操作),会导致两次(也有可能是多次)读取结果不一致。

解决读问题

我们可以通过设置隔离级别来解决上述读的问题。

常见数据库的默认级别:

  • Oracle默认的事务隔离级别是READ_COMMITTED;
  • 而MySQL默认的事务隔离级别是REPEATABLE_READ。

Spring事务管理的API

PlatformTransactionManager:平台事务管理器

Spring进行事务管理的时候,主要使用一个PlatformTransactionManager接口,它表示事务管理器,是Spring中真正管理事务的对象。Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类,如下:

  • org.springframework.jdbc.datasource.DateSourseTransactionManager:使用Spring JDBC或iBatis进行持久化数据时使用。也就是说,如果底层使用JDBC来管理事务,那么就使用它;
  • org.springframework.orm.hibernate5.HibernateTransactionManager:使用Hibernate5.0版本进行持久化数据时使用。也就是说,如果底层使用Hibernate5.0来管理事务,那么就使用它。

TransactionDefinition:事务定义信息

TransactionDefinition也是一个接口,它用于定义事务的相关的信息,如隔离级别、超时信息、传播行为以及(设置)是否只读等。

TrancactionStatus:事务的状态

TrancactionStatus也是一个接口,用于记录在事务管理过程中事务的状态。

Spring事务管理的API之间的关系

Spring中的这组事务管理的API是如何进行事务管理的呢?Spring在进行事务管理的时候,平台事务管理器根据事务定义的信息来进行事务的管理,在事务管理过程中,事务就会产生各种状态,于是Spring会将这些状态的信息记录到事务状态的这个对象当中。

Spring事务的传播行为

Spring中事务的传播行为很有可能会让人很困惑,因为它还是比较难理解的,有可能你粗略过了一遍Spring,到最后也压根不是很了解它。为了让初学者能够理解起来更简单,我尽量通俗易懂地进行讲解。

Spring事务的传播行为的用途

在真实的项目开发中,你必然会遇到特别复杂的业务逻辑,那就是出现业务层之间的方法相互调用的情况。这种情况下,必然就会出现一些问题,如下图所示。

从上图可知,之所以要引出Spring事务的传播行为,其实就是为了要解决我们上面遇到的问题。Spring事务的传播行为主要是用来解决什么问题的呢?这里,直接给出答案。Spring事务的传播行为主要是用来解决特别复杂的业务逻辑之间的方法相互调用的问题,也就是说Spring事务的传播行为主要用来解决业务层方法相互调用的问题。

Spring中七种事务传播行为

Spring中提供了七种事务的传播行为,这七种事务的传播行又为可分为三类。

我们先从整体上认识这七种事务的传播行为,要有一个全局观念,然后接下来我会一个一个详细地去介绍它们,力求通俗易懂。

保证多个操作在同一个事务中

PROPAGATION_REQUIRED

如果没有设置Spring事务的传播行为的话,那么这是它的默认值。它到底指的是什么意思呢?我用通俗易懂的话来说吧!

  • 如果x方法中有事务,这时在y方法中需要调用x方法,那么在y方法中就使用x方法中的事务;

  • 如果x方法中没有事务,那么创建一个新的事务,将所有操作包含进来。

PROPAGATION_SUPPORTS

如果设置Spring事务的传播行为为它的话,那么可以分为以下两种情况来讨论。

  • 若x方法中有事务,这时在y方法中调用了x方法,则在y方法中就会使用x方法中的事务;
  • 若x方法中没有事务,则在y方法中就压根不会事务。
PROPAGATION_MANDATORY

如果设置Spring事务的传播行为为它的话,那么可以分为以下两种情况来讨论。

  • 若x方法中有事务,这时在y方法中调用了x方法,则在y方法中就会使用x方法中的事务;
  • 若x方法中没有事务,则在y方法中就会抛出异常,即不执行y方法。

保证多个操作不在同一个事务中

PROPAGATION_REQUIRES_NEW

如果设置Spring事务的传播行为为它的话,那么可以分为以下两种情况来讨论。

  • 如果x方法中有事务,这时在y方法中调用了x方法,那么在y方法中就会将x方法的事务挂起(暂停),然后创建一个新的事务,在该新事务中只包含自身操作。

  • 如果x方法中没有事务,那么就会创建一个新的事务,将y方法中的自身操作包含进来。

PROPAGATION_NOT_SUPPORTED

如果设置Spring事务的传播行为为它的话,那么x方法中有事务,这时在y方法中调用了x方法,在y方法中就会将x方法的事务挂起(暂停),并且不使用事务管理。

PROPAGATION_NEVER

如果设置Spring事务的传播行为为它的话,那么x方法中有事务,直接就会报异常。

嵌套式事务

PROPAGATION_NESTED

如果我们将Spring事务的传播行为设置为它的话,那么会发生什么事情呢?如果x方法中有事务,那么在y方法中执行到x方法时,就会按照x方法中的事务来执行,并且执行完成后,会设置一个保存点(相当于你玩游戏,存个档)。接着,执行y方法中的操作,如果没有异常,就执行通过,如果有异常,这时你可以选择回滚到最初始位置,也可以选择回滚到保存点(存档的位置)。

Spring的事务管理入门

下面,我会通过一个转账的案例来演示Spring中的事务管理,这个例子用在这儿,很合适。

搭建Spring事务管理的环境

创建web项目,引入jar包

首先创建一个动态web项目,例如spring_demo03_tx,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?

  • 首先,导入Spirng框架的基本开发包,一共有6个。

  • 然后,导入MySQL数据库驱动jar包。

  • 接着,咱要在程序中使用JdbcTemplate模板类,所以还须导入如下jar包。

其中,spring-tx-4.2.4.RELEASE.jar就是Spring用于事务管理时使用到的jar包。

  • 紧接着,由于咱们还要在程序中使用C3P0连接池,所以还得导入C3P0连接池所须的jar包。

  • 再接着,由于咱们接下来会使用到Spring声明式事务管理这种方式来管理事务,所以还得导入Spring AOP开发的jar包。

  • 最后,在程序中咱还要通过Spring来整合JUnit进行单元测试,所以在项目中还应导入spring-tx-4.2.4.RELEASE.jar包。

创建数据库和表

创建数据库和表的sql语句如下所示,为了方便测试,可以在数据库表中自己添加几条记录。

create database spring4_demo03_tx;
use spring4_demo03_tx;
create table account(
	id int primary key auto_increment,
	name varchar(20),
	money double
);

INSERT INTO `account` VALUES ('1', '秦始皇', '10000');
INSERT INTO `account` VALUES ('2', '刘邦', '10000');
INSERT INTO `account` VALUES ('3', '项羽', '10000');
INSERT INTO `account` VALUES ('4', '扶苏', '10000');

编写Account实体类

在src目录下新建一个com.meimeixia.tx.domain包,并在该包下编写一个Account类。

package com.meimeixia.tx.domain;

public class Account {
	
	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 "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
	}
	
}

编写service层中的接口和实现类

首先,在src目录下新建一个com.meimeixia.tx.demo01包,并在该包下编写一个名为AccountService的接口。

package com.meimeixia.tx.demo01;

/** * 转账的业务层的接口 * @author liayun * */
public interface AccountService {
	
	//转账的方法
	public void transfer(String from, String to, Double money);
	
}

然后,再在com.meimeixia.tx.demo01包下编写以上接口的一个实现类(AccountServiceImpl.java)。

package com.meimeixia.tx.demo01;

/** * 转账的业务层的实现类 * @author liayun * */
public class AccountServiceImpl implements AccountService {
	
	//注入AccountDao
	private AccountDao accountDao;
	
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	/* * from:转出账号 * to:转入账号 * money:转账金额 */
	@Override
	public void transfer(String from, String to, Double money) {
		accountDao.outMoney(from, money);
		accountDao.inMoney(to, money);
	}

}

编写dao层中的接口和实现类

首先,在com.meimeixia.tx.demo01包下编写一个名为AccountDao的接口。

package com.meimeixia.tx.demo01;

/** * 转账的Dao的接口 * @author liayun * */
public interface AccountDao {
	
	public void outMoney(String from, Double money);
	
	public void inMoney(String to, Double money);
	
}

然后,在com.meimeixia.tx.demo01包下编写以上接口的实现类,先将该实现类写成下面这样。

package com.meimeixia.tx.demo01;

/** * 转账的Dao的实现类 * @author liayun * */
public class AccountDaoImpl implements AccountDao {

	//扣钱
	@Override
	public void outMoney(String from, Double money) {
		//待会再来写扣钱的具体实现代码...
	}

	//加钱
	@Override
	public void inMoney(String to, Double money) {
		//待会再来写加钱的具体实现代码...
	}

}

配置service层和dao层中的类

首先,咱得引入Spring的配置文件,一开始tx.xml文件的内容肯定是空的,只不过包含了各种schema约束,下面我给出的tx.xml文件包含的schema约束应该是最全面的。

<?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">
	
	
        
</beans>

然后,我们就可以在Spring配置文件中配置service层和dao层中的类了(主要配置的是实现类),即在Spring配置文件中添加如下配置。

<?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">
	
	<!-- 配置Service和Dao~~~~~~~~~~~~~~~~~~ -->
	<bean id="accountService" class="com.meimeixia.tx.demo01.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
	</bean>
	
	<!-- 配置Dao~~~~~~~~~~~~~~~~~~ -->
	<bean id="accountDao" class="com.meimeixia.tx.demo01.AccountDaoImpl">
		
	</bean>

</beans>

最后,还要记得在src目录下引入Log4j的配置文件(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' ###
# error warn info debug trace
log4j.rootLogger= info, stdout

在dao层中的实现类中编写扣钱和加钱方法的具体实现代码

可以想到的是咱必然会在AccountDaoImpl实现类中使用到JdbcTemplate模板类,因为还要用它来实现底层向数据库表中进行增删改查的方法。而JdbcTemplate模板类对象的创建必须要依赖于连接池,所以,最好的办法就是将连接池和JdbcTemplate模板类都交给Spring来管理。

<?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">
	
	<!-- 配置Service和Dao~~~~~~~~~~~~~~~~~~ -->
	<bean id="accountService" class="com.meimeixia.tx.demo01.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
	</bean>
	
	<!-- 配置Dao~~~~~~~~~~~~~~~~~~ -->
	<bean id="accountDao" class="com.meimeixia.tx.demo01.AccountDaoImpl">
		
	</bean>

	<!-- 配置一个C3P0连接池和JDBC的模板~~~~~~~~~~~~~~~~~~ -->
	<!-- 第二种方式:通过context标签来引入 -->
	<context:property-placeholder location="classpath:jdbc.properties" />
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driverClass}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
	<!-- 配置Spring的JDBC模板,它还得需要使用一个连接池 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<!-- 属性注入 -->
		<property name="dataSource" ref="dataSource" />
	</bean>

</beans>

在以上tx.xml文件中,可以看到我们使用的是C3P0连接池,连接池对象中所使用到的配置信息(包括数据库驱动类的全名称、要连接的数据库、用户名以及密码等)都来自于src目录下的jdbc.properties属性文件当中,该文件的内容如下所示。

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring4_demo03_tx
jdbc.username=root
jdbc.password=liayun

接着,在AccountDaoImpl实现类中使用JdbcTemplate模板类来编写扣钱和加钱方法的具体实现代码。我们先将该实现类写成下面这样,但这肯定不是最终的样子,只不过这是我们最容易想到的,好像理应就是这样写!后面我会教大家如何优化该实现类。

package com.meimeixia.tx.demo01;

import org.springframework.jdbc.core.JdbcTemplate;

/** * 转账的Dao的实现类 * @author liayun * */
public class AccountDaoImpl implements AccountDao {
	
	private JdbcTemplate jdbcTemplate;
	
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	//扣钱
	@Override
	public void outMoney(String from, Double money) {
		jdbcTemplate.update("update account set money = money - ? where name = ?", money, from);
	}

	//加钱
	@Override
	public void inMoney(String to, Double money) {
		jdbcTemplate.update("update account set money = money + ? where name = ?", money, to);
	}

}

紧接着,在Spring的配置文件中id为accountDao的bean标签里面使用property标签注入以上jdbcTemplate属性。

优化dao层中的实现类

上面写的AccountDaoImpl实现类的代码不是最优的,你想啊,每一次你编写XxxDao的时候,都需要在其中定义一个jdbcTemplate属性,并生成set方法,这不是有点脑残吗?所以说,Spring发现你每写一个XxxDao,都得自己亲自撸一遍,怕你觉得麻烦,就给你提供了一个JdbcDaoSupport类,你只需要让你的实现类去继承它就可以不用这样干了。如果你不信的话,可以查看该类的源代码,你就能发现它里面提供了一个setJdbcTemplate方法,所以说你只需要让你的实现类去继承它,那么相当于就有了这个setJdbcTemplate方法了。

如此一来,AccountDaoImpl实现类就可以改写成下面这样了。

package com.meimeixia.tx.demo01;

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

/** * 转账的Dao的实现类 * @author liayun * */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
	
// private JdbcTemplate jdbcTemplate;
// 
// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
// this.jdbcTemplate = jdbcTemplate;
// }

	//扣钱
	@Override
	public void outMoney(String from, Double money) {
		this.getJdbcTemplate().update("update account set money = money - ? where name = ?", money, from);
	}

	//加钱
	@Override
	public void inMoney(String to, Double money) {
		this.getJdbcTemplate().update("update account set money = money + ? where name = ?", money, to);
	}

}

其实AccountDaoImpl实现类继承了JdbcDaoSupport类之后,还可以让它的代码简化,查看JdbcDaoSupport类的源代码,在它的源代码里面,还提供了一个setDataSource(DataSource dataSource)方法,如果你给它注入了一个连接池的话,它便会使用这个连接池帮你创建一个JDBC的模板。

所以说,谁继承了这个类,谁就可以直接给这个类里面注入一个连接池了。这带来的好处便是在Spring配置文件中就再也不用定义JDBC的模板了。

至此,总算是搭建好咱的Spring事务管理的环境了,真是不容易!

编写测试类并进行测试

在com.meimeixia.tx.demo01包下编写一个SpringDemo01单元测试类。

package com.meimeixia.tx.demo01;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/** * 测试转账的环境 * @author liayun * */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx.xml")
public class SpringDemo01 {
	
	@Resource(name="accountService")
	private AccountService accountService;
	
	@Test
	public void demo01() {
		accountService.transfer("秦始皇", "刘邦", 1000d);
	}
	
}

运行以上demo01方法即可实现秦始皇转账1000元给刘邦的操作。现在我来演示一个问题,在AccountServiceImpl类中调用了AccountDaoImpl类的两个方法构成了一个转账业务,但是如果秦始皇少了1000元之后,这时突然出现异常,比如银行断电,就会出现秦始皇的钱少了1000元,而刘邦的钱没有多出1000元,钱丢失了的情况。

package com.meimeixia.tx.demo01;

/** * 转账的业务层的实现类 * @author liayun * */
public class AccountServiceImpl implements AccountService {
	
	//注入AccountDao
	private AccountDao accountDao;
	
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	/* * from:转出账号 * to:转入账号 * money:转账金额 */
	@Override
	public void transfer(String from, String to, Double money) {
		accountDao.outMoney(from, money);
		int d = 1 / 0;//模拟银行断电的情况(在这儿出现了异常)
		accountDao.inMoney(to, money);
	}

}

这时应该怎么解决钱转丢了的这个问题呢?可以使用事务来解决,Spring中的事务管理主要分为两大类,它们分别是:

下面,我会分别对这两类事务管理进行介绍。

Spring的编程式事务管理

为了解决上面钱转丢了的问题,这里我会使用Spring的编程式事务管理这种方式来管理事务。步骤如下所示。

  • 第一步:配置平台事务管理器。之前,我就讲过Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类,这里咱使用的是DataSourceTransactionManager类。

  • 第二步,配置Spring提供的事务管理的模板类,也即TransactionTemplate类,通过它可以简化事务管理的代码。

  • 第三步,首先修改一下AccountServiceImpl实现类,在其中定义一个transactionTemplate属性,并生成set方法。
package com.meimeixia.tx.demo01;

import org.springframework.transaction.support.TransactionTemplate;

/** * 转账的业务层的实现类 * @author liayun * */
public class AccountServiceImpl implements AccountService {
	
	//注入AccountDao
	private AccountDao accountDao;
	
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
	
	//注入事务管理的模板
	private TransactionTemplate transactionTemplate;
	
	public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		this.transactionTemplate = transactionTemplate;
	}

	/* * from:转出账号 * to:转入账号 * money:转账金额 */
	@Override
	public void transfer(String from, String to, Double money) {
		accountDao.outMoney(from, money);
		int d = 1 / 0;//模拟银行断电的情况(在这儿出现了异常)
		accountDao.inMoney(to, money);
	}

}

然后,在id为accountService的bean标签里面使用property标签注入以上transactionTemplate属性,即在业务层中注入事务管理的模板。

  • 第四步,编写事务管理的代码。

此时,再次运行SpringDemo01单元测试类中的demo01方法,可以发现即使出现了异常,比如银行断电,也不会出现钱转丢了的情况。

Spring的声明式事务管理

XML方式的声明式事务管理

基于XML配置文件的方式来进行声明式事务的操作,不需要进行手动编写代码,通过一段配置即可完成事务管理。为了演示这种方式的声明式事务管理,我们得回到刚开始搭建好的转账环境下,即出现了异常时(比如银行断电),钱会转丢的情况下。如果是使用Spring基于XML配置文件方式的声明式事务管理来解决这个问题,那么步骤如下所示。

  • 第一步,配置平台事务管理器。之前,我就讲过Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类,这里咱使用的是DataSourceTransactionManager类。

  • 第二步,配置事务的增强,即指定对哪个事务管理器进行增强。

其中,<tx:method>标签中有如下一些属性。

  • 第三步,配置切入点和切面。

此时,运行SpringDemo01单元测试类中的demo01方法,可以发现即使出现了异常,比如银行断电,也不会出现钱转丢了的情况。

注解方式的声明式事务管理

基于注解方式来进行声明式事务的操作会更加简单,在实际开发中我们也会用的比较多。为了演示这种方式的声明式事务管理,我们得回到刚开始搭建好的转账环境下,即出现了异常时(比如银行断电),钱会转丢的情况下。如果是使用Spring基于注解方式的声明式事务管理来解决这个问题,那么步骤如下所示。

  • 第一步,配置事务管理器。

  • 第二步,开启事务注解。

以上配置添加完毕之后,Spring配置文件的内容就变成下面这个样子了。

<?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">
	
	<!-- 配置Service和Dao~~~~~~~~~~~~~~~~~~ -->
	<bean id="accountService" class="com.meimeixia.tx.demo03.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
	</bean>
	
	<!-- 配置Dao~~~~~~~~~~~~~~~~~~ -->
	<!-- <bean id="accountDao" class="com.meimeixia.tx.demo01.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> -->
	
	<!-- 注意:其实你继承了JdbcDaoSupport类之后,还可以让你的代码简化,查看该类的源代码, 在它的源代码里面,它还提供了一个setDataSource(DataSource dataSource)方法, 给它注入一个连接池,如果你给它注入了一个连接池的话,它会使用这个连接池帮你创建一个JDBC的模板。 所以说,谁继承了这个类,谁就可以直接给这个类里面注入一个连接池。 好处:这儿都不用写JDBC模板的定义了。 -->
	<bean id="accountDao" class="com.meimeixia.tx.demo03.AccountDaoImpl">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
	
	<!-- 配置一个C3P0连接池和JDBC的模板~~~~~~~~~~~~~~~~~~ -->
	<!-- 第二种方式:通过context标签来引入 -->
	<context:property-placeholder location="classpath:jdbc.properties" />
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driverClass}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
	<!-- 配置Spring的JDBC模板,它还得需要使用一个连接池 -->
	<!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 属性注入 <property name="dataSource" ref="dataSource" /> </bean> -->
		
	<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
	<!-- 配置平台的事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 平台事务管理器必须得能够拿到连接,它才能帮我们管理事务 -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 开启注解(式)事务 -->
	<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
  • 第三步,在具体使用事务的方法的所在类上面添加@Transactional注解。

此时,运行SpringDemo01单元测试类中的demo01方法,可以发现即使出现了异常,比如银行断电,也不会出现钱转丢了的情况。

相关文章