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.
Related
I have a query where I retrieve DB values like:
public HashMap<String,String> getStateCapital(String Country) {
HashMap<String,String> c1 = new HashMap<String,String>();
String query = DB query+Country;
try {
Connection db = DriverManager.getConnection(URL,Username,Password);
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(query);
while(rs.next()) {
c1.put("StateName",rs.getString(3));
c1.put("CapitalCity",rs.getString(4));
System.out.println(c1); // First c1 output
}
System.out.println(c1); // Second c1 output
}
}
Here when I print the first c1 I get HashMap<key,value> Output as
{StateName=Karnata, CapitalCity=Bengaluru}
{StateName=Kerala, CapitalCity=Thiruvananthapuram}
{StateName=Telangana, CapitalCity=Hyderabad}
{StateName=TamilNadu, CapitalCity=Chennai}
etc...
But when I print the second c1 I get HashMap<key,value> Output as
{StateName=Karnata, CapitalCity=Bengaluru}
How do I retrieve all the HashMap<key,value> like the first c1 Output outside while loop?
The values are overridden with each iteration, that's why the first println statement correctly prints out the key and value. The Map only contains the last added pair as long as by definition it doesn't allow duplicate keys.
I recommend rather using an object encapsulating the stateName and capitalCity and adding such an object into a List.
public class State {
private final String name;
private final String capitalCity
// all-args constructor and getters
}
List<State> c1 = new ArrayList<>();
...
// in the while-loop
c1.add(new State(rs.getString(3), rs.getString(3)));
Also, it is possible to group the map-values into the List resulting in Map<String, List<String>>.
Map<String, List<String>> c1 = new HashMap<>();
...
// in the while-loop
c1.computeIfAbsent("StateName", k -> new ArrayList<>()).add(rs.getString(3));
c1.computeIfAbsent("CapitalCity", k -> new ArrayList<>()).add(rs.getString(3));
An alternative solution would be using a sort of MultiValueMap (from Spring framework, but feel free to search for other libraries or frameworks if they offer something similar) that is just a wrapper to the structure above: Map<String, List<String>>.
A side note: Always program against interfaces, i.e.:
Map<String, String> map = new HashMap<>(); // :)
HashMap<String, String> map = new HashMap<>(); // :(
Consider that Map is a list of entries. Each entry is composed by a key and a value.
The key inside is unique, then the value is overridden every time you put the same key.
You can resolve to create a class that represents the state.
public class State {
private String stateName;
private String capitalCity;
public State(String stateName, String capitalCity) {
this.stateName = stateName;
this.capitalCity = capitalCity;
}
public String getStateName() {
return stateName;
}
public String getCapitalCity() {
return capitalCity;
}
#Override
public String toString() {
return "State{" +
"stateName='" + stateName + '\'' +
", capitalCity='" + capitalCity + '\'' +
'}';
}
}
And you can fill a list of State in order to avoid overriding.
public HashMap<String, String> getStateCapital(String Country) {
List<State> states = new ArrayList<>();
String query = DB query + Country;
try {
Connection db = DriverManager.getConnection(URL, Username, Password);
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(query);
while (rs.next()) {
states.add(new State(rs.getString(3), rs.getString(3)));
}
System.out.println(states);
}
}
I just wanted to know how to pass column name and its value to #Query annotation in Spring Data JPA.
Basically column names will be static and we used to put every column as a element in Entity class. But here I want something different, here column name will be dynamic I will be passing this value as Parameter to the method defined in repository.
Table - Calendar
Columns - id, PersonName, 1, 2, 3......31
Above is the table structure, 1,2,3,.....31 are the column names which represents calendar days and we have values in that columns. I'm using Spring Data JPA to fetch data from DB.
Here I just wanted to fetch person name for a particular day.
Below given the function defined in repository.
#Query("select c from Calendar c where :calendarDay=:value")
List<Calendar> getPersonName(#Param("calendarDay") String calendarDay, #Param("value") String value);
This is not working for me.
Any help would be appreciated.
The only dynamic parameter Spring JPA supports is #{#entityName}. Dynamic column names in #Query annotations are not supported., and that is what you are trying to accomplish.
Your only option is to construct a query manually using either QueryDSL, Specifications or Criteria API or simply by building a query string and passing it to your EntityManager. Regardless, you'll have to write code for that.
See, for instance:
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
How to add custom column name Spring Data JPA?
Take a look at sping data Specifications. You can find your solution there!
Reading the docs you can see that if Calendar is your domain (I would try to find a different name for my domain, there is a Calendar class in Java SE already), then you could use something like the above,
#Repository
public interface CalendarRepository extends JpaRepository<Calendar, Integer>, JpaSpecificationExecutor<Calendar> {
}
public class CalendarSpecification implements Specification<Calendar> {
private String randomColumnName; // A varchar column.
private String valueToSearchFor;
public CalendarSpecification(String randomColumnName, String valueToSearchFor) {
this.randomColumnName = randomColumnName;
this.valueToSearchFor = valueToSearchFor;
}
#Override
public Predicate toPredicate(Root<Calendar> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.and(builder.equal(root.<String>get(this.randomColumnName), this.valueToSearchFor));
}
}
#Service
public class CalendarService {
#Autowired
private CalendarRepository calendarRepository;
public List<Calendar> findCustom(String randomColumnName, String valueToSearchFor) {
CalendarSpecification cs = new CalendarSpecification(randomColumnName, valueToSearchFor);
return calendarRepository.find(cs);
// Or using lambda expression - without the need of CalendarSpecification class.
// return calendarRepository.find((Root<ProductCategory> root, CriteriaQuery<?> query, CriteriaBuilder builder) -> {
// return builder.and(builder.equal(root.<String>get(randomColumnName), valueToSearchFor));
// });
}
}
Maybe you can use CASE, WHEN.
SELECT
Id,
PersonName,
CASE
WHEN ? = 'day_01' THEN day_01
WHEN ? = 'day_02' THEN day_02
WHEN ? = 'day_03' THEN day_03
WHEN ? = 'day_04' THEN day_04
WHEN ? = 'day_05' THEN day_05'
ELSE 0
END
AS Value FROM Calendar
Java Code
// customize entity
public interface ITask {
Long getId();
String getName();
String getValue();
}
#Repository
public interface CalendarRepository {
static final String CASE_WHEN = "\nCASE\n"
+ " WHEN :field = 'day_01' THEN day_01\n"
+ " WHEN :field = 'day_02' THEN day_02\n"
+ " WHEN :field = 'day_03' THEN day_03\n"
+ " WHEN :field = 'day_04' THEN day_04\n"
+ " WHEN :field = 'day_05' THEN day_05\n"
+ " ELSE 0\n"
+ "END\n";
#Query(nativeQuery = true, value = "SELECT Id, PersoneName, " + CASE_WHEN + " AS Value FROM Calendar WHERE field = :field")
public List<ITask> findValues(#Param(value = "field") String field);
}
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.
I just wanted to know how to pass column name and its value to #Query annotation in Spring Data JPA.
Basically column names will be static and we used to put every column as a element in Entity class. But here I want something different, here column name will be dynamic I will be passing this value as Parameter to the method defined in repository.
Table - Calendar
Columns - id, PersonName, 1, 2, 3......31
Above is the table structure, 1,2,3,.....31 are the column names which represents calendar days and we have values in that columns. I'm using Spring Data JPA to fetch data from DB.
Here I just wanted to fetch person name for a particular day.
Below given the function defined in repository.
#Query("select c from Calendar c where :calendarDay=:value")
List<Calendar> getPersonName(#Param("calendarDay") String calendarDay, #Param("value") String value);
This is not working for me.
Any help would be appreciated.
The only dynamic parameter Spring JPA supports is #{#entityName}. Dynamic column names in #Query annotations are not supported., and that is what you are trying to accomplish.
Your only option is to construct a query manually using either QueryDSL, Specifications or Criteria API or simply by building a query string and passing it to your EntityManager. Regardless, you'll have to write code for that.
See, for instance:
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
How to add custom column name Spring Data JPA?
Take a look at sping data Specifications. You can find your solution there!
Reading the docs you can see that if Calendar is your domain (I would try to find a different name for my domain, there is a Calendar class in Java SE already), then you could use something like the above,
#Repository
public interface CalendarRepository extends JpaRepository<Calendar, Integer>, JpaSpecificationExecutor<Calendar> {
}
public class CalendarSpecification implements Specification<Calendar> {
private String randomColumnName; // A varchar column.
private String valueToSearchFor;
public CalendarSpecification(String randomColumnName, String valueToSearchFor) {
this.randomColumnName = randomColumnName;
this.valueToSearchFor = valueToSearchFor;
}
#Override
public Predicate toPredicate(Root<Calendar> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.and(builder.equal(root.<String>get(this.randomColumnName), this.valueToSearchFor));
}
}
#Service
public class CalendarService {
#Autowired
private CalendarRepository calendarRepository;
public List<Calendar> findCustom(String randomColumnName, String valueToSearchFor) {
CalendarSpecification cs = new CalendarSpecification(randomColumnName, valueToSearchFor);
return calendarRepository.find(cs);
// Or using lambda expression - without the need of CalendarSpecification class.
// return calendarRepository.find((Root<ProductCategory> root, CriteriaQuery<?> query, CriteriaBuilder builder) -> {
// return builder.and(builder.equal(root.<String>get(randomColumnName), valueToSearchFor));
// });
}
}
Maybe you can use CASE, WHEN.
SELECT
Id,
PersonName,
CASE
WHEN ? = 'day_01' THEN day_01
WHEN ? = 'day_02' THEN day_02
WHEN ? = 'day_03' THEN day_03
WHEN ? = 'day_04' THEN day_04
WHEN ? = 'day_05' THEN day_05'
ELSE 0
END
AS Value FROM Calendar
Java Code
// customize entity
public interface ITask {
Long getId();
String getName();
String getValue();
}
#Repository
public interface CalendarRepository {
static final String CASE_WHEN = "\nCASE\n"
+ " WHEN :field = 'day_01' THEN day_01\n"
+ " WHEN :field = 'day_02' THEN day_02\n"
+ " WHEN :field = 'day_03' THEN day_03\n"
+ " WHEN :field = 'day_04' THEN day_04\n"
+ " WHEN :field = 'day_05' THEN day_05\n"
+ " ELSE 0\n"
+ "END\n";
#Query(nativeQuery = true, value = "SELECT Id, PersoneName, " + CASE_WHEN + " AS Value FROM Calendar WHERE field = :field")
public List<ITask> findValues(#Param(value = "field") String field);
}
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());
}
}