Spring Transaction
事务,简称TX,简单说就是一系列的动作,被当作一个独立的单元,这个单元 要么成功,要么失败。通常是对数据库中数据的一系列读/写操作
在企业级应用程序开发中是必不可少的技术之一,用来确保数据的正确性和一致性。
ACID特性
事务的四大特性:
- 原子性(Atomicity):事务作为一个整体单元被执行,要么成功,要么就失败。
- 一致性(Consistency):数据一致性,如:转账业务中,转账前后总金额应该是一致的。
- 隔离性(Isolation):在多个事务并发执行时,一个事务的执行不应该影响其他的事物执行。
- 持久性(Durability):已提交的事务对于数据库的修改应该永久性的保存在数据库中。
Spring中的事务
1. 编程式的事务
2. 声明式的事务
编程式事务 ,可以自己控制事务的开启、提交、回滚等操作,但是不便于维护。必须在每个事务操作中包含另外的事务管理代码。因此要在每个操作中重复样板事务代码。此外还很难对不同的应用程序启用和禁用事务管理。
声明式事务,在大多数情况下要比编程式事务管理更好用。它将事务管理代码从业务中分离出来,以声明的方式来实现事务管理。将事务管理作为一种横切关注点,可以通过AOP方法模块化。但是,声明式事务管理不太灵活,因为无法通过代码精确的控制事务。
编程式事务此处不讲,主要是声明式事务。
先来一个简单的实例:转账业务
一个简单实例
1、数据库
-- Create table
create table DEMO_ACCOUNT
(
NAME VARCHAR2(20) not null, --账户名
BALANCE NUMBER --账户下的余额
)
数据:
2、配置文件
配置数据源
<?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:mvc="http://www.springframework.org/schema/mvc"
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-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd ">
<!-- 自动扫描 -->
<context:component-scan base-package="com.wm.spring.tx"></context:component-scan>
<!-- 导入外部配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- c3p0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxPoolSize" value="${c3p0.pool.size.max}"></property>
<property name="minPoolSize" value="${c3p0.pool.size.min}"></property>
<property name="initialPoolSize" value="${c3p0.pool.size.ini}"></property>
</bean>
<!-- spring jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 添加 事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
在配置文件中 要添加事务管理器 及其 事务注解驱动,然后才能去使用 事务注解@transactional。
在配置文件中,要添加事务transaction(TX)的命名空间及约束
xmlns:tx="http://www.springframework.org/schema/tx"
及
xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd ">
jdbc.properties
jdbc.driverClassName = oracle.jdbc.OracleDriver
jdbc.url = jdbc:oracle:thin:@10.5.1.50:1521:cddev
jdbc.username = base_55demo
jdbc.password = crm_12345
c3p0.pool.size.max=20
c3p0.pool.size.min=5
c3p0.pool.size.ini=3
3、DAO
dao层使用的是jdbcTemplate,它的使用方法就不再讲解,如果有不懂之处,可以去参考 前篇文章
AccountDAO.java类:
package com.wm.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDAO {
@Autowired
private JdbcTemplate jdbcTemplate ;
// 账户 收钱
public void income(String name, int money){
String sql = "update demo_account set balance = balance + ? where name = ?";
jdbcTemplate.update(sql, money, name);
System.out.println(name + ": 收入增加了 "+money);
}
// 账户 转钱
public void spend(String name, int money) {
String query = "select balance from demo_account where name = ?";
Integer balance = jdbcTemplate.queryForObject(query, Integer.class, name);
if (balance < money) {
throw new RuntimeException("账户:" + name + "余额不足 ! " + balance + " < " + money);
}
String sql = "update demo_account set balance = balance - ? where name = ?";
jdbcTemplate.update(sql, money, name);
System.out.println(name + ": 余额减少了 "+money);
}
}
dao中 实现的事务中的一些对数据的操作。
4、service
service中可以对 方法添加事务的注解@transactional ,至此就具备了事务的功能
AccountSVImpl.java类:
package com.wm.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountSVImpl {
@Autowired
private AccountDAO dao ;
// 转账 // 账户accB 跟 账号 accA 转钱 money
// 添加事务注解
@Transactional
public void transfer(String accA, String accB, int money){
dao.income(accA, money); //A的余额增加
dao.spend(accB, money); //B的余额减少
}
}
@transactional 注解是 使一个方法具有事务的特性。
5、测试
TransactionTest.java类:
package com.wm.spring.TEST;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.wm.spring.tx.AccountSVImpl;
public class TransactionTest {
private ApplicationContext ac = null ;
private AccountSVImpl sv = null ;
@Before
public void init(){
ac = new ClassPathXmlApplicationContext("spring-transaction.xml");
sv = ac.getBean(AccountSVImpl.class);
}
@Test
public void transfer(){
sv.transfer("JACK", "TOM", 500); // TOM 向 JACK 转 500元钱
}
}
测试结果: TOM向JACK转入500元,但是TOM余额只有400,所以 失败。整个事务都要回滚,数据库中的数据并没有改变。