JPA - updating objects in OneToMany-Relations - java

Well I am a little confused because I just can't find any solution on my problem (maybe some kind of a blackout)
Lets say I have the following (simplified) Entities:
#Entity
public class Employee {
#Id
private long id;
#OneToMany(mappedBy="owner")
private Collection<Phone> phones;
}
#Entity
public class Phone {
#Id
private long id;
#ManyToOne
private Employee owner;
}
and I have a phone object managed by JPA. And now i want to change the Employee of that phone object with something like that:
phone.setEmployee(otherEmployee);
I thought that JPA would update the corresponding Employee would be updated too. Am I wrong?
I experimented with the annotations #ManyToOne and #OneToMany (cascade = CascadeType.ALL, orphanRemoval = true) but it doesn't work.
Do I have to update the Employee object myself? Unfortunatly I didn't find that case in any tutorial or other example, so I hope you can tell me more, I am confused. Thanks in advance.
Edit to make it even clearer:
Phone phone = new Phone();
Employee employee = new Employee();
phone.setEmployee(employee);
em.persist(employee);
em.persist(phone);
employee.getPhones(); //the phone object should be included here.

This link is the answer to your question, you should manage relationships by yourself, i. e.:
class Phone {
...
void setEmployee(Employee employee) {
owner = employee;
employee.addPhone(phone);
}
...
}

Related

Java Spring Hibernate JPA PostgreSQL Avoid saving duplicate rows / records

I'm very green when it comes to databases. I feel like this is probably a pretty common database problem, I can't seem to find the correct search terms to find my answer.
My issue is "duplicate" rows in a table. I'm trying to save restaurant food menus in a database, and for the most part its working alright. I have a object called RestaurantWeek, which contains a list of RestaurantDay objects and each day contains a list of RestaurantCourse objects. They get saved as such in the database: image. "weeks_days" and "days_courses" tables are the link between the "weeks", "days" and "courses" tables.
Now the problem comes when many days can have the same "course". Almost every single day has "Salad" as a course, so what ends up happening is I have 12 identical rows in my "courses" table, the only exception being the id column: image. So now the question is, how can I tell JPA or Hibernate to use the existing "Salad" row in my "courses" table instead of inserting a new one every time? Is it possible to do this by adding some specific annotation to my objects or their properties?
I have tried setting the "name" property on "RestaurantCourse" to unique with #Column(unique=true) but then I get errors about hibernate trying to save multiple courses with the same name (since name must be unique). I have tried grabbing the "courses" table when saving data and using the same id multiple times, but then I get errors about hibernate trying to save multiple courses with the same id (since id must be unique).
Is it even possible to fix this "easily", such as with few specific annotation I'm in the unknown about? Do I need to change something else about how my data is saved to the database, such as the classes, the annotations, or the way I'm trying to save?
Here are my classes.
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="weeks")
public class RestaurantWeek {
#Id
private long id;
private Date saveDate;
private String weekName;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantDay> days;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="days")
public class RestaurantDay {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String day;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantCourse> courses;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
#Entity
#Table(name = "courses")
public class RestaurantCourse {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
private String price;
private String type;
#Type(type = "list-array")
#Column(name = "tags", columnDefinition = "text[]")
private List<String> tags;
}
And what I'm using to save:
#Repository
public interface RestaurantMenuRepository extends JpaRepository<RestaurantWeek, Long> {
}
public class RestaurantMenuServiceImpl implements RestaurantMenuService {
#Autowired
private RestaurantMenuRepository restaurantMenuRepository;
#Override
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
return this.restaurantMenuRepository.save(restaurantWeek);
}
}
Thanks in advance.
Yes is posible, you must use existing entity. But use in this method
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) parameter RestaurantWeek is not correct try put this method some dto class with need field to create entity class, additionally pass the parameter to available identify courses entity find which you can doing relationship and add to days entity.
No pass all parameter every time!
Alright, finally found the correct search terms and found the answer. Resolution was a combination of serob's answer and a bunch of googling.
In RestaurantDay I changed #OneToMany to #ManyToMany.
I created repository interfaces for RestaurantDay and RestaurantCourse.
When saving the course, I save the courses first, then the days, and finally the week, while grabbing all the new ids.
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
for (RestaurantDay day : restaurantWeek.getDays()) {
for (RestaurantCourse course : day.getCourses()) {
RestaurantCourse dbCourse = this.restaurantCourseRepository.findCourseByName(course.getName());
if (dbCourse == null) {
course.setId(this.restaurantCourseRepository.save(course).getId());
}
else {
course.setId(dbCourse.getId());
}
}
this.restaurantDayRepository.save(day);
}
return this.restaurantMenuRepository.saveAndFlush(restaurantWeek);
}
Try #NaturalId, this would make your name an Id for the Course entity:
https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/

Query dsl returning duplicate records on #one to many association(leftJoin vs leftJoin.fetch vs fetchAll)

Here is my scenario: i have person entity which looks like below.
#Entity
public class Person{
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<PhoneNumber> phoneNumbers = new HashSet<>(0);
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "AGENCY_ID")
private Agency agency;
I am unable to retrieve correct data,when i query for persons.
Problems i have :
1. duplicate records.
2. person with no agency not returning .
3. Bad performance
Here is what i tried, and see combination of above problems
query.from(qPerson).leftJoin(qPerson.phoneNumbers, telecommNumber).leftJoin(qPerson.agency,qAgency);
I have problem 1: which is obvious(in one-to-many relationship) and this can be solved in direct hibernate by using distinct(). I tried distinct in queryDsl and that doesnt seem to work well.
query.from(qPerson).leftJoin(qPerson.phoneNumbers, telecommNumber).fetch().leftJoin(qPerson.agency,qAgency).fetch();
I have problem 3 in this case: returns results correctly but performance is really bad.(Cartesian product problem, i guess).
query.from(qPerson).fetchAll();
I have problem 2 in this case :This one performs well, but doesnt return person without agency when i try to sort on agency field for example. But returns that person if i dont add below to the query.
query.orderBy(person.agency.agencyIdentifierDescription.asc());
I am trying to arrive at a solution that solves above three problems. Thanks for your help.
Well, you should define your entities as following:
"In JPA a ManyToOne relationship is always (well almost always) required to define a OneToMany relationship, the ManyToOne always defines the foreign key (JoinColumn) and the OneToMany must use a mappedBy to define its inverse ManyToOne."
from Wiki:
ManyToOne
OneToMany
example:
public class Person {
#ID
private Integer id;
#OneToMany(mappedBy = "person")
private Set<PhoneNumber> = phoneNumbers;
#ManyToOne
#JoinTable(name="agency_person", joinColumns={#JoinColumn(name="person_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="agency_id", referencedColumnName="id")})
private Agency agency;
//Getters & Setters
}
//---------------------------------------------------
public class PhoneNumber {
#ID
private Integer id;
#ManyToOne
#JoinTable(name="phonenumber_person", joinColumns={#JoinColumn(name="phone_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="person_id", referencedColumnName="id")})
private Person person;
//Getters & Setters
}
//---------------------------------------------------
public class Agency {
#ID
private Integer id;
#OneToMany(mappedBy = "agency")
private Set<Person> persons;
//Getters & Setters
}

How to set relationship between tables Ebean/Play framework

I'm new to Ebean's world, and I encounter some difficulties to set some relationships between entities.
I have basically two classes, User and Car.
A user can have several cars (so I guess OneToMany) and a car can belongs to one User (so I guess OneToOne).
How can I link these two entities? Here it is what I've done so far
User
#Entity
public class User extends Model{
#Id
#GeneratedValue
public int id;
public String name;
#ManyToMany(cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
}
Car
#Entity
public class Car extends Model{
#Id
#GeneratedValue
public int id;
#OneToOne(cascade = CascadeType.ALL)
public User user;
}
And I get the following error
PersistenceException: Error on models.User.car Can not find mappedBy
property [users] in [models.Car]
Can someone explain me clearly how to use annotations the correct way (very poor documentation), and tell me why I get this error?
You guessed wrong :)
Your User should have a #OneToMany relationship with cars so:
#OneToMany(mappedBy = "user", cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
while your Car should have a #ManyToOne relationship :
#ManyToOne(cascade = CascadeType.ALL)
public User user;
Take care on the mappedBy property in the #OneToMany annotation: you need to tell Ebean where the foreign key lies in the related class.
User
#Entity
public class User extends Model{
#Id
#GeneratedValue
public int id;
public String name;
#OneToMany(cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
}
Car
#Entity
public class Car extends Model{
#Id
#GeneratedValue
public int id;
#ManyToOne(mappedBy="car") //will give you an error
public User user;
}
mappedBy here represents the owner of relation which is important in bidirectional relation.
Think in normal condition can a car exist without the User which owns it means User is the owner in a relation.So in your case User is the owner of relation.Mapped By
But the above code will not work The attribute mappedBy is undefined for the annotation type ManyToOne
In that case #JoinColumn come into picture.Join Column

jpa inheritance - reuse an entity in several others

I am new to jpa and wonder how I have to realize this. I want to use an Entity called Address in several other Entities.
Car entity:
#Entity
public class Car
#Id
private String id;
private String licensePlate;
#ManyToOne
private Address address;
public Car() {
}
/* Getter and setters */
....
..
Person entity:
#Entity
public class Person {
#Id
private String id;
private String name;
#OneToMany(mappedBy = "address", cascade = CascadeType.ALL)
private Set<Address> addresses;
public Person() {
}
/* Getter and setters */
....
..
Address entity:
#Entity
public class Address {
#Id
private String id
private String streetAndNumber;
/* Now what?????? */
private Car car; // would fit for car
private Person person; // would fit for person
// But what would be fitted both?
I searched the internet to find a solution. But, most likely to my lack of knowledge. I couldn't find something understandable.
There is actually no need to make all relations bidirectional, it's a best practice to prefer unidirectional relationships in a model whenever possible, see Domain Driven Design Quickly.
In this case Address does not need to know of all the entities that it's associated with. Also consider making the address a Value Object (see DDD quickly) using the #Embeddable annotation.

JPA - Persisting a One to Many relationship

Maybe this is a stupid question but it's bugging me.
I have a bi-directional one to many relationship of Employee to Vehicles. When I persist an Employee in the database for the first time (i.e. it has no assigned ID) I also want its associated Vehicles to be persisted.
This works fine for me at the moment, except that my saved Vehicle entity is not getting the associated Employee mapped automatically, and in the database the employee_id foreign key column in the Vehicle table is null.
My question is, is it possible to have the Vehicle's employee persisted at the same time the Employee itself is being persisted? I realise that the Employee would need to be saved first, then the Vehicle saved afterwards. Can JPA do this automatically for me? Or do I have to do something like the following:
Vehicle vehicle1 = new Vehicle();
Set<Vehicle> vehicles = new HashSet<Vehicle>();
vehicles.add(vehicle1);
Employee newEmployee = new Employee("matt");
newEmployee.setVehicles(vehicles);
Employee savedEmployee = employeeDao.persistOrMerge(newEmployee);
vehicle1.setAssociatedEmployee(savedEmployee);
vehicleDao.persistOrMerge(vehicle1);
Thanks!
Edit: As requested, here's my mappings (without all the other methods etc.)
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="employee_id")
private Long id;
#OneToMany(mappedBy="associatedEmployee", cascade=CascadeType.ALL)
private Set<Vehicle> vehicles;
...
}
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="vehicle_id")
private Long id;
#ManyToOne
#JoinColumn(name="employee_id")
private Employee associatedEmployee;
...
}
I just realised I should have had the following method defined on my Employee class:
public void addVehicle(Vehicle vehicle) {
vehicle.setAssociatedEmployee(this);
vehicles.add(vehicle);
}
Now the code above will look like this:
Vehicle vehicle1 = new Vehicle();
Employee newEmployee = new Employee("matt");
newEmployee.addVehicle(vehicle1);
Employee savedEmployee = employeeDao.persistOrMerge(newEmployee);
Much simpler and cleaner. Thanks for your help everyone!
You have to set the associatedEmployee on the Vehicle before persisting the Employee.
Employee newEmployee = new Employee("matt");
vehicle1.setAssociatedEmployee(newEmployee);
vehicles.add(vehicle1);
newEmployee.setVehicles(vehicles);
Employee savedEmployee = employeeDao.persistOrMerge(newEmployee);
One way to do that is to set the cascade option on you "One" side of relationship:
class Employee {
//
#OneToMany(cascade = {CascadeType.PERSIST})
private Set<Vehicles> vehicles = new HashSet<Vehicles>();
//
}
by this, when you call
Employee savedEmployee = employeeDao.persistOrMerge(newEmployee);
it will save the vehicles too.

Categories

Resources