# 事务概念
什么是事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
经典场景:银行转账
事务四个特性 (ACID)
- 原子性
- 一致性
- 隔离性
- 持久性
# 事务操作 (搭建事务操作环境)
引入相关 jar 包依赖
1. 创建数据库表,添加记录
2. 创建 service,创建 dao,完成对象创建和注入关系
2.1 service 中注入 dao,dao 中注入 jdbcTemplate,JdbcTemplate 模版中注入 DataSource
<?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"/> | |
<context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false"> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> | |
</context:component-scan> | |
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> | |
<property name="driverClassName" value="${jdbc.driver}"/> | |
<property name="url" value="${jdbc.url}"/> | |
<property name="username" value="${jdbc.username}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> | |
<property name="dataSource" ref="dataSource"/> | |
</bean> | |
</beans> |
jdbc.driver=com.mysql.cj.jdbc.Driver | |
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useUnicode=true | |
jdbc.username=root | |
jdbc.password=dkx |
public interface UserService { | |
} | |
@SuppressWarnings("all") | |
@Service | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
} |
public interface UserDao { | |
} | |
@SuppressWarnings("all") | |
@Repository | |
public class UserDaoImpl implements UserDao { | |
@Autowired | |
private JdbcTemplate jdbcTemplate; | |
} |
3, 在 dao 创建两个方法,多钱和少钱的方法,在 service 创建方法 (转账方法)
Dao
public interface UserDao { | |
// 多钱 | |
int addMoney(); | |
// 少钱 | |
int reduceMoney(); | |
} | |
@SuppressWarnings("all") | |
@Repository | |
public class UserDaoImpl implements UserDao { | |
@Autowired | |
private JdbcTemplate jdbcTemplate; | |
// 多钱 | |
@Override | |
public int addMoney() { | |
String sql = "update t_account set money = money - ? where username = ?"; | |
int flag = jdbcTemplate.update(sql,100,"lucy"); | |
return flag; | |
} | |
// 少钱 | |
@Override | |
public int reduceMoney() { | |
String sql = "update t_account set money = money + ? where username = ?"; | |
int flag = jdbcTemplate.update(sql,100,"mary"); | |
return flag; | |
} | |
} |
Service
public interface UserService { | |
// 转账方法 | |
boolean accountMoney(); | |
} | |
@SuppressWarnings("all") | |
@Service | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney(){ | |
int flag = userDao.addMoney(); | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
} | |
} |
测试代码:
public class AppTest { | |
@Test | |
public void test(){ | |
ApplicationContext context = new ClassPathXmlApplicationContext | |
("appliction.xml"); | |
UserService service = context.getBean("userServiceImpl", UserService.class); | |
boolean flag = service.accountMoney(); | |
System.out.println(flag == true ? "转账OK" : "转账NO"); | |
} | |
} |
测试结果:
Jun 12, 2023 8:41:46 PM com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
INFO: {dataSource-1} inited
转账OK
刷新数据库之前
刷新数据库之后
4. 上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题,如下演示:
- 在转账方法程序过程中制造一个异常
@SuppressWarnings("all") | |
@Service | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney(){ | |
int flag = userDao.addMoney(); | |
// 制造异常,让程序终止与此 | |
int i = 1/0; | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
} | |
} |
- 恢复数据库的数据
- 执行程序
- 查看数据库数据
lucy 少钱了,mary 且没有加钱
# 上述问题使用事务进行解决:要么都成功,要么都失败
# 事务操作 (Spring 事务管理介绍)
1. 事务添加到 JavaEE 三层结构里面 Service 层 (业务逻辑层)
2. 在 Spring 进行事务管理操作
- 有两种方式:编程式事务管理和声明式事务管理 (使用)
3. 声明式事务管理
- 基于注解方式
- 基于 xml 配置文件方式
4. 在 spring 进行声明式事务管理,底层使用 AOP 原理
5.spring 事务管理 API
- 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
- PlatformTransactionManager
# 事务操作 (注解声明式事务管理)
# 1. 在 spring 配置文件配置事务管理器
<!-- 创建事务管理器 --> | |
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> | |
<!-- 注入数据源 --> | |
<property name="dataSource" ref="dataSource"/> | |
</bean> |
完整 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"/> | |
<context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false"> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> | |
</context:component-scan> | |
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> | |
<property name="driverClassName" value="${jdbc.driver}"/> | |
<property name="url" value="${jdbc.url}"/> | |
<property name="username" value="${jdbc.username}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<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"/> | |
</bean> | |
</beans> |
# 2. 在 spring 配置文件,开启事务注解
- 在 spring 配置文件引入名称空间: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:context="http://www.springframework.org/schema/context" | |
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> | |
</beans> |
- 开启事务注解
<!-- 开启事务注解 --> | |
<!-- | |
transaction-manager: 指定使用的事务管理器 | |
--> | |
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> |
完整 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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> | |
<context:property-placeholder location="classpath:jdbc.properties"/> | |
<context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false"> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> | |
</context:component-scan> | |
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> | |
<property name="driverClassName" value="${jdbc.driver}"/> | |
<property name="url" value="${jdbc.url}"/> | |
<property name="username" value="${jdbc.username}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<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"/> | |
</bean> | |
<!-- 开启事务注解 --> | |
<!-- | |
transaction-manager: 指定使用的事务管理器 | |
--> | |
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> | |
</beans> |
- 在 service 类上面 (或者 service 类里面方法上面) 添加事务注解
- @Transactional:这个注解添加到类上面,也可以添加到方法上面
- 如果将注解添加类上面,这个类里面所有的方法都会添加事务
- 如果将注解添加到方法上面,则仅为这个方法添加事务
@SuppressWarnings("all") | |
@Service | |
// 开启事务管理 | |
@Transactional | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney(){ | |
//try {} catch 等逻辑代码是编程式事务,编写会让程序看起来很臃肿 | |
// try{ | |
// 第一步 开启事务 | |
int flag = userDao.addMoney(); | |
// 制造异常,让程序终止与此 | |
int i = 1/0; | |
// 第二步 进行业务操作 | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
// 第三步 没有发生异常,提交事务 | |
// }catch(ArithmeticException e){ | |
// throw new RuntimeException(e); | |
// // 第四步 出现异常,事务回滚 | |
// } | |
} | |
} |
- 再来测试转账代码:
public class AppTest { | |
@Test | |
public void test(){ | |
ApplicationContext context = new ClassPathXmlApplicationContext | |
("appliction.xml"); | |
UserService service = context.getBean("userServiceImpl", UserService.class); | |
boolean flag = service.accountMoney(); | |
System.out.println(flag == true ? "转账OK" : "转账NO"); | |
} | |
} |
测试结果:
数据库没有发生变化,要么都成功,要么都失败
# 事务操作 (声明式事务管理参数配置)
- propagation:事务传播行为
- 当一个事务方法被另外一个事务方法调用时候,这个事务方法如何进行
事务的传播行为可以由传播属性指定,Spring 定义了 7 种类传播行为
传播属性 | 描述 |
---|---|
REQUIRED | 如果有事务正在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 |
REQUIRED_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起,方法 1 调用方法 2 时出现了问题,方法 2 依然能正常提交 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTE | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须在运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 |
// 开启事务管理 | |
@Transactional(propagation = Propagation.REQUIRED) |
isolation:事务隔离级别
- 事务有特性称为隔离性,多事务操作之间不会产生影响,不考虑隔离性产生很多问题
- 有三个读问题:脏读,不可重复读,虚 (幻) 读
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交事务读取到另一个提交事务修改的数据
- 虚 (幻) 读:一个未提交事务读取到另一个提交事务添加的数据
- 解决:通过设置事务隔离级别,解决读问题
脏读 不可重复读 幻读 RED_UNCOMMITTED<br>(读未提交) 有 有 有 READ_COMMITTED<br/>(读已提交) 无 有 有 REPEATABLE_READ<br>(可重复读) 无 无 有 SERIALIZABLE<br/>(串行化) 无 无 无 设置事务的隔离级别
// 开启事务管理
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
timeout:超时时间
- 事务需要在一定时间内进行提交,如果不提交进行回滚
- 默认值是 - 1,设置时间以秒单位进行计算
// 开启事务管理
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
readOnly:是否只读
- 读:查询操作,写:添加修改删除操作
- readOnly 默认值 false,表示可以查询,可以添加修改删除操作
- 设置 readOnly 值是 true,设置成 true 之后,只能查询
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
@Transactional(readOnly = true,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
执行修改 Update 操作后就会报错:
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [update t_account set money = money - ? where name = ?]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
上面演示中使用的异常是 ArithmeticException 算数异常,报错后事务就会回滚,但是有些异常是不会回滚的比如 IOException 就不会,如下演示:
@SuppressWarnings("all")
@Service
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
@Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public boolean accountMoney() throws IOException {
int flag = userDao.addMoney();
// if(true){
// throw new IOException();
// }
int i = 1/0;
int flag1 = userDao.reduceMoney();
return flag > 0 && flag1 > 0 ? true : false;
}
}
测试代码:
public class AppTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext
("appliction.xml");
UserService service = context.getBean("userServiceImpl", UserService.class);
boolean flag = service.accountMoney();
System.out.println(flag == true ? "转账OK" : "转账NO");
}
}
测试结果:
java.lang.ArithmeticException: / by zero
上面的算数异常不会出现不一致性的问题但是改为 IOException 呢:
@SuppressWarnings("all")
@Service
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
@Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public boolean accountMoney() throws IOException {
int flag = userDao.addMoney();
if(true){
throw new IOException();
}
// int i = 1/0;
int flag1 = userDao.reduceMoney();
return flag > 0 && flag1 > 0 ? true : false;
}
}
直接看测试结果:
java.io.IOException
可以看出事务并没有起到作用,这时就需要使用 rollbackFor: 来针对性的异常事务回滚了:
@SuppressWarnings("all")
@Service
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
@Transactional(rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public boolean accountMoney() throws IOException {
int flag = userDao.addMoney();
if(true){
throw new IOException();
}
// int i = 1/0;
int flag1 = userDao.reduceMoney();
return flag > 0 && flag1 > 0 ? true : false;
}
}
直接看测试结果:
java.io.IOException
事务发生 IO 异常后进行了回滚
注意:指定了 IO 异常回滚并不会影响它自己的算术异常回滚的机制,但是可以使用下面的 noRollbackFor 来指定算数异常不回滚
noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
ArithmeticException 是默认进行事务回滚的,将其设置为不回滚:
@SuppressWarnings("all") | |
@Service | |
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加 | |
@Transactional(noRollbackFor={ArithmeticException.class},rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney() throws IOException { | |
int flag = userDao.addMoney(); | |
// if(true){ | |
// throw new IOException(); | |
// } | |
int i = 1/0; | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
} | |
} |
测试代码:
public class AppTest { | |
@Test | |
public void test() throws IOException { | |
ApplicationContext context = new ClassPathXmlApplicationContext | |
("appliction.xml"); | |
UserService service = context.getBean("userServiceImpl", UserService.class); | |
boolean flag = service.accountMoney(); | |
System.out.println(flag == true ? "转账OK" : "转账NO"); | |
} | |
} |
测试结果:
java.lang.ArithmeticException: / by zero
# 事务操作 (XML 声明式事务管理)
1. 在 spring 配置文件中进行配置
开启 tx,aop,context 命名空间
<?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:tx="http://www.springframework.org/schema/tx" | |
xmlns:aop="http://www.springframework.org/schema/aop" | |
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd | |
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> | |
</beans> |
第 0 步:配置通过外部引用读取数据库连接池配置,并创建 JdbcTemplate 对象
<?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:tx="http://www.springframework.org/schema/tx" | |
xmlns:aop="http://www.springframework.org/schema/aop" | |
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd | |
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> | |
<context:property-placeholder location="classpath:jdbc.properties"/> | |
<context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false"> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> | |
</context:component-scan> | |
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> | |
<property name="driverClassName" value="${jdbc.driver}"/> | |
<property name="url" value="${jdbc.url}"/> | |
<property name="username" value="${jdbc.username}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> | |
<property name="dataSource" ref="dataSource"/> | |
</bean> | |
</beans> |
第一步:配置事务管理器
<!-- 创建事务管理器 --> | |
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> | |
<!-- 注入数据源 --> | |
<property name="dataSource" ref="dataSource"/> | |
</bean> |
第二步:配置通知
- 通知:实际被增强的逻辑部分
<!-- 配置通知 --> | |
<tx:advice id="txAdvice"> | |
<!-- 配置事务参数 --> | |
<tx:attributes> | |
<!-- 指定哪种规则的方法上面添加事务 --> | |
<tx:method name="accountMoney" propagation="REQUIRED"/> | |
<!--<tx:method name="accountMoney" propagation="REQUIRED"/>--> | |
</tx:attributes> | |
</tx:advice> |
第三步:配置切入点和切面
切入点:实际被增强的方法
切面:将通知应用到切入点的操作就是切面
<!-- 配置切入点和切面 --> | |
<aop:config> | |
<!-- 配置切入点 --> | |
<aop:pointcut id="pt" expression="execution(* com.dkx.service.impl.UserServiceImpl.*(..))"/> | |
<!-- 配置切面 --> | |
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> | |
</aop:config> |
完整 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:tx="http://www.springframework.org/schema/tx" | |
xmlns:aop="http://www.springframework.org/schema/aop" | |
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd | |
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> | |
<context:property-placeholder location="classpath:jdbc.properties"/> | |
<context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false"> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> | |
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> | |
</context:component-scan> | |
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> | |
<property name="driverClassName" value="${jdbc.driver}"/> | |
<property name="url" value="${jdbc.url}"/> | |
<property name="username" value="${jdbc.username}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<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"/> | |
</bean> | |
<!-- 配置通知 --> | |
<tx:advice id="txAdvice"> | |
<!-- 配置事务参数 --> | |
<tx:attributes> | |
<!-- 指定哪种规则的方法上面添加事务 --> | |
<tx:method name="accountMoney" propagation="REQUIRED"/> | |
<!--<tx:method name="accountMoney" propagation="REQUIRED"/>--> | |
</tx:attributes> | |
</tx:advice> | |
<!-- 配置切入点和切面 --> | |
<aop:config> | |
<!-- 配置切入点 --> | |
<aop:pointcut id="pt" expression="execution(* com.dkx.service.impl.UserServiceImpl.*(..))"/> | |
<!-- 配置切面 --> | |
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> | |
</aop:config> | |
</beans> |
将注解注释掉:
@SuppressWarnings("all") | |
@Service | |
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加 | |
//@Transactional(noRollbackFor={ArithmeticException.class},rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney() throws IOException { | |
int flag = userDao.addMoney(); | |
// if(true){ | |
// throw new IOException(); | |
// } | |
int i = 1/0; | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
} | |
} |
测试代码:
public class AppTest { | |
@Test | |
public void test() throws IOException { | |
ApplicationContext context = new ClassPathXmlApplicationContext | |
("appliction1.xml"); | |
UserService service = context.getBean("userServiceImpl", UserService.class); | |
boolean flag = service.accountMoney(); | |
System.out.println(flag == true ? "转账OK" : "转账NO"); | |
} | |
} |
测试结果:
java.lang.ArithmeticException: / by zero
# 事务操作 (完全注解声明式事务管理)
1. 创建配置类,使用配置类替代 xml 配置文件
目录结构:
SpringConfig
// 设置为配置 Bean | |
@Configuration | |
// 配置组件扫描 | |
@ComponentScan(basePackages = {"com.dkx.dao","com.dkx.service"}) | |
@PropertySource("classpath:jdbc.properties") | |
// 开启事务管理 | |
@EnableTransactionManagement | |
// 导入其它配置类 | |
@Import({JdbcConfig.class,TransactionManagerConfig.class,JdbcTemplateConfig.class}) | |
public class SpringConfig { | |
} |
JdbcConfig
public class JdbcConfig { | |
@Value("${jdbc.driver}") | |
private String driver; | |
@Value("${jdbc.url}") | |
private String url; | |
@Value("${jdbc.username}") | |
private String username; | |
@Value("${jdbc.password}") | |
private String password; | |
// 创建 DruidDataSource 对象 | |
@Bean | |
public DruidDataSource getDataSource(){ | |
DruidDataSource druid = new DruidDataSource(); | |
// 注入数据库连接池信息 | |
druid.setDriverClassName(driver); | |
druid.setUrl(url); | |
druid.setUsername(username); | |
druid.setPassword(password); | |
return druid; | |
} | |
} |
JdbcTemplateConfig
public class JdbcTemplateConfig { | |
// 创建 JdbcTemplate 对象 | |
@Bean | |
public JdbcTemplate getJdbcTemplate(DataSource dataSource){ | |
JdbcTemplate jdbc = new JdbcTemplate(); | |
// 注入数据源 | |
jdbc.setDataSource(dataSource); | |
return jdbc; | |
} | |
} |
TransactionManagerConfig
public class TransactionManagerConfig { | |
// 创建事务管理器 | |
@Bean | |
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ | |
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); | |
// 注入数据源 | |
transactionManager.setDataSource(dataSource); | |
return transactionManager; | |
} | |
} |
Dao
public interface UserDao { | |
// 多钱 | |
int addMoney(); | |
// 少钱 | |
int reduceMoney(); | |
} |
DaoImpl
@SuppressWarnings("all") | |
@Repository | |
public class UserDaoImpl implements UserDao { | |
@Autowired | |
private JdbcTemplate jdbcTemplate; | |
// 多钱 | |
@Override | |
public int addMoney() { | |
String sql = "update t_account set money = money - ? where name = ?"; | |
int flag = jdbcTemplate.update(sql,100,"lucy"); | |
return flag; | |
} | |
// 少钱 | |
@Override | |
public int reduceMoney() { | |
String sql = "update t_account set money = money + ? where name = ?"; | |
int flag = jdbcTemplate.update(sql,100,"mary"); | |
return flag; | |
} | |
} |
Service
public interface UserService { | |
// 转账方法 | |
boolean accountMoney() throws IOException; | |
} |
ServiceImpl
@SuppressWarnings("all") | |
@Service | |
// 开启事务管理 | |
@Transactional | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDao userDao; | |
public boolean accountMoney() throws IOException { | |
int flag = userDao.addMoney(); | |
int i = 1/0; | |
int flag1 = userDao.reduceMoney(); | |
return flag > 0 && flag1 > 0 ? true : false; | |
} | |
} |
测试代码:
public class AppTest { | |
@Test | |
public void test() throws IOException { | |
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); | |
UserService service = context.getBean("userServiceImpl", UserService.class); | |
boolean flag = service.accountMoney(); | |
System.out.println(flag == true ? "转账OK" : "转账NO"); | |
} | |
} |
测试结果:
java.lang.ArithmeticException: / by zero