Dynamic Hibernate Query - java

I have a method that I use to return a dynamic query. This method is shown below
public Query getLastId(String sProvider)
{
String serviceProvider = sProvider.toLowerCase();
String query2 = "SELECT MAX(:serviceProvider.id) " +
" FROM :sProvider :serviceProvider ";
return em.createQuery(query2)
.setParameter("sProvider", sProvider)
.setParameter("serviceProvider", serviceProvider);
}
I want this method to return this
SELECT MAX(multichoice.id) FROM Multichoice multichoice
when I call the method like this
getLastId("Multichoice");
Please how do I write the query variable to return the answer?

To do this task you can use Criteria object model and projections to run your query over different types:
Take a look at this article (15.7. Projections, aggregation and grouping)
here is the code :
List results = session.createCriteria(class)
.setProjection( Projections.max("id"))
.list();
Then instead of a string you should send a class to your method.

Related

Stored procedure call with Spring JPA

I need to invoke a stored procedure using the JPA. The stored procedure operates on multiple tables and return some of the columns from these tables.
Tried with the #Procedure it doesn't seem to work, always the stored procedure is not found in this case.
Directly calling the procedure using native query was successful, but with this approach, I am need to map the result returned to List of an Object.
My implementation in the repository looks like below,
#Query(value = "EXECUTE dbs.multitable_Test :inputObj", nativeQuery = true)
List<sp> multitable_Test(#Param("inputObj")String inputObj);
The result returned from the stored procedure needs to be mapped to the sp class.
How can this be achieved while we have multiple tables response in the single result set?
Already tried with the attributeConvert from
this link, still getting the below exception.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type
Any help with this is appreciated.
Firstly, this is not really the use case for procedure. Procedure is meant to modify data on the database without any return value, then you could use:
#Procedure(procedureName = "procedure_name")
void procedure(); // notice void
You should rather use a function using create function syntax. Function can modify data and return the result.
Secondly if you want to map it to some class, I see two solutions (using EntityManager):
Using ResultTransformer:
entityManager.createNativeQuery(
"select * from function_name(:parameter)"
)
.setParameter("parameter", parameter)
.unwrap(org.hibernate.query.NativeQuery.class)
.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return new Sp(tuple[0]);
}
#Override
public List transformList(List collection) {
return collection;
}
})
.getResultList();
Note that ResultTransformer is deprecated, but is so powerful, it will not be removed until there is a sensible replacement, see the note from hibernate developer.
Using ResultSetMapping. Place the proper annotation over an entity:
#SqlResultSetMapping(
name = "sp_mapping",
classes = #ConstructorResult(
targetClass = Sp.class,
columns = {
#ColumnResult(name = "attribute", type = Long.class)
})
)
And invoke the function using the mapping as parameter:
entityManager.createNativeQuery(
"select * " +
"from function_name(:parameter);",
"sp_mapping"
)
.setParameter("parameter", parameter)
.getResultList();

Eclipselink jpa - sql query result mapping to java object

I am trying to return a typed list from getResultList() but I am having issues with mapping my sql result list to a typed list. It keeps returning a list of generic objects. This is my current code:
EntityManager em = this.emPool.createEntityManager();
TypedQuery<Runtime> query = Runner.getRuntime(em);
List<Runtime> runtimeList = query.getResultList();
Also, in Runner class I have this:
public static TypedQuery<Runtime> getRuntime(EntityManager em) {
return em.createNamedQuery(COUNT_RUNTIMES_SQL_EXPRESSION, Runtime.class);
}
And here is the query:
SELECT u.runner_id as runnerId, COUNT(u.times) FROM RUNNER u"
+ " WHERE u.age = :60 GROUP by u.runner_id
Any tips will be greatly appreciated.
NOTE: the query I am running is a report query -> a simple group by and count
You need to use constructor expression in select clause. For example,
SELECT new com.example.Runtime(u.runner_id as runnerId, COUNT(u.times)) FROM RUNNER u"
+ " WHERE u.age = :60 GROUP by u.runner_id
Define the corresponding constructor in the class Runtime.
If using Criteria API, CriteriaBuilder.construt(...) should be used. Tested with Cmobilecom JPA for the criteria API.
Disclaimer: I am a developer of Cmobilecom JPA (for java and android)

Sql query call fails if no record is found

I am trying to fetch a string from sql db. I have the following query:
select DISTINCT HARDWARE.NAME
from HARDWARE INNER JOIN
HARDWARE_LINKING
on HARDWARE.ID = HARDWARE_LINKING.ID
where HARDWARE_LINKING.EXTERNALID='5528752'.
Now when there is no record against any id the function call fails.
here is my function code that I am calling to fetch data:
public String search(String externalId) {
String SQL = "select DISTINCT HARDWARE.NAME from HARDWARE INNER JOIN HARDWARE_LINKING on HARDWARE.ID = HARDWARE_LINKING.ID where HARDWARE_LINKING.EXTERNALID=?";
Object[] input = new Object[] {externalId};
String name = jdbcTemplate.queryForObject(SQL,input, String.class);
return name;
}
Is there any way I can return a default value? and sometimes there are multiple values return but I want only single string value to be returned, Is that possible? Thankyou in advance
One method is to put the default into the query itself. Use an aggregation function and COALESCE():
select coalesce(h.NAME, '<default value>') as NAME
from HARDWARE h INNER JOIN
HARDWARE_LINKING hl
on h.ID = hl.ID
where hl.EXTERNALID = '5528752';
An aggregation query with no GROUP BY always returns one row, so this would seem to be more what you are looking for.
Also note that I put table aliases into the query. These make the query easier to write and to read.
You should be able to check name for null or isEmpty and then decide if you should return the name from the query or a default value.
if(name != null && !name.isEmpty()){
return name;
} else {
return "default";
}

Spring data JPA and parameters that can be null

My understanding is, that with Spring data JPA I cannot have a query method to fetch all rows where a column equals a given non-null method parameter and use the same method to fetch all rows where this column is NULL when the method parameter is null.
Is that correct?
So I have to distinguish this in my JAVA code and I must use a separate query method explicitly asking for null values, like in the example below?
// Query methods
List<Something> findByParameter(Parameter parameter);
List<Something> findByParameterIsNull();
...
List<Something> result = new ArrayList<>();
if (parameter == null)
result = findByParameterIsNull();
else
result = findByParameter(parameter);
That's bad, if I have 4 parameters which could be null and would have to code 16 different query methods.
You are right.
A request has been made to support better handling of null parameters.
https://jira.spring.io/browse/DATAJPA-121
In your case, i would advise you to write your repository implementation and to use a custom CriteriaQuery to handle your case.
Also you can use the #Query annotation with the is null syntax :
#Query("[...] where :parameter is null"
public List<Something> getSomethingWithNullParameter();
EDIT
Since Spring data jpa 2.0, spring now supports #Nullable annotation. This can be helpful to handle null parameters passed.
From the documentation :
#Nullable – to be used on a parameter or return value that can be null.
i found something...if u put the parameter in the jpa method like this
#Param("value") String value,
then it can be null and in the query you will have this condition:
(table.value = :value OR :value IS NULL)
if the value is null it will automatically return true and if is not null, it will search that value in the table.
It seems Query by Example might be what you need.
Query by Example is a new feature in Spring Data (since version Hopper, out April 2016), which allows one to create simple dynamic queries with a code like this
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
personRepository.count(example);
personRepository.findOne(example);
personRepository.findAll(example);
Methods count/findOne/findAll that take an instance of org.springframework.data.domain.Example as a parameter (and some of them also take sorting/pagination parameters) are coming from org.springframework.data.repository.query.QueryByExampleExecutor<T> interface, which is extended by org.springframework.data.jpa.repository.JpaRepository<T, ID extends Serializable> interface.
In short, all JpaRepository instances now have these methods.
Today as of Jun 2018, by looking at https://jira.spring.io/browse/DATAJPA-121, the query will automatically form is null if your parameter is null.
I did that in my project, it is true:
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.7.RELEASE'
--
public interface AccountDao extends CrudRepository<T, ID> {
//this can accept null and it will become isNull
public List<MyAccount> findByEmail(String email);
}
if parameter is null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email is null
if parameter is not null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email=?
11:02:41.623 [qtp1507181879-72] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [testing#hotmail.com]
Then it comes to an interesting question, some developers want better control to ignore the parameter in query if it is null, this is still being under investigating in https://jira.spring.io/browse/DATAJPA-209.
In my case membershipNumber is nullable, and I have handled it this way. This will handle all the cases where table.membershipNumber is null too.
#Query(value = "SELECT pr FROM ABCTable pr " +
"WHERE LOWER(pr.xyz) = LOWER(:xyz) " +
"and LOWER(pr.subscriptionReference) = LOWER(:subscriptionReference) " +
"and pr.billId = :billId " +
"and ((pr.membershipNumber = :membershipId) or (pr.membershipNumber = null and :membershipId = null))")
List<PaymentRequest> getSomething (#Param("xyz") String xyz,
#Param("subscriptionReference") String subscriptionReference,
#Param("billId") Integer billId,
#Param("membershipId") String membershipNumber);
While this has been answered and the accepted answer is relevant to the current question but there is another way to handle your null parameters in a JpaRespository. Posting this here as this can be leveraged when someone wants to query by ignoring fields when null and have dynamic query built.
The below code sample should demonstrate the same
public class User{
private String firstName;
private String lastName;
}
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long>{
public Page<AppUser> findAll(Specification<AppUser> user,Pageable page);
public default Page<AppUser> findAll(User user,Pageable page){
return findAll(search(user),page);
}
static Specification<User> search(User entity) {
return (root, cq, cb) -> {
//To ensure we start with a predicate
Predicate predicate = cb.isTrue(cb.literal(true));
if(entity.getFirstName() != null && !entity.getFirstName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("firstName")), "%"+entity.getFirstName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
if(entity.getLastName() != null && !entity.getLastName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("lastName")), "%"+entity.getLastName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
return predicate;
}
}
}
I was able to apply IS NULL appropriately in case of null input using below workaround.
#Query("SELECT c FROM ConfigRLLOAContent c WHERE ((:suffixId IS NOT NULL AND c.suffixId = :suffixId) OR (:suffixId IS NULL AND c.suffixId IS NULL))")
Optional<ConfigRLLOAContent> findByRatableUnitId(#Param("suffixId") String suffixId);
Above approach will apply filters only when suffixId is non-null,
else, IS NULL filter will be applied.
There's also an issue raised on github, to which introduction of #NullMeans is proposed here.
I had the same issue with similar task - one parameter in the query was optional, so to get rid of this error, I managed to use the following query with 2 casts:
#Query(value = "select distinct name from table "
+ "where coalesce(cast(table.field_optional as text) = cast(?1 as text), true) "
+ "and lower(table.non_optional_field) like ?2 "
+ "limit ?3", nativeQuery = true)
List<String> method(String optionalParam, String param, int limit);
This coalesce part would transform into simple 'true' if optionalParam is null

can hibernate hsql automatically map the query result to a class?

I wrote a hsql:
String queryString = "select t1.a, t1.b, t2.c from table1 t1, table2 t2 where t1.id = t2.id";
and then I have a class:
class test{
String a;
String b;
String c
....//other getter and setter
}
I tried:
List = getHibernateTemplate().find(queryString);
this doesn't work, when I use test object in jsp page, it will throw out exception.
I have to manually create a test object:
List<Object[]> list = getHibernateTemplate().find(queryString);
test.seta(list.get(0)[0]);
is it possible for hibernate to automatically map the class for me in hsql ?
If you have a mapping for both table1 and table2 (see Prashant question above) you can do something like:
String queryString = "select t1 from table1 t1
inner join t1.table2 t2";
After you run the query you should have a list of t1 objects.
for(Table1 t1:listOfTable1Objects) {
t1.getA(); //for example or whatever you want to do with your object.
}
The Problem is that you do not write a HQL query. You just write a normal SQL query. In HQL, because the hibernate make the mapping from table to class, you cannot make a projection. So, if you write something like
String query = "FROM Class1 WHERE ome_condition;
without the SELECT clause, the Hibernate will be able to convert the result in the proper object.
You can see more about this here: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html
If you dont have a mapping, you may create a auxiliary class for this. Say ResultClass. Then you add #NamedNativeQuery and #SqlResultSetMapping annotations to the class:
#NamedNativeQuery(name="queryHehehe", query="select t1.field1 f1, t2.field2 f2 from table1 t1, table2 t2", resultSetMapping="mappingHehehe")
#SqlResultSetMapping(name="mappingHehehe", entities={
#EntityResult(entityClass=my.clazz.AuxiliaryClass.class, fields = {
#FieldResult(name="id", column="f1"),
#FieldResult(name="other_property", column="f2")
}),
})
public class AuxiliaryClass {
public Long id;
public String other_property;
}
I have never used this, but can work. Good luck.
If you need a query to return values from multiple tables and create an object of an unmapped class, then you need to either do what you're doing here, or use a ResultTransformer.
In order to do this with HibernateTemplate, you'll need to change the way you use the template, possibly using execute(HibernateCallback action), as you'll need to convert the sql query to a Criteria as described in Hibernate Reference Native SQL Chapter.
If you do want to try this, you'll probably want to use an AliasToBeanResultTransformer or AliasToBeanConstructorResultTransformer rather than writing your own transformer.

Categories