I have 3 Entities:
#Data
#AllArgsConstructor
#Entity
#Table(name = "beneficiary")
#Inheritance
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "type")
public abstract class Beneficiary {
public Beneficiary() {}
#Id private String id;
private String description;
}
#Data
#Entity
#DiscriminatorValue("company")
#EqualsAndHashCode(callSuper = true)
public class BeneficiaryCompany extends Beneficiary {
public BeneficiaryCompany() {
super();
}
public BeneficiaryCompany(String id, String description) {
super(id, description);
}
}
#Data
#Entity
#DiscriminatorValue("person")
#EqualsAndHashCode(callSuper = true)
public class BeneficiaryPerson extends Beneficiary {
public BeneficiaryPerson() {}
public BeneficiaryPerson(String id, String description) {
super(id, description);
}
}
An in the other class I want to have 2 separate collections:
#Data
#AllArgsConstructor
#Entity
#Table(name = "transaction")
public class Transaction {
public Transaction() {}
#Id private String id;
private String description;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = BeneficiaryCompany.class)
#JoinColumn(name = "transaction_id", nullable = false)
private Set<BeneficiaryCompany> beneficiaryCompanies;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true,targetEntity = BeneficiaryPerson.class)
#JoinColumn(name = "transaction_id", nullable = false)
private Set<BeneficiaryPerson> beneficiaryPeople;
}
The problem is that every Beneficiary was fetched into beneficiaryCompanies, and beneficiaryPeople in the debugger tells me that:
Unable to evaluate the expression Method threw
'org.hibernate.WrongClassException' exception.
The database records looks fine (DiscriminatorColumn was created). What could be the problem? Why beneficiaryCompanies contains BeneficiaryPerson objects?
#EDIT:
To fetch the records I am using SpringData JPA repositories.
Use #MappedSuperclass on your base class Beneficiary
Alexandar Petrov is absolutely correct. You have to remove #Entity because superclass is not an entity. When dealing with inheritance extending a class, you can use #MappedSuperclass annotation on the base class, in your case, it is Beneficiary.
Edit:
This is a very good article you can refer to.
Related
My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .
I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();
I'm using a JPARepository called PublicationRepository and want to find all Publications from a certain Person. This Classes are connected over the Class Author.
Person Class:
#Entity
public class Person {
#Id
private String email;
private String telefon;
private String password;
#OneToMany(mappedBy = "person")
Set<Author> Author;
}
Author Class:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Author {
#Id
private int id;
#ManyToOne
#JoinColumn(name="Person_ID")
Person person;
#ManyToOne
#JoinColumn(name="Publication_ID")
Publication publication;
private String Date;
private String Writerstatus;
}
Publication Class
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Publication {
#Id
private int id;
private String publicationname;
#OneToMany(mappedBy = "publication")
Set<Author> author;
}
And the PublicationRepository
public interface ProjektRepository extends JpaRepository<Projekt,Integer> {
}
public interface PublicationRepository extends JpaRepository<Publication,Integer> {
#Query(value = "SELECT pub.* FROM author as auth INNER JOIN publications as pub ON auth.publication_id = pub.id WHERE auth.person_id = ?1", native = true)
List<Publication> findAllPublicationsOfThisPerson(int personId);
}
Try this.
I would also recommend to annotate the entities with their table names:
#Table(name = "publication")
You use a manually build table for a Many-to-Many relationship Author
You could also delegate that to Spring Data Jpa by using #ManyToMany Annotation.
A good tutorial:
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping
Spring boot 2.2.2 ~ 2.2.4 ( the one i have tested so far)
I have an Abstract class
#MappedSuperclass
#Data
#EntityListeners(AuditingEntityListener.class)
#FilterDef(name = "tenantFilter", parameters = {#ParamDef(name = "tenantId", type = "int")})
#Filter(name = "tenantFilter", condition = "tenantId = :tenantId")
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
....
}
A personnel class extending the abstract class
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "personnel")
#AllArgsConstructor
#NoArgsConstructor
#Data
#EntityListeners(AuditingEntityListener.class)
public class Personnel extends BaseEntity {
#NotNull
#OneToOne
#JoinColumn(name = "titleID")
private Title title;
// other fields and relationships of type ManyToOne, OneToMany
}
My Jpa Repository class
public interface PersonnelRepository extends JpaRepository<Personnel, Integer> {
Optional<PersonnelData> findByFileNo(String fileNo);
Optional<PersonnelData> findByIdAndDeletedOnIsNull(Integer id);
}
In my database, I have personnel records with some of the fields being null and the relationships.
In my controller when i do findById(1), it returns null but when I do findByIdAndDeletedOnIsNull(1), it returns the record.
I have tried #NotFound(action = NotFoundAction.IGNORE) on the relationships that are null with no success.
I have a model like this one below:
#Entity(name = "request")
public class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors;
//default constructor, getters and setters
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
public class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
//default constructor, getters and setters
}
#Entity
public class ContactPerson extends Visitor {
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}
But when I try to update a visitRequest by exchanging one of the visitors with a contact person, and try to execute the method on a CRUD repository visitRequestRepository.save(visitRequest); I'm getting this exception:
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.orm.ObjectRetrievalFailureException: Object
[id=null] was not of the specified subclass
[cern.ais.visits.core.domain.visitor.Visitor] : class of the given
object did not match class of persistent copy; nested exception is
org.hibernate.WrongClassException: Object [id=null] was not of the
specified subclass [cern.ais.visits.core.domain.visitor.Visitor] :
class of the given object did not match class of persistent copy] with
root cause
Maybe the problem is that in the database there is the same id used in the contact_person and visitor tables?
How can I solve the problem? I've searched for the solutions but none worked for me.
You're probably not initializing visitRequest reference in ContactPerson. Please take a look at the following configuration, it works with Spring Boot JPA. I have used lombok to generate Getter and Setters.
Here's a working example implemented in Spring Boot https://github.com/ConsciousObserver/SpringBootJpaInheritance
#Data
#Entity(name = "request")
class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors = new ArrayList<>();
}
#NoArgsConstructor
#Data
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
public Visitor(VisitRequest visitRequest) {
this.visitRequest = visitRequest;
}
}
#NoArgsConstructor
#Data
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
#Entity
class ContactPerson extends Visitor {
private String phoneNumber;
public ContactPerson(VisitRequest visitRequest, String phoneNumber) {
super(visitRequest);
this.phoneNumber = phoneNumber;
}
}
I suspect the reason you are having this issue is your annotations #Id and #GeneratedValue are not inherited. If you define property id in ContactPerson you will have a generated Id and will not be a problem anymore.
Try changing ContactPerson class to:
#Entity
public class ContactPerson extends Visitor {
#Id
#GeneratedValue
private Long id;
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}
#Entity(name = "request")
public class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors;
//default constructor, getters and setters
}
#Entity(name = "visitor")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "visitor_type", discriminatorType = DiscriminatorType.STRING, length = 16)
#DiscriminatorValue(value = "visitor")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
public class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
//default constructor, getters and setters
}
#Entity(name = "contact_person")
#Table(name = "contact_person")
#DiscriminatorValue(value = "contact_person")
public class ContactPerson extends Visitor {
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}
And you able to persist ContactPerson like contactPersonDao.save(contactPerson),
you can`t persist field with visitor link.
I am not expert but it work for me.
And you can persist visitor as visitor if he not contactPerson
It seems the id's between different tables has same value, so when hibernate is trying to load an entity with a specific id and if another entity with same id is already present in memory then hibernate is complaining about this issue.