QueryDsl SpringData Jpa findAll how to avoid count() - java

I'm trying to use QueryDSL with Spring Data JPA, I want to use findAll with pagination but the count is always performed, also if return type is a List.
I don't need this count because it is really slow and I could loose the benefit of pagination.
Any solutions for this problem?
This is the count(), it requires about 30 seconds on MySQL:
Mysql too slow on simple query between two tables
In any case I don't want repeat the count for each page I require, this information is required just for first call.

Since QuerydslPredicateExecutor does not support returning Slice as the return value of findAll(Predicate, Pageable), so the Count Query seems unavoidable. But you can define a new base repository interface and implement the findAll method in a way that it does not issue a count query for pagination. For starters, you should define an interface which will be used as the base interface for all other Repositories:
/**
* Interface for adding one method to all repositories.
*
* <p>The main motivation of this interface is to provide a way
* to paginate list of items without issuing a count query
* beforehand. Basically we're going to get one element more
* than requested and form a {#link Page} object out of it.</p>
*/
#NoRepositoryBean
public interface SliceableRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>,
QuerydslPredicateExecutor<T> {
Page<T> findAll(Predicate predicate, Pageable pageable);
}
Then, implement this interface like:
public class SliceableRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID>
implements SliceableRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public SliceableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
path = DEFAULT_ENTITY_PATH_RESOLVER.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
int oneMore = pageable.getPageSize() + 1;
JPQLQuery query = createQuery(predicate)
.offset(pageable.getOffset())
.limit(oneMore);
Sort sort = pageable.getSort();
query = querydsl.applySorting(sort, query);
List<T> entities = query.list(path);
int size = entities.size();
if (size > pageable.getPageSize())
entities.remove(size - 1);
return new PageImpl<>(entities, pageable, pageable.getOffset() + size);
}
}
Basically, this implementation would fetch one more element than requested size and use the result for constructing a Page. Then you should tell Spring Data to use this implementation as the repository base class:
#EnableJpaRepositories(repositoryBaseClass = SliceableRepositoryImpl.class)
And finally extend the SliceableRepository as your base interface:
public SomeRepository extends SliceableRepository<Some, SomeID> {}

FYI there is a spring jira issue:
https://jira.spring.io/browse/DATAJPA-289
Let's vote for this improvement

In case anyone lands here looking for how to achieve the same affect in Spring Data MongoDB as Ali did above for Spring Data JPA, here's my solution modeled on his:
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.QueryDslMongoRepository;
import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.mongodb.AbstractMongodbQuery;
/**
* Custom extension of {#link QueryDslMongoRepository} that avoids unnecessary MongoDB "count"
* operations
* <p>
* {#link QueryDslPredicateExecutor#findAll(Predicate, Pageable)} returns a {#link Page} at
* potentially great expense because determining the {#link Page}'s "totalElements" property
* requires doing a potentially expensive MongoDB "count" operation. We'd prefer a "findAll"-like
* method that returns a {#link Slice} (which doesn't have a "totalElements" property) but no such
* method exists. See {#link #findAll(Predicate, Pageable)} for more details.
*
* #see https://github.com/spring-projects/spring-data-commons/issues/1011
* #see https://stackoverflow.com/questions/37254385/querydsl-springdata-jpa-findall-how-to-avoid-count
*/
public class MyQueryDslMongoRepository<T, ID extends Serializable> extends QueryDslMongoRepository<T, ID>
implements MyAbstractRepository<T, ID> {
private final PathBuilder<T> builder;
private final EntityInformation<T, ID> entityInformation;
private final MongoOperations mongoOperations;
public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations, SimpleEntityPathResolver.INSTANCE);
}
public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations,
EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.entityInformation = entityInformation;
this.mongoOperations = mongoOperations;
}
/**
* An override of our superclass method to return a fake but cheaper-to-compute {#link Page}
* that's adequate for our purposes.
*/
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
int pageSize = pageable.getPageSize();
SpringDataMongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, entityInformation.getJavaType())
.where(predicate)
.offset(pageable.getOffset())
.limit(pageSize + 1);
applySorting(query, pageable.getSort());
List<T> entities = query.fetch();
int numFetched = entities.size();
if (numFetched > pageSize) {
entities.remove(numFetched - 1);
}
return new PageImpl<T>(entities, pageable, pageable.getOffset() + numFetched);
}
/**
* Applies the given {#link Sort} to the given {#link MongodbQuery}.
* <p>
* Copied from {#link QueryDslMongoRepository}
*/
private AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> applySorting(
AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query, Sort sort) {
if (sort == null) {
return query;
}
// TODO: find better solution than instanceof check
if (sort instanceof QSort) {
List<OrderSpecifier<?>> orderSpecifiers = ((QSort) sort).getOrderSpecifiers();
query.orderBy(orderSpecifiers.toArray(new OrderSpecifier<?>[orderSpecifiers.size()]));
return query;
}
for (Order order : sort) {
query.orderBy(toOrder(order));
}
return query;
}
/**
* Transforms a plain {#link Order} into a QueryDsl specific {#link OrderSpecifier}.
* <p>
* Copied from {#link QueryDslMongoRepository}
*/
#SuppressWarnings({ "rawtypes", "unchecked" })
private OrderSpecifier<?> toOrder(Order order) {
Expression<Object> property = builder.get(order.getProperty());
return new OrderSpecifier(
order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, property);
}
}
#NoRepositoryBean
public interface MyAbstractRepository<T, ID extends Serializable> extends Repository<T, ID>,
QueryDslPredicateExecutor<T> {
#Override
Page<T> findAll(Predicate predicate, Pageable pageable);
}
The above works for Spring Data MongoDB 1.10.23 but I assume can be modified to be made to work for more modern versions.

Based on the answer of Ali Dehghani we build the following for querydsl 4.2.1, because the querydsl syntax changed in the current version 4.x
Repository Interface:
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
public interface SliceableRepository<T> {
Slice<T> findAllSliced(EntityPath<T> entityPath, Predicate predicate, Pageable pageable);
}
Repository Implementation:
(Must be named "<Interface-Name>Impl")
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.jpa.repository.support.Querydsl;
public class SliceableRepositoryImpl<T> implements SliceableRepository<T> {
private final EntityManager entityManager;
private final JPAQueryFactory jpaQueryFactory;
public SliceableRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
this.jpaQueryFactory = new JPAQueryFactory(entityManager);
}
#Override
public Slice<T> findAllSliced(final EntityPath<T> entityPath, final Predicate predicate,
final Pageable pageable) {
final Querydsl querydsl = new Querydsl(entityManager,
new PathBuilder<>(entityPath.getType(), entityPath.getMetadata()));
final int oneMore = pageable.getPageSize() + 1;
final JPAQuery<T> query = this.jpaQueryFactory.selectFrom(entityPath)
.where(predicate)
.offset(pageable.getOffset())
.limit(oneMore);
final JPQLQuery<T> querySorted = querydsl.applySorting(pageable.getSort(), query);
final List<T> entities = querySorted.fetch();
final int size = entities.size();
// If there was one more result than requested from the pageable,
// then the slice gets "hasNext"=true
final boolean hasNext = size > pageable.getPageSize();
if (hasNext) {
entities.remove(size - 1);
}
return new SliceImpl<>(entities, pageable, hasNext);
}
}
Use the new repository as fragment in your other repositories:
public SomeRepository extends JpaRepository<Some, Long>, SliceableRepository<Some> {
}
#EnableJpaRepositories(repositoryBaseClass = SliceableRepositoryImpl.class) is NOT needed
Then use it like:
public class MyService {
#Autowired
private final SomeRepository someRepository;
public void doSomething() {
Predicate predicate = ...
Pageable pageable = ...
// QSome is the generated model class from querydsl
Slice<Some> result = someRepository.findAllSliced(QSome.some, predicate, pageable);
}
}

Related

How to write a query to get distinct values from mongodb collection?

I need to get distinct values from a collection. Those data is in a field of a collection. It means I need to get set of name from the user collection.I tried it using cmd and I get the result as I need. But I can't understand how to write the query in the spring file.Since I'm new to java I have not enough knowledge how to handle this.
Given below is a image of the database collection
Services.java
package limark.internal.css.services;
import limark.internal.css.core.model.User;
import limark.internal.css.exceptions.ResourceNotFoundException;
import limark.internal.css.persistence.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Array;
import java.time.OffsetDateTime;
import java.util.List;
import static limark.internal.css.core.MessageConstants.USER_NOT_FOUND;
#Service
#Slf4j
public class UserService {
private final UserRepository userRepository;
#Autowired
public UserService(final UserRepository userRepository){
this.userRepository = userRepository;
}
/**
* Creates a new User
* #param user object
* #return user object
*/
public User create(User user){
user.setCreatedByLoginId("");
user.setCreatedTs(OffsetDateTime.now());
return userRepository.save(user);
}
/**
* Returns list of users.
*/
public List<User> findUsers(){
return userRepository.findAll();
}
/**
* Find user by id
* #param id userId
* #return user
*/
public User findById(#NotNull String id){
return userRepository.findById(id).orElseThrow(()->new ResourceNotFoundException(USER_NOT_FOUND,
id));
}
public User update(#NotNull User user){
user.setLastUpdatedByLoginId("");
user.setLastUpdatedTs(OffsetDateTime.now());
return userRepository.save(user);
}
/**
* sets a user to not active on delete with the given id
*
* #param userId valid user id
* #throws ResourceNotFoundException when user is not found
*/
public void deleteUser(String userId) {
User user =
userRepository
.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException(USER_NOT_FOUND, userId));
user.setActive(false);
user.setLastUpdatedByLoginId("");
user.setLastUpdatedTs(OffsetDateTime.now());
userRepository.save(user);
log.info("The user {} is deleted successfully.", user.getId());
}
/**
* Returns list of users.
*/
public Array findUsersList(){
return userRepository.distinct( "firstName" );
}
}
I need to add this query inside
/**
* Returns list of users.
*/
public Array findUsersList(){
return userRepository.distinct( "firstName" );
}
You can introduce a method in the UserRepository to retrieve the distinct firstName field values and return a List<String>.
public interface UserRepository extends MongoRepository<User, String> {
#Aggregation(pipeline = { "{ '$group': { '_id' : '$firstName' } }" })
List<String> findDistinctFirstNames();
}
The call to get the list of distinct first names:
List<String> firstNamesDistinct = userRepository.findDistinctFirstNames();
This worked fine using Spring Data MongoDB v2.4 and MongoDB v4.2.

how to generate a complex query without a param with JpaRepository? [duplicate]

I am using spring data and my DAO looks like
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllOrderByIdAsc(); // I want to use some thing like this
}
In above code, commented line shows my intent. Can spring Data provides inbuilt functionality
to use such a method to find all records order by some column with ASC/DESC?
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public List<StudentEntity> findAllByOrderByIdAsc();
}
The code above should work. I'm using something similar:
public List<Pilot> findTop10ByOrderByLevelDesc();
It returns 10 rows with the highest level.
IMPORTANT:
Since I've been told that it's easy to miss the key point of this answer, here's a little clarification:
findAllByOrderByIdAsc(); // don't miss "by"
^
AFAIK, I don't think this is possible with a direct method naming query. You can however use the built in sorting mechanism, using the Sort class. The repository has a findAll(Sort) method that you can pass an instance of Sort to. For example:
import org.springframework.data.domain.Sort;
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(sortByIdAsc());
}
private Sort sortByIdAsc() {
return new Sort(Sort.Direction.ASC, "id");
}
}
Simple way:
repository.findAll(Sort.by(Sort.Direction.DESC, "colName"));
Source: https://www.baeldung.com/spring-data-sorting
Please have a look at the Spring Data JPA - Reference Documentation, section 5.3. Query Methods, especially at section 5.3.2. Query Creation, in "Table 3. Supported keywords inside method names" (links as of 2019-05-03).
I think it has exactly what you need and same query as you stated should work...
Yes you can sort using query method in Spring Data.
Ex:ascending order or descending order by using the value of the id field.
Code:
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllByOrderByIdAsc();
}
alternative solution:
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(orderByIdAsc());
}
private Sort orderByIdAsc() {
return new Sort(Sort.Direction.ASC, "id")
.and(new Sort(Sort.Direction.ASC, "name"));
}
}
Spring Data Sorting: Sorting
I try in this example to show you a complete example to personalize your OrderBy sorts
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Sort;
/**
* Spring Data repository for the User entity.
*/
#SuppressWarnings("unused")
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List <User> findAllWithCustomOrderBy(Sort sort);
}
you will use this example :
A method for build dynamically a object that instance of Sort :
import org.springframework.data.domain.Sort;
public class SampleOrderBySpring{
Sort dynamicOrderBySort = createSort();
public static void main( String[] args )
{
System.out.println("default sort \"firstName\",\"name\",\"age\",\"size\" ");
Sort defaultSort = createStaticSort();
System.out.println(userRepository.findAllWithCustomOrderBy(defaultSort ));
String[] orderBySortedArray = {"name", "firstName"};
System.out.println("default sort ,\"name\",\"firstName\" ");
Sort dynamicSort = createDynamicSort(orderBySortedArray );
System.out.println(userRepository.findAllWithCustomOrderBy(dynamicSort ));
}
public Sort createDynamicSort(String[] arrayOrdre) {
return Sort.by(arrayOrdre);
}
public Sort createStaticSort() {
String[] arrayOrdre ={"firstName","name","age","size");
return Sort.by(arrayOrdre);
}
}
Combining all answers above, you can write reusable code with BaseEntity:
#Data
#NoArgsConstructor
#MappedSuperclass
public abstract class BaseEntity {
#Transient
public static final Sort SORT_BY_CREATED_AT_DESC =
Sort.by(Sort.Direction.DESC, "createdAt");
#Id
private Long id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
#PrePersist
void prePersist() {
this.createdAt = LocalDateTime.now();
}
#PreUpdate
void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
DAO object overloads findAll method - basically, still uses findAll()
public interface StudentDAO extends CrudRepository<StudentEntity, Long> {
Iterable<StudentEntity> findAll(Sort sort);
}
StudentEntity extends BaseEntity which contains repeatable fields (maybe you want to sort by ID, as well)
#Getter
#Setter
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
class StudentEntity extends BaseEntity {
String firstName;
String surname;
}
Finally, the service and usage of SORT_BY_CREATED_AT_DESC which probably will be used not only in the StudentService.
#Service
class StudentService {
#Autowired
StudentDAO studentDao;
Iterable<StudentEntity> findStudents() {
return this.studentDao.findAll(SORT_BY_CREATED_AT_DESC);
}
}

querying couchbase with spring-data-couchbase, using multiple columns

I am using couchbase3 with spring-data-couchbase, and want to query data using spring data repository with multiple columns.
public interface UserAccountRepository extends CrudRepository<UserAccount, Long> {
public UserAccount findByEmail(Query eMail);
public UserAccount findByEmailAndStatus(Query query); // fn with multiple column, but not getting the result
}
How should I write Map function and Reduce function for the same?
For the function findByEmail(Query eMail); to work, I have added the view with Map fn()
function (doc, meta) {
emit(doc.email,doc);
}
This view have email as key, and value is the document.
But if i need to query using email and status? How should the view look like ?
I have seen this link, but not very clear.
https://stackoverflow.com/questions/28938755
I was able make springdata function to invoke a compound Key view.
My Document name is : Data
Compound Key View
function (doc, meta) {
if(doc && doc._class == "com.couchbase.entity.Data"){
emit([doc.key1, doc.key2], doc);
}
}
SpringData Repository Inferface shown below :
package com.couchbase.repository;
import java.util.List;
import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import com.couchbase.client.protocol.views.Query;
import com.couchbase.entity.Data;
public interface DataRepository extends CrudRepository<Data, String> {
#View(designDocument="Data",viewName="findByKey1AndKey2")
public List<Data> findByKey1AndKey2(Query query);
}
Test Class shown below :
import com.couchbase.client.protocol.views.ComplexKey;
import com.couchbase.client.protocol.views.Query;
public class DataTest extends WebAppConfigurationAware{
#Autowired
private DataRepository dataRepository;
#Test
public void testStringDataCompoundQuery(){
Object[] objArr = new Object[2];
objArr[0] = "aaa";
objArr[1] = 1;
Query query = new Query();
query.setKey(ComplexKey.of(objArr));
System.out.println(dataRepository.findByKey1AndKey2(query));
}
}
If this was useful for you, please up vote
You could use compound key like describe in the documentation here: http://docs.couchbase.com/developer/dev-guide-3.0/compound-keys.html

Sorting by parent entity using Specifications

I’m dealing with an issue which to my understanding looks unsupported on Spring Data JPA.
I got a grid (using JqGrid plugin for jQuery) on the view which sends parameters to the server, they are parsed and then a dynamic query generated through Specifications is executed.
The issue comes when I want to order a column which doesn’t belong to the root entity.
Eg. Transaction, Card and Account are my entities and grid displays last4digits as a way for the user to identify the card. As you can imagine last4digits belongs to Card. I query transactions per account.
Using specifications I can filter by that attribute, joining tables and so on but sorting fails as findAll() implementation assumes properties from Sort class belongs to the root entity.
Code example:
JQGridRule panFirst6DigitsRule = FilterUtils.findSearchOrFilterRule(settings, Card_.panFirst6Digits.getName());
JQGridRule panLast4DigitsRule = FilterUtils.findSearchOrFilterRule(settings, Card_.panLast4Digits.getName());
if(panFirst6DigitsRule != null) {
filterPan1 = TransactionSpecs.withPanFirst6Digits(panFirst6DigitsRule.getData(),
panFirst6DigitsRule.getOp(), gridGroupOp);
}
if(panLast4DigitsRule != null) {
filterPan2 = TransactionSpecs.withPanLast4Digits(panLast4DigitsRule.getData(),
panLast4DigitsRule.getOp(), gridGroupOp);
}
Specification<Transaction> joinSpec = TransactionSpecs.withAccountId(account.getAccountId());
Specification<Transaction> activeSpec = BaseSpecs.withEntityStatus(true);
Page<Transaction> results = transactionRepository.findAll(
Specifications.where(joinSpec).and(filterSpec).and(filterPan1).and(filterPan2).and(activeSpec), springPageable);
springPageable variable contains a Sort for last4Digits column generated this way*:
List<Order> sortOrders = new ArrayList<Order>();
Order sortOrder = new Order(Direction.ASC, "panLast4Digits");
sortOrders.add(sortOrder);
sort = new Sort(sortOrders);
*There are missing code parsing parameters and creating more Order objects
Does someone know how to implement that kind of sort over an attribute which belongs to a parent entity/class?
Thanks in advance
Version 1.4.3 for Spring-data-jpa and 4.2.8 for Hibernate
EDIT
Showing how Specification for panLast4Digits is generated
public static Specification<Transaction> withPanLast4Digits(final String panLast4Digits, final JQGridSearchOp op, final JQGridGroupOp whereOp) {
Specification<Transaction> joinSpec = new Specification<Transaction>() {
#Override
public Predicate toPredicate(Root<Transaction> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<Transaction, Card> join = joinCards(root, JoinType.INNER);
return FilterUtils.buildPredicate(cb, join.get(Card_.panLast4Digits), op, panLast4Digits, null, whereOp);
}
};
return joinSpec;
}
private static Join<Transaction, Card> joinCards(Root<Transaction> root, JoinType joinType) {
Join<Transaction, Card> join = getJoin(root, Transaction_.parentCard, joinType);
// only join if not already joined
if (join == null) {
join = root.join(Transaction_.parentCard, joinType);
}
return join;
}
protected static <C, T> Join<C, T> getJoin(Root<C> root, Attribute<? super C, T> attribute, JoinType joinType) {
Set<Join<C, ?>> joins = root.getJoins();
for (Join<C, ?> join : joins) {
if (join.getAttribute().equals(attribute) && join.getJoinType().equals(joinType)) {
return (Join<C, T>) join;
}
}
return null;
}
Also I have updated to spring-data-jpa 1.6.0 and hibernate 4.3.5
the attribute for Sorting is "yourChildentity.attribute"
In your Case you can use the PagingAndSortingRepository this way:
let's assume you have two entities : an Account and a Card
#Entity
public class Account{
// Autogeneration and Ill just assume that your id is type long
private Long id;
#ManyToOne
#JoinColumn(name="CARD_ID")
private Card creditCard;
//getters and setters
}
#Entity
public class Card{
//Id and other attributes.
private String panLast4Digits;
//getters and Setters
}
Repository interface :
#Repository
public interface AccountRepository extends PagingAndSortingRepository<Account, Long>,
JpaSpecificationExecutor<Account>{
}
Service Layer :
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface AccountService{
//you can specify other arguments the one that you want to filter by
Page<Account> filter(Pageable pageable);
}
Service Implementation:
#Service
public calss AccountServiceImpl implements AccountService{
#Resource//or #Autowired
private AccountRepository repository;
#Override
public Page<Account> filter(Pageable pageable){
//Filter using Specifications if you have other arguments passed in the signature of the method.
return repository.findAll(pageable);//if you have specifications than return repository.findAll(yourspecification,pageable);
}
Now the call to service throw an endpoint or a Controller:
just a mthod to see how to sort throw child entity parameter :
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
// method
#Resource
private AccountService service;
public Page<Account> consumeMyService(){
// 0 : for Page 1
// 12 for page size
// Soting throw Child enntiy Account , by attribute panLast4Digits
PageRequest pageable = new PageRequest(0,
12, Direction.ASC, "mycard.panLast4Digits");
Page<Account> service.filter(pageable);
}
You must register you beans by configuring Jpa:repositories for the repository interfaces, and context:component-scan for service implementation
this answer may be useful too.

Spring - How to use BeanPropertyRowMapper without matching column names

I work on an application that has been converted from pure JDBC to Spring template with row mapper. The issue that I have is that the column in database doesn't match the property names which prevent me from using BeanPropertyRowMapper easily.
I saw some posts about using aliases in queries. This would work but it makes it impossible to do a SELECT *
Isn't there any annotation that can be used with BeanPropertyRowMapper as #Column from JPA?
I saw Some posts about using aliases in queries
This is actually an approach suggested in JavaDocs:
To facilitate mapping between columns and fields that don't have matching names, try using column aliases in the SQL statement like "select fname as first_name from customer".
From: BeanPropertyRowMapper.
impossible to do a SELECT *
Please do not use SELECT *. This makes you vulnerable to any database schema change, including completely backward compatible ones like adding or rearranging columns.
Isn't there any annotation that can be used with BeanPropertyRowMapper as #Column from JPA?
Yes, it is called jpa, hibernate and maybe ibatis. Seriously, either use aliases or implement your own RowMapper, Spring is not a full-featured orm.
You can override the BeanPropertyRowMapper.underscoreName, and get the name of the Column annotation to mapping the field with #Column(name = "EXAMPLE_KEY") in the PropertyDescriptor(getter/setter binding).
#Slf4j
public class ColumnRowMapper<T> extends BeanPropertyRowMapper<T> {
private ColumnRowMapper(final Class<T> mappedClass)
{
super(mappedClass);
}
#Override
protected String underscoreName(final String name)
{
final Column annotation;
final String columnName;
Field declaredField = null;
try
{
declaredField = getMappedClass().getDeclaredField(name);
}
catch (NoSuchFieldException | SecurityException e)
{
log.warn("Ups, field «{}» not found in «{}».", name, getMappedClass());
}
if (declaredField == null || (annotation = declaredField.getAnnotation(Column.class)) == null
|| StringUtils.isEmpty(columnName = annotation.name()))
{
return super.underscoreName(name);
}
return StringUtils.lowerCase(columnName);
}
/**
* New instance.
*
* #param <T> the generic type
* #param mappedClass the mapped class
* #return the bean property row mapper
*/
public static <T> BeanPropertyRowMapper<T> newInstance(final Class<T> mappedClass)
{
return new ColumnRowMapper<>(mappedClass);
}
}
A version of above mapper but with early initiation of mapping index, since reflection is way too slow:
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.Column;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
#Slf4j
public class ColumnRowMapper<T> extends BeanPropertyRowMapper<T> {
private Map<String, String> columnIndex;
private ColumnRowMapper(final Class<T> mappedClass)
{
super(mappedClass);
}
#Override
protected void initialize(Class<T> mappedClass) {
columnIndex = new ConcurrentHashMap<>();
for (Field f: mappedClass.getDeclaredFields()) {
String fieldName = f.getName();
Column annotation = f.getAnnotation(Column.class);
if (annotation == null) {
continue;
}
String columnName = annotation.name();
if (StringUtils.isEmpty(columnName)) {
continue;
}
columnIndex.put(fieldName, StringUtils.lowerCase(columnName));
}
super.initialize(mappedClass);
}
#Override
protected #NonNull String underscoreName(final #NonNull String name)
{
if (columnIndex.containsKey(name)) {
return columnIndex.get(name);
}
return super.underscoreName(name);
}
/**
* New instance.
*
* #param <T> the generic type
* #param mappedClass the mapped class
* #return the bean property row mapper
*/
public static <T> BeanPropertyRowMapper<T> newInstance(final Class<T> mappedClass)
{
return new ColumnRowMapper<>(mappedClass);
}
}

Categories

Resources