事务的概念
百度百科:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
自己的理解:
理解事务之前,先讲一个你日常生活中最常干的事:取钱。 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。
事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。这篇总结下Spring事务。
事务具备ACID四种特性
(1)原子性(Atomicity)
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
(3)隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
(4)持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
如果不考虑事务的隔离性会引发以下问题:
读问题:
- 脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
- 幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
写问题: 丢失更新
事务的隔离级别
(1)read uncommited:未提交读,是最低的事务隔离级别,任何读问题解决不了。它允许另外一个事务可以看到这个事务未 提交的数据。
(2)read commited:已提交读,解决脏读,但是不可重复读和虚读有可能发生。保证一个事物提交后才能被另外一个事务读 取。另外一个事务不能读取该事物未提交的数据。
(3)repeatable read:重复读,解决脏读和不可重复读,但是虚读有可能发生。这种事务隔离级别可以防止脏读,不可重复 读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了 以下情况产生(不可重复读)。
(4)serializable:这是花费最高代价但最可靠的事务隔离级别,解决所有读问题。事务被处理为顺序执行。除了防止脏读,不 可重复读之外,还避免了幻象读。
事务的传播行为
普通的业务逻辑直接调用Dao层方法就可以完成,那当遇到特别复杂的业务逻辑时,有可能出现业务层方法的互相调用。
事务的传播行为主要用来解决业务层的方法相互调用的问题
Spring中提供了七种事务的传播行为:
1.保证多个操作在同一个事务中
PROPAGATION_REQUIRED
:默认值,如果A中有事务,使用A中的事务,如果A没有,创建一个新的事务,将操作包含进来
如果x()方法中有事务,那么Service2()中就会把DAO3.c()和DAO4.d()两个业务层方法都装进x()方法的事务里面,如果x()方法中没有事务,那么就会创建一个新的事务将x()中的业务层的方法和DAO3.c(),DAO4.d()都包含进去
PROPAGATION_SUPPORTS
:支持事务,如果A中有事务,使用A中的事务。如果A没有事务,不使用事务。
PROPAGATION_MANDATORY
:如果A中有事务,使用A中的事务。如果A没有事务,抛出异常。
2.保证多个操作不在同一个事务中
PROPAGATION_REQUIRES_NEW :
如果A中有事务,将A的事务挂起(暂停),创建新事务,只包含自身操作。如果A中没有事务,创建一个新事务,包含自身操作。
如果x()中有事务,那么先暂停x()中的事务,然后创建一个新的事务,只包含
DAO3.c(),DAO4.d(),如果x()中没有事务对于y()还是前面的操作
PROPAGATION_NOT_SUPPORTED
:如果A中有事务,将A的事务挂起。不使用事务管理。
PROPAGATION_NEVER
:如果A中有事务,报异常。
3.嵌套式事务
PROPAGATION_NESTED
:嵌套事务,如果A中有事务,按照A的事务执行,执行完成后,设置一个保存点,执行B中的操作,如果没有异常,执行通过,如果有异常,可以选择回滚到最初始位置,也可以回滚到保存点(选择回滚到保存点就不会影响到A的事务)。
为什么需要事务?
1.数据库设计
填写数据(也可以自己编写插入方法)
2.创建Service的接口和实现类
/**
* 转账的业务层的接口
* @author hp
*
*/
public interface AccountService {
/**
* @param from 转账人
* @param to 收账人
* @param momey 转账金额
*/
public void transfer(String from, String to, Double momey);
}
======================================================================================
/**
* 转账的业务层的实现类
* @author hp
*
*/
public class AccountServiceImpl implements AccountService {
/**
* @param from 转账人
* @param to 收账人
* @param momey 转账金额
*/
public void transfer(String from, String to, Double momey) {
// TODO Auto-generated method stub
}
}
3.创建Dao的接口和实现类
/**
* 转账的DAO的接口
* @author hp
*
*/
public interface AccountDao {
public void outMoney(String from, Double money);
public void inMoney(String to, Double money);
}
=========================================================================================
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
public void outMoney(String from, Double money) {
// TODO Auto-generated method stub
}
public void inMoney(String to, Double money) {
// TODO Auto-generated method stub
}
}
4.jdbc 配置文件 jdbc.properties
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql:///test1?characterEncoding=utf-8
jdbc.username = root
jdbc.password = root
5.配置 Service 和 Dao,交给Spring管理
<!-- 配置Dao层 -->
<bean id="accountDao" class="com.ysx.tx.demo1.AccountDaoImpl"></bean>
<!-- 配置Service -->
<bean id="accountService" class="com.ysx.tx.demo1.AccountServiceImpl"></bean>
6.配置连接池并交给Dao层使用
<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置JDBC连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置Dao层 -->
<bean id="accountDao" class="com.ysx.tx.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
7.在Dao的实现类中重写转账方法和转入方法
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
public void outMoney(String from, Double money) {
// TODO Auto-generated method stub
this.getJdbcTemplate().update("update account set money = money - ? where name = ?", money, from);
}
public void inMoney(String to, Double money) {
// TODO Auto-generated method stub
this.getJdbcTemplate().update("update account set money = money + ? where name = ?", money, to);
}
}
8.业务层从Spring中获取Dao对象
applicationContext.xml
<bean id="accountService" class="com.ysx.tx.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
AccountServiceImpl.java
import javax.annotation.Resource;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 转账的业务层的实现类
* @author hp
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx.xml")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* @param from 转账人
* @param to 收账人
* @param momey 转账金额
*/
public void transfer(String from, String to, Double momey) {
// TODO Auto-generated method stub
accountDao.outMoney(from, momey);
accountDao.inMoney(to, momey);
}
}
9.测试类 SpringDemo1.java
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 测试转账的环境
* @author hp
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx.xml")
public class SpringDemo1 {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void demo1() {
accountService.transfer("林青霞", "杨大侠", 20.0);
}
}
运行结果
那如果在 transfer 方法中 outMoney 和 inMoney 方法之间出错时,比如
public void transfer(String from, String to, Double momey) {
// TODO Auto-generated method stub
accountDao.outMoney(from, momey);
int i = 1/0;
accountDao.inMoney(to, momey);
}
结果就会变成钱从转账人账户扣除了却没有给收账人价钱,这不乱套了?
所以对于这样的需要一气呵成的操作我们就可以通过事务来处理
Spring的事务管理
一类:编程式事务(需要手动编写代码)--了解
Spring的事务管理的API
PlatformTransactionManager:平台事务管理器(接口,是Spring用于管理事务的真正的对象。)
DataSourceTransactionManager :底层使用JDBC管理事务
HibernateTransactionManager :底层使用Hibernate管理事务
TransactionDefinition :事务定义信息
事务定义:用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读
TransactionStatus:事务的状态
事务状态:用于记录在事务管理过程中,事务的状态的对象。
事务管理的API的关系:Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态的信息记录到事务状态的对象中。
第一步:配置平台事务管理器
第二步:Spring提供了事务管理的模板类
配置事务的管理的模板类
第三步:在业务层注入事务管理的模板
第四步:编写事务管理的代码
二类:声明式事务管理(通过配置实现)---AOP
XML方式的声明式事务管理
还是在刚刚的例子中,首先我们需要导入AOP的jar包
然后在配置文件中编写
<!-- 配置事务管理器======================================== -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的增强===================================== -->
<tx:advice id="advice1" transaction-manager="transactionManager">
<tx:attributes>
<!-- 事务管理的规则=========================== -->
<!-- <tx:method name="abc*" propagation="REQUIRED"/>像这样的就是对于以abc开头的方法名 -->
<tx:method name="transfer"/>
<!--
read-only表示是否只读,true时只能执行查询
timeout表示过期时间,-1表示不过期
-->
<tx:method name="*" propagation="REQUIRED" read-only="false" timeout="-1"/>
</tx:attributes>
</tx:advice>
<!-- aop配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.ysx.tx.demo1.AccountServiceImpl.*(..))" id="pointcut1"/>
<aop:advisor advice-ref="advice1" pointcut-ref="pointcut1"/>
</aop:config>
在<tx:method>中可以设置事务定义信息
注解方式的声明式事务管理
配置事务管理器并开启注解事务
<!-- 配置事务管理器======================================== -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务====================================== -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务层添加注解
import javax.annotation.Resource;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 转账的业务层的实现类
* @author hp
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx2.xml")
@Transactional <------------------------------注解
public class AccountServiceImpl implements AccountService {
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Resource(name = "accountDao")
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* @param from 转账人
* @param to 收账人
* @param momey 转账金额
*/
public void transfer(final String from, final String to, final Double momey) {
accountDao.outMoney(from, momey);
//int i = 1/0;
accountDao.inMoney(to, momey);
}
}
这个注解里也同样可以配置