一、事务简介:来自百度百科
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和endtransaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(endtransaction)之间执行的全体操作组成。
例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性( durability )。持久性也称永久性( permanence ),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
个人的理解事务是对数据库的一组操作,这一组操作是密不可分的,是必须一起完成的。比如说转账事务:有A和B两个账户,
A账户向B账户转钱的话,分为两个操作,一个是A账户减去一定数目的钱,另一个是B账户增加一定数目的钱。
对于转账事务,这两个操作是必须要一起执行的。
举例说明:
1.新建工程Spring405;
2.在mysql中新建数据库db_bank,并新建数据表t_account:在t_account中添加数据如下
这里有两个账户,里面分别有500块钱。
3.新建数据库操作接口BankDao:
package com.test.dao;
public interface BankDao {
public void inMoney(int money,int userId);
public void outMoney(int money,int userId);
}
这里有两个操作,一个是给userId转入钱,一个是userId转出钱
4.新建数据库操作实现类:
package com.test.dao.impl;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import com.test.dao.BankDao;
public class BankDaoImpl implements BankDao{
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setNamedParameterJdbcTemplate(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
@Override
public void inMoney(int money, int userId) {
// TODO Auto-generated method stub
String sql="update t_account set count=count+:money where userId=:userId";
MapSqlParameterSource sps=new MapSqlParameterSource();
sps.addValue("money", money);
sps.addValue("userId", userId);
namedParameterJdbcTemplate.update(sql,sps);
}
@Override
public void outMoney(int money, int userId) {
// TODO Auto-generated method stub
String sql="update t_account set count=count-:money where userId=:userId";
MapSqlParameterSource sps=new MapSqlParameterSource();
sps.addValue("money", money);
sps.addValue("userId", userId);
namedParameterJdbcTemplate.update(sql,sps);
}
}
这里使用NamedParameterJdbcTemplate类来完成数据库操作。
4.新建Service接口:
package com.test.service;
public interface BankService {
/**
* A向B转账count元
* @param count
* @param userIdA
* @param userIdB
*/
public void transferAccounts(int count,int userIdA,int userIdB);
}
这个Service中只有一个A向B转钱的功能。
5.新建Service接口实现类:
package com.test.service.impl;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.test.dao.BankDao;
import com.test.service.BankService;
public class BankServiceImpl implements BankService{
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
@Override
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
这里A向B转钱功能,分为两个操作:A的账户减掉钱,B的账户增加钱。
6.写Spring配置文件:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankDao" class="com.test.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="bankService" class="com.test.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
7.写测试方法:
package com.test.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.service.BankService;
public class T {
private ApplicationContext ac;
@Before
public void setUp() throws Exception {
ac=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void transferAccounts(){
BankService bankService = (BankService) ac.getBean("bankService");
bankService.transferAccounts(50, 1, 2);
}
}
这里由账户1向账户2转账50元。运行测试方法:
这里转账功能完成。
上面是正确的转账流程。但是万一有个地方出错的话,会出现什么情况呢?修改inMoney方法为:
@Override
public void inMoney(int money, int userId) {
// TODO Auto-generated method stub
String sql="update t_account2 set count=count+:money where userId=:userId";
MapSqlParameterSource sps=new MapSqlParameterSource();
sps.addValue("money", money);
sps.addValue("userId", userId);
namedParameterJdbcTemplate.update(sql,sps);
}
这里改成给t_account2表转钱。
运行测试方法,这时程序会报错t_account2表不存在,数据表的结果为:
这时发生了不可思议的事情,第一个操作转出钱成功了导致账户1减少了50块钱,而第二个操作转入钱由于t_account2表不存在导致账户2没有转入50块钱。
这样的话就发生了错误。为了避免这种错误的发生,需要引入事务管理,把这两种操作放入到一个事务中,当其中一个操作出错时,数据库表回滚到事务执行之前。
二、编程式事务管理
这里介绍一种事务管理方法:编程式事务管理
1.修改BankServiceImpl类,引入事务模板类对象:
package com.test.service.impl;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.test.dao.BankDao;
import com.test.service.BankService;
public class BankServiceImpl implements BankService{
private BankDao bankDao;
private TransactionTemplate transactionTemplate;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
// TODO Auto-generated method stub
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
// TODO Auto-generated method stub
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
});
}
}
这里添加了TransactionTemplate类的对象transactionTemplate,并生成了这个对象的set方法以方便给这个对象注入值。修改转账功能的实现函数,这里执行了transactionTemplate的execute函数,重写了doInTransactionWithoutResult方法,把这两个操作都放在了这个方法中。
2.修改Spring配置文件:添加TransactionTemplate类的实例
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
TransactionTemplate类中有一个transactionManager属性,这个属性需要DataSource类对象值。
在BankServiceImpl实例中注入值:
<bean id="bankService" class="com.test.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
3.这样的话编程式事务管理就配置完成了,运行测试方法,虽然程序还是报错了t_account2表不存在,但是数据库中的各个账户信息并没有发生变化:
这里使用了编程式事务管理,当执行第二个转入操作的时候由于报错了,于是会进行事务回滚,数据库中数据回滚到事务执行之前的状态。
使用编程式事务管理有个缺点,看如下的转账功能实现代码:
@Override
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
// TODO Auto-generated method stub
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
// TODO Auto-generated method stub
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
});
}
这里我们的转账功能的业务逻辑代码其实就两个数据库操作代码,而使用编程式事务管理的时候在这个转账功能中插入了一些与转账功能无关的代码,破坏了业务的逻辑性。