1.事务的四大特性:
- 原子性:事务包含的所有操作要么全部成功,要么全部失败回滚;成功必须要完全应用到数据库,失败则不能对数据库产生影响;
- 一致性:事务执行前和执行后必须处于一致性状态,
例:用户A和用户B的前加起来一共是5000; 无论AB用户之间是如何相互转换的,事务结束后两个用户的钱加起来还是5000,这就是 事务的一致性。
- 隔离性:当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不被其他事务的操作所干扰,多个并发事务之间要相互隔离;
01:脏读
是指一个事务处理过程里读取了另一个未提交的事务中的数据,然后使用了这个数据;
例:用户A向用户B转账100元,A通知B查看账户,B发现前确实已到账,而之后无论第二条SQL是否执行,只要该事务部提交,则所有操作都将回滚,当B再次查看账户时就会发现前其实并没有到账。02:不可重复读(更新的时候会发生)
不可重复读是指在一个事务内,多次读取同一个数据,在这个事务还没有结束 ,另一个事务也访问该同一数据,但是由于第二个事务的修改,那么第一个事务两次读取的数据可能不一样,因此称为不可重复读;即同一个事务中原始数据读取不可重复。 注:不可重复读和脏读的区别,脏读是某一个事务读取另一个事务未提交的脏数据; 不可重复读则是读取前一事务提03:幻读:
当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行修改,这种数据涉及到表中的全部数据行,同时,第二个事务也对这个表数据进行修改,这个修改是对表中新增/删除一条数据,那么操作第一个事务的用户发现表中的数据还没有修改的数据行,就好像发生了幻觉一样,这就是发生了幻读。
注:幻读和不可重复读都读取另一条已经提交的事务,所不同的是不可重复读查询的都是同一数据项,而幻读针对的是一批数据整体。解决:
数据库提供的四种隔离级别:
01:Read uncommitted(读未提交):最低级别,任何情况都会发生。
02:Read Committed(读已提交):可避免脏读的发生。
03:Repeatable read(可重复读):可避免脏读、不可重复读的发生。
04:Serializable(串行化):避免脏读、不可重复读,幻读的发生。
注: 四种隔离级别最高:Seralizable级别,最低的是Read uncommitted级别; 级别越高,执行效率就越低; 隔离级别的设置只对当前链接有效,对JDBC操作数据库来说,一个Connection对象相当于一个链接,只对该Connection对象设置的隔离级别只对该connection对象有效,与其它链接connection对象无关。
01:Mysql的默认隔离级别是:可重复读:Repeatable read;
- 持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便在数据库系统遇到故障的情况下也不会丢失事物的操作。
2.Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略。
Spring事物管理高层抽象主要包括3个接口,Spring的事物主要是由他们共同完成的:
- PlatformTransactionManager:事务管理器--主要用于平台相关事务的管理
- TransactionDefinition:事务定义信息(隔离、传播、超时、只读)--通过配置如何进行事务管理
- TransactionStatus:事务具体运行状态--事务管理过程中,每个时间点事务的状态信息
PlatformTransactionManager
该接口有三个方法:
- commit:提交事务
- rollback:回滚事务
- getTransaction:获取事务状态
DataSourceTransactionManager针对JdbcTemplate、MyBatis事物控制,使用Connection(连接)进行事务控制:
- 开启事务 connection.setAutoCommit(false);
- 提交事务 connection.commit();
- 回滚事务 connection.rollback();
TransactionDefinition
该方法主要提供的方法:
- String getName():获取事务对象名称。
- int getIsolationLevel():获取事务的隔离级别。
- int getPropagationBehavior():获取事务的传播行为。
- int getTimeout():获取事务的超时时间(事务的有效期)。
- boolean isReadOnly():获取事务是否只读(保存、更新、删除--对数据进行操作-变成可读写对,查询-设置这个属性为true,只读不能写),事务管理器能够根据这个返回值进行优化。
在上述五个方法的描述中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务,解决两个被事务管理的方法相互调用问题。传播行为的种类如表 1 所示。
属性名称 | 值 | 描 述 |
---|---|---|
PROPAGATION_REQUIRED(默认) | required | 支持当前事务。如果不存在,就新建一个 |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果不存在,就不使用事务 |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果不存在,则抛出异常 |
PROPAGATION_REQUIRES_NEW | requires_new | 如果事务存在,挂起当前事务,创建新的事务 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。如果事务存在,则将其挂起 |
PROPAGATION_NEVER | never | 不支持当前事务,如果 事务存在,则抛出异常 |
PROPAGATION.NESTED | nested | 嵌套事务,如果当前事务存在,则嵌套事务执行,底层将使用Jdbc的 Savepoint 形成嵌套事务,允许在同一个事务设置保存点,回滚保存点,只对DataSourceTransactionManager起效 |
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。
通常情况下,数据的查询不会改变原数据,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理。如果没有指定事务的传播行为,则 Spring3 默认的传播行为是 required。
【面试题】REQUIRED、REQUIRES_NEW、NESTED区分
REQUIRED:只有一个事务(默认、推荐)
REQUIRES_NEW:存在两个事务,如果事务存在,挂起事务,重新又开启了一个新的事务
NESTED:嵌套事务,事务可以设置保存点,回滚到保存点,选择提交或者回滚
TransactionStatus
TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如表 2 所示。
名称 | 说明 |
---|---|
void flush()(hibernate用的) | 刷新事务 |
boolean hasSavepoint() | 获取是否存在保存点 |
boolean isCompleted() | 获取事务是否完成 |
boolean isNewTransaction() | 获取是否是新事务 |
boolean isRollbackOnly() | 获取是否回滚 |
void setRollbackOnly() | 设置事务回滚 |
事务的结束:必须通过commit确认事务提交,rollback作用标记为回滚
数据库操作中,如果只是回滚,后面不操作,数据库在关闭连接的时候,自动发出commit。
try{
操作
}catch(){
rollback
}finally{
commit
}
【三个事务超级接口对象之间的关系】
- 首先用户管理事务,需要先配置TransactionDefinition(事务定义信息、事务的管理方案);
- 然后根据TransactionDefinition,通过TransactionManager(事务管理器)进行事务管理;
- 最后事务运行过程中,每个时刻都可以通过获取TransactionStatus(事务状态)来了解事务的运行状态。
3.Spring事务管理两种方式
Spring支持两种方式事务管理
(1)编程式的事务管理
通过TransactionTemplate手动管理事务,在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码(入侵性)
(2)使用XML或注释配置声明式事务
- Spring的声明式事务是通过AOP实现(环绕通知)
- 开发中经常使用(代码入侵性小)-推荐使用
4.声明式事务管理案例-转账(xml、注解)
xml方式
在t_account表中
//接口类
package com.account.dao;
public interface IAccountDao {
public void in(String name,Double money);//存入(转入)
public void out(String name,Double money);//取出(转出)
}
//实现类
package com.account.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
@Override//存入(转入)
public void in(String name, Double money) {
// TODO Auto-generated method stub
String sql="update t_account set money =money+ ? where name=?";
super.getJdbcTemplate().update(sql,money,name);
}
@Override//取出(转出)
public void out(String name, Double money) {
// TODO Auto-generated method stub
String sql="update t_account set money=money - ? where name=?";
super.getJdbcTemplate().update(sql,money,name);
}
}
package com.account.service;
public interface IAccountService {
void transfer(String oitName,String inName,Double money);
}
package com.account.service;
import com.account.dao.IAccountDao;
public class AccountSerivceImpl implements IAccountService{
//注入dao
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao=accountDao;
}
//转账操作的业务逻辑
@Override
public void transfer(String outName, String inName, Double money) {
// TODO Auto-generated method stub
//调出dao层
//先取出
accountDao.out(outName, money);
//再转入
accountDao.in(inName, money);
}
}
package com.account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.account.service.IAccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringTest {
//注入测试的service
@Autowired
private IAccountService accountService;
//需求:账号转账,张艺兴账号取出1000元,存放到王一博账号上
@Test
public void testTransfer() {
accountService.transfer("张艺兴", "王一博", 1000d);
System.out.println("转账成功!");
}
}
//在applicationContext.xml
<!-- 引入外部属性配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置内置的数据源bean,使用db.properties -->
<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>
<!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步:定义通知,通知中要处理的就是事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性定义 -->
<tx:attributes>
<!-- 配置具体的方法的事务属性
isolation//事务的隔离级别,默认是按数据库的隔离级别来
propagation//事务的传播行为,默认是同一个事务
timeout=“-1”:事务的超时时间,默认值使用数据库的超时时间
read-only=“false”:事务是否只读,默认课读写
rollback-for:遇到哪些异常就回滚,其他的都不回滚
no-rollback-for:遇到哪些异常不回滚,其他的都回滚。和上面互斥
-->
<tx:method name="transfer" isolation="DEFAULT"
propagation="REQUIRED" timeout="-1" read-only="false"/>
<!-- 支持通配符
要求service中方法名字必须符合下面的规则 -->
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入点,让通知关联切入点,即事务控制业务层的方法 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 配置dao,注入jdbctemplate -->
<bean id="bookDao" class="com.feng.dao.BookDao">
<!-- 方案一:在BookDao中提供jdbcTemplate属性,通过set方法注入jdbctemplate -->
<!-- <property name="jdbcTemplate" ref="jdbcTemplate"></property> -->
<!-- 方案二:BookDao类继承JdbcDaoSupport,直接注入数据源,就拥有了jdbctemplate对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 创建转账dao -->
<bean id="accountDao" class="com.account.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 创建转账service -->
<bean id="accountService" class="com.account.service.AccountSerivceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
如果在转账过程中,即存入或者取出的sql语句任意一个执行不成功,则转账操作也是失败的。(要么都成功要么都失败)
注解方式
//接口类
package com.account.dao;
public interface IAccountDao {
public void in(String name,Double money);//存入(转入)
public void out(String name,Double money);//取出(转出)
}
//账户操作持久层
//技术方案
package com.account.dao;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
//<bean id="accountDao" class="com.account.dao.AccountDaoImpl">
@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
//注入数据源
@Autowired
public void setDi( DataSource dataSource) {
// TODO Auto-generated method stub
super.setDataSource(dataSource);
}
@Override//存入(转入)
public void in(String name, Double money) {
// TODO Auto-generated method stub
String sql="update t_account set money =money+ ? where name=?";
super.getJdbcTemplate().update(sql,money,name);
}
@Override//取出(转出)
public void out(String name, Double money) {
// TODO Auto-generated method stub
String sql="update t_account set money=money - ? where name=?";
super.getJdbcTemplate().update(sql,money,name);
}
}
package com.account.service;
public interface IAccountService {
void transfer(String oitName,String inName,Double money);
}
package com.account.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.account.dao.IAccountDao;
//<bean id="accountService" class="com.account.service.AccountSerivceImpl">
@Service("accountService")
@Transactional//添加事务,会对该类中所有对公有方法,自动加上事务--全局对设置,默认可写
public class AccountSerivceImpl implements IAccountService{
//注入dao
@Autowired
private IAccountDao accountDao;
//转账操作的业务逻辑
@Override
public void transfer(String outName, String inName, Double money) {
// TODO Auto-generated method stub
//调出dao层
//先取出
accountDao.out(outName, money);
//再转入
accountDao.in(inName, money);
}
@Transactional(readOnly = true)//局部覆盖全局
public void findAcount() {
System.out.println("查询账户信息");
}
}
//applicationCOntext.xml
<!-- 引入外部属性配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置内置的数据源bean,使用db.properties -->
<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>
<!-- 配置bean组件扫描 -->
<context:component-scan base-package="com.account"></context:component-scan>
<!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>