0

クライアントがサービスで2つのメソッドを呼び出すと、セッションが関連付けられていない2番目のメソッドのトランザクションで失敗するという問題があります。しかし、両方のメソッドをサービスに組み合わせて、クライアントコードからその1つのメソッドを呼び出すと、成功します。

なぜこれが起こるのか誰かが私に説明できますか?

次のコードを検討してください。

@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は、現在のトランザクションに関連付けられたセッションがないことを示す例外をスローするため、レイジーにロードされた#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>
4

1 に答える 1

1

JTAトランザクションを操作する場合、セッション(つまり、多かれ少なかれentityManager)は自動的にトランザクションにバインドされます。これは、トランザクションT1中にフェッチされたエンティティが、T1にバインドされたentityManager/セッションでフェッチされることを意味します。

T1がコミットされると、entityManager /セッションはトランザクションにアタッチされなくなります(T1が終了したため)。

クライアントがこれを行う場合:

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

parentはT1中にフェッチされるため、T1にバインドされたentityManager(EM1と呼びます)にバインドされます。しかし、T1は終了しています(戻るときにコミットされましたfindById)。

getChildrenForは:で注釈が付けられているため@Transactional、新しいtx(つまり、T2)がtxManagerによって開始されます。これにより、T2に関連付けられた新しいentityManager(つまり、EM2)が作成されます。ただし、 EM1にparent属し、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

getChildrenFor( Long id )この時点で、同じtx(呼び出し)で親と子をフェッチする場合、両方(親と子)が同じentityManagerに属しているため、マージする必要がないことを理解していただければ幸いです。

于 2013-01-30T09:38:06.900 に答える