事务就是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行(事务提交)要么全部放弃(事务回滚)。
eg:在一个业务里操作数据库中的数据有多个步骤,如果中间有一步没有操作成功,就要将这个事务回滚,不改变数据库中的数据,如果这多步操作都执行成功了,就将事务提交,就是说这多个步骤是绑定在一起的,要么都成功,要么都失败。
eg:转账操作,小明给小红转1000元,小红收到1000元,这两个操作要绑定在一起,要么一起成功,要么一起失败。
任何支持事务的数据库,都必须具备四个特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),也就是我们常说的事务ACID,这样才能保证事务((Transaction)中数据的正确性。
1. 原子性(Atomicity):事务开始执行,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
比如小明向小红转账,不可能小明扣了钱,小红却没收到。
2. 一致性(Consistency):事务执行前后数据的完整性应该保持一致。
小明和用户小红的钱加起来一共是2000,那么不管小明和小红之间如何转账,转几次账,事务结束后两个他俩的钱相加起来应该还得是2000,这就是事务的一致性。
3. 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,一个用户的事务不跟被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
比如小明在转账时,小红不能取钱,两个事务之间要相互隔离
4. 持久性(Durability):一个事务一旦被提交,他对数据库中的数据的改变是永久性的,机试数据库发生故障也不应该对其有任何影响。
事务的隔离性就是指,多个并发的事务同时访问一个数据库时,一个事务不能被另一个事务所干扰,每个并发的事务间要相互进行隔离。而事务的隔离级别就是为了解决并发事务导致的问题。
1. 如果不考虑隔离性产生的问题:
服务器程序本身就是多线程的环境,每一个浏览器(用户)访问服务器的时候,服务器都会创建一个新的线程来处理用户的请求,在这次请求的过程中,如果需要访问数据库就会有事务的操作,也就是说服务器时多线程的环境多事务并发的场景,如果多个用户同时访问同一个网站的一个功能,同时访问数据库中的同一条数据,多个事务同时访问同一个事务的情况就会出现,如果不做事务的隔离性处理,就可能会出现一些问题:
更新问题:第一类丢失更新、第二类丢失更新
读取问题:脏读、不可重复读、幻读
1、第一类丢失更新:某一个事务的回滚,导致另一个事务已更新的数据丢失了。(回滚丢失)
2、第二类丢失更新:某一个事务的提交,导致另一个事务已更新的数据丢失了。(提交丢失)
3、脏读:某一个事务,读取了另一个事务未提交的数据。
4、不可重复读:某一个事务,对同一个数据前后读取的结果不一致。(针对一行数据,要锁住行)
5、幻读:某一个事务,对同一个表前后查询到的行数不一致。(针对表,要锁住整张表)
不可重复读和幻读不要搞混:不可重复读针对一行数据的修改,幻读针对于向一张表中插入一条数据或删除一条数据,解决不可重复读需要锁住当前数据所在的行,解决幻读需要锁住整张表。
Read Uncommitted(读未提交):一个事务可以读取另一个事务未提交的数据,安全级别最低,问题都会产生
Read Committed(读已提交):一个事务可以读取另一个事务已提交的数据,可以解决第一类丢失更新和脏读
Repeatable Read(可重复读):事务开启后,其他事务不能对数据再进行修改(行锁),直到事务结束。
Serializable(序列化):解决所有问题。离级别最高,可以解决所有的问题,但是他解决问题是有代价的,需要对数据表加锁,加锁会降低数据处理的性能,效率最低。(银行,金融的业务可以选择)
一般我们都用读已提交和可重复读,如果对安全性要求比较高就是用可重复读,否则使用读已提交,具体情况根据业务问题而定。
Spring事务管理高层抽象接口主要包括3个:
1、事务管理器(事务的开启、提交、回滚):PlatformTransactionManager
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
2、事务定义信息(隔离、传播、超时、只读):TransactionDefinition
public interface TransactionDefinition {
//事务的传播特性:
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
//事务的隔离级别:
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//超时:
int TIMEOUT_DEFAULT = -1;
//操作属性的方法:
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
3、事务具体运行状态(事务是否已经提交、是否有保存点等):TransactionStatus
public interface TransactionStatus extends SavepointManager, Flushable {
//是否是新事务
boolean isNewTransaction();
//是否设置保存点
boolean hasSavepoint();
//只读
void setRollbackOnly();
//是否已回滚
boolean isRollbackOnly();
@Override
void flush();
//是否已提交
boolean isCompleted();
}
/**
* 两个方法都加了 @Transactional,相互调用,那么他们之间的事务应该如何处理?
*
* 以被调用者的方法为中心:beidiaoyongzhe()方法
* 1、propagation_required:
* 如果调用我的方法在事务中执行,那我也在事务中执行
* 如果调用我的方法没有事务,那我就新建一个事务
*
* 2、propagation_supports:
* 如果调用我的方法在事务中执行,那我也在事务中执行。
* 如果调用我的方法没有在事务中执行,那我也不用事务。
*
* 3、propagation_mandatory:
* 调用我的方法必须在事务中执行,没有就抛出异常。
*
* 4、propagation_requierd_new:
* 不管调用我的方法有没有在事务中执行,我都要新建一个事务,我要使用我自己的。
* 如果调用我的方法有事务,那我就将它挂起,不使用。
*
* 5、propagation_not_supported:
* 不管调用我的方法有没有在事务中执行,我都不用事务。
* 如果调用我的方法有事务,我也要将它挂起
*
* 6、propagation_nerver:
* 调用我的方法不能有事务,我自己也不能有事务。
* 如果调用我的方法有事务,则抛出异常。
*
* 7、propagation_insert:
* 如果调用我的方法有事务,则在嵌套事务内执行。
* 如果调用我的方法没有事务,则执行与propagation_required类似的操作。
*/
@Service
public class PersonService {
/**
* 如果这个方法中出现了异常,回滚,那 beidiaoyongzhe() 方法需不需要回滚?
*/
@Transactional
public void diaoyongzhe(){
System.out.println("我是调用者....");
beidiaoyongzhe();
//出现异常....
}
/**
* 如果这个方法中出现了异常,回滚,那 diaoyongzhe() 方法需不需要回滚?
*/
@Transactional
public void beidiaoyongzhe(){
System.out.println("我是被调用者....");
//出现异常....
}
}
总结:
1、被调用方死活不用事务的:
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
2、被调用方可用可不用事务的:
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
3、被调用方必须用事务的:
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
propagation_required:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
propagation_inserted:如果当前存在事务,则在嵌套事务内执行。如果没有,就新建一个。
1、数据库建表:
create table account(
id int(11) not null auto_increment,
name varchar(20) not null,
money double default null,
primary key(id)
)engine=InnoDB auto_increment=4 default charset=utf8;
Dao层接口
public interface AccountDao {
/**
* @param out 转出账号
* @param money 转多少钱
*/
public void outMoney(String out,Double money);
/**
* @param in 转入账号
* @param money 转多少钱
*/
public void inMoney(String in,Double money);
}
Dao层实现类:使用JDBCTemplate操作数据库
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, Double money) {
String sql = "update account set money = money-? where name = ?";
this.getJdbcTemplate().update(sql, money, out);
}
@Override
public void inMoney(String in, Double money) {
String sql = "update account set money = money+? where name = ?";
this.getJdbcTemplate().update(sql,money,in);
}
}
3、Service层实现转账:
Service层接口:
public interface AccountService {
/**
* @param out 转出账户
* @param in 转入账户
* @param money 转多少钱
*/
public void transfer(String out,String in,Double money);
}
Service层实现类:实现转账操作
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDaoImpl accountDao) {
this.accountDao = accountDao;
}
//转账操作
@Override
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out,money);
//中间出现异常
int i=1/0;
accountDao.inMoney(in,money);
}
}
4、xml配置文件与数据源配置文件:
db.driverClass=com.mysql.jdbc.Driver
db.url = jdbc:mysql:///spring_transaction
db.username = root
db.password = root
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}"/>
<property name="jdbcUrl" value="${db.url}"/>
<property name="user" value="${db.username}"/>
<property name="password" value="${db.password}"/>
bean>
<bean id="accountDao" class="com.hh.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="accountService" class="com.hh.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
bean>
beans>
5、测试转账结果:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class TransactionTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void demo1(){
accountService.transfer("aaa", "bbb", 200d);
}
}
数据库中数据:结果转出账户将钱转出去了,但是转入账户却没有收到钱,这种问题很严重的,因此必须使用事务管理,对于业务层如果有多个操作数据库的指令,必须加事务,没得商量。
1、在xml中配置以下依赖关系:
在AccountService中使用事务模板TransactionTemplate(简化事务管理而封装的类)
TransactionTemplate依赖DataSourceTransactionManager(PlatformTransactionManager的实现类)
事务管理器DataSourceTransactionManager依赖DataSource
<bean id="accountDao" class="com.hh.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
bean>
<bean id="accountService" class="com.hh.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<property name="transactionTemplate" ref="transactionTemplate"/>
bean>
2、在Service层对操作数据库的方法加上事务:
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
//注入事务模板
@Autowired
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDaoImpl accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
//转账操作
@Override
public void transfer(String out, String in, Double money) {
/**
* TransactionTemplate已经搭好了事务框架,
* 我们只需将多条指令放在doInTransaction中执行就可以了
*
* TransactionTempale采用和其他Spring模板,如JdbcTempalte和HibernateTemplate一样的方法。
* 它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。
* 如同其他模板,TransactionTemplate是线程安全的
*
* 使用TransactionCallback()可以返回一个值。
* 使用TransactionCallbackWithoutResult则没有返回值。
*/
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
accountDao.outMoney(out,money);
int i=1/0;
accountDao.inMoney(in,money);
return "成功";
}
});
}
}
编程式事务管理需要手动的在Service层改代码,因此建议使用声明式事务管理
使用TransactionProxyFactoryBean来配置事务代理Bean,它是一个专门为目标Bean生成事务代理的工厂Bean。既然TransactionProxyFactoryBean产生的是事务代理Bean,可见Spring的声明式事务策略是基于Spring AOP的。
每个TransactionProxyFactoryBean为一个目标Bean生成一个事务代理Bean,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚。
1、配置XMl文件:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionProxyFactoryBean"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="accountService"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIREDprop>
props>
property>
bean>
2、使用代理类后我们的AccountService就不需要改动了,只需要该测试类即可:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans1.xml")
public class TransactionTest1 {
//注入AccountService的代理类
@Resource(name="transactionProxyFactoryBean")
private AccountService accountService;
@Test
public void demo1(){
accountService.transfer("aaa", "bbb", 200d);
}
}
3、结果:转账失败
1、xml的配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pointcut1" expression="execution(* com.heng.service.AccountService+.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
aop:config>
2、测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans2.xml")
public class TransactionTest2 {
//注入AccountService
@Resource(name="accountService")
private AccountService accountService;
@Test
public void demo1(){
accountService.transfer("aaa", "bbb", 200d);
}
}
3、结果:转账失败
1、xm文件l配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
2、在Service层中想要使用事务的类上或者方法上加上注解@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDaoImpl accountDao) {
this.accountDao = accountDao;
}
/**
* 转账操作
*
* 事务属性:
* propagation:传播机制
* isolation:隔离级别
* readOnly:只读
* rollbackFor:发生哪些异常回滚
* noRollbackFor:发生哪些异常不回滚
*/
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false)
@Override
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out,money);
int i=1/0;
accountDao.inMoney(in,money);
}
}
总结:
Spring将事务管理分成了两类:声明式事务管理、编程式事务管理
基于TransactionProxyFactoryBean的方式:需要为每个需要事务管理的类配置一个TransactionProxy FactoryBean
进行增强,很少使用。
基于基于Aspetj的xml方式:常使用,不需要修改业务层的类。
基于注解的方式:常使用,但是需要修改业务层的类,加一个注解。