I've been trying to implement QBE (Query by Example) functionality using plain JPA2 spec, while using Hibernate 3.5.5 as JPA2 implementaion. The problem i am stumbled upon is the following:
I have implemented GenericDao<T> with the following methods:
Code:
public List<T> findByExample(T exampleInstance) {
TypedQuery<T> query = getQueryByExample(exampleInstance);
if (query == null)
return null;
else
return query.getResultList();
}
private TypedQuery<T> getQueryByExample(T exampleInstance) {
try {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<T> c = cb.createQuery(persistentClass);
Root<T> r = c.from(persistentClass);
Predicate p = cb.conjunction();
if (exampleInstance != null) {
Metamodel mm = entityManager.getMetamodel();
EntityType<T> et = mm.entity(persistentClass);
Set<Attribute<? super T, ?>> attrs = et.getAttributes();
for (Attribute<? super T, ?> a : attrs) {
String name = a.getName();
String javaName = a.getJavaMember().getName();
String getter = "get"
+ javaName.substring(0, 1).toUpperCase()
+ javaName.substring(1);
Method m;
m = persistentClass.getMethod(getter, (Class<?>[]) null);
if (m.invoke(exampleInstance, (Object[]) null) != null)
p = cb.and(p, cb.equal(r.get(name), m.invoke(
exampleInstance, (Object[]) null)));
}
}
c.select(r).where(p);
TypedQuery<T> query = entityManager.createQuery(c);
return query;
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
It works fine for Entities with simple properties, when examples of such entities are passed to this method, but if say, we have Entity1 which has field of type Entity2:
@OneToOne
@JoinColumn(name = "entity2_id", nullable = false)
private Entity2 entity2;
If we do the following :
Code:
Entity1 example=new Entity1();
Enitity2 example2= new Entity2();
example.setEntity2(example2);
new GenericDao<Entity1>.findByExample(example);
It fails with the following exception:
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute query
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1179)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1112)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:245)
at org.hibernate.ejb.criteria.CriteriaQueryCompiler$3.getResultList(CriteriaQueryCompiler.java:187)
.......................
at ................................
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at junit.framework.TestCase.runTest(TestCase.java:168)
at org.jmock.core.VerifyingTestCase.runBare(Unknown Source)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:124)
at junit.framework.TestSuite.runTest(TestSuite.java:232)
at junit.framework.TestSuite.run(TestSuite.java:227)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:79)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.hibernate.exception.SQLGrammarException: could not execute query
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:92)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.loader.Loader.doList(Loader.java:2297)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2172)
at org.hibernate.loader.Loader.list(Loader.java:2167)
at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:448)
at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:363)
at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1258)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:236)
... 26 more
Caused by: java.sql.SQLException: Wrong data type: java.lang.NumberFormatException: For input string: "[B@12cd8d4"
at org.hsqldb.jdbc.Util.throwError(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.setParameter(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.setBytes(Unknown Source)
at org.hibernate.type.AbstractBynaryType.set(AbstractBynaryType.java:66)
at org.hibernate.type.SerializableType.set(SerializableType.java:52)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:154)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:136)
at org.hibernate.param.NamedParameterSpecification.bind(NamedParameterSpecification.java:67)
at org.hibernate.loader.hql.QueryLoader.bindParameterValues(QueryLoader.java:567)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1612)
at org.hibernate.loader.Loader.doQuery(Loader.java:717)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:270)
at org.hibernate.loader.Loader.doList(Loader.java:2294)
... 34 more
The reason for this is that it takes the query constructed with CriteriaBuilder, and which looks something like:
select generatedAlias0 from ENTITY1 as generatedAlias0 where ( 1=1 ) and ( generatedAlias0.Entity2=:param0 )Transforms it to SQL:
select Entity1.field1, Entity1.field2, .... from Entity1_Table Entity1 where 1=1 and Entity1 .entity2_id=?
Which itself is incorrect, since it does not try to parametrize all the fields of Entity2 but ID only, afterwards
it tries to replace parameter with example2.toString() obviously failing hard.
Any ideas?