文章目录
- 事务
- JdbcTemplate
- 准备
- 声明式事务概念
- 概念
- 代码式事务
- 声明式事务
- 基于注解的声明式事务
- 准备
- 案例
- 加入事务
- 事务属性
- 失效场景
- 访问权限
- final, static修饰
- 方法内部调用
- 未被Spring管理
- 多线程调用
- 吞异常
- 抛出别的异常
- 自定义回滚异常
- 嵌套事务回滚过多
事务
JdbcTemplate
Spring框架对JDBC进行了封装,使用JdbcTemplate易于实现对数据库的操作
准备
- 依赖
<!--spring jdbc-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.2</version>
</dependency>
<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version>
</dependency>
<!--数据源-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version>
</dependency>
- jdbc.properties
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.driver=com.mysql.cj.jdbc.Driver
- 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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--外部属性文件--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><!--配置数据源--><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><!--配置jdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!--装配数据源--><property name="dataSource" ref="druidDataSource"></property></bean></beans>
- 配置数据库
- 增删改操作
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;//添加 修改 删除//这三个操作对数据项都使用JdbcTemplate的update方法@Testpublic void insertTest(){//1 sql语句String sql1 = "INSERT INTO t_emp VALUE(NULL,?,?,?)";//2 调用JdbcTemplate方法,传参//int row = jdbcTemplate.update(sql, "Max", 19, "f");// orObject[] params = {"Fraud", 19, "f"};int row = jdbcTemplate.update(sql1,params);System.out.println(row);}@Testpublic void updateTest(){String sql2 = "UPDATE t_emp SET SEX = ? WHERE ID = ?";Object[] params = {"m", 3};int row = jdbcTemplate.update(sql2,params);System.out.println(row);}@Testpublic void deleteTest(){String sql3 = "delete from t_emp where id = ?";int row = jdbcTemplate.update(sql3,3);System.out.println(row);}
}
- 查询操作
//查询:返回对象
//queryForObject---获取一个对象
@Test
public void selectObject() {//1 自行封装,使用lamba//String sql = "select * from t_emp where id = ? ";//Emp empresult = jdbcTemplate.queryForObject(sql,// (rs, rowNum) ->{// Emp emp = new Emp();// emp.setId(rs.getInt( "id"));// emp.setName(rs.getString( "name"));// emp.setAge(rs.getInt( "age"));// emp.setSex(rs.getString("sex"));// }, 1);//System.out.println(empresult);*///2 使用封装类String sql = "select * from t_emp where id = ?";Emp emp = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(Emp.class), 1);System.out.println(emp);
}
//查询:返回list集合
//query---获取一系列对象,存于List中
@Test
public void selectList() {String sql = "select * from t_emp";List<Emp> empList = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Emp.class));System.out.println(empList);
}
//查询:返回单值
@Test
public void selectElement() {String sql = "select count(*) from t_emp";Integer count = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);
}
声明式事务概念
概念
- 事务(transaction)是访问并可能操作各种数据想的一个操作序列,要么全部执行要么全部不执行,作为不可分割的工作单位;
事务由事务开始与事务结束之间执行的全部数据库操作组成; - 特性
原子性:要么全部执行要么全部不执行,执行中发生错误则全部回滚;
一致性:事务执行前与执行后数据库处于一致性状态,事务成功则系统正确应用变化,处于有效状态;事务错误则回滚所有变化,回到原始状态;
隔离性:不同事务操纵相同数据时,各自拥有独立完整数据空间,并发事务之间的修改必须相互隔离。事务查看数据时,数据只会处于其余事务修改它之前,或完成修改之后的状态,不会处于变化过程中;
持久性:事务成功结束后,其对数据库产生的变化必须保存,即使系统崩溃,在重启后也能恢复到事务成功时的状态;
代码式事务
避免由于Spring AOP导致的事务失效问题,能够更小粒度地控制事务范围,且更加直观;
全部由代码完成,自行控制事务,细节无法屏蔽,繁琐,且代码复用性低;
声明式事务
抽取固定模式的代码进行封装,提高开发效率,减少冗余代码,由框架考虑各种问题,优化健壮性及性能等;
基于注解的声明式事务
准备
- 配置文件
<!--扫描组件--><context:component-scan base-package="com.jobs.spring6.tx"></context:component-scan>
- 建表
- 接口
案例
BookDao
public interface BookDao {Integer getPrice(Integer bookId);void stockChange(Integer bookId);void balanceChange(Integer userId, Integer bookId);
}
BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic Integer getPrice(Integer bookId) {String sql = "select price from t_book where book_id = ?";Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);return price;}@Overridepublic void stockChange(Integer bookId) {String sql = "update t_book set stock = stock - 1 where book_id = ?";jdbcTemplate.update(sql, bookId);}@Overridepublic void balanceChange(Integer userId, Integer bookId) {String sql = "update t_user set balance = balance - ? where user_id = ?";jdbcTemplate.update(sql, getPrice(bookId), userId);}
}
BookService
public interface BookService {void buyBook(Integer bookId, Integer userId);
}
BookServiceImpl
@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {bookDao.getPrice(bookId);bookDao.stockChange(bookId);bookDao.balanceChange(userId, bookId);}
}
BookController
@Controller
public class BookController {@Autowiredprivate BookService bookService;public void userBuyBook(Integer bookId, Integer userId) {bookService.buyBook(bookId, userId);}
}
BuyTest
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class BuyTest {@Autowiredprivate BookController bookController;@Testpublic void buyBookTest(){bookController.userBuyBook(2, 1);}
}
加入事务
- 配置文件
xmlns:tx="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="name" ref="druidDataSource"></property></bean><!--transaction-manager属性的默认值为transactionManager若事务管理器Bean的id为此值,者可忽略此属性--><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 因为
Service
层标识业务逻辑,一个方法代表一个完成的功能,因此处理事务一般在Service
层处理 - 通过注解@Transactional标识的方法(该方法单独)或其类(其中所有方法),都会被事务管理器管理;
@Transactional
@Service
public class BookServiceImpl implements BookService {...}
// or
@Transactional
@Override
public void buyBook(Integer bookId, Integer userId) {...}
事务属性
- 只读—readOnly = true
该事务操作不涉及写,则可以针对查询操作进行优化; - 超时—timeout = ‘num’
num = -1:永不超时
num > 0:num秒内执行,超时则抛出异常
- 回滚策略
rollbackFor/rollbackForClassName:对指定异常种类进行回滚
noRollbackFor/noRollbackForClassName:对指定异常种类不进行回滚
- 隔离级别—isolation
事务之间相互隔离并发运行的手段,隔离级别越高,数据一致性越好,但并发性越弱;
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交:READ UNCOMMITTED | 有 | 有 | 有 |
读已提交:READ COMMITTED | 无 | 有 | 有 |
可重复读:REPEATABLE READ | 无 | 无 | 有 |
串行化:SERIALIZABLE | 无 | 无 | 无 |
- 传播行为—propagation
例如a方法和b方法,其上都存在事务,当a方法执行时需要调用b方法,那么事务按照传播行为所规定的进行变化;
类型 | 行为 |
---|---|
REQUIRED | 没有事务就新建事务,存在事务则加入 |
SUPPORTS | 存在事务则加入,不存在就不进行事务 |
MANDATORY | 存在事务则加入,不存在就抛出异常 |
REQUIRES_NEW | 不在乎之前是否存在事务,直接开启新事务,并将之前事务挂起(存在的话) |
NOT_SUPPORTED | 不支持事务,存在则挂起 |
NEVER | 不支持事务,存在则抛出异常 |
NESTED | 存在事务则在其中嵌套一个新的独立事务,新事务可以独立提交或回滚,若不存在事务则同REQUIRED |
失效场景
访问权限
Spring事务只支持public方法,其余访问权限会导致事务失效;
//失效
@Transactional
private void add(){}
final, static修饰
Spring事务底层使用了AOP,即会需求生成代理类(jdk动态或cglib),使用上述修饰符会导致代理类无法重写,进而无法实现事务;
//失效
@Transactional
public final/static void add(){}
方法内部调用
相同类内部 a 方法调用事务方法 b 方法,不会生成事务,因为内部this
调用方法无法让AOP生成代理。
//失效
@Service
public class UserService {public void a(){b();}@Transactionalpublic void b(){}
}
解决方法:
- 创建一个另外的Service
@Service
public class UserServiceA {public void a(){b();}
}
@Service
public class UserServiceB {@Transactionalpublic void b(){}
}
- 在类内注入自身
@Service
public class UserService {@Autowiredprivate UserService userService;public void a(){userService.b();}@Transactionalpublic void b(){}
}
- 通过AopContent类
@Service
public class UserService {public void a(){((UserService)AopContent.currentProxy()).b();}@Transactionalpublic void b(){}
}
未被Spring管理
忘记注解等操作致使Spring未管理到对象,未创建Bean实例
//失效
public class UserService {@Transactionalpublic void add(){}
}
多线程调用
Spring的事务通过数据库连接来实现,当两个方法不在相同线程中时,获取到的数据库连接不同,当前线程会各自保存不同的map(key 是数据源, value 是数据库连接)。因此一个方法中抛出异常,另外一个方法也回滚是无法做到的,只有同一个数据库连接才可以同时提交和回滚。
吞异常
开发者自己手动捕获了异常,但又没有将其抛出,意味着这个事务即使异常也不会回滚,异常被吞掉了,Spring将认为该程序是正常的。
@Service
public class UserService { @Transactionalpublic void add() {try {a();b();} catch (Exception e) {}}
}
抛出别的异常
开发者抛出异常但其种类不正确,Spring默认只会回滚RuntimeException
和Error
,对于普通Exception
不会执行回滚。
@Service
public class UserService {@Transactionalpublic void add(...) throws Exception {try {...} catch (Exception e) {throw new Exception(e);}}
}
自定义回滚异常
通过事务属性rollbackFor
(及其同类)自定义后,可能导致程序报错抛出异常,但非开发者自定义的异常,因此导致事务不会回滚。
即使rollbackFor
有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。因为如果使用默认值,一旦程序抛出了Exception
,事务不会回滚。所以,建议一般情况下,建议将该参数设置成Exception
或Throwable
。
嵌套事务回滚过多
当嵌套了内部事务时,若原本希望出现异常时只回滚 b 方法的内容,不回滚外部内容,但事实上全部都回滚了。因为异常会持续上抛,知道被捕获或到达最外层。
public class UserService { @Autowiredprivate RoleService roleService; @Transactionalpublic void a() {roleService.b();}
}@Service
public class RoleService { @Transactionalpublic void b() {...}
}
可将内部嵌套事务放在try...catch...
中手动捕获异常,使其不继续上抛,达成只回滚内部事务,不影响外部。
public class UserService { @Autowiredprivate RoleService roleService; @Transactionalpublic void a() throws Exception {try {roleService.b();} catch (Exception) {}}
}@Service
public class RoleService { @Transactionalpublic void b() {...}
}