How can I use mysql null safe equality operator <=> in HQL? - mysql

I wanted to get records of Patient(POJO class) who's contact number is not null. So, I referred this post.
In the answer two ways are specified
SELECT *
FROM table
WHERE YourColumn IS NOT NULL;
SELECT *
FROM table
WHERE NOT (YourColumn <=> NULL);
From above I wrote below hql which runs successfully
from Patient p where p.contactNo is not null
But, for 2nd type of hql
from Patient p where not (p.contactNo <=> null)
throws Exception
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: >
How can I use mysql null safe equality operator <=> in HQL?

HQL is a different language than MySQL. MySQL operators are not necessarily available in HQL.
This being said, you can given Hibernate MySQL queries (provided your database is MySQL):
Query query = entityMangager.createNativeQuery("Some MySQL code");
List results = query.getResultList();
EntityManager is an interface from the Java Persistence API. Hibernate has a tutorial about using the JPA, but here are the main points:
In order to have an entity manager, you need META-INF/persistence.xml file in your classpath. Then, inside a Java EE container, you get an instance of this interface with the #PersistenceContext annotation:
#PersistenceContext(unitName = "persistenceUnit")
private EntityManager em;
Outside a Java EE container, you can get one with the Persistence class:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");
EntityManager em = factory.createEntityManager();
In both case, "persistenceUnit" must be the name of a persistence unit defined in your persistence.xml file.

Related

Is there any way to update SET type column using JPA query?

What I want to do is update field in table by JPA query.
I set my MySQL table with SET type.
create table staff (
id BIGINT,
...
roles SET('A', 'B', 'C') not null,
...
)
and class
public class Staff {
private Long id;
#Converter(someConverterHere.class)
private Set<Role> roles;
}
and I tried to update this field like the way below
#Modifying
#Query("update Staff s SET s.roles = :newRoles where s.id = :id")
Integer updateStaffRoles(#Param("id") Long id, #Param("newRoles") Set<Role> newRoles);
Then, When I gave this Set.of(Role.A, Role.B) as argument, it showed me the error like this,
Caused by: java.lang.IllegalArgumentException: Parameter value [A] did not match expected type [java.util.Set (n/a)]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54)
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27)
at org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90)
at org.hibernate.query.internal.QueryParameterBindingImpl.setBindValue(QueryParameterBindingImpl.java:55)
at org.hibernate.query.internal.QueryParameterBindingsImpl.expandListValuedParameters(QueryParameterBindingsImpl.java:636)
at org.hibernate.query.internal.AbstractProducedQuery.doExecuteUpdate(AbstractProducedQuery.java:1629)
at org.hibernate.query.internal.AbstractProducedQuery.executeUpdate(AbstractProducedQuery.java:1612)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$ModifyingExecution.doExecute(JpaQueryExecution.java:238)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor$QueryMethodInvoker.invoke(QueryExecutorMethodInterceptor.java:195)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:152)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 81 common frames omitted
It seems jpa query builder validates all single value in newRoles and compare its type with type of Staff.roles, which cannot be true (Role and Set)
Is there any way to solve this? I was not able to find any solution for this.
dependency
org.springframework.data:spring-data-jpa:2.3.2.RELEASE
=====================================================
Temporal solution:
I changed paramter type from Set to Set<Set> and it works. And now, This problem becomes problem when using QueryDsl. Using "set" of querydsl, this problem happens again :(
Querydsl
// newRoles : Set<Role>
// Error: IllegalArgumentException
query.set(QStaff.Staff.roles, newRoles));
// Of course, compile error
query.set(QStaff.Staff.roles, Set.of(newRoles)));
Custom types need to be mapped by a custom type in your JPA implementation. For Hibernate that is through the User Type interface. In order to use extended types through QueryDSL, you need to create custom expression types for it. Furthermore, for any of the operations you want to support (like set contains, equality), you will have to register custom functions. It can be done, but not using the regular API's. You're probably best off denormalizing your metamodel and using an #ElementCollection instead. However, if you insist on getting to work, here are some pointers:
hibernate-types is a widely popular project that implements many custom types for Hibernate. It doesn't implement SET, but you can probably use the implementation for arrays as basis.
hibernate-types-querydsl-apt is a project that extends metamodel generation of QueryDSL for some of the custom types. You should probably mimic anything in that repository for sets as well.

EclipseLink: "Updates are not allowed" on a native query INSERT INTO

since upgrading to EclipseLink 2.5 from EL 2.1 we get a PersistenceException on a previously running code.
The scenary is really simple: we have two identical tables, the only difference is that one is a "history" version of the other. Basically, when we are sure that a row won't change anymore, we move the row to the history table. This means that while the id is generated on the first table, it is "inherited" on the history table. Here are the entities:
#Entity
#Table(name = "DOCUMENT")
public class Document implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
// Some other fields
}
#Entity
#Table(name = "DOCUMENT_HISTORY")
public class DocumentHistory implements Serializable {
#Id
private long id;
// Some other fields
}
In order to move a row to the history table we use a native query (because some columns are not mapped as fields in the entity classes):
String query = "INSERT INTO DOCUMENT_HISTORY SELECT * FROM DOCUMENT t WHERE t.id=?1";
Query updateQuery = entityManager.createNativeQuery(query);
updateQuery.setParameter(1, document.getId());
updateQuery.executeUpdate();
When executing this query, sometimes this exception is thrown:
javax.persistence.PersistenceException: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5):
org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:868)
at org.eclipse.persistence.internal.jpa.QueryImpl.performPreQueryFlush(QueryImpl.java:963)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeUpdate(QueryImpl.java:296)
at org.myc.utility.jpa.user.DocumentManager.toDocumentHistory(DocumentManager.java:141)
at java.lang.Thread.run(Thread.java:662)
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
at org.eclipse.persistence.exceptions.ValidationException.primaryKeyUpdateDisallowed(ValidationException.java:2548)
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.writeFromObjectIntoRowWithChangeRecord(AbstractDirectMapping.java:1257)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildRowForUpdateWithChangeSet(ObjectBuilder.java:1768)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1030)
at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:899)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:798)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1793)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1775)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1726)
at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:267)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:192)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:138)
at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4196)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1587)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:452)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:863)
... 5 more
Did anyone have the same problem? Why do you think this doesn't happen on all rows but only some of them (with no apparent specific characteristics)?
Thanks in advance,
Luc
P.S.: Is it correct for EL to perform mapping/query validation on a native query?
As stated in JPA 2.0 specification, executeUpdate() executes an update or delete statement thus insert is not supported.
You can insert an entity into the underlying database by persisting and flushing it with use of EntityManager (within a transaction). An dummy example in your particular case might look as follows:
// transaction starts
Document doc = em.find(Document.class, id);
DocumentHistory docHistory = new DocumentHistory(doc);
em.persist(docHistory);
// transaction ends
NOTE: instead of a proprietary solution (which is fine) you may consider of using EclipseLink's auditing feature:
EclipseLink also support full history support, which allows a complete history of all changes made to the database to be tracked in a mirror history table.
Ok, I found the problem and it wasn't related at all with that query, so I still don't know why the exception is thrown there.
The problem was that instead of using entityManager.find() to get the newly inserted history row, we were creating a DocumentHistory instance (with same ID) which was not attached to a persistence context, then using that instance to perform other operations.
Hope it helps someone.

using mysql option SQL_CALC_FOUND_ROWS in JPA Criteria Query

I have a rather complex query here, where I also need to return the total of available result sets in addition to a limited result set.
There is the SQL_CALC_FOUND_ROWS option in MySQL, which allows to return that number in a subsequent query by using an interceptor. I have already implemented this in a similar project, by using a hibernate with a native query and an interceptor and it worked fine.
However in this case, there already is this complex JPA Criteria query and I would be more than happy if I could add that mysql option to the criteria query, but couldn't find a way to do this so far.
Below are some details about how the entityManager and Criteria is set up
#Stateless
class Dao{
#PersistenceContext(unitName = "persistenceName")
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Entity> find(String someParam) {
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> cq = cb.createQuery(Entity.class);
Root<Entity> root = cq.from(Entity.class);
// some dummy predicate here
cq.where(predicates);
TypedQuery<Entity> query = this.entityManager.createQuery(cq);
// limit query
query.setFirstResult(100);
query.setMaxResults(10);
return query.getResultList();
}
}
Any ideas how to add the SQL_CALC_FOUND_ROWS option?
This thing runs in Glassfish 3 with Hibernate 3.5
I had the same issue when using eclipselink, and this fixed it for me:
query.setHint("eclipselink.sql.hint", "SQL_CALC_FOUND_ROWS");
The eclipselink.sql.hint flag instructs the string to be added immediately after the first word in the query, in this case SELECT. There may be an equivalent for Hibernate.

How best to retrieve result of SELECT COUNT(*) from SQL query in Java/JDBC - Long? BigInteger?

I'm using Hibernate but doing a simple SQLQuery, so I think this boils down to a basic JDBC question. My production app runs on MySQL but my test cases use an in memory HSQLDB. I find that a SELECT COUNT operation returns BigInteger from MySQL but Long from HSQLDB.
MySQL 5.5.22
HSQLDB 2.2.5
The code I've come up with is:
SQLQuery tq = session.createSQLQuery(
"SELECT COUNT(*) AS count FROM calendar_month WHERE date = :date");
tq.setDate("date", eachDate);
Object countobj = tq.list().get(0);
int count = (countobj instanceof BigInteger) ?
((BigInteger)countobj).intValue() : ((Long)countobj).intValue();
This problem of the return type negates answers to other SO questions such as getting count(*) using createSQLQuery in hibernate? where the advice is to use setResultTransformer to map the return value into a bean. The bean must have a type of either BigInteger or Long, and fails if the type is not correct.
I'm reluctant to use a cast operator on the 'COUNT(*) AS count' portion of my SQL for fear of database interoperability. I realise I'm already using createSQLQuery so I'm already stepping outside the bounds of Hibernates attempts at database neutrality, but having had trouble before with the differences between MySQL and HSQLDB in terms of database constraints
Any advice?
I don't known a clear solution for this problem, but I will suggest you to use H2 database for your tests.
H2 database has a feature that you can connect using a compatibility mode to several different databases.
For example to use MySQL mode you connect to the database using this jdbc:h2:~/test;MODE=MySQL URL.
You can downcast to Number and then call the intValue() method. E.g.
SQLQuery tq = session.createSQLQuery("SELECT COUNT(*) AS count FROM calendar_month WHERE date = :date");
tq.setDate("date", eachDate);
Object countobj = tq.list().get(0);
int count = ((Number) countobj).intValue();
Two ideas:
You can get result value as String and then parse it to Long or BigInteger
Do not use COUNT(*) AS count FROM ..., better use something like COUNT(*) AS cnt ... but in your example code you do not use name of result column but it index, so you can use simply COUNT(*) FROM ...

how to use string left function in hql

I have a sql query like this
select column from table where path = left('INPUTSTRING', length(path));
and trying to accomplish it in hql like this,
return session.createQuery("from Table where Path = left(:input, length(Path))").
query.setParameter("input", inputPath).
.list();
and getting an error like this
Caused by: org.hibernate.hql.ast.QuerySyntaxException: unexpected token: left near line 1
how to get this done? What is the corresponding string function in hql? Is there a solution for this using criteria query apis?
Yes, left() is not supported by the MySQLDialect. See the list of HQL supported functions on API docs.
Now you have left with 2 options.
Use session.createSQLQuery() method.
Create Your own Dialect class by extending the MySQLDialect and register the function there. This is told at hibernate forum here explained well in a blog post here.
I'm not sure if HQL does this for you , but you can use IQuery/session.CreateSQLQuery() to use a raw SQL query to populate a mapped entity. I've never used it for substrings, but have used it for aggregate functions. Check chapter 13 of the NHibernate docs and see if that does it for you. You can check the query substitution available in Nhibernate - here