文章目录
事务
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/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">
<!--外部属性文件-->
<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 {
@Autowired
private JdbcTemplate jdbcTemplate;
//添加 修改 删除
//这三个操作对数据项都使用JdbcTemplate的update方法
@Test
public void insertTest(){
//1 sql语句
String sql1 = "INSERT INTO t_emp VALUE(NULL,?,?,?)";
//2 调用JdbcTemplate方法,传参
//int row = jdbcTemplate.update(sql, "Max", 19, "f");
// or
Object[] params = {"Fraud", 19, "f"};
int row = jdbcTemplate.update(sql1,params);
System.out.println(row);
}
@Test
public 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);
}
@Test
public 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 {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer getPrice(Integer bookId) {
String sql = "select price from t_book where book_id = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
return price;
}
@Override
public void stockChange(Integer bookId) {
String sql = "update t_book set stock = stock - 1 where book_id = ?";
jdbcTemplate.update(sql, bookId);
}
@Override
public 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 {
@Autowired
private BookDao bookDao;
@Override
public void buyBook(Integer bookId, Integer userId) {
bookDao.getPrice(bookId);
bookDao.stockChange(bookId);
bookDao.balanceChange(userId, bookId);
}
}
BookController
@Controller
public class BookController {
@Autowired
private BookService bookService;
public void userBuyBook(Integer bookId, Integer userId) {
bookService.buyBook(bookId, userId);
}
}
BuyTest
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class BuyTest {
@Autowired
private BookController bookController;
@Test
public 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();
}
@Transactional
public void b(){}
}
解决方法:
- 创建一个另外的Service
@Service
public class UserServiceA {
public void a(){
b();
}
}
@Service
public class UserServiceB {
@Transactional
public void b(){}
}
- 在类内注入自身
@Service
public class UserService {
@Autowired
private UserService userService;
public void a(){
userService.b();
}
@Transactional
public void b(){}
}
- 通过AopContent类
@Service
public class UserService {
public void a(){
((UserService)AopContent.currentProxy()).b();
}
@Transactional
public void b(){}
}
未被Spring管理
忘记注解等操作致使Spring未管理到对象,未创建Bean实例
//失效
public class UserService {
@Transactional
public void add(){}
}
多线程调用
Spring的事务通过数据库连接来实现,当两个方法不在相同线程中时,获取到的数据库连接不同,当前线程会各自保存不同的map(key 是数据源, value 是数据库连接)。因此一个方法中抛出异常,另外一个方法也回滚是无法做到的,只有同一个数据库连接才可以同时提交和回滚。
吞异常
开发者自己手动捕获了异常,但又没有将其抛出,意味着这个事务即使异常也不会回滚,异常被吞掉了,Spring将认为该程序是正常的。
@Service
public class UserService {
@Transactional
public void add() {
try {
a();
b();
} catch (Exception e) {}
}
}
抛出别的异常
开发者抛出异常但其种类不正确,Spring默认只会回滚RuntimeException
和Error
,对于普通Exception
不会执行回滚。
@Service
public class UserService {
@Transactional
public void add(...) throws Exception {
try {...} catch (Exception e) {
throw new Exception(e);
}
}
}
自定义回滚异常
通过事务属性rollbackFor
(及其同类)自定义后,可能导致程序报错抛出异常,但非开发者自定义的异常,因此导致事务不会回滚。
即使rollbackFor
有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。因为如果使用默认值,一旦程序抛出了Exception
,事务不会回滚。所以,建议一般情况下,建议将该参数设置成Exception
或Throwable
。
嵌套事务回滚过多
当嵌套了内部事务时,若原本希望出现异常时只回滚 b 方法的内容,不回滚外部内容,但事实上全部都回滚了。因为异常会持续上抛,知道被捕获或到达最外层。
public class UserService {
@Autowired
private RoleService roleService;
@Transactional
public void a() {
roleService.b();
}
}
@Service
public class RoleService {
@Transactional
public void b() {...}
}
可将内部嵌套事务放在try...catch...
中手动捕获异常,使其不继续上抛,达成只回滚内部事务,不影响外部。
public class UserService {
@Autowired
private RoleService roleService;
@Transactional
public void a() throws Exception {
try {
roleService.b();
} catch (Exception) {}
}
}
@Service
public class RoleService {
@Transactional
public void b() {...}
}