事务指数据库中多个操作合并在一起形成的操作序列
1.当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
2.当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
事务特征(ACID)
原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
一致性(Consistency)事务前后数据的完整性必须保持一致
隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
脏读:允许读取未提交的信息
解决方案: Serializable(表级写锁)
J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
Spring为业务层提供了整套的事务解决方案
PlatformTransactionManager
TransactionDefinition
TransactionStatus
事务管理的对象是谁,Connection必须保证一件事
同一个业务操作多次Dao必须保证是同一个Connection
平台事务管理器实现类
DataSourceTransactionManager 适用于Spring JDBC或MyBatis
HibernateTransactionManager 适用于Hibernate3.0及以上版本
JpaTransactionManager 适用于JPA
JdoTransactionManager 适用于JDO
JtaTransactionManager 适用于JTA
JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
此接口定义了事务的基本操作
获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
提交事务 :
void commit(TransactionStatus status)
回滚事务 :
void rollback(TransactionStatus status)
此接口定义了事务的基本信息
获取事务定义名称
String getName()
获取事务的读写属性
boolean isReadOnly()
获取事务隔离级别
int getIsolationLevel()
获事务超时时间
int getTimeout()
获取事务传播行为特征
int getPropagationBehavior()
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
编程式
声明式(XML)
声明式(注解)
银行转账业务说明
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
/**
* 转账操作
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
public void transfer(String outName,String inName,Double money);
public void transfer(String outName,String inName,Double money){
accountDao.inMoney(outName,money); accountDao.outMoney(inName,money);
}
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
update>
public void transfer(String outName,String inName,Double money){
//创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
//创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
//创建事务状态对象,用于控制事务执行
TransactionStatus ts = dstm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0; //模拟业务层事务过程中出现错误
accountDao.outMoney(inName,money);
//提交事务
dstm.commit(ts);
}
将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
配置AOP通知类,并注入dataSource
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
bean>
使用环绕通知将通知类织入到原始业务对象执行过程中
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="tx" pointcut-ref="pt"/>
aop:aspect>
aop:config>
AOP配置事务是否具有特例性?
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
bean>
使用tx命名空间配置事务专属通知类
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
tx:attributes>
tx:advice>
使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
aop:config>
aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
aop:advisor配置的通知类必须实现通知接口
名称:tx:advice
类型:标签
归属:beans标签
作用:专用于声明事务通知
格式:
<beans>
<tx:advice id="txAdvice" transaction-manager="txManager">
tx:advice>
beans>
基本属性:
id :用于配置aop时指定通知器的id
transaction-manager :指定事务管理器bean
名称:tx:attributes
类型:标签
归属:tx:advice标签
作用:定义通知属性
格式:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
tx:attributes>
tx:advice>
基本属性:
名称:tx:method
类型:标签
归属:tx:attribute标签
作用:设置具体的事务属性
格式:
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
tx:attributes>
说明:
通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
tx:method属性
这个传播行为简单理解为两个方法间相互调用
A调用B,B的管理事务行为
记住两个
Required A中有事务,B加入,A中没有事务,B自己生成一个(管好自己)
Supports A中有事务,B加入,A中没有事务,B也没有(不管)
场景A:生成订单业务
子业务S1:记录日志到数据库表X
子业务S2:保存订单数据到数据库表Y
子业务S3:……
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
(S1需要新事务)
场景B:生成订单业务
背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
背景2:每次获取完订单号,表M中记录的编号自增1
子业务S1:从表M中获取订单编号
子业务S2:保存订单数据,订单编号来自于表M
子业务S3:……
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
(S1需要新事务)
名称:@Transactional
类型:方法注解,类注解,接口注解
位置:方法定义上方,类定义上方,接口定义上方
作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
就近原则,在类上,该类所有方法都会被事务控制
范例:
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
名称:tx:annotation-driven
类型:标签
归属:beans标签
作用:开启事务注解驱动,并指定对应的事务管理器
范例:
<tx:annotation-driven transaction-manager="txManager"/>
名称:@EnableTransactionManagement
类型:类注解
位置:Spring注解配置类上方
作用:开启注解驱动,等同XML格式中的注解驱动
范例:
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
TransactionTemplate
JdbcTemplate
RedisTemplate
RabbitTemplate
JmsTemplate
HibernateTemplate
RestTemplate
提供标准的sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
提供标准的具名sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(:name,:money)";
Map pm = new HashMap();
pm.put("name",account.getName());
pm.put("money",account.getMoney());
jdbcTemplate.update(sql,pm);
}
RedisTemplate对象结构
public void changeMoney(Integer id, Double money) {
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
策略模式(Strategy Pattern)使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。
事务其实是AOP的应用
把多个操作合并在一起形成操作序列,绑定一起,要嘛一起成功,要嘛一起失败(回滚),保证数据的安安全性
事务四大特征(ACID)
隔离级别
脏读 Read uncommitted:允许读取未提交的信息 解决方案(表级读锁)
不可重复读 Repeatable read:读取过程中单个数据发生变化 (行级写锁)
幻读 Serializable :读取过程中数据条目发生了变化 (表级写锁)
两个重点接口
DataSourceTransactionManage 基本的事务管理 提交/回滚 精确到方法级别
org.springframework.jdbc.datasource.DataSourceTransactionManager
TransactionDefinition 事务的属性/设置
同一个业务操作多次,保证Connection是同一个
下面expression和advice-ref类似包含关系,先确定切入点,然后再确定需要具体绑定事务的方法
这个@Transactional可以在很多位置,建议放在类上做个类全局的统配,如果有特殊的在需要特殊的方法上再定义一个,就近原则生效的。
感觉最大的变化是Service层的调用,本来需要有一大堆调用,现在直接从容器中拿对象,调用方法就好了,非常简单,而且解耦
一般第三方的库之类的用xml配置更好些,不然要创建个类,写方法返回啥的,不如xml来的舒服,而其他的建议用注解,指定类上直接加注解,绑定上了,就不用再去配具体的类到容器什么的。
用注解记得
扫描包
引入外部文件
配置关系映射dao
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"/>
</bean>
开启注解
< context:annotation-config>
开启事务,需要传入个线程池的配置
开启aop代理
<aop:aspectj-autoproxy/>
都需要在xml配置下。不然不会生效