事务的ACID特性
- 原子性(atomicity):确保动作要么全部完成要么完全不起作用
- 一致性(consistency):数据和资源就处于一种满足业务规则的一致性状态中
- 隔离性(isolation): 用户的操作不能混淆
- 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响
事务的分类
- 编程式事务:需要编写代码控制事务在哪里开始,哪里提交,哪里回滚。
- 声明式事务:由Spring自动控制,事务在业务逻辑方法执行前开始,在业务逻辑方法正常结束后提交,在业务逻辑方法抛出异常时回滚
事务的传播行为
- PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
并发事务所导致的问题
-
脏读:对于两个事务a和b,加入b读取了被a更新但是还没有提交的字段,之后a回滚,b读取的内容就是临时类容且无效
-
不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
-
幻读:对于a和b,如果a读取了一个字段,然后b在表中加入了几个新行,之后如果a再次读取相同的表,就会多出几行
事务的其他属性
- 事务超时:所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
- 事务的只读属性:事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
- 事务的回滚规则:通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
spring的编程式事务
编程式事务需要使用类TransactionTemplate 在TransactionTemplate的execute方法中new一个匿名内部类TransactionCallback() 在该类的的继承的方法==doInTransaction(TransactionStatus status)==中完成事务
package springboot.tx.program;
import java.math.BigDecimal;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class StuCardServiceImpl implements StuCardService{
private StuCardDao scDao;
private TransactionTemplate transactionTemplate;
public void transaferMoney(final String targetCardNo, final String sourceCardNo, final String money) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
StuCard sourceCard = scDao.getStuCardInfoById(sourceCardNo);
StuCard targetCard = scDao.getStuCardInfoById(targetCardNo);
//修改后勤人员信息
sourceCard.setMoney(sourceCard.getMoney().subtract(new BigDecimal(money)));
targetCard.setMoney(targetCard.getMoney().add(new BigDecimal(money)));
scDao.updateStuCardByCardNo(targetCard);
if(sourceCard.getMoney().compareTo(new BigDecimal("0")) < 0){
System.out.println("noEnoughMoney");
}
scDao.updateStuCardByCardNo(sourceCard);
System.out.println("支付完成!商品购买成功!!!!!");
return null;
}
});
}
/**
* @return the scDao
*/
public StuCardDao getScDao() {
return scDao;
}
/**
* @param scDao the scDao to set
*/
public void setScDao(StuCardDao scDao) {
this.scDao = scDao;
}
/**
* @return the transactionTemplate
*/
public TransactionTemplate getTransactionTemplate() {
return transactionTemplate;
}
/**
* @param transactionTemplate the transactionTemplate to set
*/
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
事务的声明事开发(xml配置方式)
service中的业务逻辑代码变化不大
public void transaferMoney(String targetCardNo, String sourceCardNo, String money) {
StuCard sourceCard = scDao.getStuCardInfoById(sourceCardNo);
StuCard targetCard = scDao.getStuCardInfoById(targetCardNo);
// 修改后勤人员信息
sourceCard.setMoney(sourceCard.getMoney().subtract(new BigDecimal(money)));
targetCard.setMoney(targetCard.getMoney().add(new BigDecimal(money)));
scDao.updateStuCardByCardNo(targetCard);
if (sourceCard.getMoney().compareTo(new BigDecimal("0")) < 0) {
System.out.println("noEnoughMoney");
}
scDao.updateStuCardByCardNo(sourceCard);
System.out.println("支付完成!商品购买成功!!!!!");
}
主要是xml中的tx标签的配置 要记得加上 xmlns:tx=“http://www.springframework.org/schema/tx” 以及xsi标签中的http://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<import resource="applicationContext-jdbc.xml"/>
<bean id="stuCardDao" class="springboot.tx.xml.StuCardDaoImpl">
<property name="jt" ref="jdbcTemplate"></property>
</bean>
<bean id="stuCardService" class="springboot.tx.xml.StuCardServiceImpl">
<property name="scDao" ref="stuCardDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
<!-- 事务传播器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="trans*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:pointcut expression="execution(* springboot.tx.xml.*.*(..))" id="advicePointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="advicePointCut"/>
</aop:config>
</beans>
该xml中引用的applicationContext-jdbc.xml如下:其中包含了事务管理器的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<context:property-placeholder
location="config/DB.properties" />
<context:component-scan
base-package="spring.jdbc"></context:component-scan>
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql_driver}"></property>
<property name="url" value="${mysql_url}"></property>
<property name="username" value="${mysql_username}"></property>
<property name="password" value="${mysql_userpassword}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="dataSource"></constructor-arg>
</bean>
<!--
实例化事务管理器
-->
<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>
</beans>
Spring 声明式事务开发( 注解方式)
该方式是最简单的方法也是现在常用的方法
首先要配置ioc自动扫描的包和事务管理器xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!--
通过import 标签引入其他上下文配置文件
-->
<import resource="applicationContext-jdbc.xml"/>
<context:component-scan base-package="springboot.tx.anno"></context:component-scan>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
然后要在bean中添加注解 使其加入ioc容器
package springboot.tx.anno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class StuCardDaoImpl implements StuCardDao{
@Autowired
private JdbcTemplate jt;
/**
* 根据学生卡编号查询学生卡信息
*/
public StuCard getStuCardInfoById(String cardNo) {
RowMapper<StuCard> rm = new BeanPropertyRowMapper<StuCard>(StuCard.class);
return jt.queryForObject("select card_no stuCardNo, money from stu_card where card_no = ?", rm, cardNo);
}
/**
* 更新学生卡信�?
*/
public void updateStuCardByCardNo(StuCard sc) {
jt.update("update stu_card set money = ? where card_no = ?", sc.getMoney(), sc.getStuCardNo());
}
/**
* @return the jt
*/
public JdbcTemplate getJt() {
return jt;
}
/**
* @param jt the jt to set
*/
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
}
package springboot.tx.anno;
import java.math.BigDecimal;
import org.aspectj.lang.annotation.AdviceName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@Repository
public class StuCardServiceImpl implements StuCardService {
@Autowired
private StuCardDao scDao;
@Transactional
public void transaferMoney(String targetCardNo, String sourceCardNo, String money) {
StuCard sourceCard = scDao.getStuCardInfoById(sourceCardNo);
StuCard targetCard = scDao.getStuCardInfoById(targetCardNo);
// 修改后勤人员信息
sourceCard.setMoney(sourceCard.getMoney().subtract(new BigDecimal(money)));
targetCard.setMoney(targetCard.getMoney().add(new BigDecimal(money)));
scDao.updateStuCardByCardNo(targetCard);
if (sourceCard.getMoney().compareTo(new BigDecimal("0")) < 0) {
System.out.println("noEnoughMoney");
}
scDao.updateStuCardByCardNo(sourceCard);
System.out.println("支付完成!商品购买成功!!!!!");
}
public StuCardDao getScDao() {
return scDao;
}
public void setScDao(StuCardDao scDao) {
this.scDao = scDao;
}
}
然后在事务方法上加入注解@Transactional 上方代码已给出
该注解默认propagation() default Propagation.REQUIRED
还有一些其他的属性如超时之类的 可以参照源码
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;
/**
* Describes a transaction attribute on an individual method or on a class.
*
* <p>At the class level, this annotation applies as a default to all methods of
* the declaring class and its subclasses. Note that it does not apply to ancestor
* classes up the class hierarchy; methods need to be locally redeclared in order
* to participate in a subclass-level annotation.
*
* <p>This annotation type is generally directly comparable to Spring's
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
* convert the data to the latter class, so that Spring's transaction support code
* does not have to know about annotations. If no rules are relevant to the exception,
* it will be treated like
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* (rolling back on {@link RuntimeException} and {@link Error} but not on checked
* exceptions).
*
* <p>For specific information about the semantics of this annotation's attributes,
* consult the {@link org.springframework.transaction.TransactionDefinition} and
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
* @since 1.2
* @see org.springframework.transaction.interceptor.TransactionAttribute
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* Alias for {@link #transactionManager}.
* @see #transactionManager
*/
@AliasFor("transactionManager")
String value() default "";
/**
* A <em>qualifier</em> value for the specified transaction.
* <p>May be used to determine the target transaction manager,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.transaction.PlatformTransactionManager}
* bean definition.
* @since 4.2
* @see #value
*/
@AliasFor("value")
String transactionManager() default "";
/**
* The transaction propagation type.
* <p>Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* The transaction isolation level.
* <p>Defaults to {@link Isolation#DEFAULT}.
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
* transactions. Consider switching the "validateExistingTransactions" flag to
* "true" on your transaction manager if you'd like isolation level declarations
* to get rejected when participating in an existing transaction with a different
* isolation level.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* The timeout for this transaction (in seconds).
* <p>Defaults to the default timeout of the underlying transaction system.
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
* transactions.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;
/**
* Defines zero (0) or more exception {@link Class classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {@link RuntimeException}
* and {@link Error} but not on checked exceptions (business exceptions). See
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* @see #rollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}), indicating which exception types must cause
* a transaction rollback.
* <p>This can be a substring of a fully qualified class name, with no wildcard
* support at present. For example, a value of {@code "ServletException"} would
* match {@code javax.servlet.ServletException} and its subclasses.
* <p><b>NB:</b> Consider carefully how specific the pattern is and whether
* to include package information (which isn't mandatory). For example,
* {@code "Exception"} will match nearly anything and will probably hide other
* rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
* were meant to define a rule for all checked exceptions. With more unusual
* {@link Exception} names such as {@code "BaseBusinessException"} there is no
* need to use a FQN.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}.
* @see #rollbackFor
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
String[] rollbackForClassName() default {};
/**
* Defines zero (0) or more exception {@link Class Classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must
* <b>not</b> cause a transaction rollback.
* <p>This is the preferred way to construct a rollback rule (in contrast
* to {@link #noRollbackForClassName}), matching the exception class and
* its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}.
* @see #noRollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}) indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>See the description of {@link #rollbackForClassName} for further
* information on how the specified names are treated.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}.
* @see #noRollbackFor
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
String[] noRollbackForClassName() default {};
}