JPA Hibernate 5: OneToOne in nested Embeddable causes metamodel issue - jpa

I have an entity:
#Entity
public class Test {
#Embedded
Content content;
// getters setters..
}
This contains an embedded class as you can see:
#Embeddable
public class Content {
#OneToOne
Person person;
#Embedded
Language language;
// getters setters..
}
This contains again an embeddable. 2 times nested embeddable
#Embeddable
public class Language {
String format;
#OneToOne
IdentifierCode identifierCode;
// getters setters..
}
When using the automatic schema generation feature of JPA all columns are generated in the correct way.
I use the #Data annotation on each #Entity and #Embeddable to generate getters, setters, constructors, etc..
When starting the application server (EAP 7), I notice this warning in the logs:
HHH015011: Unable to locate static metamodel field :
org.package.Language_#identifierCode; this may or may not indicate a
problem with the static metamodel
Indeed, when opening the metamodel class Language_; no identifierCode column reference is present:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(Language.class)
public abstract class Language_ {
public static volatile SingularAttribute<Language, String> format;
}
I don't see what I'm doing wroing. Is it not possible to use #OneToOne in a nested #Embeddable? The metamodel Content_ correctly generates the singular attribute for person!
It seems when using multiple nested embeddables, something goes wrong. When using only one level of embeddables, it works.
I tried other stuff:
Adding Access.Field on the class. Nothing happens.
Instantiation the #Embedded class, like #Embedded Language language = new Language(). Nothing happens.
Replaced the #OneToOne with #ManyToOne. Nothing happens.

This sounds like a bug in your JPA provider, which you should report to them.
The JPA provider I use (DataNucleus) creates a
public static volatile SingularAttribute<Language, mydomain.model.IdentifierCode> identifierCode;
One option you have is to just use the datanucleus-jpa-query.jar in your CLASSPATH to generate the static metamodel and use those generated classes with your existing provider, alternatively use it for persistence too.

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.

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.

Ebean inheritance "Abstract class with no readMethod" exception

I try to use inheritance with Ebean in Play! Framework 2.1.0. The inheritance strategy is "single table", as it is the only one supported by Ebean. I closely follow example from JPA Wikibook
#Entity
#Inheritance
#DiscriminatorColumn(name="price_type")
#Table(name="prices")
public abstract class Price {
#Id
public long id;
// Price value
#Column(precision=2, scale=18)
public BigDecimal value;
}
#Entity
#DiscriminatorValue("F")
public class FixedPrice extends Price {
// NO id field here
...
}
#Entity
#DiscriminatorValue("V")
public class VariablePrice extends Price {
// NO id field here
...
}
This code passes compilation, but I get
RuntimeException: Abstract class with no readMethod for models.Price.id
com.avaje.ebeaninternal.server.deploy.ReflectGetter.create(ReflectGetter.java:33)
com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.setBeanReflect(BeanDescriptorManager.java:1353)
com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.createByteCode(BeanDescriptorManager.java:1142)
com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readDeployAssociations(BeanDescriptorManager.java:1058)
com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityDeploymentAssociations(BeanDescriptorManager.java:565)
com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:252)
com.avaje.ebeaninternal.server.core.InternalConfiguration.<init>(InternalConfiguration.java:124)
com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:210)
com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:64)
com.avaje.ebean.EbeanServerFactory.create(EbeanServerFactory.java:59)
play.db.ebean.EbeanPlugin.onStart(EbeanPlugin.java:79)
Google search brings only one relevant link which is source code of ReflectGetter.java. The comment there says
For abstract classes that hold the id property we need to use reflection to get the id values some times.
This provides the BeanReflectGetter objects to do that.
If I drop abstract keyword from superclass declaration, exception disappears. I would really prefer not to make superclass concrete though.
Add getter/setter for your id field and it will go away.

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.

JPA ManyToMany relation update failed due to constraint key in another relation

While developing an Eclipse GEF application using an eclipselink implementation of JPA i have found an error that has been annoying me for a while:
I have three different classes:
The first one represents a variable contained in a model:
#Entity
public class Variable extends AbstractVariable{
#Id
#Generated value
private int id;
/** Lots more of stuff */
#ManyToOne(cascade=CascadeType.ALL)
Model model;
//Setters, getters and the other functions.
}
And another subclass of the abstractvariant class, which is a variable which can hold a concatenation of variables.
#Entity
public class VariableList extends AbstractVariable{
#Id
#Generated value
private int id;
/** Lots more of stuff */
#ManyToMany(cascade=CascadeType.ALL)
List<AbstractVariable> variables;
//Setters, getters and the other functions.
}
The second class, a gef editpart that can hold a variable value.
#Entity
public class VariableEditPart{
#Id
#Generated value
private int id;
/** Lots more of stuff */
VariableList vars;
//Setters, getters and the other functions.
}
And a last class with the gef model:
#Entity
public class Model{
#Id
#Generated value
private int id;
/** Lots more of stuff */
#OneToMany(cascade=CascadeType.ALL)
List<Variable> availableVars;
#OneToMany(cascade=CascadeType.ALL)
List<VariableEditPart> editParts;
//Setters, getters and the other functions.
}
The issue here is that JPA creates a table for the relation variablelist-variable, and another relation with the editpart and the variablelist, so, as soon as I try to update the model at the database after some modifications, It tries automatically to delete the Variable, and ends up with a constraint violation error caused because the list of variables holded by the model still points to that variable (which by the way, I was not pretending to delete, and I've tested lots of differenst cascadeType's to avoid it without any luck...).
Thanks for your attention and be kind with my english, it's not my native language ;)
It seems you have a very interrelated model with everything referencing everything in cycles.
What are you doing exactly? When you remove the Variable, are you removing all references to it? You need to.