首页 > 其他分享 >Spring05_Spring事务

Spring05_Spring事务

时间:2023-04-19 19:22:44浏览次数:37  
标签:事务 int Spring Spring05 id book money public

一、JdbcTemplate 工具

​ JdbcTemplate 类是 Spring 框架提供一个用于操作数据库的模板类,JdbcTemplate 类支持声明式事务管理。该类提供如下方法来执行数据库操作。

​ 1、queryForObject 查询单个对象

queryForObject(String sql,RowMapper mapper,Object[] args)

​ 2、query 查询集合

List query(String sql,RowMapper mapper,Object[] args)

​ 3、update 增加、删除、修改

int update(String sql, Object[] args)

​ 4、batchUpdate 批量增加、删除、修改

int[] batchUpdate(String sql, List list)

package com.qlu.pojo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(chain = true)
public class Book {
    private Long id;
    private String bookName;
    private String bookAuthor;
    private Date createTime;
    private Date updateTime;
}

​ 相同包的放到一起了,大家当个工具就行,毕竟都用框架了,集成其他的比这方便。

package com.qlu.service;

import com.qlu.pojo.Book;

public interface BookService {

    void add(Book book);

    void add(Integer value);

    void delete();

    void update(Book book);

    void find();

}

package com.qlu.service.impl;

import com.qlu.pojo.Book;
import com.qlu.service.BookService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service("bookService")
public class BookServiceImpl implements BookService {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void add(Book book) {
        System.out.println("添加图书");
        String sql = "insert into book (book_name,book_author,create_time,update_time) " +
                "values(?,?,?,?)";
        Object[] args = {book.getBookName(),book.getBookAuthor(),book.getCreateTime(),book.getUpdateTime()};
        int result = jdbcTemplate.update(sql, args);
        System.out.println(result>0?"成功":"失败");
    }

    @Override
    public void add(Integer value) {
        System.out.println("有参数的add方法  添加图书");
    }

    @Override
    public void delete() {
        System.out.println("删除图书");
    }

    @Override
    public void update(Book book) {
        System.out.println("修改图书");
        String sql = "update book  set book_name =?,book_author=?,update_time=? where id = ?";
        Object[] args = {book.getBookName(),book.getBookAuthor(),book.getUpdateTime(),book.getId()};
        int result = jdbcTemplate.update(sql, args);
        System.out.println(result>0?"成功":"失败");
    }

    @Override
    public void find() {
        System.out.println("查找图书");
    }
}

​ 就用了一个 @Service 注解,其他的 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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:util="http://www.springframework.org/schema/util"
       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/util
        http://www.springframework.org/schema/util/spring-util.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">

    <bean id="bookService" class="com.qlu.service.impl.BookServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>


    <!-- 配置 Spring 的事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 配置 C3P0 数据库链接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl"
                  value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8&amp;useUnicode=true"/>
        <property name="user" value="root"/>
        <property name="password" value="a.miracle"/>
        <property name="maxPoolSize" value="50"/>
        <property name="minPoolSize" value="20"/>
        <property name="initialPoolSize" value="20"/>
        <property name="maxIdleTime" value="200"/>
    </bean>

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

</beans>

​ 因为使用到了数据库连接池的配置,所以需要以下东西

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

​ 到这儿就差不多了,反正我不想写了。

二、Spring 事务

​ 此部分用到了上面的 Jdbc模板工具类

(一)事务概述

​ 事务是恢复和并发控制的基本单位。即一组操作要么全都不做,要不就全都做。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

​ 1.原子性:一个事务不可分割,里面的操作只能选择全做或者全不做。

​ 2.一致性:即事务是把数据库从一个一致的状态变到另一个一致的状态。

​ 3.隔离性:事务的执行不能相互干扰。

​ 4.持久性:事务一旦提交对数据库的改变就是永久的。

(二)事务管理举例

​ 我们以银行赚钱为实例,创建如下数据表并且插入数据如下

CREATE TABLE `money` (
  `id` bigint NOT NULL,
  `person_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `sum` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

image-20230419163504603

​ 依然配置用到了c3p0等数据库连接池,可以参少上面 标记 处,下面给出 dao、pojo、service 层的逻辑。

package com.qlu.service;

public interface MoneyService {
    /**
     * 转钱
     * @param fromId 从哪儿
     * @param toId 转给谁
     * @param money 转账金额
     * @return
     */
    boolean changeMoney(int fromId,int toId,int money);
}

​ 下面是数据持久层,在数据持久层的 changeMoney 有两个参数,你可以认为是改变某个 id 的 sum 值(sum表示总的金额)。

package com.qlu.dao;

public interface MoneyDao {
    /**
     * 根据id修改金额
     * @param id
     * @param money
     * @return
     */
    int changeMoney(int id,int money);
}

@Repository("moneyDao")
public class MoneyDaoImpl implements MoneyDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int changeMoney(int id, int money) {
        String sql = "update money set sum = sum + ? where id = ?";
        Object[] args = {money,id};
        return jdbcTemplate.update(sql,args);
    }
}

​ 下面是业务逻辑层,service 中的 changeMoney 表示从哪个 id 转移到哪个 id ,转多少钱。

package com.qlu.service;

public interface MoneyService {
    /**
     * 转钱
     * @param fromId 从哪儿
     * @param toId 转给谁
     * @param money 转账金额
     * @return
     */
    boolean changeMoney(int fromId,int toId,int money);
}

package com.qlu.service.impl;

@Service("moneyService")
public class MoneyServiceImpl implements MoneyService {

    @Autowired
    private MoneyDao moneyDao;

    @Override
    public boolean changeMoney(int fromId, int toId, int money) {
        /**
         * from 减少
         * to 增加
         */
        //张三减少
        int result1 = moneyDao.changeMoney(fromId,-1*money);
        //李四增加
        int result2 = moneyDao.changeMoney(toId,money);
        return (result1>0 && result2>0 ? true:false);
    }
}

​ 通过 Ctrl + Shift + T 生成测试业务逻辑的测试类

@Test
public void textTx1() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml");
    MoneyService moneyService = applicationContext.getBean(MoneyService.class);
    boolean result = moneyService.changeMoney(1, 2, 100);
    System.out.println(result);
}

​ 成功后我们可以看到数据库的变化

image-20230419163547954

​ 显然在这种正常的情况下没有错的,我们可以自定义一个 /by zero 异常在张三的钱减少和李四的钱增加之间来触发这个异常,

image-20230419165613851

​ 此时张三的钱没有了,但是李四的钱却没有增加,这一点类似于我们在多线程访问共享资源的情况一样,不给共享资源上锁,就可能出现两个线程信息不同步的情况。

image-20230419165741721

​ 为解决这个情况,Spring 提供了 TransactionManger,该功能依赖于 AOP,下面的配置都是老几样了,无非就是相当于把这个事务管理器当成一个增强函数来用,然后定义切面 aop-config,里面定义切点和增强组成切面,需要注意的是这个 aop:aspect 得换成我们事务管理的 aop:advisor

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"

    <!-- 配置 Spring 的事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务的消息通知 -->
    <tx:advice id="tAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!-- 配置消息通知类型列表,监控的方法由上而下开始匹配,以及事务的传播特性 -->
            <tx:method name="find*" propagation="SUPPORTS"/>
            <tx:method name="query*" propagation="SUPPORTS"/>
            <tx:method name="add*"/>
            <tx:method name="delete*"/>
            <tx:method name="update*"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.qlu.service.impl.*.*(..))"/>
    <!--配置切面,将消息通知切入到Service层-->
        <aop:advisor advice-ref="tAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

​ 关于配置事务的消息通知点这里

​ 再来跑一下,那自然还是有异常抛出,但是重点关注于数据库的数据,和上面可以说是完全一样,事务具有原子性(不可分割),这个减少 money 和增加 money 要不都做,要不都别做。

image-20230419183701278

​ 难办?那就都别办了,在张三减少了之后触发异常,程序被迫中止,那已经完成的 “张三减少” 也应该回滚成为没有减少的状态。image-20230419184349218

​ 针对可能抛出异常的代码块,我们可能会选择使用 try...catch 环绕捕获异常

    @Override
    public boolean changeMoney(int fromId, int toId, int money) {
        /**
         * from 减少
         * to 增加
         */
        try {
            //张三减少
            int result1 = moneyDao.changeMoney(fromId,-1*money);

            int exception = 1/0;
            //李四增加
            int result2 = moneyDao.changeMoney(toId,money);
            return (result1>0 && result2>0 ? true:false);
        } catch (Exception e) {
            e.printStackTrace();
            
            //throw e; 
            return false;
        }
    }

image-20230419184936840

​ 此时在这个业务内部就完成了对异常的处理(捕获到打印出信息),那在外部事务管理就不会捕获到这个异常。

image-20230419184951542

​ 解决方法:再把这个异常扔出来就行了,这样外部事务管理就可以捕获到异常信息

image-20230419185352163

​ 数据库没有变化(忘了应该在测试的 before 里面加个当前系统时间的)

image-20230419185400653

(三)Spring 事务的传播

​ 这个配置事务的消息通知就把它当成增强函数看就行,毕竟事务管理就是基于AOP的,你AOP能用注解事务管理也肯定是能用的。

tx:attributes 内的 tx:method 有如下属性:

​ 1、name 属性:表示在执行哪些方法时,会触发消息通知,name 的属性值支持模糊匹配,例如:find*,表示 调用以 find 开头的方法时都会触发消息通知。

​ 2、propagation 属性:表示事务的传播特性:

​ 属性值“SUPPORTS”表示支持当前事务,如果当前没有事务, 就使用无事务机制;

​ 属性值“REQUIRED”支持当前事务,有事务就加入到该事务中,如果没有事务则新建事务,默认值;

​ 属性值 REQUIRES_NEW 如果有当前事务,则挂起当前事务,新建新事务,反之,直接新建事务;

​ 属性值 MANDATORY 支持当前事务,如果没有事务,则抛出异常。

​ 3、timeout 属性:事务超时时间 默认值是-1,-1 表示不超时,以秒为单位。

​ 4、read-only 属性:事务是否只读,默认值是 false

​ 关于事务的传播机制:

传播机制 说明
REQUIRED 如果当前没有事务,就创建一个事务,如果已经存在事务,就加入到这个事务。当前传播机制也是spring默认传播机制
REQUIRES_NEW 新建事务,如果当前存在事务,就抛出异常。
SUPPORTS 支持当前事务,如果当前没有事务, 就使用无事务机制。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
MANDATORY 支持当前事务,如果没有事务,则抛出异常。
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED 如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

标签:事务,int,Spring,Spring05,id,book,money,public
From: https://www.cnblogs.com/purearc/p/17334376.html

相关文章

  • how to inject <class> type in spring
      sample:ClassitemClass;publicClassgetItemClass(){returnitemClass}publicvoidsetItemClass(ClassitemClass){this.itemClass=itemClass;} NowinjectitemClasspropertyinspring:<beanid="shampoo"class="com.test.Product&......
  • MySQL数据库事务
    什么是数据库事务数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。数据库事务的四大特性数据库事务的四大特性和程序事务相同,......
  • SpringBoot利用Filter获取请求数据request和修改返回response中的数据
    WrapperedRequestimportjavax.servlet.ReadListener;importjavax.servlet.ServletInputStream;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletRequestWrapper;importjava.io.*;publicclassWrapperedRequestextendsHttpSe......
  • 浅析Spring Security 核心组件
    前言近几天在网上找了一个SpringSecurity和JWT的例子来学习,项目地址是:github.com/szerhusenBC…作为学习SpringSecurity还是不错的,通过研究该demo发现自己对SpringSecurity一知半解,并没有弄清楚SpringSeurity的流程,所以才想写一篇文章先来分析分析SpringSecurity的......
  • springboot学习之五(自动配置)
    一、@Conditional源码springboot的自动配置是通过@Conditional注解(条件判断)实现的.@Conditional是Spring4版本新提供的一种注解,它的作用是按照设定的条件进行判断,把满足判断条件的bean注册到Spring容器。packageorg.springframework.context.annotation;importjava.lang.a......
  • IDEA Spring Boot项目的依赖入库问题
    SpringBoot项目在创建的时候,尽量把需要的依赖通过官网选择器勾选开发的依赖框架后期在开发过程中,可以通过点击当前版本的boot-pom的依赖看其支持的依赖的版本,手动进行导入或通过.pom的文件下右键generate,通过editstarter再次进入官网选择器进行依赖的选择和删除重新对ma......
  • 创建idea springboot(spring Initializr项目)需要手动导包)
    用注解@RestController 的时候,报错:找不到符号原来是没有import这个包,复制粘贴 @RestController 的时候,idea并没有自动import这个包,导致找不到,要手动import。手动导入就行了。另外,如果是创建的maven项目,由于main类文件的放置位置不同,也会报错:nested exception is java.lang.I......
  • @SpringBootApplication等四个爆红
    在黑马的上面学习,按步骤做,出现爆红问题,之后尝试过很多方法,后发现没导包。导包后可以 ......
  • Java SpringBoot 加载 yml 配置文件中字典项
    将字典数据,配置在yml文件中,通过加载yml将数据加载到Map中SpringBoot中yml配置、引用其它yml中的配置。#在配置文件目录(如:resources)下新建application-xxx必须以application开头的yml文件,多个文件用","号分隔,不能换行项目结构文件application.ymlserver:po......
  • MySQL事务实现原理
    事务是什么?首先思考一个问题,事务是什么?以下是事务的相关解释MySQL中的事务是一种用于确保数据库操作的完整性和一致性的机制。事务处理具有以下四个基本特性,通常被称为ACID特性:原子性(Atomicity):原子性是指事务中的所有操作要么全部完成,要么全部不完成。事务中的操作不可分割,如果......