JPA - SELECT entity FROM a subset - java

I've got an Entity class named Fee. After performing an initial query, if more than one Fee is returned, I'd like to add some (WHERE) conditions only on the rows returned by the first query. Since a code snippet is better than a thousand words, here it is:
// Results of initial query
List<Fee> fees = queryFindFees.getResultList();
if (fees == null || fees.size() <= 0)
return null;
if (fees.size() == 1) {
Fee f = fees.get(0);
jpa.refresh(f);
return f;
}
// More than one fee found
String sqlBase = "SELECT f FROM Fee f WHERE f IN :fees";
String sqlComplete = sqlBase/* + " AND f.isValid = ?1"*/;
queryFindFees = jpa.createQuery(sqlComplete);
queryFindFees.setParameter("fees", fees);
// Commented out for test
// queryFindFees.setParameter(1, "Y");
List<Fee> specFees = queryFindFees.getResultList();
Now, since I'm actually asking for the EntityManager to return all the Fees already returned in the previous query, I would expect the same resultset. Instead the list specFees is always empty.
What am I doing wrong?
Thanks in advance,
Luca
EDIT 1: Details of Entity class Fee
#Entity
public class Fee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private long id;
#Column(nullable = false, length = 1)
private String isValid;
...
#Override
public boolean equals(Object arg0) {
if (!(arg0 instanceof Fee))
return false;
Fee f0 = (Fee) arg0;
return f0.getId() == this.getId();
}
}
The generated SQL (sqlComplete variable) is as expected:
SELECT f FROM Fee f WHERE f IN :fees
EDIT 2: As suggested by Deepak, using the collection of IDs works:
String sqlBase = "SELECT f FROM Fee f WHERE f.id IN :feesIds";
String sqlComplete = sqlBase/* + " AND f.isValid = ?1"*/;
List<Long> feesIds = new ArrayList<Long>();
for (Fee f : fees) {
feesIds.add(f.getId());
}
queryFindFees = jpa.createQuery(sqlComplete);
queryFindFees.setParameter("feesIds", feesIds);
Using this code the query works (returns all the original Fees), however if possible I would like to avoid using the for cycle because the number of Fee instances may be very large...

Looks like your overridden equals method is creating problem .Make your id Long Object and use the below overridden method .Alternatively u can use this query also
SELECT f FROM Fee f WHERE f.id IN :fees
this time your fees parameter will contain list of ids of Fee objects
#Entity
public class Fee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private Long id;
#Column(nullable = false, length = 1)
private String isValid;
...
#Override
public boolean equals(Object arg0) {
if (!(arg0 instanceof Fee))
return false;
Fee f0 = (Fee) arg0;
return f0.getId().equals(this.getId());
}
}

Related

How to get Page as result in Querydsl query with fetch or fetchResults properly?

Hi what i trying to achieve here is, i want to submit Pageable data into QueryDsl query and get the result as Page, how can i do it properly? here is what i do until now :
here is my controller :
#PostMapping("/view-latest-stock-by-product-codes")
public ResponseEntity<RequestResponseDTO<Page<StockAkhirResponseDto>>> findStockByProductCodes(
#RequestBody StockViewByProductCodesDto request) {
Page<StockAkhirResponseDto> stockAkhir = stockService.findByBulkProduct(request);
return ResponseEntity.ok(new RequestResponseDTO<>(PESAN_TAMPIL_BERHASIL, stockAkhir));
}
in my controller i submit StockViewByProductCodesDto which is looked like this :
#Data
public class StockViewByProductCodesDto implements Serializable {
private static final long serialVersionUID = -2530161364843162467L;
#Schema(description = "Kode gudang yang ingin di tampilkan", example = "GBKTJKT1", required = true)
private String warehouseCode;
#Schema(description = "id dari sebuah branch", example = "1", required = true)
private Long branchId;
#Schema(description = "Kode Branch", example = "JKT", required = true)
private String branchCode;
#Schema(description = "Kode Product yang merupakan kode yang di ambil dari master product", example = "[\"MCM-508\",\"TL-101\"]", required = true)
private List<String> productCodes;
#Schema(description = "Size of row per page", example = "15", required = true)
#NotNull
private int size;
#Schema(description = "Page number", example = "1", required = true)
#NotNull
private int page;
#Schema(description = "Sort by", example = "id", required = false)
private String sort;
}
and here is my service :
public Page<StockAkhirResponseDto> findByBulkProduct(StockViewByProductCodesDto request) {
String warehouseCode = request.getWarehouseCode();
Long branchId = request.getBranchId();
String branchCode = request.getBranchCode();
List<String> productCodes = request.getProductCodes();
Set<String> productCodesSet = new HashSet<String>(productCodes);
Pageable pageable = PageUtils.pageableUtils(request);
Page<StockAkhirResponseDto> stockAkhir = iStockQdslRepository.findBulkStockAkhirPage(warehouseCode, branchId, branchCode, productCodesSet, pageable);
return stockAkhir;
}
as you can see, i extract pageable information with PageUtils.pageableUtils(request), here is my pageableUtils function looked like :
public static Pageable pageableUtils(RequestKeyword request) {
int page = 0;
int size = 20;
if (request.getPage() > 0) {
page = request.getPage() - 1;
}
if (request.getSize() > 0) {
size = request.getSize();
}
if (!request.getSort().isEmpty()) {
return PageRequest.of(page, size, Sort.by(request.getSort()).descending());
} else {
return PageRequest.of(page, size);
}
}
after i got the Pageable data, i submit it into my repository, which is looked like this :
public Page<StockAkhirResponseDto> findBulkStockAkhirPage(String warehouseCode, Long branchId, String branchCode,
Set<String> productCodes, Pageable pageable) {
JPQLQuery<Tuple> query = new JPAQuery<>(em);
long offset = pageable.getOffset();
long limit = pageable.getPageSize();
QStock qStock = QStock.stock;
NumberExpression<Integer> totalQty = qStock.qty.sum().intValue();
query = query.select(qStock.productId, qStock.productCode, totalQty).from(qStock)
.where(qStock.warehouseCode.eq(warehouseCode), qStock.productCode.in(productCodes),
qStock.branchCode.eq(branchCode), qStock.branchId.eq(branchId))
.groupBy(qStock.productId, qStock.productCode);
query.limit(limit);
query.offset(offset);
QueryResults<Tuple> result = query.fetchResults();
long total = result.getTotal();
List<Tuple> rows = result.getResults();
List<StockAkhirResponseDto> stockAkhirDto = rows.stream()
.map(t -> new StockAkhirResponseDto(t.get(0, Long.class), t.get(1, String.class), t.get(2, Integer.class)))
.collect(Collectors.toList());
return new PageImpl<>(stockAkhirDto, pageable, total);
}
there is no error in my editor when viewing this my repository and i able to run my project, but when i execute my repository function, i got this error :
"org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]; nested exception is
java.lang.IllegalArgumentException:
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]"
the problem is here, on this line :
QueryResults<Tuple> result = query.fetchResults();
when i execute that line, it give me that error, i try to get the fetchResult, because i want to get the .getTotal() for the total.
but if i execute the query with .fetch(), it worked fine, like this :
List<StockAkhirResponseDto> stockAkhirDto = query.fetch()
i got my sql result execute correctly, what did i missed here? how do i get Page result correctly?
Your problem could be related with an open QueryDSL issue. The documented issue has to do with the use of fetchCount but I think very likely could be also your case.
Consider the following comment in the mentioned issue:
fetchCount() uses a COUNT function, which is an aggregate function. Your query already has aggregate functions. You cant aggregate aggregate functions, unless a subquery is used (which is not available in JPA). Therefore this use case cannot be supported.
The issue also provides a temporary solution.
Basically, the idea is be able to perform the COUNT by creating a statement over the initial select. AFAIK it is not possible with QueryDsl and this is why in the indicated workarounds they access the underline mechanisms provided by Hibernate.
Perhaps, another thing that you can try to avoid the limitation is to create a database view for your query, the corresponding QueryDsl objects over it, and use these objects to perform the actual computation. I am aware that it is not an ideal solution, but it will bypass this current QueryDsl limitation.

CrudRepository - how to find objects by map key and value

I have problem with "selecting" values using CrudRepository.
For example:
I have Client class:
#Entity
public class Client {
#Id
private Long id;
#ManyToMany
Map<AttributeType, Attribute> map = new HashMap<>();
}
and AttributeType class:
#Entity
public class AttributeType {
#Id
private Long id;
#Column(unique = true)
private String name;
}
"Attribute" is abstract entity which have "subtypes" of String and Boolean (AttributeBoolean, AttributeString), and both have 2 fields ID, and VAL (which is String val or Boolean val depends on "className").
So, if I want to select client list which have "AttributeType" in map I can use:
public interface ClientRepository extends CrudRepository<Client, Long> {
#Query("select cli from Client cli where KEY(cli.map) = :attributeType ")
List<Client> selectByAttributeType(#Param("attributeType ") AttributeType attributeType );
}
But I have problem with "selecting clients which AttributeType equals x and that Attribute equals y".
I was trying to use:
#Query("select cli from Client cli \n" +
"where KEY(cli.map) = :attributeType \n" +
"and VALUE(cli.map).val = :val")
List<Client> selectByAttributeTypeAndParam(
#Param("attributeType") AttributeType attributeType, #Param("val") String val);
But it throws exception: Caused by: java.lang.IllegalArgumentException: Parameter value [xxxxxxxx] did not match expected type [java.lang.Boolean (n/a)].
So, the question is:
Do you have any idea, how can I select Every Client where:
AttributeType = x
and value from map (for that AttributeType) = y ?
//edit...
To better understund:
We have situation like this:
AttributeType at1 =... //does not matter what is it
AttributeType at2 =... //does not matter what is it
AttributeType at3 =... //does not matter what is it
Client c1 = new Client();
Map<AttributeType, Attribute> map1 = c1.getMap();
map1.put(at1, new AttributeString("123456789");
map1.put(at2, new AttributeBoolean(true);
Client c2 = new Client();
Map<AttributeType, Attribute> map2 = c2.getMap();
map2.put(at1, new AttributeString("111111111");
map2.put(at3, new AttributeBoolean(true);
So, i want CrudRepository function which take 2 parameters.
1) AttributeType
2) SomeValue (can be String or Boolean)
If this function looks like:
#Query("some query - no idea how to write it")
List<Client> selectByAttributeTypeAndParam(
#Param("attributeType") AttributeType attributeType, #Param("val") String val);
and if i run:
List<Client> someList = selectByAttributeTypeAndParam(at1, "12345679");
I want that someList have 1 record which is c1.
And if i have equivalent function for Boolean search and i run:
List<Client> someList = selectByAttributeTypeAndParam(at2, true);
Than i want that someList have both c1 and c2 records.

Criteria 2 sum Ljava.lang.Object; cannot be cast

I'm trying to build a request using Criteria with 2 sum but having this error:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to
Stats
Stats class:
#Entity
#Table(name = "STATS")
#Getter
public class Stats{
#Id
#Column(name = "STAT_ID")
private int statId;
#Column(name = "NB_STATS_CONFIRMED")
private long nbStatsConfirmed;
#Column(name = "NB_STATS_DELETED")
private long nbStatsDeleted;
#Column(name = "DATE_STAT")
private Date dateStat;
}
Looks like:
STAT_ID NB_STATS_CONFIRMED NB_STATS_DELETED DATE_STAT
1 5 2 2018-01-23
2 2 1 2018-01-22
3 8 0 2018-01-20
I want to sum NB_STATS_CONFIRMED and NB_STATS_DELETED between 2 dates (DATE_STAT).
Criteria request:
#SuppressWarnings("unchecked")
#Override
public List<Stats> listStats(final Date start, final Date end) {
Criteria cr = createRequest(); // method creating a criteria
if (start != null) {
cr.add(Restrictions.ge("dateStat", start));
}
if (fin != null) {
cr.add(Restrictions.le("dateStat", end));
}
cr.setProjection(Projections.projectionList()
.add(Projections.sum("nbStatsConfirmed"), "nbStatsConfirmed")
.add(Projections.sum("nbStatsCanceled"), "nbStatsCanceled"));
return cr.list();
}
And then I call the method:
List<Stats> listStats = dao.listStats(start, end);
I tried [Ljava.lang.Object; cannot be cast to & Sum projection and results constraint
What am I doing wrong ?
I found a solution if some one is interested but have to do 2 requests:
long nbStatsConfirmed = (long) AJobUtil.valeurDefaut((cr.setProjection(
Projections.sum("nbStatsConfirmed"))
.uniqueResult()), (long) 0);
long nbStatsCanceled = (long) AJobUtil.valeurDefaut((cr.setProjection(
Projections.sum("nbStatsCanceled"))
.uniqueResult()), (long) 0);
long[] stats = { nbStatsConfirmed, nbStatsCanceled };
return stats;
Still open if you have a different solution (with 1 request) !

jpa 2 criteria hibernate 5.2 nested joins

I'm trying to add to my crud services the possibility to specify what nested relationship I need so I don't have to read everything from the database.
Take for example I have those entities
Company.java
private List<Department> departments;
private SalaryCode salaryCode;
Department.java
private List<Employee> employees;
private Company company;
private SalaryCode salaryCode;
Employee.java
private Department department;
private SalaryCode salaryCode
And my Criteria query for now is this :
Session session = sessionFactory.openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(clazz);
Root<T> root = criteriaQuery.from(clazz);
//nestedRelationships is a varargs passed as parameters
for(String nestedRelationship : nestedRelationships) {
root.fetch(nestedRelationship, JoinType.LEFT);
}
List<T> result = session.createQuery(criteriaQuery.select(root)).list();
The thing is if I specify "department" as nestedRelationship and querying for Employee entity it works well but when I try to specify "department.salaryCode" it doesn't work saying " Unable to locate Attribute with the the given name ".
Of course I'm fetching "department" first and then "department.salaryCode".
Is it supported? If yes how does it work and if it's not supported what can I do?
Yes,it is supported. You need to use Joins.
Root<Company> root = criteriaQuery.from(Company.class);
Join<Company,Department> joinDepartment = root.join( Company_.departments );
Join<Department,SalaryCode> joinSalaryCode = joinDepartment.join( Department_.salaryCode );
To generate metamodel classes(e.g. Department_ ) have a look at here.
I found a solution by making an algorithm using the Root element
protected void fetch(Root<T> root, String... joins) {
//Sort the joins so they are like this :
//A
//A.F
//B.E
//B.E.D
//B.G
Arrays.sort(joins);
Map<String, Fetch> flattenFetches = new HashMap<>();
for (String join : joins) {
try {
if (join.contains(".")) {
String[] subrelations = join.split("\\.");
Fetch lastRelation = null;
int i;
for (i = subrelations.length - 1; i >= 0; i--) {
String subJoin = String.join(".", Arrays.copyOf(subrelations, i));
if (flattenFetches.containsKey(subJoin)) {
lastRelation = flattenFetches.get(subJoin);
break;
}
}
if (lastRelation == null) {
lastRelation = root.fetch(subrelations[0], JoinType.LEFT);
flattenFetches.put(subrelations[0], lastRelation);
i = 1;
}
for (; i < subrelations.length; i++) {
String relation = subrelations[i];
String path = String.join(".", Arrays.copyOf(subrelations, i + 1));
if (i == subrelations.length - 1) {
Fetch fetch = lastRelation.fetch(relation, JoinType.LEFT);
flattenFetches.put(path, fetch);
} else {
lastRelation = lastRelation.fetch(relation, JoinType.LEFT);
flattenFetches.put(path, lastRelation);
}
}
} else {
Fetch fetch = root.fetch(join, JoinType.LEFT);
flattenFetches.put(join, fetch);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
and to use it I just have to do for example :
employeeController.getAll("punches", "currentSchedule.shifts", "defaultDepartment.currentSchedule.shifts",
"defaultDepartment.company.currentSchedule.shifts", "bankExtras")
I would like to comment the algorithm but I do not have time and it's pretty easy to understand

Hibernate - how to test sequence mapping and strategy in jUnit

What's the methodology of testing #SequenceGenerator from Hibernate? I want to be sure that every sequence is perfectly mapped, no mistake in spelling, and incrementing is done by 1. Is there any way to do this dynamically for all sequences?
Here's the sample of my sequence mapping:
#Column(name = "ADDRESS_ID", nullable = false, precision = 20)
#Id
#SequenceGenerator(name = "AddressSeq", sequenceName = "ADDRESS_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AddressSeq")
private Long addressId;
You can use this answer to obtain a list of your entity classes:
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
reflections = new Reflections(
new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false),
new ResourcesScanner())
.setUrls(
ClasspathHelper.forClassLoader(classLoadersList
.toArray(new ClassLoader[0])))
.filterInputsBy(
new FilterBuilder().include(FilterBuilder
.prefix("me.business.model"))));
And get your DDL with:
ClassPathResource cpr = new ClassPathResource("db/schema.sql");
schemaContent = new String(FileCopyUtils.copyToByteArray(cpr
.getInputStream())).toLowerCase();
And get the sequence for each class witH;
private String getSequenceName(Class<?> clazz) {
for (Field f : clazz.getDeclaredFields()) {
if (f.isAnnotationPresent(SequenceGenerator.class)) {
SequenceGenerator sg = f.getAnnotation(SequenceGenerator.class);
return sg.sequenceName();
}
}
return null;
}
The test is simple:
Set<Class<?>> entities = reflections.getSubTypesOf(Object.class);
for (Class<?> clazz : entities) {
String name = getSequenceName(clazz);
if (name == null)
continue;
if (!schemaContent.contains(name.toLowerCase())) {
fail("The clazz " + clazz.getSimpleName()
+ " has a sequence called: " + name
+ " and it doesn't exits");
}
}
You can see it here
If you want to see if it works, change the sequence name in one of your entitites and run the test.

Categories