読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

EclipseLinkとSpring2.5でLazy Loading

JPA EclipseLink Spring

拙作R42FWではEclipseLinkとSpring2.5を組み合わせて使用していますが、
この組み合わせで、JPAのLazy Loadingが動作させることができたので、その手順を紹介します。

EclipseLink単品で使用する場合の手順では動かない

まず最初にこちらに書いてある手順でやってみたのですが、動きませんでした。

どうやら、Springと組み合わせる場合はこの手順ではダメのようです。


TomcatのVM引数に追加

-javaagent:eclipselink.jar


applicationContext.xml

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" />
  </property>
  <property name="jpaPropertyMap">
    <map>
      <entry key="eclipselink.weaving" value="true" />
    </map>
  </property>
</bean>


Tomcat起動時にエラーが発生

java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
	at org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver.addTransformer(InstrumentationLoadTimeWeaver.java:88)
	at org.springframework.context.weaving.AspectJWeavingEnabler.postProcessBeanFactory(AspectJWeavingEnabler.java:69)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:553)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:536)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:362)
	at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:45)
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3934)
	at org.apache.catalina.core.StandardContext.start(StandardContext.java:4429)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardHost.start(StandardHost.java:722)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
	at org.apache.catalina.core.StandardService.start(StandardService.java:516)
	at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
	at org.apache.catalina.startup.Catalina.start(Catalina.java:583)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
	at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)

Springと組み合わせる場合の手順

まず、applicationContext.xmlを次のようにします。

<!-- これを追加 -->
<context:load-time-weaver aspectj-weaving="on" class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />


<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" />
  </property>
  <property name="jpaPropertyMap">
    <map>
      <entry key="eclipselink.weaving" value="true" />
    </map>
  </property>
</bean>


次に、javaagentの値を次のようにspringのものへ変更します。

-javaagent:spring-agent.jar


これで、Lazy Loadingができるようになります。


以下の文字列は、とあるエンティティをtoStringした結果です。
_persistence_productImage_vh={UnitOfWorkQueryValueHolder: not instantiated}
というところがバイトコードエンハンスされたっぽい箇所です。

実行SQLのログを見ても、きちんとLazy Loadingされている模様です。

jp.root42.r42fw_sample_shop.model.entity.Product@1db3f6c[
  id=10
  productName=弁当9
  productNameKana=ベントウキュウ
  productCode=bento9
  price=900
  productStatusType=SELLING
  _persistence_productImage_vh={UnitOfWorkQueryValueHolder: not instantiated}
]

動きそうに見えて動かない設定

ちなみに、以下のように設定しても同等な気がしたので、試しにやってみたのですがダメでした。


applicationContext.xml

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" />
  </property>
  <property name="loadTimeWeaver">
    <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> ← <context:load-time-weaver>を消してこれを追加
  </property>     
  <property name="jpaPropertyMap">
    <map>
      <entry key="eclipselink.weaving" value="true" />
    </map>
  </property>
</bean>


ぬるぽ

java.lang.NullPointerException
	at org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getMethodReturnType(PrivilegedAccessHelper.java:300)
	at org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getGetMethodReturnType(MethodAttributeAccessor.java:104)
	at org.eclipse.persistence.mappings.ForeignReferenceMapping.validateBeforeInitialization(ForeignReferenceMapping.java:1373)
	at org.eclipse.persistence.descriptors.ClassDescriptor.validateBeforeInitialization(ClassDescriptor.java:5123)
	at org.eclipse.persistence.descriptors.ClassDescriptor.preInitialize(ClassDescriptor.java:3198)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:429)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:406)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:666)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.loginAndDetectDatasource(DatabaseSessionImpl.java:617)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:227)
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:255)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.getServerSession(EntityManagerFactoryImpl.java:111)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:163)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:150)
	at org.springframework.orm.jpa.JpaTransactionManager.createEntityManagerForTransaction(JpaTransactionManager.java:392)
	at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:320)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:374)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:263)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:101)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)

エンハンス済みクラスファイルの中身を見てみよう!と試してみたが失敗


次のようなコードでクラスファイルをファイルへダンプして、
jadデコンパイルしてみようと思いましたが、なぜかShop.classが0バイトに…残念。

final InputStream src =
    Shop.class.getClassLoader().getResourceAsStream(
        "/jp/root42/r42fw_sample_shop/model/entity/Shop.class");

final FileOutputStream dst = new FileOutputStream(new File("C:/Temp/Shop.class"));

IOUtils.copy(src, dst);
IOUtils.closeQuietly(src);
IOUtils.closeQuietly(dst);