当前位置: 首页 > 知识库问答 >
问题:

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

高宸
2023-03-14

我有一个问题,如果我的服务上有一个客户端调用两个方法,它就会失败,因为第二个html" target="_blank">方法中的事务没有与之关联的会话。但是,如果我将这两种方法组合到服务中,并从客户机代码中调用其中一种方法,它就会成功。

谁能给我解释一下为什么会这样?

考虑下面的代码:

@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抛出一个异常,表示没有与当前事务关联的会话,因此它无法遍历延迟加载的PersistentSet#getChildren。

现在我不是JPA或Spring专家,所以这可能是有意的行为。如果是,你能告诉我为什么吗?我是否需要创建一个不是服务要公开的实体的DTA,然后让我的客户机使用它而不是实体?我认为,由于两个调用都使用相同的实体管理器引用,因此客户端应该能够进行两个调用。

顺便说一句,这是使用CTW的“Spring配置”。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个答案

申屠新觉
2023-03-14

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

一旦提交了T1,实体管理器/会话就不再附加到任何事务(因为T1已经完成)。

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

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

parent是在T1期间获取的,因此它绑定到与T1绑定的entityManager(我们称之为EM1)。但是T1已经完成(它是在findByIdreturn时提交的)。

因为getChildrenFor是用@Transactional注释的:一个新的tx(即.T2)由txManager启动。这将创建一个新的entityManager(即。EM2)与T2相关联。但是属于EM1,并且EM1仍然没有绑定到任何正在运行的tx。

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

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

调用合并

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

(请注意,持久性上下文是与当前entityManager关联的存储

mergedParent现在属于EM2,并且EM2绑定到当前运行的T2,因此调用mergedParent。getChildren()不应该失败。

关于merge的重要备注:需要注意的是merge返回一个新实例,并且不要接触传入参数的实例。在使用JPA时,认为merge修改实例是一个非常常见的错误/误解。

在这一点上,我希望您理解,当您在同一个tx中获取父级和子级时(调用getChildrenFor(Long id)),没有必要合并,因为父级和子级都属于同一个entityManager。

 类似资料:
  • 长话短说:我们开发并维护了一个库,可以在使用JavaEE7/CDI/JPA的其他项目中使用。应用程序将在Glassfish-4.0下运行,并使用Hibernate的JPA实现实现底层的PostgreSQL持久性。这是将用Spring/Struts/Hibernate编写的旧应用程序重写到JavaEE7/CDI/JTA新世界的长期迁移工作的一部分。 问题:出于审计目的,我们的库需要在执行用户语句之前

  • 并用@Transactional注释了具体类。 我们使用Jboss应用服务器支持通过JNDI与MQ集成。这里的问题是,如果监听器中的任何层有任何异常,则整个事务不会回滚,消息也不会移动到退出队列。很明显,当我们使用Hibernate事务管理器时,它不知道其他资源,如JMS事务。 我可以安全地用JTA事务替换它吗,因为Jboss将处理整个事务管理?这样做是否有任何可预见的风险?

  • 我指的是https://developer.jboss.org/wiki/SessionsAndTransactions,目前正试图了解与JTA的划界问题。它声明在使用getCurrentSession()的特定事务中,总是给出相同的当前会话。是不是意味着: < li >如果另一个用户在另一个线程中执行同一段代码(通过查找获取一个事务,然后使用getCurrentSession()并关闭该事务),

  • 环境: 我们有一个应用程序部署在 JBoss 4.2.3.GA 服务器中,它使用Hibernate 3.4 和 JTA 1.0。 有一个导入器创建或更新某些实体,然后导入一些数据。由于多种原因,大部分导入是在新事务中完成的,在每个事务中,在外部事务中创建/更新的实体可能会再次更新。 调用序列类似于以下伪代码: 服务1: 服务2: 问题: 现在的问题是,我们最终会遇到一个竞争条件,有几个事务试图锁定

  • 我对Hibernate中的会话和事务的概念有点困惑。据我所知,Hibernate使用会话(持久性上下文),它基本上是需要持久,删除或数据库中任何内容的实体的缓存。会话封装事务,因此我启动一个会话,然后创建一个事务。事务关闭后,持久性上下文中的所有内容都将刷新到数据库,如果我关闭会话,也会发生同样的事情。 为什么我需要两者?我可以在不创建交易的情况下做同样的事情吗?

  • 我正在计划一个设计,我将从同一个池中获得两个连接(plocal)。仅在一个连接上启动事务,而不在另一个连接上启动事务。我希望在同一过程中使用这两个数据库连接,并使用非事务连接进行模式调用,使用事务连接进行支持事务的记录级调用。这种方法有效吗?