Hibernate : Difference between Map and Result Transformer - java

I was exploring the way to return map from Hibernate.
I came to know i can do it by two ways
First one was : using map
String HQL_QUERY =
"select new map(hp.col1 , hp.col2) from HP hp where hp.col1 in (:Ids)";
test =
getSession().createQuery(HQL_QUERY).setParameterList("Ids", ids).list();
Otherone is using setResultTransformer
String HQL_QUERY =
"select hp.col1 , hp.col2 from HP hp where hp.col1 in (:Ids)";
test =
getSession().createQuery(HQL_QUERY).setParameter("Ids", ids)
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP).list();
In both ways i am getting same result .... no issues.
But i wanna ask if there is any advantage of using one over other.
For example if you are using map you have to write less code , but might be possible that result transformer is more efficient(not sure).
I tried to find it on google but didn't find much.
Please help

They are just two ways of expressing the same thing.
Here is a confirmation from hibernate sources : in QueryLoader.java which is the class parsing hql queries :
selectNewTransformer = HolderInstantiator.createSelectNewTransformer(
selectClause.getConstructor(),
selectClause.isMap(),
selectClause.isList());
Here the selectClause.isMap() will be true (if you want to confirm that, have a look at ConstructorNode.java)
And then in HolderInstantiator.java :
public static ResultTransformer createSelectNewTransformer(Constructor constructor, boolean returnMaps, boolean returnLists) {
if ( constructor != null ) {
return new AliasToBeanConstructorResultTransformer(constructor);
}
else if ( returnMaps ) {
return Transformers.ALIAS_TO_ENTITY_MAP;
}
else if ( returnLists ) {
return Transformers.TO_LIST;
}
else {
return null;
}
}
So i guess the map() inside the hql select clause being more readable and more concise make it the best choice. Apart from that, exactly the same thing will happen behind the scene.

Related

Efficient way of mimicking hibernate criteria on cached map

I have just wrote a code to cach a table in the memory (simple java hashmap). Now one of the code that i am trying to replace is the find the objects based on criteria. it receives multiple field parameters and if those fields are not empty and not null, they were being added as part of hibernate query criteria.
To replace this, what i am thinking to do is
For each valid param (not null and no empty) I will create a HashSet which will satisfy this criteria.
Once i am done making hashsets for all valid criteria, I will call Set.retainAll(second_set) on all sets. So that at the end, I will have only that set which is intersection of all valid criteria.
Does it sound like the best approach or is there any better way to implement this ?
EDIT
Though, My original post is still valid and I am looking for that answer. I ended up implementing it in the following way. The reason is that it was kind a cumbersome with sets since after creating all sets, I had to first figure out which set is non empty so that the retainAll could be called. it was resulting in lots of if-else statements. My current implementation is like this
private List<MyObj> getCachedObjs(Long criteria1, String criteria2, String criteria3) {
List<MyObj> results = new ArrayList<>();
int totalActiveFilters = 0;
if (criteria1 != null){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria2)){
totalActiveFilters++;
}
if (!StringUtil.isBlank(criteria3)){
totalActiveFilters++;
}
for (Map.Entry<Long, MyObj> objEntry : objCache.entrySet()){
MyObj obj = objEntry.getValue();
int matchedFilters = 0;
if (criteria1 != null) {
if (obj.getCriteria1().equals(criteria1)) {
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria2)){
if (obj.getCriteria2().equals(criteria2)){
matchedFilters++;
}
}
if (!StringUtil.isBlank(criteria3)){
if (game.getCriteria3().equals(criteria3)){
matchedFilters++;
}
}
if (matchedFilters == totalActiveFilters){
results.add(obj);
}
}
return results;
}

Hibernate Query By Example not working as expected

everyone.
Sorry for some english mistakes.
I am using Vaadin to build a front-end search interface for some search tasks.
In front-end I have only one text field called "telefone" that should fill three fields in my entity bean (telefone1, telefone2 and telefone3), because the telephone number provided by user can be found in any "telefone" field. See the code below:
#SuppressWarnings("unchecked")
#Override
public List<Fornecedor> executaPesquisaAvancadaDeFornecedores(Fornecedor fornecedor) {
Map<Integer, Fornecedor> fornecedoresMap = new HashMap<Integer, Fornecedor>();
if(fornecedor.getTelefone1() != null) fornecedor.setTelefone1("%" + fornecedor.getTelefone1() + "%");
fornecedor.setTelefone2(fornecedor.getTelefone1());
fornecedor.setTelefone3(fornecedor.getTelefone1());
Example exampleFornecedor = Example.create(fornecedor).enableLike(MatchMode.ANYWHERE).ignoreCase();
Criteria criteria = super.getSession().createCriteria(Fornecedor.class, "fornecedor").add(exampleFornecedor);
List<Fornecedor> fornecedores = criteria.list();
for(Fornecedor f : fornecedores) {
fornecedoresMap.put(f.getId(), f);
}
return new ArrayList<Fornecedor>(fornecedoresMap.values());
}
In this case, there is no result provided by criteria.list() method. But if I remove "telefone2" and "telefone3" setters, like this...
if(fornecedor.getTelefone1() != null) fornecedor.setTelefone1("%" + fornecedor.getTelefone1() + "%");
... the criteria and the QBE returns the result correctly, but only if the telephone provided is saved in "telefone1" field.
My question is why this occurs? AFAIK, Hibernate Query By Example matches the results based on the given search fields filled, ignoring null attributes.
Thanks.
You are building a condition (with s = '%' + entered search string + '%'):
(telefone1 LIKE s) AND (telefone2 LIKE s) AND (telefone3 LIKE s)
I guess that you really want
(telefone1 LIKE s) OR (telefone2 LIKE s) OR (telefone3 LIKE s)
If that is true you can't use query by example which always produces and-connected conditions. Try to use HQL or the criteria API instead.

How to do generic queries with Objectify, to get a sample entity of each Kind

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".

AliasToBeanResultTransformer and Hibernate SQLQuery

I have a rather complex query (too many nested levels for either HQL or Criteria queries), so I've written it as a SQLQuery. I'd really like to use the AliasToBeanResultTransformer to transform my results into a List, but I'm having some issues. I've included code snippets below of what I've got right now.
When I log the results for the transformed query, I can see that the transformer does create a List, however, all the fields in each AdvancedClauseSearchResultDTO are null. I assume that means that I'm doing something wrong with aliasing ... that the AliasToBeanResultTransformer can't find the correct setters to call. However, the AdvancedClauseSearchResultDTO class does have public setters for each of the columns that I've aliased in my sql string. If this was a Criteria query, I'd use projections to define an alias for each column to be returned, but I'm unsure of how to accomplish the same thing using a SQLQuery.
Any advice on how to get the aliases set so that the ResultTransformer can use them? I've seen some limited documentation that suggested that using the 'as aliasName' method should work, but it doesn't seem to be for me.
Beginning snippet of query string definition, note the 'as' alias definitions
StringBuffer clauseBaseQuery = new StringBuffer();
clauseBaseQuery.append("select ");
clauseBaseQuery.append(" clauseDetail.clause_detail_id as clauseDetailId,");
clauseBaseQuery.append(" clauseDetail.clause_id as clauseId,");
clauseBaseQuery.append(" providers.provider_name as provider, ");
clauseBaseQuery.append(" products.product_name as product, ");
SQLQuery creation & setting of resultTransformer
Query query = session.createSQLQuery(clauseBaseQuery.toString());
query.setResultTransformer(new AdvancedClauseSearchResultTransformer());
return (List<AdvancedClauseSearchResultDTO>)query.list();
AdvancedClauseSearchResultTransformer class (uses AliasToBeanResultTransformer and then does some extra processing):
class AdvancedClauseSearchResultTransformer implements ResultTransformer {
//Use the aliasTransformer to do most of the work
ResultTransformer aliasTransformer = Transformers.aliasToBean(AdvancedClauseSearchResultDTO.class);
#Override
public List transformList(List list) {
log.debug("transforming CLAUSE results");
List<AdvancedClauseSearchResultDTO> result = aliasTransformer.transformList(list);
//for each row, set the status field
for (AdvancedClauseSearchResultDTO dto : result) {
log.debug("dto = " + dto);
String status = null;
Date effectiveDate = dto.getEffectiveDate();
Date terminationDate = dto.getTerminationDate();
Date now = new Date(System.currentTimeMillis());
if (now.before(effectiveDate)) {
status = "Pending";
} else if (now.after(terminationDate)) {
status = "Terminated";
} else {
status = "Active";
}
dto.setStatus(status);
if (StringUtils.isNotEmpty(dto.getReasonForAmendment())){
dto.setAmended(Boolean.TRUE);
}else{
dto.setAmended(Boolean.FALSE);
}
}
return result;
}
#Override
public Object transformTuple(Object[] os, String[] strings) {
Object result = aliasTransformer.transformTuple(os, strings);
return result;
}
}
This depends on the backend you're using, which you don't mention in your post.
Various DB backends use case insensitive naming for the columns unless you properly escape them, so they end being retrieved as CLAUSEDETAILID or clausedetailid, even when you specify the column result name with the proper case.
With PostgreSQL (and I believe Oracle, too), you have to write your query like this (note the column quoting):
StringBuffer clauseBaseQuery = new StringBuffer();
clauseBaseQuery.append("select ");
clauseBaseQuery.append(" clauseDetail.clause_detail_id as \"clauseDetailId\",");
clauseBaseQuery.append(" clauseDetail.clause_id as \"clauseId\",");
clauseBaseQuery.append(" providers.provider_name as \"provider\", ");
clauseBaseQuery.append(" products.product_name as \"product\", ");
Query query = session.createSQLQuery(clauseBaseQuery.toString());
So that will allow Hibernate to properly recognize the property and map the result to a bean, provided you also specfied the tranformation:
query.setResultTransformer(Transformers.aliasToBean(AdvancedClauseSearchResultDTO.class));
as was suggested by #zinan.yumak.
I did some more research on this today, and finally noticed a good stack trace of the underlying error I was getting, and a hibernate forum entry that helped me get past this.
The Exception I was getting is:
Caused by: org.hibernate.PropertyNotFoundException: Could not find setter for CLAUSEDETAILID
It appears as if Hibernate is taking my camel case aliases & turning them into all uppercase, so it can't find the matching setters in my AdvancedClauseSearchResultDTO class.
Here's the forum entry that pointed me in the right direction:
https://forum.hibernate.org/viewtopic.php?f=1&t=1001608
I ended up using the approach detailed in that post for my own ResultTransformer, and that's working for me.
I think it is not a good method to write a result transformer to solve
your problem. Try something like this,
Query query = session.createSQLQuery(clauseBaseQuery.toString());
query.setResultTransformer(Transformers.aliasToBean(AdvancedClauseSearchResultDTO.class));
And in AdvancedClauseSearchResultDTO class, modify setter methods to set required
fields for you. For example,
class AdvancedClauseSearchResultDTO {
private Date effectiveDate;
private String status;
.
.
public void getEffectiveDate() {
return effectiveDate;
}
public void setEffectiveDate(Date aDate) {
Date now = new Date(System.currentTimeMillis());
if (now.before(effectiveDate)) {
this.status = "Pending";
} else if (now.after(terminationDate)) {
this.status = "Terminated";
} else {
this.status = "Active";
}
this.effectiveDate = aDate;
}
}
You got the idea...
The easiest fix is to put quotation marks for the column alias like:
select first_name as "firstName" from employee

Hibernate: Parse/Translate HQL FROM part to get pairs class alias, class name

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.

Categories