please help me to solve the following issue:
I have a class, where several fields are marked as #NotNull:
public class SearchCommentRequest {
#NotNull
private Date fromDate;
#NotNull
private Date toDate;
//...
}
Object if this class is passed to controller as #RequestBody annotated also with #Valid:
#PostMapping(value = "/comment/search", consumes="application/json", produces = "text/csv")
public ResponseEntity<byte[]> searchComments(#RequestBody #Valid SearchCommentRequest searchRequest) {
List<SearchCommentResult> comments = commentService.searchComments(searchRequest);
So, I expect that if either fromDate or toDate is null - exception will be thrown.
Writing my integration tests, I decided to check this validation case as well:
#Test
public void searchCommentsValidateRequest() throws Exception {
ObjectMapper mapper = new ObjectMapper();
SearchCommentRequest request = new SearchCommentRequest();
// toDate = null;
request.setFromDate(new Date());
String requestBody = mapper.writer().writeValueAsString(request);
mockMvc.perform(post(COMMENT_SEARCH_ENDPOINT)
.contentType("application/json")
.content(requestBody))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().is(400));
}
But it looks like mockMvc is ignoring validation. Searching for the same issues, I found several sources where solution was adding the following dependencies:
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
But it didn't help.
I'm using Spring 4.3.3.RELEASE and manually added to pom.xml the following dependency:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
Actually, #Notnull is often use at entity level.
It will validate and throw exception when you persist entity automatically.
If you want to validate at controller and use #Valid.
You should declare more about BindingResult result
and check errors
if(result.hasErrors()){
//do something or throw exceptions
}
Related
I switched to Spring Boot 3 and setup a simple project.
This contains input validation. I created a simple model and annotated the fields
#Data
#NoArgsConstructor
public class DemoUserCreateDto {
#Email(message = "email must be valid")
private String emailContentCreator;
#NotNull(message = "must not be null")
private String emailContentConsumer;
}
On my Controller level I have a simple endpoint:
#PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public String createUsers(#RequestBody #Valid DemoUserCreateDto demoUserCreateDto) {
return "DONE";
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
PROBLEM:
The validation is done on the #NotNull field. The #Email field does not have any effect.
import is:
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
The exception thrown on the NotNull validation is:
org.springframework.validation.BindException
but I was expecting a
MethodArgumentNotValidException
WHAT I'VE TRIED:
I tried to annotate my controller with #Validated. No success.
The thing is that I've set this up in previous projects with spring boot 2.x.
I've seen that in termins of validator libraries, jakarta is used instead of javax.
Has something changed in the implementation? I've also read that there is a change in the latest lombok version, where they force you to use the validation statement on the getter method of the attribute. I've reset to a minor version, but nothing changed.
Very simple and straight forward exception. is there any way to cache exception using caffeine/springboot ?
some specific exceptions in my method can be very time consuming... [404 for example] i wish i could cache it and avoid long processing
A simple way to cache exceptions is to encapsulate the call, catch the exception and represent it as a value. I am just adding more details based on #ben-manes comment.
Approach 1: encapsulate the exception as a business object
Approach 2: return null value or Optional object on exception. For caching null values, you need to explicitly enable caching of null values (refer here - Spring Boot Cacheable - Cache null values)
Here is an example based on Spring Caching (can be extended to Caffeine). The following class loads a Book entity object which may result in exception. The exception is handled and cached, so next time the same ISBN code (argument) is passed the return value (exception) is returned from the cache.
#Component
public class SimpleBookRepository implements BookRepository {
#Override
#Cacheable("books")
public Book getByIsbn(String isbn) {
Book book = loadBook(isbn);
return book;
}
// Don't do this at home
private void loadBook(String isbn) {
Book book;
try {
//get book from DB here
book = loadBook();//DB call. can throw exception.
} catch (Exception e) {
book = new Book("None found"); Approach 1 - //encapsulate error as an entity.
book = null; // Approach 2 - set as null
}
return book;
}
}
1. Very first time add these dependency in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.7.0</version>
</dependency>
2. add #CacheConfig(cacheNames = {"customer"}) , #Slf4j under #Service
annotation
3. add #Cacheable on your method where you want caching. and in method add log
public Customer saveCustomer (Customer customerId) {
log.info("Inside save Customer method of Customer Service");
return customerRepository.save(customerId);
}
There are a few important point:
The #CacheConfig is a class level annotation and help to streamline caching configurations.
The #Cacheable annotation used to demarcate methods that are cacheable. In simple words, this annotation used to show caching API that we want to store results for this method into the cache so, on subsequent invocations, the value in the cache returned without execute the method.
Jackson don't automatically convert complexy names of fields from json-object into dto-object.
I have gotten a request
#PostMapping(path = "model/byFile")
public ResponseEntity<ModelDto>
createModel(#RequestBody List<DataFileDto> modelList) {
}
dto
public class DataFileDto {
#NotNull
String kyId;
#NotNull
String nameAs;
//default constructor
//getters and setter
//Builder
}
When json arrives, when converting, Jackson takes out the value and puts it only in simple words , but writes null in compound words.
To avoid this, I have to use the #JsonProperty("ky_id") property, which I find inconvenient. The project itself is old. I can't add there
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
or
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.0</version>
</dependency>
because then the project will break.
I also noticed that there was a custom ObjectMapper configuration set up there.
#Bean
public ObjectMapper mapper() {
var module = new SimpleModule();
module.addSerializer(BigDecimal.class, new BigDecimalSerializer());
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.registerModule(module);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);
return objectMapper;
}
update
I added
...
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
...
thanks
What could have affected the fact that Jackson does not work properly with compound words, despite the fact that setters and getters are present ?
How can this be fixed ?
I don't think we can talk about "affecting". I think it's about the default configuration of Jackson. If you want a different behaviour you have either to use the #JsonProperty annotation or have a global configuration with objectMapper.setPropertyNamingStrategy(my strategy here).
I have an entity class with an enumeration class, while I am trying to insert data then it shows me a conversion error. What is the best and easiest way to implement it?
enum class Vehicle {
Bike,
Bicycle
}
class VehicleTemplate{
#Id
#GeneratedValue
var id:Long?=null
var model:String?=null
var type:Vehicle?=null
}
interface VehicleTemplateInf : ReactiveNeo4jRepository< VehicleTemplate,Long>
#RestController
#RequestMapping("/product/category/")
class CrtProductCategory {
#Autowired
lateinit var vehicleTemplateInf: VehicleTemplateInf
#PutMapping("add")
fun addCategory(#RequestBody vehicleTemplate: VehicleTemplate?): Mono< VehicleTemplate > {
return vehicleTemplateInf(vehicleTemplate!!)
}
}
Displayed Error
"No converter found capable of converting from type [Product.Vehicle] to type [org.neo4j.driver.Value]"
I have used this dependencies
<dependency>
<groupId>org.neo4j.springframework.data</groupId>
<artifactId>spring-data-neo4j-rx-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.11.0</version>
</dependency>
In our Spring MVC web application for job recruiting, I work on a RESTful service to get information about available companies for a given account, or more detailed data for a single company.
This is implemented using Spring MVC in a pretty straightforward way.
Logically, the API for a single company shows more details than for a list of companies. In particular, there are two fields (CoverPhoto and Logo) whoch are only to be shown when querying the details for a single company by its id.
For the generation of the Json output, I use Jackson to annotate the returned DTO object for specific field names because sometimes they are different from the member variable names.
One of the ways to implement this in an elegant way is using JsonViews, as described in these tutorials:
https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
http://www.baeldung.com/jackson-json-view-annotation
The only difference between them is that the second one uses interfaces for the View classes, and the first one uses classes. But that should not make any difference and my code is not working as expected with either of them.
I have created to interfaces (ObjectList and OblectDetails) and annotated the fields in my DTO with
#JsonView(Views.ObjectList.class)
for the fields I want to see on both the lisdt and the details API, and with
#JsonView(Views.ObjectDetails.class)
for the fields only to shown in the single company API.
But unfortunately, both API's show all fields, regardless of the annotation. Also fields without a #JsonView annotation appear in the output JSON, while according to the documentation, when annotating the Controller method with a #JsonView, each fields should also be annotated with a #JsonView annotation to show up.
My simplified code looks as follows:
DTO:
package nl.xxxxxx.dto.too;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonView;
#JsonAutoDetect
#JsonPropertyOrder({"id", "name", "logo", "coverPhoto", "description", "shortDescription",
"phone", "address"})
public class XxxCompanyDto {
#JsonView(Views.ObjectList.class)
private Long id;
#JsonView(Views.ObjectList.class)
private String name;
#JsonView(Views.ObjectDetails.class)
private String logo;
#JsonView(Views.ObjectDetails.class)
#JsonProperty("cover_photo")
private String coverPhoto;
#JsonView(Views.ObjectList.class)
private String description;
//more fields
//setters, no getters are used to prevent ambiguity for Json generation
//omitted for clarity
}
Views:
package nl.xxx.dto.too;
public class Views {
public interface ObjectList {}
public interface ObjectDetails extends ObjectList {}
}
Controller:
package nl.xxx.controller;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.web.bind.annotation.*;
//more imports
/**
* Created by Klaas van Gelder on 17-Nov-16.
*/
#RestController
public class XxxCompanyController {
#Autowired
//services omitted
#JsonView(Views.ObjectDetails.class)
#RequestMapping(value = "/public-api/xxx/company/{companyId}", method = RequestMethod.GET)
public TooCompanyDto getCompanyById(
#RequestHeader(value = "X-Channel") String publicationChannelToken,
#PathVariable(value = "companyId") Long companyId) {
XxxCompany tooCompany = tooCompanyService.getCompanyById(companyId);
//some verifications omitted
TooCompanyDto tooCompanyDto = tooCompanyJsonConverter.convertToDto(tooCompany);
return tooCompanyDto;
}
#JsonView(Views.ObjectList.class)
#RequestMapping(value = "/public-api/xxx/company", method = RequestMethod.GET)
public List<TooCompanyDto> listCompaniesForChannel(
#RequestHeader(value = "X-Channel") String publicationChannelToken) {
XxxPublicationChannel channel = tooVacancyService.findPublicationChannelByToken(publicationChannelToken);
List<XxxCompany> xxxCompaniesForChannel = xxxCompanyService.findCompaniesByPublicationChannelToken(publicationChannelToken);
List<XxxCompanyDto> dtoList = new ArrayList<>();
for (XxxCompany xxxCompany : xxxCompaniesForChannel) {
XxxCompanyDto xxxCompanyDto = xxxCompanyJsonConverter.convertToDto(xxxCompany);
dtoList.add(xxxCompanyDto);
}
return dtoList;
}
}
Maven:
org.springframework
spring-core
4.2.2.BUILD-SNAPSHOT
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-2-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-2-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-2-version}</version>
</dependency>
//more depedencies
with <jackson-2-version>2.2.2</jackson-2-version> in parent POM
It seems that the JsonView annotations are completely ignored. I can probably use another solution by using two separate DTO classes but it would be nice to get this working as it should!
Any hints are more than welcome!