学习了AOP之后就可以应用一下了,所以这次我们了解一下Spring的声明式事务。
事务在我们的很多方面都可以体现,就拿我们平时的买卖活动,或者是银行的转账来说,这些活动要么是成功,要么是失败,比如:张三给李四转账100块钱,我们的实现是张三的账户划掉100然后李四的账户加上100,这是一个整体,要么成功,要么失败。我们不可能说张三的账户少了钱,但是因为某种原因导致李四的账户没多钱。所以说我们要通过某种机制保证这个整体活动的成功。接下来进入主题。
1. Spring事务简介
1.1 事务的四要素
名称 | 内容 |
---|---|
原子性(atomicity) | 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。 |
一致性(consistency) | 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。 |
隔离性(isolation) | 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。 |
持久性(durability) | 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。 |
1.2 Spring事务管理方式
- 编程式事务
编程式事务从名字应该可以看出,就是事务的管理方式由自己编写代码来实现,主要的实现方式就是通过对事务的代码嵌入进去我们的业务代码之中 - 声明式事务
声明事务主要是利用了Spring AOP通过面向切面编程把事务代码直接嵌入事务代码中,所以这里我们获取到的有事务管理的Bean实际上是Spring经过加工之后的代理类
概念性的东西不是内行,关于Spring事务的一些概念性的东西可以参考一下链接,希望有帮助
事务简介
|-https://baike.baidu.com/item/acid/10738?fr=aladdin
Spring事务
|-http://www.cnblogs.com/ysxlin/archive/2008/06/06/1215300.html
|- http://lxy2330.iteye.com/blog/1122183
|- http://www.cnblogs.com/longshiyVip/p/5061637.html
2. Spring事务准备工作
现在有两个人需要进行转账工作,我这里就随便的设计了这么一个剧情了,首先有一个dao
(Data Access Object数据访问对象,与数据库打交道,这里我没有使用接口来实现,但是在一般的应用中是需要用接口实现的)
//这里继承了JdbcTemplate主要是方便实现,它对我们的Jdbc进行了封装,可以使我们不用考虑数据库连接关闭等等的问题
//而且此次的Spring事务主要是说的Jdbc事务
public class TransferDao extends JdbcTemplate {
/**
* 将钱的数目转向当前user的账户
* @param money money
* @param username username
*/
public void transferMoneyToOne(Float money, String username){
this.update("UPDATE account SET money = money + (?) WHERE username = ?", money, username);
}
}
还有一个service(这里主要是写我们业务相关的代码)
public class TransferService {
private TransferDao dao;
/**
* 转账业务,这里为了方便直接写了张三转给了李四100块钱
*/
public void transferMoney(){
dao.transferMoneyToOne((float) -100, "张三");
// int i = 1 / 0;//为了测试出现异常的情况,测试时可去掉
dao.transferMoneyToOne((float) 100, "李四");
// throw new RuntimeException("这里突然死机了");//测试时可去掉
}
public TransferDao getDao() {
return dao;
}
public void setDao(TransferDao dao) {
this.dao = dao;
}
}
上面注释抛出的异常和代码错误主要是为了等下代码测试的时候模拟我们日常中可能出现的各种错误
这里还需要一个数据库和表(数据库自建,表的sql在下方)
-- ----------------------------
-- Table structure for man
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(255) default NULL,
`money` float(255,0) default NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of man
-- ----------------------------
INSERT INTO `account` VALUES ('张三', '9600');
INSERT INTO `account` VALUES ('李四', '970');
3. Spring事务配置
事务需要加入的依赖较之前的需要加入tx依赖而且涉及数据库操作需要mysql连接包以及数据库连接池,这里使用的是阿里的druid,maven的pom.xml依赖主要如下:
<properties>
<spring.version>4.3.10.RELEASE</spring.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
以下是classpath下的applicationContext.xml配置文件
因为是事务配置与之前的相比需要加入tx命名约束
<?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"
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-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!--配置DataSource-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--以下是数据库连接的具体信息-->
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_demo_1"/>
</bean>
<!--配置TransferDao-->
<bean class="cn.lger.dao.TransferDao" name="transferDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置TransferService-->
<bean name="transferService" class="cn.lger.service.TransferService">
<property name="dao" ref="transferDao"/>
</bean>
<!--配置管理事务的类-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--给定管理事务的类 设置通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
这里设置需要事务操作的方法(切入点),
其中还有一些属性没有写,这里全部采用默认操作,
*为全部都需要事务操作
-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--设置切入点-->
<aop:pointcut id="txPointcut" expression="execution(* cn.lger.service.TransferService.*(..))"/>
<!--给定之前配置的通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
在上面的tx通知配置中<tx:method name="*"/>
,注释写明了采用默认的操作,但是这里还是提一下,<tx:method name="被代理方法名(需要事务管理的方法)" propagation="传播行为" read-only="是否只读" isolation="隔离级别" timeout="事务所需最大时间"/>
这里不作赘述,下面的链接我个人觉得解释的很好
事务配置之
<tx:method/>
有关的设置
http://blog.sina.com.cn/s/blog_4a40057401000865.html
http://www.iteye.com/topic/410461
4. Spring事务测试
经过以上的准备工作以及配置这个项目已经能够运行了,所以,我们现在来测试一下当没有发生异常的情况下是否能够转账成功
public class TestTx {
/**
* 测试service的转账功能
*/
@Test
public void test01(){
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
TransferService service = context.getBean(TransferService.class);
service.transferMoney();
}
}
经过测试张三成功地转账给了李四100元,然后我们把之前的TransferService
的异常的注释给去掉,然后进行测试,发现数据库中的数据稳如泰山,不会变,这就是事务,当抛出异常时会影响事务的ACID四要素,这个整体活动当然不会成功执行,所以,你看到的这就是事务。
上面代码可以在Github下载