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 am using Spring Data JPA with Hibernate.
Lets say I have the following entity defined:
#Entity
#Table(name = "foods")
public class Food {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "food_id")
private Long foodId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "food_type_id")
#NotNull
private FoodType foodType;
...
}
#Entity
#Table(name = "food_types")
public class FoodType {
public static final Integer PERISHABLE;
public static final Integer NON_PERISHABLE;
#Id
#Column(name = "food_type")
private Integer foodTypeId;
private String name;
...
}
Every time when I want to create a Food entity and save it to the database, currently code looks like this:
Food food = new Food();
FoodType foodType = foodTypeRepository.findById(FoodType.PERISHABLE); // Call to DB to get Entity
food.setFoodType(foodType);
....
foodRepository.save(food);
If we consider FoodType to be constant in the DB. Can I use it like this:
Food food = new Food();
FoodType foodType = new FoodType();
foodType.setFoodTypeId(FoodType.PERISHABLE); // No Call to DB
food.setFoodType(foodType);
....
foodRepository.save(food);
I have tested it and yes I can use it that way, hibernate will save the Food entity, but are there any downsides, pitfalls, etc... I am not seeing.
PS. This is just a simple example illustrating the idea, it is part of old legacy project which I cannot modify to remove constant from DB, and use an enum instead.
To avoid extra call to DB you should use:
FoodType foodType = foodTypeRepository.getOne(FoodType.PERISHABLE);
under the hood it calls EntityManager.getReference that obtain a reference to an entity without having to load its data as opposed to the foodTypeRepository.findById that lead to call EntityManager.find that obtain an entity along with its data.
See also this section of the hibernate documentation.
P.S. You can not use:
Food food = new Food();
FoodType foodType = new FoodType();
foodType.setFoodTypeId(FoodType.PERISHABLE);
as in this case hibernate consider foodType as a transient entity (not associated with a persistence context) and will try to save it as a new record if you have a proper cascading on your #ManyToOne association.
P.S.S. As it's mentioned in the documentation the method JpaRepository#getOne(ID) is deprecated and you should use JpaRepository#getById(ID) instead.
You do not need to fetch the entity associated with FoodType.PERISHABLE in order to set the relation on a Food entity to it and I'm not aware of any side effects or pitfalls of using FoodType.PERISHABLE directly as long it is a valid FoodType id.
As others mentioned, you could also use JpaRepository#getById(ID id) and that's probably the more canonical way of addressing this problem:
T getById(ID id) Returns a reference to the entity with the given
identifier. Depending on how the JPA persistence provider is
implemented this is very likely to always return an instance and throw
an EntityNotFoundException on first access. Some of them will reject
invalid identifiers immediately.
I'm trying to use JPA (with Hibernate) to save 2 entities. Spring data is providing the interface but I don't think it matters here.
I have a main entity called 'Model'. This model has many 'Parameter' entities linked. I'm writing a method to save a model and its parameters.
This is the method:
private void cascadeSave(Model model) {
modelRepository.save(model);
for (ParameterValue value : model.getParameterValues()) {
parameterValueRepository.save(value);
}
}
This is the problem:
When I load a Model that already existed before, add some new parameters to it and then call this method to save both of them something strange happens:
Before the first save (modelRepository.save) this is what the model's data looks like when debugging:
The model has 2 parameters, with filled in values (name and model are filled).
Now, after saving the model the first save in my method, this happens. Note that the object reference is a different one so Hibernate must have done something magical and recreated the values instead of leaving them alone:
For some reason hibernate cleared all the attributes of the parameters in the set.
Now when the saving of the new parameters happens in the following code it fails because of not null constraints etc.
My question: Why does hibernate clear all of the fields?
Here are the relevant mappings:
ParameterValue
#Entity
#Table(name = "tbl_parameter_value")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "PARAMETER_TYPE")
public abstract class ParameterValue extends AbstractBaseObject {
#Column(nullable = false)
#NotBlank
private String name;
private String stringValue;
private Double doubleValue;
private Integer intValue;
private Boolean booleanValue;
#Enumerated(EnumType.STRING)
private ModelType modelParameterType;
#Column(precision = 7, scale = 6)
private BigDecimal bigDecimalValue;
#Lob
private byte[] blobValue;
ParameterValue() {
}
ParameterValue(String name) {
this.name = name;
}
ModelParameterValue
#Entity
#DiscriminatorValue(value = "MODEL")
public class ModelParameterValue extends ParameterValue {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "model_id", foreignKey = #ForeignKey(name = "FK_VALUE_MODEL"))
private Model model;
ModelParameterValue() {
super();
}
ModelParameterValue(String name) {
super(name);
}
Model
#Entity
#Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "model")
private Set<ModelParameterValue> parameterValues = new HashSet<>();
EDIT
I was able to reproduce this with a minimal example.
If you replace everything spring data does this is what happened under the hood (em is a JPA EntityManager):
public Model simpleTest() {
Model model = new Model("My Test Model");
em.persist(model);
model.addParameter(new Parameter("Param 1"));
em.merge(model);
for (Parameter child : model.getParameters()) {
em.persist(child);
}
return model;
}
When the merge is executed, all of the attributes of the parameters are set to null. They are actually just replaced with completely new parameters.
I guess you are using Spring Data Jpa as your modelRepository. This indicates following consequences.
Spring Repository Save
S save(S entity)
Saves a given entity. Use the returned
instance for further operations as the save operation might have
changed the entity instance completely.
So it is normal behaviour which you had encountered.
Code should be changed to :
model = modelRepository.save(model);
for (ParameterValue value : model.getParameterValues()) {
parameterValueRepository.save(value);
}
EDIT:
I think that your saving function is broken in sense, that you do it backwards. Either you can use CascadeType on your relation or you have to save children first.
Cascade
Cascade works like that "If you save Parent, save Children, if you update Parent, update Children ..."
So we can put cascade on your relation like that :
#Entity
#Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "model", cascade = CascadeType.ALL)
private Set<ModelParameterValue> parameterValues = new HashSet<>();
and then only save like this
private void cascadeSave(Model model) {
modelRepository.save(model);
//ParamValues will be saved/updated automaticlly if your model has changed
}
Bottom-Up save
Second option is just to save params first and then model with them.
private void cascadeSave(Model model) {
model.setParameterValues(
model.getParameterValues().stream()
.map(param -> parameterValueRepository.save(param))
.collect(Collectors.toSet())
);
modelRepository.save(model);
}
I haven't checked second code in my compiler but the idea is to first save children (ParamValues), put it into Model and then save Model : )
I am designing an e-commerce web application using Spring JPA. I have 2 classes Product and Category, where a Product can be assigned to many categories, but a Category does not concern about Product.
#Entity(name = "products")
class Product {
#Id
#Column(name = "product_id")
private Long productId;
#ManyToMany
#JoinTable(
name = "product_category_links",
joinColumns = #JoinColumn(name = "product_id", referencedColumnName = "product_id"),
inverseJoinColumns = #JoinColumn(name = "category_id", referencedColumnName = "category_id"))
private List<Category> categories;
// getters, setters,
}
#Entity(name = "categories")
class Category {
#Id
#Column(name = "category_id")
private Long category_id;
// getters, setters
}
When a Product has its categories changed, I have a requirement to also update something in database. I am thinking of maintain this integrity by creating a dedicated service method to update product's category.
class ProductService
#Autowired
private ProductRepository productRepository;
private ComplexDBService complexDBService;
#Transactional
public void addCategory(Long productId, Long categoryId) {
Product p = productRepository.findByProductId(productId);
Category c = categoryRepository.findByCategoryId(categoryId);
p.getCategories.add(c);
complexDBService.doSomething();
}
}
But I think this is not practical because a Product can still have categories changed in other places. For example, in a controller, someone can get a Product directly from the repository can change its categories. I don't want to forbid this use case.
So I am thinking of putting the logic addCategory(Long productId, Long categoryId) in Product class itself, which actually suggested by Domain Driven Design. But I cannot figure out how to do that because I cannot inject the ComplextDBService into Product. One way is to pass it as an argument to addCategory method as addCategory(Long productId, Long categoryId, ComplextDBService complexDBService), is this a good practice? Is there some other ways to put custom database manipulation logic in a domain class?
addCategory(Long productId, Long categoryId, ComplextDBService complexDBService), is this a good practice?
No, it isn't. In complicated business cases, you sometime have to pass some kind of "service" as a parameter into method invoked on aggregate, but as a rule of thumb you should only invoke read-only-query method on this "service".
Is there some other ways to put custom database manipulation logic in a domain class?
There should only occur Product related things inside Product aggregate, e.g. manipulation of Product state.
Your requirement is to respond to occurrence inside Product aggregate.
Domain Event to the rescue
You need to invert the control. Product aggregate should inform the outside about Events inside itself and the outside should react to this. Product should not depend on other not related aggregates/concepts.
class Product {
void addCategory(CategorySnapshot category) {
categories.add(category);
eventPublisher.publish(new ProductCategoryAdded(getSnapshot(), category));
}
}
Now you should register other components to listen to ProductCategoryAdded event, it doesn’t matter what those other components are (if you need to make db operations, maybe you are implementing CQRS?).
You can implement publisher by yourself or use frameworks like Guava Event Bus, Axon etc.
By the way, you are missing lots of important concepts of DDD.
Aggregate-Product should not have list of other Aggregate-Category (maybe this bounded-context of your project should not be implemented using DDD at all?)
You should not add objects directly into list owned by aggregate p.getCategories.add(c)
//getters, setters - those are not object-oriented…
Lets say we have User entity class. User can be friends with other users. How can i map this self-reference collection field without creating a new entity called Connection or creating multiple entries in the database?
#Entity
public class User {
...
#ManyToMany
private Collection<User> friends;
...
}
USER_ID-FRIEND_ID
1 - 2
2 - 1 (duplicate... I don't need it)
Following is snapshot from my code for ElementEntity:
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ElementEntity> children;
#JoinColumn(name = "ParentId", referencedColumnName = "ElementId")
#ManyToOne(fetch = FetchType.LAZY)
private ElementEntity parent;
Where on database there are fields:
ElementId - primary key;
ParentId relation with parent
You can't - you need both records in the database.
Actually, for friendship relations, I'd say that a graph database like neo4j is the proper thing to use. There you have the two users and simply add an edge "friends".
At least you will need a relational table.
So you have a USER table and a FRIENDS:
user_id friend_id
1 2
But #Bozho answer is way better than mine (neo4j).
Well, in fact you can.
You can use annotations like #PreUpdate, #PrePersists, #PostUpdate and so to convert manually the elements of a collection. This way your entity can render then them way you want while in database you only store a raw text.
A more pausible alternative will be to use #Convert annotation, available since jpa 2.1 (#UserType in hibernate). It tells jpa to convert the field into another type everytime it read/save in database.
For it you should use #Convert anotation, specifying and AttributeConverter object.
For example
public class Parent {
#Id
private Integer id;
#Convert(converter = FriendConverter.class)
private Set<Parent>friends;
}
And converter class like the following:
#Component
public class FriendConverter implements AttributeConverter<List, String>{
#Autowired
private SomeRepository someRepository;
#Override
public String convertToDatabaseColumn(List attribute) {
StringBuilder sb = new StringBuilder();
for (Object object : attribute) {
Parent parent = (parent) object;
sb.append(parent.getId()).append(".");
}
return sb.toString();
}
#Override
public List convertToEntityAttribute(String dbData) {
String[] split = dbData.split(".");
List<Parent>friends = new ArrayList<>();
for (String string : split) {
Parent parent = someRepository.findById(Integer.valueOf(string));
friends.add(accion);
}
return friends;
}
}
It is a dummy implementation but it gives you the idea.
As a personal comment, I do recommend to map the relationship as it should. In the future it will avoid you problems. AttributeConverter comes in handy when working with enums