Why application is failing to start for findAll method with Specifications? - spring-data-jpa

I'm using spring-data-dynamodb in my boot app.
Repository class:
//.. imports
class MyRepository extends PagingAndSortingRepository<User, String> {
Page<User> findByName(String name, Pageable pageable);
Page<User> findByJoinedDateBetween(String startDate, String endDate, Pageable pageable);
#EnableScan
#EnableScanCount
Page<User> findAll(Pageable pageable);
}
With above repository class, the application starts and returns expected output.
But, with below repository class setup (adding JpaSpecificationExecutor<User>):
//.. imports
class MyRepository extends PagingAndSortingRepository<User, String>, JpaSpecificationExecutor<User> {
Page<User> findByName(String lastName, Pageable pageable);
Page<User> findByJoinedDateBetween(String startDate, String endDate, Pageable pageable);
#EnableScan
#EnableScanCount
Page<User> findAll(Specification<User> specs, Pageable pageable);
}
the app is failing to start with:
org.springframework.data.mapping.PropertyReferenceException: No property findAll found for type User!
Am I not using it correctly? Is this because there is no support for Specifications at spring-data-dynamodb? Are #EnableDynamoDBRepositories and JpaSpecificationExecutor<User> compatible?

Related

Why does EntityManager.createNativeQuery(String sql, Class resultClass) return a Query instead of a TypedQuery?

Java JPA EntityManager has methods for creating a Query and a TypedQuery for jpql and named queries:
public Query createQuery(String qlString);
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass);
public Query createNamedQuery(String name);
public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass);
So, for a native query, why does it accept a raw Class parameter, and return a Query, rather than a TypedQuery?
public Query createNativeQuery(String sqlString);
public Query createNativeQuery(String sqlString, Class resultClass);
That is, why isn't the second form of createNativeQuery method:
public <T> TypedQuery<T> createNativeQuery(String sqlString, Class<T> resultClass);
following the pattern of the other two.

How to supply LocalDateTime to a jpa/hibernate query?

I'm building a query in my #RepositoryRestResource
where the query looks like this:
#Query("Select DISTINCT comp from InsuranceCompany comp " +
"LEFT JOIN comp.orders ord " +
"wHERE ord.invoiced = false " +
"and (:date is null or :date >= ord.completionTime)"
)
public Page<InsuranceCompany> method(LocalDateTime date, Pageable pageable);
But it throws the following excpetion
Failed to convert from type [java.lang.String] to type [java.time.LocalDateTime] for value '2020-02-14T15:50:24'
when I call the end point with:
GET /method?date=2020-02-14T15:50:24
Mark it with #DateTimeFormat to have Spring converted it correctly:
public Page<InsuranceCompany> method(#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime date,
Pageable pageable);
Spring by default cannot convert REST parameters to LocalDateTime. You need to provide information on the format of the date, at a parameter level with the #DateTimeFormat annotation, or globally using the DateTimeFormatterRegistrar.
This article explains the two alternatives: https://www.baeldung.com/spring-date-parameters
Option 1: Setting the date/time format globally for all Spring Boot App REST Endpoints
You can configure spring globally to use a certain date / date-time format for your REST endpoints. Suggesting that you use the default Jackson for handling JSON mapping, you can create a configuration class as follows where you set the formats:
#Configuration
public class DateTimeSerializationConfiguration implements Jackson2ObjectMapperBuilderCustomizer {
private static final DateTimeFormatter DATE_FORMATTER = ISO_LOCAL_DATE;
private static final DateTimeFormatter DATE_TIME_FORMATTER = ISO_DATE_TIME;
private static final DateTimeFormatter TIME_FORMATTER = ofPattern("HH:mm");
#Bean
public Formatter<LocalDate> localDateFormatter() {
return new Formatter<LocalDate>() {
#Override
public LocalDate parse(String text, Locale locale) {
return LocalDate.parse(text, DATE_FORMATTER);
}
#Override
public String print(LocalDate object, Locale locale) {
return DATE_FORMATTER.format(object);
}
};
}
#Bean
public Formatter<LocalDateTime> localDateTimeFormatter() {
return new Formatter<LocalDateTime>() {
#Override
public LocalDateTime parse(String text, Locale locale) {
return LocalDateTime.parse(text, DATE_TIME_FORMATTER);
}
#Override
public String print(LocalDateTime object, Locale locale) {
return DATE_TIME_FORMATTER.format(object);
}
};
}
#Bean
public Formatter<LocalTime> localTimeFormatter() {
return new Formatter<LocalTime>() {
#Override
public LocalTime parse(String text, Locale locale) {
return LocalTime.parse(text, TIME_FORMATTER);
}
#Override
public String print(LocalTime object, Locale locale) {
return TIME_FORMATTER.format(object);
}
};
}
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.serializers(
new LocalDateSerializer(DATE_FORMATTER),
new LocalDateTimeSerializer(DATE_TIME_FORMATTER),
new LocalTimeSerializer(TIME_FORMATTER));
jacksonObjectMapperBuilder.deserializers(
new LocalDateDeserializer(DATE_FORMATTER),
new LocalDateTimeDeserializer(DATE_TIME_FORMATTER),
new LocalTimeDeserializer(TIME_FORMATTER));
}
}
Then, you can create controller methods like this:
#RestController
public class BookingController {
private final YourService yourService;
#Autowired
public BookingController(YourService yourService) {
this.yourService = yourService;
}
#GetMapping("/your/api/endpoint")
public YourObject yourControllerMethod(#RequestParam LocalDate date, Pageable pageable) {
return yourService.yourServiceMethod(date, pageable);
}
// Or: with LocalDateTime
#GetMapping("/your/api/endpoint")
public YourObject yourControllerMethod(#RequestParam LocalDateTime dateTime, Pageable pageable) {
return yourService.yourServiceMethod(dateTime, pageable);
}
}
Option 2: Setting the date/time format for each REST Endpoint individually
If you prefer to set the format for each endpoint individually, you have to annotate the request parameter with #DateTimeFormat and specify the expected format. The example below shows different examples on how to accomplish this:
#RestController
public class BookingController {
private final YourService yourService;
#Autowired
public BookingController(YourService yourService) {
this.yourService = yourService;
}
#GetMapping("/your/api/endpoint")
public YourObject yourControllerMethod(#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, Pageable pageable) {
return yourService.yourServiceMethod(date, pageable);
}
// Or: with LocalDateTime
#GetMapping("/your/api/endpoint")
public YourObject yourControllerMethod(#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, Pageable pageable) {
return yourService.yourServiceMethod(dateTime, pageable);
}
// Or: with your custom pattern
#GetMapping("/your/api/endpoint")
public YourObject yourControllerMethod(#RequestParam #DateTimeFormat(pattern = "dd.MM.yyyy") LocalDate date, Pageable pageable) {
return yourService.yourServiceMethod(date, pageable);
}
}

Spring data r2dbc and pagination

I'm using the new spring data r2dbc module and i'm able to extract the data using a ReactiveCrudRepository.
Now i need to introduce pagination but i cannot manage to do it.
I tried with this
public interface TestRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findByEntityId(Long entityId, Pageable page);
}
but when i try to execute this i obtain this error
org.springframework.data.repository.query.ParameterOutOfBoundsException: Invalid parameter index! You seem to have declared too little query method parameters!
at org.springframework.data.repository.query.Parameters.getParameter(Parameters.java:237)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
is there a way to use pagination with this module?
The new R2dbcEntityTemplate Spring Data R2dbc 1.2 contains pagination operations like this.
private final R2dbcEntityTemplate template;
public Flux<Post> findByTitleContains(String name) {
return this.template.select(Post.class)
.matching(Query.query(where("title").like("%" + name + "%")).limit(10).offset(0))
.all();
}
Spring Data R2dbc 1.2 (not released yet) will accept a Pageable parameter as in Repository.
public Flux<PostSummary> findByTitleLike(String title, Pageable pageable);
The complete code examples, check here, test codes.
No, there currently is no way to use implicit pagination. You should specify your whole queries to use it.
Here is an example:
#Query("SELECT * FROM my_entity WHERE entity_id = :entityId OFFSET :offset LIMIT :limit")
Flux<MyEntity> findByEntityId(Long entityId, int offset, int limit);
The newer versions of Spring Data R2dbc accepts Pageable as #Hantsy mentioned, but there is a catch.
If you are fetching all the records without any WHERE clause then following is NOT working:
public interface MyEntityRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findAll(Pageable pageable);
}
Changing findAll() to findBy() is working fine.
public interface MyEntityRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findBy(Pageable pageable);
}
I was able to achieve this using spring-boot-starter-data-r2dbc.2.4.3
As #Hantsy said the ReactiveCrudRepository will accept Pageable as a parameter inside of the queries but this won't solve the paging issue. In hibernate you expect to be returned a Page of an Object but for Reactive it's going to be a Flux.
I was however able to achieve this by using the PageImpl class and using the count method from the ReactiveCrudRepository interface.
For example
public interface TestRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findByEntityId(Long entityId, Pageable page);
}
public Mono<<Page<MyEntity>> getMyEntities(Long entityId, PageRequest request) {
return testRepository.findByEntityId(entityId, request)
.collectList()
.zipWith(testRepository.count())
.flatMap(entityTuples ->
new PageImpl<>(entityTuples.getT1(), request, entityTuples.getT2()));
}

Spring Boot MongoDB findTop5 returns empty array

I am using Spring Boot and MongoDB for a personal project to create a movie and video game database. I have methods that retrieve the latest five entries of each category. When I was testing on a local Tomcat and MongoDB server, the methods work as expected. However, when I deploy to a tomcat server and connect to a MongoDB server, the methods that retrieve the latest five entries always return an empty array. here is the code:
Repository:
public interface MovieRepository extends MongoRepository<Movies, String>{
public List<Movies> findAllByOrderByTitleAsc(Pageable pageable);
#Query("{'title': {$regex: ?0, $options: 'i'}}")
public List<Movies> findByTitle(String title);
public List<Movies> findTop5ByCreatedAtLessThan(String currentDate);
public Movies findMovieById(String id);
public List<Movies> findByPlatformsIn(List<String> platforms);
}
Controller:
#RestController
#RequestMapping("/api")
public class MovieController {
#Autowired
private MovieRepository movieRepository;
#Autowired
private MapValidationErrorService mapValidationErrorService;
#Autowired
private CurrentDateService currentDateService;
#GetMapping("/movies")
public List<Movies> findAllMovies(#RequestParam(value="page", defaultValue="0")int page) {
return movieRepository.findAllByOrderByTitleAsc(PageRequest.of(page, 20));
}
#GetMapping("/movies/{movieId}")
public Movies findMovieById(#PathVariable("movieId") String movieId) {
return movieRepository.findMovieById(movieId);
}
#PostMapping("/movies")
public ResponseEntity<?> saveMovie(#Valid #RequestBody Movies movie, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.mapvalidationService(result);
if (errorMap != null) return errorMap;
movie.setId(null);
Movies newMovie = movieRepository.save(movie);
return new ResponseEntity<Movies>(newMovie, HttpStatus.OK);
}
#PutMapping("/movies")
public ResponseEntity<?> updateMovie(#Valid #RequestBody Movies movie, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.mapvalidationService(result);
if (errorMap != null) return errorMap;
Movies updatedMovie = movieRepository.save(movie);
return new ResponseEntity<Movies>(updatedMovie, HttpStatus.OK);
}
#DeleteMapping("/movies/{movieId}")
public void deleteMovieById(#PathVariable("movieId") String movieId) {
movieRepository.deleteById(movieId);
}
#GetMapping("/moviePlatforms")
public List<Movies> findMoviesByPlatforms(#RequestParam(value="platform") List<String>platforms) {
return movieRepository.findByPlatformsIn(platforms);
}
#GetMapping("/newFiveMovies")
public List<Movies> findTop5ByCreatedAt() {
String currentDate = currentDateService.getCurrentDate();
return movieRepository.findByCreatedAt(currentDate);
}
}
I have changed the find top 5 methods to use the #Query annotation, but it yields the same results. All the other methods in the controller and repository work as expected. I have tried using MongoDB on a Mac, and also on MongoDB Atlas. Any help is greatly appreciated.
I figured out that the date I was sending as a parameter was formatted wrong. The date was being stored as "M/d/yy h:mm a", but I was sending "M/dd/yyyy hh:mm a".

QueryDslMongoRepository Projection

I am using spring-data for mongodb with querydsl.
I have a repository
public interface DocumentRepository extends MongoRepository<Document, String> ,QueryDslPredicateExecutor<Document> {}
and an entity
#QueryEntity
public class Document {
private String id;
private String name;
private String description;
private boolean locked;
private String message;
}
I need to load a list of documents with id and name informations.
So only id and name should be loaded and set in my entity.
I think query projection is the right word for it.
Is this supported?
In addition I need to implement some lazy loading logic.
Is there anything like "skip" and "limit" features in a repository?
There's quite a few aspects to this, as it is - unfortunately - not a single question but multiple ones.
For the projection you can simply use the fields attribute of the #Query annotation:
interface DocumentRepository extends MongoRepository<Document, String>, QuerydslPredicateExecutor<Document> {
#Query(value = "{}", fields = "{ 'id' : 1, 'name' : 1 }")
List<Document> findDocumentsProjected();
}
You can combine this with the query derivation mechanism (by not setting query), with pagination (see below) and even a dedicated projection type in the return clause (e.g. a DocumentExcerpt with only id and name fields).
Pagination is fully supported on the repository abstraction. You already get findAll(Pageable) and a Querydsl specific version of the method by extending the base interfaces. You can also use the pagination API in finder methods adding a Pageable as parameter and returning a Page
Page<Document> findByDescriptionLike(String description, Pageable pageable)
See more on that in the reference documentation.
Projection
For all I know projections are not supported by the default Spring Data repositories. If you want to make sure only the projection is sent from the DB to your application (e.g. for performance reasons) you will have to implement the corresponding query yourself. Adding custom methods to extensions of the standard repo should not be too much effort.
If you just want to hide the content of certain fields from some client calling your application, you would typically use another set of entity objects with a suitable mapping in between. Using the same POJO for different levels of detail is always confusing as you will not know if a field is actually null or if the value was just suppressed in a certain context.
Pagination
I am currently not able to test any code, but according to the documentation of QueryDslPredicateExecutor the method findAll(predicate, pageable) should be what you want:
it returns a Page object that is a regular Iterable for your Document
you have to pass it a Pageable for which you can e.g. use a PageRequest; initializing it for known values of skip and limit should be trivial
I also found this approach for JPA
Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection
I am currently trying to implement this for MongoDB.
According to the Answer of this -> Question <- I implemeted following solution.
Entity
#QueryEntity
public class Document extends AbstractObject {
}
Custom QuerydslMongoRepository
public interface CustomQuerydslMongoRepository<T extends AbstractObject,ID extends Serializable> extends MongoRepository<T, ID> ,QueryDslPredicateExecutor<T>{
Page<T> findAll(Predicate predicate, Pageable pageable,Path... paths);
Page<T> findAll(Predicate predicate, Pageable pageable,List<Path> projections);
}
Custom QuerydslMongoRepository Implementation
public class CustomQuerydslMongoRepositoryImpl<T extends AbstractObject,ID extends Serializable> extends QueryDslMongoRepository<T,ID> implements CustomQuerydslMongoRepository<T,ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> pathBuilder;
private final MongoOperations mongoOperations;
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations,DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations, EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
this.path=resolver.createPath(entityInformation.getJavaType());
this.pathBuilder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.mongoOperations=mongoOperations;
}
#Override
public Page<T> findAll( Predicate predicate, Pageable pageable,Path... paths) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
long total = query.count();
List<T> content = total > pageable.getOffset() ? query.where(predicate).list(paths) : Collections.<T>emptyList();
return new PageImpl<T>(content, pageable, total);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable, List<Path> projections) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
long total = query.count();
List<T> content = total > pageable.getOffset() ? query.where(predicate).list(projections.toArray(new Path[0])) : Collections.<T>emptyList();
return new PageImpl<T>(content, pageable, total);
}
}
Custom Repository Factory
public class CustomQueryDslMongodbRepositoryFactoryBean<R extends QueryDslMongoRepository<T, I>, T, I extends Serializable> extends MongoRepositoryFactoryBean<R, T, I> {
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new CustomQueryDslMongodbRepositoryFactory<T,I>(operations);
}
public static class CustomQueryDslMongodbRepositoryFactory<T, I extends Serializable> extends MongoRepositoryFactory {
private MongoOperations operations;
public CustomQueryDslMongodbRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.operations = mongoOperations;
}
#SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQuerydslMongoRepositoryImpl(getEntityInformation(metadata.getDomainType()), operations);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQuerydslMongoRepository.class;
}
}
}
Entity Repository
public interface DocumentRepository extends CustomQuerydslMongoRepository<Document, String>{
}
Usage in Service
#Autowired
DocumentRepository repository;
public List<Document> getAllDocumentsForListing(){
return repository.findAll( QDocument.document.id.isNotEmpty().and(QDocument.document.version.isNotNull()), new PageRequest(0, 10),QDocument.document.name,QDocument.document.version).getContent();
}