Spring事务(Transaction)

事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。

在现实世界中,最常见的与事务相关的例子可能就是银行转账了。假设我们需要将 1000 元从 A 账户中转到 B 账户中,这个转账操作共涉及了以下两个操作。

  • 从 A 账户中扣除 1000 元;
  • 往 B 账户中存入 1000 元。

如果 A 账户成功地扣除了 1000 元,但向 B 账户存入时失败的话,那么我们将凭空损失 1000 元;如果 A 账户扣款时失败,但却成功地向 B 账户存入 1000 元的话,我们的账户就凭空多出了 1000 元,那么银行就会遭受损失。因此我们必须保证事务中的所有操作要么全部成功,要么全部失败,理解了这一点,我们也就抓住了事务的核心。

作为一款优秀的开源框架和应用平台,Spring 也对事务提供了很好的支持。Spring 借助 IoC 容器强大的配置能力,为事务提供了丰富的功能支持。

场景引入 :银行转账

image-20220724195226489

  • 如果中间出现了异常,就会导致上面的执行了,但是下面的没有执行

方法: 引入事务, 如果中间出现了异常,就进行事务回滚

使用@Transactional

User.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
package pers.dhx_.pojo;
import org.springframework.stereotype.Component;
@Component
public class User {
private int id;
private String name;
private double money;
public User(){}

public User(int id, String name, double money) {
this.id = id;
this.name = name;
this.money = money;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}

}

UserDAO.java

1
2
3
4
5
6
package pers.dhx_.DAO;
import pers.dhx_.pojo.User;
public interface UserDAO {
public void addMoney(User user,double money);
public void removeMoney(User user,double money);
}

UserDAOImpl.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 pers.dhx_.DAO.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import pers.dhx_.DAO.UserDAO;
import pers.dhx_.pojo.User;
@Repository
public class UserDAOImpl implements UserDAO {
// 注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney(User user,double money ) {
String sql="update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,money,user.getName());
}
@Override
public void removeMoney(User user,double money) {
String sql="update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,money,user.getName());
}
}

UserService.java

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pers.dhx_.DAO.Impl.UserDAOImpl;
import pers.dhx_.DAO.UserDAO;
import pers.dhx_.pojo.User;
@Service
public class UserService {
///注入DAO
@Autowired
private UserDAO userDAO;
public void transferMoney(User u1,User u2,double money)
{
userDAO.removeMoney(u1,money); // u1 少钱
userDAO.addMoney(u2,money); // u2多钱
}
public void setUserDAO(UserDAOImpl userDAO) {
this.userDAO = userDAO;
}
}

bean1.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
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--开启组件扫描-->
<context:component-scan base-package="pers.dhx_"></context:component-scan>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///userdb"/>
<property name="username" value="root"/>
<property name="password" value="qwer"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>

<!-- JdbcTemplate对象 -->
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/> <!--注入数据源-->
</bean>

</beans>

Test

1
2
3
4
5
6
7
8
9
@Test
public void AccountTest()
{
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean1.xml");
User lucy=new User(1,"lucy",1000);
User mary=new User(2,"mary",1000);
UserService userService=applicationContext.getBean("userService",UserService.class);
userService.transferMoney(lucy,mary,100);
}

事务管理方式

Spring 支持以下 2 种事务管理方式。

事务管理方式 说明
编程式事务管理 编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
声明式事务管理 Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

  • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

Spring 的声明式事务管理主要通过以下 2 种方式实现:

事务操作

一、基于注解的声明式事务管理

  1. 在spring配置文件配置事务管理器
1
2
3
4
5
6
7
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  1. 在spring配置文件引入命名空间tx

    • 注意包含tx 的 内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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 http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  2. 在service类上面(获取service类里面方法上面添加事务注解)

    @Transactional

    • 可以加到类上面,也可以加到方法上面。
    • 添加到类上面,相当于为这个类里面的所有方法添加事务
    • 添加到方法上面,相当于为这个方法添加事务

1、事务传播行为

@Transactional(transactionManager = "REQUIRED")

@Transactional默认的传播行为是REQUIRED,且一般情况下只用REQUIRED和REQUIRES_NEW,其他了解即可

  1. REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务。这是spring默认的传播行为。
  2. NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
  3. REQUIRESNEW:不管是否存在事务,该方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
  4. MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
  5. SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
  6. NEVER:该方法绝对不能在事务范围内执行。如果在就抛异常。只有该方法没有关联到任何事务,才正常执行。
  7. NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

事务管理器

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。

1
2
3
4
5
6
7
public interface PlatformTransactionManager extends TransactionManager {    
Transaction Status getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
}

该接口中各方法说明如下:

名称 说明
TransactionStatus getTransaction(TransactionDefinition definition) 用于获取事务的状态信息
void commit(TransactionStatus status) 用于提交事务
void rollback(TransactionStatus status) 用于回滚事务

Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。

实现类 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
org.springframework.orm.hibernate3.HibernateTransactionManager 使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
org.springframework.orm.jpa.JpaTransactionManager 使用 JPA 进行持久化时使用。
org.springframework.jdo.JdoTransactionManager 当持久化机制是 Jdo 时使用。
org.springframework.transaction.jta.JtaTransactionManager 使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。

TransactionDefinition 接口

Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。

1
2
3
4
5
6
public interface TransactionDefinition {    
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();}

该接口中方法说明如下。

方法 说明
String getName() 获取事务的名称
int getIsolationLevel() 获取事务的隔离级别
int getPropagationBehavior() 获取事务的传播行为
int getTimeout() 获取事务的超时时间
boolean isReadOnly() 获取事务是否只读

2、事务的隔离级别

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。

在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致==脏读、幻读以及不可重复读==等问题的出现。

在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。

Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。

方法 说明
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTED Oracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READ MySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

关于事务隔离级别、脏读、幻读、不可重复度等概念的详细介绍,请阅读《数据库事务隔离级别》一节。

🎈4个级别

1.读未提交:==read uncommitted==

(最低的隔离级别)《没有提交就读到了》

什么是读未提交?
事务A可以读取到事务B未提交的数据。
这种隔离级别存在的问题就是:脏读现象!(Dirty Read)
我们称读到了脏数据。

这种隔离级别一般都是理论上的,大多数的数据库隔离级别都是二档起步!

2.读已提交:==read committed==

《提交之后才能读到》

  • 什么是读已提交?

    事务A只能读取到事务B提交之后的数据。

  • 这种隔离级别解决了什么问题?
    解决了脏读的现象。

  • 这种隔离级别存在什么问题?
    不可重复读取数据。

  • 什么是不可重复读取数据呢?
    在事务开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次再读取的时候,读到的数据是4条,3不等于4称为不可重复取。

    这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实。oracle数据库默认的隔离级别是:read committed

3.可重复读:==repeatable read== -->MySQL默认

提交之后也读不到,永远读取的都是刚开启事务时的数据

  1. 什么是可重复读取?
    事务A开启之后,不管是多久,每一次在事务A中读取到的数据
    都是一致的。即使事务B将数据已经修改,并且提交了,事务A
    读取到的数据还是没有发生改变,这就是可重复读。

  2. 可重复读解决了什么问题?
    解决了不可重复读取数据。

  3. 可重复读存在的问题是什么?
    可以会出现幻影读。
    每一次读取到的数据都是幻象。不够真实!

    早晨9点开始开启了事务,只要事务不结束,到晚上9点,读到的数据还是那样!
    读到的是假象。不够绝对的真实。

    mysql中默认的事务隔离级别就是==repeatable read==

4.序列化/串行化:==serializable==

(最高的隔离级别)

  • 最高隔离级别,效率最低。解决了所有的问题。
    这种隔离级别表示事务排队,不能并发!
    synchronized,线程同步(事务同步)
    每一次读取到的数据都是最真实的,并且效率是最低的。

image-20220629122720382

会卡住没有反应,直到另一个事务 commit

查看隔离级别:

MySQL 8 之前:SELECT @@tx_isolation

MySQL 8 之后:select @@transaction_isolation;

1
2
3
4
5
+----------------------+
| @@transaction_isolation |
+----------------------+
| REPEATABLE-READ |
+----------------------+

MySQL默认的隔离级别为repeatable read

隔离: isolation

修改事务隔离级别

1
2
3
4
5
6
7
set global transaction isolation level read uncommitted
设置 全局 事务 隔离 等级 读 未提交
set global transaction isolation level read committed

set global transaction isolation level repeatable read

set global transaction isolation level serializable

3、事务超时

  • 默认是 -1

设置事务超时时间

1
@Transactional (propagation = Propagation.REQUIRED,timeout=30)

Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。

设置了超时时间,如DataSourceTransactionManager首先开启事物会调用其doBegin方法:

1
2
3
4
int timeout = determineTimeout(definition);  
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}

其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着ResourceHolderSupport的setTimeoutInSeconds实现:

4、事务的只读readonly

readOnly=true只读,不能更新,删除

1
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)   

注意:一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务。

由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。

1、在JDBC中,指定只读事务的办法为: connection.setReadOnly(true);

2、在Hibernate中,指定只读事务的办法为: session.setFlushMode(FlushMode.NEVER);

此时,Hibernate也会为只读事务提供Session方面的一些优化手段

3、在Spring的Hibernate封装中,指定只读事务的办法为: bean配置文件中,prop属性增加“readOnly”,或者用注解方式@Transactional(readOnly=true)

【 if the transaction is marked as read-only, Spring will set the Hibernate Session’s flush mode to FLUSH_NEVER, and will set the JDBC transaction to read-only】也就是说在Spring中设置只读事务是利用上面两种方式。

1
@Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)

TransactionStatus 接口

TransactionStatus 接口提供了一些简单的方法,来控制事务的执行、查询事务的状态,接口定义如下。

1
2
3
4
5
6
7
public interface TransactionStatus extends SavepointManager {    
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}

该接口中各方法说明如下。

名称 说明
boolean hasSavepoint() 获取是否存在保存点
boolean isCompleted() 获取事务是否完成
boolean isNewTransaction() 获取是否是新事务
boolean isRollbackOnly() 获取事务是否回滚
void setRollbackOnly() 设置事务回滚

二、基于xml文件的声明式事务管理配置


1️⃣配置事务管理器

  • 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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>

2️⃣配置通知

propagation :传播;扩展;宣传;培养 —> 事务传播

pointcut : 切入点

advisor : 顾问;提供意见者 — > 通知

1
2
3
4
5
6
7
8
9
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>

3️⃣配置切入点和切面

1
2
3
4
5
6
7
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

总览

bean.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
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>

<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>

三、完全注解开发

  • 依然是创建配置类,使用配置类替代xml文件
  • @Configuration

ctrl+ enter 下方添加行 ctrl+shift+BackSpace 下方删除行

使用到的注解 用处
@Configuration 表明这个类是一个配置类
@ComponentScan(basePackages = “com.atguigu”) 组件扫描
@EnableTransactionManagement 开启事务
@Bean 告诉方法,产生一个Bean对象,这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

TxConfig.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
package com.atguigu.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}

//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

//创建事务管理器
// @Bean
// public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
// DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
// transactionManager.setDataSource(dataSource);
// return transactionManager;
// }
}

用到的注解

使用到的注解 作用
@Configuration 表明这个类是一个配置类
@ComponentScan(basePackages = “com.atguigu”) 开启组件扫描
@EnableTransactionManagement 开启事务
@Bean 告诉方法,产生一个Bean对象,这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。