事务的特性
原子性: 强调事务的不可分割.
一致性: 事务的执行的前后数据的完整性保持一致.
隔离性: 一个事务执行的过程中, 不应该受到其他事务的干扰.
持久性: 事务一旦结束, 数据就持久化到数据库.
如果不考虑隔离性会引发的安全性问题
脏读: 一个事务读到了另一个事务的未提交的数据.
不可重复读: 一个事务读到了另一个事务已经提交的 update 的数据, 导致多次查询的结果不一致.
虚读: 一个事务读到了另一个事务已经提交的 insert 的数据, 导致多次查询的结果不一致.
解决读问题: 设置事务的隔离级别
未提交读: 脏读, 不可重复读和虚读都有可能发生.
已提交读: 避免脏读, 但是不可重复读和虚读有可能发生.
可重复读: 避免脏读和不可重复读, 但是虚读有可能发生.
串行化的: 避免以上所有读问题.
Spring 的声明式事务管理方式
Spring 进行声明式事务配置的方式有两种:
基于 xml 配置文件方式
基于注解方式
但无论使用什么方式进行 Spring 的事务操作, 首先要配置一个事务管理器.
搭建转账的环境
第一步, 创建数据库表.
- DROP TABLE IF EXISTS `account`;
- CREATE TABLE `account` (`id` int(11) DEFAULT NULL,
- `username` varchar(100) DEFAULT NULL,
- `salary` int(11) DEFAULT NULL
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- INSERT INTO `account` VALUES ('1', '小郑', '10000');
- INSERT INTO `account` VALUES ('2', '小谭', '10000');
第二步, 创建一个 web 项目, 并引入 Spring 的相关 jar 包.
第三步, 创建业务层和 DAO 层的类.
在 Web 项目的 src 目录下创建一个 cn.itcast.tx 包, 并在该包下编写业务层和 DAO 层的类.
业务层 --BookService.java
- public class BookService {
- private BookDao bookDao;
- public void setBookDao(BookDao bookDao) {
- this.bookDao = bookDao;
- }
- }
DAO 层 --BookDao.java
- public class BookDao {
- private JdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- }
第四步, 配置业务层和 DAO 层的类.
- <?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">
- <!-- 配置 C3P0 连接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
- <property name="jdbcUrl" value="jdbc:mysql:///spring_lee"></property>
- <property name="user" value="root"></property>
- <property name="password" value="yezi"></property>
- </bean>
- <!-- 创建 service 和 dao 的对象 -->
- <bean id="bookService" class="cn.itcast.tx.BookService">
- <!-- 注入 dao -->
- <property name="bookDao" ref="bookDao"></property>
- </bean>
- <bean id="bookDao" class="cn.itcast.tx.BookDao">
- <!-- 注入 JdbcTemplate 模板类的对象 -->
- <property name="jdbcTemplate" ref="jdbcTemplate"></property>
- </bean>
- <!-- 创建 JdbcTemplate 模板类的对象 -->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <!-- 注入 dataSource -->
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- </beans>
第五步, 转账的具体实现, 实现小郑转账 1000 元给小谭.
JavaEE 中 DAO 层做的事情主要是对数据库进行操作, 在 DAO 层里面一般不写业务操作, 一般写单独操作数据库的方法. 所以 BookDao 类的代码要修改为:
- public class BookDao {
- private JdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- // 小郑少 1000
- public void lessMoney() {
- String sql = "update account set salary=salary-? where username=?";
- jdbcTemplate.update(sql, 1000, "小郑");
- }
- // 小谭多 1000
- public void moreMoney() {
- String sql = "update account set salary=salary+? where username=?";
- jdbcTemplate.update(sql, 1000, "小谭");
- }
- }
JavaEE 中 Service 层写具体的业务操作, 所以 BookService 类的代码要修改为:
- public class BookService {
- private BookDao bookDao;
- public void setBookDao(BookDao bookDao) {
- this.bookDao = bookDao;
- }
- // 转账的业务
- public void accountMoney() {
- // 1. 小郑少 1000
- bookDao.lessMoney();
- // 2. 小谭多 1000
- bookDao.moreMoney();
- }
- }
第六步, 编写一个测试类.
在 cn.itcast.tx 包下编写一个 TestDemo 单元测试类.
- public class TestDemo {
- @Test
- public void testAccount() {
- ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
- BookService bookService = (BookService) context.getBean("bookService");
- bookService.accountMoney();
- }
- }
测试以上方法即可实现小郑转账 1000 元给小谭. 现在我来演示一个问题, 在 BookService 类中调用 BookDao 类的两个方法构成了转账业务, 但是如果小郑少了 1000 元之后, 这时突然出现异常, 比如银行断电, 就会出现小郑的钱少了, 而小谭的钱没有多, 钱丢失了的情况.
- public class BookService {
- private BookDao bookDao;
- public void setBookDao(BookDao bookDao) {
- this.bookDao = bookDao;
- }
- // 转账的业务
- public void accountMoney() {
- // 1. 小郑少 1000
- bookDao.lessMoney();
- int x = 10 / 0; // 模拟银行断电的情况 (出现的异常)
- // 2. 小谭多 1000
- bookDao.moreMoney();
- }
- }
这时应该怎么解决这个问题呢? 就可使用事务来解决. Spring 中进行事务的操作主要有两种方式:
第一种: 编程式事务管理 (这种了解就行, 不用掌握)
第二种: 声明式事务管理
基于 xml 配置文件方式
基于注解方式
Spring 的声明式事务管理 --XML 方式: 思想就是 AOP
基于 xml 配置文件的方式来进行声明式事务的操作, 不需要进行手动编写代码, 通过一段配置完成事务管理.
第一步, 配置事务管理器.
Spring 针对不同的持久化框架, 提供了不同 PlatformTransactionManager 接口的实现类, 如下:
所以我们需要在 Spring 的配置文件中添加如下配置:
- <!-- 1. 配置事务的管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <!-- 指定要对哪个数据库进行事务操作 -->
- <property name="dataSource" ref="dataSource"></property>
- </bean>
第二步, 配置事务的增强, 即指定对哪个事务管理器进行增强. 故需要向 Spring 的配置文件中添加如下配置:
- <!-- 2. 配置事务的增强, 指定对哪个事务管理器进行增强 -->
- <tx:advice id="txadvice" transaction-manager="transactionManager">
- <tx:attributes>
- <!--
- 表示来配置你要增强的方法的匹配的一个规则,
- 注意: 只须改方法的命名规则, 其他都是固定的!
- propagation: 事务的传播行为.
- -->
- <tx:method name="account*" propagation="REQUIRED"></tx:method>
- <!-- <tx:method name="insert*" propagation="REQUIRED"></tx:method> -->
- </tx:attributes>
- </tx:advice>
第三步, 配置切入点和切面. 这步须向 Spring 的配置文件中添加如下配置:
- <!-- 3. 配置切入点和切面 (最重要的一步) -->
- <aop:config>
- <!-- 切入点 -->
- <aop:pointcut expression="execution(* cn.itcast.tx.BookService.*(..))" id="pointcut1"/>
- <!-- 切面, 即表示把哪个增强用在哪个切入点上 -->
- <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1"/>
- </aop:config>
这时测试 TestDemo 单元测试类的 testAccount 方法即可.
Spring 的声明式事务的注解方式
基于注解方式来进行声明式事务的操作会更加简单, 在实际开发中我们也会用的比较多.
第一步, 配置事务管理器.
- <!-- 1. 配置事务管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
第二步, 开启事务注解.
- <!-- 2. 开启事务的注解 -->
- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
以上配置添加完毕之后, 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">
- <!-- 配置 C3P0 连接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
- <property name="jdbcUrl" value="jdbc:mysql:///spring_lee"></property>
- <property name="user" value="root"></property>
- <property name="password" value="yezi"></property>
- </bean>
- <!-- 1. 配置事务管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- <!-- 2. 开启事务的注解 -->
- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- <!-- 创建 service 和 dao 的对象 -->
- <bean id="bookService" class="cn.itcast.tx.BookService">
- <!-- 注入 dao -->
- <property name="bookDao" ref="bookDao"></property>
- </bean>
- <bean id="bookDao" class="cn.itcast.tx.BookDao">
- <!-- 注入 JdbcTemplate 模板类的对象 -->
- <property name="jdbcTemplate" ref="jdbcTemplate"></property>
- </bean>
- <!-- 创建 JdbcTemplate 模板类的对象 -->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <!-- 注入 dataSource -->
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- </beans>
第三步, 在具体使用事务的方法所在的类上面添加注解:@Transactional. 即 BookService 类应修改为:
- @Transactional
- public class BookService {
- private BookDao bookDao;
- public void setBookDao(BookDao bookDao) {
- this.bookDao = bookDao;
- }
- // 转账的业务
- public void accountMoney() {
- // 1. 小郑少 1000
- bookDao.lessMoney();
- int x = 10 / 0; // 模拟银行断电的情况 (出现的异常)
- // 2. 小谭多 1000
- bookDao.moreMoney();
- }
- }
这时测试 TestDemo 单元测试类的 testAccount 方法即可.
来源: http://www.bubuko.com/infodetail-2605921.html