天天看点

hualinux spring 4.18:@Transactional 声明式事务

 目录

​​一、知识点​​

​​1.1 声明式事务操作​​

​​1.2 相关依赖​​

​​二、例1:普通例子​​

​​2.1 建立数据库和表​​

​​2.2 目录结构​​

​​2.3 实现代码​​

​​2.4 运行结果​​

​​三、例2:在例1的基础上添加事务@Transactional ​​

​​3.1 模式事务​​

​​3.2 开启事务​​

​​3.3 配置事务管理器来控制事务​​

什么是事务,简单来说,就是要几个sql绑在一起,要么所有执行成功,要么所有执行失败,不能出现只执行部分的情况。

经典的就是银行转帐,A向B转帐,最少需要执行2步,2个sql

步骤1:查询A余额是否足够,然后在A-转账金额

步骤2:B账号添加A转账数

你不能只执行一个吧?!这是绝对不能出现只执行成功一个的,如果只有一部分执行成功,那么就要全部回滚,并提示失败。

一、知识点

1.1 声明式事务操作

1.给方法上方 @Transactional 表示当前方法是一个事务方法

2.@EnableLoadTimeWeaving 开启基于注解的事务管理功能

    @EnableXXX  不开启的话,事务是不会被执行的

3.配置事务管理器来控制事务

      @Bean

      public PlatformTransactionManager transactionManager() throws Exception{}

1.2 相关依赖

c3p0、mchange-commons-java、mysql-connector-java、spring-jdbc

pom.xml要有如下配置:我目前的spring版本为5.2.3,涉及有spring的版本要一致,其它的版本可以去​​maven官网仓库​​查

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/mchange-commons-java -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>mchange-commons-java</artifactId>
            <version>0.2.20</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>      

c3p0:主要是读取外部配置文件,一般数据库配置文件放在外部,我这里使用yml方式

mchange-commons-java:c3p0数据库连接池的辅助包

mysql-connector-java:mysql的jdbc驱动包,是java连接mysql数据库使用的,我这里只是配置文件,并没用到mysql数据库

spring-jdbc:spring操作jdbc操作数据库用的,如mysql数据库

二、例1:普通例子

2.1 建立数据库和表

我在hua数据库如下:

hualinux spring 4.18:@Transactional 声明式事务

表如下:

插入数据

INSERT INTO tb1_user (username,age) VALUES ('admin',18)      

2.2 目录结构

2.3 实现代码

com.hualinux.tx.UserDao.java

package com.hualinux.tx;

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

import java.util.UUID;

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert(){
        String sql = "INSERT INTO tb1_user (username,age) VALUES (?,?)";
        String username  = UUID.randomUUID().toString().substring(0,5);
        jdbcTemplate.update(sql,username,19);
    }
}      

com.hualinux.tx.UserService.java

package com.hualinux.tx;

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

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    
    public void insertUser(){
        userDao.insert();
        System.out.println("插入完成...");
    }
}      

resources-->db.yml

我是直接使用《​​hualinux spring 4.16:@Profile 切换环境​​》中的数据库配置文件db.yml,只是修改了用户名和密码

user: hua
pwd: hua123
driverClass: com.mysql.cj.jdbc.Driver
jdbcUrlTest: jdbc:mysql://127.0.0.1:3306/hua_test?serverTimezone=GMT%2B8
jdbcUrlDev: jdbc:mysql://127.0.0.1:3306/hua_dev?serverTimezone=GMT%2B8"
jdbcUrlProd: jdbc:mysql://127.0.0.1:3306/hua?serverTimezone=GMT%2B8      

注:下面值不能加双引号或引号,如果加了会连引号一起获取的

#下面写法是错误的

driverClass: "com.mysql.cj.jdbc.Driver"

jdbcUrlTest: "jdbc:mysql://127.0.0.1:3306/hua_test?serverTimezone=GMT%2B8"

jdbcUrlDev: "jdbc:mysql://127.0.0.1:3306/hua_dev?serverTimezone=GMT%2B8"

jdbcUrlProd: "jdbc:mysql://127.0.0.1:3306/hua?serverTimezone=GMT%2B8"

 com.hualinux.tx.TxConf.java配置如下

package com.hualinux.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;


/*
* 声明式事务:
*
* 环境搭建:
* 1.导入相关依赖:数据源、数据库驱动c3p0、Springjdbc模拟
* 2.配置数据源:JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
* */
@PropertySource(value={"classpath:/db.yml"})
@ComponentScan("com.hualinux.tx")
@Configuration
public class TxConf {

    @Value("${user}")
    private String user;
    @Value("${pwd}")
    private String pwd;
    @Value("${driverClass}")
    private String driverClass;
    @Value("${jdbcUrlTest}")
    private String jdbcUrlTest;
    @Value("${jdbcUrlDev}")
    private String jdbcUrlDev;
    @Value("${jdbcUrlProd}")
    private String jdbcUrlProd;


    //数据源
    @Bean
    public DataSource dataSource() throws Exception{
        //主要是测试一下是否读到db.yml文件数据,及是否填写正确
        System.out.println(user+"\n"+pwd+"\n"+jdbcUrlProd+"\n"+driverClass);
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setDriverClass(driverClass);
        //测试环境使用的数据库
        dataSource.setJdbcUrl(jdbcUrlProd);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        //Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器找组件
        //下面dataSource(),是找组件并不是运行一次方法,因为这个类是配置类
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }


}      

src-->test-->java-->IOCTest_Tx.java代码

import com.hualinux.tx.TxConf;
import com.hualinux.tx.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class IOCTest_Tx {
    AnnotationConfigApplicationContext ctx;

    @Test
    public void test01() {
        ctx = new AnnotationConfigApplicationContext(TxConf.class);
        UserService userService = ctx.getBean(UserService.class);

        userService.insertUser();
        ctx.close();
    }

}      

2.4 运行结果

运行上面的IOCTest_Tx.java中的测试方法test01,如果数据库打开能连接用户名和密码没问题,最后会提示

hualinux spring 4.18:@Transactional 声明式事务

查看数据库变化

hualinux spring 4.18:@Transactional 声明式事务

三、例2:在例1的基础上添加事务@Transactional 

3.1 模式事务

com.hualinux.tx.UserService.java类在最后添加一条语句模拟事务,如下:

package com.hualinux.tx;

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

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Transactional
    public void insertUser(){
        userDao.insert();
        System.out.println("插入完成...");
        //之前的是没有事务的,模拟事务添加一条,要求不成功就回滚
        int i=10/0;

    }
}      

再运行IOCTest_TxTest.java中的test01方法,效果如下

hualinux spring 4.18:@Transactional 声明式事务

抛异常了,查看一下表没有第3条数据插入,发现还是插入了

hualinux spring 4.18:@Transactional 声明式事务

 说明事务没生效啊!!因为默认事务是关闭的,没有开启,需要添加一个开启的注解。

@EnableLoadTimeWeaving 开启基于注解的事务管理功能

@EnableXXX  不开启的话,事务是不会被执行的      

3.2 开启事务

所以事务配置类得开启一下,com.hualinux.tx.TxConf.java代码,在类顶上添加多一个注解

@EnableLoadTimeWeaving      

整个代码如下:

package com.hualinux.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;


/*
 * 声明式事务:
 *
 * 环境搭建:
 * 1.导入相关依赖:数据源、数据库驱动c3p0、Springjdbc模拟
 * 2.配置数据源:JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
 * 3.给方法上歀 @Transactional 表示当前方法是一个事务方法
 * 4.@EnableLoadTimeWeaving 开启基于注解的事务管理功能
 *     @EnableXXX  不开启的话,事务是不会被执行的
 * */
@EnableLoadTimeWeaving
@PropertySource(value={"classpath:/db.yml"})
@ComponentScan("com.hualinux.tx")
@Configuration
public class TxConf {

    @Value("${user}")
    private String user;
    @Value("${pwd}")
    private String pwd;
    @Value("${driverClass}")
    private String driverClass;
    @Value("${jdbcUrlTest}")
    private String jdbcUrlTest;
    @Value("${jdbcUrlDev}")
    private String jdbcUrlDev;
    @Value("${jdbcUrlProd}")
    private String jdbcUrlProd;


    //数据源
    @Bean
    public DataSource dataSource() throws Exception{
        //主要是测试一下是否读到db.yml文件数据,及是否填写正确
        System.out.println(user+"\n"+pwd+"\n"+jdbcUrlProd+"\n"+driverClass);
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setDriverClass(driverClass);
        //测试环境使用的数据库
        dataSource.setJdbcUrl(jdbcUrlProd);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        //Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器找组件
        //下面dataSource(),是找组件并不是运行一次方法,因为这个类是配置类
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }


}      

再运行IOCTest_TxTest.java中的test01方法,发现报如下错误:

Error creating bean with name 'loadTimeWeaver' defined in org.springframework.context.annotation.LoadTimeWeavingConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.instrument.classloading.LoadTimeWeaver]: Factory method 'loadTimeWeaver' threw exception; nested exception is java.lang.IllegalStateException: ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-instrument-{version}.jar
hualinux spring 4.18:@Transactional 声明式事务

再去看一下数据库是否有变化,发现不有变化,所以完成了。

3.3 配置事务管理器来控制事务

//注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager() throws Exception{
        return  new DataSourceTransactionManager(dataSource());
    }      
package com.hualinux.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;


/*
 * 声明式事务:
 *
 * 环境搭建:
 * 1.导入相关依赖:数据源、数据库驱动c3p0、Springjdbc模拟
 * 2.配置数据源:JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
 * 3.给方法上歀 @Transactional 表示当前方法是一个事务方法
 * 4.@EnableLoadTimeWeaving 开启基于注解的事务管理功能
 *     @EnableXXX  不开启的话,事务是不会被执行的
 * */
@EnableLoadTimeWeaving
@PropertySource(value={"classpath:/db.yml"})
@ComponentScan("com.hualinux.tx")
@Configuration
public class TxConf {

    @Value("${user}")
    private String user;
    @Value("${pwd}")
    private String pwd;
    @Value("${driverClass}")
    private String driverClass;
    @Value("${jdbcUrlTest}")
    private String jdbcUrlTest;
    @Value("${jdbcUrlDev}")
    private String jdbcUrlDev;
    @Value("${jdbcUrlProd}")
    private String jdbcUrlProd;


    //数据源
    @Bean
    public DataSource dataSource() throws Exception{
        //主要是测试一下是否读到db.yml文件数据,及是否填写正确
        System.out.println(user+"\n"+pwd+"\n"+jdbcUrlProd+"\n"+driverClass);
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setDriverClass(driverClass);
        //测试环境使用的数据库
        dataSource.setJdbcUrl(jdbcUrlProd);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        //Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器找组件
        //下面dataSource(),是找组件并不是运行一次方法,因为这个类是配置类
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    //注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager() throws Exception{
        return  new DataSourceTransactionManager(dataSource());
    }

}      

继续阅读