Spring 事务管理
事务管理
一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID:
- **原子性:**事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
- **一致性:**这表示数据库的引用完整性的一致性,表中唯一的主键等。
- **隔离性:**可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
- **持久性:**一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。
一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:
- 使用 begin transaction 命令开始事务。
- 使用 SQL 查询语句执行各种删除、更新或插入操作。
- 如果所有的操作都成功,则执行提交操作,否则回滚所有操作。
Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。
局部事务 vs. 全局事务
局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。
局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。
全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。
编程式 vs. 声明式
Spring 支持两种类型的事务管理:
声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用 Spring AOP 框架的声明式事务管理。
Spring 事务抽象
Spring事务管理的五大属性:隔离级别、传播行为、是否只读、事务超时、回滚规则
Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
序号 | 方法 & 描述 |
---|---|
1 | **TransactionStatus getTransaction(TransactionDefinition definition)**根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。 |
2 | **void commit(TransactionStatus status)**该方法提交给定的事务和关于它的状态。 |
3 | **void rollback(TransactionStatus status)**该方法执行一个给定事务的回滚。 |
TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}
序号 | 方法 & 描述 |
---|---|
1 | **int getPropagationBehavior()**该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。 |
2 | **int getIsolationLevel()**该方法返回该事务独立于其他事务的工作的程度。 |
3 | **String getName()**该方法返回该事务的名称。 |
4 | **int getTimeout()**该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。 |
5 | **boolean isReadOnly()**该方法返回该事务是否是只读的。 |
下面是隔离级别的可能值:
序号 | 隔离 & 描述 |
---|---|
1 | TransactionDefinition.ISOLATION_DEFAULT这是默认的隔离级别。 |
2 | TransactionDefinition.ISOLATION_READ_COMMITTED表明能够阻止误读;可以发生不可重复读和虚读。 |
3 | TransactionDefinition.ISOLATION_READ_UNCOMMITTED表明可以发生误读、不可重复读和虚读。 |
4 | TransactionDefinition.ISOLATION_REPEATABLE_READ表明能够阻止误读和不可重复读;可以发生虚读。 |
5 | TransactionDefinition.ISOLATION_SERIALIZABLE表明能够阻止误读、不可重复读和虚读。 |
下面是传播类型的可能值:
序号 | 传播 & 描述 |
---|---|
1 | TransactionDefinition.PROPAGATION_MANDATORY支持当前事务;如果不存在当前事务,则抛出一个异常。 |
2 | TransactionDefinition.PROPAGATION_NESTED如果存在当前事务,则在一个嵌套的事务中执行。 |
3 | TransactionDefinition.PROPAGATION_NEVER不支持当前事务;如果存在当前事务,则抛出一个异常。 |
4 | TransactionDefinition.PROPAGATION_NOT_SUPPORTED不支持当前事务;而总是执行非事务性。 |
5 | TransactionDefinition.PROPAGATION_REQUIRED支持当前事务;如果不存在事务,则创建一个新的事务。 |
6 | TransactionDefinition.PROPAGATION_REQUIRES_NEW创建一个新事务,如果存在一个事务,则把当前事务挂起。 |
7 | TransactionDefinition.PROPAGATION_SUPPORTS支持当前事务;如果不存在,则执行非事务性。 |
8 | TransactionDefinition.TIMEOUT_DEFAULT使用默认超时的底层事务系统,或者如果不支持超时则没有。 |
TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
序号 | 方法 & 描述 |
---|---|
1 | **boolean hasSavepoint()**该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。 |
2 | **boolean isCompleted()**该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。 |
3 | **boolean isNewTransaction()**在当前事务时新的情况下,该方法返回 true。 |
4 | **boolean isRollbackOnly()**该方法返回该事务是否已标记为 rollback-only。 |
5 | **void setRollbackOnly()**该方法设置该事务为 rollback-only 标记。 |
声明式事务管理
声明式事务管理
声明式事务管理方法允许您在配置的帮助下而不是源代码硬编程来管理事务。这意味着您可以将事务管理从事务代码中隔离出来。您可以只使用注释或基于配置的XML来管理事务。bean配置会指定事务型方法。下面是与声明式事务相关的步骤:
- 我们使用标签,它创建一个事务处理的建议,同时,我们定义一个匹配所有方法的切入点,我们希望这些方法是事务类型的并且会引用事务类型的建议。
- 如果在事务型配置中包含一个方法的名称,那么创建的建议在调用方法之前就会在事务中开始进行。
- 目标方法会在try / catch块中执行。
- 如果方法正常结束,AOP建议会成功的提交事务,否则它执行回滚操作。
让我们看看上述步骤是如何实现的。在我们开始之前,至少有两个数据库表是至关重要的,在事务的帮助下,我们可以实现各种CRUD操作。以学生表为例,该表是使用预设DDL在MySQL TEST数据库中创建的。
CREATE TABLE Student(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);
第二个表是Marks,我们用来存储基于老式的学生标记。在这里,SID是Student表的外键。
CREATE TABLE Marks(
SID INT NOT NULL,
MARKS INT NOT NULL,
YEAR INT NOT NULL
);
现在让我们编写Spring JDBC应用程序来在Student和Marks表中实现简单的操作。让我们适当的使用Eclipse IDE,并按照如下所示的步骤来创建一个Spring应用程序:
步骤 | 描述 |
---|---|
1个 | 创建一个名为SpringExample的项目,并在创建的项目中的src文件夹下创建包com.tutorialspoint。 |
2个 | 使用添加外部JAR选项选择必需的Spring库,解释见Spring Hello World示例一章。 |
3 | 在项目中添加其他必需的库mysql-connector-java.jar,org.springframework.jdbc.jar和org.springframework.transaction.jar。 |
4 | 创建DAO接口StudentDAO并列出所有需要的方法。尽管它不是必需的并且你可以直接编写StudentJDBCTemplate类,但是作为一个好的实践,我们还是做吧。 |
5 | 在com.tutorialspoint包下创建其他必需的Java的类StudentMarks,StudentMarksMapper,StudentJDBCTemplate和MainApp。如果需要的话,你可以创建其他的POJO类。 |
6 | 确保您已经在TEST数据库中创建了Student和Marks表。还要确保您的MySQL服务器运行正常并且您使用的是用户的名和密码可以读/写访问数据库。 |
7 | 在src文件夹下创建Beans配置文件Beans.xml。 |
8 | 最后一步是创建所有Java文件和Bean配置文件的内容并按照如下所示的方法运行应用程序。 |
英文下面数据访问对象接口文件StudentDAO.java的内容:
package com.tutorialspoint;
import java.util.List;
import javax.sql.DataSource;
public interface StudentDAO {
/**
* This is the method to be used to initialize
* database resources ie. connection.
*/
public void setDataSource(DataSource ds);
/**
* This is the method to be used to create
* a record in the Student and Marks tables.
*/
public void create(String name, Integer age, Integer marks, Integer year);
/**
* This is the method to be used to list down
* all the records from the Student and Marks tables.
*/
public List<StudentMarks> listStudents();
}
以下是StudentMarks.java文件的内容:
package com.tutorialspoint;
public class StudentMarks {
private Integer age;
private String name;
private Integer id;
private Integer marks;
private Integer year;
private Integer sid;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setMarks(Integer marks) {
this.marks = marks;
}
public Integer getMarks() {
return marks;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public Integer getSid() {
return sid;
}
}
的英文下面StudentMarksMapper.java文件的内容:
package com.tutorialspoint;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class StudentMarksMapper implements RowMapper<StudentMarks> {
public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
StudentMarks studentMarks = new StudentMarks();
studentMarks.setId(rs.getInt("id"));
studentMarks.setName(rs.getString("name"));
studentMarks.setAge(rs.getInt("age"));
studentMarks.setSid(rs.getInt("sid"));
studentMarks.setMarks(rs.getInt("marks"));
studentMarks.setYear(rs.getInt("year"));
return studentMarks;
}
}
下面是定义的DAO接口StudentDAO实现类文件StudentJDBCTemplate.java:
package com.tutorialspoint;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
public class StudentJDBCTemplate implements StudentDAO{
private JdbcTemplate jdbcTemplateObject;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public void create(String name, Integer age, Integer marks, Integer year){
try {
String SQL1 = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL1, name, age);
// Get the latest student id to be used in Marks table
String SQL2 = "select max(id) from Student";
int sid = jdbcTemplateObject.queryForInt( SQL2 );
String SQL3 = "insert into Marks(sid, marks, year) " +
"values (?, ?, ?)";
jdbcTemplateObject.update( SQL3, sid, marks, year);
System.out.println("Created Name = " + name + ", Age = " + age);
// to simulate the exception.
throw new RuntimeException("simulate Error condition") ;
} catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
throw e;
}
}
public List<StudentMarks> listStudents() {
String SQL = "select * from Student, Marks where Student.id=Marks.sid";
List <StudentMarks> studentMarks=jdbcTemplateObject.query(SQL,
new StudentMarksMapper());
return studentMarks;
}
}
现在让我们改变主应用程序文件MainApp.java,如下所示:
package com.tutorialspoint;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
StudentDAO studentJDBCTemplate =
(StudentDAO)context.getBean("studentJDBCTemplate");
System.out.println("------Records creation--------" );
studentJDBCTemplate.create("Zara", 11, 99, 2010);
studentJDBCTemplate.create("Nuha", 20, 97, 2010);
studentJDBCTemplate.create("Ayan", 25, 100, 2011);
System.out.println("------Listing all the records--------" );
List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
for (StudentMarks record : studentMarks) {
System.out.print("ID : " + record.getId() );
System.out.print(", Name : " + record.getName() );
System.out.print(", Marks : " + record.getMarks());
System.out.print(", Year : " + record.getYear());
System.out.println(", Age : " + record.getAge());
}
}
}
以下是配置文件Beans.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- Initialization for data source -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
<property name="username" value="root"/>
<property name="password" value="cohondob"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="create"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="createOperation"
expression="execution(* com.tutorialspoint.StudentJDBCTemplate.create(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
</aop:config>
<!-- Initialization for TransactionManager -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Definition for studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate"
class="com.tutorialspoint.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
当您完成了创建源和bean配置文件后,让我们运行应用程序。如果您的应用程序运行顺利的话,那么会输出如下所示的异常。在这种情况下,事务会回滚和在数据库表中不会创造任何记录。
------Records creation--------
Created Name = Zara, Age = 11
Exception in thread "main" java.lang.RuntimeException: simulate Error condition
在删除异常后,您可以尝试上述示例,在这种情况下,会提交事务并且您可以在数据库中看到一条记录