As the title could be a bit unclear I will explain much here.
I have an entity mapped with JPA. In this entity I use an embeddable class which contains to attributes. The problem is that I want to map the two attributes on the same database column (example comes after). To achieve that, I override the two attributes and map them to the column and for one of the two I use insertable = false and updatable = false.
The thing is that doesn't work (getting "repeated column mapping..." and I can't find a solution to this problem. I also didn't find someone with a similar problem. I hope I searched well but as I searched a lot I think it's ok.
For your information, I'm using JPA 2.1 with Hibernate implementation (but I don't want to use Hibernate specific features).
Here is my source code for this class. The problem is with the "Periode" object which represent a period (of many kind : month, year). The thing is I want to use this class to represent a year (stored in database as an integer in a single column) and so be able to access Periode start and end date properties.
Periode comes from legacy code and I have relatively limited "power" over it (as it's used in many other projects). Database is also legacy so I can't change it in order to put two dates instead of just the year.
@SuppressWarnings("serial")
@Embeddable
public class CandidateId implements Serializable {
// Fields
private Periode period;
private IdentifiantContrat contractId;
// Constructors
@SuppressWarnings("unused")
private CandidateId() {}
public CandidateId(Periode period, IdentifiantContrat contractId) {
this.period = period;
this.contractId = contractId;
}
public CandidateId(Periode period, IdentifiantAffilie employerId) {
this(period, new IdentifiantContrat(employerId, 0));
}
// Getters
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "debut", column = @Column(name = "PERIODE", nullable = false) ),
@AttributeOverride(name = "fin", column = @Column(name = "PERIODE", nullable = false, insertable = false, updatable = false))
})
// @Converts({
// @Convert(attributeName = "debut", converter = FiscalStartDateConverter.class),
// @Convert(attributeName = "fin", converter = FiscalEndDateConverter.class)
// })
public Periode getPeriod() {
return period;
}
@Embedded
public IdentifiantContrat getContractId() {
return contractId;
}
// Setters
public void setPeriod(Periode period) {
this.period = period;
}
public void setContractId(IdentifiantContrat contractId) {
this.contractId = contractId;
}
// Methods
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((contractId == null) ? 0 : contractId.hashCode());
result = prime * result + ((period == null) ? 0 : period.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CandidateId other = (CandidateId) obj;
if (contractId == null) {
if (other.contractId != null)
return false;
} else if (!contractId.equals(other.contractId))
return false;
if (period == null) {
if (other.period != null)
return false;
} else if (!period.equals(other.period))
return false;
return true;
}
@Override
public String toString() {
return "CandidatDeclarationId [period=" + period + ", contractId=" + contractId + "]";
}
}
As you can see, I plan to use converters to convert int value from db to the two matching dates, but one problem at a time.
When testing my code, I get this information:
javax.persistence.PersistenceException: [PersistenceUnit: belgium-fiscal-model] Unable to build Hibernate SessionFactory
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(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 be.groups.kernel.utils.persistence.JpaUtil.initEntityManagerFactory(JpaUtil.java:168)
at be.groups.kernel.utils.persistence.JpaUtil.createEntityManagerFactory(JpaUtil.java:65)
at be.groups.belgium.fiscal.model.services.CandidateServiceTest.setUp(CandidateServiceTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: be.groups.belgium.fiscal.model.domain.Candidate column: PERIODE (should be mapped with insert="false" update="false")
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:709)
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:750)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:506)
at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1360)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1851)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
... 25 more
java.lang.NullPointerException
at be.groups.belgium.fiscal.model.services.CandidateServiceTest.tearDown(CandidateServiceTest.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
If something is unclear, just tell me I'll try to precise it.
EDIT : Here is the Candidate class as asked by OndrejM. But I don't think the problem come from it (I already checked that, though I miss to mention it sorry^^). But maybe I'm missing something so if it can help, here is the class!
@Entity
@Table(name = "ECHEANCIER_FISCAL_PERSONNE")
public class Candidate {
// Fields
private CandidateId id;
private Date datePassage;
private String justification;
private Integer actionProgramme;
private Integer actionGestionnaire;
private BaseSignaturePersistance persistenceSignature;
// Constructors
private Candidate() {}
// Getters
@EmbeddedId
public CandidateId getId() {
return id;
}
@Column(name = "ACTION_PROGRAMME", nullable = true)
public Integer getActionProgramme() {
return actionProgramme;
}
@Column(name = "ACTION_GESTIONNAIRE", nullable = true)
public Integer getActionGestionnaire() {
return actionGestionnaire;
}
@Column(name = "DT_PASSAGE", nullable = true)
public Date getDatePassage() {
return datePassage;
}
@Column(name = "JUSTIFICATION", nullable = true)
public String getJustification() {
return justification;
}
@Embedded
public BaseSignaturePersistance getPersistenceSignature() {
return persistenceSignature;
}
// Setters
public void setId(CandidateId id) {
this.id = id;
}
public void setActionProgramme(Integer actionProgramme) {
this.actionProgramme = actionProgramme;
}
public void setActionGestionnaire(Integer actionGestionnaire) {
this.actionGestionnaire = actionGestionnaire;
}
public void setDatePassage(Date datePassage) {
this.datePassage = datePassage;
}
public void setJustification(String justification) {
this.justification = justification;
}
public void setPersistenceSignature(BaseSignaturePersistance persistenceSignature) {
this.persistenceSignature = persistenceSignature;
}
// Methods
/**
* Tells if this concerns a single contract or not
*
* @return true if this concerns a single contract, else false
*
* @since 1.0.0
*/
@Transient
public boolean isSingleContract() {
return id.getContractId().getNumero() != 0;
}
/**
* Tells if this concerns an employer (multiple contracts) or not
*
* @return true if this concerns an employer, else false
*
* @since 1.0.0
*/
@Transient
public boolean isWholeEmployer() {
return !isSingleContract();
}
/**
* Tells if the candidate is blocked
*
* @return true if this is blocked, else false
*
* @since 2.0.0
*/
@Transient
public boolean isBlocked() {
return actionGestionnaire == null
? (actionProgramme == null || actionProgramme == StatutCloture.PAS_PRET_POUR_PREPARATION.getValeur())
: (actionGestionnaire == StatutCloture.PAS_PRET_POUR_PREPARATION.getValeur());
}
}
Here is also the BaseSignaturePersistence class which is embedded in Candidate class. Sorry for mixing english and french code but as I said I work with legacy code and so some classes are in french (decision to use english has only be made recently).
@Embeddable
public class BaseSignaturePersistance {
/**
* Auteur creation des données.
*/
private String auteurCreation;
/**
* Auteur modification des données.
*/
private String auteurModification;
/**
* Date creation des données
*/
private Date dateCreation;
/**
* Date modification des données.
*/
private Date dateModification;
/**
* Nouvel Objet Signature.
*
* @param lAuteurCreation
* @param lDateCreation
* @param lAuteurModification
* @param lDateModification
*/
@Requires("auteurCreation!=null && dateCreation!=null")
@Ensures({"this.auteurCreation == auteurCreation && this.dateCreation == dateCreation",
"this.dateModification!=null ? this.auteurModification!=null : true",
"this.auteurModification!=null ? this.dateModification!=null : true"})
public BaseSignaturePersistance(String auteurCreation, Date dateCreation, String auteurModification, Date dateModification) {
this.auteurCreation = auteurCreation;
this.dateCreation = dateCreation;
this.auteurModification = auteurModification;
this.dateModification = dateModification;
}
/**
* Nouvel objet signature contenant seulement la partie creation
* @param unAuteurCreation
* @param uneDateCreation
*/
@Requires({
"unAuteurCreation!=null",
"uneDateCreation!=null"
})
public BaseSignaturePersistance(String unAuteurCreation, Date uneDateCreation){
this(unAuteurCreation, uneDateCreation, null, null);
}
/**
* Nouvel objet signature contenant seulement la partie creation
* @param unAuteurCreation
*/
@Requires({
"auteurCreation!=null"
})
public BaseSignaturePersistance(String unAuteurCreation){
this(unAuteurCreation, DateUtility.getNow(), null, null);
}
/**
* Nouvel objet signature.
* Utiliser setModification ou SetSuppression pour compléter ce qui est nécessaire.
*/
public BaseSignaturePersistance(){
this.auteurCreation = null;
this.dateCreation = null;
this.auteurModification = null;
this.dateModification = null;
}
/**
* Affecte {@code auteur} et {@code date} à {@code auteurModification} et {@code dateModification}.
*
* @param auteur
* @param date
*/
@Requires("auteur!=null && date!=null")
@Ensures("auteurModification == auteur && dateModification == date")
public void setModification(String auteur, Date date) {
auteurModification = auteur;
dateModification = date;
}
public void copy(BaseSignaturePersistance other) {
auteurCreation = other.auteurCreation;
dateCreation = other.dateCreation;
auteurModification = other.auteurModification;
dateModification = other.dateModification;
}
@Column(name = "AUTEUR_CREATION", nullable = false)
public String getAuteurCreation() {
return auteurCreation;
}
@Column(name = "AUTEUR_MODIFICATION")
public String getAuteurModification() {
return auteurModification;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "DT_CREATION", nullable = false)
public Date getDateCreation() {
return dateCreation;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "DT_MODIFICATION")
public Date getDateModification() {
return dateModification;
}
public void setAuteurCreation(String auteurCreation) {
this.auteurCreation = auteurCreation;
}
public void setAuteurModification(String auteurModification) {
this.auteurModification = auteurModification;
}
public void setDateCreation(Date dateCreation) {
this.dateCreation = dateCreation;
}
public void setDateModification(Date dateModification) {
this.dateModification = dateModification;
}
/**
* Gives most recent date the object has been modified
*
* @return modification date if any, else creation date
*/
@Transient
public Date getDateDerniereModification() {
return dateModification != null ? dateModification : dateCreation;
}
/**
* Gives most recent author that has modified the object
*
* @return modification author if any, else creation author
*/
@Transient
public String getAuteurDerniereModification() {
return auteurModification != null ? auteurModification : auteurCreation;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((auteurCreation == null) ? 0 : auteurCreation.hashCode());
result = prime
* result
+ ((auteurModification == null) ? 0 : auteurModification
.hashCode());
result = prime * result
+ ((dateCreation == null) ? 0 : dateCreation.hashCode());
result = prime
* result
+ ((dateModification == null) ? 0 : dateModification.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BaseSignaturePersistance other = (BaseSignaturePersistance) obj;
if (auteurCreation == null) {
if (other.auteurCreation != null)
return false;
} else if (!auteurCreation.equals(other.auteurCreation))
return false;
if (auteurModification == null) {
if (other.auteurModification != null)
return false;
} else if (!auteurModification.equals(other.auteurModification))
return false;
if (dateCreation == null) {
if (other.dateCreation != null)
return false;
} else if (!dateCreation.equals(other.dateCreation))
return false;
if (dateModification == null) {
if (other.dateModification != null)
return false;
} else if (!dateModification.equals(other.dateModification))
return false;
return true;
}
@Override
public String toString() {
return "BaseSignaturePersistance [auteurCreation=" + auteurCreation
+ ", auteurModification=" + auteurModification
+ ", dateCreation=" + dateCreation + ", dateModification="
+ dateModification + "]";
}
}
I don't see a problem in your code. The only uncommon thing is that you are mapping two properties of an embedded id onto a single column, but I don't see a problem there.
However, the fact is that hibernate does not support this mapping in embedded primary keys (probably a bug). I checked the code of hibernate and I found out that insertable/updatable is not considered for embedded ids, only for properties. Compare the code how properties are checked and how columns for primary key are checked (line 750).
And now a possible solution:
Try to remove Periode class from CandidadeId and map PERIODE column to a new field alone. Then embed Periode class directly into Candidate entity, and override attributes of both properties that map to PERIODE column to be read only (insertable/updatable = false).
That would remove the legacy entity Periode from the primary key, and still make it available from the entity Candidate. The drawback is that you cannot modify Periode class, but always need to modify period field on CandidateId instead. You may however create a pre-update listener and copy the value from Periode class into the new period field before the entity is persisted.
Something like following (I used mapping on fields instead of properties to make it more readable):