I'm writing a dynamic query using hibernates criteria language. I'm stunned as I'm unable to find any information regarding restricting the root entity dynamically (i.e. without specifying the id property).
I have an interface IEntity. There are several entities implementing IEntity. Some of them have an ID-property id while others have another ID-property (shadowId).
I want to write a single method covering both cases. Here is what I got so far:
#Override
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does not work:
criteria.add(Property.forName("this").eq(ent));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
I would like to avoid to have a if statement like
if (ent instanceof TypeWithPropertyId){
criteria.add(Property.forName("id").eq(ent));
} else {
criteria.add(Property.forName("shadowId").eq(ent));
}
Edit
Note: HibernateUtils.deproxy(Class<?> clazz) removes any Proxy and returns the original class.
As of now, I didn't find anything in the criteria api which could help me. But I found IdentifierEqExpression by accident. Sadly IdentifierEqExpression still needs the ID Value and can not extract it itself. So for the moment I'm using a custom extension:
public class ThisEqualsExpression extends IdentifierEqExpression {
public ThisEqualsExpression(final Object value, final SessionFactory sf) {
super(sf.getClassMetadata(deproxy(value.getClass())).getIdentifier(value, null));
}
}
I would prefer not using this hack-ish approach, but I still like this better than an if-statement:
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does work:
criteria.add(new ThisEqualsExpression(ent, currentSession().getSessionFactory()));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
Edit 2:
As requested some clarification:
In pure SQL, i have something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
But I want something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
WHERE foo.id=<some ID> --This is the important part
Back to Java:
Foos ID-Property depends on its type. It may be id (which I used in the SQL example above), but it could also be some other property. I was wondering if there was a way to restrict a criterias root entity without knowing the propertys name.
Related
I have to access the same table for multiple references from a "root" table. In order to do so, I'm creating aliases for these tables:
protected final Table<XyzRecord> foo = Tables.XYZ.as("foo", <foo-alias-function>);
protected final Table<XyzRecord> bar = Tables.XYZ.as("bar", <bar-alias-function>);
bar-alias-function would be declared as follows_
protected final Function<Field<?>, String> fooFieldAliasFunction = f -> "foo_" + f.getName();
Now since I'd like to benefit from type safe queries, I need to re-use the same alias-function in my queries to access the fields:
jooq.select()
.from (root)
.leftJoin(foo).on(
checklistTarget.field(fooFieldAliasFunction.apply(Tables.XYZ.ID), Tables.XYZ.ID.getType())
.eq(root.FOO_ID)
)
.leftJoin(bar).on(
checklistTarget.field(barFieldAliasFunction.apply(Tables.XYZ.ID), Tables.XYZ.ID.getType())
.eq(root.BAR_ID)
)
...
;
This seems awefully clumsy (a lot of code) and not terribly efficient (since the aliased field names are probably stored with the aliased table).
I assumed there would be a method on the alias that would give me the aliased field directly (e.g. foo.getField(Tables.XYZ.ID), but that doesn't seem to be the case.
Of course the problem is amplified if I want to select specific fields...
Am I missing something? What's the recommended way of doing this?
Thank you!
I assumed there would be a method on the alias that would give me the aliased field directly (e.g. foo.getField(Tables.XYZ.ID), but that doesn't seem to be the case.
This kind of API would be useful indeed, although the existing Table.field(Field) method shouldn't be retrofitted to assume this behaviour. A new method might be introduced. On the other hand, you could write a simple utility:
<T, R extends Record> Field<T> field(Table<R> table, TableField<R, T> field) {
if (table == foo)
return foo.field(fooFieldAliasFunction.apply(field), field.getType());
else if (table == bar)
return foo.field(barFieldAliasFunction.apply(field), field.getType());
else
throw IllegalArgumentException();
}
And then call it like this:
jooq.select()
.from (root)
.leftJoin(foo).on(field(foo, XYZ.ID).eq(root.FOO_ID))
.leftJoin(bar).on(field(bar, XYZ.ID).eq(root.BAR_ID))
...
;
I have many different Kinds in my app, they are unrelated in the Datastore, but they share a common Java base class which helps me process them generically. (By generically I mean without regard to their kind, not in the Java 'generics' sense.)
Now I want to perform some tests on one entity from each kind, and I can't figure out how to do it.
I want to do something like this:
Class<? extends MyBaseUnit> cl = getNextKind();
MyBaseUnit bu = (MyBaseUnit) ofy().load().type( cl ).filter( ?? ).first().now();
I don't think there is any such thing as a null filter, and if I just remove the filter() call then first() returns a Ref and I can't seem to do much with that.
I guess I could use a filter of ("id >", 0) for all the kinds with a long id, but what would a similar meaningless filter be for the ones with a string name?
Or maybe there is a better way of doing this? My ideal would be to retrieve a different entity every time I run the test.
In the end I did it the ugly way that I contemplated at the end of my question:
for (KindInfo ki: kinds) {
BaseUnit bu = null;
List<? extends BaseUnit> lbu = null;
if (ki.usesLongKey()) {
lbu = ofy().load().type( cl ).filter( "id !=", 7).limit(1).list();
} else {
lbu = ofy().load().type( cl ).filter( "kn !=", "barf35" ).limit(1).list();
}
if ((null == lbu) || (0 == lbu.size())) {
Log.i( "No entities for type=" + cl.getName() );
} else {
bu = (BaseUnit) lbu.get(0);
runTestsOnSampleEntity( bu );
}
}
The filters are just made up values ("kn" is the attribute name used by all of my Kinds that use a string key name).
I initially tried to use a filter of ("id !=", 0") since 0 is not a valid id, but this caused a ""java.lang.IllegalArgumentException".
If my list is empty, I get the following error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')'
Below is my hibernate related method:
#Override
public List<SomeThing> findByIds(List<Integer> someIds) {
return sessionFactory.getCurrentSession().createCriteria(SomeClass.class)
.add(Restrictions.in("id", someIds))
.list();
}
What should I do to guard against this error?
I know I could short-circuit the call and return an empty list like:
if(someIds == null || someIds.size() == 0) {
return new List<SomeThing>();
}
But is there a more elegant way to do this?
I would say Hibernate needs to fix this issue, and give meaningful message.
I think its responsibility of the provider/hibernate to check for the empty/null List.
One can imagine the cause, it tries to construct where clause, something like id in (), somewhere in org.hibernate.loader.criteria.CriteriaQueryTranslator or similar..But because here the List is empty, it would be throwing an exception. But they already created query with ( and could not complete because of exception/empty List.
NO. If you execute the query with empty parameters for in clause, it will fail (you may verify this by running plain SQL). Better not to execute the query if the input param is null/empty.
Only thing I can advice is to use isEmpty() function and != null in if statement and little restructuring as:
#Override
public List<SomeThing> findByIds(List<Integer> someIds) {
List<Something> result = null; //you may initialize with empty list
if(someIds != null || !someIds.isEmpty() {
result = sessionFactory.getCurrentSession().createCriteria(SomeClass.class)
.add(Restrictions.in("id", someIds))
.list();
}
return result;
}
(This is mostly base on #Yogendra Singh's reply, with a twist to make it more adoptable to commonly-seen situation of multiple optional argument)
Criteria API aims to let you compose your query programmatically. Such kind of dynamic feature is expected to be handled in your code.
Normally we make optional criteria by this:
#Override
public List<SomeThing> findBySearchParams(SearchParam searchParam) {
// create criteria with mandatory search criteria
Criteria criteria = sessionFactory.getCurrentSession()
.createCriteria(SomeClass.class);
.add(Restriction("someField", searchParam.getSomeField()));
// add "id" only if "someId" contains value
if(searchParam.getSomeIds() != null && !searchParam.getSomeIds().empty()) {
criteria.add(Restrictions.in("id", searchParam.getSomeIds()));
}
// add "anotherField" only if "anOptionalField" is not null
if(searchParam.getAnOptionalField() != null) {
criteria.add(Restrictions.in("anotherField", searchParam.getAnOptionalField()));
}
return criteria.list();
}
Edit:
Although Hibernate does not (yet) provide a more elegant way for that, you can write something yourself to make it looks more elegant:
class SmartCriteriaBuilder {
private Criteria criteria;
SmartCriteriaBuilder (Criteria criteria) { this.criteria = criteria;}
SmartCriteriaBuilder in(String field, Collection values) {
if (!empty(values)) {
this.criteria.add(Restrictions.in(field,values));
}
}
// all other kind of restrictions ....
Criteria toCriteria() {
return this.criteria;
}
}
Then you can do something looks smarter:
SmartCriteriaBuilder criteriaBuilder =
new SmartCriteriaBuilder(sessionFactory.getCurrentSession().createCriteria());
criteriaBuilder .in("someField", listPossiblyNullOrEmpty);
return criteriaBuilder .toCriteria().list();
I have this method signature:
public int nrOfEntities(Class<? extends MailConfirmation> clazz, User user, String email)
I would like nrOfEntities to return the number of entities that:
Are of the concrete class clazz
Have a matching User if user != null
Have a matching email if user == null
It's the class matching I'm having a problem with. I've tried a few statements without any luck.
Can clazz have subtypes that should not be counted?
If not, is it not sufficient to create the query on clazz?
Criteria criteria = session.createCriteria(clazz);
if (user == null) {
criteria.add(Restrictions.eq("email", email);
} else {
criteria.add(Restrictions.eq("user", user);
}
int result = (Integer) criteria.setProjection(Projections.rowCount()).uniqueResult();
Now I am guessing how your mapping looks (that there are "email" and "user" properties).
If that is not working, I know that there is a pseudo property named "class", at least in HQL. Maybe you can experiment with that.
Are you looking for "from " + clazz.getSimpleName() + " where ..."?
If you want to test the class of an object, you should be able to use something like the following:
Object entity = ... // Get the entity however
boolean matchesClass = entity.getClass().equals(clazz);
If this isn't working for you, give some examples of how it fails since it should be this straightforward!
Can anyone point me out, how can I parse/evaluate HQL and get map where key is table alias and value - full qualified class name.
E.g. for HQL
SELECT a.id from Foo a INNER JOIN a.test b
I wish to have pairs:
a, package1.Foo
b. package2.TestClassName
It's relatively easy to do for result set
HQLQueryPlan hqlPlan = ((SessionFactoryImpl)sf).getQueryPlanCache().getHQLQueryPlan( getQueryString(), false, ((SessionImpl)session).getEnabledFilters() );
String[] aliases = hqlPlan.getReturnMetadata().getReturnAliases();
Type[] types = hqlPlan.getReturnMetadata().getReturnTypes();
See details here.
Hardly a good way of doing it, but it seems you can get the AST through some internal interfaces and traverse this:
QueryTranslator[] translators = hqlPlan.getTranslators();
AST ast = (AST)((QueryTranslatorImpl)translators[0]).getSqlAST();
new NodeTraverser(new NodeTraverser.VisitationStrategy() {
public void visit(AST node) {
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
}
}).traverseDepthFirst(ast);
So this seems to retrieve the alias-mappings from the compiled query, but I would be very careful using this solution: it typecasts objects to subclasses not usually visible to a hibernate-client and interprets the AST based on guessing the semantics of the different nodes. This might not work on all HQL-statements, and might not work, or have different behaviour, on a future hibernate-version.
I found right solution for my question. Your original post was almost correct except that part:
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
Please correct your answer answer and I accept it.