使用 AspectJ 样式 Spring AOP 的请求作用域 bean 的空指针异常(@Around 建议使用 @Transactional webservice 方法)
我正在使用 Spring 3.1.0.RC1。我有一个基于 Apache CXF 的 Web 服务。我试图为每个 @Transactional 注释的服务方法提供建议。
我有一些关于周围建议的方面。在该方法内部,我将数据存储到请求范围的事务负载 bean 中。后来我在另一个单例作用域 bean 中请求此有效负载中的 uuid。结果是空指针异常。
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
at com.spp.mui.jaxws.service.virtual._0_1.VirtualWebService.postOfferSet(VirtualWebService.java:209)
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.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.spp.mui.aop.tx.NewAgeTransactionLoggingAspect.logTransaction(NewAgeTransactionLoggingAspect.java:85)
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.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.spp.mui.aop.ws.TraceWebServiceMethodAspect.invoke(TraceWebServiceMethodAspect.java:68)
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.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy95.postOfferSet(Unknown Source)
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.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:173)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:89)
... 49 more
Caused by: java.lang.NullPointerException
at com.spp.mui.jaxws.agent.WSResponseAgent.getResponse(WSResponseAgent.java:28)
at com.spp.mui.jaxws.agent.WSResponseAgent.getResponse(WSResponseAgent.java:18)
at com.spp.mui.jaxws.handler.offer.PostVirtualOffersHandler.handle(PostVirtualOffersHandler.java:58)
at com.spp.mui.jaxws.service.virtual._0_1.VirtualWebService.postOfferSet(VirtualWebService.java:205)
... 88 more
那么这个堆栈跟踪中发生了什么?
调用了 Web 服务方法(即 postOfferSet)。该方法是由带有 @Around 切入点的方面建议的。该方法也带有 @Transactional 注释。
更新我已经将原始配置更新为基于Java配置。无论是 XML 配置还是 Java 配置,我都遇到了同样的问题。我的 Java 配置是当前正在使用的配置,也是我希望帮助诊断的配置。请向下滚动(向前跳)查看下面的 Java 配置。 (对于其他刚接触这篇文章的人,请继续阅读)。
我的方面配置看起来有点像
<aop:aspectj-autoproxy />
<bean id="newAgeTxLoggingAspect" class="com.spp.mui.aop.tx.NewAgeTransactionLoggingAspect">
<property name="transactionLogService" ref="txLogServiceEngine" />
<property name="securable" ref="securityAgent" />
<property name="loggableTransactionToken" ref="loggableTransactionToken" />
<property name="logQuery" value="${newAgeTxLoggingAspect.logQuery}" />
<property name="logReply" value="${newAgeTxLoggingAspect.logReply}" />
<property name="logSubmit" value="${newAgeTxLoggingAspect.logSubmit}" />
<property name="maxReplySize" value="${newAgeTxLoggingAspect.maxReplySize}" />
<property name="maxRequestSize" value="${newAgeTxLoggingAspect.maxRequestSize}" />
<property name="order" value="5" />
</bean>
前面提到的 loggableTransactionToken 是请求范围的。
这是它的 bean def
<!--
Request scoped bean that allows LoggableTransaction instances to be created and accessed on a single-thread of execution
Consult http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-scopes-other-injection
-->
<bean id="loggableTransactionToken" class="com.spp.mui.commons.spring.LoggableTransactionToken" scope="request">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
前面提到的 Web 服务方法委托给 Handler
这是 Handler 的一个片段
@Override
public ConfirmationType handle(OfferSetType request) {
// step 1: convert from JAXB type to domain objects
List<MktVirtualOffer> candidateOffers = codecService.convert(request, List.class);
...
// step 4: get response -- response encapsulates transaction id
return responder.getResponse();
}
请注意,Handler 使用了 Responder?
这是响应程序(又名 WSResponseAgent)的片段,
@Override
public ConfirmationType getResponse() {
ConfirmationType response = new ConfirmationType();
LoggableTransaction lt = token.getToken();
response.setTransactionID(lt.getUuid());
logger.debug(ReflectUtil.toString(lt));
return response;
}
希望我已经提供了足够的信息来帮助您调查为什么我会收到 NPE?
更新这是Java配置和附加实现
AOP配置
@Configuration
@EnableAspectJAutoProxy
@Import(value = { LoggingConfig.class, AuthConfig.class })
public class AopConfig {
@Resource
private Environment env;
@Resource
private LoggingConfig loggingConfig;
@Resource
private AuthConfig authConfig;
/* Consult
* static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html#transaction-declarative-applying-more-than-just-tx-advice
* and http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-ataspectj-advice-ordering
*/
@Bean
public NewAgeTransactionLoggingAspect txLoggingAspect() {
NewAgeTransactionLoggingAspect aspect = new NewAgeTransactionLoggingAspect();
aspect.setEnvironment(env);
aspect.setLoggableTransactionToken(loggingConfig.loggableTxToken());
aspect.setSecurable(authConfig.securable());
aspect.setTransactionLogService(loggingConfig.txLogService());
aspect.setLogQuery(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logQuery", Boolean.toString(true))));
aspect.setLogReply(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logReply", Boolean.toString(true))));
aspect.setLogSubmit(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logSubmit", Boolean.toString(true))));
aspect.setMaxRequestSize(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.maxRequestSize", "100000000")));
aspect.setMaxReplySize(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.maxReplySize", "100000000")));
aspect.setOrder(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.order", "5")));
return aspect;
}
}
这是负责设置NewAgeTransactionLoggingService和LoggableTransactionToken的配置的片段
@Bean
public TransactionLogService txLogService() {
HibernateTransactionLogService service = new HibernateTransactionLogService();
service.setLogParameters(Boolean.valueOf(Boolean.toString(true)));
service.setTimeDispatcher(timeConfig.timeDispatcher());
service.setSessionFactory(sessionFactory);
return service;
}
/**
* @return <p>Request scoped bean that allows LoggableTransaction instances to be created and accessed on a request.<br/>
* Consult <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-scopes-other-injection">Bean Factory Scopes</a></p>
*/
@Bean
@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Token<LoggableTransaction> loggableTxToken() {
// XXX Is this is the correct way to specify a request-scoped bean?
LoggableTransactionToken token = new LoggableTransactionToken();
return token;
}
这是带有@Around建议的方面的方法。
@Around("com.spp.mui.aop.Advisables.transactionLoggable()")
public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
// Prepare to log the execution time of the method call..
long t0 = 0;
t0 = System.currentTimeMillis();
Object resultObject = null;
try {
resultObject = pjp.proceed();
} catch (Throwable t) {
logFailedTransactionAndRethrow(pjp, t0, t);
}
logSuccessfulTransaction(pjp, t0, resultObject);
return resultObject;
}
I am using Spring 3.1.0.RC1. I have an Apache CXF based web service. I am trying to advise each @Transactional annotated service method.
I have an aspect with some Around advice. Inside the method I am squirreling away data into a request-scoped transaction payload bean. Later I ask for the uuid out of this payload in another singleton scoped bean. The result is a null pointer exception.
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
at com.spp.mui.jaxws.service.virtual._0_1.VirtualWebService.postOfferSet(VirtualWebService.java:209)
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.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.spp.mui.aop.tx.NewAgeTransactionLoggingAspect.logTransaction(NewAgeTransactionLoggingAspect.java:85)
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.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.spp.mui.aop.ws.TraceWebServiceMethodAspect.invoke(TraceWebServiceMethodAspect.java:68)
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.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy95.postOfferSet(Unknown Source)
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.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:173)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:89)
... 49 more
Caused by: java.lang.NullPointerException
at com.spp.mui.jaxws.agent.WSResponseAgent.getResponse(WSResponseAgent.java:28)
at com.spp.mui.jaxws.agent.WSResponseAgent.getResponse(WSResponseAgent.java:18)
at com.spp.mui.jaxws.handler.offer.PostVirtualOffersHandler.handle(PostVirtualOffersHandler.java:58)
at com.spp.mui.jaxws.service.virtual._0_1.VirtualWebService.postOfferSet(VirtualWebService.java:205)
... 88 more
So what's going on in this stack trace?
A web service method (i.e., postOfferSet) was invoked. That method was advised by an aspect with an @Around pointcut. That method is also @Transactional annotated.
UPDATE I have since updated the original configuration to be Java config based. With either an XML config or a Java config I run into the same problem. My Java config is the one currently in play, and the one I'd like help diagnosing. Please scroll down (skip ahead) to see this Java config below. (For others new to this post read on).
My aspect config looks a little like
<aop:aspectj-autoproxy />
<bean id="newAgeTxLoggingAspect" class="com.spp.mui.aop.tx.NewAgeTransactionLoggingAspect">
<property name="transactionLogService" ref="txLogServiceEngine" />
<property name="securable" ref="securityAgent" />
<property name="loggableTransactionToken" ref="loggableTransactionToken" />
<property name="logQuery" value="${newAgeTxLoggingAspect.logQuery}" />
<property name="logReply" value="${newAgeTxLoggingAspect.logReply}" />
<property name="logSubmit" value="${newAgeTxLoggingAspect.logSubmit}" />
<property name="maxReplySize" value="${newAgeTxLoggingAspect.maxReplySize}" />
<property name="maxRequestSize" value="${newAgeTxLoggingAspect.maxRequestSize}" />
<property name="order" value="5" />
</bean>
As mentioned earlier the loggableTransactionToken is request-scoped.
Here's the bean def for it
<!--
Request scoped bean that allows LoggableTransaction instances to be created and accessed on a single-thread of execution
Consult http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-scopes-other-injection
-->
<bean id="loggableTransactionToken" class="com.spp.mui.commons.spring.LoggableTransactionToken" scope="request">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
The aforementioned web service method delegates to a Handler
Here's a snippet from the Handler
@Override
public ConfirmationType handle(OfferSetType request) {
// step 1: convert from JAXB type to domain objects
List<MktVirtualOffer> candidateOffers = codecService.convert(request, List.class);
...
// step 4: get response -- response encapsulates transaction id
return responder.getResponse();
}
Notice the Handler uses a Responder?
Here's a snippet from the Responder, a.k.a. WSResponseAgent
@Override
public ConfirmationType getResponse() {
ConfirmationType response = new ConfirmationType();
LoggableTransaction lt = token.getToken();
response.setTransactionID(lt.getUuid());
logger.debug(ReflectUtil.toString(lt));
return response;
}
Hopefully, I've provided enough to help you help me sleuth why I'm getting the NPE?
UPDATE Here's the Java config and additional impl
AOP config
@Configuration
@EnableAspectJAutoProxy
@Import(value = { LoggingConfig.class, AuthConfig.class })
public class AopConfig {
@Resource
private Environment env;
@Resource
private LoggingConfig loggingConfig;
@Resource
private AuthConfig authConfig;
/* Consult
* static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html#transaction-declarative-applying-more-than-just-tx-advice
* and http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-ataspectj-advice-ordering
*/
@Bean
public NewAgeTransactionLoggingAspect txLoggingAspect() {
NewAgeTransactionLoggingAspect aspect = new NewAgeTransactionLoggingAspect();
aspect.setEnvironment(env);
aspect.setLoggableTransactionToken(loggingConfig.loggableTxToken());
aspect.setSecurable(authConfig.securable());
aspect.setTransactionLogService(loggingConfig.txLogService());
aspect.setLogQuery(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logQuery", Boolean.toString(true))));
aspect.setLogReply(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logReply", Boolean.toString(true))));
aspect.setLogSubmit(Boolean.valueOf(env.getProperty("newAgeTxLoggingAspect.logSubmit", Boolean.toString(true))));
aspect.setMaxRequestSize(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.maxRequestSize", "100000000")));
aspect.setMaxReplySize(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.maxReplySize", "100000000")));
aspect.setOrder(Integer.valueOf(env.getProperty("newAgeTxLoggingAspect.order", "5")));
return aspect;
}
}
Here's a snippet from the config responsible for setting up the NewAgeTransactionLoggingService and the LoggableTransactionToken
@Bean
public TransactionLogService txLogService() {
HibernateTransactionLogService service = new HibernateTransactionLogService();
service.setLogParameters(Boolean.valueOf(Boolean.toString(true)));
service.setTimeDispatcher(timeConfig.timeDispatcher());
service.setSessionFactory(sessionFactory);
return service;
}
/**
* @return <p>Request scoped bean that allows LoggableTransaction instances to be created and accessed on a request.<br/>
* Consult <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-scopes-other-injection">Bean Factory Scopes</a></p>
*/
@Bean
@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Token<LoggableTransaction> loggableTxToken() {
// XXX Is this is the correct way to specify a request-scoped bean?
LoggableTransactionToken token = new LoggableTransactionToken();
return token;
}
Here's the aspect's method with @Around advice.
@Around("com.spp.mui.aop.Advisables.transactionLoggable()")
public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
// Prepare to log the execution time of the method call..
long t0 = 0;
t0 = System.currentTimeMillis();
Object resultObject = null;
try {
resultObject = pjp.proceed();
} catch (Throwable t) {
logFailedTransactionAndRethrow(pjp, t0, t);
}
logSuccessfulTransaction(pjp, t0, resultObject);
return resultObject;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
于是,我又得自己解决这个问题了。
基础知识:
1) 创建一个自定义 ServletFilter,创建 LoggableTransaction 并在那里设置令牌。我扩展了 Spring 的 OncePerRequestFilter 并使用 WebApplicationContextUtils 在 doFilterInternal 方法中获取请求范围的 bean。
2) 在web.xml 中注册此过滤器。另外一定要注册Spring的RequestContextListener和ContextLoaderListener。
3) 上面的切面实现保持不变,除了切入点引用。切入点现在引用一个服务方法(Web 服务委托给的方法)。
4)这是最关键的......如果令牌没有实现接口,那么bean定义应该用@Scope(value =“request”,proxyMode = ScopedProxyMode.TARGET_CLASS)来装饰。您将需要类路径上的 CGLib。无论如何,我需要它来实现休眠。
后来事情进展得很顺利。
So, I had to solve this one myself again.
The basics:
1) Create a custom ServletFilter, create the LoggableTransaction and set on token there. I extended Spring's OncePerRequestFilter and used WebApplicationContextUtils to fetch request scoped bean within doFilterInternal method.
2) Register this Filter in web.xml. Also be sure to register Spring's RequestContextListener and ContextLoaderListener.
3) The aspect implementation above, remains the same, save for the pointcut reference. The pointcut now references a service method (one that the web service delegates to).
4) This was the most critical... if the token does NOT implement an interface, then the bean definition should be adorned with @Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS). You'll need CGLib on the classpath. I needed it anyway for Hibernate.
Things worked beautifully afterwards.