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 have a parent entity Stock which has a child entity StockDetails in a OneToOne relation.
I can't figure out how to properly set and replace values for the Stock.details field.
Here are my entity classes (#Getter/#Setter from Lombok):
#Getter
#Setter
#NoArgsConstructor
#Entity
#Table(name = "stocks")
public class Stock
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String isin;
private String symbol;
private String name;
#OneToMany(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<StockChartPoint> chart;
#OneToOne(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private StockDetails details;
public void setDetails(StockDetails d)
{
details = d;
details.setStock(this);
}
}
and
#Getter
#Setter
#NoArgsConstructor
#Entity
#Table(name = "stock_details")
public class StockDetails
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
#JoinColumn(name = "stock")
private Stock stock;
}
Inserting a new value (and corresponding row) into the DB works fine when the details table is empty and I can see that Hibernate logs exactly one intert statement. My code looks like this:
dbService.transactional(session ->
{
var stocks = getAllStocks();
var s = stocks.get(0);
StockDetails n = new StockDetails();
s.setDetails(n);
session.save(s);
});
The DbService.transactional method (TransactionExecutor is just a functional interface):
public void transactional(TransactionExecutor executor)
{
Transaction t = null;
try
{
t = session.beginTransaction();
executor.execute(session);
session.flush();
t.commit();
}
catch (HibernateException ex)
{
if (t != null)
t.rollback();
}
}
But when there is already an existing row in the details table the old row is deleted and a new row is inserted. The result is that the PK of the details table is increasing every time I update the values. Is there a pattern which I couldn't find to address this? I could also just update the existing Stock.details field but this would lead in just copying all the fields from object A to object B and I guess there is a smarter way doing this. I tried using an EntityManager, merge()/saveOrUpdate()/persist() instead of save() as well as manipulating the ID in the new object but this resulted in changing nothing or throwing exceptions.
Then there is another problem I encountered and I don't know if it's related to the first one:
When executing the dbService.transactional(...) block twice it behaves differently: Now two rows are added to the DB. The SQL log looks like this:
...
Hibernate: insert into stock_details (stock) values (?)
Hibernate: delete from stock_details where id=?
-> insert/delete produces by the first run
Hibernate: select stock0_.id as id1_3_, stock0_.isin as isin2_3_, stock0_.name as name3_3_,
stock0_.symbol as symbol4_3_ from stocks stock0_
Hibernate: insert into stock_details (stock) values (?)
-> Just insert, no delete
Please let me know if more information is needed.
mysql.mysql-connector-java > 8.0.22
org.hibernate.hibernate-core > 5.4.25.Final
org.hibernate.hibernate-validator > 5.4.3.Final
But when there is already an existing row in the details table the old row is deleted...
...which is to be expected with orphanRemoval = true
...and a new row is inserted
...which is obviously to be expected as well, since you're overwriting the existing StockDetails associated with s with a brand new instance of StockDetails.
If you wish to update the existing StockDetails, rather than create a new StockDetails entity, you need to, well, do just that in Java code.
I could also just update the existing Stock.details field but this would lead in just copying all the fields from object A to object B...
that would be the least error-prone approach
...but I guess there is a smarter way doing this
You could just do:
StockDetails n = new StockDetails();
n.setId(s.getStockDetails().getId());
... //configure the remaining properties
n.setStock(s);
entityManager.merge(n);
As you might have guessed you are actually creating a new StockDetails and replacing the old one. If you want to update the existing StockDetails you really need to fetch that and update it field by field (as you told you could do). So like:
StockDetails sd = s.getStockDetails();
sd.setSomeFiled(updateValue);
// ... copying field by field
To prevent copying field by field you can obtain StockDetails from the database and make the possible edits directly into it so there would not be a need to copy each field.
However if it is a case - for example - that you need to write values from some DTO to your entity there are libraries that you can use to ease the pain of copying.
Just to mention one ModelMapper. With it the copying goes like:
ModelMapper mm = new ModelMapper();
StockDetails sd = s.getStocDetails();
mm.map(stockDetailsDto, sd);
where stockDetailsDto is some DTO object that contains fields to update to entity.
If your StockDetails contains all the other fields also even those not changed the em.merge is most easy as told in answer from crizzis.
But if you get only the updated fields then the other fields would be set to null. Sometimes id is not settable and then merge is impossible.
I was looking for an answer on SO and several articles, but I think clear explanation is unfortunately missed, so I decided to ask.
Say we have two entities (box contains several items):
#Entity
#Table(name = "box")
class Box
{
#Id
private Long id;
#OneToMany(mappedBy = "box")
private List<Item> items = new LinkedList<>();
public void addItem(Item item)
{
items.add(item);
item.setBox(this);
}
// setters, getters, delete synchronize method
}
#Entity
#Table(name = "item")
class Item
{
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "box_id")
private Box box;
// setters getters
}
Which side of the relationship should I persist?
Does it depend on the owning side of the relationship?
Does it depend on the cascade type and owning side has nothing in common?
Place CascadeType.PERSIST in the Box entity, then em.persist(box)
Place CascadeType.PERSIST in the Item entity, then em.persist(item)
Both, and persist is up to me - can be em.persist(box) or em.persist(item)
You can persist the relationship from both sides.
If you persist the box entity, you will only have to call the persist method of the entity manager once and pass the box entity as a parameter, with the item entity list, the box items parameter will have to have a cascade type.
If you persist the item entity, you will have to call the persist method of the entity manager N times, being N the number of items, passing the Item entity with the box parameter, the box parameter will have to have a cascade type. In the first insertion Box will not have ID, but once you insert the first Item, Box will have ID so in the following N-1 Items you will have to establish that Box with Id since if not, it will create N boxes.
Based on all this I think that in most logics it is better to persist the parent entity, cob cascade type persist in the property that represents the relation with the daughter class.
I have 2 domain classes, PO class and Product Class, and there is a List inside PO class. So Product is ManyToOne to PO, and PO is OneToMany to Product.
public class PO {
....
#OneToMany(mappedBy = "po", cascade= CascadeType.ALL, orphanRemoval=true)
private List<Product> products = new ArrayList<>();
.....
}
public class Product {
....
#ManyToOne(optional = false)
#JoinColumn(name = "po_id")
private PO po;
....
}
And when building the po object. I set the po field in Product with the reference of po.
PO repo = new PO();
....
for (StockLine item: source.getStockLines()) {
Product prod = new Product();
....
prod.setPo(repo);
repo.getProducts().add(prod);
}
when I call PORepo.save(po); it's working, foreign key is filled and all data in both tables are correct.
But the problem is when I fetch the PO using PORepo.findAll(), I debugged and found the object is actually recursively reference itself.
Is this normal? I don't think it's normal, but where I have done wrong?
btw, if I don't add #JsonManagedReference and #JsonBackReference the generated json will be in recursive format too. The json issue can be fixed by above to annotations, but how to fix the returned object issue?
I am using spring boot data jpa 1.5.6.RELEASE
It's correct behavior for lazy loading. You load root object and during serializing getProducts() is called. Each of the product tries to lazyly load PO, which in turn loads list of products etc.
To break this chain introduce a DTO (Data Transfer Object) and convert your entity to TDO objects. Then serialize the DTO.
There is an alternative way to "unproxy" entity. See here
I want to delete an entity that has a #OneToMany relationship with another one, set up like this:
public class Dealership implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "dealership", orphanRemoval = true)
private Set<Car> cars;
}
public class Car implements Serializable {
#ManyToOne
#JoinColumn(name="co_id")
private Dealership dealership;
}
The thing is that, when I delete the dealership, I want to delete only the cars that were not sold. And no matter what I try, Hibernate deletes ALL cars tied with the dealership, through the cascade. Here's what I've been trying. In this example, I'm trying to transfer the sold cars to another dealership, and then I delete the dealership. This is supposed to delete the dealership, its employees, and ONLY the cars that were not sold:
Session session = SessionManager.getSession();
Transaction tx = session.beginTransaction();
Dealership dealershipToDelete = (Dealership) session.load(Dealership.class, idDealership);
for(Car c: dealershipToDelete.getCars().stream().filter(c -> c.isSold()).toArray(Car[]::new)){
Dealership newDealership = (Dealership) session.load(Dealership.class, idNewDealership);
c.setDealership(newDealership);
dealershipToDelete.getCars().remove(c);
}
session.update(dealershipToDelete);
session.flush();
session.delete(dealershipToDelete);
tx.commit();
session.close();
But it always deletes ALL cars. Even when I manage to make Hibernate update the cars with the new dealership. It updates them, and then it deletes them. Help would be greatly appreciated. Thanks.
Just refresh() the Dealership object before deleting it to reflect the changes made to its relationship with the Car class.Have slightly modified your code,try this:
Dealership dealershipToDelete = (Dealership) session.load(Dealership.class, idDealership);
Dealership newDealership = (Dealership) session.load(Dealership.class, idNewDealership);
for(Car c: dealershipToDelete.getCars().stream().filter(c -> c.isSold()).toArray(Car[]::new)){
c.setDealership(newDealership);
newDealership.getCars().add(c);
}
session.flush(); //this will flush the updates to sold Car, with the new Dealership details
session.refresh(dealershipToDelete); //this will load the updated "dealershipToDelete" without the 'Sold Car' object,the 'Unsold' ones will still be there
session.delete(dealershipToDelete); //this will delete the Dealership and its related unsold car objects.
tx.commit();
session.close();
you might try something like this :
first set the foreign key to nullable :
public class Car implements Serializable {
#ManyToOne
#JoinColumn(name="co_id" , nullable = true)
private Dealership dealership;
}
then you get the Id of dealershipToRemove:
int id = dealershipToRemove.getId();
then you delete all cars that have as dealership with the given id
query = session.createNativeQuery("delete from cars where co_id = :id and date is null");
query.setParameter(1,id);
query.executeUpdate();
then you break the relationship between dealershipToRemove and its cars :
dealershipToRemove .setCars(null);
session.remove(dealershipToRemove);