modelMapper's skip() method always return null - java

I cant set my id to null. because modelMapper's skip() method always return null. I don't know how to fix it. I trying to convert dto to entity.
I trying to convert dto to entity but
SubSectionGroupOptionsEntity skip = skip();
always return null. I am using some configuration.
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE).setAmbiguityIgnored(true).setDeepCopyEnabled(true).setSkipNullEnabled(true);
modelMapper.addMappings(new PropertyMap<SubSectionGroupOptionsDTO, SubSectionGroupOptionsEntity>() {
#Override
protected void configure() {
SubSectionGroupOptionsEntity skip = skip();
skip.setId(null);
}
});
QualitySectionMasterEntity qualitySectionMasterEntity = modelMapper.map(sectionSaveDTO, QualitySectionMasterEntity.class);
Look here's my SubSectionGroupOptionsDTO class :
#AllArgsConstructor
#ToString
#Builder
#Data
#NoArgsConstructor
public class SubSectionGroupOptionsDTO {
private Long id;
private String optionName;
private String inputType;
private List<SubSectionGroupOptionActionsDTO> quesAnsGrpOptionAction;
}
and my entity class is :
#NoArgsConstructor
#AllArgsConstructor
#Data
#ToString
#Builder
#Entity
#Table(name = "sub_section_group_options")
public class SubSectionGroupOptionsEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "option_name")
private String optionName;
#Column(name = "input_type")
private String inputType;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "sub_section_group_options_id")
private List<SubSectionGroupOptionActions> subSectionGroupOptionActionsSet;
}
Remember ::: SubSectionGroupOptionsEntity is a deep sub child of
QualitySectionMasterEntity
please look and provide a fixed solution :

Related

Error when mapping classes with attribute that has foreign key

I have the Hardware entity, HardwareDtoRequest and HardwareDtoResponse classes, where I'm using the modelMapper to map them. In the Hardware table, there is a foreign key to the Provider table.
The problem is that I am not able to map this attribute to HardwareDtoRequest, when I call the POST method in Postman passing only the provider_id in the request body it saves only one record with that particular ID, when trying to save again another record with the same ID it updates the old one.
How do I map this foreign key attribute to the DtoRequest and save?
Hardware.java
#Getter
#Setter
#Entity
public class Hardware {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 100)
private String name;
#ManyToOne
#JoinColumn(name = "provider_id")
private Provider provider;
}
Provider.java
#Getter
#Setter
#Entity
public class Provider {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 100)
private String name;
}
HardwareDtoRequest.java
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class HardwareDtoRequest {
#NotNull(message = "required field")
private String name;
#NotNull(message = "required field")
private Long providerId;
}
HardwareDtoResponse.java
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class HardwareDtoResponse {
private Long id;
private String name;
private ProviderDtoResponse provider;
}
HardwareMapper.java
public HardwareDtoResponse toHardwareDtoResponse(Hardware hardware) {
return mapper.map(hardware, HardwareDtoResponse.class);
}
public Hardware toHardware(HardwareDtoRequest hardwareDtoRequest) {
return mapper.map(hardwareDtoRequest, Hardware.class);
}
HardwareService.java
#Transactional
public HardwareDtoResponse save(HardwareDtoRequest hardwareDtoRequest) {
Hardware hardware = mapper.toHardware(hardwareDtoRequest);
Hardware saveHardware = hardwareRepository.save(hardware);
return mapper.toHardwareDtoResponse(saveHardware);
}
HardwareController.java
#PostMapping
public ResponseEntity<HardwareDtoResponse> save(#Valid #RequestBody HardwareDtoRequest hardwareDtoRequest) {
log.info("saving hardware: {}", hardwareDtoRequest);
HardwareDtoResponse hardware = hardwareService.save(hardwareDtoRequest);
return new ResponseEntity<>(hardware, HttpStatus.CREATED);
}
I managed to solve it, for those who have the same problem of mapping dtos with modelMapper, I use the following snippet in ModelMapperConfig:
#Configuration
public class ModelMapperConfig {
#Bean
public ModelMapper mapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
return modelMapper;
}
}
You can try to set provider manually. Like this:
public Hardware toHardware(HardwareDtoRequest hardwareDtoRequest) {
Hardware hardware = mapper.map(hardwareDtoRequest, Hardware.class);
Provider provider = providerRepository.findById(hardwareDtoRequest.providerId);
hardware.setProvider(provider);
return hardware;
}

Deleting an entity with one to one relation

My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .

Getting nulls while using ModelMapper

I'm trying to utilize the ModelMapper in my convertion process. What I need to do is to convert the Sample entity to SampleDTO object.
I have the Sample entity like the following:
#Entity
#Table(name = "sample", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Sample {
private static final String SEQUENCE = "SAMPLE_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "name")
private String name;
#Column
private String surname;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id_deetails")
private Details details;
}
Which holds the Details one:
#Entity
#Table(name = "details", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Details {
private static final String SEQUENCE = "DETAILS_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "street_name")
private String streetName;
#Column
private String city;
}
I'd like the DTO to be this format:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class SampleDTO {
private Long id;
private String name;
private String surname;
private String streetName;
private String city;
}
I also made a ModelMapper bean like:
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
And I made a converter component:
#Component
public class EntityDtoConverter {
private final ModelMapper modelMapper;
#Autowired
public EntityDtoConverter(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public SampleDTO sampleToDto(Sample entity) {
return modelMapper.map(entity, SampleDTO.class);
}
}
The problem is
when I try to use this mapper converter in my service
#Service
public class SampleService {
private final SampleRepository sampleRepository;
private final EntityDtoConverter entityDtoConverter;
#Autowired
public SampleService(SampleRepository sampleRepository, EntityDtoConverter entityDtoConverter) {
this.sampleRepository = sampleRepository;
this.entityDtoConverter = entityDtoConverter;
}
public List<SampleDTO> getSamples() {
List<SampleDTO> samples = sampleRepository.findAll()
.map(entityDtoConverter::sampleToDto);
return new List<SampleDTO>(samplesPage);
}
}
I get nulls in places of Details fields.
I have followed Baeldung's tutorial about model-to-dto conversion with ModelMapper and the documentation of it as well but the least wasn't much of help. There is something I'm missing and I have no idea what it is.
I'm working on:
Java 11
Spring Boot 2.3.0
ModelMapper 2.3.8
Try:
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
Also check: Modelmapper: How to apply custom mapping when source object is null?

Not exposing the path of entities that have a composite primary key to the front end when using the Springframework Page object

I'm working on an API endpoint that returns a Springframework Page response. I want the front end to be able to sort the data but I can't expect the front end to know that the column they want to sort on is actually inside a composite primary key.
In the example below (a simplified version of what I'm working on) you can see that the startDate column is inside a RouteEntityPk class, which is linked to the RouteEntity class with the #EmbeddedId annotation. To Sort on that column the front end would need to add ?sort=pk.startdate,asc to the request. I want the front end to only have to provide ?sort=startdate,asc.
Is there a way - using Spring magic - of having the repository know that startdate == pk.startdate, or will I have to write a translator which will remove the pk when showing the sort column to the front end, and add it where necessary when reading it from the request?
Controller:
#GetMapping(value = "routes/{routeId}", produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Page<Route>> getRouteByRouteId(#PathVariable(value = "routeId") final String routeId,
#PageableDefault(size = 20) #SortDefault.SortDefaults({
#SortDefault(sort = "order", direction = Sort.Direction.DESC),
#SortDefault(sort = "endDate", direction = Sort.Direction.DESC)
}) final Pageable pageable) {
return ResponseEntity.ok(routeService.getRouteByRouteId(routeId, pageable));
}
Service:
public Page<Route> getRouteByRouteId(String routeId, Pageable pageable) {
Page<RouteEntity> routeEntities = routeRepository.findByRouteId(routeId, pageable);
return new PageImpl<>(
Collections.singletonList(routeTransformer.toRoute(routeId, routeEntities)),
pageable,
routeEntities.getContent().size()
);
}
Repository:
#Repository
public interface RouteRepository extends JpaRepository<RouteEntity, RouteEntityPk> {
#Query(value = " SELECT re FROM RouteEntity re"
+ " AND re.pk.routeId = :routeId")
Page<RouteEntity> findByRouteId(#Param("routeId") final String routeId,
Pageable pageable);
}
Entities:
Route:
#Data
#Entity
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "ROUTE", schema = "NAV")
public class RouteEntity {
#EmbeddedId
private RouteEntityPk pk;
#Column(name = "NAME")
private String name;
#Column(name = "ORDER")
private Integer order;
#Column(name = "END_DTE")
private LocalDate endDate;
}
RoutePk:
#Data
#Builder(toBuilder = true)
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
public class RouteEntityPk implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "ROUTE_ID")
private String routeId;
#Column(name = "STRT_DTE")
private LocalDate startDate;
}
Models:
Route:
#Data
#Builder
public class Route {
public String name;
public String routeId;
public List<RouteItem> items;
}
Item:
#Data
#Builder
public class Item {
public Integer order;
public LocalDate startDate;
public LocalDate endDate;
}
Transformer:
public Route toRoute(String routeId, Page<RouteEntity> routeEntities) {
return Route.builder()
.name(getRouteName(routeEntities))
.routeId(routeId)
.items(routeEntities.getContent().stream()
.map(this::toRouteItem)
.collect(Collectors.toList()))
.build();
}
private Item toRouteItem(RouteEntity item) {
return ParcelshopDrop.builder()
.order(item.getOrder())
.startDate(item.getStartDate())
.endDate(item.getEndDate())
.build();
}
So it looks like the way to do this is to use the other way you can deal with composite primary key's in JPA, the annotation #IdClass. This way you can put the fields in the main entity and refer to them as such.
Below is a link to the baeldung article I followed and the changes to the entities I posted above that make this work:
https://www.baeldung.com/jpa-composite-primary-keys
Entities:
Route:
#Data
#Entity
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#IdClass(RouteEntityPk.class)
#Table(name = "ROUTE", schema = "NAV")
public class RouteEntity {
#Id
#Column(name = "ROUTE_ID")
private String routeId;
#Id
#Column(name = "STRT_DTE")
private LocalDate startDate;
#Column(name = "NAME")
private String name;
#Column(name = "ORDER")
private Integer order;
#Column(name = "END_DTE")
private LocalDate endDate;
}
RoutePk:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
public class RouteEntityPk implements Serializable {
private static final long serialVersionUID = 1L;
private String routeId;
private LocalDate startDate;
}
This is one solution, probably not the best, but you can transform Pageable object in order to replace the field name like this :
In your controller getRouteByRouteId method :
List<Order> orders = pageable.getSort().stream().map(o -> o.getProperty().equals("startdate") ? new Order(o.getDirection(), "pk.startdate"): o).collect(Collectors.toList());
Then you can call the service with the modified object :
return ResponseEntity.ok(routeService.getRouteByRouteId(routeId, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders))));

#JsonIgnoreProperties doesn't work in #OneToMany relation

#JsonIgnoreProperties doesn't work properly
I'm writing a one to many relation, one property can have multiply propertySale, I try to use #JsonIgnoreProperties to avoid infinite recursion, but for some reason it doesn't work when I try to save a propertySale. Counld someone please tell me where I do wrong?
In Property.java
#Data
#Getter
#Entity
#Table(name = "Property")
public class Property {
#Id
#GeneratedValue
private Long id;
...
#OneToMany(mappedBy="property", cascade = CascadeType.ALL, targetEntity = PropertySale.class)
#JsonIgnoreProperties("property")
private Set<PropertySale> propertySales = new HashSet<>();
...
public void addPropertySale(PropertySale propertySale){
this.propertySales.add(propertySale);
}
}
In PropertySale.java
#Data
#Getter
#Entity
#Table(name = "PropertySale")
public class PropertySale {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "property_id", referencedColumnName = "id")
#JsonIgnoreProperties("propertySales")
private Property property;
...
In PropertySaleServiceImp.java
#Service
public class PropertySaleServiceImp implements PropertySaleService{
#Autowired
private PropertySaleRepository propertySaleRepository;
#Autowired
private PropertyRepository propertyRepository;
#Override
public ResponseEntity<PropertySale> savePropertySale(PropertySale propertySale) {
Optional<Property> existPropertyOpt = this.propertyRepository.findById(propertySale.getProperty().getId());
if(existPropertyOpt.isPresent()){
Example<PropertySale> propertySaleExample = Example.of(propertySale);
Optional<PropertySale> existPropSale = this.propertySaleRepository.findOne(propertySaleExample);
if(existPropSale.isPresent()){
throw new PropertySaleAlreadyExistException();
}else{
Property existProperty = existPropertyOpt.get();
propertySale.setProperty(existProperty);
existProperty.addPropertySale(propertySale);
this.propertyRepository.save(existProperty);
return new ResponseEntity<>(propertySale, HttpStatus.CREATED);
}
}else{
throw new PropertyNotFoundException(propertySale.getProperty().getId());
}
}
I get
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
when the line this.propertyRepository.save(existProperty);
is executed.

Categories