Spring AOP应用之——声明式事务

Spring AOP的应用配置

1、Spring JdbcTemplate

​ 在spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以尽可能保留灵活性,将数据库存取的工作量讲到最低。

1、配置并测试数据源

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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.oi</groupId>
<artifactId>spring_demo</artifactId>
<version>1.0-SNAPSHOT</version>

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

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>

</project>

dbconfig.properties

1
2
3
4
jdbc.username=root123
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
</beans>

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
2、给spring容器添加JdbcTemplate

​ spring容器提供了一个JdbcTemplate类,用来方便操作数据库。

1、添加pom依赖

pom.xml

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>

jdbcTemplate.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
System.out.println(jdbcTemplate);
}
}
3、插入数据

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(?,?)";
int result = jdbcTemplate.update(sql, 1111, "zhangsan");
System.out.println(result);
}
}
4、批量插入数据

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(?,?)";
List<Object[]> list = new ArrayList<Object[]>();
list.add(new Object[]{1,"zhangsan1"});
list.add(new Object[]{2,"zhangsan2"});
list.add(new Object[]{3,"zhangsan3"});
int[] result = jdbcTemplate.batchUpdate(sql, list);
for (int i : result) {
System.out.println(i);
}
}
}
5、查询某个值,并以对象的方式返回

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.oi.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from emp where empno = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 7369);
System.out.println(emp);
}
}
6、查询返回集合对象

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.oi.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.List;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from emp where sal > ?";
List<Emp> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), 1500);
for (Emp emp : query) {
System.out.println(emp);
}
}
}
7、返回组合函数的值

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.oi.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.List;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select max(sal) from emp";
Double aDouble = jdbcTemplate.queryForObject(sql, Double.class);
System.out.println(aDouble);
}
}
8、使用具备具名函数的JdbcTemplate

jdbcTemplate.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.oi.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
NamedParameterJdbcTemplate jdbcTemplate = context.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(:empno,:ename)";
Map<String,Object> map = new HashMap<>();
map.put("empno",2222);
map.put("ename","sili");
int update = jdbcTemplate.update(sql, map);
System.out.println(update);
}
}
9、整合EmpDao

jdbcTemplate.xml

1
<context:component-scan base-package="com.oi"></context:component-scan>

EmpDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.oi.dao;

import com.oi.bean.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

public class EmpDao {

@Autowired
private JdbcTemplate jdbcTemplate;

public void save(Emp emp){
String sql = "insert into emp(empno,ename) values(?,?)";
int update = jdbcTemplate.update(sql, emp.getEmpno(), emp.getEname());
System.out.println(update);
}
}

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.oi.bean.Emp;
import com.oi.dao.EmpDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
EmpDao empDao = context.getBean("empDao", EmpDao.class);
empDao.save(new Emp(3333,"wangwu"));
}
}

2、声明式事务

1、环境准备

tx.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
Navicat MySQL Data Transfer

Source Server : localhost
Source Server Version : 50528
Source Host : localhost:3306
Source Database : tx

Target Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001

Date: 2020-02-13 19:19:32
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `account`
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(10) NOT NULL DEFAULT '',
`balance` double DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO account VALUES ('lisi', '1000');
INSERT INTO account VALUES ('zhangsan', '1000');

-- ----------------------------
-- Table structure for `book`
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`id` int(10) NOT NULL,
`book_name` varchar(10) DEFAULT NULL,
`price` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO book VALUES ('1', '西游记', '100');
INSERT INTO book VALUES ('2', '水浒传', '100');
INSERT INTO book VALUES ('3', '三国演义', '100');
INSERT INTO book VALUES ('4', '红楼梦', '100');

-- ----------------------------
-- Table structure for `book_stock`
-- ----------------------------
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
`id` int(255) NOT NULL DEFAULT '0',
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of book_stock
-- ----------------------------
INSERT INTO book_stock VALUES ('1', '1000');
INSERT INTO book_stock VALUES ('2', '1000');
INSERT INTO book_stock VALUES ('3', '1000');
INSERT INTO book_stock VALUES ('4', '1000');

BookDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.oi.dao;

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

@Repository
public class BookDao {

@Autowired
JdbcTemplate jdbcTemplate;

/**
* 减去某个用户的余额
* @param userName
* @param price
*/
public void updateBalance(String userName,int price){
String sql = "update account set balance=balance-? where username=?";
jdbcTemplate.update(sql,price,userName);
}

/**
* 按照图书的id来获取图书的价格
* @param id
* @return
*/
public int getPrice(int id){
String sql = "select price from book where id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,id);
}

/**
* 减库存,减去某本书的库存
* @param id
*/
public void updateStock(int id){
String sql = "update book_stock set stock=stock-1 where id=?";
jdbcTemplate.update(sql,id);
}
}

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
public void checkout(String username,int id){

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
import com.oi.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;

public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.checkout("zhangsan","1");
}
}

总结:在事务控制方面,主要有两个分类:

编程式事务:在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法

声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。

2、声明式事务的简单配置

​ Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

​ Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

​ 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。下图是spring提供的事务管理器
![事务管理器](Spring AOP应用之——声明式事务/事务管理器.png)

1、在配置文件中添加事务管理器

jdbcTemplate.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.oi"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--事务控制-->
<!--配置事务管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启基于注解的事务控制模式,依赖tx名称空间-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional
public void checkout(String username,int id){

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}
3、事务配置的属性

​ isolation:设置事务的隔离级别

​ propagation:事务的传播行为

​ noRollbackFor:那些异常事务可以不回滚

​ noRollbackForClassName:填写的参数是全类名

​ rollbackFor:哪些异常事务需要回滚

​ rollbackForClassName:填写的参数是全类名

​ readOnly:设置事务是否为只读事务

​ timeout:事务超出指定执行时长后自动终止并回滚,单位是秒

4、测试超时属性

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(timeout = 3)
public void checkout(String username,int id){

bookDao.updateStock(id);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}
5、设置事务只读

​ 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;

​ 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

​ 对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(timeout = 3,readOnly = true)
public void checkout(String username,int id){

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}
6、设置哪些异常不回滚

​ 注意:运行时异常默认回滚,编译时异常默认不回滚

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class,NullPointerException.class})
public void checkout(String username,int id){

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
int i = 1/0;
}

@Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})
public void checkout(String username,int id){

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
int i = 1/0;
}
}
7、设置哪些异常回滚

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})
public void checkout(String username,int id) throws FileNotFoundException {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
// int i = 1/0;
new FileInputStream("aaa.txt");
}
}
8、设置隔离级别

​ 隔离级别没有接触的同学可以看我之前的事务视频,里面有详细讲解,此处不再赘述。

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(timeout = 3,isolation = Isolation.READ_COMMITTED)
public void checkout(String username,int id) throws FileNotFoundException {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
// int i = 1/0;
new FileInputStream("aaa.txt");
}
}
9、事务的传播特性

事务的传播特性指的是当一个事务方法被

+另一个事务方法调用时,这个事务方法应该如何进行?

spring的事务传播行为一共有7种:

![传播特性](Spring AOP应用之——声明式事务/传播特性.jpg)

10、测试事务的传播特性

BookDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.oi.dao;

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

@Repository
public class BookDao {

@Autowired
JdbcTemplate jdbcTemplate;

/**
* 减去某个用户的余额
* @param userName
* @param price
*/
public void updateBalance(String userName,int price){
String sql = "update account set balance=balance-? where username=?";
jdbcTemplate.update(sql,price,userName);
}

/**
* 按照图书的id来获取图书的价格
* @param id
* @return
*/
public int getPrice(int id){
String sql = "select price from book where id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,id);
}

/**
* 减库存,减去某本书的库存
* @param id
*/
public void updateStock(int id){
String sql = "update book_stock set stock=stock-1 where id=?";
jdbcTemplate.update(sql,id);
}

/**
* 修改图书价格
* @param id
* @param price
*/
public void updatePrice(int id,int price){
String sql = "update book set price=? where id =?";
jdbcTemplate.update(sql,price,id);
}
}

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.oi.service;

import org.springframework.beans.factory.annotation.Autowired;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,int id) {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}

@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
int i = 1/0;
}
}

MulService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.oi.service;

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

@Service
public class MulService {

@Autowired
private BookService bookService;

@Transactional
public void mulTx(){
bookService.checkout("zhangsan",1);
bookService.updatePrice(1,1000);
}
}

MyTest.java

1
2
3
4
5
6
7
8
9
10
11
import com.oi.service.MulService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
MulService mulService = context.getBean("mulService", MulService.class);
mulService.mulTx();
}
}

​ 通过上图的结果发现,如果设置的传播特性是Required,那么所有的事务都会统一成一个事务,一旦发生错误,所有的数据都要进行回滚。


BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,int id) {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}

@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
int i = 1/0;
}
}

​ 通过修改checkout方法的传播特性为Required_new,发现价格进行了回滚,而其他的数据没有进行回滚。


BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,int id) {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}

@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
}
}

MulService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.oi.service;

import com.oi.bean.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MulService {

@Autowired
private BookService bookService;

@Transactional
public void mulTx(){
bookService.checkout("zhangsan",1);
bookService.updatePrice(1,1000);
int i = 1/0;
}
}

​ 将bookservice方法的传播行为为Required,并且将报错设置在MulService中,发现会都进行回滚。


BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,int id) {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
}
}

MulService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.oi.service;

import com.oi.bean.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MulService {

@Autowired
private BookService bookService;

@Transactional
public void mulTx(){
bookService.checkout("zhangsan",1);
bookService.updatePrice(1,1000);
int i = 1/0;
}
}

​ 将bookservice方法的传播行为为Requires_new,并且将报错设置在MulService中,发现都不会进行回滚。


BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.oi.service;

import com.oi.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账:传入哪个用户买了哪本书
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,int id) {

bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
}

@Transactional
public void mulTx(){
checkout("zhangsan",1);
updatePrice(1,1000);
int i = 1/0;
}
}

​ 如果在bookservice执行的话,会发现刚刚的效果就没有了,原因是外层调用的时候使用的AOP,但是本类方法自己的调用就是最最普通的调用,就是同一个事务。

总结:

1
2
3
4
5
6
7
8
9
1、事务传播级别是REQUIRED,当checkout()被调用时(假定被另一类中commit()调用),如果checkout()中的代码抛出异常,即便被捕获,commit()中的其他代码都会roll back

2、是REQUIRES_NEW,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,不会导致checkout()回滚

3、是NESTED,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,会导致checkout()回滚

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

3、基于xml的事务配置

jdbcTemplate.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.oi"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--事务控制-->
<!--配置事务管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
基于xml配置的事务:依赖tx名称空间和aop名称空间
1、spring中提供事务管理器(切面),配置这个事务管理器
2、配置出事务方法
3、告诉spring哪些方法是事务方法(事务切面按照我们的切入点表达式去切入事务方法)
-->
<bean id="bookService" class="com.oi.service.BookService"></bean>
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.oi.service.*.*(..))"/>
<!--事务建议:advice-ref:指向事务管理器的配置-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor>
</aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性-->
<tx:attributes>
<!--指明哪些方法是事务方法-->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
</beans>