Entity bean 3.0 composite key issue - jpa

1: I have a table as shown below :
Name Null? Type
ATX_ID NOT NULL NUMBER(16)
ATX_GLM_CD NOT NULL NUMBER(5)
ATX_CRDR_FLG NOT NULL VARCHAR2(1)
ATX_AMT NOT NULL NUMBER(15,2)
ATX_STTS NOT NULL VARCHAR2(1)
ATX_TCM_ID NOT NULL NUMBER(16)
ATX_TXN_DT NOT NULL DATE
ATX_CRTE_BY NOT NULL VARCHAR2(30)
ATX_CRTE_DT NOT NULL DATE
The columns ATX_ID,ATX_GLM_CD and ATX_CRDR_FLG form a composite primary key.
2: I have created an entity bean class for the above table as follows :
#Entity
public class AcctngTxns implements Serializable {
private BigDecimal atxAmt;
private String atxStts;
private BigDecimal atxTcmId;
private Date atxTxnDt;
private String atxCrteBy;
private Date atxCrteDt;
#EmbeddedId
private AcctngTxnsPK acctngTxnsPK;
public AcctngTxns() {
//super();
}
/*getters and setters*/
}
#Embeddable
public class AcctngTxnsPK implements Serializable {
private long atxId;
private long atxGlmCd;
private String atxCrdrFlg;
private static final long serialVersionUID = 1L;
public AcctngTxnsPK() {
//super();
}
/*necessary overrides*/
}
3: /orm.xml/
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
4: /persistence.xml/
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
DataSource
com.nseit.ncfm2.data.ejb.entity.AcctngTxns
5: While accessing the entity bean via a session bean,I am getting the following exception :
<[weblogic.servlet.internal.WebAppServletContext#1a1bc8f - appName: '_auto_generated_ear_', name: 'AWebApp', context-path: '/AWebApp', spec-version: '2.5'] Servlet failed with Exception
javax.ejb.EJBException: EJB Exception: ; nested exception is:
org.apache.openjpa.persistence.ArgumentException: Fields "com.nseit.ncfm2.data.ejb.entity.AcctngTxns.acctngTxnsPK" are not a default persistent type, and do not have any annotations indicating their persistence strategy. If you do not want these fields to be persisted, annotate them with #Transient.
at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.unwrapRemoteException(RemoteBusinessIntfProxy.java:105)
at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:87)
at $Proxy127.gottaAccessEntity3(Unknown Source)
at jsp_servlet.__result.jspService(_result.java:115)
at weblogic.servlet.jsp.JspBase.service(JspBase.java:34)
Truncated. see log file for complete stacktrace
org.apache.openjpa.persistence.ArgumentException: Fields "com.nseit.ncfm2.data.ejb.entity.AcctngTxns.acctngTxnsPK" are not a default persistent type, and do not have any annotations indicating their persistence strategy. If you do not want these fields to be persisted, annotate them with #Transient.
at org.apache.openjpa.persistence.PersistenceMetaDataFactory.validateStrategies(PersistenceMetaDataFactory.java:399)
at org.apache.openjpa.persistence.PersistenceMetaDataFactory.load(PersistenceMetaDataFactory.java:205)
at org.apache.openjpa.meta.MetaDataRepository.getMetaDataInternal(MetaDataRepository.java:474)
at org.apache.openjpa.meta.MetaDataRepository.getMetaData(MetaDataRepository.java:294)
at org.apache.openjpa.kernel.BrokerImpl.newObjectId(BrokerImpl.java:1114)
Truncated. see log file for complete stacktrace
7: Certainly,I do not want the primary key fields to be updated.
8: I tried to figure out the implementation of the below points mentioned in JPA documentation :
A composite primary key must be represented and mapped to multiple fields or properties of the entity class, or must be represented and mapped as an embeddable class.
If the class is mapped to multiple fields or properties of the entity class, the names and types of the primary key fields or properties in the primary key class must match those of the entity class.
8: Please help me in resolving the issue.
Thanks !

I found a solution by trial-and-error method. It seems that with JPA 1.0,it is necessary to mention the embedded-id in orm.xml file as follows :
Thanks.

Related

Is JPA Embeddable a Value Object and Why Can't it Represent a Table

PROBLEM: I have read-only data in a table. Its rows have no id - only composite key define its identity. I want it as a Value Object (in DDD terms) in my app.
RESEARCH: But if I put an #Embeddable annotation instead of #Entity with #Id id field, then javax.persistence.metamodel doesn't see it and says Not an embeddable on Metamodel metamodel.embeddable(MyClass.class);. I could wrap it with an #Entity class and autogenerate id, but this is not what I architectually intended to achieve.
QUESTION: Is JPA Embeddable a Value Object? Can Embeddable exist without a parent Entity and represent a Table?
There are many articles on the topic that show this is a real JPA inconvenience:
http://thepaulrayner.com/persisting-value-objects/
https://www.baeldung.com/spring-persisting-ddd-aggregates
https://paucls.wordpress.com/2017/03/04/ddd-building-blocks-value-objects/
https://medium.com/#benoit.averty/domain-driven-design-storing-value-objects-in-a-spring-application-with-a-relational-database-e7a7b555a0e4
Most of them suggest solutions based on normalised relational database, with a header-entity as one table and its value-objects as other separate tables.
My frustration was augmented with the necessity to integrate with a non-normalized read-only table. The table had no id field and meant to store object-values. No bindings with a header-entity table. To map it with JPA was a problem, because only entities with id are mapped.
The solution was to wrap MyValueObject class with MyEntity class, making MyValueObject its composite key:
#Data
#Entity
#Table(schema = "my_schema", name = "my_table")
public class MyEntity {
#EmbeddedId MyValueObject valueObject;
}
As a slight hack, to bypass JPA requirements for default empty constructor and not to break the immutability of Value Object, we add it as private and sacrifice final modifier for fields. Privacy and absence of setters conforms the initial DDD idea of Value Object:
// #Value // Can't use, unfortunately.
#Embeddable
#Immutable
#AllArgsConstructor
#Getter
#NoArgsConstructor(staticName = "private") // Makes MyValueObject() private.
public class MyValueObject implements Serializable {
#Column(name = "field_one")
private String myString;
#Column(name = "field_two")
private Double myDouble;
#Transient private Double notNeeded;
}
Also there is a handful Lombok's #Value annotaion to configure value objects.

Saving value in Hibernate to another table than entity table

I have two tables bo_operator and hist_bo_operator_password. In bo_operator the id column is foreign key to hist_bo_operator_password and I can have many the same operator_id in hist_bo_operator_password and only one id in bo_operator.
My entity:
#Entity
#Table(name="bo_operator")
public class Operator implements Serializable
and that's how I am getting values from hist_bo_operator_password:
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
public List<String> oldPasswords = new ArrayList<String>();
but when I'm trying to get only one value by:
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
public String oldPassword;
I'm getting error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: local.vlex.operator.model.Operator.oldPassword
and all I want to do is makeing an insert into hist_bo_operator_password by
operator.setOldPassword(oldPassword);. I think the problem is that it doesn't know which password take if there is many values for the same id.
How to achive it?
#Edit
I also tried:
#Table(name="bo_operator")
#SecondaryTable(name = "hist_bo_operator_password",pkJoinColumns=#PrimaryKeyJoinColumn(name="id_operator", referencedColumnName="id"))
I even found ORDER BY so:
#Column(name="password", table="hist_bo_operator_password")
#OrderBy("data_ins")
public String oldPassword;
but seems like there is no #Limit or something like this in JPA and I still have many values to the same id which cause error:
org.hibernate.HibernateException: Duplicate identifier in table for: [local.vlex.operator.model.Operator#1]
As you described at the first sentence you have one-to-many relationship
#ElementCollection
#CollectionTable(name="hist_bo_operator_password", joinColumns=#JoinColumn(name="id_operator"))
#Column(name="password")
#OrderBy("data_ins")
public List<String> oldPasswords = new ArrayList<String>();
Then add necessary getter
Optional<String> getPassword() {
return oldPasswords.size() > 0
? Optional.of(oldPasswords.get(0))
: Optional.empty();
}
And setter
void setPassword(String password) { // or maybe addPassword?
oldPasswords.add(password);
}
Why don't you create entity of hist_bo_operator_password?
Then you could have List of that entities (instead of just String List) and just add another object to List and save entity Operator.

Hibernate Search and composed key using #IdClass

I have a problem to integrate Hibernate Search in existing project with hundreds of entities but at least half of entities use #IdClass annotation as composed key. Can I solve the problem using the annotation #IdClass?
I also read this post Hibernate search and composed keybut I have not managed to solve my problem.
I have the following example:
entity class:
#Entity
#Table(name="FAKVS_DB")
#IdClass(value=PK_FAKVS_DB.class)
#Audited
#Indexed
public class FAKVS_DB implements Serializable {
#Id
#Column(name="Key_FAM", length=10, nullable=false)l
private String keyFam;
#Id
#Column(name="Komponentennr", nullable=false)
private Integer komponentenNr;
#Id
#Column(name="Hinweis", nullable=true, length=4)
private String hinweis;
//getters and setters
}
and composed key:
public class PK_FAKVS_DB implements Serializable {
private String keyFam;
private Integer komponentenNr;
private String hinweis;
//getters and setters
}
The error that occurs is:
HSEARCH000058: HSEARCH000212: An exception occurred while the MassIndexer was transforming identifiers to Lucene Documents
java.lang.ClassCastException: package.entities.module.fi.pk.PK_FAKVS_DB cannot be cast to java.lang.Integer
at org.hibernate.type.descriptor.java.IntegerTypeDescriptor.unwrap(IntegerTypeDescriptor.java:36)
at org.hibernate.type.descriptor.sql.IntegerTypeDescriptor$1.doBind(IntegerTypeDescriptor.java:63)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:90)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:286)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:281)
at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1995)
at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1966)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1901)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1862)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1839)
at org.hibernate.loader.Loader.doQuery(Loader.java:910)
If I can not use #IdClass annotation can you tell me what are the alternatives?
Thank you very much in advance.
An alternative is to add a new property to be used as Id by Hibernate Search. You can mark this with #DocumentId to have the Hibernate Search engine treat the alternative property as the identifier in the index.
You will need to ensure that this new property is unique of course; this can typically done by generating a String from the real id. You probably want to annotate the new getter with #Transient so that it doesn't get persisted in the database.

EclipseLink MultiTenant and Spring Data JPA - #IdClass annotation required - Why?

I'm developing a multi-tenant (multi-schema) application using Spring-Data-JPA and EclipseLink.
When not using multi-tenant capabilities everything is ok, JPA entity works as a charme and obviously works with only one schema.
When I try to activate the multi-tenant adding the folloqing annotation to the entity :
#Multitenant(value=MultitenantType.TABLE_PER_TENANT)
#TenantTableDiscriminator(type=TenantTableDiscriminatorType.SCHEMA, contextProperty="eclipselink-tenant.id")
and I restart the application, i get the following exception :
Caused by: java.lang.IllegalArgumentException: No #IdClass attributes exist on the IdentifiableType [EntityTypeImpl#15818739:CrsMomiJob [ javaType: class com.gpdati.momi.model.core.CrsMomiJob descriptor: RelationalDescriptor(com.gpdati.momi.model.core.CrsMomiJob --> [DatabaseTable(CRS_MOMI_JOB)]), mappings: 7]]. There still may be one or more #Id or an #EmbeddedId on type.
at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getIdClassAttributes(IdentifiableTypeImpl.java:169)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:170)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:71)
at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146)
at com.gpdati.momi.jpa.MultiTenantJpaRepositoryFactory.getTargetRepository(MultiTenantJpaRepositoryFactory.java:30)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:67)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:136)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:153)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:43)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
... 79 more
It seems like the #Id annotation on the Id field is no more read from Spring-Data that look for a #IdClass annotation (I thought #IdClass annotation is required when using a composite primary key, that's not my case)
Any clue?
Thanks!
Here the full entity code :
#Entity
#Table(name="CRS_MOMI_JOB")
#Multitenant(value=MultitenantType.TABLE_PER_TENANT)
#TenantTableDiscriminator(type=TenantTableDiscriminatorType.SCHEMA, contextProperty="eclipselink-tenant.id")
public class CrsMomiJob implements Serializable {
private static final long serialVersionUID = -432489894772L;
private String abilitata;
#Column(name="HOT_CODICE")
private String hotCodice;
#Column(name="INT_CODICE")
private String intCodice;
private Long intervallo;
private String note;
private String parametri;
#Id
private BigDecimal id;
public CrsMomiJob() {
}
... all getters and setters ...
}
Seems to be a bug in the EclipseLink meta model code in hasSingleIdAttribute(), this is returning true (as the id is composite for multitenants) but this should be hidden, so should be returning false.
Please log a bug.

Why is this JPA 2.0 mapping giving me an error in Eclipse/JBoss Tools?

I have the following situation:
(source: kawoolutions.com)
JPA 2.0 mappings (It might probably suffice to consider only the Zip and ZipId classes as this is where the error seems to come from):
#Entity
#Table(name = "GeoAreas")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class GeoArea implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected Integer id;
#Column(name = "name")
protected String name;
...
}
#Entity
#Table(name = "Countries")
#DiscriminatorValue(value = "country")
public class Country extends GeoArea
{
#Column(name = "iso_code")
private String isoCode;
#Column(name = "iso_nbr")
private String isoNbr;
#Column(name = "dial_code")
private Integer dialCode = null;
...
}
#Entity
#Table(name = "Zips")
#IdClass(value = ZipId.class)
public class Zip implements Serializable
{
#Id
#Column(name = "code")
private String code;
#Id
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country = null;
...
}
public class ZipId implements Serializable
{
private String country;
private String code;
...
}
Pretty simple design:
A country is a geo area and inherits the ID from the root class. A ZIP code is unique within its country so it combines an ISO code plus the actual ZIP code as PK. Thus Zips references Countries.iso_code, which has an alternative unique, not-null key on it (reference to non-primary key column!). The Zip.country association gets an #Id annotation and its variable name is the same as the one in its ID class ZipId.
However I get this error message from within Eclipse (also using JBoss Tools):
Validation Message:
"The attribute matching the ID class attribute country does not have the correct type java.lang.String"
Why is this wrong in JPA 2.0 syntax (see #Id annotation on Zip.country)? I don't think it is. After all the types of Zip.country and ZipId.country can't be the same for JPA 2 because of the #Id annotation on the #ManyToOne and the PK being a simple integer, which becomes the ID class counterpart. Can anyone check/confirm this please?
Could this be a bug, probably in JBoss Tools? (Which software component is reporting the above bug? When putting the 3 tables and entity classes into a new JavaSE project there's no error shown with the exact code...)
Answering own question...
The way I modeled the reference, I use a String because the FK points to the iso_code column in the Countries table which is a CHAR(2), so basically my mapping is right. However, the problem is that JPA 2.0 doesn't allow anything but references to primary key columns. This is what the Eclipse Dali JPA validator shows.
Taken from "Pro JPA 2.0" by Keith/Schincariol p.283 top, "Basic Rules for Derived Identifiers" (rule #6): "If an id attribute in an entity is a relationship, then the type of the matching attribute in the id class is of the same type as the primary key type of the target entity in the relationship (whether the primary key type is a simple type, an id class, or an embedded id class)."
Personal addendum:
I disagree with JPA 2.0 having this limitation. JPA 1.0 mappings allow references to non-PK columns. Note, that using JPA 1.0 mappings instead isn't what I'm looking for. I'd rather be interested in the reason why this restriction was imposed on JPA 2.0. The JPA 2.0 is definitely limiting.
I'd say focus your attention on the CompoundIdentity relationship. See this question, and my answer there
Help Mapping a Composite Foreign Key in JPA 2.0
ZipId has no "country" field in your case
I have not tested your code, but it looks pretty much related to the use of the #PrimareKeyJoinColumn annotation.
The JPA 2.0 specification in section 11.1.40 states:
The PrimaryKeyJoinColumn annotation is
used to join the primary table of an
entity subclass in the JOINED mapping
strategy to the primary table of its
superclass; it is used within a
SecondaryTable annotation to join a
secondary table to a primary table;
and it may be used in a OneToOne
mapping in which the primary key of
the referencing entity is used as a
foreign key to the referenced
entity[108].
The example in the spec looks like your case.
#Entity
#Table(name="CUST")
#Inheritance(strategy=JOINED)
#DiscriminatorValue("CUST")
public class Customer { ... }
#Entity
#Table(name="VCUST")
#DiscriminatorValue("VCUST")
#PrimaryKeyJoinColumn(name="CUST_ID")
public class ValuedCustomer extends Customer { ... }
I hope that helps!