when use namedQuery in entity class get error
#NamedQuery(name = "Classification.search", query = "SELECT c FROM Classification c WHERE c.id LIKE :value")
Method for call namedQuery
public List<Classification> search(String value) {
Query query = getEntityManager().createNamedQuery("Classification.search", Classification.getClass()).setParameter("value", "%"+value+"%");
query.setMaxResults(10);
return query.getResultList();
}
java.lang.IllegalArgumentException: You have attempted to set a value of type class java.lang.String for parameter value with expected type of class java.lang.Integer from query string SELECT c FROM Classification c WHERE c.id LIKE :value.
but when use this method is work without Error.
public List<Classification> findLimited(String _clasif, int maxResult) {
String querySt = "SELECT c FROM Classification c WHERE c.id LIKE '%" + _clasif + "%'";
Query query = em.createQuery(querySt);
query.setMaxResults(maxResult);
List<Classification> classif;
classif = query.getResultList();
if (classif != null) {
return classif;
}
return new ArrayList<>();
}
i use eclipselink 2.6 with JPA
As per the BNF for JPQL, "LIKE" is for use with String values only. Use with non-String values would only be a vendor extension, and hence vendor-dependent. Whether it is part of a named query or criteria or string-based JPQL is irrelevant.
like_expression ::= string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
i solve that by this code.
public List<Classification> search(String value) {
Query query = getEntityManager().createQuery(getNamedQueryCode(entityClass, "Classification.search").replace(":value", value));
query.setMaxResults(10);
return query.getResultList();
}
this method get namedQuery string from entity class by nameKey and class.
private String getNamedQueryCode(Class<? extends Object> clazz, String namedQueryKey) {
NamedQueries namedQueriesAnnotation = clazz.getAnnotation(NamedQueries.class);
NamedQuery[] namedQueryAnnotations = namedQueriesAnnotation.value();
String code = null;
for (NamedQuery namedQuery : namedQueryAnnotations) {
if (namedQuery.name().equals(namedQueryKey)) {
code = namedQuery.query();
break;
}
}
if (code == null) {
if (clazz.getSuperclass().getAnnotation(MappedSuperclass.class) != null) {
code = getNamedQueryCode(clazz.getSuperclass(), namedQueryKey);
}
}
//if not found
return code;
}
Related
In some E2E tests I'm faced with a problem. Let's say, I have the following JPQL query:
Query query = entityManager.createQuery(
" select d from Document d left join d.audit da " +
" where " +
" (cast(:startDate as java.time.ZonedDateTime)) is null " +
" or truncate_for_minutes(da.dateCreate, 'UTC') >= " +
" truncate_for_minutes(:startDate, 'UTC')")
.setParameter("startDate", ZonedDateTime.now());
In the query string I use named parameter startDate which can be null. The query above works. But if I pass null, the following exception is thrown:
Caused by: org.postgresql.util.PSQLException:
ERROR: cannot cast type bytea to timestamp without time zone
Without type casting the following exception is thrown:
Caused by: org.postgresql.util.PSQLException:
ERROR: could not determine data type of parameter $1
Without check for null the following exception is thrown:
Caused by: org.postgresql.util.PSQLException:
ERROR: function pg_catalog.timezone(unknown, bytea) does not exist
No function matches the given name and argument types.
You might need to add explicit type casts.
I use this query in Spring Data repository by using #Query string. Function truncate_for_minute(...) - is just a small customization for the PostgreSQL function date_trunc(...).
I know that I can implement custom repository and build query string dynamically, but why I can't check for null ZonedDateTime in JPQL string? Maybe there is a way to do it?
My environment:
Java 11
PostgreSQL 11.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 8.3.0) 8.3.0, 64-bit
Hibernate 5.3.7 Final
Another solution is to use Criteria API and build the query dynamically.
#Repository
#RequiredArgsConstructor
public class CustomDocumentRepositoryImpl implements CustomDocumentRegistry {
private final EntityManager em;
#Override
public Page<Document> findDocumentsForExpertByFilter(SearchDocumentCriteria criteria,
Pageable pageable) {
final String AUDIT_TABLE = "...";
final String USER_TABLE = "...";
final String ID_FIELD = "id";
final String FIRST_NAME_FIELD = "...";
final String LAST_NAME_FIELD = "...";
final String MIDDLE_NAME_FIELD = "...";
final String WORKSTATION_FIELD = "...";
final String DATE_CREATE_FIELD = "...";
final String LIKE_MASK = "%%%s%%";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Document> query = cb.createQuery(Document.class);
Root<Document> root = query.from(Document.class);
Path<ZonedDateTime> dateCreatePath =
root.get(AUDIT_TABLE).get(DATE_CREATE_FIELD);
Path<String> lastNamePath =
root.get(AUDIT_TABLE).get(USER_TABLE).get(LAST_NAME_FIELD);
Path<String> firstNamePath =
root.get(AUDIT_TABLE).get(USER_TABLE).get(FIRST_NAME_FIELD);
Path<String> middleNamePath =
root.get(AUDIT_TABLE).get(USER_TABLE).get(MIDDLE_NAME_FIELD);
root.fetch(AUDIT_TABLE, JoinType.LEFT)
.fetch(USER_TABLE, JoinType.LEFT);
Predicate documentIdsPredicate;
List<Long> documentIds = criteria.getIds();
if (isNull(documentIds) || documentIds.isEmpty()) {
documentIdsPredicate = cb.isNotNull(root.get(ID_FIELD));
} else {
documentIdsPredicate = root.get(ID_FIELD).in(criteria.getIds());
}
Predicate startDatePredicate;
ZonedDateTime startDate = criteria.getStartDate();
if (isNull(startDate)) {
startDatePredicate = cb.isNotNull(dateCreatePath);
} else {
startDatePredicate = cb.greaterThanOrEqualTo(dateCreatePath, startDate);
}
Predicate endDatePredicate;
ZonedDateTime endDate = criteria.getEndDate();
if (isNull(endDate)) {
endDatePredicate = cb.isNotNull(dateCreatePath);
} else {
endDatePredicate = cb.lessThanOrEqualTo(dateCreatePath, endDate);
}
Predicate lastNamePredicate = cb.like(cb.upper(lastNamePath),
format(LIKE_MASK, criteria.getLastName().toUpperCase()));
Predicate firstNamePredicate = cb.like(cb.upper(firstNamePath),
format(LIKE_MASK, criteria.getFirstName().toUpperCase()));
Predicate middleNamePredicate = cb.like(cb.upper(middleNamePath),
format(LIKE_MASK, criteria.getMiddleName().toUpperCase()));
Predicate fullNamePredicate =
cb.and(lastNamePredicate, firstNamePredicate, middleNamePredicate);
Predicate compositePredicate = cb.and(
fullNamePredicate,
documentIdsPredicate,
startDatePredicate,
endDatePredicate
);
query.where(compositePredicate);
Query limitedQuery = em.createQuery(query
.orderBy(cb.desc(root.get(AUDIT_TABLE).get(DATE_CREATE_FIELD))))
.setFirstResult(nonNull(criteria.getSize()) ?
criteria.getPage() * criteria.getSize() :
criteria.getPage());
if (nonNull(criteria.getSize())) {
limitedQuery.setMaxResults(criteria.getSize());
}
List<Document> documents = limitedQuery.getResultList();
return new PageImpl<>(documents, pageable, criteria.getSize());
}
}
Generates the following SQL:
select
document0_.id as id1_3_0_,
user1_.id as id1_13_1_,
document0_.created_dt as created_2_3_0_,
document0_.updated_dt as updated_3_3_0_,
document0_.created_user_id as created_6_3_0_,
document0_.updated_user_id as updated_7_3_0_,
document0_.name as name4_3_0_,
user1_.first_name as first_na2_13_1_,
user1_.last_name as last_nam3_13_1_,
user1_.middle_name as middle_n5_13_1_
from
some_scheme.document document0_
left outer join
some_scheme.s_user user1_
on document0_.created_user_id=user1_.id cross
join
some_scheme.s_user user2_
where
document0_.created_user_id=user2_.id
and (
upper(user2_.last_name) like '%LASTNAME%'
)
and (
upper(user2_.first_name) like '%FIRSTNAME%'
)
and (
upper(user2_.middle_name) like '%MIDDLENAME%'
)
and (
document0_.id in (
2 , 1
)
)
and document0_.created_dt>=...
and document0_.created_dt<=...
order by
document0_.created_dt desc limit 10;
In my case, the problem was as follows. I registered customization of SQL function date_trunc:
public class CustomSqlFunction implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"truncate_for_minutes",
new SQLFunctionTemplate(
StandardBasicTypes.TIMESTAMP,
"date_trunc('minute', (?1 AT TIME ZONE ?2))"
)
);
}
}
If change the StandardBasicTypes.TIMESTAMP to ZonedDateTime.INSTANCE and pass ZonedDateTime in one parameter, then the comparison in the JPQL query does not cause errors:
public class CustomSqlFunction implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"truncate_for_minutes",
new SQLFunctionTemplate(
ZonedDateTimeType.INSTANCE,
"date_trunc('minute', ?1)"
)
);
}
}
Is it possible to read out the columnnames?
My Application: Java, SpringBoot, JPA, Hibernate
My code:
Query query = entityManager.createNativeQuery(
"select name as Name, concat('Time:',time, ' AND:', event) as FOO
from XYZ
where NAME = 'BLA'");
List<Object[]> resultList = query.getResultList();
I need the columnnames (Name and FOO) in the first Object[] of the ResultSet or
somewhere else...
My unsightly solution:
private String getColumnNames(String sql) {
StringBuilder sb = new StringBuilder();
String part1 = sql.split("from")[0];
part1 = part1.replaceAll("\\(.*?\\)", "");
String[] columns = part1.split(",");
for (String column : columns) {
String alias = column;
if (column.toUpperCase().contains(" AS ")) {
alias = column.toUpperCase().split(" AS ")[1];
}
alias = alias.replace("'","");
sb.append(alias+",");
}
String columnNames = sb.toString();
if (columnNames.length() != 0) {
columnNames = columnNames.substring(0, columnNames.length()-1);
}
return columnNames;
}
Thanks
You should use other overloaded versions of createNativeQuery() :
public Query createNativeQuery(String sqlString, Class resultClass);
or
public Query createNativeQuery(String sqlString, String resultSetMapping);
In this way, you could map the result of the query to a class that contains name and foo fields.
For example with the first way :
Query query = entityManager.createNativeQuery(
"select name as Name, concat('Time:',time, ' AND:', event) as FOO
from XYZ
where NAME = 'BLA'", Xyz.class);
List<Xyz> resultList = query.getResultList();
Note that query.getResultList() will return a raw type.
What you should do here is to:
First create an entity with Name and FOO attributes, so you can map it as a result of query.getResultList().
Then map it with a List<YourEntity> instead of List<Object[].
And Last thing is to define a mapper for the request so it can map those results to the desired entity, if the query uses different attributes names other than the ones in the entity, using #SqlResultSetMapping.
You can check this Hibernate Tips: How to map native query results to entities tutorial for further details.
Edit:
If you can't create a new Entity to map the results, you can map those results in a HashMap in each element of the Object[] like this:
Map results = new HashMap();
results.put("Name", obj[0]);
results.put("Foo", obj[1]);
Yes it is possible. Lets assume your entity name is XYZ and you Repository would be XYZRepository, Lets create a DTO which only contains name and eventDetais since we want to extract only these 2 details from table.
public class XyzDTO {
String name;
String eventDetails;
public XyzDTO() {
}
public XyzDTO(String name, String eventDetails) {
this.name = name;
this.eventDetails =eventDetails;
}
}
This code would be written in XYZRepository.
#Query(nativeQuery = true)
List<Details> getList();
This code would be written in XYZ entity.
#SqlResultSetMapping(name = "getList",
classes = #ConstructorResult(targetClass = XyzDTO.class,
columns = {#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "eventDetails", type = String.class)}))
#NamedNativeQuery(name = "XYZ.getList",
query = "select name as name, concat('Time:',time, ' AND:', event) as eventDetails from XYZ where name = 'BL")
public class XYZ implements Serializable {
}
Code:
public void getDetails() {
try {
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
String hql = "select c.mobile, c.password FROM CrbtSubMasterDemo c where rownum<20";
Query query = session.createQuery(hql);
List<CrbtSubMasterDemo> itr = query.list();
session.getTransaction().commit();
for (CrbtSubMasterDemo pojo : itr) {//excepion line
System.out.println("[" + pojo.getMobile() + "]");
}
} catch (Exception e) {
e.printStackTrace();
}
}
CrbtSubMasterDemo is pojo mapped with the db.
When I try to run it, it gives following Exception:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.telemune.demoPojo.CrbtSubMasterDemo
at com.telemune.demoHibernate.QueryTester.getDetails(QueryTester.java:57)
at com.telemune.demoHibernate.QueryTester.main(QueryTester.java:23)
The question is query.list() is returning the list of objects of pojo class. Then why is this Exception. I am new to Hibernate, sorry if its a silly question.
When you write this:
String hql = "select c.mobile, c.password FROM CrbtSubMasterDemo c where rownum<20";
Your result set is not a List of CrbtSubMasterDemo
Try to write:
String hql = "select FROM CrbtSubMasterDemo c where rownum<20";
Another way is define a new constructor of CrbtSubMasterDemo where you pass only two fields c.mobile, c.password
so your query becomes:
String hql = "select new " + CrbtSubMasterDemo.class.getName() + "(c.mobile, c.password) FROM CrbtSubMasterDemo c where rownum<20";
If you follow this solution, remeber to add a default constructor too (without parameters), so in your pojo you have:
public CrbtSubMasterDemo(String mobile, String password) {
this.mobile = mobile;
this.password = password
}
and
public CrbtSubMasterDemo() {
}
String hql = "select c.mobile, c.password FROM CrbtSubMasterDemo c where rownum<20";
Query query = session.createQuery(hql);
Result of this will be List<Object[]>
List<Object[]> itr = query.list();
for (Object[] row : itr) {
System.out.println(String.format("mobile:%s, password:%s", row[0], row[1]));
}
if mobile and password are strings, of course. You can use a transformer to transform results directly to CrbtSubMasterDemo.
Hibernate 3.2: Transformers for HQL and SQL
FluentHibernateResultTransformer
Sir, Many times user faces this kinda requirements . Hibernate has ResultTransformer to convert a hql/sql in Object.
public CrbtSubMasterDemo{
private Stirng mobile;
private String password;
public CrbtSubMasterDemo(){
--------------
}
#####after setting the transation set whichever columns you are selecting should be given as name of property of your object
String hql = "select c.mobile as mobile, c.password as password FROM CrbtSubMasterDemo c where rownum<20";
Query query = session.createQuery(hql);
List<CrbtSubMasterDemo> itr = query.setResultTransformer(Transformers.aliasToBean(CrbtSubMasterDemo.class) ).list();
##No need to commit the transaction.
}
It will convert you query into the CrbtSubMasterDemo
Do not directly cast the result of "query.list();" to List of CrbtSubMasterDemo. As query.list() return object list. Iterate over the object list received and cast one by one to put in list List of CrbtSubMasterDemo
I have an SQL query that I would like to convert into a criteria (Hibernate 3.3.2) to use it into my persistancy layer. That's the query :
SELECT TYPE_CHANTIER.ID,
TYPE_CHANTIER.CODE,
TYPE_CHANTIER.LIBELLE
FROM TYPE_CHANTIER
INNER JOIN CATEGORIE_CHANTIER
ON TYPE_CHANTIER.ID = CATEGORIE_CHANTIER.TYPE
INNER JOIN CHANTIER
ON CATEGORIE_CHANTIER.ID = CHANTIER.CATEGORIE
WHERE CHANTIER.ID = 26869423;
I already try a lot a combinations, but there is no result that would be good for me ... This is my code :
public TypeChantier recupererTypeChantier(Integer idChantier) {
TypeChantier retour = null;
if (idChantier != null) {
Criteria criteria = getSession().createCriteria(TypeChantier.class);
// requete
criteria.createAlias("categorieChantiers", "categorieChantiers", JoinType.INNER_JOIN);
criteria.createAlias("categorieChantiers.chantiers", "chantiers", JoinType.INNER_JOIN);
// requete
criteria.add(Restrictions.eq("chantiers.id", idChantier));
// resultat
retour = (TypeChantier) criteria.uniqueResult();
}
return retour;
}
this code return a java null pointer exception
I am trying to make "filter" search for all questions in my database. Now I get a exception telling me that I can't compare enum values with string. Is it because I don't use the fully qualified package name of wher the enum type is declared? If so, is it better ways than hard-coding the package name?
Exception Description: Error compiling the query [SELECT q FROM
Question q WHERE q.status = 'APPROVED'], line 1, column 40: invalid
enum equal expression, cannot compare enum value of type
[app.utility.Status} with a non enum value of type
[java.lang.String].
public List<Question> all(Status status, ViewOption viewOption) {
String jpql = "SELECT q FROM Question q ";
boolean isWhereClauseAdded = false;
if (status != Status.ALL) {
if (!isWhereClauseAdded) {
jpql += "WHERE ";
}
jpql += "q.status = '" + status + "'";
}
if (viewOption != ViewOption.ALL) {
if (!isWhereClauseAdded) {
jpql += "WHERE ";
}
// Check if 'AND' operator is needed.
if (status != Status.ALL) {
jpql += " AND ";
}
switch (viewOption) {
case ONLY_IMAGES:
jpql += "q.image != ''";
break;
case NO_IMAGES:
jpql += "q.image = '' ";
break;
}
}
TypedQuery<Question> query = entityManager.createQuery(jpql,
Question.class);
return query.getResultList();
}
The right thing to do would be to use a query parameter:
String jpql = "select ... where q.status = :status";
Query query = em.createQuery(jpql).setParameter("status", status);
Rather than creating your query dynamically be concatenating query parts, you should also use the Criteria API, which has been designed with this goal in mind.
Can you try changing:
jpql += "q.status = '" + status + "'";
To:
jpql += "q.status = app.utility.Status." + status;