Spring学习(五)事务

事务

JdbcTemplate

Spring框架对JDBC进行了封装,使用JdbcTemplate易于实现对数据库的操作

准备

  1. 依赖
<!--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>
  1. jdbc.properties
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.driver=com.mysql.cj.jdbc.Driver
  1. 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>
  1. 配置数据库
    在这里插入图片描述
  2. 增删改操作
@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);
    }
}
  1. 查询操作
//查询:返回对象
//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);
}

声明式事务概念

概念

  1. 事务(transaction)是访问并可能操作各种数据想的一个操作序列,要么全部执行要么全部不执行,作为不可分割的工作单位;
    事务由事务开始与事务结束之间执行的全部数据库操作组成;
  2. 特性

原子性:要么全部执行要么全部不执行,执行中发生错误则全部回滚;
一致性:事务执行前与执行后数据库处于一致性状态,事务成功则系统正确应用变化,处于有效状态;事务错误则回滚所有变化,回到原始状态;
隔离性:不同事务操纵相同数据时,各自拥有独立完整数据空间,并发事务之间的修改必须相互隔离。事务查看数据时,数据只会处于其余事务修改它之前,或完成修改之后的状态,不会处于变化过程中;
持久性:事务成功结束后,其对数据库产生的变化必须保存,即使系统崩溃,在重启后也能恢复到事务成功时的状态;

代码式事务

避免由于Spring AOP导致的事务失效问题,能够更小粒度地控制事务范围,且更加直观;
全部由代码完成,自行控制事务,细节无法屏蔽,繁琐,且代码复用性低;

声明式事务

抽取固定模式的代码进行封装,提高开发效率,减少冗余代码,由框架考虑各种问题,优化健壮性及性能等;

基于注解的声明式事务

准备

  1. 配置文件
 <!--扫描组件-->
 <context:component-scan base-package="com.jobs.spring6.tx"></context:component-scan>
  1. 建表
    在这里插入图片描述
  2. 接口
    在这里插入图片描述

案例

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);
    }
}

加入事务

  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>
  1. 因为Service层标识业务逻辑,一个方法代表一个完成的功能,因此处理事务一般在Service层处理
  2. 通过注解@Transactional标识的方法(该方法单独)或其类(其中所有方法),都会被事务管理器管理;
@Transactional
@Service
public class BookServiceImpl implements BookService {...}
// or
@Transactional
@Override
public void buyBook(Integer bookId, Integer userId) {...}

事务属性

  1. 只读—readOnly = true
    该事务操作不涉及写,则可以针对查询操作进行优化;
  2. 超时—timeout = ‘num’

num = -1:永不超时
num > 0:num秒内执行,超时则抛出异常

  1. 回滚策略

rollbackFor/rollbackForClassName:对指定异常种类进行回滚
noRollbackFor/noRollbackForClassName:对指定异常种类不进行回滚

  1. 隔离级别—isolation
    事务之间相互隔离并发运行的手段,隔离级别越高,数据一致性越好,但并发性越弱;
隔离级别脏读不可重复读幻读
读未提交:READ UNCOMMITTED
读已提交:READ COMMITTED
可重复读:REPEATABLE READ
串行化:SERIALIZABLE
  1. 传播行为—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(){}
}

解决方法:

  1. 创建一个另外的Service
@Service
public class UserServiceA {
	public void a(){
		b();
	}
}
@Service
public class UserServiceB {
	@Transactional
	public void b(){}
}
  1. 在类内注入自身
@Service
public class UserService {
	@Autowired
	private UserService userService;
	public void a(){
		userService.b();
	}
	@Transactional
	public void b(){}
}
  1. 通过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默认只会回滚RuntimeExceptionError,对于普通Exception不会执行回滚。

@Service
public class UserService {
    @Transactional
    public void add(...) throws Exception {
        try {...} catch (Exception e) {
            throw new Exception(e);
        }
    }
}

自定义回滚异常

通过事务属性rollbackFor(及其同类)自定义后,可能导致程序报错抛出异常,但非开发者自定义的异常,因此导致事务不会回滚。
即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚。所以,建议一般情况下,建议将该参数设置成ExceptionThrowable

嵌套事务回滚过多

当嵌套了内部事务时,若原本希望出现异常时只回滚 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() {...}
}
  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值