简体   繁体   中英

Hibernate JPA ManyToOne foreign key mapping with composite key via IdClass does not find columns

I am trying to resolve an issue with the database mapping in our application using hibernate. We annotate the classes using JPA and so far we were successful. However, we now want to introduce a composite key with a unique string identifying the customer together with a string identifying the database entry. To set the compound key, we use an IdClass called MandtId.java .

However, it seems that our mapping does not work as it can not find the column to join on the other side.

Here are some snippets of the code to show our annotations:

@SuppressWarnings("serial")
@Embeddable
public class MandtId implements Serializable {
private String mandt;

private String id;

public MandtId() {
}

public MandtId(String mandt, String id) {
    this.mandt = mandt;
    this.id    = id;
}

public String getId() {
    return id;
}

public String getMandt() {
    return mandt;
}

public void setId(String x) {
    id = x;
}

public void setMandt(String x) {
    mandt = x;
}

/* (non-Javadoc)
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    int hashCode = 0;
    if (mandt != null) hashCode |= mandt.hashCode();
    if (id != null) hashCode |= id.hashCode();
    return hashCode;
}

/* (non-Javadoc)
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object other) {
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MandtId))return false;
    MandtId otherMandtId = (MandtId)other;
    return Objects.equals(mandt, otherMandtId.mandt) && Objects.equals(id, otherMandtId.id); 
}

/* (non-Javadoc)
 * @see java.lang.Object#toString()
 */
@Override
public String toString() {
    return (mandt != null ? mandt : "[null]") + " / " + (id != null ? id : "[null]");
}

}

@Entity
@IdClass(MandtId.class)
public class EnumValue extends ReadWriteRecord {

@Id
@Column(name="mandt", insertable = false, updatable = false)
private String mandt;

@Id
@Column(name="enumValueId", insertable = false, updatable = false)
private String id;

@ManyToOne(fetch=FetchType.EAGER, optional=false)
@JoinColumns( {
    @JoinColumn(name="mandt", referencedColumnName="mandt", insertable = false, updatable = false),
    @JoinColumn(name="enumTypeId", referencedColumnName="enumTypeId", insertable = false, updatable = false)
} )
private EnumType enumType;

...more but irrelevant code...

@Entity
@IdClass(MandtId.class)
public class EnumType extends ReadWriteRecord {

@Id
@Column(name="mandt", insertable = false, updatable = false)
private String mandt;

@Id
@Column(name="enumTypeId", insertable = false, updatable = false)
private String id;

@OneToMany(mappedBy="enumType", fetch=FetchType.EAGER)
@OrderBy("sortIndex")
private List<EnumValue> values;

...more but irrelevant code...

The ReadWriteRecord class does not contain any id related annotations, just additional columns.

ReadWriteRecord.java

@MappedSuperclass
public abstract class ReadWriteRecord extends PersistentRecord {

@Column
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime modifiedAt;

@Column
private String modifiedBy;

@Column
private boolean deleted;

public boolean getDeleted() {
    return deleted;
}

public void setDeleted(boolean x) {
    deleted = x;
}

public LocalDateTime getModifiedAt() {
    return modifiedAt;
}

public String getModifiedBy() {
    return modifiedBy;
}

public final void touch(UserContext context) {
    final LocalDateTime timestamp = LocalDateTime.now(ZoneId.of("Z"));
    onTouch(context, timestamp);
}

protected void onTouch(UserContext context, LocalDateTime timestamp) {
    modifiedBy = context != null ? context.getUserId() : null;
    modifiedAt = timestamp;
}
}

PersistendRecord.java

public abstract class PersistentRecord {

/**
 * Indicates what to do when this record is passed to DatabaseSession.store().
 * <ul>
 * <li> if false, EntitiyManager.merge() is called.
 * <li> if true, EntitiyManager.persist() is called.
 * </ul>
 * <p>This field is managed by the DatabaseSession and therefore not public.
 *    Users must call markNew() after the creation of new records.
 */
boolean mustInsert = false;

@Override
public String toString() {
    return String.format("%s(%s - %s)", getClass().getSimpleName(), getMandt(), getId());
}

/**
 * Returns the ID of the record, used by toString() and intended for generic logging.
 * @return the primary key of the record, any format is allowed.
 */
public abstract String getId();

/**
 * Returns the Mandant of the record, used by toString() and intended for generic logging.
 * @return the primary key of the record, any format is allowed.
 */
public abstract String getMandt();

/**
 * Sets the ID of the record.
 * @param id primary key of the record.
 */
public abstract void setId(MandtId id);

/**
 * Must be called after creating new entities so that EntitiyManager.persist() is called instead of EntitiyManager.merge().
 */
protected void markNew() {
    mustInsert = true;
}

/**
 * Can be called to force lazy loading.
 */
protected void fetch() { }

}

When mapping and debugging this issue, we find that an exception is thrown, which contains the following message:

Unable to find column with logical name: mandt in org.hibernate.mapping.Table(EnumType) and its related supertables and secondary tables

Since the column is there in both classes, it should be found. 列,因此应该找到它。 Any suggestions on how to solve this issue? I would prefer a mapping suggestion as I would preferably keep the database scheme as it is.

Stacktrace:

 javax.persistence.PersistenceException: [PersistenceUnit: default] Unable    to build Hibernate SessionFactory
at   org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceExcep     tion(EntityManagerFactoryBuilderImpl.java:1249)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.access$600(EntityManagerFactoryBuilderImpl.java:120)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:860)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:425)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:849)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:75)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:39)
at platform.data.DatabaseSession.<init>(DatabaseSession.java:94)
at platform.data.MandtDaoImpl.loadAllMandt(MandtDaoImpl.java:16)
at platform.data.Mandt.getMandt(Mandt.java:18)
at platform.data.Mandt.lambda$0(Mandt.java:10)
at platform.utils.Lazy.value(Lazy.java:24)
at platform.data.Mandt.deflt(Mandt.java:13)
at pm.business.InjectedLinkProject.getResourceTypes(InjectedLinkProject.java:66)
at pm.business.InjectedLinkProject.<init>(InjectedLinkProject.java:57)
at pm.business.PmLogic.<clinit>(PmLogic.java:17)
at main.business.MainApplication.<clinit>(MainApplication.java:22)
at main.application.MainController.<clinit>(MainController.java:25)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$158$$anonfun$apply$158.apply(routes_routing.scala:2477)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$158$$anonfun$apply$158.apply(routes_routing.scala:2477)
at play.core.Router$HandlerInvokerFactory$$anon$5.resultCall(Router.scala:267)
at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.invocation(Router.scala:255)
at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:55)
at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at play.core.j.HttpExecutionContext$$anon$2.run(HttpExecutionContext.scala:40)
at play.api.libs.iteratee.Execution$trampoline$.execute(Execution.scala:46)
at play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:32)
at scala.concurrent.impl.Future$.apply(Future.scala:31)
at scala.concurrent.Future$.apply(Future.scala:492)
at play.core.j.JavaAction$class.apply(JavaAction.scala:82)
at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.apply(Router.scala:252)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:129)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:128)
at scala.Option.map(Option.scala:146)
at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:128)
at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:121)
at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:41)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:393)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
 Caused by: org.hibernate.MappingException: Unable to find column with logical name: mandt in org.hibernate.mapping.Table(EnumType) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:582)
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:258)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1598)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1521)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1422)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
... 56 more
  org.hibernate.MappingException: Unable to find column with logical name: mandt in org.hibernate.mapping.Table(EnumType) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:582)
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:258)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1598)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1521)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1422)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:425)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:849)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:75)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:39)
at platform.data.DatabaseSession.<init>(DatabaseSession.java:94)
at platform.data.MandtDaoImpl.loadAllMandt(MandtDaoImpl.java:16)
at platform.data.Mandt.getMandt(Mandt.java:18)
at platform.data.Mandt.lambda$0(Mandt.java:10)
at platform.utils.Lazy.value(Lazy.java:24)
at platform.data.Mandt.deflt(Mandt.java:13)
at pm.business.InjectedLinkProject.getResourceTypes(InjectedLinkProject.java:66)
at pm.business.InjectedLinkProject.<init>(InjectedLinkProject.java:57)
at pm.business.PmLogic.<clinit>(PmLogic.java:17)
at main.business.MainApplication.<clinit>(MainApplication.java:22)
at main.application.MainController.<clinit>(MainController.java:25)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$158$$anonfun$apply$158.apply(routes_routing.scala:2477)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$158$$anonfun$apply$158.apply(routes_routing.scala:2477)
at play.core.Router$HandlerInvokerFactory$$anon$5.resultCall(Router.scala:267)
at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.invocation(Router.scala:255)
at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:55)
at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at play.core.j.HttpExecutionContext$$anon$2.run(HttpExecutionContext.scala:40)
at play.api.libs.iteratee.Execution$trampoline$.execute(Execution.scala:46)
at play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:32)
at scala.concurrent.impl.Future$.apply(Future.scala:31)
at scala.concurrent.Future$.apply(Future.scala:492)
at play.core.j.JavaAction$class.apply(JavaAction.scala:82)
at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.apply(Router.scala:252)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:129)
at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:128)
at scala.Option.map(Option.scala:146)
at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:128)
at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:121)
at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:41)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:393)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

Thanks

I could fix the issue myself using a workaround proposed elsewhere on stackoverflow ( https://stackoverflow.com/a/13147366/4563947 ).

Here are some snippets of the code to show our annotations that work:

@SuppressWarnings("serial")
public class MandtId implements Serializable {
private String mandt;

private String id;

public MandtId() {
}

public MandtId(String mandt, String id) {
    this.mandt = mandt;
    this.id    = id;
}

public String getId() {
    return id;
}

public String getMandt() {
    return mandt;
}

public void setId(String x) {
    id = x;
}

public void setMandt(String x) {
    mandt = x;
}

/* (non-Javadoc)
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    int hashCode = 0;
    if (mandt != null) hashCode |= mandt.hashCode();
    if (id != null) hashCode |= id.hashCode();
    return hashCode;
}

/* (non-Javadoc)
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object other) {
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MandtId))return false;
    MandtId otherMandtId = (MandtId)other;
    return Objects.equals(mandt, otherMandtId.mandt) && Objects.equals(id, otherMandtId.id); 
}

/* (non-Javadoc)
 * @see java.lang.Object#toString()
 */
@Override
public String toString() {
    return (mandt != null ? mandt : "[null]") + " / " + (id != null ? id : "[null]");
}

}

@Entity
@IdClass(MandtId.class)
public class EnumValue extends ReadWriteRecord {

@Id
@Column(name="Mandt")
private String mandt;

@Id
@Column(name="EnumValueId")
private String id;

@ManyToOne(fetch=FetchType.EAGER, optional=false)
    @JoinColumnsOrFormulas( {
        @JoinColumnOrFormula(column= @JoinColumn(name="Mandt", referencedColumnName="Mandt", insertable=false, updatable=false)),
        @JoinColumnOrFormula(column= @JoinColumn(name="EnumTypeId", referencedColumnName="EnumTypeId", insertable=false, updatable=false))
} )
private EnumType enumType;

...more but irrelevant code...

@Entity
@IdClass(MandtId.class)
public class EnumType extends ReadWriteRecord {

@Id
@Column(name="Mandt")
private String mandt;

@Id
@Column(name="EnumTypeId")
private String id;

@OneToMany(mappedBy="enumType", fetch=FetchType.EAGER)
@OrderBy("sortIndex")
private List<EnumValue> values;

...more but irrelevant code...

The JoinColumnOrFormula annotations fix the issues with not found logical names, mixing updatable and non-updatable constraints etc. Important is that even though we annotate with ColumnOrFormula, we never specify a formula at all. Formulas caused issues with formula to column conversion.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM