no parameter binding found for name (Spring data jpa) - spring-data-jpa

I have two entities like A and B such that:
class B {
private Integer id;
private String field1;
private String field2;
// getters and setters
}
class A {
private Date date;
//one to one mapping is there between A and B
private B b;
//getters and setters
}
I have a spring data repository such that:
#Query("from A a where a.date= :date and a.b.id =:#{#b.id}")
A findByBAndDate(#Param(value = "date") Date date,#Param(value = "b") B b);
But I am getting the exception, no parameter binding found for name b!.
However, if I modify the above query as:
#Query("from A a where a.b.id =:#{#b.id}")
A findByB(#Param(value = "b") B b);
all works fine. What's the issue with this.

It seems that we must use SPeL for all parameters in the query, not mixing jpa ways and SPel : :date mixed with :#{#b.id}.
Try this, I have not tested I don't know how it would behave with a Date :
#Query("from A a where a.date= :#{#date} and a.b.id =:#{#b.id}")
A findByBAndDate(#Param(value = "date") Date date,#Param(value = "b") B b);

Related

Bug in Spring Data JPA: Spring Data returns List<BigInteger> instead of List<Long>

I have DAO implementation over spring-data:
public interface TestDataRepository extends CrudRepository<DpConfigData, Long> {
#Query(value = "select distinct(oid) from unit", nativeQuery = true)
List<Long> testMethod();
}
And unit test to test menioned DAO:
#Test
public void test(){
List<Long> testData = dpConfigDataEntityDataRepository.testMethod();
for (Long oid:testData){
System.out.print(oid);
}
}
Running test give strange result - List<Long> testData in runtime is populated by BigInteger instances, not by Long. As result I get ClassCastException: java.math.BigInteger cannot be cast to java.lang.Long
JPA implementation - Hibernate.
As DB I use PostgreSQL, unit.oid field has BigInt type on DB layer.
It is mapped to Long in case of fetching whole unit, but with custom query as "select distinct ..." something went wrong and it is mapped to BigInteger.
So, my question: what is the cause of such strange behaviour?
How to solve/workaround it in elegant way?
This is a issue with Spring data JPA.
If in DB the datatype is defined as BigInteger and in JPA query we tries to fetch as Long then it will not give any error , but it set value as BigInteger in Long datatype.
Solutions:
Use BigInteger as return type
#Query(value = "select distinct(oid) from unit", nativeQuery = true)
List<BigInteger> testMethod();
then set the variable as below.
Long variable = bigIntegerValue.longValue();
Use String as return Type and convert to Long
#Query(value = "select distinct(oid) from unit", nativeQuery = true)
List<String> testMethod();
then set the value as
Long variable = Long.valueOf(stringValue);
Change DB column type to Integer/Number.
Get the Value from Entity Object.
Long variable = dpConfigData.getOid();
where dpConfigData is object of Entity(DpConfigData.class)
BigInt in postgresql maps to BigInteger because its unsigned
I think your best option is to change oid from Long to BigInteger in your JPA object
Finally I worked around this problem by manual mapping on "service" layer.
Example(pseudo code):
public interface TestDataRepository extends CrudRepository<DpConfigData, Long> {
#Query(value = "select distinct(oid) from unit", nativeQuery = true)
List<Object> testMethod();
}
}
then in Service Layer I do manual mapping:
public class TestServiceImpl extends TestService {
pulic List<Object> testMethod(){
List<Object> rawList = testDataRepository.testMethod();
List<Object> resultList = new ArrayList(rawList.size());
for(Object rw:rawList){
resultList.add(Long.valueOf(String.valueOf(rw)));
}
return resultList;
}
}
This issue seems to be resolved in version 2.1.8
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
You can try this by using JPQL as below:
public interface TestDataRepository extends
JpaRepository<DpConfigData, Long> {
#Query(value = "select distinct(u.oid) from unit u")
List<Long> testMethod();
}
Just make sure that your Entity Object should also have same data type Long for the given attribute.

JPA link field value

I have three entities, for example A, B, C. Entity A is parent for B, with inheritance type joined. Entity B aggregates entity C with ManyToOne relationship.
Structure looks like next:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
class A {
String str;
String cStr;
}
#Entity
class B extends A {
#ManyToOne
C c;
}
#Entity
class C {
String str;
}
I don't know if it is possible at all on entity level but, I need to link value of str from C to the filed A.cStr. How it should looks like: I create entity B with populated value of c, store it and value from C.str is populating into filed A.cStr. And when I fetch A from datebase I can see A.cStr with same value as C.str has.
No, it's not possible. All you need to do to get the C string from A is to implement a method and override it in B:
in A:
public String getCString() {
return null;
}
in B:
#Override
public String getCString() {
return c.getStr();
}
Of course, the instances of A that are not B instances won't have any CString.

JPA basic type ElementCollection computed value in query

Consider the following Entity :
#Entity
public class Parent{
#Column
private String name;
#ElementCollection
#CollectionTable(name="REF_TABLE",joinColumns=#JoinColumn(name="REF_COLUMN_ID"))
#Column(name="REF_COLUMN_VALUE")
public Set<String> getValues() {
return values;
}
}
I need to implement JPA criteria where clause for "UPPER(REF_COLUMN_VALUE) IN (?values)"
So far I have :
root.joinSet(Parent_.values, JoinType.LEFT).in(collectionOfValues)
How can I apply UPPER function for each element in values ?
I know I can achieve this by defining and wrapping String value as #Embeddable class, that would give me meta-attribute for value property and I could do :
joinSet = root.joinSet(Parent_.values, JoinType.LEFT);
criteriaBuilder.isMember(criteriaBuilder.upper(joinSet.get(WrapedString_.value)),criteriaBuilder.literal(collectionOfValues))
Can I do it without introducing the wrapper ?
Thanks
In case someone interested in :
SetJoin<Object, Object> setJoin = root.joinSet(Parent_.values.getName(), JoinType.LEFT);
CriteriaBuilder.In<String> in = criteriaBuilder.in(criteriaBuilder.upper(setJoin.as(String.class)));
for(String v:collectionOfValues){
in.value(v.toUpperCase());
}
Don't forget to add distinct to your query as LEFT_JOIN is being used.

Is there a way to transform objects that spring data repositories return?

Right now I have an entity object and a DTO. The repository returns a list of objects arrays when I do a simple example like: findById(). Is there a way to easily map the return type to be a custom DTO object rather than always return entity objects?
Example is below:
#Query("Select f.id, f.name from Food f where f.id = :id")
public List<Object[]> findById(#Param("id") String id);
My DTO object looks like:
FoodDto{
private String id;
private String name;
}
Right now I've only ever been able to get repositories to return a List< Object[] > type.
Try this.
#Query("Select new package.FoodDto(f.id, f.name) from Food f where f.id = :id")
public List<FoodDto> findById(#Param("id") String id);
Assuming class FoodDto is in package, if not you need to set the full package.
Also I assume the FoodDto have a constructor that match
public FoodDto(int id, String name){
//Variable assignation
}
I never tried in spring-jpa but that works in JPQL so I assume it will work XD

jpa convert scalar values from native sql to entity

Is there a way in JPA to convert random select clause values using createNativeQuery to a domain object. The domain object is not managed
I have the following sql query
select name, count(*) as cnt, sum(average_events)/count(*) as avg_events from (complex subquery)
I want to convert the values a,b,c into a domain object with three instance variables a, b and c. This domain object is not managed by JPA and hence does not have #Entity and no corresponding table.
Currently I am doing the following, which returns a list of objects.
Query query = objectManager.getEntityManager().createNativeQuery(sqlStr);
List resultList = query.getResultList();
Use this syntax:
SELECT new foo.MyCustomObject(a, b, c) FROM ...
Where MyCustomObject is any class with matching consdtructor:
public class MyCustomObject {
private final String name;
private final int cnt;
private final float avg;
public MyCustomObject(String name, int cnt, float avg) {
this.name = name;
this.cnt = cnt;
this.avg = avg;
}
//...getters
}
Scala bonus: equivalent class:
class MyCustomObject(name: String, cnt: Int, avg: Float)
//no, actually that's it
Just pass in the "resultClass" to the createNativeQuery call. Certainly works in DataNucleus JPA.