0

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:

MandtId.java

@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]");
}

}

EnumValue.java

@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...

EnumType.java

@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 mandt 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

Ben
  • 2,314
  • 1
  • 19
  • 36
  • please post complete stacktrace – Zulfi Jul 06 '16 at 11:32
  • why have you annotated MandtId with @Embeddable .. i dont see any user for it – Zulfi Jul 06 '16 at 11:45
  • I did this because most examples online did this. But you are right, from my perspective it is not necessary. – Ben Jul 06 '16 at 12:05
  • I additionally found out that when ommitting the referencedColumnName, it seems to pass the foreign key mapping stage of hibernate, but then finds a wrong mapping (maps mandt->id and id->mandt), especially for other annotated classes (not shown here) where the name and the referencedColumnName differ. – Ben Jul 06 '16 at 12:05
  • how is your ReadWriteRecord configured .. can you please post that class too – Zulfi Jul 06 '16 at 12:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/116568/discussion-between-zulfi-and-ben). – Zulfi Jul 06 '16 at 12:09
  • I posted the classes you asked as well in the original post. – Ben Jul 06 '16 at 13:39

1 Answers1

1

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:

MandtId.java

@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]");
}

}

EnumValue.java

@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...

EnumType.java

@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.

Community
  • 1
  • 1
Ben
  • 2,314
  • 1
  • 19
  • 36