Perform Mapstruct mapping outside of Hibernate session - java

I am using Spring Data and Mapstruct and I don't want hibernate to blindly load all the elements while mapping entity to dto.
Example:
public class VacancyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "job_category_id", nullable = false)
JobCategoryEntity jobCategory;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id", nullable = false)
CompanyEntity company;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employer_created_by", nullable = false)
EmployerProfileEntity employerCreatedBy;
#Column(nullable = false)
String title;
.... }
DTO:
public class VacancyDto {
Integer id;
String title;
CompanyDto company;
EmployerProfileDto employerCreatedBy;
JobCategoryDto jobCategory;
...}
So I have two methods findByIdWithCompanyAndCity and findByIdWithJobAndCityAndEmployer in VacancyRepository to perform only one SQL request.
And two #Transactional methods in my VacancyService: findWithCompanyAndCity and findWithCompanyAndCityAndEmployer.
Best practice is returning Dto from Service layer, so we need to parse Entity to Dto in the Service.
And I really don't want to just leave whole mapping in #Transactional (session) because if I add some field really deep into my entity, Mapstruct just trigger N+1 problem.
Best that I came up with, is to include each inner entity into method and check manually that Mapstruct don't add some new methods. (it is faster then checking names)
Ex:
#Mapping(target = "id", source = "entity.id")
#Mapping(target = "description", source = "entity.description")
#Mapping(target = "jobCategory", source = "jobCategoryDto")
#Mapping(target = "employerCreatedBy", source = "employerProfileDto")
#Mapping(target = "city", source = "cityDto")
#Mapping(target = "company", ignore = true)
VacancyDto toDto(VacancyEntity entity,
JobCategoryDto jobCategoryDto,
EmployerProfileDto employerProfileDto,
CityDto cityDto);
....
But this doesn't fix the real issue. There are still session while mapping, so it can lead to N+1 problem.
So I came up with several solutions
Use special method in Service to trigger #Transactional method and then map into DTO out of session scope. But it seems really ugly to double methods in Service
Return Entity from Service (which is Bad Practice) and map into DTO there.
I know that I'll get LazyInitializationException in both cases, but it seems to me like it more robust and scalable then just unpredictably SELECT.
How do I perform the mapping from entity to DTO in the service layer but outside the Hibernate session in an elegant way?

You didn't ask a question but it seems the question is supposed to be:
How do I perform the mapping from entity to DTO in the service layer but outside the Hibernate session in an elegant way.
I'd recommend the TransactionTemplate for this.
Usage looks like this:
#Autowired
VacancyRepository repo;
#Autowired
TransactionTemplate tx;
void someMethod(String company, String city){
VacancyEntity vac = tx.execute(__ -> repo.findWithCompanyAndCity(company, city));
return mappToDto(vac);
}
That said, I think you are using the wrong a approach to solve the underlying problem.
I suggest you take a look at having a test to verify the number of SQL statements executed.
See https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/ for a way to do that.

To avoid the N + 1 problem you still need to use an entity graph, although I think this is a perfect use case for Blaze-Persistence Entity Views.
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(VacancyEntity.class)
public interface VacancyDto {
#IdMapping
Integer getId();
String getTitle();
CompanyDto getCompany();
EmployerProfileDto getEmployerCreatedBy();
JobCategoryDto getJobCategory();
#EntityView(CompanyEntity.class)
interface CompanyDto {
#IdMapping
Integer getId();
String getName();
}
#EntityView(EmployerProfileEntity.class)
interface EmployerProfileDto {
#IdMapping
Integer getId();
String getName();
}
#EntityView(JobCategoryEntity.class)
interface JobCategoryDto {
#IdMapping
Integer getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
VacancyDto a = entityViewManager.find(entityManager, VacancyDto.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<VacancyDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Related

Hibernate - Entity to JSON

I need to convert to json an Entity with JsonManagedReference and JsonBackReference implementations:
#Entity
#Table(name = "myparenttable", schema = "myschema", catalog = "mydb")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Parent implements Serializable {
private Integer id_parent;
private String name;
#JsonManagedReference
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<Child> children;
//getters and setters
}
#Entity
#Table(name = "mychildtable", schema = "myschema", catalog = "mydb")
public class Child implements Serializable {
private Integer id_child;
private String description;
#JsonBackReference
private Parent parent;
//getters and setters
}
With this setup, the persist function is straightforward, I just perform a
em.persist(parent);
and both entities are inserted into the database; but also I need to convert those entities into json for audit purposes. I get a infinite recursion error when doing this:
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(parent);
Is there a way to do both?
You may want to annotate your parent object into Child class with
#JsonIgnore
private Parent parent;
in this way the reference of the parent object isn't put into the serialized json object.
Check if you realy need to implement Serializable interface
This is a perfect use case for using DTOs with Blaze-Persistence Entity Views.
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(Parent.class)
public interface ParentDto {
#IdMapping
Integer getId();
String getName();
List<ChildDto> getChildren();
#EntityView(Child.class)
interface ChildDto {
#IdMapping
Integer getId();
String getDescription();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ParentDto a = entityViewManager.find(entityManager, ParentDto.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
On top solving your serialization issue, using Blaze-Persistence Entity-Views will also improve performance because it only select the columns that are actually needed.

Fetch join hibernate conditionally or change the entity design to conditional fetch on children?

I know for a fact that with clause on fetch join are not allowed by hibernate
I am using spring data jpa and postgres.
Here is how my entity is designed
public class Organisation {
#Id
private Long id;
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.EXTRA)
private Set<Assignment> assignments = new HashSet<>();
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
private List<Event> events;
}
public class Event {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisations_id", nullable = false)
private Organisation organisation;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
}
public class EventValue {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "event_id")
private Event Event;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "assignment_id")
private Assignment assignment;
}
public class Assignment {
#Id
private Long id;
#OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
Kind of a three way mapping. What the above entity design says is:
one organisation can have many events
one events can have many event values
one organisation can have many assignments
one assignment can be mapped to only one organisation and whithin the event of this organisation it is supposed to have only one event value (but as per entity design above it can have set of values which is not directly mapped to assignment)
So, I tried to query something like this.
#Query("select assignment from Assignment left join fetch assignment.organisation org
left join fetch org.event event left join fetch event.eventValues eventValue
with eventValue.assignment.id=?1 where assignment.id=?1)
Assignment getByAssignmentId(Long id);
What am I trying to achive with the query ?
To get assignment with given (id) -> organisation -> list of activities with HashSet containing only ONE activity value mapped to assignment.
The query is obviously going to fail because of using with clause on fetch join. I somehow feel the entity has 3 way dependency so it might be wrong.
I do not want to generic jdbcTemplate solution or SqlResultMapping solution where we need to do some kind of projection and set values manually. Is there a ORM solution to solve this problem ?
The reason why a WITH or ON clause is disallowed for join fetches is pretty simple. Hibernate works on managed entities, which means, once the entities are managed by the current persistence context, changes done to these objects will be flushed back to the database at the end of the transaction.
Now, if you were allowed to use the WITH or ON clause in a join fetch, the querying itself could alter the managed state of a collection, which would lead to UPDATE/DELETE statements to flush the collection changes back. Since this is completely unexpected, but a necessary side effect, it is disallowed.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Assignment.class)
interface AssignmentDto {
Long getId();
OrganisationDto getOrganisation();
}
#EntityView(Organisation.class)
interface OrganisationDto {
Long getId();
List<EventDto> getEvents();
}
#EntityView(Event.class)
interface EventDto {
Long getId();
#Mapping("eventValues[assignment.id = VIEW_ROOT(id)]")
EventValueDto getEventValue();
}
#EntityView(EventValue.class)
interface EventValueDto {
Long getId();
// Other stuff
}
The JOIN condition is modeled in the mapping expression eventValues[assignment.id = VIEW_ROOT(id)] which translates to what you would expect.
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AssignmentDto dto = entityViewManager.find(entityManager, AssignmentDto.class, id);
But 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
It will only fetch the mappings that you tell it to fetch.

Dynamic fetching for relations in Spring JPA

I want to be able to dynamically load the relations of my entity, depending on which RestService got called.
Entity classes:
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private Buyer buyer;
// some more attributes
}
#Entity
public class Buyer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// some more attributes
}
RestController class:
#GetMapping
public Iterable<Order> getAll() {
// here I want JPA to NOT load the buyers for the order
return orderRepository.findAll();
}
#GetMapping("/{id}")
public Order get(#PathVariable("id") String id) {
// here I want JPA to load the buyers for the order
return orderRepository.findById(Long.parseLong(id)).orElseThrow();
}
None of the two fetchtypes LAZY and EAGER or json annotations (like #JsonIgnore, #JsonIdentityInfo, #JsonManagedReference and #JsonBackReference) seem to make this possible as far as I understood and tried.
If this is not possible, maybe someone can explain how to solve this problem then. On the one hand I sometimes need those relations in my frontend to display some values and on the other hand when I always load them I get huge performance problems or infinity recursions.
I don't think JPA supports your use case directly.
One option is to create the same entity twice - one with eager and the other with lazy. Switch them in the methods.
Another option is to use a DTO (Data Transfer Object) as the response, instead of the entity class itself. You will have to write a mapper logic to convert an entity to DTO though.

How to map a DTO to multiple entities?

I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.

Automatic fill Pojo using annotation with orientdb

first point is I am useing orientdb version 2.1.7.
I try to realize a project with orientdb inlcuding jpa. As described in this doc automatic loading, saving and deleting works for simple POJOs.
But there are two points that did not work out.
I want to make a property unique. I know it works when I do it programmatically like this:
OrientVertexType vertexType = graph.createVertexType(vertexName);
vertexType.createProperty("id", OType.STRING);
vertexType.createIndex("ididx", OClass.INDEX_TYPE.UNIQUE, "id");
But is there a way to do this via annotations? The JPA annotaion (#Column(unique = true, nullable = false)) seems not to work.
I have two VertexTypes which are connected by Edges. Also I want that the collection is automatic loaded via annotation. Example (getters and setters are not listed):
The User Object:
public class MyUser implements iMyUser {
private String id;
private String name;
private Set<MyGroup> groups;
...
}
The Group Object:
public class MyGroup implements iMyGroup {
private String name;
private String id;
...
}
In JPA you can add something like #JoinTable(name = "table", joinColumns = { #JoinColumn(name = "colname") }, inverseJoinColumns = { #JoinColumn(name = "colname") }) to the groups property in MyUser and when you call the methode getGroups() you get the groups which have a relation. Is there a annotation in orientdb that supports such an behaviour?
I think this (#Adjacency) might be a solution but till now I didn't have any success in implementing it.
Also is there a list or something which annotations are supported?
Regards,
foo
The second point is possible with tinkerpop frames. #Adjacency annotation for direct relations between objects and #GremlinGroovy for queries. It is a bit strange to have the annotation at the interface, but however it works.
For the first point there might be no solution via annotation, but you can create unique index via the orientdb backend.

Categories

Resources