事务管理的概念
数据处理系统的操作应该满足ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
- 原子性:在数据上执行的多个操作必须全部执行或者提交,任何一个失败,所有操作都必须回滚。
- 一致性:一个活动失误结束后底层数据库必须处于一致状态。
- 隔离性:定义如何保护未提交数据受其他并发事务影响。
- 持久性:提交时更改变成永久的不会丢失。
首先用JDBC API直接管理一个事务
本章所用的maven依赖如下文件:
<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.lonelyquantum.springbeginning.wileybookch4</groupId>
<artifactId>JDBCTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JDBCTest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.10.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<h2.version>1.4.196</h2.version>
<aspectj.version>1.8.10</aspectj.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
首先在数据库中用如下语句创建一个ACCOUNT表:
CREATE TABLE ACCOUNT(
ID BIGINT IDENTITY PRIMARY KEY,
OWNER_NAME VARCHAR(255),
BALANCE DOUBLE,
ACCESS_TIME TIMESTAMP,
LOCKED BOOLEAN
);
然后向其中插入两个账户条目:
INSERT INTO ACCOUNT(ID, OWNER_NAME, BALANCE, ACCESS_TIME, LOCKED) VALUES (100, 'Madoka Kaname', 10, '2017-08-30', false);
INSERT INTO ACCOUNT(ID, OWNER_NAME, BALANCE, ACCESS_TIME, LOCKED) VALUES (101, 'Homura Akemi', 20, '2017-08-30', false);
然后定义一个带有转账方法的AccountService接口,该方法将指定数额钱从源账号转到目标账号:
public interface AccountService {
public void transferMoney(long sourceAccountId, long targetAccountId,
double amount);
}
接下来创建实现AccountService接口的类AccountServiceJdbcTxImpl
public class AccountServiceJdbcTxImpl implements AccountService {
public void transferMoney(long sourceAccountId, long targetAccountId,
double amount) {
Connection connection = null;
try {
DriverManager.registerDriver(new Driver());
connection = DriverManager.getConnection(
"jdbc:h2:tcp://localhost/~/test", "sa", "");
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
statement.executeUpdate("update account set balance = balance - " + amount + " where id = " + sourceAccountId);
statement.executeUpdate("update account set balance = balance + " + amount + " where id = " + targetAccountId);
connection.commit();
} catch (SQLException e) {
try {
connection.rollback();
} catch (SQLException ex) {}
throw new RuntimeException(e);
} finally {
try {
connection.close();
} catch (SQLException ex) {}
}
}
}
其中connection.setAutoCommit(false)标记了JDBC API定义的事务边界的起点。操作处理结束后调用connection.commit(),表明事务划分的成功终止,更改已经反映到数据库中。如果中途捕捉到异常,将会调用connection.rollback()进行回滚抛弃目前所做的一切更改。最后无论失败与成功都关闭连接。
然后我们就可以在Main函数中调用实现类的方法了:
public class Main {
public static void main(String[] args) {
AccountService accountService = new AccountServiceJdbcTxImpl();
accountService.transferMoney(100L,101L, 5.0d);
}
}
执行前各账户状况:
执行后各账户情况:
转账成功!
Spring 的事务抽象模型
Spring的事务抽象模型,基于PlatformTransactionManager接口,且有不同实现对应各种数据访问技术。于是决定更改数据访问策略时,不需要修改事务代码,只需要修改transactionManager的Bean定义。
首先创建如下Bean配置类:
@Configuration
public class Ch6Configuration {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:tcp://localhost/~/test");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
}
定义了dataSource和transactionManager Bean。
然后在主函数中创建Spring容器并检查transactionManager Bean的配置是否有效。
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Ch6Configuration.class);
PlatformTransactionManager transactionManager = applicationContext.getBean(PlatformTransactionManager.class);
System.out.println(transactionManager != null);
}
}
结果为有效:
说明事务管理Bean配置成功。
本地事务与全局事务
- 本地事务:仅控制单一数据库上执行的DML操作。
- 全局事务:一个事务可能涉及多个数据库。
JEE使用采用了2PC策略的JTA来处理事务管理,为不付出该策略的性能代价,可借助Spring的抽象平台事务模型,先使用本地事务在轻量级Web容器上运行,当需要使用全局事务时再进行更改。
2PC(Two Phase Commitment Protocol)
实现分布式事务的关键就是两阶段提交协议。在此协议中,一个或多个资源管理器的活动均由一个称为事务协调器的单独软件组件来控制。此协议中的五个步骤如下:
应用程序调用事务协调器中的提交方法 。
事务协调器将联络事务中涉及的每个资源管理器,并通知它们准备提交事务(这是第一阶段的开始)。
为了以肯定的方式响应准备阶段,资源管理器必须将自己置于以下状态:确保能在被要求提交事务时提交事务,或在被要求回滚事务时回滚事务。大多数资源管理器会 将包含其计划更改的日记文件(或等效文件)写入持久存储区中。如果资源管理器无法准备事务,它会以一个否定响应来回应事务协调器。
事务协调器收集来自资源管理器的所有响应。
在 第二阶段,事务协调器将事务的结果通知给每个资源管理器。如果任一资源管理器做出否定响应,则事务协调器会将一个回滚命令发送给事务中涉及的所有资源管理 器。如果资源管理器都做出肯定响应,则事务协调器会指示所有的资源管理器提交事务。一旦通知资源管理器提交,此后的事务就不能失败了。通过以肯定的方式响 应第一阶段,每个资源管理器均已确保,如果以后通知它提交事务,则事务不会失败。
PlatformTransactionManager的实现
- DataSourceTransactionManager:在仅使用JDBC时适用。
- JpaTransactionManager:在使用JPA时使用,同时在实现时还可能使用JDBC。
- HibernateTransactionManager:在使用Hibernate而没有JPA时适用,同时在实现时还可能使用JDBC。
- JdoTransactionManager:在使用JDO时适用,同时在实现时还可使用JDBC。
- JtaTransactionManager:使用全局事务时适用,可以使用任何数据访问技术。
Spring抽象事务模型优点
- 能在相同应用程序中使用不同数据访问技术,以及同时使用声明式和编程式事务模型。
- 需要更改数据访问技术时只要更改transactionManager Bean定义即可。
- 很容易在本地事务和全局事务之间切换。