数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。
在我们日常工作中,涉及到事务的场景非常多,一个 service 中往往需要调用不同的 dao 层方法,这些方法要么同时成功要么同时失败,我们需要在 service 层确保这一点。
说到事务最典型的案例就是转账了:
张三要给李四转账 500 块钱,这里涉及到两个操作,从张三的账户上减去 500 块钱,给李四的账户添加 500 块钱,这两个操作要么同时成功要么同时失败,如何确保他们同时成功或者同时失败呢?答案就是事务。
事务有四大特性(ACID):
这就是事务的四大特性。
Spring 作为 Java 开发中的基础设施,对于事务也提供了很好的支持,总体上来说,Spring 支持两种类型的事务,声明式事务和编程式事务。
编程式事务类似于 Jdbc 事务的写法,需要将事务的代码嵌入到业务逻辑中,这样代码的耦合度较高,而声明式事务通过 AOP 的思想能够有效的将事务和业务逻辑代码解耦,因此在实际开发中,声明式事务得到了广泛的应用,而编程式事务则较少使用,考虑到文章内容的完整,本文对两种事务方式都会介绍。
Spring 中对事务的支持提供了三大基础设施,我们先来了解下。
这三个核心类是 Spring 处理事务的核心类。
PlatformTransactionManager 是事务处理的核心,它有诸多的实现类,如下:
PlatformTransactionManager 的定义如下:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
可以看到 PlatformTransactionManager
中定义了基本的事务操作方法,这些事务操作方法都是平台无关的,具体的实现都是由不同的子类来实现的。
这就像 JDBC 一样,SUN 公司制定标准,其他数据库厂商提供具体的实现。这么做的好处就是我们 Java 程序员只需要掌握好这套标准即可,不用去管接口的具体实现。以 PlatformTransactionManager
为例,它有众多实现,如果你使用的是 JDBC 那么可以将 DataSourceTransactionManager
作为事务管理器;如果你使用的是 Hibernate,那么可以将 HibernateTransactionManager
作为事务管理器;如果你使用的是 JPA,那么可以将 JpaTransactionManager
作为事务管理器。DataSourceTransactionManager
、HibernateTransactionManager
以及 JpaTransactionManager
都是 PlatformTransactionManager
的具体实现,但是我们并不需要掌握这些具体实现类的用法,我们只需要掌握好 PlatformTransactionManager
的用法即可。
PlatformTransactionManager
中主要有如下三个方法:
1.getTransaction()
getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象,TransactionDefinition 中定义了一些事务的基本规则,例如传播性、隔离级别等。
2.commit()
commit() 方法用来提交事务。
3.rollback()
rollback() 方法用来回滚事务。
TransactionDefinition
用来描述事务的具体规则,也称作事务的属性。事务有哪些属性呢?看下图:
可以看到,主要是五种属性:
这五种属性接下来松哥会和大家详细介绍。
TransactionDefinition
类中的方法如下:
可以看到一共有五个方法:
TransactionDefinition 也有诸多的实现类,如下:
如果开发者使用了编程式事务的话,直接使用 DefaultTransactionDefinition
即可。
TransactionStatus 可以直接理解为事务本身,该接口源码如下:
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
这就是 Spring 中支持事务的三大基础设施。
我们先来看看编程式事务怎么玩。
通过 PlatformTransactionManager 或者 TransactionTemplate 可以实现编程式事务。如果是在 Spring Boot 项目中,这两个对象 Spring Boot 会自动提供,我们直接使用即可。但是如果是在传统的 SSM 项目中,则需要我们通过配置来提供这两个对象,松哥给一个简单的配置参考,如下(简单起见,数据库操作我们使用 JdbcTemplate):
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_tran?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
有了这两个对象,接下来的代码就简单了:
@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager txManager;
public void transfer() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus status = txManager.getTransaction(definition);
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
txManager.commit(status);
} catch (DataAccessException e) {
e.printStackTrace();
txManager.rollback(status);
}
}
}
这段代码很简单,没啥好解释的,在 try...catch...
中进行业务操作,没问题就 commit,有问题就 rollback。如果我们需要配置事务的隔离性、传播性等,可以在 DefaultTransactionDefinition 对象中进行配置。
上面的代码是通过 PlatformTransactionManager 实现的编程式事务,我们也可以通过 TransactionTemplate 来实现编程式事务,如下:
@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
TransactionTemplate tranTemplate;
public void transfer() {
tranTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
} catch (DataAccessException e) {
status.setRollbackOnly();
e.printStackTrace();
}
}
});
}
}
直接注入 TransactionTemplate,然后在 execute 方法中添加回调写核心的业务即可,当抛出异常时,将当前事务标注为只能回滚即可。注意,execute 方法中,如果不需要获取事务执行的结果,则直接使用 TransactionCallbackWithoutResult 类即可,如果要获取事务执行结果,则使用 TransactionCallback 即可。
这就是两种编程式事务的玩法。
编程式事务由于代码入侵太严重了,因为在实际开发中使用的很少,我们在项目中更多的是使用声明式事务。
声明式事务如果使用 XML
配置,可以做到无侵入;如果使用 Java
配置,也只有一个 @Transactional
注解侵入而已,相对来说非常容易。
以下配置针对传统 SSM 项目(因为在 Spring Boot 项目中,事务相关的组件已经配置好了):
XML 配置声明式事务大致上可以分为三个步骤,如下:
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_tran?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="m3"/>
<tx:method name="m4"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc1" expression="execution(* org.javaboy.demo.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>
第二步和第三步中定义出来的方法交集,就是我们要添加事务的方法。
配置完成后,如下一些方法就自动具备事务了:
public class UserService {
public void m3(){
jdbcTemplate.update("update user set money=997 where username=?", "zhangsan");
}
}
我们也可以使用 Java 配置来实现声明式事务:
@Configuration
@ComponentScan
//开启事务注解支持
@EnableTransactionManagement
public class JavaConfig {
@Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setPassword("123");
ds.setUsername("root");
ds.setUrl("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai");
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
return ds;
}
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
这里要配置的东西其实和 XML 中配置的都差不多,最最关键的就两个:
配置完成后,接下来,哪个方法需要事务就在哪个方法上添加 @Transactional
注解即可,向下面这样:
@Transactional(noRollbackFor = ArithmeticException.class)
public void update4() {
jdbcTemplate.update("update account set money = ? where username=?;", 998, "lisi");
int i = 1 / 0;
}
当然这个稍微有点代码入侵,不过问题不大,日常开发中这种方式使用较多。当@Transactional
注解加在类上面的时候,表示该类的所有方法都有事务,该注解加在方法上面的时候,表示该方法有事务。
也可以 Java 代码和 XML 混合配置来实现声明式事务,就是一部分配置用 XML 来实现,一部分配置用 Java 代码来实现:
假设 XML 配置如下:
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--
开启事务的注解配置,添加了这个配置,就可以直接在代码中通过 @Transactional 注解来开启事务了
-->
<tx:annotation-driven />
</beans>
那么 Java 代码中的配置如下:
@Configuration
@ComponentScan
@ImportResource(locations = "classpath:applicationContext3.xml")
public class JavaConfig {
@Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setPassword("123");
ds.setUsername("root");
ds.setUrl("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai");
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
return ds;
}
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
Java 配置中通过 @ImportResource 注解导入了 XML 配置,XML 配置中的内容就是开启 @Transactional
注解的支持,所以 Java 配置中省略了 @EnableTransactionManagement 注解。
这就是声明式事务的几种配置方式。好玩吧!
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。