Spring data swagger generates requestbody field from getter method - spring

I have this DTO which i use as the #RequestBody of an endpoint in my spring data app:
#Builder
#AllArgsConstructor
#EqualsAndHashCode
#Getter
#NoArgsConstructor
public class CreateBridgeInput {
private String customIdName;
private BridgeType type;
private String zoneId;
#Override
public String getId() {
return this.customIdName;
}
}
If I make CreateBridgeInput implement this interface
public interface EntityInputBase {
public String getId();
}
The generated swagger file includes a field called id in the expected request body, despite there being no field called id.
Why does it do this, and how do I prevent this from happening? I found that I can use the #Hidden annotation from swagger, but if a class that extends EntityInputBase does actually have an id field, it will hide it when it shouldn't.

Related

Instructing Sping Data MongoDB to use correct mapping between ObjectId and its class

I cannot retrieve the 2nd level nested objects in Spring Data MongoDB
I have nested collection in MongoDB to retrieve with Spring. Imagine this schema
#Data
#Builder
#Document(collection = "emitted")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Emitter{
#Id
private String id;
#Field("installation")
#DocumentReference(lazy = true)
private Installaton installation;
// other fields
#Data
#Builder
#Document(collection = "installation")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Installation {
#Id
private String id;
#Field("subject")
#DocumentReference(lazy = true)
private Subject subject;
// other fields
#Data
#Builder
#Document(collection = "subject")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Subject {
#Id
private String id;
// other fields
Plus, I have MapStruct to map nested object field to string, for the purpose of avoiding cyclic reference introducing the search by id of the collection:
#ObjectFactory
public <T> T map(#NonNull final String id, #TargetType Class<T> type) {
return mongoTemplate.findById(id, type);
}
Everything works at first level, but at nested level I have this error:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.bson.types.ObjectId] to type [com.package.collections.Subject]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:175)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1826)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1818)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1337)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1311)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$DefaultConversionContext.convert(MappingMongoConverter.java:2371)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$ConversionContext.convert(MappingMongoConverter.java:2174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1936)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:638)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:549)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:527)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:427)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:423)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:120)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3326)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2940)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2618)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2588)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:922)
at com.package.myapp.services.mapper.ReferenceMapper.map(ReferenceMapper.java:26)
at com.package.myapp.services.mapper.InstallationMapperImpl.toEntity(InstallationMapperImpl.java:102)
When asking the conversion, the findById works correctly and retrieve the object and the nested one. It fails when the request is for 2nd level nested object, where the ObjectId is retrieved but cannot be converted and fails.
I'm answering myself because I found a solution suited for my problem.
I only needed the entity object with the id, so I wrote a converter:
public class ObjectIdToSubjectConverter implements Converter<ObjectId, Subject> {
#Override
public Subject convert(ObjectId source) {
return Subject.builder().id(source.toString()).build();
}
}
And register it as a bean:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.singletonList(new ObjectIdToSubjectConverter());
}
}

How to do a Findby In Springboot Mongo repository for Nested Objects

Need your help here in a Spring Data CrudRepository on how to do a findby of nested fields.
My class is of following structure and I need to query using truckId which is of the nested objects
#Document(collection = "unt-truck")
public class TruckModelDTO {
private String type;
private TestDTO testDTO;
}
Class TestDTO.java
public class TestDTO{
private TruckDTO truckDTO;
Private String version;
}
Class TruckDTO.java
public class TruckDTO {
private String truckId;
private String legacySystem;
}
Class TruckRepository.java
#Repository
public interface TruckRepository extends MongoRepository<TruckModelDTO, String> {
// TruckModelDTO findByTruckId(String truckid);
}
So how should I use findby for truckId which is inside a nested class?
You can use with either Entity or DTO as response. but you have to mention the relation between two or three Entity based on you requirement.
I have created Entity. you have to create DTO with same based on entity then apply main DTO as return type in JPA Repository query.
1.Entity
#Entity
public class TruckModel {
private String type;
// mention the relation based on your requirement
private Test test;
}
Entity
#Entity
public class Test {
// here mention the relation based on you requirement
private Truck truck;
Private String version;
}
3.Entity
#Entity
public class Truck {
private String truckId;
private String legacySystem;
}
Repository
#Repository
public interface TruckModelRepository extends MongoRepository<TruckModel, String> {
TruckModelDTO findByTestTruckTruckId(String truckid);
}

Why are nested objects retrieved from a OpenFeign request to a Spring Data Rest endpoint null?

I have the following domain classes in my client app:
#Value
public class Car {
private Long id;
private Model model;
}
#Value
public class Make {
private Long id;
private String name;
private Model model;
}
#Value
public class Model {
private Long id;
private String name;
}
My client app tries to get this data from a Spring Data Rest endpoint that return a HATEOAS response. The client does this via OpenFeign:
#FeignClient(name="car-service")
#Validated
public interface CarClient {
#GetMapping("/api/cars")
CollectionModel<Car> getAllCars();
}
But the each car has its make=null. How can I get make and model to be returned?

Spring Boot locking code to get an unique id

I wrote a controller that must return an unique String. The requirement is that two calling of this controller never return the same String, even after years and even if the code will scale to more VMs.
My question is if the following code is correct to achieve to declared purpose, or if you have any hint.
Controller:
#RestController
public class UtilityController {
#Autowired
UtilityServices utilityServices;
#GetMapping("/uniqueIdentifier")
#ResponseBody
public String uniqueIdentifier() {
return utilityServices.getUniqueIdentifier();
}
Service:
#Service
public class UtilityServices {
#Autowired
private UniqueIdRepository uniqueIdRepository;
#Transactional
public String getUniqueIdentifier() {
String uniqueId = RandomString.getSecureRandomString();
while (uniqueIdRepository.existsById(uniqueId)) {
uniqueId = RandomString.getSecureRandomString();
}
uniqueIdRepository.save(new UniqueId(uniqueId));
return uniqueId;
}
}
Entity:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode
#ToString
public class UniqueId implements Serializable {
#Id
private String uniqueId;
}
Repository:
public interface UniqueIdRepository extends CrudRepository<UniqueId, String> {
}
That's all. I omit the code the RandomString class because it's not relevant in this context: I wrote a code based on SecureRandom, it is very likely that each time it returns a different String, but I have no guarantees about it. Let's assume that sooner or later my RandomString.getSecureRandomString() method can return the same String.
I'm not sure if the #Transactional annotation guarantees that the getUniqueIdentifier() method never throws an error.
The much better idea at your case will be using UUID:
Thus, anyone can create a UUID and use it to identify something with near certainty that the identifier does not duplicate one that has already been, or will be, created to identify something else. Information labelled with UUIDs by independent parties can, therefore, be later combined into a single database or transmitted on the same channel, with a negligible probability of duplication.
#Service
public class UtilityServices {
#Autowired
private UniqueIdRepository uniqueIdRepository;
#Transactional
public String getUniqueIdentifier() {
String uniqueId = String.format("%s-%s",
RandomStringUtils.randomAlphanumeric(4),
UUID.randomUUID().toString().replace("-", "")
);
// you could left this check
while (uniqueIdRepository.existsById(uniqueId)) {
uniqueId = UUID.randomUUID().toString().replace("-", "");
}
uniqueIdRepository.save(new UniqueId(uniqueId));
return uniqueId;
}
}
BTW you could use #Data for Model:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class UniqueId implements Serializable {
private static final long serialVersionUID = 0L;
#Id
private String uniqueId;
}
And don't forget about serialVersionUID.
Useful references:
What is a serialVersionUID and why should I use it?
thanks to #M.Deinum for #Data details for entitites

No converter found capable of converting from type to type

I am getting the following stacktrace:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [referencedata.ABDeadlineType] to type [referencedata.DeadlineType]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:187)
at org.springframework.data.repository.query.ResultProcessor$ProjectingConverter.convert(ResultProcessor.java:256)
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter$1.convert(ResultProcessor.java:201)
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:212)
at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:149)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:121)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:483)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy143.findAllSummarizedBy(Unknown Source)
at
My classes are the following
DeadlineType
#Data
public class DeadlineType extends DefaultIdAndText {
#Value("#{target.id}")
String id;
#Value("#{target.code}")
String text;
#Value("#{target.id}")
public String getId() {
return id;
}
#Value("#{target.code}")
public String getText() {
return text;
}
}
ABDeadlineType
#Data
#Entity
#Table(name = "deadline_type")
#AllArgsConstructor
#NoArgsConstructor
public class ABDeadlineType {
private #Id
String id;
private String code;
}
DefaultIdAndText
#Data #AllArgsConstructor
#NoArgsConstructor
public class DefaultIdAndText implements IdAndText {
public DefaultIdAndText(IdAndText idAndText){
this.id = idAndText.getId();
this.text = idAndText.getText();
}
#NotEmpty String id;
String text;
}
DeadlineTypeRepository
public interface DeadlineTypeRepository extends JpaRepository<ABDeadlineType, Long> {
List<DeadlineType> findAllSummarizedBy();
}
Update
Could it be an issue that the projection/mapping using #Value("#{target.id}") format, does not work correctly because these have been done on a class and not on an interface???
Return ABDeadlineType from repository:
public interface ABDeadlineTypeRepository extends JpaRepository<ABDeadlineType, Long> {
List<ABDeadlineType> findAllSummarizedBy();
}
and then convert to DeadlineType. Manually or use mapstruct.
Or call constructor from #Query annotation:
public interface DeadlineTypeRepository extends JpaRepository<ABDeadlineType, Long> {
#Query("select new package.DeadlineType(a.id, a.code) from ABDeadlineType a ")
List<DeadlineType> findAllSummarizedBy();
}
Or use #Projection:
#Projection(name = "deadline", types = { ABDeadlineType.class })
public interface DeadlineType {
#Value("#{target.id}")
String getId();
#Value("#{target.code}")
String getText();
}
Update:
Spring can work without #Projection annotation:
public interface DeadlineType {
String getId();
String getText();
}
You may already have this working, but the I created a test project with the classes below allowing you to retrieve the data into an entity, projection or dto.
Projection - this will return the code column twice, once named code and also named text (for example only). As you say above, you don't need the #Projection annotation
import org.springframework.beans.factory.annotation.Value;
public interface DeadlineTypeProjection {
String getId();
// can get code and or change name of getter below
String getCode();
// Points to the code attribute of entity class
#Value(value = "#{target.code}")
String getText();
}
DTO class - not sure why this was inheriting from your base class and then redefining the attributes. JsonProperty just an example of how you'd change the name of the field passed back to a REST end point
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class DeadlineType {
String id;
// Use this annotation if you need to change the name of the property that is passed back from controller
// Needs to be called code to be used in Repository
#JsonProperty(value = "text")
String code;
}
Entity class
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Data
#Entity
#Table(name = "deadline_type")
public class ABDeadlineType {
#Id
private String id;
private String code;
}
Repository - your repository extends JpaRepository<ABDeadlineType, Long> but the Id is a String, so updated below to JpaRepository<ABDeadlineType, String>
import com.example.demo.entity.ABDeadlineType;
import com.example.demo.projection.DeadlineTypeProjection;
import com.example.demo.transfer.DeadlineType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ABDeadlineTypeRepository extends JpaRepository<ABDeadlineType, String> {
List<ABDeadlineType> findAll();
List<DeadlineType> findAllDtoBy();
List<DeadlineTypeProjection> findAllProjectionBy();
}
Example Controller - accesses the repository directly to simplify code
#RequestMapping(value = "deadlinetype")
#RestController
public class DeadlineTypeController {
private final ABDeadlineTypeRepository abDeadlineTypeRepository;
#Autowired
public DeadlineTypeController(ABDeadlineTypeRepository abDeadlineTypeRepository) {
this.abDeadlineTypeRepository = abDeadlineTypeRepository;
}
#GetMapping(value = "/list")
public ResponseEntity<List<ABDeadlineType>> list() {
List<ABDeadlineType> types = abDeadlineTypeRepository.findAll();
return ResponseEntity.ok(types);
}
#GetMapping(value = "/listdto")
public ResponseEntity<List<DeadlineType>> listDto() {
List<DeadlineType> types = abDeadlineTypeRepository.findAllDtoBy();
return ResponseEntity.ok(types);
}
#GetMapping(value = "/listprojection")
public ResponseEntity<List<DeadlineTypeProjection>> listProjection() {
List<DeadlineTypeProjection> types = abDeadlineTypeRepository.findAllProjectionBy();
return ResponseEntity.ok(types);
}
}
Hope that helps
Les
I have met the same problem recently with spring-data-jpa:2.5.0.
Solution (for queries with no #Query annotation):
For class-based projection (DTOs), the problem is the #NoArgsConstructor in the DTO class. Revemoving it should make things work.
Something interesting I found during debug:
With the presence of a non argument constructor, the returnedType somehow was created with 0 input properties.
When a query is actually created, JpaQueryCreator (spring-data-jpa) would check if it needs to do custom construction according to the number of input properties.
As it's not the case with 0 input properties, it would then return the whole entity instance.
https://github.com/spring-projects/spring-data-jpa/blob/main/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java#L169
Finally when the result is being returned, the target type and returned type don't match, as there is no converter available to convert from the entity instance to the projectiong dto. The error was thrown.
https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/query/ResultProcessor.java#L162
Simple Solution::
use {nativeQuery=true} in your query.
for example
#Query(value = "select d.id,d.name,d.breed,d.origin from Dog d",nativeQuery = true)
List<Dog> findALL();
If you look at the exception stack trace it says that, it failed to convert from ABDeadlineType to DeadlineType. Because your repository is going to return you the objects of ABDeadlineType. How the spring-data-jpa will convert into the other one(DeadlineType). You should return the same type from repository and then have some intermediate util class to convert it into your model class.
public interface ABDeadlineTypeRepository extends JpaRepository<ABDeadlineType, Long> {
List<ABDeadlineType> findAllSummarizedBy();
}
Turns out, when the table name is different than the model name, you have to change the annotations to:
#Entity
#Table(name = "table_name")
class WhateverNameYouWant {
...
Instead of simply using the #Entity annotation.
What was weird for me, is that the class it was trying to convert to didn't exist. This worked for me.
Well I have another answer I have used Interfaces for Projections and Classes for
Dto's and I am using ModelMapper to map my Projections to Dto Class
So my 1 Dto class may have many Projections which can mapped to Dto and used to Taste
gradle
implementation 'org.modelmapper:modelmapper:3.1.0'
#Autowired
private ModelMapper modelMapper;
List<UserDto> usersdto = repository.findUserByRoleName().stream().map(userprojection -> modelMapper.map(userprojection, UserDto.class))
.collect(Collectors.toList());
My projection is like this
public interface UserProjection {
String getId();
String getEmail();
}
My dto is
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String phone;
private String email;
}
And I am able to get fields from custom queries
Change the class name to DeadlineType inside
extends JpaRepository<class, type>
For example:
In your code, the repository where you placed you query extends JpaRepository that with class and id type <ABDeadlineType, Long>. So it expects to return ABDeadlineType data.
public interface DeadlineTypeRepository extends JpaRepository<ABDeadlineType, Long> {
List<DeadlineType> findAllSummarizedBy();
}
As you want to get DeadlineType data, you should keep your query in such repository like
public interface DeadlineTypeRepository extends JpaRepository<DeadlineType, Long>
So, either replace the class name inside JpaRepository<>
Or place your query in another repository. Then you don't need to do any mapping or write extra codes for it.
In my case, it worked.

Resources