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…
Related
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.
Currently, the application is being developed by utilizing Spring Boot 2.2.
The part I'm curious about refactoring is located on the user entity.
User entities receive favorite jobs and genres from users.
This genre and job consist of user entity and 1:N structure with each entity, and multiple choices are possible without duplication.
#Entity
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userName;
private String email;
private String password;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<Job> likeJobs = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<Genre> likeGenres = new ArrayList<>();
...
For example, genres include 'Hip-hop, Pop, K-POP' and jobs such as 'Drummer, DJ, Beatmaker, and Singer'.
Genre and job structure itself can be considered the same.
Therefore, there are many duplicate codes as below.
public void addJobs(Job job){
this.likeJobs.add(job);
List<Job> jobsWithoutDuplicates = removeDuplicateFromJobs(this.likeJobs);
this.likeJobs.clear();
this.likeJobs.addAll(jobsWithoutDuplicates);
}
public void addJobs(List<Job> jobs){
this.likeJobs.addAll(jobs);
List<Job> jobsWithoutDuplicates = removeDuplicateFromJobs(this.likeJobs);
this.likeJobs.clear();
this.likeJobs.addAll(jobsWithoutDuplicates);
}
public void addGenres(Genre genre){
this.likeGenres.add(genre);
List<Genre> genresWithoutDuplicates = removeDuplicateFromGenres(this.likeGenres);
this.likeGenres.clear();
this.likeGenres.addAll(genresWithoutDuplicates);
}
public void addGenres(List<Genre> genres){
this.likeGenres.addAll(genres);
List<Genre> genresWithoutDuplicates = removeDuplicateFromGenres(this.likeGenres);
this.likeGenres.clear();
this.likeGenres.addAll(genresWithoutDuplicates);
}
public List<Job> removeDuplicateFromJobs(List<Job> jobs){
return jobs.stream().distinct().collect(Collectors.toList());
}
public List<Genre> removeDuplicateFromGenres(List<Genre> genres){
return genres.stream().distinct().collect(Collectors.toList());
}
I think I can definitely refact this but I don't know what to do.
The refactored code must be type-safe.
The refactored code must be threaded safe.
Do not malfunction after refactoring.
Given the conditions, is there any way to do a good refactoring without violating OOP's SOLID principle?
The first way I did it was the generic type.
I created addJobsOrGenres(List<?> JobsOrGenres).
Then i created an additional method called isInstanceOf().
Through the above two methods, both job and genre objects processed methods that enter the whatever, but I don't know if this is a beautiful refactoring.
Too long for a comment; adding as an answer.
If your addXXX methods take Set instead of List, you can get rid of removeDuplicateFromXXXX methods. Keep in mind proper implementation of equals and hashcode methods if you go ahead with Set.
You can get rid of addJobs(Job job). And let there be addJobs(Set<Job> jobs) only. I don't see a harm in that. This way you will have one method to modify in case a pre-processing or post-processing logic comes up in future. Same goes for addGenres.
The refactored code must be type-safe.
When you're doing List<Job> or List<Genere>, type-safety is taken care of. I wouldn't go with addJobsOrGenres (List<?> JobsOrGenres) - one new requirement comes for job or genere, you start adding more if-elses. This makes it more prone to mistake jobs for genere or vice-versa. Also, see point 2 above about pre and post processing as another reason why you shouldn't do this.
The refactored code must be threaded safe.
Your code does mutation of shared variables, it's not thread safe. You need to add a locking mechanism of some sort. Depending on your use-case (if there are many reads or writes), pick one of the Lock strategies.
I use crnk (JSON-API) in java project and I have 3 questions regarding its usage with spring boot and jpa - haven't found exact implementation details in documentation.
For example, I have 2 entities and respective tables:
#Entity
#JsonApiResource(type = "employee")
public class Employee {
#Id
#JsonApiId
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "typeId")
private EmployeeType employeeType; //stored in table as typeId
}
#Entity
#JsonApiResource(type = "type")
public class EmployeeType {
#Id
#JsonApiId
private int id;
private String typeName;
private int salary;
}
How should JsonApiRelation be introduced in order to be able to call "/employee/1" and "/employee/1/type" urls?
For example there is one more entity.
#Entity
#JsonApiResource(type = "project")
public class Project {
#Id
#JsonApiId
private int id;
private String supervisorName;
private String projectName;
}
First, I'd like to have List of Projects for each Employee, where he is a supervisor, joint by name and have it listed as attribute in Json.
Tried implementing it with #OneToMany and #JoinColumn annotations but got StackOverflowException. How could this be implemented. And second, how could this be implemented with Relation? Like "/employee/1/projects" url.
How should I implement custom filtering of results for findAll method? For example, I have a List of all Employees, but I'd like to exclude some of them from the response. Which class/method should be introduced for this behaviour?
#JsonApiRelation annotation should not be necessary. Crnk will detect the #ManyToOne annotation and map it accordingly.
in case of crnk-jpa it is sufficient to specify all relationships in JPA. Matching JSON API relationships. So your approach seems good. What was the StackoverflowException stacktrace? (next to the examples, there are also many example entities in crnk-jpa)
I would make use of a decorator. See http://www.crnk.io/documentation/#_request_filtering. RepositoryDecoratorFactory allows to place a custom repository between the caller and crnk-jpa (or any other kind of repository). There you can do any kind of modification perform (maybe) calling the "real" repository. => Will add an example for this
feel free also make open up tickets in crnk for any documentation/example clarifications.
I'm using SpringBoot and JPA to build a REST interface.
Now, I have a strange JSON returned for the list of products fetched from the database. Let's say that I have:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "categoryId", nullable = false, updatable = false)
private Category category;
...
}
#Entity
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "category", cascade = CascadeType.DETACH)
#OrderBy("name ASC")
private List<Product> products = Collections.emptyList();
...
}
The JPA repository for the Product is defined as:
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findAll();
}
In my controller I have:
#Autowired
private ProductRepository productRepo;
#RequestMapping("/all-products", method = RequestMethod.GET)
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("products", productRepo.findAll());
return model;
}
What is driving my crazy, is that if I try to call this service as follows:
$ curl localhost:8080/all-products
I get a recursive output due to the relationship between tables product and category, e.g.:
{"products":[{"id":1,"name":"Product1","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,...
What am I doing wrong?
You're not doing anything wrong (at least at the code level it's rather conceptual) - json serializer just goes like this:
Product - serialize it, but wait - there is a category field, so serializer must serialize the category field
Category - serialize it, but wait - there is a products field, so serializer must serialize each of the product in the list
Product - because your collection contains the product & product contains category it goes in a endless loop untill a timeout.
You must use a view or just skip it.
Use #JsonView
Use a view as a POJO
Return new ProductView that has all fields of product and a reference (category) to new CategoryView (you can end at this point) that has collection of (products) new ProductViewWithoutReferences, and so on
Use #JsonIgnore on a collection of products
And as a side note - if it's a #RestController and you're invoking "all-products" then it's a bit unusual to return something else than a list. Wrapping the response in a map is redundant. Many rest clients expect a list when they invoke list() method.
I know it's a bit late, but adding it here in case anybody faces the same problem.
Here is another relevant answer I could find which discuss about similar topic
https://stackoverflow.com/a/3359884/6785908
quoting it here
Jackson 1.6 has annotation-based support for handling such
parent/child linkage, see
http://wiki.fasterxml.com/JacksonFeatureBiDirReferences.
You can of course already exclude serialization of parent link already
using most JSON processing packages (jackson, gson and flex-json at
least support it), but the real trick is in how to deserialize it back
(re-create parent link), not just handle serialization side. Although
sounds like for now just exclusion might work for you.
EDIT (April 2012): Jackson 2.0 now supports true identity
references, so you can solve it this way also.
Adding #JsonIgnore worked for me
#OneToMany(mappedBy = "policy")
#JsonIgnore
private List<Payment> payments;
#JeanValjean your are the best
I have the following simple scenario:
Cars table: Id, Name, Model
Cars Schedule table:Id, CarId, ScheduleTime, ScheduleDate
I am using Spring MVC with Hibernate, with the structure of:
Domain
repo
repoImpl
service
serviceImpl
what I need to do is displaying the car name in the list of the Cars Schedule without having to add a field called CarName in the CarsSchedule table.
What is the best practice for doing this?
In Car entity you should have
#OneToMany(mappedBy = "car")
private List<CarsSchedule> schedules;
I assumed that the relation is #OneToMany, but you can just switch it to #OneToOne private CarsSchedule schedule;, if that's the case.
And in CarsSchedule entity, this
#ManyToOne
#JoinColumn(name = "CarId")
private Car car;
With this setup, once you have the carsSchedule instance in your controller (and model), you can display the name of the car on the page with #{carsSchedule.car.name}.
I think you should have a one-to-many relationship in the table Cars Schedule:
//in class CarsSchedule
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CAR_ID", nullable = false)
public Stock getCar() {
return this.car;
}
Then in the controller, you retrieve the list of CarSchedule (that has also the Car into it) and put the list into the model:
#RequestMapping(value = "car-list", method = RequestMethod.GET)
public String getList(HttpSession session, Map<String, Object> model) {
//get the list from the service
List<CarsSchedule> list = service.getCarScheduleList();
//put into the model
model.put("form", new PersonUserRoleForm());
return "mymodel";
}
then you have a mymodel.jsp maybe, where you can retieve the variable mymodel
You can follow this simple tutorial:
http://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation/
Ciao
If you're setting the annotations on the property accessor method, you can simply add
#Transient
public String getName() {
return car.getName();
}
It will be invisible for your database but visible for all other layers. In JSP you would access it as ${carsSchedule.name}, and it would be an immediate child in your JSON or XML if that is the representation you use
Even if your annotating properties themselves you can still do
#Transient
private String name;
public String getName() {
return car.getName();
}
I recon that your main idea is to avoid persisting another field. Note just that the transient annotation is the javax.persistence.Transient and it has no implication to the serialization just tells the persistence provider to ignore the field upon persisting