目的:
新建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 生效,要么就用代理对象调用该方法,要么就用带有事务的方法调用该方法。