事务方法调用spring框架的方法,框架的方法也会加入到当前事务吗?

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>