Spring5 基础3

167 阅读9分钟

事务

事务概念

何为事务:

事务是数据库操作最基本的单元,指的是逻辑上的一组操作,要么都成功,如果其中有一点失败,那整体操作都失败。

典型场景:银行转账

事物的四大特性(ACID):

原子性:

  • 执行过程中不可分割,要么都成、要么都失败

一致性:

  • 操作之前和操作之后总量不会变化

隔离性:

  • 在多事务操作时,它们之间不会产生影响(两个人同时操作同一条记录,他们之间不会产生影响)

持久性:

  • 事务完成后提交到数据库

事务操作

事务案例操作(银行转账):

创建数据库表:

USE user_db;
CREATE TABLE t_account2(
	id VARCHAR(20) PRIMARY KEY,
	username VARCHAR(50),
	money INT
);

创建 service,搭建 dao,完成对象创建和注入关系:

  1. 在 service中注入 dao,在 dao中注入 JdbcTemplate,
  2. 在 JdbcTemplate注入 DataSource
  3. 在 dao中创建两个方法,一个是加钱的方法,一个是减钱的方法,在 service中创建一个转账方法
  4. 以上逻辑,如果正常运行没有问题,但是如果代码执行过程出现异常,会出现问题
  5. 所以应该使用事务来解决代码出现异常的问题

事务操作_Spring事务管理介绍:

在开发中,事务一般添加到 JavaEE三层结构中的 Service层中(业务逻辑层)

在 Spring中进行事务管理操作,一般分为两种方式:

  • 编程式事务管理(代码臃肿)
  • 声明式事务管理(推荐)

声明式事务管理(两种):

  • 基于注解方式(推荐)
  • 基于 xml配置方式

在 Spring进行声明式事务管理,底层使用 AOP

  • 在 Spring中,声明式事务管理用到的就是 AOP技术

Spring事务管理 API:

提供一个接口,代表事务管理器,此接口针对不同的框架提供不同的实现类

  • PlatformTransactionManager:事务总接口
  • DataSourceTransactionManager:jdbc、mybatis事务接口实现类

事务操作_声明式注解方式:

  1. 在 Spring配置文件中(xml)配置(创建)事务管理器
  2. 在 Spring配置文件中,开启事务注解(注解方式)
    • 引入名称空间 tx,类似于 context(代码扫描)
    • 开启事务注解 <tx:annotation-driven transaction-manager="">
  3. 在 Service类上面(或 Service类的方法上面)添加事务注解
    • @Transactional:此注解可以添加到类、方法上面
    • 添加在类上面,那类中所有的方法都添加了事务
    • 添加到指定方法上面,那此方法会添加事务
银行转账案例_声明式注解完整代码:
  • 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: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 http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--
        context: 开启组件扫描:
    -->
    <context:component-scan base-package="com.atjava.spring5_2"></context:component-scan>

    <!--
        添加数据库连接池:
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        -->
        <property name="url" value="jdbc:mysql:///user_db"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>

    <!--
        创建 jdbcTemplate对象:
    -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--创建事务管理器:-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入被操作的数据库源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启事务注解:-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
</beans>
  • service
package com.atjava.spring5_2.service;

import com.atjava.spring5_2.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author lv
 * @create 2022-01-12 23:38
 */
@Service(value = "accountService")
//@Transactional // 事务注解
public class AccountService {
    // 注入属性:
    @Autowired
    @Qualifier(value = "accountDaoImpl")
    private AccountDao accountDao;
    // 用户转账操作:使用事务
    @Transactional
    public void turnAccount() {
        // 事务操作流程:
//        try {
//            // 第一步:开启事务
//            // 第二步:进行业务操作
//            accountDao.reduce();
//            // 模拟异常
//            int n = 10/0;
//            accountDao.add();
//
//            // 第三步:无异常发生,提交事务
//        } catch(Exception e) {
//            // 第四步:出现异常,事务回滚
//        }

        // 事务:声明式注解方式
        accountDao.reduce();
        int n = 10 / 0;
        accountDao.add();
    }
}
  • dao
package com.atjava.spring5_2.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * @author lv
 * @create 2022-01-12 23:42
 */
@Repository(value = "accountDaoImpl")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    @Qualifier(value = "jdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    @Override
    public void reduce() {
        Object[] obj = {100, "1"};
        String sql = "update t_account set money=money-? where id=?";
        int update = jdbcTemplate.update(sql, obj);
        System.out.println("reduce: " + update);
    }

    @Override
    public void add() {
        Object[] obj = new Object[2];
        obj[0] = 100;
        obj[1] = "2";
        String sql = "update t_account set money=money+? where id=?";
        int update = jdbcTemplate.update(sql, obj);
        System.out.println("add: " + update);
    }
}
  • test
@Test
public void testAccount() {
    //PlatformTransactionManager事务接口
    //DataSourceTransactionManager,jdbc、mybatis事务实现类
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean2.xml");
    AccountService service = context.getBean("accountService", AccountService.class);
    service.turnAccount();
}

事务操作_声明式事务管理参数配置:

在 service类上添加注解 @Transactional,在此注解里面可以配置事务相关参数

  • 事务方法:对数据库表数据进行变化的操作

@Transactional(xxx) 配置事务相关参数:

  • propagation:事务传播行为

    1. 多事务方法(数据库的增删改方法)之间进行调用,这个过程中事务是如何进行管理的
    2. 事务传播行为是指:一个有事务的方法中调用一个无事务的方法,或一个无事务方法中调用一个有事务方法等,它们之间是如何处理的
    3. @Transactional(propagation=Propagational.REQUIRED)
  • isolation:事务隔离级别

    1. 事务有特性称为隔离性,多事务操作之间不会产生影响,不考虑隔离性会产生很多问题
    2. 三个读问题:脏读、不可重复读、虚(幻)读
    3. @Transactional(isolation = Isolation.READ_COMMITTED)
  • timeout:超时时间

    1. 事务需要在一定时间内进行提交,如果超过设置时间,事务会自动回滚
    2. 默认超时时间是 -1,单位是秒
    3. @Transactional(timeout = -1, isolation = Isolation.READ_COMMITTED)
  • readonly:是否只读

    1. 读:数据库查询操作;写:数据库增删改操作
    2. readonly:默认值 false,表示可以查询,可以增删改操作
    3. 设置值 true,只能查询
    4. @Transactional(readonly = true, timeout = -1)
  • rollbackFor:回滚

    1. 设置执行过程中出现指定异常后进行事务回滚
  • noRollbackFor:不回滚

    1. 设置执行过程中出现指定异常后不进行事务回滚

Spring 框架事务传播行为有七种:

传播属性描述
REQUIRED如果 add方法本身有事务,调用 update方法后,update使用当前 add方法里面的事务;如果 add方法本身无事务,调用 update方法后,创建新事务
REQUIRED_NEW当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,因该将它挂起
xxxxxx

事务隔离性产生的三个问题:

  • 脏读:一个未提交事务读取到另一个未提交事务的数据
  • 不可重复读:一个未提交事务读取到另一个提交事务修改的数据
  • 虚(幻)读:一个未提交事务读取到另一个提交事务添加的数据

事务的隔离级别:

隔离级别脏读不可重复读虚读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

事务操作_声明式 xml配置方式:

在 Spring配置文件中进行配置:

  1. 配置(创建)事务管理器
  2. 配置通知(实际增强的逻辑部分称为通知)<tx:advice />
  3. 配置切入点(类中实际被增强的方法)和切面(将通知应用到切入点的过程)<aop:config />
  • 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:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--
        context: 开启组件扫描:
    -->
    <context:component-scan base-package="com.atjava.spring5_2"></context:component-scan>

    <!--
        添加数据库连接池:
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        -->
        <property name="url" value="jdbc:mysql:///user_db"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>

    <!--
        创建 jdbcTemplate对象:
    -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--1. 创建事务管理器:-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入被操作的数据库源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 配置通知:-->
    <tx:advice id="txadvice">
        <!--配置事务参数:-->
        <tx:attributes>
            <!--指定那种规则的方法上面添加事务-->
            <!--<tx:method name="turn*"/>-->
            <tx:method name="turnAccount" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--3. 配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut
                id="pt"
                expression="execution(* com.atjava.spring5_2.service.AccountService.*(..))"
        ></aop:pointcut>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>
  • test
@Test
public void testAccountXml() {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean3.xml");
    AccountService service = context.getBean("accountService", AccountService.class);
    service.turnAccount();
}

事务操作_完全注解声明式事务管理:

创建配置类,使用配置类完全替代 xml配置文件:

  1. 配置类注解 @Configuration
  2. 组件扫描 @ComponentScan(basePackages = "com.atjava.spring5_2")
  3. 创建数据库连接池 DruidDataSource
  4. 创建 JdbcTemplate模板对象 JdbcTemplate
  5. 创建事务管理器 DataSourceTransactionManager
  6. 开启事务 @EnableTransactionManagement
  • config
package com.atjava.spring5_2.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * @author lv
 * @create 2022-01-16 22:16
 */
// 1.配置类注解
@Configuration
// 2.组件扫描
@ComponentScan(basePackages = "com.atjava.spring5_2")
// 6.开启事务
@EnableTransactionManagement
public class TxConfig {
    // 3.创建数据库连接池
    @Bean // 创建 bean实例
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 设置驱动名称
        dataSource.setUrl("jdbc:mysql:///user_db"); // 数据库路径
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    // 4.创建 JdbcTemplate模板对象
    /* (DataSource dataSource):表示在 IOC容器中已经存在 DataSource类型的 dataSource对象,
        类似于 @Autowired根据类型注入。
    */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // jdbcTemplate.setDataSource(this.getDruidDataSource());
        // 注入 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    // 5.创建事务管理器:
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
  • test
    • AnnotationConfigApplicationContext(configClass)注解类
@Test
public void testAccountAllAnnotation() {
    ApplicationContext context =
            new AnnotationConfigApplicationContext(TxConfig.class);
    AccountService service = context.getBean("accountService", AccountService.class);
    service.turnAccount();
}

Spring5 框架新功能

  1. 整个 Spring5 框架的代码基于 Java8
  2. 运行时兼容 JDK9
  3. 许多不建议使用的类和方法已在代码库中删除

核心特性:

  1. Spring5 框架自带了通用的日志封装(查看程序运行、排查问题)
  2. Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
    • 在 Spring5框架中整合 Log4j2

核心容器:

  1. 支持 @Nullable注解
  2. 函数式风格(Lambda表达式) GenericApplicationContext
  3. Spring WebMVC

测试方面的改进:

  1. Spring5支持整合 JUnit5

在 Spring5框架中整合 Log4j2

  1. 引入相关 jar包
    • log4j-api-2.11.2.jar
    • log4j-core-2.11.2.jar
    • log4j-slf4j-impl-2.11.2.jar
    • slf4j-api-1.7.30.jar
  2. 创建 log4j2.xml配置文件
  3. 测试:
package com.atjava.spring5_2.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author lv
 * @create 2022-01-17 21:27
 */
public class UserLog {
    private static final Logger log = LoggerFactory.getLogger(UserLog.class);

    public static void main(String[] args) {
        // 在控制台手动添加日志:
        log.info("hello log4j2 info");
        log.warn("hello log4j2 warn");
        /**
         * 2022-01-17 21:32:35.257 [main] INFO  com.atjava.spring5_2.test.UserLog - hello log4j2 info
         * 2022-01-17 21:32:35.261 [main] WARN  com.atjava.spring5_2.test.UserLog - hello log4j2 warn
         */
    }
}

Spring5框架支持 @Nullable注解

  1. @Nullable注解可以使用在方法上、属性上、参数上
  2. 分别表示方法返回值可以为空、属性值可以为空、参数值可以为空
// 方法:
@Nullable
ApplicationContext getParent();
// 属性:
@Nullable
private String userName;
// 参数:
public <T> void registerBean(@Nullable String beanName, @Nullable Supplier<T> supplier, ...) {
    this.reader.registerBean(beanClass, beanName, supplier, customizers);
}

Spring5框架支持函数式风格(Lambda表达式)GenericApplicationContext

通过 Lambda表达式和 GenericApplicationContext类,将独自创建的对象交给 Spring管理。

  1. 创建 GenericApplicationContext对象
  2. 调用 context的方法进行对象注册
// 函数式风格创建对象,交给 Spring进行管理
@Test
public void testGenericApplicationContext() {
    // 1.创建 GenericApplicationContext对象
    GenericApplicationContext context =
            new GenericApplicationContext();
    // 2.调用 context的方法进行对象注册
    context.refresh(); // 表示进行内容清空
    // Lambda表达式:() -> new User()
    // 不指定对象名称
    // context.registerBean(User.class, () -> new User()); // 注册对象
    // 指定对象名称 user1
    context.registerBean("user1", User.class, () -> new User()); // 注册对象,指定名称

    // 3.测试,获取在 Spring中注册的对象
    // User user = context.getBean("com.atjava.spring5_2.bean.User"); // 类的全路径
    User user = context.getBean("user1", User.class); // 指定对象名称

    System.out.println(user);
    // com.atjava.spring5_2.bean.User@57855c9a

}

Spring5支持整合 JUnit5

整合 JUnit4:

  1. 引入 Spring相关针对测试的依赖包:
    • 引入 JUnit4的 jar包
    • spring-test-5.2.6.RELEASE.jar
  2. 创建测试类,使用相关注解方式完成
    • @RunWith(SpringJUnit4ClassRunner.class)指定整合的单元测试框架的版本
    • @ContextConfiguration("classpath:bean2.xml")加载配置文件
package com.atjava.spring5_2.test;

import com.atjava.spring5_2.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author lv
 * @create 2022-01-18 22:01
 */
// 指定整合的单元测试框架的版本
@RunWith(SpringJUnit4ClassRunner.class)
/**
 * 加载配置文件:
 * 类似于如下代码
 * ApplicationContext context =
 *                 new ClassPathXmlApplicationContext("bean3.xml");
 *
  */
@ContextConfiguration("classpath:bean2.xml")
public class JTest4 {
    // 因为已经加载了配置文件,可以直接注入
    @Autowired
    // @Qualifier(value = "accountService")
    private AccountService accountService;

    @Test
    public void testAccountService() {
        // 直接调用:
        accountService.turnAccount();
    }
}

整合 JUnit5:

  1. 引入 JUnit5的 jar包
  2. 创建测试类,使用注解完成
    • @ExtendWith(SpringExtension.class)指定整合的单元测试框架的版本:JUnit5
    • @ContextConfiguration("classpath:bean2.xml")添加配置文件:bean2.xml
package com.atjava.spring5_2.test;

import com.atjava.spring5_2.service.AccountService;
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;

/**
 * @author lv
 * @create 2022-01-18 22:44
 */
// 指定整合的单元测试框架的版本:JUnit5
@ExtendWith(SpringExtension.class)
// 添加配置文件:bean2.xml
@ContextConfiguration("classpath:bean2.xml")
public class JTest5 {
    @Autowired
    private AccountService accountService;

    @Test
    public void testJUnit5() {
        accountService.turnAccount();
    }
}
  • 复合注解:@SpringJUnitConfig(locations = "classpath:bean2.xml")
    • 表示 @ExtendWith和 @ContextConfiguration的组合
package com.atjava.spring5_2.test;

import com.atjava.spring5_2.service.AccountService;
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;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

/**
 * @author lv
 * @create 2022-01-18 22:44
 */
// 指定整合的单元测试框架的版本:JUnit5
// @ExtendWith(SpringExtension.class)
// 添加配置文件:bean2.xml
// @ContextConfiguration("classpath:bean2.xml")

// 复合注解:
@SpringJUnitConfig(locations = "classpath:bean2.xml")
public class JTest5 {
    @Autowired
    private AccountService accountService;

    @Test
    public void testJUnit5() {
        accountService.turnAccount();
    }
}

SpringWebFlux:

WebFlux介绍:

响应式编程:

WebFlux执行流程与核心 API:

SpringWebFlux 基于注解编程模型:

SpringWebFlux 基于函数式编程模型: