2011-03-08 11 views
15

encontré un comportamiento extraño cuando se utiliza transacciones Spring anidados: cuando, en la misma clase, un método anotado como @Transactional llama a otro método también anotado como @Transactional no se utiliza la segunda anotación.transacción anidada sobre el Spring

Vamos a considerar la clase siguiente:

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

La entidad si la clase siguiente:

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

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

    public int getId() { 
     return id; 
    } 
} 

y las partes de Cuerda 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> 

y la clase de configuración (Podría haber fusionado esto en applicationContext.xml).

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

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

Para completar el archivo 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> 

Así que en la clase principal, la primera operación se lleva a cabo como se esperaba que se encuentra en una nueva transacción. La salida (incluyendo algunos mensajes de depuración) es:

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

La segunda operación da el siguiente resultado:

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

Esto no es lo que esperaba ya que en la anotación de singleInsert con @Transactional(propagation=Propagation.REQUIRES_NEW) yo esperaría una nueva transacción que se creará para cada llamada que no es lo que está sucediendo ya que se usa la misma transacción de nivel superior para ambas inserciones.

La tercera operación falla, así como no hay ninguna transacción se crea en absoluto:

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 

En los @Configuration frijoles asegura la primavera que las llamadas al método en la misma clase se proxified que es evidente que no ocurre aquí. ¿Hay alguna manera de cambiar este comportamiento?

Respuesta

8

Este comportamiento es el comportamiento documentado de Spring al utilizar el modo proxy para AOP. Se puede cambiar cambiando al modo aspectj que realiza la instrumentación del código ya sea en compilación o en tiempo de ejecución.

6

Esto no es específicamente un problema con @Transactional. Es debido a la configuración de su <tx:annotation-driven/>.

Spring utiliza dos mecanismos AOP diferentes: proxies dinámicos JDK o CGLIB. Los proxys dinámicos JDK son los predeterminados y funcionan mediante el uso de interfaces en ejecutar -time. CGLIB funciona generando subclases en compila -time. Si especifica <tx:annotation-driven proxy-target-class="true"/>, Spring usará CGLIB, y su segundo @Transactional disparará.

Puedes leer más sobre.

Cuestiones relacionadas