目 录CONTENT

文章目录

spring的事务管理(5)

Eric
2022-02-15 / 0 评论 / 0 点赞 / 160 阅读 / 3,446 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-12-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1 概念

  • 事务是数据库操作最基本的单元,逻辑上的一组操作,要么全部成功,要么全部失败。

2 事务的特性

  • 原子性

  • 一致性

  • 隔离性

  • 持久性

3 准备工作

  • account.sql
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `money` double NULL DEFAULT NULL COMMENT '账户余额',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `account` VALUES (1, 'lucy', 1000);
INSERT INTO `account` VALUES (2, 'marry', 1000);
  • 导入相关jar包的Maven坐标:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>
  • Account.java
package top.open1024.spring.domain;

/**
 * 账户
 */
public class Account {

    /**
     * 主键
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 账户余额
     */
    private Double money;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • db.properties
jdbc.url=jdbc:mysql://192.168.1.107:33060/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
       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:component-scan base-package="top.open1024.spring"></context:component-scan>

    <!-- 导入数据库连接信息 -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>

    <!-- 配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!-- 配置jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

4 事务操作介绍

4.1 概述

  • 事务添加到JavaEE三层结构中的Service层(业务逻辑层)。

  • 在Spring中进行事务管理操作有两种方式:

    • 编程式事务管理。

    • 声明式事务管理(经常使用)。

  • 声明式事务管理:

    • 基于注解方式(经常使用)。

    • 基于XML配置文件方式。

  • Spring事务管理API

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

    • @Transaction注解可以添加在类上面,也可以放在方法上面。
      • 如果添加在类上面,这个类里面的所有的方法都添加了事务。
      • 如果添加在方法上面,则这个方法添加了事务。
      • 源码:
   package org.springframework.transaction.annotation;
   
   import java.lang.annotation.Documented;
   import java.lang.annotation.ElementType;
   import java.lang.annotation.Inherited;
   import java.lang.annotation.Retention;
   import java.lang.annotation.RetentionPolicy;
   import java.lang.annotation.Target;
   import org.springframework.core.annotation.AliasFor;
   
   @Target({ElementType.TYPE, ElementType.METHOD})
   @Retention(RetentionPolicy.RUNTIME)
   @Inherited
   @Documented
   public @interface Transactional {
       @AliasFor("transactionManager")
       String value() default "";
   
       @AliasFor("value")
       String transactionManager() default "";
       //传播行为
       Propagation propagation() default Propagation.REQUIRED;
       //事务隔离级别
       Isolation isolation() default Isolation.DEFAULT;
       //超时时间
       int timeout() default -1;
       //是否只读
       boolean readOnly() default false;
       //回滚
       Class<? extends Throwable>[] rollbackFor() default {};
   
       String[] rollbackForClassName() default {};
       //不回滚
       Class<? extends Throwable>[] noRollbackFor() default {};
   
       String[] noRollbackForClassName() default {};
   }

      • propagation:事务的传播行为。
        • 多事务方法(对数据库表数据进行变化的操作)直接进行调用,这个过程中事务是如何进行管理的。
@Transactional
public void add(){
    //调用update方法
    update();
}
public void update(){}
      • -属性:

      • REQUIRED:

        • 如果add方法本身有事务,则调用update方法之后,update使用当前add方法里面的事务。
        • 如果add方法本身没有事务,调用update方法之后,会创建新的事务。
      • REQUIRED_NEW:

        • 使用add方法调用update方法,无论add是否有事务,都会创建新的事务。
      • isolation:事务隔离级别。

        • 多事务操作之间不会产生影响。
        • 如果不考虑隔离性,会产生脏读、不可重复读、虚读。
        • 脏读:一个未提交的事务读到另一个事务未提交的事务(针对update)。
        • 不可重复读:一个未提交的事务读到另一个提交事务的修改数据(针对update),前后多次读取,导致数据内容不一致。
        • 幻读:一个未提交的事务读到了另一个提交事务的添加数据(针对insert),前后多次读取,数据总量不一致。
      • 通过设置事务的隔离级别,可以解决脏读问题。

        • READ UNCOMMITTED:会出现脏读、不可重复读、幻读。
        • READ COMMITTED:会出现不可重复读、幻读。
        • REPEATABLE READ:会出现幻读。
        • SERIALIZABLE:不会出现问题。
      • timeout:超时时间。

        • 事务需要在一定时间内进行提交,如果不提交就进行回滚。
        • 在Spring中,默认值是-1(不回滚),设置时间以秒单位进行计算。
      • readOnly:是否只读。

        • 默认值是false,表示可以查询、添加、修改、删除操作。
        • 如果设置readOnly为true,就只能查询。
      • rollbackFor:回滚。

        • 设置出现了那些异常进行事务回滚。
      • noRollbackFor:不回滚。

        • 设置那些异常不进行事务回滚。

4.2 基于注解方式的声明式事务管理

  • 在Spring的配置文件applicationContext.xml中配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入数据源 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
  • 在Spring的配置文件applicationContext.xml中,开启事务注解
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 完整的applicationContext.xml文件信息
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="top.open1024.spring"></context:component-scan>

    <!-- 导入数据库连接信息 -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>

    <!-- 配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!-- 配置jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 开启事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
  • AccountDao.java
package top.open1024.spring.dao;

import top.open1024.spring.domain.Account;

public interface AccountDao {
    /**
     * 转入
     *
     * @param account
     * @param money
     */
    void addMoney(Account account, Double money);

    /**
     * 转出
     *
     * @param account
     * @param money
     */
    void reduceMoney(Account account, Double money);
}
  • AccountDaoImpl.java
package top.open1024.spring.dao.impl;

import top.open1024.spring.dao.AccountDao;
import top.open1024.spring.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addMoney(Account account, Double money) {
        jdbcTemplate.update(" update account set money = money + ? where username = ? ", money, account.getName());
    }

    @Override
    public void reduceMoney(Account account, Double money) {
        jdbcTemplate.update(" update account set money = money - ? where username = ? ", money, account.getName());
    }
}
  • AccountService.java
package top.open1024.spring.service;

public interface AccountService {

    /**
     * 转账
     *
     * @param from
     * @param target
     * @param money
     */
    void transfer(String from, String target, Double money);

}
  • AccountServiceImpl.java
package top.open1024.spring.service.impl;

import top.open1024.spring.dao.AccountDao;
import top.open1024.spring.domain.Account;
import top.open1024.spring.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String from, String target, Double money) {
        Account targetAccount = new Account();
        targetAccount.setName(target);
        accountDao.addMoney(targetAccount, money);

        int i = 100 / 0;

        Account fromAccount = new Account();
        fromAccount.setName(from);
        accountDao.reduceMoney(fromAccount, money);
    }
}
  • 测试:
package top.open1024.spring;

import top.open1024.spring.service.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    private AccountService accountService = null;

    @Before
    public void before(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        accountService = context.getBean(AccountService.class);
    }

    @Test
    public void test(){
        accountService.transfer("lucy","marrry",100.00);
    }
}

4.3 基于XML配置文件方式的声明式事务管理

  • 在Spring的配置文件applicationContext.xml中配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入数据源 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
  • 在Spring的配置文件applicationContext.xml中配置通知
<!-- 配置通知 -->
<tx:advice id="txAdvice">
    <!-- 配置事务参数 -->
    <tx:attributes>
        <!-- 指定那种规则的方法上面添加事务 -->
        <tx:method name="add*" />
        <tx:method name="update*" />
        <tx:method name="delete*" />
    </tx:attributes>
</tx:advice>
  • 在Spring的配置文件applicationContext.xml中配置切入点和通知
<!-- 配置切入点和切面 -->
<aop:config>
    <!-- 配置切入点 -->
    <aop:pointcut id="pointcut" expression="execution(* top.open1024.spring.service.impl.AccountServiceImpl.*(..))"/>
    <!-- 配置切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
  • 完整的applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="top.open1024.spring"></context:component-scan>

    <!-- 导入数据库连接信息 -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>

    <!-- 配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!-- 配置jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置通知 -->
    <tx:advice id="txAdvice">
        <!-- 配置事务参数 -->
        <tx:attributes>
            <!-- 指定那种规则的方法上面添加事务 -->
            <tx:method name="add*"/>
            <tx:method name="update*"/>
            <tx:method name="delete*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置切入点和切面 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pointcut" expression="execution(* top.open1024.spring.service.impl.AccountServiceImpl.*(..))"/>
        <!-- 配置切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

</beans>

4.4 完全注解方式的声明式事务管理

  • SpringConfig.java

    package top.open1024.spring.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.TransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    
    @Configuration
    @ComponentScan(basePackages = "top.open1024.spring")
    @EnableTransactionManagement //开启事务
    public class SpringConfig {
    
        /**
         * 连接池
         *
         * @return
         */
        @Bean
        public DataSource dataSource() {
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            druidDataSource.setUrl("jdbc:mysql://192.168.134.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true");
            druidDataSource.setUsername("root");
            druidDataSource.setPassword("123456");
            return dataSource();
        }
    
        /**
         * JdbcTemplate
         *
         * @return
         */
        @Bean
        public JdbcTemplate jdbcTemplate() {
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            jdbcTemplate.setDataSource(dataSource());
            return jdbcTemplate;
        }
    
    
        /**
         * 事务管理器
         *
         * @return
         */
        @Bean
        public TransactionManager transactionManager() {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource());
            return transactionManager;
        }
    
    }
    
0

评论区