为了在特定领域帮助我们简化代码,Spring 封装了很多 『Template』形式的模板类。例如:RedisTemplate、RestTemplate 等等,包括我们今天要学习的 JDBCTemplate。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.atguigugroupId>
<artifactId>day50spring-JDBCTemplateartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.31version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
<scope>providedscope>
dependency>
dependencies>
project>
datasource.url=jdbc:mysql://localhost:3306/mybatis2?characterEncoding=utf8&serverTimezone=UTC
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.username=root
datasource.password=123456
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${datasource.username}">property>
<property name="password" value="${datasource.password}">property>
<property name="url" value="${datasource.url}">property>
<property name="driverClassName" value="${datasource.driver}">property>
bean>
package com.atguigu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
// 全参构造器
@AllArgsConstructor
// 无参构造器
@NoArgsConstructor
public class Soldier {
private Integer soldierId;
private String soldierName;
private String soldierWeapon;
}
package com.atguigu.dao;
import com.atguigu.pojo.Soldier;
import java.sql.SQLException;
import java.util.List;
public interface SoldierDao {
/**
* 根据id删除
* @param soldierId
*/
void deleteById(Integer soldierId) throws SQLException;
/**
* 更新士兵对象
* @param soldier
*/
void update(Soldier soldier) throws SQLException;
/**
* 添加士兵对象
* @param soldier
*/
void add(Soldier soldier) throws SQLException;
/**
* 根据id查询士兵
* @param soldierId
* @return
*/
Soldier getById(Integer soldierId) throws SQLException;
/**
* 获取所有士兵
* @return
*/
List<Soldier> findAll() throws SQLException;
}
package com.atguigu.dao.impl;
import com.atguigu.dao.SoldierDao;
import com.atguigu.pojo.Soldier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
// 持久层注解
@Repository
public class SoldierDaoImpl implements SoldierDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void deleteById(Integer soldierId) throws SQLException {
String sql = "delete from t_soldier where soldier_id=?";
jdbcTemplate.update(sql, soldierId);
}
@Override
public void update(Soldier soldier) throws SQLException {
String sql = "update t_soldier set soldier_name=?,t_soldier_weapon=? where soldier_id=?";
jdbcTemplate.update(sql, soldier.getSoldierName(),soldier.getSoldierWeapon(),soldier.getSoldierId());
}
@Override
public void add(Soldier soldier) throws SQLException {
String sql = "insert into t_soldier(soldier_name,soldier_weapon) values(?,?)";
jdbcTemplate.update(sql, soldier.getSoldierName(), soldier.getSoldierWeapon());
}
@Override
public Soldier getById(Integer soldierId) throws SQLException {
String sql = "select soldier_id soldierId,soldier_name soldierName,soldier_weapon soldierWeapon from t_soldier where soldier_id=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Soldier.class), soldierId);
}
@Override
public List<Soldier> findAll() throws SQLException {
String sql = "select soldier_id soldierId,soldier_name soldierName,soldier_weapon soldierWeapon from t_soldier";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Soldier.class));
}
}
<context:component-scan base-package="com.atguigu">context:component-scan>
package com.atguigu;
import com.atguigu.dao.SoldierDao;
import com.atguigu.dao.impl.SoldierDaoImpl;
import com.atguigu.pojo.Soldier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.SQLException;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class JDBCTest {
@Autowired
private SoldierDao soldierDao;
@Test
public void testFindAll() throws SQLException {
List<Soldier> all = soldierDao.findAll();
for (Soldier soldier : all) {
System.out.println(soldier);
}
}
@Test
public void testFindOne() throws SQLException {
System.out.println(soldierDao.getById(1));
}
}

事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
//将连接的autoCommit还原成true
conn.setAutoCommit(true);
// 释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
所以,我们可以总结下面两个概念:
Spring中的声明式事务是通过事务管理器来进行事务管理的,所以在Spring中定义了事务管理器的顶级接口,针对各种不同的持久层框架,又定义了不同的事务管理器类来进行事务管理
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager 接口本身没有变化,它继承了 TransactionManager
public interface TransactionManager {
}
TransactionManager接口中什么都没有,但是它还是有存在的意义——定义一个技术体系。

我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
DataSourceTransactionManager类中的主要方法:
如果持久层使用Hibernate框架的话,则需要使用HibernateTransactionManager
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.atguigugroupId>
<artifactId>day50spring-transcationartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.31version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
<scope>providedscope>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
dependencies>
project>
datasource.url=jdbc:mysql://localhost:3306/mybatis2?characterEncoding=utf8&serverTimezone=UTC
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.username=root
datasource.password=123456
<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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.atguigu">context:component-scan>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${datasource.username}">property>
<property name="password" value="${datasource.password}">property>
<property name="url" value="${datasource.url}">property>
<property name="driverClassName" value="${datasource.driver}">property>
bean>
beans>
CREATE TABLE t_account(
account_id INT PRIMARY KEY AUTO_INCREMENT,
account_name VARCHAR(20),
money DOUBLE
);
INSERT INTO t_account VALUES (NULL,'zs',1000);
INSERT INTO t_account VALUES (NULL,'ls',1000);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Integer accountId;
private String accountName;
private Double money;
}
package com.atguigu.dao;
public interface AccountDao {
/**
* 修改用户的金额
* @param id
* @param money
*/
void updateAccountMoney(Integer id, Integer money);
}
package com.atguigu.dao.impl;
import com.atguigu.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplatel;
@Override
public void updateAccountMoney(Integer id, Integer money) {
String sql = "update t_account set money=money+? where account_id=?";
jdbcTemplatel.update(sql, money, id);
}
}
package com.atguigu.serivce;
public interface AccountService {
/**
* 转账方法
* @param fromId 转出账户的id
* @param toId 转入账户的id
* @param money 转账金额
*/
void transfer(Integer fromId,Integer toId,Integer money);
}
package com.atguigu.serivce.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
package com.atguigu.controller;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class AccountController {
@Autowired
private AccountService accountService;
public void transfer(Integer fromId,Integer toId,Integer money){
accountService.transfer(fromId, toId, money);
// 转账成功
System.out.println("转账成功!!!");
}
}
package com.atguigu;
import com.atguigu.controller.AccountController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {
@Autowired
private AccountController accountController;
@Test
public void testTransfer(){
accountController.transfer(1, 2, 500);
}
}
因为没有添加事务,所以报错后并没有进行回滚


在spring的配置文件中配置事务管理器对象
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
在spring的配置文件中开启基于注解的声明式事务功能
<tx:annotation-driven/>
注意:导入名称空间时有好几个重复的,我们需要的是 tx 结尾的那个。

package com.atguigu.serivce.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
package com.atguigu;
import com.atguigu.controller.AccountController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {
@Autowired
private AccountController accountController;
@Test
public void testTransfer(){
accountController.transfer(1, 2, 500);
}
}

<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
文件名:logbcak.xml
<configuration debug="true">
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%npattern>
encoder>
appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
root>
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG"/>
configuration>

需要查看的类:org.springframework.jdbc.datasource.DataSourceTransactionManager



对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。但是如果你的方法中执行写操作,那么就会报错
@Transactional(readOnly = true)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
会抛出下面异常
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
如果一个类中每一个方法上都使用了@Transactional注解,那么就可以将@Transactional注解提取到类上。反过来说:@Transactional注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的@Transactional注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了@Transactional注解。
对一个方法来说,离它最近的@Transactional注解中的事务属性设置生效。
在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了。因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。
然后在这个基础上,对增删改方法设置@Transactional注解 readOnly 属性为 false。
package com.atguigu.serivce.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* read-only属性表示事务是否只读:默认值是false,如果设置为true 那么当前事务中只能做数据库的读操作,不能做写操作
* 该属性的作用:可以对只读的数据库操作做一些优化
*/
@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(readOnly = false)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
PS:Spring 环境下很多场合都有类似设定,一个注解如果标记了类的每一个方法那么通常就可以提取到类级别。但是,如果不是类中的所有方法都需要用到事务,则绝不允许将@Transaction注解放在类上
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 模拟超时
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Nov 17 09:55:44 CST 2022
默认只针对运行时异常回滚,编译时异常不回滚。情景模拟代码如下:
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) throws ClassNotFoundException {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 什么是运行时异常:不需要在编译时处理的异常
// 什么是编译时异常:需要在编译时就进行处理的异常
// 默认情况时遇到运行时异常才回滚
Class.forName("1eu1j1");
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
运行后抛出异常并没有事务回滚


@Transactional(rollbackFor = Exception.class)
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
@Transactional(
noRollbackFor = FileNotFoundException.class
)
不管是哪个设置范围大,都是在大范围内在排除小范围的设定。例如:
意思是除了 FileNotFoundException 之外,其他所有 Exception 范围的异常都回滚;但是碰到 FileNotFoundException 不回滚。
此时 Spring 采纳了 rollbackFor 属性的设定:遇到 FileNotFoundException 异常会回滚。
| 级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
|---|---|---|---|---|---|---|
| 1 | 读未提交 | read uncommitted | 是 | 是 | 是 | |
| 2 | 读已提交 | read committed | 否 | 是 | 是 | Oracle |
| 3 | 可重复读 | repeatable read | 否 | 否 | 是 | MySQL |
| 4 | 串行化 | serializable | 否 | 否 | 否 | 最高的隔离级别 |
在 @Transactional 注解中使用 isolation 属性设置事务的隔离级别。 取值使用 org.springframework.transaction.annotation.Isolation 枚举类提供的数值。
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
isolation :
(1). READ_UNCOMMITTED : 读未提交,在这种隔离级别下脏读、不可重复读、幻读都可能发生
(2). READ_COMMITTED : 读已提交,Oracle数据库的默认隔离级别,在这种隔离级别下不可能发生脏读
(3). REPEATABLE_READ : 可重复读,MySQL数据库的默认隔离级别,在这种隔离级别下不可能发生脏读、不可重复读; 这种隔离级别其实是使用了行锁
(4). SERIALIZABLE : 串行化,在这种隔离级别下不可能发生脏读、不可重复读、幻读; 这种隔离级别使用表锁事务并行性问题:
- 脏读: 一个事务读取到了另一个事务未提交的数据,并且另一个事务最终没有提交
- 不可重复读: 一个事务中多次读取到的数据的内容不一致(原因是当前事务执行的时候,其它的事务使用UPDATE操作修改了数据)
- 幻读: 一个事务中多次读取到的数据的行数不一致(原因是当前事务执行的时候,其它事务使用了INSERT、DELETE操作新增、删除了记录)
事务的传播行为要研究的是是当两个方法嵌套执行的时候,外层方法的事务能否传播到内层方法以及怎么传播到内层方法
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:
Propagation propagation() default Propagation.REQUIRED;
propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:
| 名称 | 含义 |
|---|---|
| REQUIRES_NEW | 如果当前存在事务(外层方法开启了事务),那么我就先将当前事务暂停,然后我自己新建事务执行,我的事务执行完之后再恢复当前事务,如果当前不存在事务(外层方法没有开启事务),那么我就新建事务执行 |
| REQUIRED 默认值 | 如果当前存在事务(外层方法开启了事务),那么我就加入外层方法的事务一起执行,如果当前不存在事务(外层方法没有开启事务),那么我就自己新建事务执行 |
| SUPPORTS | 如果当前存在事务(外层方法开启了事务),那么我就加入到外层方法的事务中一起执行,如果当前不存在事务(外层方法没有开启事务),那么我自己就以非事务方式运行 |
| MANDATORY | 如果当前存在事务(外层方法开启了事务),那么我就加入到外层方法的事务中一起执行,如果当前不存在事务(外层方法没有开启事务),那么我就报错(也可以理解为强制外层方法必须有事务) |
| NOT_SUPPORTED | 如果当前存在事务(外层方法开启了事务),那么我就先将当前事务暂停,我自己以非事务方式执行,我执行完之后再恢复当前事务, 如果外层方法不存在事务(外层方法没有开启事务),那么我就以非事务方式执行 |
| NEVER | 如果当前存在事务(外层方法开启了事务),那么我就直接报错, 如果当前不存在事务(外层方法没有开启事务),那么我就以非事务方式执行 |
package com.atguigu.service;
public interface OuterService {
void outerMethod();
}
package com.atguigu.service.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.pojo.Account;
import com.atguigu.service.AccountService;
import com.atguigu.service.OuterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OuterServiceImpl implements OuterService {
@Autowired
private AccountService accountService;
@Autowired
private AccountDao accountDao;
@Transactional
@Override
public void outerMethod() {
// 1.新增一条数据
accountDao.add(new Account(3, "ww", 1000));
// 2.调用AccountService的转账方法
accountService.transfer(1, 2, 500);
// int i = 10/ 0;
// 3.修改id为3的用户的金额,给其加600
accountDao.updateAccountMoney(3, 600);
}
}
package com.atguigu.serivce.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* read-only属性表示事务是否只读:默认值是false,如果设置为true 那么当前事务中只能做数据库的读操作,不能做写操作
* 该属性的作用:可以对只读的数据库操作做一些优化
*/
@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3,
propagation = Propagation.REQUIRED)
@Override
public void transfer(Integer fromId, Integer toId, Integer money) throws ClassNotFoundException {
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
int num = 10 / 0;
// 什么是运行时异常:不需要在编译时处理的异常
// 什么是编译时异常:需要在编译时就进行处理的异常
// 默认情况时遇到运行时异常才回滚
Class.forName("1eu1j1");
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
package com.atguigu;
import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {
@Autowired
private AccountController accountController;
@Autowired
private OuterService outerService;
@Test
public void testTransfer() throws ClassNotFoundException {
accountController.transfer(1, 2, 500);
}
@Test
public void testPropagation(){
outerService.outerMethod();
}
}

效果:内层方法A、内层方法B所作的修改没有生效,总事务回滚了
内部方法报错 总事物回滚
新增的数据也进行回滚了,可以通过日志进行查看

修改AccountServiceImpl内层方法
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3,
propagation = Propagation.REQUIRES_NEW)
@Override
public void transfer(Integer fromId, Integer toId, Integer money){
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
效果:内层方法报错了,内层和外层的事务都回滚了


如果内层方法没报错,外层方法报错了那么内层事物会提交 外层事务回滚
内层方法代码
@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// timeout 单位:秒 超过指定的时间没响应 则抛出异常
@Transactional(readOnly = false,
timeout = 3,
propagation = Propagation.REQUIRES_NEW)
@Override
public void transfer(Integer fromId, Integer toId, Integer money){
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
// int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
外层方法代码
package com.atguigu.serivce.impl;
import com.atguigu.dao.AccountDao;
import com.atguigu.pojo.Account;
import com.atguigu.serivce.AccountService;
import com.atguigu.serivce.OuterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OuterServiceImpl implements OuterService {
@Autowired
private AccountService accountService;
@Autowired
private AccountDao accountDao;
@Transactional
@Override
public void outerMethod() {
// 1.新增一条数据
accountDao.add(new Account(3, "ww", 1000));
// 2.调用AccountService的转账方法
accountService.transfer(1, 2, 500);
int num = 10 / 0;
// 3.修改id为3的用户的金额,给其加600
accountDao.updateAccountMoney(3, 600);
}
}
测试效果 内部方法执行的转账成功了


修改内层方法为SUPPORTS模式
@Transactional(readOnly = false,
timeout = 3,
propagation = Propagation.SUPPORTS)
@Override
public void transfer(Integer fromId, Integer toId, Integer money){
// 1.转出账户扣款
accountDao.updateAccountMoney(fromId, -money);
// 测试报错是否会回滚
int num = 10 / 0;
// 2.转入账户收款
accountDao.updateAccountMoney(toId, money);
}
}
执行测试方法 外层方法有事务 会加入到外层事务中 报错后事务会回滚


外层方法去掉事务后 再进行测试 内部方法报错后 并没有进行回滚(以非事务方式执行)


propagation : 事务的传播性,它是研究方法嵌套执行的时候,内层方法是否会共用外层方法的事务
事务传播性的取值:
(1). REQUIRED : 如果当前存在事务,则加入到当前事务中一起执行,如果当前不存在事务则新建事务执行
(2). SUPPORTS : 如果当前存在事务,则加入当前事务中一起执行,如果当前不存在事务则以非事务方式执行,一般用于只读事务
(3). MANDATORY : 如果当前存在事务,则加入当前事务中一起执行,如果当前不存在事务则报错
(4). REQUIRES_NEW : 新创建事务执行,如果外层方法有事务先将外层方法的事务暂停,等内层方法事务执行完毕之后再恢复外层方法的事务
(5). NOT_SUPPORTED : 以非事务方式执行,如果外层方法有事务则先将外层方法事务暂停,等我执行完之后再恢复外层方法的事务
(6). NEVER : 以非事务方式执行,如果外层方法有事务则报错事务传播性总结:
(1). 如果一个方法里面只有读操作,一般情况下会设置其事务传播性为SUPPORTS,并且可以设置readOnly为true
(2). 如果一个方法里面有写操作,一般情况下会设置其事务的传播性为REQUIRED
(3). 如果一个方法里面有写操作,并且当前方法执行没有异常就一定要提交事务(不受外层方法影响),此时使用REQUIRES_NEW
相比于基于注解的声明式事务,基于 XML 的声明式事务需要一个额外的依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
将上一个基于注解的 module 中的代码转移到新module。去掉 @Transactional 注解。
去掉 tx:annotation-driven 标签,然后加入下面的配置:
<tx:advice id="adv1">
<tx:attributes>
<tx:method name="transfer" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt1" expression="execution(public void com.atguigu.service.impl.AccountServiceImpl.transfer(Integer,Integer,Double))"/>
<aop:advisor advice-ref="adv1" pointcut-ref="pt1"/>
aop:config>
即使需要事务功能的目标方法已经被切入点表达式涵盖到了,但是如果没有给它配置事务属性,那么这个方法就还是没有事务。所以事务属性必须配置。
JCP(Java Community Process) 是一个由SUN公司发起的,开放的国际组织。主要由Java开发者以及被授权者组成,负责Java技术规范维护,Java技术发展和更新。
JCP官网地址:https://jcp.org/en/home/index
JSR 的全称是:Java Specification Request,意思是 Java 规范提案。谁向谁提案呢?任何人都可以向 JCP (Java Community Process) 提出新增一个标准化技术规范的正式请求。JSR已成为Java界的一个重要标准。登录 JCP 官网可以查看所有 JSR 标准。
JSR 305: Annotations for Software Defect Detection(提供了一系列的软件缺陷检测(数据校验)的注解)
This JSR will work to develop standard annotations (such as @NonNull) that can be applied to Java programs to assist tools that detect software defects.
主要功能:使用注解(例如@NonNull等等)协助开发者侦测软件缺陷。
Spring 从 5.0 版本开始支持了 JSR 305 规范中涉及到的相关注解。
package org.springframework.lang;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierNickname;
/**
* A common Spring annotation to declare that annotated elements cannot be {@code null}.
*
* Leverages JSR-305 meta-annotations to indicate nullability in Java to common
* tools with JSR-305 support and used by Kotlin to infer nullability of Spring API.
*
*
Should be used at parameter, return value, and field level. Method overrides should
* repeat parent {@code @NonNull} annotations unless they behave differently.
*
*
Use {@code @NonNullApi} (scope = parameters + return values) and/or {@code @NonNullFields}
* (scope = fields) to set the default behavior to non-nullable in order to avoid annotating
* your whole codebase with {@code @NonNull}.
*
* @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 5.0
* @see NonNullApi
* @see NonNullFields
* @see Nullable
*/
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Nonnull
@TypeQualifierNickname
public @interface NonNull {
}
| 注解名称 | 含义 | 可标记位置 |
|---|---|---|
| @Nullable | 可以为空 | @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) |
| @NonNull | 不应为空 | @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) |
| @NonNullFields | 在特定包下的字段不应为空 | @Target(ElementType.PACKAGE) @TypeQualifierDefault(ElementType.FIELD) |
| @NonNullApi | 参数和方法返回值不应为空 | @Target(ElementType.PACKAGE) @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) |
在原有环境基础上增加依赖 替换junit的依赖
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.7.0version>
<scope>testscope>
dependency>
package com.atguigu;
import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(value = "classpath:spring.xml")
public class TestTransaction {
@Autowired
private AccountController accountController;
@Autowired
private OuterService outerService;
@Test
public void testTransfer() throws ClassNotFoundException {
accountController.transfer(1, 2, 500);
}
@Test
public void testPropagation(){
outerService.outerMethod();
}
}
@SpringJUnitConfig 注解综合了前面两个注解的功能,此时指定 Spring 配置文件位置即可。但是注意此时需要使用 locations 属性,不是 value 属性了。
package com.atguigu;
import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:spring.xml")
public class TestTransaction {
@Autowired
private AccountController accountController;
@Autowired
private OuterService outerService;
@Test
public void testTransfer() throws ClassNotFoundException {
accountController.transfer(1, 2, 500);
}
@Test
public void testPropagation(){
outerService.outerMethod();
}
}