How to keep data in sync with JPA across entities in session cache? - spring-data-jpa

Im trying to figure out how to keep data in sync in the session cache. I have the following example:
#Table (name = "language")
#Entity
public class Language
{
#Id
#Column (name = "key", unique = true, nullable = false)
private String key;
#Column (name = "name", unique = true, nullable = false)
private String name;
#OneToMany (mappedBy = "language", cascade = { CascadeType.DETACH, CascadeType.REFRESH, CascadeType.REMOVE })
private Set<Translation> translations;
}
#Table (name = "translation")
#Entity
public class Translation
{
#Id
#GeneratedValue (strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne(optional = false)
private Language language;
#ManyToOne (optional = false)
#JoinColumn(referencedColumnName = "key")
private TranslatableEntity translatable;
#Column(name = "value")
private String value;
}
#Table (name = "translatable")
#Entity
public class Translatable
{
#Id
#GeneratedValue (strategy = GenerationType.SEQUENCE)
private Long id;
#NotNull
#Size (max = 255)
#Column (name = "key", unique = true, nullable = false)
private String key;
#OneToMany (mappedBy = "translatable", cascade = { CascadeType.DETACH, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.PERSIST })
#MapKey (name = "language")
private Map<Language, Translation> translations;
}
So basically I do:
// Print current translations for en language
Language language = languageRepository.getOne("en");
printTranslations(language);
// Add new translatable object with translation for english language
Translatable translatable = new Translatable();
translatable.addTranslation("en", "...")
translatableRepository.saveAndFlush(translatable)
// Again print translations for en language
// This still prints outdated information
language = languageRepository.getOne("en");
printTranslations(language);
So my question is how to keep data consistent.
When inserting/removing a new translatable with some translations the translation list in Language instances are not updated in the session cache.
I could not find any satisfactory answer to this. This one came closest: Keeping entity relationship in sync when deleting child in JPA.
Thanks

JPA doesn't maintain the two sides of a bidirectional relationship for you.
And the purpose of the first level cache is that within a transaction entities get loaded only once.
This gives you two options to solve the problem:
maintain both sides of the relationship yourself, for example by implementing the Translatable.add method so that it updates Language.translations and Translation.language
force a reload of language by either evicting it or by closing the session (i.e. the transaction) before calling languageRepository.getOne("en")

Related

How to split specific properties of an entity and save it in separate table and associate using the id?

There are 2 entities Document and DocumentData. Now how can I get the field dataUUID to contain the field uuid which is generated when a byte[] is saved using the DocumentData entity.
#Entity(name = "DOCUMENT_DETAILS")
public class Document extends BaseEntity {
#OneToOne(targetEntity = DocumentData.class, casacade = Cascade.ALL)
#JoinColumn(name = "DATA_UUID", foreignKey = #ForeignKey(name = "DATA_UUID_ID")
private String dataUUID;
#Column(name = "BACK_UP_NAME")
private String backUpName;
}
#Entity(name = "DOCUMENT_DATA")
public class DocumentData {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String uuid;
#Column(name = "DATA")
#Lob
private byte[] data;
}
Every entity must have an Id specified. Document doesn't. So you need to tell your JPA provider that one side is dependent on the other for it's identity: commonly referred to as OneToOne with shared primary key.
To to do you need to make the relationship bi-directional and add the Id annotation to the relationship.
It seems to me to make more sense to have DocumentData as the dependent entity:
#Entity(name = "DOCUMENT_DETAILS")
public class Document {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(name = "DATA_UUID")
private String dataUUID;
#Column(name = "BACK_UP_NAME")
private String backUpName;
#OneToOne(mappedBy = "document");
private DocumentData data;
}
#Entity(name = "DOCUMENT_DATA")
public class DocumentData {
#Id //depends on Document for its ID
#OneToOne
#JoinColumn(name = "DATA_UUID", foreignKey = #ForeignKey(name = "DATA_UUID_ID")
private Document document;
#Column(name = "DATA")
#Lob
private byte[] data;
}
When saving you need to make sure both sides of the relationship are set correctly:
It makes sense to encapsulate this in the setter in Document:
public void setData(DocumentData data){
this.data = data;
data.setDocument(this);
}
Then:
Document doc = new Document();
DocumentData data = new DocumentData();
doc.setData(data);
//save
There is another solution where DocumentData could be an Embeddable rather than an independent entity however I won't go into that.

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

JPA: How to fill related entity after the master is inserted

I have two tables called SL_DOCUMENT and SL_PROPOSE. The SL_DOCUMENT has its own ID (ID_DOCUMENT) and a foreign key to SL_PROPOSE (ID_PROPOSE). The SL_PROPOSE ID column is ID_PROPOSE. The particularity is that SL_PROPOSE ID value is actually the SL_DOCUMENT.ID_DOCUMENT value. i.e., after a new SL_DOCUMENT is inserted, the related SL_PROPOSE should be inserted with the SL_DOCUMENT.ID_DOCUMENT as ID and later the same value should be used in SL_DOCUMENT.ID_PROPOSE column.
I did my JPA mapping as follows:
#Entity
#Table(name = "SL_DOCUMENT")
public class DocumentORM {
#Id
#Column(name = "ID_DOCUMENT")
#SequenceGenerator(name = "SEQ_SL_DOCUMENT", sequenceName = "SEQ_SL_DOCUMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SL_DOCUMENT")
private Long id;
#OneToOne(mappedBy = "document", cascade = { CascadeType.PERSIST })
// #JoinColumn(name = "ID_PROPOSE", updatable = false)
private ProposeORM propose;
// ...
}
#Entity
#Table(name = "SL_PROPOSE")
public class ProposeORM {
#Id
#Column(name = "ID_PROPOSE")
private Long id;
#MapsId
#OneToOne
#JoinColumn(name="ID_PROPOSE")
private DocumentORM document;
// ...
public ProposeORM(DocumentORM document) {
super();
this.document = document;
this.document.setPropositura(this);
}
}
To create the new instances of of DocumentORM and ProposeORM:
DocumentORM document = new DocumentORM();
ProposeORM propose = new ProposeORM(document);
And finally to insert the new Document with ProposeORM:
this.documentoDAO.insert(document);
When I really insert a document, according the snippets above, I see in the console (Websphere 8.5) the INSERT commands for the SL_DOCUMENT, SL_PROPOSE running correctly. However, when I see the tables, the column SL_DOCUMENT.ID_PROPOSE is still NULL. Even If I uncomment the #JoinColumn annotation over DocumentORM.propose, the SL_DOCUMENT.ID_PROPOSE column continues to be not filled.
The ideal would be if SL_DOCUMENT had a discriminator column and ProposeORM was a DocumentORM subclass, using the JOINED InheritanceType (there are other tables with the same kind of relationship with SL_DOCUMENT). However, these are legacy tables and it is not possible to change it.
So, what is the alternative to fill SL_DOCUMENT.ID_PROPOSE? A workaround I was thinking is fill this column using a native SQL. Do you have better ideas?
Thanks,
Rafael Afonso
The solution I see is to make ProposeORM's ID not auto-generated, since you always want it to have the ID of the document it's linked to, AND still have a join column in the document table:
#Entity
#Table(name = "SL_DOCUMENT")
public class DocumentORM {
#Id
#Column(name = "ID_DOCUMENT")
#SequenceGenerator(name = "SEQ_SL_DOCUMENT", sequenceName = "SEQ_SL_DOCUMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SL_DOCUMENT")
private Long id;
#OneToOne
#JoinColumn(name = "ID_PROPOSE")
private ProposeORM propose;
// ...
}
#Entity
#Table(name = "SL_PROPOSE")
public class ProposeORM {
#Id
#Column(name = "ID_PROPOSE")
private Long id;
#OneToOne(mappedBy = propose)
private DocumentORM document;
// ...
public ProposeORM(DocumentORM document) {
super();
this.id = document.getId();
this.document = document;
this.document.setPropositura(this);
}
}
You'll have to persist the document first, flush the EntityManager to make sure the document has a generated ID, and then persist the propose and set it into the document.

Updating multiple entities with jpa 2

I need help to understand how ORM works. Here is the scenario that is very common. I have two main tables Organization and RelatedParty which have to be in many to many relation. But there is also relation_type attribute that defines what kind of relation exists between Organization and Relatedparty.
Here are my entity classes:
Organization:
#Entity
#Table(name = "organization", catalog = "...", schema = "")
#XmlRootElement
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "organization_id", nullable = false)
private Integer organizationId;
#Column(name = "organization_name", nullable = false)
private String organizationName;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "organization")
private List<Organdrelatedparty> organdrelatedpartyList;
...
//getter setter methods
Organdrelatedparty: which uses composite primary key OrgandrelatedpartyPK
#Entity
#Table(name = "organdrelatedparty", catalog = "...", schema = "")
#XmlRootElement
public class Organdrelatedparty implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected OrgandrelatedpartyPK organdrelatedpartyPK;
#JoinColumn(name = "relatedParty_id", referencedColumnName = "relatedParty_id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false, cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private Relatedparty relatedparty;
#JoinColumn(name = "orgRelation_id", referencedColumnName = "orgRelation_id", nullable = false)
#ManyToOne(optional = false)
private ParOrgrelationtype orgRelationid;
#JoinColumn(name = "organization_id", referencedColumnName = "organization_id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false)
private Organization organization;
...
//getter setter methods
OrgandrelatedpartyPK
#Embeddable
public class OrgandrelatedpartyPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "relatedParty_id", nullable = false)
private int relatedPartyid;
#Basic(optional = false)
#NotNull
#Column(name = "organization_id", nullable = false)
private int organizationId;
...
//getter setter methods
RelatedParty: which is in unidirectional oneToMany relationship with organdRelatedParty class. In other word that relatedParty entity has no knowledge about organdRelatedParty entity that is on the other side.
#Entity
#Table(name = "relatedparty", catalog = "...", schema = "")
#XmlRootElement
public class Relatedparty implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "relatedParty_id", nullable = false)
private Integer relatedPartyid;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "firstName", nullable = false, length = 100)
private String firstName;
#Size(max = 100)
#Column(name = "lastName", length = 100)
private String lastName;
#Basic(optional = false)
#NotNull
#Column(name = "isForeign", nullable = false)
private boolean isForeign;
...
//getter setter methods
For insertion, if I persist new Organization Entity, it cascades persist activity to new OrgandrelatedParty which also cascades persist activity to new RelatedParty. So all the related entities are persisted and it works fine.
For updating, User is expected to change existing organization and relatedParty entities and also add new relatedParty to organization. So we prefer to delete all OrgandrelatedParties first and add new relatedParties and edited relatedParties again after that.
This is our method that handles updating: We pass new organization and also all new and old relatedParties as a list to method:
firs we delete all old OrgAndRelatedParties then we create again all relatedParties in list as new OrgandrelatedParties. This is main method to update organization.
public void updateOrganization(Organization newOrganization, List<Relatedparty> newShareList) throws ControlException {
try{
tx.begin();
this.updateOrgAndRelatedShares(newOrganization, newShareList);
customerController.updateOrganization(newOrganization);
tx.commit();
}catch(ControlException ex){
...
customerController's updateOrganization method does first find old Organization by find method of entity manager then copies all attributes of new organization to old then merges old organization and flush:
public void updateOrganization(Organization newOrganization)
{
Organization preOrganization = em.find(Organization.class, newOrganization.getOrganizationId);
preOrganization.setOrganizationId(newOrganization.getOrganizationId);
preOrganization.setOrganizationName(newOrganization.getOrganizationName);
em.merge(preOrganization);
em.flush();
}
here are other methods:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void updateOrgAndRelatedShares(Organization org, List<Relatedparty> shareList) throws ControlException
{
for(Iterator<Organdrelatedparty> it = org.getOrgandrelatedpartyList().iterator(); it.hasNext();)
{
Organdrelatedparty op = it.next();
it.remove();
op.setOrganization(null);
op.setRelatedparty(null);
deleteOrgRelated(op);
}
org.getOrgandrelatedpartyList().clear();
for(Relatedparty relatedParty: shareList){
int parOrgRelationTypeId = relatedParty.getIsPerson() ? 1:2;
createOrgAndRelatedParty(org, relatedParty, parOrgRelationTypeId);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteOrgRelated(Organdrelatedparty org) throws ControlException{
try{
org = em.find(Organdrelatedparty.class, org.getOrgandrelatedpartyPK());
em.remove(org);
em.flush();
}
catch(Exception ex){
Logger.getLogger(RelatedpartyController.class.getName()).log(Level.SEVERE, null, ex);
throw new ControlException("Couln't delete org relation", ex);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void createOrgAndRelatedParty(Organization org, Relatedparty relatedParty, int parOrgRelationTypeId) throws ControlException{
if(findRelatedPartyByRegNum(relatedParty.getRegisterNumber()) == null || relatedParty.getRelatedPartyid() == null){
createRelated(relatedParty);
}else{
relatedParty = updateRelatedParty(relatedParty);
}
Organdrelatedparty preOrp = new Organdrelatedparty(relatedParty.getRelatedPartyid(),
preOrp.setOrganization(org);
preOrp.setRelatedparty(relatedParty);
preOrp.setOrgRelationid(prepareOrgandRelatedPartyType(parOrgRelationTypeId));
org.getOrgandrelatedpartyList().add(preOrp);
}
And my question is when I merge organization entity with new List organdrelatedpartyList
it throws exception like this:
SEVERE: java.lang.IllegalArgumentException: Cannot merge an entity that has been removed: mn.bsoft.crasmonclient.model.Organdrelatedparty[ organdrelatedpartyPK=mn.bsoft.crasmonclient.model.OrgandrelatedpartyPK[ relatedPartyid=71, organizationId=19 ] ]
I found out that eclipseLink does persist operation first then remove operations. So I think that it tries to insert organdrelatedparty entity that has same composite id with entity which was not deleted previously from database. I flushes every time I remove old organdrelatedparties. But it doesn't help. What is the solution? Any idea guys.
I'm using jpa 2.0; eclipseLink as provider and glassfish 3.1.2
You seem to be making these a lot more complicated than they need to be.
Why don't you just remove the Organdrelatedparty that have been removed, instead of deleting all of them, then reincarnating some of them? Reincarnating objects, especially in the same transaction is normally a bad idea.
The error that is occurring is on merge() according to the code you included you are only call merge in updateOrgAndRelatedShares(), so I don't see how this object is removed at this point? Or is your code different than you show, please include the exception stack.
You updateOrganization() method is bad, it updates the objects Id, which you should never do. Also it calls merge for no reason, it already changed the object.
Also I would normally recommend using an IdClass instead of an EmbeddedId, and recommend using TABLE or SEQUENCE id generation instead if IDENTITY.

Foreign Key constraint violation when persisting a many-to-one class

I'm getting an error when trying to persist a many to one entity:
Internal Exception: org.postgresql.util.PSQLException: ERROR: insert or update on table "concept" violates foreign key constraint "concept_concept_class_fk"
Detail: Key (concept_class_id)=(Concept) is not present in table "concept_class".
Error Code: 0
Call: INSERT INTO concept (concept_key, description, label, code, concept_class_id) VALUES (?, ?, ?, ?, ?)
bind => [27, description_1, label_1, code_1, Concept]
Query: InsertObjectQuery(com.mirth.results.entities.Concept[conceptKey=27])
at com.sun.ejb.containers.BaseContainer.checkExceptionClientTx(BaseContainer.java:3728)
at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:3576)
at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1354)
... 101 more
Here is the method that tries to persist it. I've put a comment where the line is:
#Override
public void loadConcept(String metaDataFilePath, String dataFilePath) throws Exception {
try {
ConceptClassMetaData conceptClassMetaData = (ConceptClassMetaData) ModelSerializer.getInstance().fromXML(FileUtils.readFileToString(new File(metaDataFilePath), "UTF8"));
em.executeNativeQuery(conceptClassMetaData.getCreateStatement());
ConceptClassRow conceptClassRow = conceptClassMetaData.getConceptClassRow();
ConceptClass conceptClass = em.findByPrimaryKey(ConceptClass.class, conceptClassRow.getId());
if (conceptClass == null) {
conceptClass = new ConceptClass(conceptClassRow.getId());
}
conceptClass.setLabel(conceptClassRow.getLabel());
conceptClass.setOid(conceptClassRow.getOid());
conceptClass.setDescription(conceptClassRow.getDescription());
conceptClass = em.merge(conceptClass);
DataParser dataParser = new DataParser(conceptClassMetaData, dataFilePath);
for (ConceptModel conceptModel : dataParser.getConceptRows()) {
ConceptFilter<Concept> filter = new ConceptFilter<Concept>(Concept.class);
filter.setCode(conceptModel.getCode());
filter.setConceptClass(conceptClass.getLabel());
List<Concept> concepts = em.findAllByFilter(filter);
Concept concept = new Concept();
if (concepts != null && !concepts.isEmpty()) {
concept = concepts.get(0);
}
concept.setCode(conceptModel.getCode());
concept.setDescription(conceptModel.getDescription());
concept.setLabel(conceptModel.getLabel());
concept.setConceptClass(conceptClass);
concept = em.merge(concept); //THIS LINE CAUSES THE ERROR!
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
...
Here are how the two entities are defined:
#Entity
#Table(name = "concept")
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="concept_class_id", discriminatorType=DiscriminatorType.STRING)
public class Concept extends KanaEntity {
#Id
#Basic(optional = false)
#Column(name = "concept_key")
protected Integer conceptKey;
#Basic(optional = false)
#Column(name = "code")
private String code;
#Basic(optional = false)
#Column(name = "label")
private String label;
#Column(name = "description")
private String description;
#JoinColumn(name = "concept_class_id", referencedColumnName = "id")
#ManyToOne
private ConceptClass conceptClass;
...
#Entity
#Table(name = "concept_class")
public class ConceptClass extends KanaEntity {
#Id
#Basic(optional = false)
#Column(name = "id")
private String id;
#Basic(optional = false)
#Column(name = "label")
private String label;
#Column(name = "oid")
private String oid;
#Column(name = "description")
private String description;
....
And also, what's important is the sql that's being generated:
INSERT INTO concept_class (id, oid, description, label) VALUES (?, ?, ?, ?) bind => [LOINC_TEST, 2.16.212.31.231.54, This is a meta data file for LOINC_TEST, loinc_test]
INSERT INTO concept (concept_key, description, label, code, concept_class_id) VALUES (?, ?, ?, ?, ?) bind => [27, description_1, label_1, code_1, Concept]
The reason this is failing is obvious: It's inserting the word Concept for the concept_class_id. It should be inserting the word LOINC_TEST. I can't figure out why it's using this word. I've used the debugger to look at the Concept and the ConceptClass instance and neither of them contain this word. I'm using eclipselink. Does anyone know why this is happening?
You have two conflicting definitions for the column concept_class_id.
The column concept_class_id appears in the #DiscriminatorColumn and #JoinColumn annotations around class Concept. You can't do that. Those two annotations are fighting for control of the column concept_class_id in table concept. #DiscriminatorColumn happens to be winning. That's why the class name, "Concept", appears in the SQL binding where you expect a ConceptClass id.
By the way, #DiscriminatorColumn is only useful when multiple classes share a single table. The #D/C records which class a row represents. If only objects of class Concept are stored in the concept table, then you can remove #DiscriminatorColumn. #D/C is pointless unless you have multiple classes in a table.
To summarize, fixes include:
Alter "name" in Concept's #DiscriminatorColumn annotation
Alter "name" in Concept.conceptClass's #JoinColumn annotation
Remove #DiscriminatorColumn annotation on class Concept
HTH