I have 4 persistent classes which all have the same fields (exactly) the only 3 difference between them is 1) the class name, 2) the table name and 3) the data. i am aware that this might seem strange to some but trust me there is a good reason which i won't go into here.
now, i'm using hibernate annotations to configure my class which should work like so:
#Entity
#Table(name = "store")
public class Store
{
#Id
#Column(name = "unique_id")
protected String id;
#Column
protected String category;
...
}
.. and this does work, for a single stand-alone class, however there are many fields to map and i'd like to do it all in one hit for all four similar classes, ie:
public class StoreBase
{
#Id
#Column(name = "unique_id")
protected String id;
#Column
protected String category;
...
}
#Entity
#Table(name = "store1")
public class Store1 extends StoreBase
{}
#Entity
#Table(name = "store2")
public class Store2 extends StoreBase
{}
#Entity
#Table(name = "store3")
public class Store3 extends StoreBase
{}
#Entity
#Table(name = "store4")
public class Store4 extends StoreBase
{}
however when attempting this i get the following exception:
Caused by: org.hibernate.AnnotationException: No identifier specified for entity: package.entities.Store1
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:672)
at org.hibernate.cfg.AnnotationConfiguration.processArtifactsOfType(AnnotationConfiguration.java:546)
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:291)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1292)
at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:867)
i'm guessing this is because the super class is not being searched for the identifier?
is there a way to utilise inheritance in this context?
thanks, paul.
#MappedSuperclass
public class StoreBase
See docs for more info.
Have a look at #MappedSuperclass.
Related
I'm learning about the ways of mapping inheritance from database to java with JPA/Hibernate. I've found several examples of how to do it, but not how to apply it.
Now, I'm trying to apply this knowledge on a small project, but I run into a problem where I can't do it the way I thought it would be ideal.
About the code below, the problem is: I have an "Expense" class that records a new expense (credit card debt, etc.), this debt has a creditor, which can be a person (PF) or institution (PJ). A expense has only one creditor, but I'm forced to model with one of each subclass.
#Data
#Entity
#Table(name = "expense")
public class Expense {
// CODE
#ManyToOne
#JoinColumn(name = "creditorPF")
private CreditorPF creditorPF;
#ManyToOne
#JoinColumn(name = "creditorPJ")
private CreditorPJ creditorPJ;
}
#Data
#Entity
#Table(name = "creditor")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Creditor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idCreditor")
protected Long id;
#NonNull
protected String description;
}
#Getter
#Setter
#Entity
#Table(name = "creditor_pf")
#PrimaryKeyJoinColumn(name = "idCreditor")
public class CreditorPF extends Creditor {
private String cpf;
#Builder
public CreditorPF() {
super("");
}
#Builder
public CreditorPF(String cpf, String nome) {
super(nome);
this.cpf = cpf;
}
}
#Getter
#Setter
#Entity
#Table(name = "creditor_pj")
#PrimaryKeyJoinColumn(name = "idCreditor")
public class CreditorPJ extends Creditor {
private String cnpj;
#Builder
public CreditorPJ(String cnpj, String nome) {
super(nome);
this.cnpj = cnpj;
}
#Builder
public CreditorPJ() {
super("");
}
}
This works fine, but I don't think it's a good design, because the design is allowing one more creditor per subclass, even if I add validations to prevent it, the design would be semantically incorrect.
Is there a way I can get a design like this code below, but that I can get the subclass information when I retrieve the object through hibernate?
#Data
#Entity
#Table(name = "expense")
public class Expense {
// CODE
#ManyToOne
#JoinColumn(name = "creditor")
private Creditor creditor;
}
In case of getting the Credit type from THROUGH the Expense Object ,there's no way besides using instanceof or getClass() as #Chris said ,btw it's preferable to use composition over inheritence,since it doesn't introduce this problem and preserves database consistency since you can"t be FORCED to have nullable fields, and in your case you can implement it using a class Creditor containing an enum which holds the creditor type since it's know to you ,hope this helps !
I am trying to create Ebean views on tables based on a simple join and I am running into issues when I try to extend the Model for the base table.
The Views fields and the Models fields are the exact same.
My table Model looks like this:
#Entity
#Table(name = "assets")
public class Asset extends EnvironmentModel<Integer> {
#Id
#Column
#PrimaryKey
#Attribute(index = 0)
private int assetId;
#Column
#Attribute(index = 1)
private String make;
etc...
}
That works just fine.
Now what I am trying to to do with the View is:
#View(name = "assets_view")
public class AssetView extends Asset {
}
I thought I might be able to do this because the AssetView and the Asset having the same exact fields.
When I do it this way I get the exception:
Caused by: javax.persistence.PersistenceException: models.asset.AssetView is NOT an Entity Bean registered with this server?
So my next attempt was to add the #Entity annotation to the View class. e.g.
#Entity
#View(name = "assets_view")
public class AssetView extends Asset {
}
I get the following exception when compiling:
Error injecting constructor, java.lang.IllegalStateException: Checking class models.asset.AssetView and found class models.asset.Asset that has #Entity annotation rather than MappedSuperclass?
But I can't remove the #Entity annotation from my Asset class because I need that to do inserts.
My questions is:
Is there any way to a have a view and a table share the same model, so I can query from the view and insert/update into the table?
Ok, I found an answer and I don't know if this is obvious.
Basically, I just made my base class a #MappedSuperClass e.g.
#MappedSuperclass
public class _Asset extends EnvironmentModel<Integer> {
#Id
#Column
#PrimaryKey
#Attribute(index = 0)
private int assetId;
#Column
#Attribute(index = 1)
private String make;
etc..
}
Then I extended my Asset table and AssetView from that Mapped super class e.g.
#Entity
#Table(name = "assets")
public class Asset extends _Asset {
}
--
#Entity
#View(name = "assets_view")
public class AssetView extends _Asset {
public static final Model.Find<Integer, AssetView> finder = new Model.Finder<>(AssetView.class);
}
I'm trying to create a jpa repository but there is a problem with a foreign-key primary-key. Although it is specified in the abstract base class (MessageDestination), it seems to be invisible from the repository of specialized MessageDestination class (e.g. MessageDestinationRoom).
[...] nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageDestinationRoomDAO': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: This class [class com.chat.message.entity.MessageDestinationRoom] does not define an IdClass
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Message implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
#OneToOne(targetEntity = MessageDestination.class,
cascade=CascadeType.ALL, mappedBy="msg")
#NotNull
private MessageDestination dest;
//...
}
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class MessageDestination implements Serializable {
#Id #OneToOne(cascade=CascadeType.ALL)
private Message msg;
}
#Entity
public class MessageDestinationRoom extends MessageDestination {
#OneToOne #NotNull
private Room destRoom;
//...
}
public interface MessageDestinationRoomDAO
extends JpaRepository<MessageDestinationRoom, Message> {
public Set<MessageDestinationRoom> findMessageDestinationRoomByDestRoom
(Room dest);
}
To solve the issue I saw that I can annotate MessageDestination as a #MappedSuperclass, but this can't work because it needs to be an #Entity to be stored in Message. Sadly, it's not possible:
org.hibernate.AnnotationException: An entity cannot be annotated with both #Entity and #MappedSuperclass
Any ideas? Thanks...
Since you are using table per class inheritance strategy and you dont have any mapped superclass (so each entity must have its own id).
You can annonate your MessageDestination Entity as #MappedSuperClass and remove the #Entity from MessageDestination. As by default its each subclass will inherited all its field including the #Id field
Pending for a better answer because the only solution I found is quite ugly. That consists of splitting the primary and the foreign key, so there is redundancy...
This:
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class MessageDestination implements Serializable {
#Id #OneToOne(cascade=CascadeType.ALL)
private Message msg;
}
public interface MessageDestinationRoomDAO
extends JpaRepository<MessageDestinationRoom, Message> {
public Set<MessageDestinationRoom> findMessageDestinationRoomByDestRoom
(Room dest);
}
becomes this:
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class MessageDestination implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
#OneToOne(cascade=CascadeType.ALL)
private Message msg;
}
interface MessageDestinationRoomDAO
extends JpaRepository<MessageDestinationRoom, Long> {
public Set<MessageDestinationRoom> findMessageDestinationRoomByDestRoom
(Room dest);
}
I was also getting same issue when I was using #oneToMany And #ManyToOne Annotation based Mapping.
Basically what I was doing mistake was in the class that was throwing the error "does not define an IdClass" was having composite Keys i.e More that one #Id annotation used over two member variables due to which it was getting considered as Composite Key and since hibernate expects a seperate Key class needs to be defined in case of composite key this failure was coming.
For reasons that were around before I got to this project, there are tables that are similar types but have different ID columns.
So, when I try this
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Element implements Serializable {
public String title;
}
#Entity
public class PrimaryElement extends Element {
#Id
long pid;
}
#Entity
public class OtherElement extends Element {
#Id
long oid;
}
But then I get an obvious error
No identifier specified for entity: Element
Now, I can't very well put the ID in the Element class because they are obviously mapped to different columns.
I have tried various flavors of #Id and 'abstract' and #MappedSuperClass and so on..
I am at a complete loss. Is there a way around this?
Any insights would be appreciated.
Thanks!
You can either replace the #Entity and #Inheritance annotations of your Element class by #MappedSuperclass (this annotation is responsible for technical mappings and only PrimaryElement and OtherElement will be fully featured entities) or move the oid field with it's #Id annotation to the class Element and use the #AttributeOverride annotation in it's subclasses to modify the column names (in this case also the abstract class Element will be a fully featured entity).
#AttributeOverride(name="oid", column=#Column(name="primary_element_id"))
UPDATE:
#MappedSuperclass
public abstract class Element implements Serializable {
#Id
private long id;
public String title;
}
#Entity
#AttributeOverride(name="id", column=#Column(name="pid"))
public class PrimaryElement extends Element {
}
#Entity
#AttributeOverride(name="id", column=#Column(name="oid"))
public class OtherElement extends Element {
}
I have two hibernate classes: a base class, and an extended class that has additional fields. (These fields are mapped by other tables.)
For example, I have:
#Entity
#Table(name="Book")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
public class B extends A {
public String node_ID;
// ...
}
public class Node {
public String ID; // maps to B.node_ID
// ...
}
How do I map this in Hibernate? The hibernate documentation states three types of inheritence configurations: one table per class, one table with a type column, and a join table -- none of which apply here.
The reason I need to do this is because class A is from generic framework that's reused over multiple projects, and class B (and Node) are extensions specific to one project -- they won't be used again. In the future, I may have perhaps a class C with a house_ID or some other field.
Edit: If I try the above pseudo-code configuration (two entities mapped to the same table) I get an error that the DTYPE column doesn't exist. The HQL has a "where DTYPE="A" appended.
This is possible by mapping the #DiscriminatorColumn and #DiscriminatorValue to the same values for both classes; this can be from any column you use that has the same data regardless of which type (not sure if it works with null values).
The classes should look like so:
#Entity
#Table(name="Book")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="published")
#DiscriminatorValue(value="true")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
#DiscriminatorValue(value="true")
public class B extends A {
public String node_ID;
// ...
}
For anyone who got here like me and does not want to have the dtype column but instead want to use the same table for more than one entity as is I would recommend using this
Basically you can create a Base like this
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
... any other variables, getters + setters
}
#Entity
#Table(name= "book")
public class BookA extends BaseBook<BookA>{
//Default class no need to specify any variables or getters/setters
}
#Entity
#Table(name= "book")
public class BookB extends BaseBook<BookB>{
#Column(name = "other_field")
private String otherFieldInTableButNotMapedInBase
... Any other fields, getter/setter
}
From the above we have created base super class which does not have any entity or table mapping. We then create BookA to be default with the Entity + Table mapping. From there we can create other Entities all extending from BaseBook but pointing to one table