I have been looking into JPA/Hibernate #Entity inheritance for a while now and can't seem to find anything that addresses what I am trying to achieve.
Basically I want to be able to define an #Entity with all of the column and table mappings as required. Then I want to be able to extend the #Entity in a number of different locations with different sets of #Transient methods defined in the body of each "sub-Entity". This is a basic example of what I am trying to achieve but with no success thus far:
#Entity
#Table(name = "mountain")
public class MountainEntityBase implements Serializable {
public Integer mountainId = 0;
public Integer height = 0;
public List<ExplorerEntityBase> explorers = new ArrayList<ExplorerEntityBase>();
#Id
#GeneratedValue
#Column(name = "mountain_id")
public Integer getMountainId() { return mountainId; }
public void setMountainId(Integer mountainId) { this.mountainId = mountainId; }
#Column(name="height")
public String getHeight() { return height; }
public void setHeight(String height) { this.height = height; }
#OneToMany(mappedBy="mountainId")
public List<ExplorerEntityBase> getExplorers() { return this.explorers; }
public void setExplorers(List<ExplorerEntityBase> explorers) { this.explorers = explorers; }
}
.
#Entity
public class MountainEntity extends MountainEntityBase implements Serializable {
public List<MountainEntity> allMountainsExploredBy = new ArrayList<MountainEntity>();
#Transient
public List<MountianEntity> getAllMountainsExploredBy(String explorerName){
// Implementation
}
}
So any extended class will define only #Transients in its body. But also I want to allow for situations where the child class is empty:
#Entity
public class MountainEntity extends MountainEntityBase implements Serializable {
}
Thanks in advance for any help with this.
Inheritance in JPA is specified on the root entity using the #Inheritance annotation. There you can specify the database representation of the hierarchy. Check the documentation for more details.
If your child classes define only transient fields (not methods) (i.e. not saved in the db), then perhaps a discriminator column is the best option. But it may be the case that you don't actually need inheritance - the main entity can have all the methods (because it has all the fields the methods operate on)
Related
I am currently defining JPA entities for a legacy database (lots of composite keys, but also single-column keys). I have created the following entity superclass:
#MappedSuperclass
public abstract class AbstractEntity<ID extends Serializable> {
public abstract ID getId();
public abstract void setId(ID id);
}
And then a superclass for composite keys (as well as a superclass for long primary key, not listed here):
#MappedSuperclass
public abstract class AbstractEmbeddedIdEntity<ID extends Serializable> extends AbstractEntity<ID> {
#EmbeddedId
private ID id;
public AbstractEmbeddedIdEntity() {
id = newId();
}
#Override
public ID getId() {
return id;
}
#Override
public void setId(ID id) {
this.id = id;
}
protected abstract ID newId();
}
And finally concrete entities like this:
#Entity
#Table(name = "firstEntity")
public class FirstEntity extends AbstractEmbeddedIdEntity<FirstEntityId> {
public FirstEntity() {
}
#Embeddable
public static class FirstEntityId implements Serializable {
#Column(name = "firstId")
private String firstId;
public FirstEntityId() {
}
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof FirstEntityId)) {
return false;
}
FirstEntityId other = (FirstEntityId) obj;
return
Objects.equals(firstId, other.firstId);
}
#Override
public int hashCode() {
return Objects.hash(firstId);
}
}
#Override
protected FirstEntityId newId() {
return new FirstEntityId();
}
}
Now the issue is that if I have multiple entities like this and try to access an ID property of an entity (currently with Spring Boot, e.g. findByIdFirstId(String firstId)), an exception is thrown:
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [firstId] on this ManagedType [unknown]
I have debugged this and found out that in hibernate, the metamodel maps all of my entities to the same MappedSupperclass instance. During application startup, the #EmbeddedId returned by newId() is set to the MappedSupperclass, overwriting the ID of the previous entity. So in the end, all entities are mapped to the same MappedSupperclass, but the MappedSupperclass only has the #EmbeddedId of the last entity.
In the above example, accessing the ID property fails because the #EmbeddedId of the last entity doesn't have a property called "firstId" (it has been overwritten with the ID properties of the last entity).
Now I am wondering if my approach is wrong, if I am missing something or if this could be an issue with hibernate?
Complete example using spring boot available on github. Run with mvn spring-boot:run.
This looks to me like a bug in hibernate, therefore I have created a ticket in the hibernate bug tracker.
As a workaround I am now defining the ID attribute (#EmbeddedId) in the concreate entity classes instead of the abstract superclass.
The situation is this:
On the side of the domain we have a superclass let's call it Plant
and two subclasses Vegestable and DecorativePlant.
public abstract class Plant {
private String name;
#Mapping("elementValue.carbonValue")
private int carbonValue;
#Mapping("elementValue.oxygenValue")
private int oxygenValue;
}
public abstract class Vegestable extends Plant {
private int nutritionValue;
}
public abstract class DecorativePlant extends Plant {
private int rating;
}
now on the side of our soap api we have simular objects. The main difference
would probably be that we don't want the decorative plant on the soap-side to have
public class Vegestable {
private int nutrition;
private ElementValue elementValue;
}
public class DecorativePlant {
private int rating;
}
public class ElementValue {
private int carbonValue;
private int oxygenValue;
}
So now I was wondering if it is possible to specify that Dozer only maps the fields carbonValue and oxygenValue for subclasses of Vegestable and not for subclasses of DecorativePlant? If it's possible in Dozer than I won't have to actually alter my classes on the domain level and basically place the carbonValue and oxygenValue in both subclasses and than Dozer won't do the mapping for DecorativePlant. (A side from the #Mapping annotation I'm doing all my mapping in a mappings.xml file.)
Big thanks in advance!
I have the following class structure (it actually is a VO layer with Hibernate mappings):
public abstract class abstractClassVO {
private int id;
private String name;
}
public class concreteClassAVO extends abstractClassVO {
private String aAttribute;
}
public class concreteClassBVO extends abstractClassVO {
private Long bAttribute;
}
And the equivalent DTO objects:
public abstract class abstractClassDTO {
private int id;
private String name;
}
public class concreteClassADTO extends abstractClassDTO {
private String aAttribute;
}
public class concreteClassBDTO extends abstractClassDTO {
private Long bAttribute;
}
Then I have another object like this:
public class compositeObject {
private int anAttribute;
private abstractClassVO myInstance;
}
and its equivalent:
public class compositeObjectDTO{
private int anAttribute;
private abstractClassDTO myInstance;
}
How can I tell dozer to automatically map myInstance to the specific DTO that corresponds to the concrete class implementation in the VO layer?
Currently, out of the box, Dozer isn't even putting anything in the myInstance field of the compositeObjectDTO class. My guess is that it's due to the fact that abstractClassDTO it is an abstact class, and since it cannot determine the implementation, it does nothing. I am not getting any exceptions.
Dozer can't do it out of the box but you could write a helper that would determine destination class by source class. You can get this information from DozerBeanMapper.getMappingMetadata().getClassMappings* methods. These methods return list of ClassMappingMetadata that contains destination class. You just only need to chech whether destination class is inherited from abstractClassDTO. This check can be omitted if you only have one mapping for one VO.
For bi-directional mapping you should additionally check ClassMappingMetadata.MappingDirection field.
I'm facing a little problem with my web application which is in vaadin and i'm using jpa and eclipselink for the mapping. I have three entities :
encaiss (#MappedSuperclass contains just Id)
|
|
Encaissement (it contains the main and common properties)
/ \
/ \
Encaissement_Technique Encaissement_espece
When i create an entity "Encaissement" with "Espece" as type, it is well created in the table Encaissement but it doesn't exist in the table Encaissement_espece.
I guess that I should join the two tables according to the identifier (ID) which is in a #MappedSuperclass class. I would appreciate any help for managing my subordinate class (that is Encaissement_Technique and Encaissement_espece) because my next step would be to add records to those two tables from a simple form (so if i have a field "libelle" which is present in Encaissement but not in Encaissement_Espece how can make such instruction :
Encaissement_Espece espece= new Encaissement_Espece();
espece.setLibelle(field.getValue().toString());
Those are my entities :
encaiss, this class contain just the Id for all the classes
#MappedSuperclass
public abstract class encaiss {
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="encaiss_seq_gen")
#SequenceGenerator(name="encaiss_seq_gen", sequenceName="ENCAISSEMENT_SEQ", allocationSize = 1, initialValue = 1)
protected Integer id_encaissement;
public Integer getId_encaissement() {
return id_encaissement;
}
public void setId_encaissement(Integer id_encaissement) {
this.id_encaissement = id_encaissement;
}
}
Encaissement (wich extend encaiss just to have an Id)
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="ENCAISS_TYPE")
#Table(name="ENCAISSEMENT")
public class Encaissement extends encaiss implements Serializable{
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "ID_CLIENT")
private Client Client;
#Column(name="ENCAISS_TYPE")
protected String encaiss_type;
#Column(name="LIBELLE")
protected String libelle;
#Column(name="PIECE_JOINTE")
protected String piece_jointe;
#Embedded
protected Avis_Recette avis_recette;
public Encaissement(String encaiss_type, String libelle, String piece_jointe){
this.encaiss_type=encaiss_type;
this.libelle=libelle;
this.piece_jointe=piece_jointe;
}
public Encaissement(){
}
}
Encaissement_Espece, inherits from Encaissement
#Entity
#DiscriminatorValue("Espece")
#Table(name="ENCAISSEMENT_ESPECE")
public class Encaissement_Espece extends Encaissement{
public Caisse getCaisse() {
return caisse;
}
public void setCaisse(Caisse caisse) {
this.caisse = caisse;
}
public float getMontant() {
return montant;
}
public void setMontant(float montant) {
this.montant = montant;
}
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "ID_CAISSE")
private Caisse caisse;
#Column(name = "MONTANT")
private float montant;
public Encaissement_Espece(float montant){
this.montant=montant;
}
public Encaissement_Espece(){
}
}
Encaissement_Technique, inherits from Encaissement
#Entity
#DiscriminatorValue("Technique")
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="ENCAISS_TECHNIQUE_TYPE")
#Table(name="ENCAISSEMENT_TECHNIQUE")
public class Encaissement_Technique extends Encaissement implements Serializable{
public Banque getBanque() {
return banque;
}
public void setBanque(Banque banque) {
this.banque = banque;
}
public float getPrimeCoass() {
return primeCoass;
}
public void setPrimeCoass(float primeCoass) {
this.primeCoass = primeCoass;
}
public Set<Periode> getPeriode() {
return periode;
}
public void setPeriode(Set<Periode> periode) {
this.periode = periode;
}
public String getEncaiss_technique_type() {
return encaiss_technique_type;
}
public void setEncaiss_technique_type(String encaiss_technique_type) {
this.encaiss_technique_type = encaiss_technique_type;
}
#Column(name="PRIMECOASS")
protected float primeCoass;
#Column(name="ENCAISS_TECHNIQUE_TYPE")
protected String encaiss_technique_type;
public Encaissement_Technique(float primeCoass, String encaiss_technique_type){
this.primeCoass=primeCoass;
this.encaiss_technique_type=encaiss_technique_type;
}
public Encaissement_Technique(){
}
}
I hope i will find a pertinent answer as i searched for this in vain. It'll help me a lot.
Thank you.
"When i create an entity "Encaissement" with "Espece" as type, it is well created in the table Encaissement but it doesn't exist in the table Encaissement_espece." This statement suggests you have an instance of Encaissement and expect JPA to turn it into an instance of Encaissement_Espece just by changing the encaiss_type value. Java object inheritance doesn't work that way, which is what JPA inheritance tries to map to a relational database. An object in java cannot change what it is simply by setting a flag - you need to create a new instance if you want the data represented differently.
In this case, you need to create an instance of the Encaissement_Espece class. Because this class maps to the Encaissement and Encaissement_espece tables, JPA will automatically insert a row into both to represent this object. When you create Encaissement instance, a row goes into the Encaissement table, while when you create Encaissement_Technique instances, a row goes into both Encaissement_Technique and Encaissement tables. If you wish to change the object's type once it is persisted, you need to remove the old instance, flush, then persist the new one.
As mentioned in another answer, the encaiss_type is controlled through the class type itself and so does not need a mapping. Having one might be handy for queries or access (though you can just use instance of etc); it should be marked as insertable=false, updatable=false so that you do not attempt to modify the value directly.
remove the
#Column(name="ENCAISS_TYPE")
protected String encaiss_type;
from the Encaissment.
It will be handle automatically by JPA. It should solve the problem.
i've a base class that provide only identification:
public abstract class Identifable<T> {
#Id
private T id = null;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public boolean hasId() {
return id != null;
}
}
and several subclasses that extends it like:
#Entity
#Cache
public class MyEntity extends Identifable<String> {
/* some specific attributes and methods */
}
I get an java.lang.IllegalStateException: #Id field 'id' in com.mypkg.MyEntity must be of type Long, long, or String.
Why? Can't Objectify see the inherited #Id field?
Thanks
The cause:
Objectify only inspects types at runtime using reflection. Because of type erasure all unbounded type parameters are during compilation converted to Object type, which is what objectify sees and complains.
The solution:
Use concrete type for id field. Possibly move it to a child class, as proposed by #Anthony.
In JPA, you must use for a field marked with #Id one of the following types:
any Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date; java.sql.Date; java.math.BigDecimal; java.math.BigInteger
Just remove the generics from the base class and use one of the mentioned types for your id field.
Let's reason about if for a while... You are trying to build a super type in which the type of the ID varies. Are you sure that this is what you want objectify to build (a hierarchy of objects in which the root entity has a unknown ID type)? While I've seen this kind of code in several ORM frameworks, this is how I would build what you want.
Interface (not part of the object hierarchy):
public interface Identifable<T> {
public T getId();
public void setId(T id);
public boolean hasId();
}
Root of your hiearchy implements Identifable with a concrete type for the id:
#Entity
public class MyBaseClass implements Identifable<String> {
#Id
private String id = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean hasId() {
return id != null;
}
}
And subclass comes naturally out of it:
#EntitySubclass(index=true)
public class MyEntity extends MyBaseClass {
// fields, accessors and mutators
}