How to update other table when inserting with OneToOne using Spring JPA - spring-boot

I have two tables joined with a OneToOne relationship, one side exists in the database. When I insert the other side I want the first side's foreign key column to update so that it knows about the relationship without having to do it manually. Is this possible.
Here's my simplified example, I am using #MappedSuperclass because I have some shared fields in most of my Entities I included it here just in case it's causing an issue.
The base entity
#MappedSuperclass
#Data
public abstract class BaseEntity {
//defines some common fields I have in all my entities such Id
}
Abstract Image class
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
#Data
public abstract class Image extends BaseEntity {
//defines some other fields
public abstract UUID getTypeId();
}
UserProfilePhoto
#Entity
#Data
public class UserProfilePhoto extends Image {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userProfilePhoto")
private Profile profile;
#Override
public UUID getTypeId() {
return user.getId();
}
}
Profile
#Entity
public class Profile extends Base {
//defines some other fields
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn()
private UserProfilePhoto profilePhoto;
}
I'm looking for the following behavior:
When UserProfilePhoto is saved to image table (with Profile ID) the corresponding ImageId is updated in Profile
When Profile is Deleted the corresponding UserProfilePhoto is deleted from image table.
When UserProfilePhoto is deleted Profile remains but the foreign key column is nulled.
From researching this I think it's possible but it's a matter of getting the annotations correct. I've tried different combinations with no luck. Is what I'm looking for even possible?

No, it is not possible the way you describe it.
Any bidirectional relationship in JPA is controlled exclusively by the side indicated by mappedBy, so you need to update that side in your code, in order to have it persisted.
You can do that by invoking the other side in the setter, or by editing the other side in the first place.

Related

Hibernate - #ManyToOne on Entities that extends from Abstract class

I have a similar structure already existing in my database and it has many data:
#MappedSuperclass
public abstract class Person {
#Id
Long id;
#Column(name="COMMONFIELD")
String commonField;
}
#Entity
#Table(name = "WOMAN")
public class Woman extends Person {
.....
}
#Entity
#Table(name = "MAN")
public class Man extends Person {
.....
}
This code will work with two different tables WOMAN and MAN where both will contain the common fields defined in the abstract class Person.
Now I will like to have a class Car that has an owner which can be either a woman or a man. A person can also be the owner of several cars. So I declare it like this:
#Entity
#Table(name = "CAR")
public class Office {
....
#ManyToOne
#JoinColumn(name = "IDPERSON")
Person owner;
}
The issue I encounter is that I cannot create an attribute linked to a Mapped-Superclass.
Of course, I could solve this issue simply by defining Person as an entity and declaring it to be InheritanceType.SINGLE_TABLE and Woman and Man with a discriminator this way they could share the same table and it will work. However, this solution is not possible for me because the data is already there and there are many dependencies from it, so I cannot change the WOMAN or MAN table.
Is there a way to use a discriminator in this case too, so that Hibernate will know in which table it has to look for the person?
Thank you very much in advance.
So I couldn't find any way to tell hibernate to use a discriminator on a specific attribute.
So what I did, I created a Gender Enum
enum Gender {
WOMAN, MAN;
}
And I added it as an attribute of a Car. I also wired the woman and the man service and created a getter method for the owner that will decide which service to use based on gender.
#Entity
#Table(name = "CAR")
public class Office {
....
#Column(name = "PERSONID")
Long personId;
#Column(name = "GENDER")
Gender gender;
#Autowired
#Transient
WomanService womanService;
#Autowired
#Transient
ManService manService;
Optional<Person> getOwner(){
switch(gender){
case WOMAN: return womanService.findById(personId);
case MAN: return manService.findById(personId);
default: return Optional.empty();
}
}
}
I'm aware this solution sucks but it's the best I could come up with to avoid redefining other entities.

how to write the JpaRepository for tables which has composite keys

Please refer attached screenshot to understand the table structure.
Empd_Id is the primary key in 'Employee' table which in turn becomes as a part of composite key along with 'product_id' in table called 'product'.
Any employee can have multiple products so in that case it becomes 'One-to-Many' relationship between 'Employee-Product' tables. Now I'm confused whether I need to write just 1 JpaRepository interface i.e. for employee or 2 JpaRepository interfaces (1 for Employee and another for Product). My gut feeling is just 1 interface for Employee table but how???
Following is my code snippet:-
1st JPA repository interface
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
Entity:-
#Entity
#Table(name="product")
public class Product{
#EmbeddedId
private EmpProd empProd;
#Column(name="product_name")
private String commerceUserId;
#Column(name="description")
private String description;
For composite keys:-
#Embeddable
public class EmpProd implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull
#Column(name="emp_id")
private String empId;
#NotNull
#Column(name="product_id")
private String productId;
2nd Jpa repository interface
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
Entity class:-
#Entity
#Table(name="employee")
public class Employee{
#Id
#NotNull
#Column(name="emp_id")
private String empId;
#Column(name="first_name")
private String firstName;
Though, I have written 2 separate JPA repositories, I strongly believe there will be need for just 1, the main one i.e.
public interface MyMainDataRepository extends JpaRepository {
}
But I do not know to related both entity classes and fetch data from using single Jpa repository as I'm new to Spring Data JPA. I would really appreciate if someone can help me here. Thanks
The two entities Product and Employee don't have any connection as far as JPA is concerned. Therefore you can't access both through a single repository.
If for example, Product would have an actual reference to an Employee you could use a ProductRepository to load Products and navigate from there to the referenced Employees.
But even if that might be feasible, I'd guess that Product and Employee should be considered different aggregates and therefore, should have their own repository each. See Are you supposed to have one repository per table in JPA? for more information on that question.
Given the entities, your repositories look just fine. Note that the entities do look atypical due to the use of String productId instead of Product product.
If you wanted to fetch the employee details, you need the following interface,
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
If you wanted to fetch the product details, you need the following interface,
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
The employee is related to product table, the iteration happens via product and related employees. From this, you can not access the employee table directly and retrieve the employee results from MyRepository interface.

Save data to database via Spring Data repository

I am planning to store data from multiple tables which has one to many JPA relationship. I am creating my Repository interface which extends from JPARepository. My question is If I want to save a data on Many sides of relationship (in the below scenario it's Tour) then shall I do with TourRepository or PersonRespository?
On a similar note Is it ideal to create individual repository classes for every JPA entities where data need to be stored? or any better way with limited repository classes the data store to database can be achieved?
#Entity
#Table(name="Person")
public class Person implements Serializable{
...
...
#OneToMany(mappedBy = "person")
private List<Tour> tours;
...
#Entity
#Table(name = "Tour")
public class Tour implements Serializable{
...
...
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
...
You have two independent entities. Person can exist without Tour and Tour can exist without Person. So you should have two repositories - for Person and Tour to store their data independently:
Tour tour1 = new Tour("tour1");
tourRepo.save(tour1);
Person person1 = new Person("person1");
person1.addTour(tour1);
personRepo.save(person1);
You chose the bidirectional one-to-many association so you have to use a 'helper' method like addTour to link both entities:
public Person addTour(Tour tour) {
tour.setPerson(this);
this.tours.add(tour);
return this;
}
Additional info: Best Practices for Many-To-One and One-To-Many Association Mappings
Add cascade to tours:
#OneToMany(mappedBy = "person", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Tour> tours;
When you save person object, his tours will be saved automatically.
By the way, in Person class, you should have an addTour(...) utilities method like this:
// Person.java
public void addTour(Tour tour){
this.tours.add(tour);
tour.setPerson(this);
}
I would suggest you to use CascadeType.ALL on #OneToMany mapping in Person entity:
#OneToMany(mappedBy = "person",cascade = {CascadeType.ALL})
private List<Tour> tours;
And then create repository for person to save person object with the list of tours .
CascadeType.ALL means persistence will propagate all EntityManager operations like PERSIST, REMOVE, REFRESH, MERGE, DETACH to the relating entities.

JPA Many to many unidirectional

I use spring data jpa and i try to do a many to many unidirectional relation.
#Entity
public class Appartment {
...
#ManyToMany
private List<AppartmentFeatureOption> featureOption;
}
#Entity
public class AppartmentFeatureOption {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long appartmentFeatureOptionId;
private String name;
private BigDecimal value;
}
My database is created at run time, but i get this error
org.hibernate.DuplicateMappingException: Same physical table name [appartment_feature_option] references several logical table names: [AppartmentFeatureOption], [Appartment_AppartmentFeatureOption]
Any idea?
Edit with this code that work
#ManyToMany
#JoinTable(name="appartment_feautre_option_appartment", joinColumns=#JoinColumn(name="appartment_id"), inverseJoinColumns=#JoinColumn(name="appartment_feautre_option_id"))
private List<AppartmentFeatureOption> featureOption;
Is this is actually your real code, maybe the issue is that you are using a ManyToMany relationship between Appartment and AppartmentFeatureOption whereas there is no link to Appartment in the AppartmentFeatureOption.
From my understanding for one Appartment you want to have several AppartmentFeatureOption, which is a OneToMany relationship.

Retrieve entity auto generated Id

I am trying to find a way to retrieve the auto generated Id of an entity that is persisted in the database via cascade. I am using Hibernate 4.1.9, Spring data 1.2 and Spring framework 3.2.1. Here are the entities in question : Location, Home, Room.
Location parent class
#Entity
#Table(name = "location")
#Inheritance(strategy = InheritanceType.JOINED)
public class Location implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "location_id", unique = true)
private long uuid;
// other attributes and methods not relevant
}
Home class extending a Location, referencing a set of Rooms
#Entity
#Table(name = "home")
#Inheritance(strategy = InheritanceType.JOINED)
#PrimaryKeyJoinColumn(name = "home_id")
public class Home extends Location implements Serializable
{
#OneToMany(mappedBy = "containingHome", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Room> rooms;
// other attributes and methods not relevant
}
and finally the Room class referencing a Home object
#Entity
#Table(name = "room")
#PrimaryKeyJoinColumn(name = "room_id")
public class Room extends Location implements Serializable
{
#ManyToOne()
#JoinColumn(name = "home_id")
protected Home containingHome;
// other attributes and methods not relevant
}
I am using Spring data to create Repositories for the entities.
LocationRepository
public interface LocationRepository extends JpaRepository<Location, Long>
{ }
The problem I am having is that I need the id in order to be able to retrieve the different objects from the database and that is generated automatically. The only way I can access the id through the element is if I get the managed object when I save it to the database. But if I try to save each location in turn like so:
Home home = new Home();
home = locationService.save(home) // service that just calls locationRepository.save method
Room bedroom = new Room(home);
bedroom = locationService.save(bedroom);
I get a duplicate entry of room in the database which I think is related to a Hibernate issue https://hibernate.onjira.com/browse/HHH-7404. If I just call
Home home = new Home();
Room bedroom = new Room(home);
locationService.save(home)
there are no doubles but I have no way to retrieve the room object since it was persisted on cascade and its id is 0. Is there a way to solve this without introducing other fields in the location like a unique name that I have to generate myself? Any help is much appreciated.
Edit
If in the last case I have home = locationService.save(home) and then call home.getUuid() I get the right value which is normal I think since I retrieve a managed object. But if I do bedroom.getUuid() I get 0 since bedroom is not managed and so it has not had its id field updated with the value from the database.
Have you tried calling home.getUuid(); (assuming you have a getter for that field) after the persist call?
You might be surprised, but Hibernate (and JPA) will update the in memory copy with the id.

Resources