I have 3 Models which belong together:
Device
Person
Inspection
An Inspection could have many devices and one person.
I realized these relations using a separate table:
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Table(name = "ACTION_INSPECTION")
public class ActionInspection implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long actionInspectionId;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "personId")
private Person person;
#NotNull
#JsonProperty("devices")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "deviceId")
private Device device;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "inspectionId")
private Inspection inspection;
//endregion
//constructors and getters & setters
}
Get- and Post-Methods of ActionInspectionController:
#GetMapping(value = "/all")
public List<ActionInspection> getAllActionInspections() {
return actionInspectionRepository.findAll();
}
#GetMapping(value = "")
public ResponseEntity<?> getActionInspectionById(#RequestParam long actionInspectionId ) {
Optional<ActionInspection> optionalActionInspection = actionInspectionRepository.findById(actionInspectionId);
ResponseEntity<?> result;
if (optionalActionInspection.isPresent()) {
ActionInspection actionInspection = optionalActionInspection.get();
result = new ResponseEntity<ActionInspection>(actionInspection, HttpStatus.OK);
}
else {
result = new ResponseEntity<String>(String.format("ActionInspection with Id %d doesn't exist", actionInspectionId), HttpStatus.NOT_FOUND);
}
return result;
}
#Transactional
#PostMapping(value = "")
public ResponseEntity<?> insertActionInspection(#Valid #RequestBody ActionInspection actionInspection, BindingResult bindingResult) {
ResponseEntity<?> result = null;
String errorMessage = "";
boolean error = false;
Optional<Person> optionalPerson = personRepository.findById(actionInspection.getPerson().getPersonId());
Optional<Device> optionalDevice = deviceRepository.findById(actionInspection.getDevice().getDeviceId());
if (optionalPerson.isPresent())
actionInspection.setPerson(optionalPerson.get());
else
result = new ResponseEntity<String>("Person not found", HttpStatus.NOT_FOUND);
if (!optionalDevice.isPresent())
actionInspection.setDevice(optionalDevice.get());
else
result = new ResponseEntity<String>("Device not found", HttpStatus.NOT_FOUND);
try {
inspectionRepository.save(actionInspection.getInspection());
actionInspectionRepository.save(actionInspection);
result = new ResponseEntity<ActionInspection>(actionInspection, HttpStatus.CREATED);
}
catch (Exception e) {
e.printStackTrace();
error = bindingResult.hasErrors();
errorMessage = bindingResult.toString();
result = new ResponseEntity<String>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
}
So, creating a new ActionInspection includes creating a new Inspection as well if needed.
Using the GetMethod to list all ActionInspections I get this:
[
{
"actionInspectionId": 1,
"person": {
"personId": 1,
"lastname": "Maier",
"firstname": "Sepp"
},
"inspection": {
"inspectionId": 3,
"inspectionDate": "2021-12-31",
"inspectionState": 2,
"inspectionInterval": 1.0
},
"devices": {
"deviceId": 3,
"deviceName": "Kaffeemaschine",
"deviceState": 2,
"deviceDescription": "praktisch"
}
},
{
"actionInspectionId": 2,
"person": {
"personId": 1,
"lastname": "Maier",
"firstname": "Sepp"
},
"inspection": {
"inspectionId": 3,
"inspectionDate": "2021-12-31",
"inspectionState": 2,
"inspectionInterval": 1.0
},
"devices": {
"deviceId": 2,
"deviceName": "Farbdrucker",
"deviceState": 1,
"deviceDescription": "sehr leise"
}
}
]
But I need to get and post Json which includes a list of all devices which belongs to the same inspection(Id):
[
{
"person": {
"personId": 1,
"lastname": "Maier",
"firstname": "Sepp"
},
"inspection": {
"inspectionId": 3,
"inspectionDate": "2021-12-31",
"inspectionState": 2,
"inspectionInterval": 1
},
"devices": [
{
"deviceId": 3,
"deviceName": "Kaffeemaschine",
"deviceState": 2,
"deviceDescription": "praktisch"
},
{
"deviceId": 2,
"deviceName": "Farbdrucker",
"deviceState": 1,
"deviceDescription": "sehr leise"
}
]
}
]
Is there a way I can realize this?
Many thanks in advance for every input I'll get - also for criticism (no matter if good or bad).
Related
I have the following entity:
public class EventCategoryEntity {
private UUID id;
#Column("category_name")
private String categoryName;
#Column("group_name")
private String groupName;
}
Which I retrieve via Flux(one to many relationship):
public Flux<EventCategoryEntity> findAllCategories() {
return eventRepository.findAll();
}
#Query("select ec.id, ec.name as category_name,ecg.name as group_name" +
"from event_category ec left join event_category_relation ecr on (ec.id = ecr.event_category_id) " +
"left join event_category_group ecg on (ecr.event_category_group_id = ecg.id);")
Flux<EventCategoryEntity> findAll();
Then output via Controller in JSON response like:
{
"id": "87108493-4fc1-4b12-8ffc-e10aa039fc39",
"categoryName": "soccer",
"groupName": "team",
},
{
"id": "87108493-4fc1-4b12-8ffc-e10aa039fc39",
"categoryName": "soccer",
"groupName": "ball",
}
]
But I would like to aggregate response by id like this:
[
{
"id": "87108493-4fc1-4b12-8ffc-e10aa039fc39",
"categoryName": "soccer",
"groupName: ["team", "ball"]
}
]
I've prepared DTO object, but I don't know how this flux map into the this:
public class EventCategory {
private UUID id;
private String categoryName;
private List<CategoryGroup> categoryGroup;
private class CategoryGroup {
private String groupName;
}
}
You can use collectMultimap which collects all elements emitted by this Flux into a Map.
#Test
public void collectMultimap() {
Flux<EventCategoryEntity> flux1 = Flux.just(
new EventCategoryEntity("87108493-4fc1-4b12-8ffc-e10aa039fc39","soccer","team"),
new EventCategoryEntity("87108493-4fc1-4b12-8ffc-e10aa039fc39","soccer","ball"),
new EventCategoryEntity("57108499-4fc1-4b52-8ffc-e11aa039fc85","soccer","tennis")
);
flux1.collectMultimap(EventCategoryEntity::getId, EventCategoryEntity::getGroupName)
.map(ids -> ids.keySet().stream().map(t -> {
List<String> categoryGroupList = ids.get(t).stream().toList();
Optional<EventCategoryEntity> f = flux1.toStream().filter(pp -> pp.getId().equals(t)).findFirst();
return new EventCategoryDTO(f.get().getId(), f.get().getCategoryName(), categoryGroupList);
}).toList())
.subscribe(result -> System.out.println(result.toString()));
}
Output:
[EventCategoryDTO(id=87108493-4fc1-4b12-8ffc-e10aa039fc39,
categoryName=soccer, categoryGroup=[team, ball]),
EventCategoryDTO(id=57108499-4fc1-4b52-8ffc-e11aa039fc85,
categoryName=soccer, categoryGroup=[tennis])]
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#collectMultimap-java.util.function.Function-java.util.function.Function-
I have this object that I'm retrieving from antoher microservice.
#Service
public class WebClientService {
public Mono<CuCoPerson> getCuCoPerson(Integer cucoId, String GS_AUTH_TOKEN) {
WebClient webClient = WebClient.create();
return webClient.get()
.uri(GET_RELATION_BY_ID + cucoId)
.header("Accept", "application/json")
.header("Authorization", GS_AUTH_TOKEN)
.retrieve()
.bodyToMono(CuCoPerson.class)
.map(cuCoPerson -> {
List<CustomerRelation> matches = cuCoPerson.getRelatedCustomers()
.stream()
.filter(relation -> relation.getSystemId().equals(400) || relation.getSystemId().equals(300) || relation.getSystemId().equals(410))
.filter(relation -> relation.getCustomerId().contains("F"))
.collect(Collectors.toList());
cuCoPerson.setRelatedCustomers(matches);
return cuCoPerson;
});
}
The controller is as follows:
#GetMapping("/getCucoId/{cucoId}")
public Mono<CuCoPerson> getCucoRelationById(#PathVariable Integer cucoId, #RequestHeader(value="Authorization") String GS_AUTH_TOKEN) {
return webClientService.getCuCoPerson(cucoId, GS_AUTH_TOKEN);
}
It returns a list of related customers like this:
{
"id": 1,
"relatedCustomers": [
{
"customerId": "xxx",
"systemId": 999
}
]
}
And now I need to add this list of customers to my PeopleDTO class which is as follows:
public class PeopleDTO {
private String processType;
private String operation;
private String entity;
private String entityType;
private Long id;
private Document document;
#Getter
#Setter
class Customer {
private String systemId;
private String customerId;
}
private List<Customer> customers;
}
UPDATE:
Here I'm hardcoding my PeopleDTO just as an example:
public PeopleDTO createPeople(Long id) {
PeopleDTO people = new PeopleDTO();
people.setProcessType("ONLINE");
people.setOperation("UPDATE");
people.setEntity("DOCUMENT");
people.setEntityType("DOCUMENT");
people.setIdCuco(id);
people.setDocument(new Document());
people.setCustomers(......);
}
So the result should be something like this:
{
"type": "ONLINE",
"operation": "UPDATE",
"id": 1,
"entity": "DOCUMENT",
"entityType": "NIE",
"documents": {
"id": 1,
"additionals": {
"issuing_authority": "Spain",
"country_doc": "ES",
"place_of_birth": "",
"valid_from": "1995-08-09",
"valid_to": "0001-01-01"
},
"code": "X12345",
"typeDocument": "NIE"
},
"id": 1,
"relatedCustomers": [
{
"customerId": "xxx",
"systemId": 999
}
]
}
The controller that supposed to retrieve this info is:
#GetMapping("getId/{cucoId}")
public PeopleDTO getFullPeople(#PathVariable Long id) {
return webClientService.createPeople(id);
}
How should I set the customers in here?
The json below is what was sent to me, when I findByPinCode. I have created relations between them in the following accordance with the json answer. Here are the relationships.
{
"RequestIdentifier": "00000000-0000-0000-0000-000000000000",
"Status": {
"Name": "string",
"Code": 0,
"Message": "string"
},
"Response": {
"Person": {
"Pin": "string",
"LastName": "string",
"FirstName": "string",
"FatherName": "string",
"BirthDate": "string"
},
"Company": {
"Voen": "string",
"Name": "string",
"PersonType": "string",
"Persons": [
{
"Pin": "string",
"LastName": "string",
"FirstName": "string",
"FatherName": "string",
"BirthDate": "string"
}
]
},
"Farms": [
{
"Id": 0,
"Name": "string",
"Fields": [
{
"VillageName": "string",
"DocType": "string",
"FieldAmount": 0,
"Unit": "string",
"Plants": [
{
"Name": "string",
"FieldAmount": 0,
"Unit": "string"
}
]
}
],
"Animals": [
{
"Sort": "string",
"Count": 0
}
],
"Bees": [
{
"Sort": "string",
"Count": 0
}
]
}
]
}
}
(One)Company To (Many)Persons
(One)FarmInfo To (Many)FarmFields
(One)FarmFields To (Many)Plants
(One)FarmInfo To (Many)Animals
(One)FarmInfo To (Many)Bees
and thus converting them to my dto class first. Then I want to convert them to my entity classes and persist them completely to the database.
This is my DTO class.
#Data
#NoArgsConstructor
#AllArgsConstructor
public class FarmInfoMainResult {
#JsonProperty(value = "RequestIdentifier")
private String requestIdentifier;
#JsonProperty(value = "Status")
private ResponseStatus status;
#JsonProperty(value = "Response")
private FarmInfoData response;
}
and this is my business logic layer of method, which I want to that in here convertint the dto class to the BaseEntity class and at the end of I want to persist BaseEntity class to the database.
#Override
public ResponseEntity<FarmInfoMainResult> findFarmInfoByPin(String pin, String username, String branchCode) {
String requestIdentifier = UUID.randomUUID().toString();
ResponseEntity<FarmInfoMainResult> farmInfoPinServiceResponse = clientUtility.getForObject(farmInfoForPinUrl + pin, requestIdentifier, FarmInfoMainResult.class);
System.out.println("farmInfoPinServiceResponse " + farmInfoPinServiceResponse.getBody());
/*
Here are I want to convert farmInfoPinServiceResponse variable to the my Base Entity class and so persist it to the database, so that
thus, values will be automatically added to them as they are related to each other in other relational databases.
*/
return farmInfoPinServiceResponse;
}
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String requestIdentifier;
#OneToOne(cascade = CascadeType.ALL)
private Status status;
#OneToOne(cascade = CascadeType.ALL)
private Response response;
}
But I am getting the following error in my Response class. basic' attribute type should not be a container
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Response {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private PersonFarmInfo personFarmInfo;
private CompanyFarmInfo companyFarmInfo;
private FarmInfo farmInfo;
private FarmFieldInfo farmFieldInfo;
private FarmPlantInfo farmPlantInfo;
private FarmAnimalInfo farmAnimalInfo;
private FarmBeeInfo farmBeeInfo;
#OneToOne(mappedBy = "response")
private BaseEntity baseEntity;
}
If I don't create main entity class, there will be a situation like this. I will add the properties of each class that is in the dto to the properties of each of my entity class and print them to the database. But by insert to a single entity class I want others to be affected as well. I wanted to solve the issue by creating a main entity class like this, but I'm getting the following error.
As I said at the beginning, the codes below do this by separating them into parts. These codes my old version codes.
#Override
public ResponseEntity<FarmInfoMainResult> findFarmInfoByPin(String pin, String username, String branchCode) {
String requestIdentifier = UUID.randomUUID().toString();
ResponseEntity<FarmInfoMainResult> farmInfoPinServiceResponse = clientUtility.getForObject(farmInfoForPinUrl + pin, requestIdentifier, FarmInfoMainResult.class);
System.out.println("farmInfoPinServiceResponse " + farmInfoPinServiceResponse.getBody());
saveFarmInfoPin(farmInfoPinServiceResponse.getBody(), username, branchCode, pin);
return farmInfoPinServiceResponse;
}
Here I am sending the object stored in dto to my saveFarmInfoPin method.Then this method works. Here, my method saves all my object values stored in dto separately.
private void saveFarmInfoPin(FarmInfoMainResult farmInfoMainResult, String username, String branchcode, String pin) {
// try {
if (farmInfoMainResult.getResponse() != null) {
saveFarmInfoPersonPin(farmInfoMainResult.getResponse().getPerson(), farmInfoMainResult.getRequestIdentifier(), username, branchcode, pin);
saveFarmInfoCompanyPin(farmInfoMainResult.getResponse().getCompany(), farmInfoMainResult.getRequestIdentifier(), username, branchcode);
saveFarminfo(farmInfoMainResult.getResponse().getFarms());
/* } catch (Exception e) {
e.printStackTrace();
}
*/
}
}
Finally, my following methods divide each of my dto objects into parts, first converting them to my entity class, and then adding them to the database via repository.
private void saveFarmInfoPersonPin(FarmInfoPerson farmInfoPersonDto, String requestIdentifier, String username, String branchCode, String pin) {
if (farmInfoPersonDto != null) {
FarmPersonInfo newFarmInfoPerson = new FarmPersonInfo();
newFarmInfoPerson.setRequestIdentifier(requestIdentifier);
newFarmInfoPerson.setBranchCode(branchCode);
newFarmInfoPerson.setUsername(username);
newFarmInfoPerson.setPin(pin);
newFarmInfoPerson.setPin(farmInfoPersonDto.getPin());
newFarmInfoPerson.setFatherName(farmInfoPersonDto.getFatherName());
newFarmInfoPerson.setFirstName(farmInfoPersonDto.getFirstName());
newFarmInfoPerson.setBirthDate(farmInfoPersonDto.getBirthDate());
personFarmInfoRepository.save(newFarmInfoPerson);
}
}
private void saveFarmInfoCompanyPin(FarmInfoCompany farmInfoCompany, String requestIdentifier, String username, String branchCode) {
List<FarmPersonInfo> newFarmInfoPersonList = new ArrayList<>();
if (farmInfoCompany != null) {
FarmCompanyInfo newCompanyFarmInfo = new FarmCompanyInfo();
newCompanyFarmInfo.setName(farmInfoCompany.getName());
newCompanyFarmInfo.setRequestIdentifier(requestIdentifier);
newCompanyFarmInfo.setUsername(username);
newCompanyFarmInfo.setBranchCode(branchCode);
newCompanyFarmInfo.setPersonType(farmInfoCompany.getPersonType());
newCompanyFarmInfo.setVoen(farmInfoCompany.getVoen());
// CompanyFarmInfo companyFarmInfo = companyFarmInfoRepository.save(newCompanyFarmInfo);
for (FarmInfoPerson farmInfoPerson : farmInfoCompany.getPersons()) {
FarmPersonInfo personInfo = new FarmPersonInfo();
personInfo.setPin(farmInfoPerson.getPin());
personInfo.setFatherName(farmInfoPerson.getFatherName());
personInfo.setFirstName(farmInfoPerson.getFirstName());
personInfo.setBirthDate(farmInfoPerson.getBirthDate());
personInfo.setFarmCompanyInfo(newCompanyFarmInfo);
newFarmInfoPersonList.add(personInfo);
}
personFarmInfoRepository.saveAll(newFarmInfoPersonList);
}
}
private void saveFarminfo(List<FarmInfoFarm> farmInfoFarms) {
List<FarmFieldInfo> newFarmFieldInfoList = new ArrayList<>();
List<FarmAnimalInfo> newFarmAnimalInfoList = new ArrayList<>();
List<FarmBeeInfo> newFarmBeeInfoList = new ArrayList<>();
List<FarmPlantInfo> newFarmPlantInfoList = new ArrayList<>();
for (FarmInfoFarm farmInfoFarm : farmInfoFarms) {
FarmInfo newFarmInfo = new FarmInfo();
newFarmInfo.setName(farmInfoFarm.getName());
newFarmInfo.setFarmInfoId(farmInfoFarm.getId());
FarmInfo farmInfo = farmInfoRepository.save(newFarmInfo);
for (FarmInfoField farmInfoField : farmInfoFarm.getFields()) {
FarmFieldInfo newFarmFieldInfo = new FarmFieldInfo();
newFarmFieldInfo.setVillageName(farmInfoField.getVillageName());
newFarmFieldInfo.setDocType(farmInfoField.getDocType());
newFarmFieldInfo.setFieldAmount(farmInfoField.getFieldAmount());
newFarmFieldInfo.setUnit(farmInfoField.getUnit());
newFarmFieldInfo.setFarmInfo(farmInfo);
newFarmFieldInfoList.add(newFarmFieldInfo);
for (FarmInfoPlant farmInfoPlant : farmInfoField.getPlants()) {
FarmPlantInfo newfarmPlantInfo = new FarmPlantInfo();
newfarmPlantInfo.setName(farmInfoPlant.getName());
newfarmPlantInfo.setFieldAmount(farmInfoPlant.getFieldAmount());
newfarmPlantInfo.setUnit(farmInfoPlant.getUnit());
newfarmPlantInfo.setFarmFieldInfo(newFarmFieldInfo);
newFarmPlantInfoList.add(newfarmPlantInfo);
}
farmPlantInfoRepository.saveAll(newFarmPlantInfoList);
}
farmFieldInfoRepository.saveAll(newFarmFieldInfoList);
for (FarmInfoAnimal farmInfoAnimal : farmInfoFarm.getAnimals()) {
FarmAnimalInfo newFarmAnimalInfo = new FarmAnimalInfo();
newFarmAnimalInfo.setCount(farmInfoAnimal.getCount());
newFarmAnimalInfo.setSort(farmInfoAnimal.getSort());
newFarmAnimalInfo.setFarmInfo(farmInfo);
newFarmAnimalInfoList.add(newFarmAnimalInfo);
}
farmAnimalInfoRepository.saveAll(newFarmAnimalInfoList);
for (FarmInfoBee farmInfoBee : farmInfoFarm.getBees()) {
FarmBeeInfo newfarmBeeInfo = new FarmBeeInfo();
newfarmBeeInfo.setCount(farmInfoBee.getCount());
newfarmBeeInfo.setSort(farmInfoBee.getSort());
newfarmBeeInfo.setFarmInfo(farmInfo);
newFarmBeeInfoList.add(newfarmBeeInfo);
}
farmBeeInfoRepository.saveAll(newFarmBeeInfoList);
}
}
But as I said, instead of separating them all into separate-parts, can I add all of these objects to my main entity class and bring all these particles together through just one method?
I'm thinking of something like this, now I will use jackson to convert all the values in the json value I have received to the classes it belongs to, then I will add them to my main class and finally add my main class to my database. Since the main class will already have all the relations in itself, I think that all of them will be affected by doing this operation once.
entity
Entity
#Table(name ="posts")
public class Post {
#Id
#SequenceGenerator(name = "jpaSequence.Post",
sequenceName = "SEQUENCE_POST",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.Post")
private Long id;
private String subject;
#OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private User user;
public Post() {
}
#Entity
#Table(name = "comments")
public class Comment {
#Id
#SequenceGenerator(name = "jpaSequence.Comment",
sequenceName = "SEQUENCE_COMMENT",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.Comment")
private Long id;
private String reply;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private User user;
public Comment() {
}
#Entity
#Table(name = "users")
public class User {
#Id
#SequenceGenerator(name = "jpaSequence.User",
sequenceName = "SEQUENCE_USER",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.User")
private Long id;
private String name;
private String email;
public User() {
}
mapper
#Mapper(componentModel = "spring" )
public interface PostMapper {
Post postDtoToPostEntity(PostDto dto);
PostDto postEntityToPostDto(Post entity);
Iterable<Post> postListDtoToPostListEntity(Iterable<PostDto> list);
Iterable<PostDto> postListEntityToPostListDto(Iterable<Post> list);
}
#Mapper(componentModel = "spring")
public interface CommentMapper {
Comment commentDtoToCommentEntity (CommentDto dto);
CommentDto commentEntityToCommentDto(Comment entity);
Iterable<Comment> commentListDtoToCommentListEntity(Iterable<CommentDto> list);
Iterable<CommentDto> commentListEntityToCommentListDto(Iterable<Comment> list);
}
-service
#Service
public class PostReadServiceImpl implements PostReadService {
private PostRepository repository;
private PostTransformer transformer;
private CommentTransformer transformerComment;
#Autowired
public PostReadServiceImpl(PostRepository repository, PostTransformer transformer,
CommentTransformer transformerComment) {
this.repository = repository;
this.transformer = transformer;
this.transformerComment = transformerComment;
}
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = find(id);
return this.transformer.transformEntityToDto(entity);
}
private Post find(Long id){
return this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
}
#Transactional
#Override
public Iterable<CommentDto> getListEntriesCommentById(Long id) {
Iterable<Comment> listComment = findListComment(id);
Iterable<CommentDto> commentDtoList = this.transformerComment.transformListEntityToDtoList(listComment);
return commentDtoList;
}
private Iterable<Comment> findListComment(Long id){
Post post = this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
List<Comment> comments = post.getComments();
return comments;
}
}
When using MapStruct, I get infinite recursion.
Even if I didn 't request the related comments entity from the received object , I still see infinite nesting in debug mode, and this is despite the fact that I selected
fetch = FetchType.LAZY
Who has any ideas how to fix this and why it works like this ?
Solution
rest
#RestController
#RequestMapping("api/read")
public class PostReadRest {
private static final String PATH_TO_READ_POST_BY_ID = "post/{id}";
private static final String PATH_TO_READ_POST_LIST = "post/list";
private PostReadService service;
#Autowired
public PostReadRest(PostReadService service) {
this.service = service;
}
#GetMapping(PATH_TO_READ_POST_BY_ID)
public PostDto getPostById(#PathVariable String id){
Long idLong = Long.valueOf(id);
return service.getEntryById(idLong);
}
}
service
#Service
public class PostReadServiceImpl implements PostReadService {
private PostRepository repository;
private PostTransformer transformer;
private CommentTransformer transformerComment;
#Autowired
public PostReadServiceImpl(PostRepository repository, PostTransformer transformer,
CommentTransformer transformerComment) {
this.repository = repository;
this.transformer = transformer;
this.transformerComment = transformerComment;
}
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = find(id);
PostDto postDto = this.transformer.transformEntityToDto(entity);
return postDto;
}
private Post find(Long id){
return this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
}
}
mapper
#Mapper(componentModel = "spring")
public interface PostMapper {
Post postDtoToPostEntity(PostDto dto);
#Mapping(target = "post.comments", ignore = true)
CommentDto toCommentDto(Comment entity);
PostDto postEntityToPostDto(Post entity);
Iterable<Post> postListDtoToPostListEntity(Iterable<PostDto> list);
Iterable<PostDto> postListEntityToPostListDto(Iterable<Post> list);
}
#Mapping(target = "post.comments", ignore = true)
post - this is the name of a field 'Comment' Entity.
comments - this is the name of a field 'Post' Entity, but ,
it is field here is second level field. And This is must ignore.
transformers
#Component
public class PostTransformer {
private PostMapper mapper;
#Autowired
public PostTransformer(PostMapper mapper) {
this.mapper = mapper;
}
public PostDto transformEntityToDto(Post entity){
PostDto postDto = this.mapper.postEntityToPostDto(entity);
return postDto;
}
public Post transformDtoToEntity(PostDto dto){
return this.mapper.postDtoToPostEntity(dto);
}
public Iterable<PostDto> transformListEntityToDtoList(Iterable<Post> listEntity){
Iterable<PostDto> postDtoList = this.mapper.postListEntityToPostListDto(listEntity);
return postDtoList;
}
public Iterable<Post> transformListDtoToEntityList(Iterable<PostDto> listDto){
return this.mapper.postListDtoToPostListEntity(listDto);
}
}
result
{
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [
{
"id": 1,
"reply": "Nice !!",
"post": {
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
},
"user": {
"id": 2,
"name": "user2",
"email": "user2#test.com"
}
},
{
"id": 2,
"reply": "Cool !!",
"post": {
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
},
"user": {
"id": 3,
"name": "user3",
"email": "user3#test.com"
}
}
],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
}
It's important.
If you didn't point over a method (it have an annotation #Override) the annotation #Transactional , you encounter with an Exception, if you use - FetchType.LAZY
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = findEntry(id);
List<Comment> comments = entity.getComments();
PostDto postDto = this.transformer.transformEntityToDto(entity);
return postDto;
}
While the transaction is been performing, while the Mapper is been performing, there will be perform additional query, that get nested entries from tables related.
I have 2 entities, wired by One-To-One dependency.
User:
public class User {
#OneToOne(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, mappedBy = "user")
private UserParams params;
...
}
UserParams:
public class UserParams {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
...
}
I transform it with ModelMapper:
#Bean
public ModelMapper modelMapper() {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setSkipNullEnabled(true)
.setPropertyCondition(Conditions.isNotNull())
.setFieldAccessLevel(PRIVATE);
return mapper;
}
I'll explain how mapper works. I exclude 'user' field from UserParams mapping & map it separately. This way, I reseive only ID of 'user' field for UserParams & full dto 'params' for User.
//exclude from mapping
#PostConstruct
public void init() {
mapper.createTypeMap(UserParams.class, UserParamsDto.class)
.addMappings(m -> m.skip(UserParamsDto::setUser)).setPostConverter(toDtoConverter());
mapper.createTypeMap(UserParamsDto.class, UserParams.class)
.addMappings(m -> m.skip(UserParams::setUser)).setPostConverter(toEntityConverter());
}
//map separately
#Override
protected void mapSpecificFields(UserParams source, UserParamsDto destination) {
whenNotNull(source.getUser(), u -> destination.setUser(u.getId()));
}
#Override
protected void mapSpecificFields(UserParamsDto source, UserParams destination) {
whenNotNull(source.getUser(), u -> destination.setUser(userRepository.findById(u).orElse(null)));
}
Postconverters realize transformation:
protected Converter<E, D> toDtoConverter() {
return context -> {
E source = context.getSource();
D destination = context.getDestination();
mapSpecificFields(source, destination);
return context.getDestination();
};
}
protected Converter<D, E> toEntityConverter() {
return context -> {
D source = context.getSource();
E destination = context.getDestination();
mapSpecificFields(source, destination);
return context.getDestination();
};
}
Than, when I call UserParams (by /params?id=1), I reseive JSON with filled user:
{
"id": 5,
"created": "2019-06-13T20:07:52.221",
"updated": null,
"user": 1,
"height": 180,
"weight": 75,
"gender": null,
"birthDate": null
}
But when I call user, field params transforms to dto, except field user:
{
"id": 1,
"created": "2019-06-13T19:50:16",
"updated": null,
"params": {
"id": 2,
"created": "2019-06-13T19:50:22",
"updated": null,
"user": null, //null
"height": 180,
"weight": 75,
"gender": null,
"birthDate": null
}
}
As I understand, when maps User, mapper not call postConverter for nested objects, I checked using breakpoints. Why? What to do?
Project here.