2011-03-08 4 views
15

J'ai trouvé un comportement étrange lors de l'utilisation de transactions Spring imbriquées: lorsque, dans la même classe, une méthode annotée @Transactional appelle une autre méthode également annotée comme @Transactional la deuxième annotation n'est pas utilisée.Transaction imbriquée au printemps

Considérons la classe suivante:

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(); 
    } 
} 

L'entité si la classe suivante:

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

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

    public int getId() { 
     return id; 
    } 
} 

et les parties de cordes 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> 

et la classe de configuration (Je pourrais avoir fusionner ceci dans applicationContext.xml).

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

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

Pour être complet le fichier 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> 

Ainsi, dans la classe principale, la première opération est réalisée comme prévu qui est dans une nouvelle transaction. La sortie (y compris certains messages debug) est:

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

La deuxième opération donne le résultat suivant:

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

Ce n'est pas ce que je pensais depuis à annoter singleInsert avec @Transactional(propagation=Propagation.REQUIRES_NEW) j'attendre une nouvelle transaction à créer pour chaque appel qui n'est pas ce qui se passe puisque la même transaction de niveau supérieur est utilisée pour les deux insertions.

La troisième opération échoue ainsi que aucune transaction est créée à tous:

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 

Dans les @Configuration haricots printemps assure que les appels à la méthode de la même classe sont proxified ce qui est évidemment pas le cas ici. Y at-il un moyen de changer ce comportement?

Répondre

8

Ce comportement est le comportement documenté de Spring lors de l'utilisation du mode proxy pour AOP. Il peut être modifié en basculant vers le modequi exécute l'instrumentation de code lors de la compilation ou de l'exécution.

6

Ce n'est pas spécifiquement un problème avec @Transactional. C'est en raison de la configuration de votre <tx:annotation-driven/>.

Spring utilise deux mécanismes AOP différents: les proxies dynamiques JDK ou CGLIB. JDK proxies dynamiques est la valeur par défaut et il fonctionne grâce à l'utilisation d'interfaces à exécuter -time. CGLIB fonctionne en générant des sous-classes au compiler -time. Si vous spécifiez <tx:annotation-driven proxy-target-class="true"/>, Spring utilisera CGLIB et votre deuxième @Transactional se déclenchera.

Vous pouvez en savoir plus sur le sujet here.

Questions connexes