Maven手动配置Spring事务

目的:

        新建Maven工程,配置Spring事务。

代码:

        新建Maven项目

点击下一步,输入以下内容

 完成Maven工程的建立。

 完善pom.xml文件的依赖。

1)Spring的jar包依赖

        <!-- Spring包依赖(该依赖已经包含spring-beans,spring-core,spring-context,spring-expression四大核心jar包,同时还包含spring-aop,spring-jcl) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>

现在本工程已经可以使用Spring了。

我们先测试一下Spring是否好用,这里用的是配置类的方式。

建立配置类 com.transaction.config.AppConfig.Class

 

 建立Book这个Bean类用作测试

 声明该Bean并建立hello方法。

 建立测试类,测试Spring是否好用。

package com.test;

import com.transaction.bean.Book;
import com.transaction.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test
{
    public static void main(String[] args)
    {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        Book book = (Book) ac.getBean("book");
        book.hello();

        System.out.println("=========执行完毕==========");
    }
}

 执行结果如下

 Spring的测试搞定了。

下面就要处理数据库方面了,先要建个t_book表。我是在intellij idea中连接MySQL数据库的。

 

/*书表*/
create table t_book
(
    id varchar(20) not null,	        /*编码*/
    name varchar(60) null,		        /*书名*/
    author varchar(30) null,		    /*作者*/
    price numeric(14,2) null default 0, /*单价*/
    demo varchar(200) null,             /*备注*/
    constraint pk_t_book primary key(id)
);

 2)添加JdbcTemplate依赖

        <!--JdbcTemplate依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.23</version>
        </dependency>

3)添加MySQL依赖

        <!--MySQL数据库依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>

 4)添加DBCP数据库连接池依赖

        <!--Apache的数据库连接池依赖-->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

好,现在我们要测试一下数据库操作了。

我们要在配置类AppConfig中建立2个Bean。

    /**
     * 数据库连接源
     */
    @Bean
    public DataSource dataSource()
    {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost/test");
        dataSource.setUsername("root");
        dataSource.setPassword("password");

        return dataSource;
    }

 

    /**
     * JdbcTemplate,因为已经声明为Bean,所以参数DataSource会自动填充(容器会自动查找该DataSource类型的Bean)
     */
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource)
    {
        return new JdbcTemplate(dataSource);
    }

这样我们就可以用JdbcTemplate来为表t_book插入数据了。

修改类Book如下

package com.transaction.bean;

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

@Component
public class Book
{
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void hello()
    {
        jdbcTemplate.execute("insert into t_book values ('001','西游记','吴承恩',100,'四大名著之一')");

        System.out.println("你好,世界!");
    }
}

运行程序,查看数据库内容如下

 数据已经插入进来了。

下面我们要开始本章重点事务的测试了。

先说一下事务的原理:正常JdbcTemplate执行时是去数据库连接池取连接的,执行完一条语句后再放回去,有第二条SQL再重复上面的动作。而开启事务时JdbcTemplate是到事务管理器中取连接的,事务管理器中的连接是设置setAutoCommit(false)的,它是与线程绑定的,它能保证在事务中的每一条SQL语句都取得是同一个数据库连接,事务完成后再统一提交。

我们要在AppConfig配置类中建立一个事务管理器Bean。

    /**
     * 事务管理器(有事务时,SQL是从事务管理器取数据库连接的,每次都是同一个,setAutoCommit(false),而不是从连接池中取)
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource)
    {
        return new DataSourceTransactionManager(dataSource);

在Spring中事务管理器默认是关闭的(Spring Boot默认是开启的),需要手工开启,否则事务不生效的。

 配置类AppConfig的完整代码如下

package com.transaction.config;

import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@ComponentScan("com.transaction")       // 包扫描路径
@EnableTransactionManagement            // 开启事务管理器
public class AppConfig
{

    /**
     * 数据库连接源
     */
    @Bean
    public DataSource dataSource()
    {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost/test");
        dataSource.setUsername("root");
        dataSource.setPassword("password");

        return dataSource;
    }

    /**
     * JdbcTemplate,因为已经声明为Bean,所以参数DataSource会自动填充(容器会自动查找该DataSource类型的Bean)
     */
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource)
    {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 事务管理器(有事务时,SQL是从事务管理器取数据库连接的,每次都是同一个,setAutoCommit(false),而不是从连接池中取)
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource)
    {
        return new DataSourceTransactionManager(dataSource);
    }

}

在Book类中开始添加带事务的方法

    /**
     * 虽然本方法有@Transactional,但事务也不是在什么情况下都生效<br>
     * 1、代理对象直接调用本方法,生效。<br>
     * 2、代理对象调用A方法(该方法有事务),A直接调用本方法,生效。<br>
     * 3、代理对象调用A方法(该方法无事务),A直接用本方法,不生效。<br>
     * 4、代理对象调用A方法(该方法无事务),A用代理间接调用本方法,生效。<br>
     */
    @Transactional      // 这里添加事务注解
    public void addData()
    {
        // 第一条SQL
        jdbcTemplate.execute("insert into t_book values ('002','寓言故事','王仙名',50,'瞎编的')");

        // 人为抛异常
        int i = 5/0;

        // 第二条SQL
        jdbcTemplate.execute("insert into t_book values ('003','美猴王','李建',80,'也是瞎编的')");

    }

 该方法是带有事务的,问题是该事务也不是什么时候都生效,详细见上面的注释声明。

(1)代理对象直接调用本方法,生效。

 因为Book类里有@Transactional注解,因此用getBean("book")得到的book是一个代理类。我们直接用代理类调用该addData()方法时事务是生效的。

执行如下

 

查看数据无变化,说明事务回滚了。

(2)代理对象调用A方法(该方法有事务),A直接调用本方法,生效。

 执行如下

数据并没有插入进去。

(3)代理对象调用B方法(该方法无事务),B直接用本方法,尽管本方法有事务注解,但不生效。

 

事务不生效,第一条SQL数据插入成功了

 (4)代理对象调用C方法(该方法无事务),C用代理间接调用本方法,生效。

 Book类中要先声明一个代理,自己代理自己,然后用这个代理去调用addData()方法

先把数据清空

 修改测试类代码并执行

 事务生效,数据回滚了

 测试类与Book类完整代码如下

package com.test;

import com.transaction.bean.Book;
import com.transaction.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test
{
    public static void main(String[] args)
    {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        Book book = (Book) ac.getBean("book");

        // 1、代理对象直接调用带有事务的方法,生效。
        //book.addData();

        // 2、代理对象调用A方法(该方法有事务),A直接调用本方法,生效。
        //book.A();

        // 3、代理对象调用B方法(该方法无事务),B直接用本方法,不生效。
        //book.B();

        // 4、代理对象调用C方法(该方法无事务),C用代理间接调用本方法,生效
        book.C();



        System.out.println("=========执行完毕==========");
    }
}

package com.transaction.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class Book
{
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    Book book;

    public void hello()
    {
        jdbcTemplate.execute("insert into t_book values ('001','西游记','吴承恩',100,'四大名著之一')");

        System.out.println("你好,世界!");
    }

    /**
     * 虽然本方法有@Transactional,但事务也不是在什么情况下都生效<br>
     * 1、代理对象直接调用本方法,生效。<br>
     * 2、代理对象调用A方法(该方法有事务),A直接调用本方法,生效。<br>
     * 3、代理对象调用A方法(该方法无事务),A直接用本方法,不生效。<br>
     * 4、代理对象调用A方法(该方法无事务),A用代理间接调用本方法,生效。<br>
     */
    @Transactional      // 这里添加事务注解
    public void addData()
    {
        // 第一条SQL
        jdbcTemplate.execute("insert into t_book values ('002','寓言故事','王仙名',50,'瞎编的')");

        // 人为抛异常
        int i = 5/0;

        // 第二条SQL
        jdbcTemplate.execute("insert into t_book values ('003','美猴王','李建',80,'也是瞎编的')");

    }

    @Transactional
    public void A()
    {
        // addData(); 这里实际上是this调用的,不是代理调用,addData()上无论有没有@Transactional都不起作用,都不走事务。
        // 但由于A方法本身是带有事务的,addData()还是包含在A方法的事务里的。
        addData();
    }

    public void B()
    {
        // 内部方法调用
        // addData(); 这里实际上是this调用的,不是代理调用,addData()上有没有@Transactional都不起作用。
        // 由于B本身无事务,那么最终addData()永远不会走事务
        addData();
    }

    public void C()
    {
        // 内部方法调用
        // 这里实际上是代理调用,所以addData()上的@Transactional生效。
        book.addData();
    }



}

 测试结束!

总结

要想使@Transactional 生效,要么就用代理对象调用该方法,要么就用带有事务的方法调用该方法。

 

猜你喜欢

转载自blog.csdn.net/lag_csdn/article/details/127749200