多个JTA事务:没有与当前事务关联的会话

时间:2013-01-29 20:07:12

标签: java spring hibernate jpa

我有一个问题,如果我有一个客户端调用我的服务上的两个方法,它会失败,第二个方法中的事务没有与之关联的会话。但是如果我将两种方法结合到服务中并从我的客户端代码中调用一个方法就可以成功。

有人可以向我解释为什么会这样吗?

请考虑以下代码:

@Configurable
public class ParentService {

    @PersistenceContext
    private EntityManager entityManager;

    public ParentService() {
    }

    @Transactional
    public Parent findById( Long id ) {
       return entityManager.findById( Parent.class, id );
    }

    @Transactional
    public Set<Child> getChildrenFor( Parent parent ) {
       return Collections.unmodifiableSet( new HashSet<>( parent.getChildren() ) );
    }

    @Transactional
    public Set<Child> getChildrenFor( Long id ) {
       Parent parent = findById( id );
       return getChildrenFor( parent );
    }

    ...
}

所以这里发生的是我的客户端代码(不知道事务)如果我调用#​​getChildrenFor(id)我很好。但如果我打电话:

   Parent parent = service.findById( id );
   Set<Child> children = service.getChildrenOf( parent );

然后hibernate抛出一个异常,说没有与当前事务关联的会话,所以它不能遍历Lagetily加载的#getChildren的PersistentSet。

现在我不是JPA或Spring专家,所以也许这是预期的行为。如果是这样,你能让我知道为什么吗?我是否需要创建一个不是要公开的服务实体的DTA,然后让我的客户使用它而不是实体?我想是因为两个调用都使用相同的实体管理器引用,客户端应该能够进行两次调用。

顺便说一句,这是使用CTW“弹簧配置”。 JpaTransactionManager配置为交叉切割,如下所示:

@Bean
public PlatformTransactionManager transactionManager() {

    JpaTransactionManager txnMgr = new JpaTransactionManager();

    txnMgr.setEntityManagerFactory( entityManagerFactory().getObject() );

    // cross cut transactional methods with txn management
    AnnotationTransactionAspect.aspectOf().setTransactionManager( txnMgr );

    return txnMgr;
}

请告诉我我可以提供的任何其他信息,以帮助解决此问题。

Spring XML配置:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
    <context:spring-configured/>
    <context:component-scan base-package="com.myapp"/>
</beans>

Spring Java配置:

@Configuration
@PropertySource( "classpath:database.properties" )
public class DatabaseConfiguration {

    @Value( "${database.dialect}" )
    private String databaseDialect;

    @Value( "${database.url}" )
    private String databaseUrl;

    @Value( "${database.driverClassName}" )
    private String databaseDriver;

    @Value( "${database.username}" )
    private String databaseUser;

    @Value( "${database.password}" )
    private String databasePassword;

    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();

        factory.setPersistenceUnitName( "persistenceUnit" );
        factory.setDataSource( dataSource() );

        Properties props = new Properties();
        props.setProperty( "hibernate.dialect", databaseDialect );
        factory.setJpaProperties( props );

        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txnMgr = new JpaTransactionManager();
        txnMgr.setEntityManagerFactory( entityManagerFactory().getObject() );

        // cross cut transactional methods with txn management
        AnnotationTransactionAspect.aspectOf().setTransactionManager( txnMgr );

        return txnMgr;
    }

    @Bean
    public DataSource dataSource() {
        final BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName( databaseDriver );
        dataSource.setUrl( databaseUrl );
        dataSource.setUsername( databaseUser );
        dataSource.setPassword( databasePassword );

        dataSource.setTestOnBorrow( true );
        dataSource.setTestOnReturn( true );
        dataSource.setTestWhileIdle( true );
        dataSource.setTimeBetweenEvictionRunsMillis( 1800000 );
        dataSource.setNumTestsPerEvictionRun( 3 );
        dataSource.setMinEvictableIdleTimeMillis( 1800000 );

        return dataSource;
    }
}

POM:

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.2</version>
        <!-- NB: do not use 1.3 or 1.3.x due to MASPECTJ-90 and do not use 1.4 due to de`clare parents issue  -->
        <dependencies>
            <!-- NB: You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) -->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
        </dependencies>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <outxml>true</outxml>
            <aspectLibraries>
                <aspectLibrary>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                </aspectLibrary>
            </aspectLibraries>
            <source>${java.version}</source>
            <target>${java.version}</target>
        </configuration>
    </plugin>

1 个答案:

答案 0 :(得分:1)

使用JTA事务时,会话(即或多或少的entityManager)会自动绑定到事务。 这意味着在事务T1期间获取的实体是在与T1绑定的entityManager /会话中获取的。

提交T1后,entityManager / session不再附加到任何事务(因为T1已完成)。

当您的客户执行此操作时:

Parent parent = service.findById( id );
Set<Child> children = service.getChildrenFor( parent );
在T1期间获取

parent,因此,它绑定到与T1绑定的entityManager(让我们称之为EM1)。但是T1已经完成(它是在findById返回时提交的。)

由于getChildrenFor注释为@Transactional:txManager启动新的tx(即T2)。这将创建与T2相关联的新entityManager(即EM2)。但是parent属于EM1,而EM1仍未绑定到任何正在运行的tx。

要解决您的问题,您可以调整此方法的代码:

@Transactional
public Set<Child> getChildrenFor( Parent parent ) {
   Parent mergedParent = entityManager.merge(parent);
   return Collections.unmodifiableSet( new HashSet<>( mergedParent.getChildren() ) );
}

致电merge

  

将给定实体的状态合并到当前持久化上下文中。

(请注意持久性上下文是与当前entityManager关联的商店

mergedParent现在属于EM2,EM2绑定到当前正在运行的T2,因此调用mergedParent.getChildren()不会失败。

关于merge

重要提示:请务必注意merge返回新实例并且不要触及传入的实例论点。使用JPA时认为merge修改实例是一个非常常见的错误/误解。

此时,我希望您理解当您在同一个tx(调用getChildrenFor( Long id ))中获取父级和子级时,不需要合并,因为两者(父级和子级)属于同一个entityManager