sevice层的一个事务方法A(),
在A()方法里面调用了spring框架中某个类的一个方法B(),
问题是,方法B()会加入到A()方法的事务当中吗?
如果是的话,是不是在项目启动时,spring就定义好了框架中每个方法的事务配置?
还有就是这些与spring的事务的传播属性有没有关系?
希望有大神能够从spring的原理去解释。
“事务”是对一个封闭的系统的原子化来说的,通常是指数据库。你随便写一个方法B,它没有可逆的操作,那么会破坏事务的完整性。
一个极端的例子,你的B()的代码是调用支付宝向另一个账户付款,你还能指望这个操作成功之后取消操作,再把钱返回来么?显然不行。
我感觉你想做的是嵌套事务,有关这个,参考:https://www.cnblogs.com/jimmy-muyuan/p/5722708.html
事务的保障为整体操作的原子性,关于保障多个方法执行之间的原子性 ,你应该保持合理的持久化上下文。如果你用的是JPA或者Hibernate,那么你最好将
所有的查询方法放在前面,最后将更改数据的方法放在后面。然后手动刷新。
事务回滚本质依赖于数据库的事务回滚方法,当方法回滚时,数据库只会回滚此次事务进行的写操作。而数据库主键不会因此减小,因为它内部是一个自增数。
所以,在不影响业务逻辑的情况下,不用考虑主键恢复。这是数据库的基础设施
spring管理事务是管理到方法级别的,就是根据传播属性控制的
方法B是否回滚,应该看你的spring配置了,可以配置回滚,也可以配置不回滚。
spring配置很灵活的,方法A,B可以配置到1个事务,同时回滚;也可以配置到2个事务,分别管理回滚,也可以配置一个支持一个不支持事务。
参考下:https://blog.csdn.net/mawming/article/details/52277431
这个问题的关键在于事务的传播机制,如果没有事务,就新建事务,如果当前连接中已经开启了事务,就加入这个事务中。
PROPAGATION_REQUIRED,了解一下。
下面的方法是CrudService类的,它是加入到了已经开启的事务中的
public void insert(T entity) {
Validate.isTrue(entity.getId() == null);
preInsert(entity);
dao.insert(entity);
}
以下是SysCrudService类里的方法,继承了上面的CrudService类
preInsert方法没有配置事务
@Override
protected void preInsert(T entity) {
DataFieldMaxValueIncrementer incrementer = sequenceFactory.name(getSequenceName(entity));
entity.setId(incrementer.nextLongValue());
entity.setCreateDate(new Date());
entity.setUpdateDate(entity.getCreateDate());
entity.setDelFlag(AuditEntity.DEL_FLAG_NORMAL);
User user = UserContext.getCurrentUser();
if (user != null) {
entity.setCreateBy(user.getId());
entity.setUpdateBy(user.getId());
} else {
entity.setCreateBy(0L);
entity.setUpdateBy(0L);
}
}
上面方法中的DataFieldMaxValueIncrementer对象incrementer调用的nextLongValue()方法里调用了一个自身的一个抽象方法getNextKey(),这个抽象方法又被SequenceGenerator类重写了,代码如下:
public class SequenceGenerator extends AbstractDataFieldMaxValueIncrementer {
private static String SELECT_SEQ = "select name,nextid,increment,padding,prefix from %s where name=?";
private static String UPDATE_SEQ = "update %s set nextid=? where name=? and nextid=?";
private static String INSERT_SEQ = "insert into %s (name,nextid,increment,padding,prefix) value (?, ?, ?, ?, ?)";
private long maxID;
private long currentID = 0l;
private JdbcHelper jdbcHelper;
private int increment = 5;
private String selectSql;
private String updateSql;
private String insertSql;
/** The name of the rowId for this sequence */
private String rowId;
private String prefix;
public SequenceGenerator() {
this.maxID = 0L;
this.currentID = 0L;
}
public SequenceGenerator(DataSource dataSource, String incrementerName, String rowId, int increment) {
super(dataSource, incrementerName);
this.maxID = 0L;
this.currentID = 0L;
this.increment = increment;
this.jdbcHelper = new JdbcHelper(new JdbcTemplate(dataSource));
this.setRowId(rowId);
this.setIncrementerName(incrementerName);
}
@Override
protected long getNextKey() {
return nextUniqueID();
}
/**
* Returns the next available unique ID. Essentially this provides for the
* functionality of an auto-increment database field.
*/
public synchronized long nextUniqueID() {
if (!(currentID < maxID)) {
// Get next block -- make 5 attempts at maximum.
getNextBlock(5);
}
long id = currentID;
currentID++;
return id;
}
private void getNextBlock(int count) {
if (count == 0) {
LogFactory.getLog(SequenceGenerator.class).error("Failed at last attempt to obtain an ID, aborting...");
return;
}
boolean success = false;
try {
Sequence sequence = this.jdbcHelper.directQueryForObject(Sequence.class, this.selectSql, this.rowId);
if (sequence == null) {
sequence = new Sequence(this.increment);
sequence.setNextid(1L);
sequence.setName(this.rowId);
this.jdbcHelper.directExecuteUpdate(this.insertSql, //
sequence.getName(), //
sequence.getNextid(), //
sequence.getIncrement(), sequence.getPadding(), sequence.getPrefix());
}
this.prefix = sequence.getPrefix();
this.currentID = sequence.getNextid();
long newID = this.currentID + sequence.getIncrement();
sequence.setCurrid(currentID);
sequence.setNextid(newID);
int occurs = this.jdbcHelper.directExecuteUpdate(this.updateSql,//
sequence.getNextid(), //
sequence.getName(), //
sequence.getCurrid());//
this.setPaddingLength(sequence.getPadding());
success = occurs == 1;
if (success) {
this.maxID = newID;
}
} catch (Exception ex) {
LogFactory.getLog(SequenceGenerator.class).error(ex.getMessage());
} finally {
}
if (!success) {
LogFactory.getLog(SequenceGenerator.class).error("WARNING: failed to obtain next ID block due to thread contention. Trying again...");
// Call this method again, but sleep briefly to try to avoid thread
// contention.
try {
Thread.sleep(75);
} catch (InterruptedException ie) {
}
getNextBlock(count - 1);
}
}
}
private void getNextBlock(int count) 方法里,有一个更新SQL语句。
我遇到的问题是,当最上面的insert方法的父级事务遇到了一个回滚的时候,更新SQL语句居然也跟着回滚了。照理说getNextBlock方法都不受事务的控制的,SQL语句就不会回滚的啊。
难道是只要父级调用方法开启了事务,那么里面不管有多少层的嵌套调用都是在一个事务中?所以才会跟着回滚的?
配置文件里的jdbcTemplate和mybatis都用一个数据源,而数据源的事务配置里没有扫描到getNextBlock方法,所以该方法是没有事务的。
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
default-lazy-init="true">
<description>Spring Configuration主启动文件</description>
<!-- 加载配置属性文件 -->
<context:property-placeholder
ignore-unresolvable="true" location="classpath:config.properties" />
<!-- 必须定义 lazy-init="false" 否则 SpringContextHolde的方法将失效 -->
<bean class="com.pds.j2ee.context.SpringContextHolder" lazy-init="false">
</bean>
<!-- 定义默认数据源配置, 使用 Druid 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用) <property name="poolPreparedStatements"
value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize"
value="20" /> -->
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<!-- 定义事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- service 事务通知 -->
<tx:advice id="serviceTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="save*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="add*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="submit*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="create*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="imp*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="insert*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="set*" propagation="SUPPORTS" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="remove*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="assign*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="complete*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="sign*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="destroy*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="find*" propagation="SUPPORTS" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="select*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<!-- aop自动事务 -->
<aop:config>
<aop:pointcut id="serviceTx" expression="execution(public * com..*.service..*.*(..))" />
<aop:advisor pointcut-ref="serviceTx" advice-ref="serviceTxAdvice" />
</aop:config>
<!-- 依据特定的项目设置base-package,指定自己项目的包 -->
<context:component-scan base-package="com.argi,com.pds.modules,com.znph.saas.sys">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven proxy-target-class="true" />
<!-- 配置DatabaseProble:主要配置增长序列 -->
<bean class="com.pds.j2ee.persistence.DatabaseProble">
<property name="defaultDatasource" ref="dataSource" />
</bean>
<!-- JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0">
<ref bean="jdbcTemplate" />
</constructor-arg>
</bean>
<!-- * <pre> * CREATE TABLE sys_sequence (
name VARCHAR(50), nextid
BIGINT UNSIGNED, increment TINYINT UNSIGNED,
padding VARCHAR(10), prefix
VARCHAR(10) ) ENGINE=MyISAM * </pre> */ -->
<bean id="cachedSequenceFactory" class="com.pds.jdbc.pk.SequenceFactory">
<property name="dataSource" ref="dataSource" />
<property name="incrementerName" value="sys_sequence" />
<property name="increment" value="5"></property>
</bean>
<bean id="serialSequenceFactory" class="com.pds.jdbc.pk.SequenceFactory">
<property name="dataSource" ref="dataSource" />
<property name="incrementerName" value="sys_sequence" />
<property name="increment" value="1"></property>
</bean>
<!-- 缓存配置 -->
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:${ehcache.configFile}" />
</bean>
<!--
drop table `sys_lock`;
CREATE TABLE `sys_lock` (
`lock_name` varchar(100) DEFAULT NULL,
KEY `lock_key` (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-->
<bean class="com.pds.jdbc.lock.JdbcSemaphore">
<property name="dataSource" ref="dataSource" />
<property name="tableName" value="sys_lock" />
<property name="columnName" value="lock_name" />
</bean>
<bean id="defaultDb" class="com.pds.jdbc.ar.DB">
<constructor-arg index="0">
<ref bean="dataSource" />
</constructor-arg>
<constructor-arg index="1">
<ref bean="cachedSequenceFactory" />
</constructor-arg>
</bean>
</beans>