Вложенная транзакция на Spring - PullRequest
15 голосов
/ 08 марта 2011

Я обнаружил странное поведение при использовании вложенных транзакций Spring: когда в том же классе метод, аннотированный как @Transactional, вызывает другой метод, также аннотированный как @Transactional, вторая аннотация не используется.

Давайте рассмотрим следующий класс:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        final Main main = context.getBean(Main.class);
        // First Op
        System.out.println("Single insert: " + main.singleInsert());
        // Second Op
        main.batchInsert();
        // Third Op
        main.noTransBatchInsert();
    }

    @PersistenceContext
    private EntityManager pm;

    @Transactional(propagation=Propagation.REQUIRED)
    public void batchInsert() {
        System.out.println("batchInsert");
        System.out.println("First insert: " + singleInsert());
        System.out.println("Second insert: " + singleInsert());
    }

    public void noTransBatchInsert() {
        System.out.println("noTransBatchInsert");
        System.out.println("First insert: " + singleInsert());
        System.out.println("Second insert: " + singleInsert());
    }

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public int singleInsert() {
        System.out.println("singleInsert");
        Pojo p = new Pojo();
        pm.persist(p);
        return p.getId();
    }
}

Сущность, если следующий класс:

@Entity
public class Pojo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Override
    public String toString() {
        return "Pojo: " + id;
    }

    public int getId() {
        return id;
    }
}

и строковые части applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

    <tx:annotation-driven />

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="MyPersistenceUnit" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
</beans>

и класс конфигурации (я мог бы объединить это в applicationContext.xml).

@Configuration
@ImportResource("/META-INF/applicationContext.xml")
public class Config {

    @Bean
    public Main main() {
        return new Main();
    }
}

Для полноты файла persistence.xml:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">

    <persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
            <property name="hibernate.connection.driver_class" value="org.h2.Driver" />
            <property name="hibernate.connection.url" value="jdbc:h2:mem:TestDSJPA2;DB_CLOSE_DELAY=-1;LOCK_MODE=0" />
            <!--<property name="hibernate.connection.url" value="jdbc:h2:mem:TestDSJPA2;DB_CLOSE_DELAY=-1;LOCK_MODE=0" />-->
            <property name="hibernate.connection.username" value="sa" />
            <property name="hibernate.connection.password" value="" />
            <property name="hibernate.connection.autocommit" value="false"/>

            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>

    </persistence-unit>
</persistence>

Итак, в основном классе первая операция выполняется, как и ожидалось, в новой транзакции. Вывод (включая некоторые сообщения DEBUG):

DEBUG o.h.transaction.JDBCTransaction  - begin
singleInsert
DEBUG o.h.transaction.JDBCTransaction  - commit
Single insert: 1

Вторая операция дает следующий вывод:

batchInsert
singleInsert
DEBUG o.h.transaction.JDBCTransaction  - begin
First insert: 2
singleInsert
Second insert: 3
DEBUG

Это не то, что я ожидал, так как при аннотировании singleInsert с помощью @Transactional(propagation=Propagation.REQUIRES_NEW) я ожидал бы, что для каждого вызова будет создана новая транзакция, а не то, что происходит, поскольку одна и та же транзакция верхнего уровня используется для обеих вставок.

Третья операция не выполняется, так как транзакция вообще не создается:

noTransBatchInsert
singleInsert
DEBUG o.h.e.def.AbstractSaveEventListener  - delaying identity-insert due to no transaction in progress
First insert: 0
singleInsert
DEBUG o.h.e.def.AbstractSaveEventListener  - delaying identity-insert due to no transaction in progress
Second insert: 0

В @Configuration bean-компоненте Spring гарантирует, что вызовы метода того же класса проксируются, что, очевидно, здесь не происходит. Есть ли способ изменить это поведение?

Ответы [ 2 ]

9 голосов
/ 15 марта 2011

Это поведение является документированным поведением Spring при использовании режима proxy для AOP. Его можно изменить, переключившись в режим aspectj, который выполняет инструментарий кода при компиляции или во время выполнения.

7 голосов
/ 10 июля 2012

Это конкретно не проблема с @Transactional. Это связано с конфигурацией вашего <tx:annotation-driven/>.

Spring использует два разных механизма AOP: динамические прокси JDK или CGLIB. Динамические прокси-серверы JDK используются по умолчанию и работают через интерфейсы run -time. CGLIB работает, генерируя подклассы во время compile . Если вы укажете <tx:annotation-driven proxy-target-class="true"/>, Spring будет использовать CGLIB, а ваш второй @Transactional сработает.

Вы можете узнать больше о предмете здесь .

...