I was given this assignment, just for practice, it became very long and challenging, but it has taught me a lot, on lambdas and JPA mainly.
It is a basic Rest API, which is used to create Hotels, Rooms, Guests, Reservations, types of guests, types of rooms, etc.
My initial problem was learning about JPA relations, OneToOne, OneToMany, etc., unidirectional, bidirectional, and what not.
I'm also using PostgreSQL, using "sping.jpa.hibernate.ddl-auto=create-drop(or update)", change as needed, when I want to recreate the DB for whatever reason.
So I'm very happy and excited using my new #Annotations to relate my Entities, and fetch back lists of whatever information I needed, came across multiple problems, read many many questions here, solved my problems, but now I have come across a new problem, but then, started questioning my approach, maybe I should not leave everything to JPA.
Let me show you what I mean. I'm going to keep my classes short to show only relevant information.
I have my reservation entity.
#Data
#Entity
#Table(name = "reservation")
public class Reservation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "guest", referencedColumnName = "id")
#JsonManagedReference
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Guest guest;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "room", referencedColumnName = "id")
private Room room;
#ManyToMany(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinTable(name = "reservation_rooms",
joinColumns = { #JoinColumn(name = "reservation_id" )},
inverseJoinColumns = { #JoinColumn(name = "room_id") }
)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<ReservationRoom> roomList = new ArrayList<>();
private LocalDate start_date;
private LocalDate end_date;
private Boolean check_in;
private Boolean check_out;
public void addRoom(Room room) {
this.roomList.add(room);
}
public void removeRoom(Long id) {
Room room = this.roomList.stream().filter(g -> g.getId() == id).findFirst().orElse(null);
if (room != null) {
this.roomList.remove(room);
}
}
}
This is my Room entity.
#Data
#Entity
#Table(name = "room")
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
private Integer floor;
#JsonProperty("max_guests")
private Integer maxGuests;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference
private Hotel hotel;
#ManyToOne(fetch = FetchType.LAZY)
#JsonProperty("type")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private RoomType roomType;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Room)) {
return false;
}
return id != null && id.equals(((Room) o).getId());
}
#Override
public int hashCode() {
return getClass().hashCode();
}
}
And this is my Guest entity.
#Data
#Entity
#Table(name = "guest")
public class Guest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String first_name;
private String last_name;
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JsonProperty("type")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private GuestType guest_type;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "guestList"
)
#JsonBackReference
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<Reservation> reservationList = new ArrayList<>();
public Guest(){}
public Guest(Long id) {
this.id = id;
}
public List<Reservation> getReservationList() {
return reservationList;
}
public void setReservationList(List<Reservation> reservationList) {
this.reservationList = reservationList;
}
}
At the beginning a reservation could only have 1 room, but the requirement changed and it can have multiple rooms now. So now, the guest list needs to be linked to the room linked to the reservation, and not directly to the reservation. (I know I have a Guest and a Room, and also the List of both, this is because I'm using the single Guest as the name for the reservation, and the single Room, as the "Main" room, but don't mind that please).
Letting JPA aside, because every challenge I have faced I would ask my self "how to do it JPAish?", and then research how to do it with JPA (that's how I learned about the #ManyToMany, etc. annotations).
What I would do is just create a new table, to relate the reservations to the room (which is already done in my entities with JPA), and then add also de guest id.
So, this new table, would have a PK with reservation_id, room_id and guest_id. Very easy, then create my Reservation model, which have a List of Room, and this Room model, would have a List of Guest. Easy.
But I don't want to add a List of Guest in my current Room entity, because I have an endpoint and maybe a couple of other functions, which retrieves my Room entity, and I don't want to add a List of Guest, because as the time passes, this list would grow bigger and bigger, and it is information you don't need to be passing around.
So I did some research and found that I can extend my entity with #Inheritance or #MappedSuperclass, and I could create maybe a Reservation_Room model, which includes a List of Guest and add a List of Reservation_Room instead of a List of Room in my Reservation Entity, which I really wouldn't know if it is even possible.
Having said that, and before I keep researching and start making modifications to my code, it got me wondering, if this would be the right approach? Or if I'm forcing JPA too much on this? What would be the best approach for this? Can a 3 id relation table be easily implemented/mapped on JPA?
The main goal would be to have my Room entity exposed as it is, but when a Room is added to a Reservation, this Room would also have a List of Guest. Can I do this JPAish? Or should I create a new model and fill with the information as needed? This wouldn't exempt me from creating my 3 ids table.
Based on what you wrote here, I think you might be at a point where you are realizing that the persistence model doesn't always match the presentation model, which you use in your HTTP endpoints. This is usually the point where people discover DTOs, which you also seem to have heard of.
DTOs should be adapted/created to the needs of the representation of an endpoint. If you don't want to expose certain state, then simply don't declare a getter/field for that data in a DTO. The persistence model should simply be designed in a way, so that you can persist and query data the way you need it. Translation between DTOs and entities is a separate thing, for which I can only recommend you to give Blaze-Persistence Entity Views a try.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Reservation.class)
public interface ReservationDto {
#IdMapping
Long getId();
GuestDto getGuest();
List<RoomDto> getRooms();
}
#EntityView(Guest.class)
public interface GuestDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(Room.class)
public interface RoomDto {
#IdMapping
Long getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ReservationDto a = entityViewManager.find(entityManager, ReservationDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ReservationDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
I would say that you need to add a layer between persistence and the endpoints. So, you will have Controllers/Services/Repositories (in the Spring world). You should use entities as return type from Repositories (so used them in Services as well), but return DTOs to Controllers. In this way, you will decouple any modification that you do between them (e.g. you may lose interest to return a field stored in an entity, or you may want to add more information to the dto from other sources).
In this particular case, I would create 4 tables: Reservations, Guests, Rooms, GuestsForReservation.
Guests will contain id + guests data (name, phone number, etc)
Rooms will contain id + room data
GuestsForReservation will contain id + reservationId + guestId (so you can get the list of guests for each reservation). FK for reservationId and guestId, PK for synthetic id mentioned.
Reservations will contain id (synthetic), room id, date from, date to, potentially main guest id (it could be the person paying the bill, if it makes sense for you). No link to the GuestForReservation table, or you can have a list of GuestForReservation if you need to.
When you want to reserve a room, you have a ReservationRequest object, which will go to the ReservationService, here you are going to query the ReservationRepository by roomId and dates. If nothing is returned, you create the various entities and persist them in ReservationRepository and GuestForReservation repository.
By using the service and the combination of various repositories, you should be able to get all the information that you need (list of guests per room, list of guests per date, etc). At the service level, you then map the data you need to a DTO and pass it to the controller (in the format that you need), or even to other services (depending on your needs).
For what concern the mapping between entities and DTOs, there are different options, you could simply create a Component called ReservationMapper (for example) and do it yourself (take an entity and build a DTO with what you need); implements Converter from the Springframework; use MapStruct (cumbersome in my opinion); etc.
If you want to represent in JPA an id made of multiple columns, usually #Embeddable classes are used (you should mark them as EmbeddedId when you use them), you can google them for more info.
I'm trying to insert an row into my database. I have following sql setup (its just an example):
Table person:
(id, name)
Table person_street:
(person_id, street_id)
Table street
(id, name)
This should be a many to many relation. This example doesn't make sense, but I think you'll understand the idea.
Now I have these entities in java
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#JoinTable(
name = "person_street",
joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "street_id")
)
#ManyToMany
private List<Street> streets;
}
This is a good solution to work with my person objects (after reading them from my database).
But I have problems while inserting/creating person objects.
I want to create a new person in my frontend. It calls an REST interface at my backends side with String name, List<Long> streets.
Now I want to insert this new person (with the given name and streets) into my database.
But I don't want to do a select for all List<Long> streets on my street table. There is no need to change any value of the street objects. I just want to insert the link between the new person and the existing streets (in the table person_street).
What is the easiest way to do that?
Can I use my Person class for this, or does I need a new different class.
T
you can add this method to the Person class
public void addStreets(Street street) {
if(streets==null) {
streets=new ArrayList<Street>();
}
streets.add(street);
}
and after that, you get the street by the id from the street table and added to the corresponding person which you are getting from the front end after that you save the whole person.
Before I start I checked few posts and none of them resolved my problem.
Please can someone guide me here. I wanted to establish a Plant(1) to Inventories(n) Relationship.
I created couple of models, one for Plant as below where I mention the OneToMany relationship
#Entity
public class Plant implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long plantID;
#OneToMany(mappedBy = "plant", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Inventory> listInventory = new ArrayList<>();
getter and setter....
And another one for Inventory where I mention ManyToOne relationship
#Entity
public class Inventory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long inventoryId;
#ManyToOne
#JoinColumn(name = "plantID", nullable = false)
private Plant plant;
When I try to persist the data like below, it saves the plant (parent) but not its children's.
As part of persisting the data, I did something like below
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
inventory.setPlant(plant);
listInventory.add(inventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
}
After reading few posts I found that I should set the association of plant with the inventory before persisting. So I did added the code for the same but it went on infinite or just my STS hanged.
I am sure I am doing something wrong but not sure where. Please can someone guide me through.
My expectation is that Inventory will be a reference table where I will have list of inventories. When I add a new plant, I will map few of the inventories to this plant. Similarly this will happen for many plants and many inventories.
As part of persistence, I will have to save plant and its inventories. I should also be able to pass the plant ID and retrieve the corresponding Inventories.
UPDATE 1:
Also I am not sure if the relation I am using is fair enough for this scenario. As my inventory is a reference table and at the same time, when a plant is mapped to multiple inventories, each inventory can be modified before persisting.
I tried #ManyToMany and it stores the relation in a 3rd table with a unique reference to both the tables but I wont be able to get the details of each inventory record.
With #ManyToMany, when I fetch it bring the values from the reference table and not the modified one which was persisted with the parent(plant) Please any advise
UPDATE 2
I tried with the same models but I changed the way I persist the data as below.
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
plant.addInventoryToPlant(inventory);
}
return plantRepository.save(plant);
}
Here is the add method in my plant model
public void addInventoryToPlant(Inventory inventory) {
listInventory.add(inventory);
inventory.setPlant(this);
}
It is just overwriting the inventory table with different plant IDs but not creating a reference table or join table to maintain all the possible mappings. If I try to add a plant with 2 inventories, it maps them first to the Inventory table. If I add another plant then this is getting overwritten. I was in an assumption that it will create a third table to maintain this entity
I have a feeling that those Inventory entities are detached and the Persistence Provider is not considering them during flush. You do not get an exception as #OneToMany is a special kind of relationship in flush algorithm because the act of persisting the owning entity does not depend on the target thus Hibernate will proceed and persist only the Plant entity.
Try using merge instead of persist:
plant.setListInventory(listInventory);
return plantRepository.merge(plant);
Update
You can also merge each inventory one by one using save as Spring JPA implicitly checks whether entity should be saved or merged:
for (Inventory inventory : listMaterial) {
Inventory mergedInventory = inventoryRepository.save(inventory);
mergedInventory.setPlant(plant);
listInventory.add(mergedInventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
This answers the UPDATE 2 part:
As I understand, Plant and Inventory are in a many-to-many relationship, but there are additional properties that are to be stored along with the information that a specific Plant is holding a specific Inventory item.
In that case, you need an additional entity (let's call it StockItem) that will be used to hold that additional state. Both Plant and Inventory will then be in a one-to-many relationship with the new entity.
Your mapping will then become:
class Plant {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "plant_id")
private Set<StockItem> stockItems;
...
}
class StockItem {
#ManyToOne(mappedBy = "stockItems")
private Plant plant;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "inventory_id")
private Inventory inventory;
//put any fields here that may vary from one plant to plant to another
private int quantity;
}
class Inventory {
#OneToMany(mappedBy = "inventory")
public Set<StockItem> stockItems;
// leave any fields that will NOT vary from one plant to another here
private String name;
}
Things to note:
This mapping assumes that you will be adding new StockItems to a Plant (new items added to Inventory.stockItems will be ignored by JPA), in which case it will be enough to set the StockItem.inventory field to a proper value, add the StockItem to the Plant.stockItems list, and save the Plant entity
The StockItem.plant and Inventory.stockItems are not absolutely necessary, remove them if you do not need them
First I am a newbie with DBs in general so if this turns out to be a dumb question, please be a bit tolerant and generous with details in ur answer and thanks alot for any effort put in to help !!
I am having trouble designing the class structure for my code and will welcome any suggestions concerning the matter. I have 3 data classes
1) School
2) Teacher
3) Workshop
A School Entity has a List<Teacher> and a List<Workshop> they hosted.
A Workshop Entity has a single Host School Entity and a List<Teacher> of participants.
A Teacher Entity had a List<Workshop> they attended and an employment history List<School> (not showing school List in the code below, as I am leaving it till later when I figure simpler things first, but its a target)
Every single school in a given city will be assigned one entry and no more, everything else from Teacher and Workshop has to reference that single Entry.
Every single teacher in a given city will be assigned on entry/ account and no more, so everything else has to reference it
I know I can work with IDs, but that will involve tons of duplication and I wont be able to get an object from within another object, the entry will be a Long and and even if I make methods to automate the whole thing, this will eat up my query quotas very fast( would really love to avoid that)
I would like to be able to query for a single entity(School or Teacher or Workshop) and be able to see all the other Entity Lists associated .
Plus,
Teachers move around, so I must be able to remove a Teacher (as a Child Entity) from one School and add it to another, while maintaining the record of which School hosted their previous workshops.
I have done this much on my own
#Entity
public class School
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.ALL, mappedBy="school")
private List<Teacher> teachers;
#OneToMany(cascade = CascadeType.ALL,mappedBy="school")
private List<Workshop> workshops;
// Getters and Setters and some methods
}
#Entity
public class Teacher
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne
private School school;
private List<Workshop> Workshops;
// Getters and Setters and some methods}
#Entity
public class Workshop
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne
private School school;
private List<Teacher> Participants;
// Getters and Setters and some methods}
Currently I am able to assign as many teachers and workshops to a particular School entity, however, I cannot assign teacher entities (that are already assigned to a School - key point here) to a Workshop. This is the Error I always get
Detected attempt to establish Workshop(no-id-yet) as the parent of School(6148469022523392)/Teacher(5585519069102080) but the entity identified by School(6148469022523392)/Teacher(5585519069102080) is already a child of School(6148469022523392). A parent cannot be established or changed once an object has been persisted.
The order of which is which varies depending on which Entity got created and persisted first.
Thanks alot and awaiting any advice and consultation ... I am not looking for a complete solution, I just need someone to point out how this could be done ( I am sure I am not the first to get stuck here and I am sure that generous experts will help out)
Now look I have some points : Three Entites you have : School , Workshop , Teacher.
School have oneTomany with - workshop & teacher. So once we are persisting school we'll have entries in both the tables - workshop & teacher.And also you wanted to have A Workshop Entity has a single Host School so we achieved that also while persisting as per below code.
Your School Entity :
#Entity
public class School
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="school")
private List<Teacher> teachers;
#OneToMany(cascade = CascadeType.PERSIST,mappedBy="school")
private List<Workshop> workshops;
// Getters and Setters and some methods
}
Your Teacher Entity:
#Entity
public class Teacher
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="key")
private School school;
// Getters and Setters and some methods}
Your WorkShop Entity:
#Entity
public class Workshop
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="key")
private School school;
and then :
em.getTransaction().begin();
School schoolObj = new School();
schoolObj.setName("School 1");
List <Teacher> teachers = new ArrayList<Teacher>();
List <Workshop> workshopList = new ArrayList<Workshop>();
Teacher teacher = new Teacher();
teacher.setSchool(schoolObj);
teacher.setName("Teacher 1");
teachers.add(teacher);
Teacher teacher1 = new Teacher();
teacher1.setSchool(schoolObj);
teacher1.setName("Teacher 2");
teachers.add(teacher1);
teacher teacher2 = new Teacher();
teacher2.setSchool(schoolObj);
teacher2.setName("Teacher 3");
teachers.add(teacher2);
Workshop ws = new Workshop();
ws.setSchool(schoolObj); //By this you setted schoolObj in workshop entity
ws.set(some attribute);
workshopList.add(ws);
school.setTeachers(teachers); //By this you setted teachers through school ,i.e., entry came in teachers table too.
school.setWorkshops(workshopList); //By this you setted workshopList through school ,i.e., entry came in workshop table too.
em.persist(schoolObj);
em.getTransaction().commit();
em.close();
Now you mentioned that: A WorkShop Entity also has a List of participants .And A Teacher Entity had a List they attended and an employment history List. This shows you are having ManyToMany between Workshop & Teacher. As in your case Workshop has List of teachers and Teachers also have List of Workshop's.So here you will be requiring a joining table to lonk this ManyToMany relationship. Similarly, between teacher & school you have ManyToMany as both have List of one other.So here also you will be needing joining table.To learn more about this click here.
Hence to set this ManyToMany relationship you have to link through a joining table not by persisting here as it will clash then. And if you want to fetch data as per this ManyToMany relationship then you have make a separate query.
Hope this help !
I would like to change a concrete superclass to one of its subclass. I've included an example below:
#Entity
#Table(name = "employees")
#Inheritance(strategy = InheritanceType.JOINED)
public class Employee {
#Id
#Column(name = "id")
private String id;
public Employee( String id ) {
this.id = id;
}
...
}
#Entity
#Table(name = "managers")
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")
public class Manager extends Employee {
public Manager( String id ) {
super(id);
}
...
}
#Entity
#Table(name = "history")
public class History {
...
/**
*
*/
#ManyToOne
#JoinColumn(name = "employee_id")
private Employee employee;
...
}
The three classes I'm working with are Employee, Manager and History. All Managers are Employees, but not all Employees are Managers. All Employees (and Managers) have a History. Employees may be promoted to Management. When this happens an Employee's history should be retained by keeping their Employee ID the same. This will allow a Manager's History through employment to be easily found.
Implementing the promotion operation is complicated by constraints withing the database: the database will not allow removing the old Employee and creating a new Manager with the same ID without removing all of the History objects by cascading operation - which Human Resources won't allow, otherwise my job would be easy!
Is it possible to add or attach the Manager (new managers) row to an existing Employee without resorting to custom SQL operation?
I've tried the following solutions without success:
public void promote( Employee employee ) {
/* copy over the ID of the employee to the manager. Will not work because employee with ID already exists */
Manager manager = new Manager(employee.getId());
this.entityManager.persist( manager );
}
... or ...
public void promote( Employee employee ) {
/* detach the employee then merge. Will not work: fails, again, with a NonUniqueObject Exception */
this.entityManager.detach( employee );
Manager manager = new Manager(employee.getId());
this.entityManager.merge( manager );
}
How can I get this done? Am I even on the right track with detach() and merge()?
Is this possible in Hibernate/JPA?
Any ideas will be helpful at this point as I'm stumped!
Aaron
As you're no doubt starting to see, you're rowing against the wind here. Unfortunately, it looks like you have a classic example of using inheritance to represent role. Both Hibernate--an Object-Relational Mapper--and Java--an Object-oriented language--are going to fight you on this one because your object model is wrong. I'd say your best bet is to fix it now with a refactoring and data migration. The end result should be that everyone is an Employee, and some Employees have some kind of "manages" relationship with one or more departments or other Employees.
I ran into a similar situation and I don't believe a flawed data model is to blame since the data model matches the real world model quite well.
You can insert the subclass record manually to achieve the desired result. Sometimes it's requisite to go around Hibernate to get something done, so this is what I did in this case as a workaround:
sessionFactory.getCurrentSession().createSQLQuery(
"insert into manager (employee_id, rank) " +
"values (:employeeId, :rank) ")
.setParameter("employeeId", employeeId)
.setParameter("rank", rank)
.executeUpdate();