Spring(下)

101 阅读9分钟

第5章 JdbcTemplate

5.1 概述

为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。

作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。

可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。

5.2 环境搭建

5.2.1 导入jar包

  1. Spring的核心包

spring-beans-5.3.1.jar

spring-context-5.3.1.jar

spring-core-5.3.1.jar

spring-expression-5.3.1.jar

spring-aop-5.3.1.jar

  1. JdbcTemplate需要的jar包

spring-jdbc-5.3.1.jar

spring-orm-5.3.1.jar

spring-tx-5.3.1.jar

  1. 连接数据库的驱动jar包和数据源

mysql-connector-java-5.1.37-bin.jar

druid-1.1.10.jar

  1. 单元测试的jar包

junit-4.12.jar

hamcrest-core-1.3.jar

<!--spring-context-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
</dependency>
<!--spring-jdbc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.1</version>
</dependency>
<!--spring-orm-->
<dependency>
    <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
    <version>5.3.1</version>
</dependency>
<!--导入druid的jar包-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<!--导入mysql的jar包-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>
<!--junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
​
​

5.2.2 创建数据库和表

在MySQL中执行资料中的ssm.sql文件生成对应的数据库和表。(之前导入过)

5.2.3 创建druid.properties文件

#key=value
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
​
jdbc.initialSize=5
jdbc.maxActive=10

5.2.4 创建Spring的配置文件

<?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 https://www.springframework.org/schema/context/spring-context.xsd">
​
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
​
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>
    
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

5.3 操作数据库

  1. 增删改

JdbcTemplate.update(String, Object...)

public class JdbcTemplateTest {
​
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-jdbc.xml");
    JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
​
    /*
       测试增删改
    */
    @Test
    public void testUpdate(){
            //写sql语句
        String sql = "insert into employee(last_name,email,salary) values(?,?,?)";
        //调用JdbcTemplate中的update方法
        jdbcTemplate.update(sql,"雷军","leijun@xiaomi.xom",9999.00);
​
​
  1. 批量增删改
  • JdbcTemplate.batchUpdate(String, List<Object[]>)
  • Object[]封装了SQL语句每一次执行时所需要的参数
  • List集合封装了SQL语句多次执行时的所有参数
/*
   测试批量增删改
*/
@Test
public void testBatchUpdate(){
    //写sql语句
    String sql = "insert into employee(last_name,email,salary) values(?,?,?)";
    //创建一个List
    List<Object[]> batchArgs = new ArrayList<>();
    Object[] arg1 = new Object[]{"李彦宏","liyanhong@baidu.com",8888.00};
    Object[] arg2 = new Object[]{"刘强东","liuqiangdong@jd.com",7777.00};
    Object[] arg3 = new Object[]{"张一鸣","zhangyiming@douyin.com",6666.00};
    batchArgs.add(arg1);
    batchArgs.add(arg2);
    batchArgs.add(arg3);
    //调用JdbcTemplate中的批处理方法
    jdbcTemplate.batchUpdate(sql,batchArgs);
}
​
  1. 查询单一值

JdbcTemplate.queryForObject(String, Class, Object...)

/*
   测试获取一个单一值
*/
@Test
public void testGetSingonValue(){
    //写sql语句
    String sql = "select avg(salary) from employee";
    //调用JdbcTemplate中的queryForObject方法
    Double avgSalary = jdbcTemplate.queryForObject(sql, Double.class);
    System.out.println(avgSalary);
}
​
​
  1. 查询单行

JdbcTemplate.queryForObject(String, RowMapper, Object...)

image.png

/*
   测试获取一行数据
*/
@Test
public void testGetOne(){
    //写sql语句
    String sql = "select id,last_name lastName,email,salary from employee where id = ?";
    //创建RowMapper对象
    RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
    //调用JdbcTemplate中的queryForObject方法
    Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
    System.out.println(employee);
}
​
  1. 查询多行

JdbcTemplate.query(String, RowMapper, Object...)

RowMapper对象依然使用BeanPropertyRowMapper

 /*
   测试获取多行数据
*/
@Test
public void testGetAll(){
​
 //写sql语句
    String sql = "select id,last_name lastName,email,salary from employee";
    //创建RowMapper对象
    RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
    //调用JdbcTemplate中的query方法
    List<Employee> employees = jdbcTemplate.query(sql, rowMapper);
    //遍历
    for (Employee employee : employees) {
        System.out.println(employee);
    }
}
​

5.4 通过注入JdbcTemplate实现Dao

JdbcTemplate类是线程安全的,所以可以在IOC容器中声明它的单个实例,并将这个实例注入到所有的Dao实例中。

  1. 在Spring的配置文件中添加配置自动扫描的包
<!--配置自动扫描的包-->
<context:component-scan base-package="com.atguigu.spring.jdbc"></context:component-scan>
  1. 创建EmployeeDao及实现类并注入JdbcTemplate
package com.atguigu.spring.jdbc.dao.impl;
​
import com.atguigu.spring.jdbc.bean.Employee;
import com.atguigu.spring.jdbc.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
​
@Repository("employeeDao")
public class EmployeeDaoImpl implements EmployeeDao {
​
    @Autowired
    private JdbcTemplate jdbcTemplate;
​
    @Override
    public Employee getEmployeeById(Integer id) {
            //写sql语句
        String sql = "select id,last_name lastName,email,salary from employee where id = ?";
        //创建RowMapper对象
        RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
        //调用JdbcTemplate中的queryForObject方法
        Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
        return employee;
    }
}
​
​
  1. 测试
/*
   测试EmployeeDao中的的方法
*/
@Test
public void testEmployeeDao(){
    EmployeeDao employeeDao = (EmployeeDao) ioc.getBean("employeeDao");
    Employee employeeById = employeeDao.getEmployeeById(1);
    System.out.println(employeeById);
}
​

第6章 声明式事务

6.1 事务概述

在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。

事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。

事务的四个特性(ACID)

  1. 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
  1. 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
  1. 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。\4) 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

6.2 Spring的事务管理

6.2.1 编程式事务管理

使用原生的JDBC API进行事务管理的步骤:

  1. 获取数据库连接Connection对象
  1. 取消事务的自动提交
  1. 执行操作
  1. 正常完成操作时手动提交事务
  1. 执行失败时回滚事务
  1. 关闭相关资源

评价

使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块 都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。

6.2.2 声明式事务管理

Spring既支持编程式事务管理,也支持声明式的事务管理。

大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。

Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

6.2.3 Spring提供的事务管理器

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

Spring的核心事务管理抽象是PlatformTransactionManager接口,它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

事务管理器的主要实现类:

img

  1. DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
  1. HibernateTransactionManager:用Hibernate框架存取数据库

6.3 引入案例

6.3.1 创建数据库和表

在MySQL中执行资料中的spring_transaction.sql文件生成对应的数据库和表。

6.3.2 需求:购买图书

  1. 接口
  • BookShopDao
public interface BookShopDao {
    //根据图书的书号获取图书的价格
    Double getBookPrice(String isbn);
    //根据图书的书号更新图书库存,一次只能买一本
    void updateBookStock(String isbn);
    //根据用户的id和图书的价格更新用户的余额
    void updateUserBalance(int userId , Double bookPrice);
}
​
  • BookShopService
public interface BookShopService {
    //购买图书
    void purchase(int userId , String isbn);
}
​
  1. 创建Spring的配置文件配置数据源和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"
       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.spring.tx"></context:component-scan>
​
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
​
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>
​
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
​
​
  1. 创建实现类
  • BookShopDaoImpl
package com.atguigu.spring.tx.dao.impl;
​
import com.atguigu.spring.tx.dao.BookShopDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
​
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
​
    @Autowired
    private JdbcTemplate jdbcTemplate;
​
    @Override
      public Double getBookPrice(String isbn) {
        //写sql语句
        String sql = "select price from book where isbn = ?";
        //调用JdbcTemplate中的queryForObject方法
        Double price = jdbcTemplate.queryForObject(sql, Double.class,isbn);
        return price;
    }
​
    @Override
    public void updateBookStock(String isbn) {
        //写sql语句
        String sql = "update book set stock = stock - 1 where isbn = ?";
        //调用JdbcTemplate中的update方法
        jdbcTemplate.update(sql,isbn);
    }
​
    @Override
    public void updateUserBalance(int userId, Double bookPrice) {
        //写sql语句
        String sql = "update account set balance = balance - ? where id = ?";
        //调用JdbcTemplate中的update方法
        jdbcTemplate.update(sql,bookPrice,userId);
    }
}
​
​
  • BookShopServiceImpl
package com.atguigu.spring.tx.service.impl;
​
import com.atguigu.spring.tx.dao.BookShopDao;
import com.atguigu.spring.tx.service.BookShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
​
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
​
    @Autowired
    private BookShopDao bookShopDao;
​
    @Override
    public void purchase(int userId, String isbn) {
        //获取图书的价格
        Double bookPrice = bookShopDao.getBookPrice(isbn);
        //更新图书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户账户的余额
        bookShopDao.updateUserBalance(userId,bookPrice);
}
}
  1. 测试
package com.atguigu.spring.tx.test;
​
import com.atguigu.spring.tx.dao.BookShopDao;
import com.atguigu.spring.tx.service.BookShopService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
​
public class TransactionTest {
​
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-tx.xml");
​
    /*
       测试BookShopDao中的方法
    */
    @Test
    public void testBookShopDao(){
        BookShopDao bookShopDao = (BookShopDao) ioc.getBean("bookShopDao");
        //获取图书价格
        Double bookPrice = bookShopDao.getBookPrice("1001");
        System.out.println(bookPrice);
        //更新图书库存
        bookShopDao.updateBookStock("1001");
        //更新用户账户余额
        bookShopDao.updateUserBalance(1,bookPrice);
    }
​
    /*
       测试BookShopServie中的方法
    */
    @Test
    public void testBookShopService(){
        BookShopService bookShopService = (BookShopService) ioc.getBean("bookShopService");
        bookShopService.purchase(1,"1001");
    }
}
​
  1. 出现的问题

当用户账户余额不足时会抛出异常,但是图书的库存却会减去一本!

  1. 解决方案

添加事务

6.4 基于注解方式的声明式事务

6.4.1 购买图书案例中添加Spring的声明式事务

  1. 导入AOP相关的jar包

spring-aspects-5.3.1.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

<!--spring-aspects-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
  1. 在Spring的配置文件中添加以下配置
<!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
​
    <!--开启事务注解支持
        当事务管理器的id是transactionManager时,可以省略指定transaction-manager属性
    -->
<!--    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>-->
    <tx:annotation-driven></tx:annotation-driven>
  1. 在需要添加事务的方法上添加@Transactional注解

image.png

6.4.2 事务的传播行为

  1. 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务传播属性可以在@Transactional注解的propagation属性中定义。

  1. Spring的7种传播行为
传播属性描述
REQUIRED如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的事务内运行。
REQUIRES_NEW当前的方法必须启动新事务,并在自己的事务内运行;如果有事务正在运行,应该将它挂起。
SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。
NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务将它挂起
MANDATORY当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。
NEVER当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。
NESTED如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。
  1. 传播行为测试
  • 创建Cashier接口
package com.atguigu.spring.tx.service;
​
import java.util.List;
​
public interface Cashier {
    //为多本书结账
    void checkout(int userId , List<String> ids);
}
​
  • 创建CashierImpl实现类
package com.atguigu.spring.tx.service.impl;
​
import com.atguigu.spring.tx.service.BookShopService;
import com.atguigu.spring.tx.service.Cashier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
import java.util.List;
​
@Service("cashier")
public class CashierImpl implements Cashier {
​
    @Autowired
    private BookShopService bookShopService;
​
    @Transactional
    @Override
    public void checkout(int userId, List<String> ids) {
        //遍历所有图书的id
        for (String id : ids) {
            //调用BookShopService中购买图书的方法
            bookShopService.purchase(userId,id);
        }
    }
}
​

此时,purchase方法就运行在了一个开启了事务的checkout方法中

  • 测试
   测试去结账里的方法
*/
@Test
public void testCashier(){
    Cashier cashier = (Cashier) ioc.getBean("cashier");
    //创建List
    List<String> ids = new ArrayList<>();
    ids.add("1001");
    ids.add("1002");
    cashier.checkout(1,ids);
}
​
  • 常用传播行为说明

    • REQUIRED

当bookShopService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

image.png

  • REQUIRES_NEW

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

img

image.png

6.4.3 事务的隔离级别

  1. 数据库事务并发问题

a) 脏读

①Transaction01将某条记录的AGE值从20修改为30。

②Transaction02读取了Transaction01更新后的值:30。

③Transaction01回滚,AGE值恢复到了20。

④Transaction02读取到的30就是一个无效的值。

简单来说就是你读到了别人更新但未提交的数据。

b) 不可重复度

①Transaction01读取了AGE值为20。

②Transaction02将AGE值修改为30并提交。

③Transaction01再次读取AGE值为30,和第一次读取不一致。

简单来说就是你两次读取的值不可能重复。

c) 幻读

①Transaction01读取了STUDENT表中的一部分数据。

②Transaction02向STUDENT表中插入了新的行。

③Transaction01读取了STUDENT表时,多出了一些行。

简单来说就是你两次读取的表中的记录不一样,好像出现幻觉似的。

  1. 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

隔离级别一共有四种:

① 读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

② 读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。

③ 可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

④ 串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

⑤ 各个隔离级别解决并发问题的能力见下表

脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

⑥ 各种数据库产品对事务隔离级别的支持程度

OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE
  1. 在Spring中指定事务隔离级别

在@Transactional的isolation属性中设置隔离级别

6.4.4 @Transactional注解中的其他属性

  1. 触发事务回滚的异常

a) 默认情况下,捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

b) @Transactional注解中设置回滚的属性

i. rollbackFor或rollbackForClassName属性:指定遇到时必须进行回滚的异常类型,可以为多个。

ii. noRollbackFor或noRollbackForClassName属性:指定遇到时不回滚的异常类型,可以为多个。

image.png

  1. 事务的超时时间

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。

timeout超时事务属性可以设置事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

  1. 只读属性

如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

readOnly只读事务属性可以设置这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

image.png

6.5 基于XML方式的声明式事务

6.5.1 修改实现类

BookShopDaoImpl

image.png

BookShopServiceImpl

image.png

6.5.2 重新创建一个Spring的配置文件

<?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" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
​
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
​
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>
​
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--配置数据源属性-->
  <property name="dataSource" ref="dataSource"></property>
    </bean>
​
    <!--配置BookShopDaoImpl-->
    <bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
​
    <!--配置BookShopServiceImpl-->
    <bean id="bookShopService" class="com.atguigu.spring.tx.xml.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"></property>
    </bean>
​
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
​
    <!--配置声明式事务-->
    <tx:advice id="tx" transaction-manager="transactionManager">
        <!--设置添加事务的方法-->
        <tx:attributes>
            <!--设置查询的方法的只读属性为true-->
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"></tx:method>
        </tx:attributes>
    </tx:advice>
​
    <!--AOP配置-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pointCut"
                      expression="execution(* com.atguigu.spring.tx.xml.BookShopServiceImpl.purchase(..))"/>
        <!--将事务方法和切入点表达式关联起来-->
        <aop:advisor advice-ref="tx" pointcut-ref="pointCut"></aop:advisor>
    </aop:config></beans>

6.5.3 测试

public class Transaction_XMLTest {
​
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-tx-xml.xml");
​
    /*
       测试BookShopServie中的方法
    */
    @Test
    public void testBookShopService(){
        BookShopService bookShopService = (BookShopService) ioc.getBean("bookShopService");
        bookShopService.purchase(1,"1001");
    }
}
​

注意:由于是从基于注解的方式配置声明式事务哪儿复制的接口和类,导包时一定不要导错,否则会出现类型转换异常。