I'm going off the writeup on many-to-many associations with composite key at
http://boris.kirzner.info/blog/archives ... osite-key/.
My particular legacy domain crap has a join entity with 3, not 2, foreign keys. Uniqueness is enforced on a combination of the three, but one of them is nillable; and there's some metadata on it as well.
When I tell my EntityManager to persist a new instance of this join entity, sometimes (not always) (very rarely in fact) it throws an NPE. The exception is repeatable when given the same input, and it always fails on that input. I can't detect a pattern in that input when compared to other input that doesn't generate an error, though. At first I thought of course it was because the nillable member of the composite key would be null in those cases; but some input that causes the error does not have a null value in that field (and some does).
Here is some example source code to show what I'm doing. Basically cribbed directly from the URL above. Thanks for reading this far.
Code:
// basic POJO entities
@Entity
public class Proposal {
private Long id;
// ... other fields elided for length
private List<ProposalAddon> proposalAddons;
@Id
@Column(name="proposal_id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@OneToMany(targetEntity=ProposalAddon.class, fetch=FetchType.LAZY, mappedBy="pk.proposal")
public List<ProposalAddon> getProposalAddons() {
return proposalAddons;
}
public void setProposalAddons(List<ProposalAddon> proposalAddons) {
this.proposalAddons = proposalAddons;
}
}
@Entity
public class AddonType {
// ... basically same as Proposal, has id, other basic fields, and List<ProposalAddon>
}
@Entity
public class Access {
// ... again, basic entity, same w.r.t. List<ProposalAddon>
}
// Join table entity
@Entity
@AssociationOverrides({
@AssociationOverride(name="pk.proposal", joinColumns=@JoinColumn(name="proposal_id")),
@AssociationOverride(name="pk.addonType", joinColumns=@JoinColumn(name="addon_type_lu_id")),
@AssociationOverride(name="pk.access", joinColumns=@JoinColumn(name="access_lu_id"))
})
public class ProposalAddon implements Serializable {
private ProposalAddonPK pk = new ProposalAddonPK();
// other metadata on this join table
private Addon addon;
@EmbeddedId
private ProposalAddonPK getPk() {
return pk;
}
private void setPk(ProposalAddonPK pk) {
this.pk = pk;
}
@Transient
public Proposal getProposal() {
return getPk().getProposal();
}
public void setProposal(Proposal proposal) {
getPk().setProposal(proposal);
}
@Transient
public AddonType getAddonType() {
return getPk().getAddonType();
}
public void setAddonType(AddonType addonType) {
getPk().setAddonType(addonType);
}
@Transient
public Access getAccess() {
return getPk().getAccess();
}
public void setAccess(Access access) {
getPk().setAccess(access);
}
@ManyToOne(targetEntity=Addon.class, optional=false, fetch=FetchType.LAZY, cascade={CascadeType.REFRESH})
@JoinColumn(name="addon_lu_id")
public Addon getAddon() {
return addon;
}
public void setAddon(Addon addon) {
this.addon = addon;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProposalAddon other = (ProposalAddon) o;
if (getPk() != null ? !getPk().equals(other.getPk()) : other.getPk() != null) return false;
return true;
}
public int hashCode() {
return (getPk() != null ? getPk().hashCode() : 0);
}
}
@Embeddable
public class ProposalAddonPK implements Serializable {
private Proposal proposal;
private AddonType addonType;
private Access access;
@ManyToOne(targetEntity=Proposal.class, fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="proposal_id")
public Proposal getProposal() {
return proposal;
}
public void setProposal(Proposal proposal) {
this.proposal = proposal;
}
@ManyToOne(targetEntity=AddonType.class, fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="addon_type_lu_id")
public AddonType getAddonType() {
return addonType;
}
public void setAddonType(AddonType addonType) {
this.addonType = addonType;
}
@ManyToOne(targetEntity=Access.class, fetch=FetchType.LAZY, optional=true)
@JoinColumn(name="access_lu_id")
public Access getAccess() {
return access;
}
public void setAccess(Access access) {
this.access = access;
}
@Override
public boolean equals(Object obj) {
// ... eliding for length
}
public int hashCode() {
int result;
result = (proposal != null ? proposal.hashCode() : 0);
result = 31 * result + (addonType != null ? addonType.hashCode() : 0);
result = 31 * result + (access != null ? access.hashCode() : 0);
return result;
}
}
Aaaand here's a stack trace.
java.lang.NullPointerException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:169)
at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:206)
at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:3619)
at org.hibernate.type.EntityType.isEqual(EntityType.java:330)
at org.hibernate.type.ComponentType.isEqual(ComponentType.java:166)
at org.hibernate.engine.EntityKey.equals(EntityKey.java:119)
at java.util.HashMap.get(Unknown Source)
at org.hibernate.engine.StatefulPersistenceContext.getEntity(StatefulPersistenceContext.java:345)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:185)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
at sun.reflect.GeneratedMethodAccessor625.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:358)
at $Proxy101.persist(Unknown Source)
at sun.reflect.GeneratedMethodAccessor624.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:197)
at $Proxy50.persist(Unknown Source)
at com.legacycrap.services.ProposalServiceImpl.setProposalAddons(ProposalServiceImpl.java:527)
at com.legacycrap.services.ProposalServiceImpl.createDefaultAddons(ProposalServiceImpl.java:452)
at com.legacycrap.services.ProposalServiceImpl$$FastClassByCGLIB$$6a8a71fc.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:700)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:160)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:635)
at com.legacycrap.services.ProposalServiceImpl$$EnhancerByCGLIB$$1bfe414c.createDefaultAddons(<generated>)
at otherjunk.customer.ProposalCreate.createDefaultProposalAddons(ProposalCreate.java:369)
at otherjunk.customer.ProposalCreate.handle(ProposalCreate.java:267)
at reallycrap.zoo.ZooServlet.service(ZooServlet.java:121)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:112)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at net.crapola.util.filters.ZooFilter.doFilter(ZooFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at net.crapola.util.filters.ZooFilter.doFilter(ZooFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
at java.lang.Thread.run(Unknown Source)