I have an entity class which inherits from four level of inheritance in which the top level parent defines the primary key (#Id) and I'm having trouble figuring out what I did wrong as I get this error:
Entity class [class D] has no primary key specified. It should define either an #Id, #EmbeddedId or an #IdClass. If you have defined PK using any of these annotations then make sure that you do not have mixed access-type (both fields and properties annotated) in your entity class hierarchy.
Here's the hierarchy:
A->B->C->(Entity)D
This is my non-entity class that gives the values to its children:
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
#MappedSuperclass
public class A implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
protected Date deleted;
public Date getDeleted() {
return deleted;
}
public void setDeleted(Date deleted) {
this.deleted = deleted;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
and this is one of its child :
#MappedSuperclass
public abstract class B extends A implements Serializable {
}
B->C
import javax.persistence.Entity;
import javax.persistence.Transient;
#MappedSuperclass
public class C extends B{
protected String name;
protected String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
And finally C-> (Entity)D
#Entity
public class D extends C{
private String codeD;
public String getCodeD(){return codeD;}
public String setCodeD(String codeD) {this.codeD = codeD;}
}
According to every source I've found, normally with the #MappedSuperclass and implementing Serializable it should work. Thought I've tried implementing Serializable even every step of the hierarchy but I received the same error message.
I'm currently using Payara 4.1.1.1621. I don't know if that might be the problem as I've seen this kind of error in Payara on some thread but they all miraculously resolved themselves.
I resolved this error by adding my base class to persistence.xml.
The configuration seems to be as the specification suggests.
The only thing that comes to my mind that can be added is the #Column annotation to explicitly declare the database column names:
#Id
#GeneratedValue
#Column(name = "id")
protected Long id;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#Column(name = "deleted")
protected Date deleted;
Try it out. If it still doesnt work try to annotate all the fields in the classes marked as #MappedSuperClass.
Also, class C should have its fields marked as protected not private?
Related
I am new with JPA, so maybe someone can explain me how to correctly annotate abstract classes using JPA?
I have an abstract class with generated id field:
public abstract class AbstractClass implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
#Id
#GeneratedValue
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
and an abstract class with name which extends AbstractClass:
public abstract class AbstractNameClass extends AbstractClass {
private String shortName;
#Column(name = "shortName", nullable = false)
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
}
I have two types of classes, one extends AbstractClass and other classes extends AbstractNameClass:
#Entity
public class Model extends AbstractNameClass {
// this class should inherit id (from AbstractClass) and shortName (from AbstractNameClass)
}
and
#Entity
public class Vehicle extends AbstractClass {
// this class should inherit only id
}
If I add #MappedSuperclass annotation on AbstractClass, then I can create and save objects which are extending AbstractClass, but how to annotate AbstractNameClass? I tried to add #Entity annotation, but I got "No identifier specified for entity" error, also I tried to add #MappedSuperclass annotation and also got the same error.
So my question would be - how to correctly annotate abstract classes using JPA, without creating AbstractClass and AbstractNameClass tables (in my db I want to have only Model and Vehicle tables)?
I'm trying, but have not been successful so far, using the following classes with Hibernate.
#MappedSuperclass
#Embeddable
public abstract class Foo {
// atributes...
}
#Embeddable
public class Poo extends Foo {
// atributes...
}
#Entity
#Table
public class None {
// atributes...
#Embedded
private Foo foo;
// constructor
public None(Foo foo) {
this.foo = foo;
}
}
// example of save
None none = new None(Poo poo);
save(none);
Hibernate returns: Cannot instantiate abstract class or interface
Is it possible to perform this operation with JPA?
I ran into the same problem.
It seems like #embedable does not work with #DiscriminatorColumn. The only way I could get this to work is to use #DiscriminatorColumn, but treat the #embedable like a separate entity on the same table.
What this means is that the query will likely join the table to itself.
#Entity
#Table(name="tbComputers")
public class Computer{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public long id;
public String motherboard;
#OneToOne
#JoinColumn(name="id")
public CPU cpu;
}
#Entity
#DiscriminatorColumn(name="cpu_type")
#Table(name="tbComputers")
public abstract class CPU {
#Id
private Long id;
#Column(name = "cpu")
public String name;
public abstract String someProcessorSpecificMethod();
}
#Entity
#DiscriminatorValue("Intel")
public class Intel extends CPU {
#Override
public String someProcessorSpecificMethod() {
return "Intel";
}
}
#Entity
#DiscriminatorValue("AMD")
public class AMD extends CPU {
#Override
public String someProcessorSpecificMethod() {
return "AMD";
}
}
EDIT: After further testing I found that while this works for reading data, it does not for persisting. It will create a separate INSERT. It seems like it is not supported https://hibernate.atlassian.net/browse/HHH-1910. The alternative is to to split the table.
I follow this post to resolve my initial problem:
Specifying distinct sequence per table in Hibernate on subclasses
But now I get an exception:
Invocation of init method failed; nested exception is
java.lang.IllegalArgumentException: Duplicate generator name idgen
Below my class, subclass and pom.xml:
EntityId (abstract class)
#MappedSuperclass
public abstract class EntityId<T extends Serializable> implements Serializable {
private static final long serialVersionUID = 1974679434867091670L;
#Id
#GeneratedValue(generator="idgen", strategy=GenerationType.SEQUENCE)
#Column(name="id")
protected T id;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
}
Category class
#Entity
#SequenceGenerator(name="idgen", sequenceName="cat_id_seq", allocationSize=1)
#AttributeOverrides({
#AttributeOverride(name="id", column = #Column(name="cat_id"))
})
#Table(name="categoria")
public class Category extends EntityId<Integer> {
private static final long serialVersionUID = -870288485902136248L;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
}
pom.xml
...
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.15.Final</version>
</dependency>
...
My problem it's similar with this post: https://hibernate.atlassian.net/browse/HHH-12329
From the link you provided. The JPA spec says that:
A sequence generator may be specified on the entity class or on the
primary key field or property. The scope of the generator name is
global to the persistence unit (across all generator types).
So, it's not legal to have two identifier generators with the same name and different configurations. The scope is global, not entity.
To resolve your issue you should push the #Id from the #MappedSuperclass into subclasses.
More details
Edited, added possible workaround:
remove annotation from field in super class;
make getter abstract;
let all sub-classes have their own sequence generator: all generators should have global unique name;
implement getter;
move annotations related to Id field on getter.
public interface EntityId<T extends Serializable> extends Serializable {
public T getId();
public void setId(T id);
}
#Entity
#Table(name="categoria")
public class Category implements EntityId<Integer> {
private static final long serialVersionUID = -870288485902136248L;
#Id
#Column(name="cat_id")
#SequenceGenerator(name="cat_id_gen", sequenceName="categoria_cat_id_seq", allocationSize=1)
#GeneratedValue(generator="cat_id_gen", strategy=GenerationType.SEQUENCE)
private Integer id;
//others attributes here...
#Override
public void setId(Integer id) {
this.id = id;
}
#Override
public Integer getId() {
return this.id;
}
}
I have a database with common audit columns in each table but with different column names.
e.g
The Person table has the following audit columns,
(per_creation_user, per_creation_date, per_update_user, per_update_date), and the address table has audit columns called (add_creation_user, add_creation_date, add_update_user, add_update_date).
I am trying to map these using JPA annotations and use an event listener class to populate these audit columns automatically whenever they are persisted in the database.
I have a base abstract class containing these audit columns, I could then annotate this with #MappedSuperclass and put the entity listener annotation on here too. All neat and tidy, unfortunately the column names differ for each audited entity. I think the only option is to have the audit columns mapped separately on each entity?
Can anybody suggest a better method for doing this?
#EntityListeners(BaseDTOEventListener.class)
#MappedSuperclass
public abstract class BaseDTO {
private String creationUser;
private Date creationDate;
}
#Entity
#Table(name="PERSON")
public class Person extends BaseDTO{
}
#Entity
#Table(name="ADDRESS")
public class Address extends BaseDTO{
}
public class BaseDTOEventListener {
#PrePersist
public void onPreInsert(BaseDTO baseDTO){
baseDTO.setCreationUser("TEST");
baseDTO.setCreationDate(new Date());
}
}
Thanks to Alan for the hint, by specifying column names on each object as below. This worked :)
#Entity
#AttributeOverrides({#AttributeOverride(name="creationUser", column=#Column(name="PER_CREATION_USER", insertable=true, updatable=false)),
#AttributeOverride(name="creationDate", column=#Column(name="PER_CREATION_DATE" insertable=true, updatable=false})
#Table(name="PERSON")
public class Person extends BaseDTO{
}
Use the #Embeddable in combination with #MappedSuperClass:
First define the BaseDTO interface:
#EntityListeners(BaseDTOEventListener.class)
#MappedSuperclass
public abstract class BaseDTO {
public abstract getAuditEmbeddable();
public void setCreationDate(Date date){
getAuditEmbeddable().setCreationDate(date);
}
public void setCreationUser(String user){
getAuditEmbeddable().setCreationUser(user);
}
}
Then define the embeddable which will hold the audited fields.
User most common column names here.
#Embeddable
public class AuditEmbeddable{
#Column(name = "creationUser")
private String creationUser;
#Column(name = "creationDate")
private Date creationDate;
public String getCreationUser() {
return creationUser;
}
public void setCreationUser(String creationUser) {
this.creationUser = creationUser;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
}
Then you inject an embedded to each of the audited entities, overriding the column names where necessary:
#Entity
#Table(name="PERSON")
public class Person extends BaseDTO{
#Embedded
private AuditEmbeddable auditEmbeddable;
public AuditEmbeddable getAuditEmbeddable() {
return auditEmbeddable;
}
public void setAuditEmbeddable(AuditEmbeddable auditEmbeddable) {
this.auditEmbeddable = auditEmbeddable;
}
}
#Entity
#Table(name="ADDRESS")
public class Address extends BaseDTO{
// lets say here you have custom names for audit fields
#Embedded
#AttributeOverrides(
#AttributeOverride(name = "creationUser", column = #Column(name = "creationUser123")),
#AttributeOverride(name = "creationDate", column = #Column(name = "creationDate123"))
)
private AuditEmbeddable auditEmbeddable;
public AuditEmbeddable getAuditEmbeddable() {
return auditEmbeddable;
}
public void setAuditEmbeddable(AuditEmbeddable auditEmbeddable) {
this.auditEmbeddable = auditEmbeddable;
}
}
Finally the listener can stay as you wrote it:
public class BaseDTOEventListener {
#PrePersist
public void onPreInsert(BaseDTO baseDTO){
baseDTO.setCreationUser("TEST");
baseDTO.setCreationDate(new Date());
}
}
Hope that helps.
You can use hibernate envers for the same purpose. You can annotate with #Audited. Apply #NotAudited to the entities you do not want to be
#Entity
#Table(name="PERSON")
#Audited
public class Person extends BaseDTO{
}
#Entity
#Audited
#Table(name="ADDRESS")
public class Address extends BaseDTO{
}
I need to override a getter of an entity object, on which the db column is defined, in it's superclass, so I can add additional annotations.
Example:
#MappedSuperclass
public class Person {
String name;
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Entity
#Table(name="employee")
#XmlType(name="employee")
public class Employee extends Person {
#Override
#XmlAttribute(name = "name")
public String getName() {
return super.getName();
}
}
Class Person contains common attributes for several entities. Class Employee extends person and defines a database table (table per class strategy). I also want to serialize class Employee to XML, so I need to add additional annotations to its getters, and therefore I'm overriding them.
The problem is that when I try to run this code, I get a Hibernate MappingException saying: Duplicate property mapping of name found in Employee.
Is there a way to tell Hibernate that the name getter in Employee is not duplicate but just overriden (perhaps with some annotation). Or is there another way to do what I need?
Try adding #Transient to the overriding property:
#Entity
#Table(name="employee")
#XmlType(name="employee")
public class Employee extends Person {
#Override
#XmlAttribute(name = "name")
#Transient
public String getName() {
return super.getName();
}
}
this is untested code but i hope it will work, use #AttributeOverride annotation like this
#Entity
#Table(name="employee")
#XmlType(name="employee")
#AttributeOverride(name = "name", column = #Column(name = "name"))
public class Employee extends Person {
#Override
#XmlAttribute(name = "name")
public String getName() {
return super.getName();
}
}