Hibernate delete orphan on replacing OneToOne relationship - java

I have the following entity:
#Entity
#Getter
#Setter
#ToString
public class Team extends EntityBase {
......
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY,
orphanRemoval = true, optional = false)
#JoinColumn(name = "auth_id")
private TeamAuthentication auth;
......
}
so it has a TeamAuthentication table reference. The latter entity looks as follows:
#Entity
#Getter
#Setter
#ToString
public class TeamAuthentication {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String accessToken;
}
What I want is that when I fetch existing Team entity from the table and replace a reference to TeamAuthentication table there for field auth, then persist this Team entity with teamRepository.save(), I want that old TeamAuthentication would be deleted from its table. At the moment it stays in the table and becomes sort of a loitering entry that won't be ever used or queried.
How can I leverage Hibernate cascade in deleting OneToOne reference on change?

Related

JPA #OneToMany and #ManyToOne: back reference is null though mappedBy is included

I have 4 Entities, that a related to each other with #OneToMany relationships.
When I try to save Order that contains OrderItem - Orderitem has no backreference.
In the code below only important fields are showed for brevity ( usual strings and primitives are omitted ). I decided to include Dish and User Entities also.
Order:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
private List < OrderItem > orderItems;
}
Dish:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Dish {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "dish")
#ToString.Exclude
private List < OrderItem > orderItems;
}
OrderItem:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#ToString.Exclude
private Dish dish;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
private int quantity;
}
User:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List < Order > orders;
}
The problem happens when I try to save Order with Spring data JPA.
Let's print Order to see OrderItem before saving.
public Order saveOrder(Order order) {
System.out.println("SERVICE saving order " + order);
return orderRepository.save(order);
}
As you can see, orderItems backreference is null before saving ( I though spring data jpa should deal with setting it ).
SERVICE saving order Order(id=0,
orderItems=[OrderItem(id=0, quantity=2, order=null)])
Here is what I have in DB ( Order and OrderItem entities ).
In your OrderItem class, add annotation below:
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST}, fetch=FetchType.LAZY)
#JoinColumn(name="order_id", referencedColumnName="id", nullable = false)
Order order.
One more thing, I suggest you use SEQUENCE_GENERATOR, beacause IDENTITY means: I'll create the entity with a null ID and the database will generate one for me. I don't think Postgres even supports that, and even if it does, a sequence generator is a better, more efficient choice.
The best option that I found for this is doing something like:
order.getOrderItems().forEach(orderItem -> orderItem.setOrder(order));
Before your save() call. Even though order is not persisted at this point, it seems like Hibernate can resolve the relation and the back references will be set correctly.
If you do not want to bother setting the back reference in your business logic, you can add something like this to your entity:
class Order {
...
#PrePersist
public void prePersist() {
setMissingBackReferences();
}
private void setMissingBackReferences() {
orderItems.forEach(oderItem -> {
if (oderItem.getOrder() == null) {
oderItem.setOrder(this);
}
});
}
...
}

Spring Boot JPA, data do not persist in join table of many-to-many relationship

I need some help/advice since I am new to Spring Boot. I am trying to make many-to-many relationship with the help of linking table. But for some reason, I can not persist data in the link table.
Here are the entities:
#Table(name = "providers")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class ProviderEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long providerId;
#OneToMany(mappedBy = "provider", fetch = FetchType.LAZY)
#JsonManagedReference
private List<ProviderPractitionersEntity> providerPractitioners;
...
-------------------------------------------------------------
#Entity
#Table(name = "company_practitioner_types")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class CompanyPractitionerTypeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "practitioner", fetch = FetchType.LAZY)
#JsonManagedReference
private List<ProviderPractitionersEntity> practitionerProviders;
...
}
---------------------------------------------------------------
#Entity
#Table(name = "provider_practitioners")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class ProviderPractitionersEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#ManyToOne
#JsonBackReference
#JoinColumn(name = "practitioner_id") /*this column is foreign key from practitioner table*/
private CompanyPractitionerEntity practitioner;
#ManyToOne
#JsonBackReference
#JoinColumn(name = "provider_id") /*this column is foreign key from provider table*/
private ProviderEntity provider;
#Column(name = "size")
private String size;
}
When I am persisting new Provider, I set this new provider object's reference and practitioner object's reference in every ProviderPractitioner object before persisting.
As the result, objects from List providerPractitioners have null values for all states. And nothing is persisted to provider_practitioners table in the database.
The reason I am trying to set many-to-many relationship this way, instead of using #ManyToMany annotation is because of the "size" variable in ProviderPractitionerEntity which contains number of one type of practitioners for one provider.
I have tried to create embededId (composite ID) for linking table, and got the same result.
********* UPDATE ***********
I created embededId class as suggested:
#Embeddable
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ProviderPractitionersId implements Serializable {
#Column(name = "practitioner_id")
private Long practitionerId;
#Column(name = "provider_id")
private Long providerId;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ProviderPractitionersId)) return false;
ProviderPractitionersId that = (ProviderPractitionersId) o;
return Objects.equals(getPractitionerId(), that.getPractitionerId()) && Objects.equals(getProviderId(), that.getProviderId());
}
#Override
public int hashCode() {
return Objects.hash(getPractitionerId(), getProviderId());
}
}
and added it to the join Entity:
#Entity
#Table(name = "provider_practitioners")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class ProviderPractitionersEntity {
#EmbeddedId
private ProviderPractitionersId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("practitionerId")
#JoinColumn(name = "practitioner_id")
private CompanyPractitionerEntity practitioner;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("providerId")
#JoinColumn(name = "provider_id")
private ProviderEntity provider;
#Column(name = "size")
private String size;
}
This is my join table in DB
create table provider_practitioners
(
practitioner_id integer,
provider_id integer,
size varchar,
PRIMARY KEY (practitioner_id, provider_id),
FOREIGN KEY (practitioner_id) REFERENCES company_practitioners (id)
ON DELETE SET NULL,
FOREIGN KEY (provider_id) REFERENCES providers (provider_id)
ON DELETE SET NULL
);
I am setting values to each ProviderPractitionersEntity (Id, provider reference, practitioner reference, size value) Before, I did not set the ID object.
public Set<ProviderPractitionersEntity> dtoToPractitionersEntity(final ProviderDto providerDto,
final ProviderEntity providerEntity) {
final Set<ProviderPractitionersEntity> providerPractitionersEntities = new HashSet<>();
//Iterate through passed list of practitioner types for given provider
providerDto.getPractitioners().forEach(practitionerDTO -> {
final ProviderPractitionersEntity providerPractitioner = new ProviderPractitionersEntity();
//Check if there is current practitioner type in the codebook table with practitioner types
final CompanyPractitionerEntity practitioner = companyPractitionerRepository.findByPractitionerId(practitionerDTO).orElseThrow();
//add provider-practitioner set's reference to practitioner object
practitioner.setPractitionerProviders(providerPractitionersEntities);
//add current practitioner reference to ProviderPractitioner instance
providerPractitioner.setPractitioner(practitioner);
//add provider reference to ProviderPractitioner entity instance
providerPractitioner.setProvider(providerEntity);
//add values to the key and add key to the ProviderPractitioner instance
final ProviderPractitionersId providerPractitionersId = new ProviderPractitionersId(practitioner.getId(), providerEntity.getProviderId());
providerPractitioner.setId(providerPractitionersId);
//set size
providerPractitioner.setSize("5-10");
When I try to persist Provider object, now I get javax.persistence.EntityNotFoundException: Unable to find providers.model.entity.ProviderPractitionersEntity with id ProviderPractitionersId(practitionerId=2, providerId=0).
providerId is 0 at this point because provider object is not persisted yet. Why is it trying to fetch it? Is it because I set up the key value?
You have to create the #EmbeddedId as you mentioned, and then in the ProviderPractitionersEntity attributes (ProviderEntity and CompanyPractitionerTypeEntity) add #MapsId with the name of the property in the composite id.
So first create the composite id:
#Embeddable
public class ProviderPractitionersId implements Serializable {
#Column(name = "practitioner_id")
private Long practitionerId;
#Column(name = "provider_id")
private Long providerId;
And then, in the many-to-many entity (ProviderPractitionersEntity) map the id and both entities this way:
#EmbeddedId
private ProviderPractitionersId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("practitionerId")
private CompanyPractitionerTypeEntity practitioner;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("providerId")
private ProviderEntity provider;
And so, in the ProviderPractitionersEntity you can add, as you mentioned, as many properties as you like, like your size or anything else.
Update after comments
As you said, relations are automatically persisted or not depending on the CascadeType you specify. If none is specified you have to manually persist all entities (including the middle table) and at the correct order. First the Provider and the Practitioner have to exist in their tables in order for the ProviderPractitioner to get inserted, as this has foreign keys to those tables. So you either fine tune the CascadeType at your needs, or perform all the inserts manually in the correct order. That depends on your specific business cases.

Composite primary key table not populating in related entity

My database contains a table with a composite-primary-key such that one of the keys is a foreign key, and the other is supposed to be used to get an entity from an external service. The code looks somewhat like this:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
public class PrimaryKey implements Serializable {
#Column(name = "A_ID")
private Long aId;
#Column(name = "F_ID")
private Long fId;
}
#Entity
#Table(name = "JOIN_ENTITY")
#Getter
#Setter
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class JoinEntity {
#EmbeddedId
private PrimaryKey pk;
#MapsId("aId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "A_ID")
private EntityA a;
public getFId() { return pk.getFId(); }
}
#Entity
#Table(name = "ENTITY_A")
#Getter
#Setter
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class EntityA implements Serializable {
....
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true)
List<JoinEntity> list = new ArrayList<>();
}
When I get a JoinEntity saved and try to get EntityA from the database the list is not being populated, but if I get some JoinEntity from the database the related EntityA is recovered correctly. What do I do to get the JoinEntity list to be recovered with the EntityA?
You need to use FetchType.EAGER on the #OneToMany association in the EntityA class:
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
List<JoinEntity> list = new ArrayList<>();
This way when you retrieve a EntityA from the database, its JoinEntitys will be automatically retrieved.
Solved the problem adding an ENTITY_F table with the id and switching to a simple ManyToMany relationship.

OneToOne relationship, keep only foreign key

I am trying to establish a OneToOne relationship between two entities (PartnerDetails and JWTData. How ever, I only want to store the primary key of PartnerDetails entity in JWTData, not the whole object, like this.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "partnerId")
private JWTData jwtData;
}
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(targetEntity = PartnerDetails.class)
#JoinColumn(name = "partner_id", foreignKey = #ForeignKey(name = "fk_jwt_partnerdetails_partnerid"))
private String partnerId;
#NotBlank
private String secret;
}
But after fetching the JWTData using repository, Hibernate cannot convert the String to a PartnerDetails. Can this be done using any other way?
If you just add PartnerDetails to JWTData then JPA will know to use only the id. JPA is an Object Oriented framework so you should reference objects unless you specifically want a field. JPA handles the details for you. Note that in this configuration JWTData in the "owning" entity because of the mappedBy annotation, therefore only setting the partnerDetails field in a JWTData instance will persist the relationship to the database. The jwtData field in PartnerDetails is for query results only and makes for a Bidirectional instead of a Unidirectional mapping. Also, because of this, having a CascadeType setting generally only makes sense on the owning entity since it is the one handling the database updates and deletes.
When playing around with JPA be sure to turn on the SQL output so that you know what is actually happening.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(mappedBy = "partnerDetails")
private JWTData jwtData;
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// even though it looks like the entire class it's only saving the id to the database.
#OneToOne
private PartnerDetails partnerDetails;

Mapping one to many relationship in separate table

I am trying to develop a system for managing dormitories. Since I don't have much experience with databases, I am stuck on a problem and I have a solution but I am not sure if this would be the right approach.
So I have Room and User. Each user can be accommodated in one room, but one room can accommodate more users. I would like to manage this relationship in one entity - Accommodation. Here I would have more properties, like start/end Date, etc.
I am using Hibernate to map the tables. From what I've read, persisting Collections from Java can be done in two ways, either by #OneToMany or by #ElementCollection. I am not quite sure if I should define this relationship in the Room entity or in the Accommodation entity? If I do it in the room entity then the Accommodation would hold just fk from the room/user tables?
Also, is it possible to only fetch the primary key when doing one-to-many relations instead of getting the whole object? I know that FETCH.LAZY does this, but in my Accommodation entity ideally I would want to store only Set studentsIds.
Thank you in advance.
#Table(name = "student")
#AllArgsConstructor
#Data
#Embeddable
#NoArgsConstructor
#javax.persistence.Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "role")
private String role;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "room", nullable = false)
private Room room_number;
}
Here is the Room entity
#javax.persistence.Entity
#Table(name = "room")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Room
{
#Id
#Column(name = "room_number")
private Long roomNumber;
#Column(name = "location_address")
private String locationAddress;
#Column(name = "dormitory_name")
private String dormitoryName;
}
Accommodation entity
#javax.persistence.Entity
#Table(name = "accommodation")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Accommodation extends Entity {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "room_number")
private Room room_number;
#OneToMany(mappedBy = "room_number", // I am not sure about this
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true) private List<Student> students;
#Column(name = "date_from")
private Date dateFrom;
#Column(name = "date_to")
private Date dateTo;
}

Categories