Delete orphans when deleting parent manyToOne annotaion - java

I have two entities, related as below
#Entity
#Table(name = "APPOINTMENT")
public class Appointment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long codeAp;
#ManyToOne(fetch = FetchType.EAGER)
, #OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "codeP")
private Patient patient;
//attributes
//getters and setters
//constructors
#Entity
#Table(name = "PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long codeP;
//attributes
//getters and setters
//constructors
I'm using JpaRepository delete method.
There is a constraint between the tables PATIENT and APPOINTMENT in database,
I want to remove orphans, when I remove Patient.
I added #OnDelete hibernate annotation but it doesn't work for me!
Can you please tell me why?
I want to keep that unidirectional relationship, can you please help me in this?

If you want to keep using the association as unidirectional only, you can define the lazy-loaded inverse side in a field without exposing getters and setters for it:
#Entity
public class Patient {
#OneToMany(mappedBy = "patient", orphanRemoval = true)
private Collection<Appointment> appointments;
}
This way orphanRemoval logic is applied from patients to their appointments and as a bonus you get the ability to navigate from patients to appointments in HQL queries.
Notice the mappedBy attribute which tells that appointments are responsible for the association management, so you continue to associate appointments with patients by setting patients in the many-to-one relation defined in the Appointment.

There is no way that you could achieve that automatic behavior on the #ManyToOne side. Its just semantically incorrect, period.
Taking under consideration though, the fact that you only want to have an uni-directional mapping and do not specify the Set<Appointment> dependency on the Patient, then a kind of workaround to your situation would be to replace the #ManyToOne with a #OneToOne relationship. Then you would be able to use orphan-removal functionality:
#OneToOne(fetch = FetchType.EAGER, orphanRemoval=true)
#JoinColumn(name = "codeP")
private Patient patient;
Keep in mind though that if you follow this path, adapt you code and at some point you will be in need to introduce #OneToMany dependency on the `Patient' side then you will stumble upon problems. So i would recommend working out pros and cons first in relation to future possible alteration to the entity graph.

Related

Duplication with CascadeType

I'm a little bit confused right now. I have three tables: specialPrice, partner, product.
In the specialPrice I can store discount for products which will available for the selected partner. So it has only three columns. Partner and product table and they row is referenced from the specialPrice table.
These three tables are represented as entities in my Java application as well. Here is my problem: if I want to store one specialPrice I got the following exception:
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: 0.
the error message is helpful, I have to use CascadeType, okay. BUT if I use the CascadeType.ALL the specialPrice will be created (so no more java.lang.IllegalStateException), but in the product and in the partner table the selected partner and the product will be duplicated... I don't understand how is this possible?
specialPrice Entity:
#Basic(optional = false)
#NotNull
#Column(name = "DISCOUNT_RATE")
private int discountRate;
#JsonBackReference
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Product productId;
#JsonBackReference
#JoinColumn(name = "PARTNER_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Partner partnerId;
reference from partner Entity:
#JsonIgnore
#OneToMany(mappedBy = "partnerId")
private Collection<SpecialPrice> specialPriceCollection;
(same for the product)
Using EclipseLink (JPA 2.1)
Can someone help me, what am I doing wrong? I don't want to duplicate the selected partner and product...
Thank you!
I understand Partner and Product are both pre-existing entities?
In such case, try the following code in your service method:
specialPrice.setPartner(partnerRepository.getOne(specialPrice.getPartnerId().getId());
specialPrice.setProduct(productRepository.getOne(specialPrice.getProductId().getId());
specialPriceRepository.save(specialPrice);
As a side note, using CascadeType.ALL with #ManyToOne is almost never a good idea.

JPA Many-to-Many relation: delete child entity not possible with active relation

In a db, I have User and Role entities. The share a many-to-many relation as a Role entity can be assigned to multiple User entities and on the other hand a User entity can be assigned to multiple Role entities.
My entity classes look like this
UserEntity
#Entity
public class UserEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true, nullable = false)
private String username;
#ManyToMany
private Set<RoleEntity> roles;
...
}
RoleEntity
#Entity
public class RoleEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true, nullable = false)
private String name;
#ManyToMany(mappedBy = "roles")
private Set<UserEntity> users;
...
}
With this configuration I am able to map the entities with each other. I am also able to delete a User entity. But I am not able to delete a Role entity as long as a relation exists.
If I add cascade = CascadeType.REMOVE the Rolegets deleted, but with it the User too of course.
To only way to get this working currently is to define a #JoinTable on both sides. But this seems more like a workaround. What am I doing wrong? As this is a regular use case, there got to be solution to this, although I haven found it yet...
You need the join table, it's not a work around. Remember you are mapping your object oriented model to a relational model. The only way to express many-to-many relationship in the relational model is defining a #JoinTable.
UPDATE: Adding comment in the answer
You sould define the #JoinTable just in one entity, for example UserEntity and mappedBy="roles" in RolesEntity inherits the definitions of #JoinColumn and #JoinTable names.
Then you need to define the cascade operations you want to perform in both sides of the relationship.
In RoleEntity
#ManyToMany(mappedBy = "roles")
private Set<UserEntity> users;
In UserEntity
#ManyToMany
#JoinTable(...)
private Set<RoleEntity> roles;

Hibernate envers: RelationTargetAuditMode.NOT_AUDITED vs #NotAudited

I try to audit an entity but I don't want to audit its relationships. If I put #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) in #ManyToOne relations, this works and I don't have any exception, but when I try to use the same annotation in a #onetomany with the param mappedby defined, I have an exception that says me that I have to audit the other entity.
Example:
#Table(name = "OWNERS")
#Entity
#EntityListeners(AuditingEntityListener.class)
#Audited
public class Owner {
...
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(fetch=FetchType.LAZY)
private User user;
...
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "owner" )
private Set<Pet> pets = new HashSet<Pet>();
...
}
When you use #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) you are telling hibernate not to audit this entity but audit the relation so you hibernate will save the id of the referenced entity. Thats why Pet must be an #Audited entity.
If you do not want to store the relation at all you need to use #NotAudited
Check this Whats the difference between #NotAudited and RelationTargetAuditMode.NOT_AUDITED in Hibernate EnVers?
Well, I think you have two options here:
Actually audit the entity Pet, if applicable;
Use the annotation #NotAudited instead of #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Think about it, The audit table for Owner doesn't have to persist the Pet's associated. If it does, use option 1.
Hope it helps!

Annotation to join columns with OR condition instead of AND condition

I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.

Hibernate. Several foreign keys in one table

Have next tables structure in SQL schema :Clients, Employees, Orders.
And 3 Entity classes in java code accordingly : Client, Employee, Order.
Both primary id fields from Clients and Employees are in Orders table as foreign keys.
Question is how it should be displayed in java code?
As I understand here it should be done smth like adding Set field to Clients and Employees annotated with #OneToMany.
But what should be done in Order Entity and maybe I have to add any additional annotations except #OneToMany?
I think you have some misconceptions about the relational mapping of Hibernate.
If in fact your Orders table have foreign keys of Clients and Employees, then the annotation you are looking for is #ManyToOne
#OneToMany annotation is used when your entity have multiple records referenced by the targeted entity, while #ManyToOne is used when your entity have only one record referencing the targeted entity.
For example:
Orders entity have one reference from Clients and one reference from Employees entities.
In this case, Orders entity could be mapped by the following way:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
#ManyToOne
private Employee employee;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
#Entity
#Table(name = "Employees")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
With the example given above you should be able to make your schema work fine with Hibernate, but for the sake of understanding, let's imagine a scenario where you would need to get all the Orders from a Client, of course you could do it with a query selecting only the Orders inside the Client table, however Hibernate offers the #OneToMany annotation which will give you the possibility to access all the Orders from a Client without the need of a separate query, only by mapping! Let's see an example:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
#OneToMany(mappedBy = "client")
private List<Order> orders;
//getters and setters
}
In this example you should be able to get all the Orders from a Client just by calling the get of the "orders" attribute. Please, note that on the #OneToMany mapping we have specified the "mappedBy" attribute as "client", it was needed because we have a bidirectional mapping between Client and Order, in a simple usage of #OneToMany you would not need this mapping.
Important: When working with #OneToMany mapping you would eventually face some lazy fetching problems, in this case I highly recommend you to take a look at this question:
Solve “failed to lazily initialize a collection of role” exception
Also, I think you should start reading more about Hibernate to understand about it's basic concepts, please, check this other question about #OneToMany and #ManyToOne annotations on Hibernate:
Hibernate/JPA ManyToOne vs OneToMany

Categories