I'm using a DTO object to retrieve information from my #RequestBody in spring rest controller and using the same DTO object in json response.
I want to hide some fields completely from response.
I tried the #BeanMapping(ignoreByDefault = true)which return null for unmapped properties but my question is:
Is there a way to completely hide the unmapped properties based on different mapping methods
Example
public Class Order {
private Long id;
private String name;
private String otherField;
}
public Class OrderDto {
private Long id;
private String name;
private String otherFieldA
private String otherFieldB;
}
#Mapper
public interface OrderMapper
//..
#Mappings({
#Mapping(target = "id", source ="id"),
#Mapping(target = "name", source ="name"),
#Mapping(target = "otherFieldA", source ="otherField")
})
#BeanMapping(ignoreByDefault = true)
OrderDto fieldAOnlyOrderToOrderDtoMapper(Order order);
#Mappings({
#Mapping(target = "id", source ="id"),
#Mapping(target = "name", source ="name"),
#Mapping(target = "otherFieldB", source ="otherField")
})
#BeanMapping(ignoreByDefault = true)
OrderDto fieldBOnlyOrderToOrderDtoMapper(Order order);
}
thus the result of calling the first mapper [
fieldAOnlyOrderToOrderDtoMapper
will return an OrderDto object that has no property named (otherFieldB)
and the call for the second mapper
fieldBOnlyOrderToOrderDtoMapper
will return an OrderDto object that has no field named (otherFiledA)
IIUC you want to ignore specific fields.. That's done like this:
#Mapper
public interface OrderMapper
//..
#Mapping(target = "otherFieldA", source ="otherField")
#Mapping(target = "otherFieldB", ignore=true")
OrderDto fieldAOnlyOrderToOrderDtoMapper(Order order);
#Mapping(target = "otherFieldA", ignore=true)
#Mapping(target = "otherFieldB", source ="otherField")
OrderDto fieldBOnlyOrderToOrderDtoMapper(Order order);
}
Related
I used spring jpa specification to build dynamically an entity query.
It's working perfect but the query returns all entity fields which makes the performance slower.
I want to fetch specific entity fields only and not fetching all entity fields and dependencies which I don't want and I will not use.
I search on the web, I tried some scenarios but without any lack.
Can anyone suggest any solution on this?
Thanks in advance
Here is what I have.I'm using spring boot 2.2.4
public class Concert {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String code;
#Column
private double totalIncome;
#Column
private double totalExpenses;
#Column
private double totalBudget;
#ManyToOne(targetEntity = Orchestra.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "orchestra_id")
private Orchestra orchestra;
#ManyToOne(targetEntity = ConcertStatus.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "concert_status_id")
private ConcertStatus status;
/* other fields */
}
Specification:
public class ConcertSpecification implements Specification<Concert> {
#Override
public Predicate toPredicate(Root<Concert> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (Criterion criteria : criteriaList) {
/* predicates builder here */
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
Repository:
public interface ConcertDao extends JpaRepository<Concert, Long>, JpaSpecificationExecutor<Concert>, PagingAndSortingRepository<Concert, Long> { }
ConcertService:
public interface ConcertService {
Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable);
}
ConcertServiceImpl:
#Service(value = "concertService")
public class ConcertServiceImpl implements ConcertService {
public Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable){
List<Concert> list = new ArrayList<>();
concertDao.findAll(spec).iterator().forEachRemaining(list::add);
return new PageImpl<Concert>(list);
}
}
Usage of projections with specifications are not supported and there is a PR for it that has been hanging for over five years.
when I use #RequestParam with required = true on rest and while testing this on swagger, it will be shown along with *required mark next to it.
#GetMapping(path = "/getinfo")
public ResponseEntity<?> getMyInfo(#RequestParam(value = "input", required = true) int input, other request parameters)
But now how can I achieve same on swagger if i have mapped url with object using #ModelAttribute .
#GetMapping(path = "/getinfo")
public ResponseEntity<?> getMyInfo(#ModelAttribute MyObject myObject)
You can try using the annotation #ApiParam
#GetMapping(path = "/getinfo")
public ResponseEntity<?> getMyInfo(#ModelAttribute("myObject") MyObject myObject)
Inside your MyObject class
public class MyObject {
private long id;
#ApiParam(name = "name", value = "Name is Mandatory", required = true)
private String name;
}
Now, name will be a *required field.
i have this DTO:
#NoArgsConstructor
public class DataDTO implements DTO {
private static final long serialVersionUID = -5105904799152965475L;
private Long deviceId;
private OffsetDateTime generatedOn;
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
public void setGeneratedOn(OffsetDateTime generatedOn) {
this.generatedOn = generatedOn;
}
}
i have this MongoDB document:
#Document(collection = "data")
#EqualsAndHashCode
public class DataDocument {
private static final long serialVersionUID = 1772572723546311500L;
#Id
private IdByDeviceIdAndGeneratedOn id;
public DataDocument() {
}
public IdByDeviceIdAndGeneratedOn getId() {
return id;
}
public void setId(IdByDeviceIdAndGeneratedOn id) {
this.id = id;
}
}
and this is the #Id class for MongoDB Document:
#EqualsAndHashCode
#ToString
public class IdByDeviceIdAndGeneratedOn {
#Id
private final Long deviceId;
#Id
#Field("generated_on")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private final OffsetDateTime generatedOn;
public IdByDeviceIdAndGeneratedOn(final Long deviceId, final OffsetDateTime generatedOn) {
this.deviceId = Objects.requireNonNull(deviceId);
this.generatedOn = Objects.requireNonNull(generatedOn);
}
public Long getDeviceId() {
return deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
}
this is the mapper for this Key class:
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, componentModel = "spring")
public interface IdByDeviceIdAndGeneratedOnMapper {
default IdByDeviceIdAndGeneratedOn toId(final Long deviceId, final OffsetDateTime generatedOn) {
return new IdByDeviceIdAndGeneratedOn(deviceId, generatedOn);
}
default Long getDeviceId(final IdByDeviceIdAndGeneratedOn id) {
return id.getDeviceId();
}
default OffsetDateTime getGeneratedOn(final IdByDeviceIdAndGeneratedOn id) {
return id.getGeneratedOn();
}
and this is the #Mapper for DataDTO and DataDocument:
#Mapper( unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {IdByDeviceIdAndGeneratedOnMapper.class,
AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class
})
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
}
this is the generic mapper:
/**
* Contract for a generic dto to entity mapper.
*
* #param <DTO> - DTO source type parameter.
* #param <DOCUMENT> - MongoDB Document destination type parameter.
*/
public interface DocumentMapper<DTO, DOCUMENT> {
DOCUMENT toDocument(DTO dto);
DTO toDto(DOCUMENT document);
}
Currently i'm receiving this errors:
for MongoDB Data docment:
Unmapped target property: "id".
for DTO:
Unmapped target properties: "deviceId, generatedOn".
How to solve this errors without loosing immutability of Id class?
What you are trying to do is to use (using constructors to construct objects) is not yet supported. There is an open issue for it #73.
However, you can achieve what you are looking for by using Object factories, this is for the toDocument mapping, for the toDto mapping you can use nested source mappings.
Your mapper would look like:
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Mapping(target = "id", source = "dto")
#Override
DataDocument toDocument(DataDTO dto);
#ObjectFactory
default IdByDeviceIdAndGeneratedOn createId(DataDTO dto) {
return dto == null ? null : new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn());
}
#Mapping(target = "deviceId", source = "id.deviceId")
#Mapping(target = "generatedOn", source = "id.generatedOn")
#Override
DataDTO toDto(DataDocument document);
}
NB: You can also make DataDocumentMapper abstract class and make the createId method protected, in case you don't want to expose it in the interface
this is solved my problem, but this doesnt look elegant.
Maybe there is more elegant way?
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
imports = {IdByDeviceIdAndGeneratedOn.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Override
#Mapping(target = "id", expression = "java( new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn()) )")
DataDocument toDocument(DataDTO dto);
#Override
#Mapping(target = "deviceId", expression = "java( document.getId().getDeviceId() )")
#Mapping(target = "generatedOn", expression = "java( document.getId().getGeneratedOn() )")
DataDTO toDto(DataDocument document);
}
I have written my custom RevisionEntity class to store additional data (for example username), like below:
#Entity
#RevisionEntity(AuditListener.class)
#Table(name = "REVINFO", schema = "history")
#AttributeOverrides({
#AttributeOverride(name = "timestamp", column = #Column(name = "REVTSTMP")),
#AttributeOverride(name = "id", column = #Column(name = "REV")) })
public class AuditEntity extends DefaultRevisionEntity {
private static final long serialVersionUID = -6578236495291540666L;
#Column(name = "USER_ID", nullable = false)
private Long userId;
#Column(name = "USER_NAME")
private String username;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
I can see that all rows in database are correctly stored, REVINFO table contains also username.
I would like to query database to get detailed information from my custom RevisionEntity, like username.
How can I do it? Is there any supported API to get it?
Lets assume you know the identifier of the entity you're interested in the revision entity metadata for, you can easily query that information using the following approach:
final AuditReader auditReader = AuditReaderFactory.get( session );
List<?> results = auditReader.createQuery()
.forRevisionsOfEntity( YourEntityClass.class, false, false )
.add( AuditEntity.id().eq( yourEntityClassId ) )
.getResultList();
The returned results will contain an Object array, e.g. Object[] where results[1] will hold the revision entity instance which contains the pertinent information your wanting.
For more details, you can see the java documentation comments here
If you only have the revision number, you can access just the revision entity instance directly by:
// I use YourAuditEntity here because AuditEntity is actually an Envers class
YourAuditEntity auditEntity = auditReader
.findRevision( YourAuditEntity.class, revisionId );
For more details on the AuditReader interface, you can see the java documentation here
I would like to make some query where my predicate is like this:
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<ProviderService> criteriaQuery = criteriaBuilder.createQuery(ProviderService.class);
// FROM
Root<ProviderService> providerService = criteriaQuery.from(ProviderService.class);
// SELECT
criteriaQuery.select(providerService);
// WHERE'S PREDICATE
List<Predicate> predicates = new ArrayList<>();
if(providers != null && providers.size() > 0) {
predicates.add(providerService.get(ProviderService_.provider).in(providers));
}
criteriaQuery.where(predicates.toArray(new Predicate[] { }));
TypedQuery<ProviderService> query = getEntityManager().createQuery(criteriaQuery);
return query.getResultList();
I check search for provider offers (ProviderService) that are supplied by any provider form collection (List) of providers.
I have received exception something like this:
Caused by: java.lang.IllegalArgumentException: Unaware how to convert value [pl.salonea.entities.Provider#85eab3b6 : pl.salonea.entities.Provider] to requested type [java.lang.Long]
at org.hibernate.jpa.criteria.ValueHandlerFactory.unknownConversion(ValueHandlerFactory.java:258)
at org.hibernate.jpa.criteria.ValueHandlerFactory.access$000(ValueHandlerFactory.java:34)
at org.hibernate.jpa.criteria.ValueHandlerFactory$LongValueHandler.convert(ValueHandlerFactory.java:152)
at org.hibernate.jpa.criteria.ValueHandlerFactory$LongValueHandler.convert(ValueHandlerFactory.java:139)
at org.hibernate.jpa.criteria.predicate.InPredicate.<init>(InPredicate.java:130)
at org.hibernate.jpa.criteria.predicate.InPredicate.<init>(InPredicate.java:108)
at org.hibernate.jpa.criteria.CriteriaBuilderImpl.in(CriteriaBuilderImpl.java:529)
at org.hibernate.jpa.criteria.expression.ExpressionImpl.in(ExpressionImpl.java:79)
EDITED:
I think it could have something to do with ProviderService composite Id that consists of (Provider, Service) and is defined as follows:
#Id
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "provider_id", referencedColumnName = "provider_id", nullable = false, columnDefinition = "BIGINT UNSIGNED")
public Provider getProvider() {
return provider;
}
public void setProvider(Provider provider) {
this.provider = provider;
}
#Id
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "service_id", referencedColumnName = "service_id", nullable = false, columnDefinition = "INT UNSIGNED")
public Service getService() {
return service;
}
public void setService(Service service) {
this.service = service;
}
And have proper IdClass as below:
public class ProviderServiceId implements Serializable {
private Long provider;
private Integer service;
/* constructors */
public ProviderServiceId() { }
public ProviderServiceId(Long providerId, Integer serviceId) {
this.provider = providerId;
this.service = serviceId;
}
// etc.
OR maybe there isn't such possibilities to compare entity attribute against list of possible values of that attribute (list of entities)
Suggested joining seems to work correctly:
// inner joining
if(provider == null) provider = providerService.join(ProviderService_.provider);
predicates.add(provider.in(providers));
I think that searching by passing list of Provider IDs rather than Provider entities will also work but haven't checked it.