Using JpaSpecificationExecutor with EntityGraph - spring

I am using a implementation of JpaSpecificationExecutor and trying to use in the repository the #EntityGraph for select which relationships entity they get in a complex query.
My entities examples (all relationships bidireccional)
#Entity
#Table(name = "trazabilidad_contenedor")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class TrazabilidadContenedor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "traConSeq")
#SequenceGenerator(name = "traConSeq")
private Long id;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "trazabilidadContenedors", allowSetters = true)
private PromoProGesCodLer promoProGesCodeLer;
.
.
.
#Entity
#Table( name = "promo_pro_ges_cod_ler")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class PromoProGesCodLer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "promoProGesCodLers", allowSetters = true)
private ProGesCodLer procesoGestoraCodLer;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "promoProGesCodLers", allowSetters = true)
private Promocion promocion;
.
.
.
#Entity
#Table(name = "promocion")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Promocion implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
.
.
.
#Entity
#Table(name = "pro_ges_cod_ler")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#NextProGesCodLer
public class ProGesCodLer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pgclSeq")
#SequenceGenerator(name = "pgclSeq")
private Long id;
#OneToMany(mappedBy = "procesoGestoraCodLer")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<PromoProGesCodLer> promoProGesCodLers = new HashSet<>();
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "proGesCodLers", allowSetters = true)
private ProcesoGestora procesoGestora;
.
.
.
And this is my repository
#Repository
public interface TrazabilidadContenedorRepository
extends JpaRepository<TrazabilidadContenedor, Long>, JpaSpecificationExecutor<TrazabilidadContenedor> {
#EntityGraph (
type = EntityGraph.EntityGraphType.FETCH,
attributePaths = {
"promoProGesCodeLer",
"promoProGesCodeLer.promocion",
"promoProGesCodeLer.promocion.direccion",
"promoProGesCodeLer.promocion.direccion.municipio",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.codLER",
"promoProGesCodeLer.procesoGestoraCodLer.codLER.lerType",
"promoProGesCodeLer.procesoGestoraCodLer.nextProGesCodLer",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora",
}
)
List<TrazabilidadContenedor> findAll(Specification<TrazabilidadContenedor> var1);
}
The constructor of my Specification‹TrazabilidadContenedor›
protected Specification<TrazabilidadContenedor> createSpecification(TrazabilidadContenedorCriteria criteria) {
Specification<TrazabilidadContenedor> specification = Specification.where(null);
if (criteria != null) {
if (criteria.getPromocionId() != null) {
specification =
specification.and((root, query, builder) ->
builder.equal(
root
.join(TrazabilidadContenedor_.promoProGesCodeLer, JoinType.LEFT)
.join(PromoProGesCodLer_.promocion, JoinType.LEFT)
.get(Promocion_.id),
criteria.getPromocionId()
)
);
}
if (criteria.getGestoraId() != null) {
specification =
specification.and(
(root, query, builder) ->
builder.equal(
root
.join(TrazabilidadContenedor_.promoProGesCodeLer, JoinType.LEFT)
.join(PromoProGesCodLer_.procesoGestoraCodLer, JoinType.LEFT)
.join(ProGesCodLer_.procesoGestora, JoinType.LEFT)
.join(ProcesoGestora_.gestora, JoinType.LEFT)
.get(Gestora_.id),
criteria.getGestoraId()
)
);
}
}
return specification;
}
When i have only one criteria , criteria.getPromocionId() or criteria.getGestoraId() it's OK , but if i use both at the same time i obtain.
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias2,role=com.cocircular.greenadvisor.domain.PromoProGesCodLer.promocion,tableName=promocion,tableAlias=promocion2_,origin=promo_pro_ges_cod_ler promoproge1_,columns={promoproge1_.promocion_id,className=com.cocircular.greenadvisor.domain.Promocion}}] [select generatedAlias0 from com.cocircular.greenadvisor.domain.TrazabilidadContenedor as generatedAlias0 inner join generatedAlias0.promoProGesCodeLer as generatedAlias1 inner join generatedAlias1.promocion as generatedAlias2 inner join generatedAlias0.promoProGesCodeLer as generatedAlias3 inner join generatedAlias3.procesoGestoraCodLer as generatedAlias4 inner join generatedAlias4.procesoGestora as generatedAlias5 inner join generatedAlias5.gestora as generatedAlias6 where ( generatedAlias0.traceabilityStatus=:param0 ) and ( ( generatedAlias6.id=75304L ) and ( generatedAlias2.id=86754L ) )]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1542)
at org.hibernate.query.Query.getResultList(Query.java:165)
at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
For this i'm usign Hibernate 5.4.15 and Spring-Boot 2.2.7.RELEASE.

Every item, presented in generated sql is bound to be present in graph.
So, let's write a full graph path:
select generatedAlias0
from com.cocircular.greenadvisor.domain.TrazabilidadContenedor as generatedAlias0
inner join generatedAlias0.promoProGesCodeLer as generatedAlias1 ---> promoProGesCodeLer
inner join generatedAlias1.promocion as generatedAlias2 ---> promoProGesCodeLer.promocion
inner join generatedAlias0.promoProGesCodeLer as generatedAlias3 ---> promoProGesCodeLer
inner join generatedAlias3.procesoGestoraCodLer as generatedAlias4 ---> promoProGesCodeLer.procesoGestoraCodLer
inner join generatedAlias4.procesoGestora as generatedAlias5 ---> promoProGesCodeLer.procesoGestoraCodLer.procesoGestora
inner join generatedAlias5.gestora as generatedAlias6 ----> promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora
where ( generatedAlias0.traceabilityStatus=:param0 )
and ( ( generatedAlias6.id=75304L ) and ( generatedAlias2.id=86754L ) )
Here's the provided graph:
attributePaths = {
"promoProGesCodeLer",
"promoProGesCodeLer.promocion",
"promoProGesCodeLer.promocion.direccion",
"promoProGesCodeLer.promocion.direccion.municipio",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.codLER",
"promoProGesCodeLer.procesoGestoraCodLer.codLER.lerType",
"promoProGesCodeLer.procesoGestoraCodLer.nextProGesCodLer",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora" }
Looks to me, that node promoProGesCodeLer.procesoGestoraCodLer is missing from graph

Related

Specification API/Criteria API - Group by data returned by existing specification object and findAll(specification,pageable)

Below are my entities:
Product
#Entity
#Table(name = "Product")
public class Product extends ReusableFields
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Long productId;
#NonNull
#Column(name = "product_name")
String productName;
String measurementUnit;
//more fields and getters setters
}
Inward Outward List related to product:
#Entity
#Table(name = "inward_outward_entries")
public class InwardOutwardList extends ReusableFields
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long entryid;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "productId", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
Product product;
#JsonSerialize(using = DoubleTwoDigitDecimalSerializer.class)
Double quantity;
//more fields
}
Inward Inventory:
#Entity
#Table(name = "inward_inventory")
public class InwardInventory extends ReusableFields implements Cloneable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "inwardid")
Long inwardid;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "inwardinventory_entry", joinColumns =
{ #JoinColumn(name = "inwardid", referencedColumnName = "inwardid") }, inverseJoinColumns =
{ #JoinColumn(name = "entryId", referencedColumnName = "entryId") })
Set<InwardOutwardList> inwardOutwardList = new HashSet<>();
//more fields
}
Inward Inventory Repo:
#Repository
public interface InwardInventoryRepo extends extends JpaRepository<InwardInventory, Long>, JpaSpecificationExecutor<InwardInventory> ,PagingAndSortingRepository<InwardInventory, Long>
{}
Previously the requirement was only to filter data and show as pages based on filters selected by user. So I have a working code to create specification dynamically based on inputs. It is working fine. After creating the specification, I am using:
Page<T> findAll(#Nullable Specification<T> spec, Pageable pageable);
to generate required list of records.
But, now a new requirement has been added to show sum of quantities grouped on product name and measurement unit. i.e. whatever data is returned after filter should be grouped by. Since the filtration logic is already working fine, I do not want to touch it.
Can somehow help how to reuse existing specification object and group the data returned by findall(specification,pageable) method.
What I already tried.
Since specification directly do not support group by, I autowired entity manager and created own criteria query. But this is not giving correct results as all the tables are getting joined twice. Might be because they are joined first during specification object and again during grouping by:
#Service
#Transactional
public class GroupBySpecification {
#Autowired
EntityManager entityManager;
Logger log = LoggerFactory.getLogger(GroupBySpecification.class);
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = root.join(InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = ioList.join(InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
Generated SQL - all the tables joined twice
SELECT DISTINCT product7_.product_name AS col_0_0_,
product10_.measurementunit AS col_1_0_,
Sum(inwardoutw12_.quantity) AS col_2_0_
FROM inward_inventory inwardinve0_
INNER JOIN inwardinventory_entry inwardoutw1_
ON inwardinve0_.inwardid = inwardoutw1_.inwardid
INNER JOIN inward_outward_entries inwardoutw2_
ON inwardoutw1_.entryid = inwardoutw2_.entryid
AND ( inwardoutw2_.is_deleted = 'false' )
INNER JOIN product product3_
ON inwardoutw2_.productid = product3_.productid
INNER JOIN warehouse warehouse4_
ON inwardinve0_.warehouse_id = warehouse4_.warehouse_id
INNER JOIN inwardinventory_entry inwardoutw5_
ON inwardinve0_.inwardid = inwardoutw5_.inwardid
INNER JOIN inward_outward_entries inwardoutw6_
ON inwardoutw5_.entryid = inwardoutw6_.entryid
AND ( inwardoutw6_.is_deleted = 'false' )
INNER JOIN product product7_
ON inwardoutw6_.productid = product7_.productid
INNER JOIN inwardinventory_entry inwardoutw8_
ON inwardinve0_.inwardid = inwardoutw8_.inwardid
INNER JOIN inward_outward_entries inwardoutw9_
ON inwardoutw8_.entryid = inwardoutw9_.entryid
AND ( inwardoutw9_.is_deleted = 'false' )
INNER JOIN product product10_
ON inwardoutw9_.productid = product10_.productid
INNER JOIN inwardinventory_entry inwardoutw11_
ON inwardinve0_.inwardid = inwardoutw11_.inwardid
INNER JOIN inward_outward_entries inwardoutw12_
ON inwardoutw11_.entryid = inwardoutw12_.entryid
AND ( inwardoutw12_.is_deleted = 'false' )
WHERE ( inwardinve0_.is_deleted = 'false' )
AND ( warehouse4_.warehousename LIKE ? )
AND ( product3_.product_name IN ( ?, ?, ?, ? ) )
GROUP BY product7_.product_name,
product10_.measurementunit
You will have to use the existing joins that are created by the specifications. You will probably have to do something like this:
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = getOrCreateJoin(root, InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = getOrCreateJoin(ioList, InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, SingularAttribute<X, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, PluralAttribute<X, ?, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}

JPA Repository - Getting duplicates in List caused by table joins

I am having a very difficult situation and yet the situation is very complex and hard to find similar case in stackoverflow.
I have the following entities
Store
#Data
#Entity
#Table(name = "store")
public class Store implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Enumerated(EnumType.STRING)
#Column(name = "storestatus", nullable = false)
private StoreStatus storeStatus = StoreStatus.UNKNOWN;
#OneToOne
#JoinColumn(name = "storetypecode_id")
private StoreTypeCode storeTypeCode;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "store")
private Address address;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "store")
private Set<StoreTranslation> storeTranslationList = new HashSet<>();
public Store() {
}
StoreTypeCode
#Data
#Entity
#Table(name = "storetypecode")
public class StoreTypeCode implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "displaysort", nullable = false)
private Integer displaySort = 999;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeTypeCode")
private Set<StoreTypeCodeTranslation> storeTypeCodeTranslationList = new HashSet<>();
public StoreTypeCode() {
}
}
And StoreCategory
#Data
#Entity
#Table(name = "storeitemcategory")
public class StoreItemCategory implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#ManyToOne
#JoinColumn(name = "store_id")
private Store store;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "storeitemcategory_storeitem",
joinColumns = {#JoinColumn(name = "storeitemcategory_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "storeitem_id", referencedColumnName = "id")})
private List<StoreItem> storeItems = new ArrayList<>();
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeItemCategory")
private Set<StoreItemCategoryTranslation> storeItemCategoryTranslationList = new HashSet<>();
public StoreItemCategory() {
}
public void addStoreItem(StoreItem storeItem) {
this.storeItems.add(storeItem);
}
}
With the above relationship, here is what I have.
Store A with storeTypeCode ("Cafe") and storeItemCategory ("Iced drinks")
StoreTypeCode has two translations 1) for English, 2) for Chinese.
Whenever I add an item to storeItems in StoreItemCategory, I get duplicates in the list. (And multiple duplicate records are inserted to 'storeitemcategory_storeitem' table.)
StoreItemCategory sic = storeItemCategoryRepository.findById(storeItemCategoryid).get();
sic.addStoreItem(new StoreItem(...));
sic = storeItemCategoryRepository.save(sic);
I suspect this has something to do with the way tables are joined for translations because when I run a query created from Spring for getting StoreItemCategory, I get multiple records of StoreItemCategory (one for English and one for Chinese from StoreTypeCode).
select
*
from
storeitemcategory storeitemc0_
left outer join
store store1_
on storeitemc0_.store_id=store1_.id
left outer join
storetranslation storetrans2_
on store1_.id=storetrans2_.store_id
left outer join
storetypecode storetypec3_
on store1_.storetypecode_id=storetypec3_.id
left outer join
storetypecodetranslation storetypec4_
on storetypec3_.id=storetypec4_.storetypecode_id
left outer join
address address5_
on store1_.id=address5_.store_id
left outer join
storeitemcategorytranslation storeitemc6_
on storeitemc0_.id=storeitemc6_.storeitemcategory_id
left outer join
storeitemcategory_storeitem storeitems7_
on storeitemc0_.id=storeitems7_.storeitemcategory_id
left outer join
storeitem storeitem8_
on storeitems7_.storeitem_id=storeitem8_.id
left outer join
store store9_
on storeitem8_.store_id=store9_.id
left outer join
storeitemtranslation storeitemt10_
on storeitem8_.id=storeitemt10_.storeitem_id
where
storeitemc0_.id=?
All my tables will have translations tables and I am not sure how to get-around with this without using set.
Does anyone have similar experience?

QuerySyntaxException in Spring Data JPA custom query

I have a Entity:
#Entity
#Table(name = "story", schema = "")
#Data
public class Story implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sID", unique = true, nullable = false)
private Long sID;
#Column(name = "vnName", nullable = false)
private String vnName;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
#Column(name = "sUpdate", length = 19)
private Date sUpdate;
}
And:
#Entity
#Table(name = "chapter", schema = "")
#Data
public class Chapter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "chID", unique = true, nullable = false)
private Long chID;
#Column(name = "chName", nullable = false)
private String chName;
#JoinColumn(name = "sID", referencedColumnName = "sID")
#ManyToOne(fetch = FetchType.LAZY)
private Story story;
}
I had created custom pojo to get the latest update story with the latest chapter:
#Data
public class NewStory{
private Story story;
private Chapter chapter;
}
but when I get list :
#Repository
public interface StoryRepository extends CrudRepository<Story, Long> {
#Query(value="SELECT NEW com.apt.truyenmvc.entity.NewStory(s as newstory, c as newchapter)"
+ " FROM story s LEFT JOIN (SELECT * FROM Chapter c INNER JOIN "
+ " (SELECT MAX(c.chID) AS chapterID FROM Story s LEFT JOIN Chapter c ON s.sID = c.sID GROUP BY s.sID) d"
+ " ON c.chID = d.chapterID) c ON s.sID = c.sID order by s.sUpdate desc")
public List<NewStory> getTopView();
}
Error:
Warning error: org.hibernate.hql.internal.ast.QuerySyntaxException: story is not mapped.
Who could help me fix it? Or could it be done in a different way?
The error is pretty self explainatory. And its just a typo in your query. You are using story. And obviously thats not mapped as an Entity.
Fix it to Story

Ebean setIncludeSoftDeletes() throw SQLException

I have two table with #History and #SoftDelet each. When I work with non deleted data it is fine. But when I try find a deleted data for model which contains field with EAGER loading option, using setIncludeSoftDeletes():
t2.find.query().setId(id).setIncludeSoftDeletes().findOne();
I got Exception:
Query threw SQLException:No value specified for parameter 2 Bind values:[1, ] Query was:select t0.id, t0.deleted, t1.id from t2 t0 left join t1_with_history t1 on t1.t2_id = t0.id and (t1.sys_period_start <= ? and (t1.sys_period_end is null or t1.sys_period_end > ?)) where t0.id = ?
why was added a join with view t1_with_history?
Models
#Entity
#Table(name = "t1")
#History
public class t1 extends Model {
public static final Finder<String, t1> find = new Finder<>(t1.class);
#Id
public Long id;
#SoftDelete
public Boolean deleted;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "t2_id")
private t2 _t2;
#JsonIgnore
public t2 get_t2(){
return this._t2;
}
}
#Entity
#Table(name = "t2")
#History
public class t2 extends Model {
public static final Finder<String, t2> find = new Finder<>(t2.class);
#Id
public Long id;
#SoftDelete
public Boolean deleted;
#OneToOne(mappedBy = "_t2", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public t1 _t1;
}

QueryDSL - HHH000104: firstResult/maxResults specified with collection fetch; applying in memory

In my Spring Boot/Data/JPA/QueryDSL application I have a following entities:
#Entity
#NamedEntityGraph(name = "graph.User", attributeNodes = { #NamedAttributeNode("authorities") })
#Table(name = "users")
public class User extends BaseEntity implements UserDetails {
private static final long serialVersionUID = 8884184875433252086L;
#Id
#SequenceGenerator(name = "users_id_seq", sequenceName = "users_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "users_id_seq")
private Long id;
private String username;
....
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "users_authorities", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "authority_id") })
private Set<Authority> authorities = new HashSet<Authority>();
....
}
#Entity
#Table(name = "authorities")
public class Authority implements GrantedAuthority {
private static final long serialVersionUID = 6118293571787931020L;
#Id
#SequenceGenerator(name = "authorities_id_seq", sequenceName = "authorities_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "authorities_id_seq")
private Integer id;
#NotEmpty
private String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "authorities")
private Set<User> users = new HashSet<User>();
....
}
and a following load method:
#Override
public List<User> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
PageRequest pageRequest = null;
Page<User> page;
int pageIndex = first / pageSize;
if (sortField == null || sortField.isEmpty()) {
pageRequest = new PageRequest(pageIndex, pageSize);
} else {
Sort.Direction direction = sortOrder == SortOrder.ASCENDING ? Sort.Direction.ASC : Sort.Direction.DESC;
pageRequest = new PageRequest(pageIndex, pageSize, new Sort(direction, sortField));
}
Predicate predicate = UserExpressions.lazyPredicate(filters);
page = userService.findAll(predicate, pageRequest);
....
}
After this method invocation I see following debug information:
Hibernate: select count(user0_.id) as col_0_0_ from users user0_ where user0_.id is not null
1266079 WARN o.h.h.i.ast.QueryTranslatorImpl - HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
Hibernate: select user0_.id as id1_10_0_, authority2_.id as id1_0_1_, user0_.created_date as created_2_10_0_, user0_.updated_date as updated_3_10_0_, user0_.account_non_locked as account_4_10_0_, user0_.age as age5_10_0_, user0_.birthday as birthday6_10_0_, user0_.email as email7_10_0_, user0_.enabled as enabled8_10_0_, user0_.first_name as first_na9_10_0_, user0_.gender as gender10_10_0_, user0_.last_name as last_na11_10_0_, user0_.password as passwor12_10_0_, user0_.username as usernam13_10_0_, authority2_.name as name2_0_1_, authoritie1_.user_id as user_id1_10_0__, authoritie1_.authority_id as authorit2_11_0__ from users user0_ left outer join users_authorities authoritie1_ on user0_.id=authoritie1_.user_id left outer join authorities authority2_ on authoritie1_.authority_id=authority2_.id where user0_.id is not null
As you can see, according to "WARN o.h.h.i.ast.QueryTranslatorImpl - HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!" - pagination is applied in memory that is not completely acceptable in case of thousands records.
How to change my configuration in order to apply pagination at database level ?

Resources