Spring(九)声明式事务

news2024/11/23 4:19:48

Spring整合Junit4和JdbcTemplater如下所示:

我们所使用的junit的jar包不同,可以整合不同版本的junit。

我们导入的依赖如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.rgf.spring</groupId>
    <artifactId>spring-transaction</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring持久化层支持jar包-->
        <!--Spring在执行持久化层操作,与持久层技术进行整合过程中,需要使用orm,jdbc.trx这三个jar包-->
        <dependency>
            <!--对象关系映射-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--spring测试相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>
</project>

 其中的事务管理如下所示:

我们的配置文件编写如下所示:

<?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"
>

    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>

我们选定一个数据库表来进行测试如下所示:

package com.rgf.spring.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//指定当前测试类在spring的测试环境中执行,此时就可以通过注入的方式直接来获取IOC容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置spring测试环境的配置文件
//classpath:类路径
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    public void  testInsert(){
        String sql="insert into t_user values(null,?,?,?,?,?)";
        jdbcTemplate.update(sql,"root","123",23,"男","123@qq.com");
    }
}

我们运行之后如下所示:

 我们在数据库表进行刷新之后如下所示:

 我们完成添加功能之后,我们进行查看查询功能,我们进行测试如下:

@Test
    public  void  testGetUserById(){
        String sql="select * from t_user where id=?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
        System.out.println(user);
    }

测试完成之后如下所示:

我们发现成功的查询出来了 

我们进行查询多条数据:

 @Test
    public  void  testGetAllUser(){
        String sql="select * from t_user";
        List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        list.forEach(System.out::println);
    }

运行之后如下所示:

 我们查询总记录数如下所示:

 @Test
    public  void  testGetCount(){
        String sql="select count(*) from t_user";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(count);
    }

运行之后如下所示:

 声明式事务概念:

编程式事务:

事务功能的相关操作全部通过自己编写代码来实现:

 Connection conn=...;
    try

    {
        //开启事务:关闭事务的自动提交
        conn.setAutoCommit(false);
        //核心操作
        //提交事务
        conn.commit();
    }catch(Exception e){
        //回滚事务
        conn.rollback();
    }finally {
        //释放数据库连接
        conn.close();
    } 

编程式的方式存在缺陷:

细节没有被屏蔽:具体操作过程中,所以细节都需要程序员自己来完成,比较繁琐。

代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务:

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

好处1:提高开发效率

好处2:消除了冗余的代码

好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

所以,我们可以总结下面两个概念:

编程式:自己写代码实现功能

声明式:通过配置让框架实现功能

 基于注解的声明式事务:

我们所导入的依赖如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.rgf.spring</groupId>
    <artifactId>spring-transaction</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring持久化层支持jar包-->
        <!--Spring在执行持久化层操作,与持久层技术进行整合过程中,需要使用orm,jdbc.trx这三个jar包-->
        <dependency>
            <!--对象关系映射-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--spring测试相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>
</project>

接下来我们创建jdbc.properties:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

接下来我们进行配置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"
>

    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>

我们再创建一个tx-annotation配置文件,如下所示:

<?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">

    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

之后我们进行创建表:

CREATE TABLE ‘t_book’ (
                             ’book_id‘ INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
                             ‘book_name’ VARCHAR(20) DEFAULT NULL COMMENT '图书名称',
                             ’price‘ INT(11) DEFAULT NULL COMMENT '价格',
                             ‘stock’ INT(10) UNSIGNED DEFAULT NULL COMMENT '库存(无符号)',
                             PRIMARY KEY (’book_id‘)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO ‘t_book’ (’book_id‘,‘book_name’,’price‘,‘stock’) VALUES (1,'斗破苍穹',80,100),
                                                                       (2,'斗罗大陆',50,100);
CREATE TABLE ’t_user‘ (
                          ’user_id‘ INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
                          ‘username’ VARCHAR(20) DEFAULT NULL COMMENT '用户名',
                          ’balance‘ INT(10) UNSIGNED DEFAULT NULL COMMENT '余额(无符号)',
                          PRIMARY KEY(’user_id‘)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into ’t_user‘ (’user_id‘,‘username’,’balance‘) VALUES (1,'admin',50);

我们进行运行之后如下所示:

 

 我们成功创建表之后,我们通过用户购买书的过程中,通过余额不足来测试声明式事务。通过表可以看到余额为balance:50

完成表的创建之后,我们进行示例如下所示:

我们现进行创建service层,controller层,dao层,:

我们创建controller层如下所示:

package com.rgf.spring.controller;

import com.rgf.spring.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Controller
public class BookController {
    @Autowired
    private BookService bookService;

    public void  buyBook(Integer userId,Integer bookId){

        bookService.buyBook(userId,bookId);
     }
}

之后我们进行创建service层:首先创建接口如下:

package com.rgf.spring.service;

public interface BookService {
    void  buyBook(Integer userId,Integer bookId);
}

我们创建service层的实现类如下所示:

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    public void  buyBook(Integer userId, Integer bookId){
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
    }

}

之后我们创建dao层:

package com.rgf.spring.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;

@Repository
public class BookDaoImpl implements BookDao{


    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Integer getPriceByBookId(Integer bookId) {
       String sql="SELECT price from t_book where book_id=?";
      return jdbcTemplate.queryForObject(sql, Integer.class,bookId);
    }

    @Override
    public void updateStock(Integer bookId) {
        String sql="update t_book set stock-=1 where book_id=?";
        jdbcTemplate.update(sql,bookId);
    }

    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql="update t_user set balance=balance-? where user_id=?";
        jdbcTemplate.update(sql,price,userId);
    }
}
package com.rgf.spring.dao;

public interface BookDao {
    /**
     * 根据图书的id查询图书的价格
     * @param bookId
     * @return
     */
    Integer getPriceByBookId(Integer bookId);

    /**
     * 更新图书的库存
     * @param bookId
     */
    void updateStock(Integer bookId);

    /**
     * 更新用户的余额
     * @param userId
     * @param price
     */
    void updateBalance(Integer userId,Integer price);
}

我们在接口里面定义方法之后,需要在实现类进行重写。

我们的自动装配如下所示:
我们利用事务进行查看:

package com.rgf.spring.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;

@Repository
public class BookDaoImpl implements BookDao{


    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Integer getPriceByBookId(Integer bookId) {
       String sql="SELECT price from t_book where book_id=?";
      return jdbcTemplate.queryForObject(sql, Integer.class,bookId);
    }

    @Override
    public void updateStock(Integer bookId) {
        String sql="update t_book set stock-=1 where book_id=?";
        jdbcTemplate.update(sql,bookId);
    }

    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql="update t_user set balance=balance-? where user_id=?";
        jdbcTemplate.update(sql,price,userId);
    }
}

我们查看表的价格:

 

 我们进行测试如下:用户1买一号位的书。

package com.rgf.spring.test;

import com.rgf.spring.controller.BookController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {
    @Autowired
    private BookController bookController;

    @Test
    public  void  testBuyBook(){
        bookController.buyBook(1,1);
    }

}

我们进行运行之后发现如下所示:

 在没有事务的情况下,当我们进行测试的时候,一个sql语句独占一个事务且自动提交。所以前两条SQL语句成功,最后一条SQL语句失败的话,失败的不会影响上面运行成功的。我们只有·将他们放在同一事务里面,通过事务的代码去操作,如果有异常,则将整个事务都进行回滚,出现要么都成功要么都失败的情况。

我们进行测试事务如下所示:

<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  <!--扫描组件-->
    <context:component-scan base-package="com.rgf.spring"></context:component-scan>
    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean 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>

    <!--开启事务的注解驱动-->
    <!--为环绕通知-->
    <!--在Spring配置文件里面,如果某一个属性的值为环绕型,当前这个属性值就会变为灰色-->
    <!--将事务管理器里面的环绕通知作用于他的连接点-->
    <!--开启事务的注解驱动
     将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
     transaction-manager属性设置事务管理器的id
     若事务管理器的bean的id默认为transactionManager,则该属性可以不写。
  -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
</beans>

我们在service层的实现类里面进行设置事务管理器:

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    public void  buyBook(Integer userId, Integer bookId){
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
    }

}

我们进行测试如下所示:

package com.rgf.spring.test;

import com.rgf.spring.controller.BookController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 声明式事务的配置步骤:
 * 1.在spring的配置文件中来配置事务管理器
 * 2.开启事务的注解驱动
 * 3.在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
 * @Transactional注解标识的位置:
 * 1.标识在方法上
 * 2.标识在类上,则类中所有的方法都会被事务管理
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {
    @Autowired
    private BookController bookController;

    @Test
    public  void  testBuyBook(){
        bookController.buyBook(1,1);
    }

}

事务属性:

事务属性:只读

介绍:对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

只有当前的事务中只有查询操作时才可以将事务进行设置为只读,否则则会直接报错。

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    @Transactional(
            readOnly = true
    )
    public void  buyBook(Integer userId, Integer bookId){
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
    }

}

报错信息如下所示:

 事务属性:超时

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源,而长时间占用资源,大概率是因为程序运行出现了问题,此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

我们进行测试如下所示:

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    @Transactional(
           //readOnly=true
            timeout = 3
    )
    public void  buyBook(Integer userId, Integer bookId){
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
    }

}

我们发现报错如下所示:

即事务超时异常 。TransactionTimedOutException.

事务属性:回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚

可以通过@Transactional中相关属性设置回滚策略

rollbackFor属性:需要设置一个Class类型的对象

rollbackForClassName属性:需要设置一个字符串类型的全类名

noRollbackFor属性:需要设置一个Class类型的对象

noRollbackForClassName属性:需要设置一个字符串类型的全类名

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLSyntaxErrorException;
import java.util.concurrent.TimeUnit;

@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    @Transactional(
           //readOnly=true
           // timeout = 3
            //noRollbackFor = SQLSyntaxErrorException.class
            noRollbackForClassName="java.sql.SQLSyntaxErrorException.class"
    )
    public void  buyBook(Integer userId, Integer bookId){
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
        System.out.println(1/0);
    }

}

我们进行测试之后即会发现不会造成回滚。但是报错信息仍然会有。

事务属性:事务隔离级别

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

隔离级别一共有四种:

读未提交   READ UNCOMMITED(Transaction01读取Transaction02未提交的修改)

读已提交  READ COMMITTED(要求Transaction01只能读取Transaction02已提交的修改)

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

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

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

隔离级别脏读不可重复读幻读
READ UNCOMMITED(读未提交)
READ COMMITTED (读已提交)
REPEAABLE READ(可重复读)
SERIALZABLE(串行化)

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

隔离级别OracleMySQL
READ UNCOMMITED(读未提交)noyes
READ COMMITTED (读已提交)yes(默认)yes
REPEAABLE READ(可重复读)noyes(默认)
SERIALZABLE(串行化)yes(默认)yes

我们设置隔离程度如下所示:

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLSyntaxErrorException;
import java.util.concurrent.TimeUnit;

@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    @Transactional(
           //readOnly=true
           // timeout = 3
            //noRollbackFor = SQLSyntaxErrorException.class
            //noRollbackForClassName="java.sql.SQLSyntaxErrorException.class"
            isolation = Isolation.DEFAULT
    )
    public void  buyBook(Integer userId, Integer bookId){
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
        System.out.println(1/0);
    }

}

事务的传播行为:

我们在controller层里面进行调用如下所示:

package com.rgf.spring.controller;

import com.rgf.spring.service.BookService;
import com.rgf.spring.service.CheckoutService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Controller
public class BookController {
    @Autowired
    private BookService bookService;
    private CheckoutService checkoutService;

    public void  buyBook(Integer userId,Integer bookId){

        bookService.buyBook(userId,bookId);
     }
     
     public  void  checkout(Integer userId,Integer[] bookIds){
        checkoutService.checkout(userId,bookIds);
     }
}

我们在service层里面调用如下所示:
先写接口:

package com.rgf.spring.service;

public interface CheckoutService {
    /**
     * 结账
     * @param userId
     * @param bookIds
     */
    void checkout(Integer userId, Integer[] bookIds);

}

然后写接口调用的方法,即为实现类:

package com.rgf.spring.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CheckoutServiceImpl implements  CheckoutService{
   @Autowired
   private  BookService bookService;
    @Override
    @Transactional
    public void checkout(Integer userId, Integer[] bookIds) {
        for (Integer bookId:bookIds){
            bookService.buyBook(userId,bookId);
        }
    }
}

我们进行测试如下所示:

package com.rgf.spring.test;

import com.rgf.spring.controller.BookController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 声明式事务的配置步骤:
 * 1.在spring的配置文件中来配置事务管理器
 * 2.开启事务的注解驱动
 * 3.在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
 * @Transactional注解标识的位置:
 * 1.标识在方法上
 * 2.标识在类上,则类中所有的方法都会被事务管理
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {
    @Autowired
    private BookController bookController;

    @Test
    public  void  testBuyBook(){
        bookController.buyBook(1,1);
    }
    
    @Test
    public  void  testCheckoutBook(){
        bookController.checkout(1,new Integer[]{1,2});
    }

}

测试之后出现报错信息:SQLStateSQLExceptionTranslator.java

我们进行刷新之后发现书的数量没有发生变化。此即为第二本书买不了的话,当前的所有书都无法进行购买。当数据出错的时候,回滚的是整个结账的操作。

我们将买书操作的事务的属性设置如下所示:

package com.rgf.spring.service;

import com.rgf.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLSyntaxErrorException;
import java.util.concurrent.TimeUnit;

@Service
public class BookServiceImpl implements BookService{
    /**
     * 买书
     * @param userId
     * @param bookId
     *
     */
    @Autowired
    private BookDao bookDao;
    @Override
    @Transactional(
           //readOnly=true
           // timeout = 3
            //noRollbackFor = SQLSyntaxErrorException.class
            //noRollbackForClassName="java.sql.SQLSyntaxErrorException.class"
          //  isolation = Isolation.DEFAULT
            propagation = Propagation.REQUIRES_NEW
    )
    public void  buyBook(Integer userId, Integer bookId){
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //查询图书的价格
        Integer price=bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
        System.out.println(1/0);
    }

}

 此时用的是买书自身的事务,即会将第一本书购买成功,第二本购买不成功。可以通过@Transaction中的propagation属性设置事务传播行为。

@Transactional(propagation = Propagation.REQUIRED),默认情况。使用调用者的事务
@Transactional(propagation = Propagation.REQUIRES_NEW),被调用者被调用的方法本身的事务

基于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 https://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:component-scan base-package="com.rgf.spring"></context:component-scan>
    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean 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="tx" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="buyBook" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:advisor advice-ref="tx" pointcut="execution(* com.rgf.spring.service.*.*(..))"></aop:advisor>
    </aop:config>

</beans>

我们的测试代码如下所示:

package com.rgf.spring.test;

import com.rgf.spring.controller.BookController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 声明式事务的配置步骤:
 * 1.在spring的配置文件中来配置事务管理器
 * 2.开启事务的注解驱动
 * 3.在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
 * @Transactional注解标识的位置:
 * 1.标识在方法上
 * 2.标识在类上,则类中所有的方法都会被事务管理
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-xml.xml")
public class TxByXMLTest {
    @Autowired
    private BookController bookController;

    @Test
    public  void  testBuyBook(){
        bookController.buyBook(1,1);
    }

    @Test
    public  void  testCheckoutBook(){
        bookController.checkout(1,new Integer[]{1,2});
    }

}

我们进行测试的时候发现出现如下问题:

 我们发现缺少jar包。我们来进行添加:

 <!--spring-aspects会帮我们传递过来aspectjweaver-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>

我们进行导入依赖。

完成之后我们继续测试即可。

注意:基于xml实现的声明式事务,必须引入aspectj的依赖

我们可以用*默认为全部方法:

<?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 https://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:component-scan base-package="com.rgf.spring"></context:component-scan>
    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean 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="tx" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="buyBook" />
            <tx:method name="*"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <!--read-only属性:设置只读属性-->
            <!--rollback-for属性:设置回滚的异常-->
            <!--no-rollback-for属性:设置不回滚的异常-->
            <!--isolation属性:设置事务的隔离级别-->
            <!--timeout属性:设置事务的超时属性-->
            <!--propagation属性:设置事务的传播行为-->
            <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
            <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
            <tx:method name="delete" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:advisor advice-ref="tx" pointcut="execution(* com.rgf.spring.service.*.*(..))"></aop:advisor>
    </aop:config>

</beans>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/942367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP_ABAP_DEBUG_场景介绍

SAP ABAP顾问能力模型_企业数字化建设者的博客-CSDN博客SAP Abap顾问能力模型https://blog.csdn.net/java_zhong1990/article/details/132469977一 背景说明 二 场景说明 1 SAPerp与第三方系统集成&#xff0c;第三系统通过接口函数请求SAP系统&#xff0c;如何调试&#xff1…

加密的PDF文件,如何解密?

PDF文件带有打开密码、限制编辑&#xff0c;这两种密码设置了之后如何解密&#xff1f; 不管是打开密码或者是限制编辑&#xff0c;在知道密码的情况下&#xff0c;解密PDF密码&#xff0c;我们只需要在PDF编辑器中打开文件 – 属性 – 安全&#xff0c;将权限状态修改为无保护…

使用Angular和MongoDB来构建具有登录功能的博客应用程序

Angular 是一个一站式框架&#xff0c;用于使用相同的可重用代码创建移动和 Web 应用程序。使用 Angular&#xff0c;您可以将整个应用程序划分为可重用的组件&#xff0c;从而更轻松地维护和重用代码。 在本教程系列中&#xff0c;您将学习如何开始使用 Angular 和 MongoDB 作…

JVM 判定对象是否死亡的两种方式

引用计数法&#xff1a;&#xff08;脑门刻字法&#xff09;和 可达性分析 引用计数算法 引用计数器的算法是这样的&#xff1a;在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一…

实用的面试经验分享:程序员们谈论他们的面试历程

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

R语言常用数组函数

目录 1.array 2.matrix 3.data.matrix 4.lower.tri 5.mat.or.vec 6.t:转置矩阵 7.cbind 8.rbind 9.diag 10.aperm&#xff1a;对数组进行轴置换&#xff08;维度重排&#xff09;操作 11.%*%&#xff1a;乘法操作要求矩阵 A 的列数等于矩阵 B 的行数 12.crossprod…

基于梯度算法优化的BP神经网络(预测应用) - 附代码

基于梯度算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于梯度算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.梯度优化BP神经网络2.1 BP神经网络参数设置2.2 梯度算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

计算机组成原理之数据的表示和运算(一)

目录 一、引言二、进位计数制2.1 十进制计数法2.2 二进制2.3 八进制2.4 十六进制2.5 进制转换&#xff1a;二进制、八进制、十六进制、十进制之间的转换2.5.1 任意进制到十进制转换2.5.2 二进制与八进制和十六进制如何进行互相转换&#xff1f;2.5.3 十进制到任意进制的转换 2.…

layui框架学习(42:文件上传模块-上)

之前学习asp.net core编程入门教程时结合layui测试过文件上传《基于ASP.Net Core和Layui的多文件上传》&#xff0c;但没有认真学习过layui的文件上传模块&#xff0c;本文开始&#xff0c;计划分两章学习并记录文件上传模块中的属性、事件及函数的使用方法。   layui中的文件…

1.1 数据库系统简介

1.1.数据库系统简介 前言&#xff1a; 数据库系统是一个软件系统&#xff0c;用于管理和操作数据库。它提供了一个组织良好、高效并能够方便存取的数据存储机制&#xff0c;并且能够支持各种数据操作、事务管理、并发控制和恢复功能。以下是数据库系统的一些主要特点和组件&a…

redis缓存雪崩、穿透、击穿解决方案

redis缓存雪崩、穿透、击穿解决方案 背景缓存雪崩缓存击穿缓存穿透总结背景 关于缓存异常,我们常见的有三个问题:缓存雪崩、缓存击穿、缓存穿透。这三个问题一旦发生,会导致大量请求直接落到数据库层面。如果请求的并发量很大,会影响数据库的运行,严重的会导致数据库宕机…

xsschallenge1~13通关详细教程

文章目录 XSS 挑战靶场通关level1level2level3level4level5level6level7level8level9level10level11level12level13 XSS 挑战靶场通关 level1 通过观察发现这个用户信息可以修改 那么我们直接输入攻击代码 <script>alert(/wuhu/)</script>弹框如下&#xff1a; …

烟花厂人员作业释放静电行为检测算法

烟花厂人员作业释放静电行为检测算法通过pythonyolo系列算法模型框架&#xff0c;烟花厂人员作业释放静电行为检测算法在工厂车间入口处能够及时捕捉到人员是否触摸静电释放仪。一旦检测到人员进入时没有触摸静电释放仪&#xff0c;系统将自动触发告警。Python是一种由Guido va…

第 1 章 绪论(计算多项式)

示例代码 1&#xff1a; /* 计算 1 - 1 / x 1 / x * x - ... */#include <stdio.h> #include <sys/timeb.h>int main(void) {double x;int n;printf("Please input x and n: ");scanf_s("%lf%d", &x, &n);struct timeb t1;ftime(&…

2023.8.27 关于 Junit5 详解

目录 引言 注解 断言 用例的执行顺序 参数化 测试套件 引言 Junit 是一个基于Java语言的单元测试框架Selenium 是一个用于Web应用程序测试的自动化测试框架结合二者能让我们的 Web自动化测试 更加完善和全面 注意&#xff1a; 本文所讲的内容&#xff0c;均基于以下依…

机器视觉工程师永不为奴,他们是肯干肯出差肯加班肯拼命肯被使唤肯被叼

​ 永不为奴&#xff0c;为什么这样呐喊&#xff0c;真的很难做到。我们职业机器视觉工程师&#xff0c;本身职业具有一大特点就是专业性。 但是我们机器视觉工程师是专业技术绝不苟同于不是技术人员言语&#xff0c;我们很专业。 肯出差&#xff1a; 设备去那里&#xff0c;…

启莱OA CloseMsg.aspx SQL注入

子贡曰&#xff1a;“贫而无谄&#xff0c;富而无骄&#xff0c;何如&#xff1f;”子曰&#xff1a;“可也。未若贫而乐&#xff0c;富而好礼者也。” 子贡曰&#xff1a;“《诗》云&#xff1a;‘如切如磋&#xff0c;如琢如磨。’其斯之谓与&#xff1f;”子曰&#xff1a;“…

mybatis plus新版代码生成器,类型转换处理器ITypeConvertHandler使用

目录 引言关键代码源码分析记录一坑类型转换的第二种方式完整源码地址 引言 当默认生成的数据类型不满足时&#xff0c;就需要自定义指定要生成的类型 关键代码 FastAutoGenerator.create(url, username, password).dataSourceConfig(builder -> {builder.typeConvertHandl…

Python计算加速利器

迷途小书童的 Note 读完需要 6分钟 速读仅需 2 分钟 1 简介 Python 是一门应用非常广泛的高级语言&#xff0c;但是&#xff0c;长久以来&#xff0c;Python的运行速度一直被人诟病&#xff0c;相比 c/c、java、c#、javascript 等一众高级编程语言&#xff0c;完全没有优势。 那…

记一次因函数入参没有带类型所引发的问题

工作中&#xff0c;sit测试中&#xff0c;测试突然发现页面异常。经排查&#xff0c;是函数参数没有定义类型所以&#xff0c;dart默认是dynamic类型&#xff0c;而dynamic编译阶段不检查类型&#xff0c;正是由于编译阶段不检查类型&#xff0c;很明显的错误也只能运行的时候才…