I have an object with 70 attributes. For ease of use I created 2 objects, a 'main' object and a 'details' object, with 1:1 relationship based on an auto-generated integer ID. I had a SEARCH screen that allowed searching on any of the main attributes, for which I build Restriction objects for whatever the user typed in. What was nice was that I did this all through iterating through the fields and building criterion - I didn't need ugly code to specifically handle each of the 30 attributes.
Now they want to search on the details fields as well. My previous screen-field-iterating code works perfectly with no changes (the whole reason for making it 'generic'), however I cannot get the JOIN to work to query on details fields.
class House {
Integer houseID;
String address;
. . .
HouseDetails houseDetails;
}
class HouseDetails {
Integer houseID;
String color;
. . .
}
I tried to create an alias and add it to the criteria :
criteria.createAlias("houseDetails", "houseDetails");
but I get this error :
org.hibernate.QueryException: could not resolve property: color of: House
Here's the thing - I know this would work if I prefix my restrictions with the alias name, but I do NOT want to have to know which table (House or HouseDetails) the field comes from. That would ruin all the automatic looping code and create specific code for each field.
Since SQL can do this as long as the column names are unique :
select * from house, housedetails where house.houseID = housedetails.houseID
and color = 'blue';
I'm wondering how can I get this to work using criteria??
As an aside, but related to this : Is there a way to perform something like Java's introspection on Hibernate HBM.XML mapping files? A number of times I've wanted to do this to solve problems but never found an answer. For the above problem, if I could easily find out which table contained each field, I could add the prefix to the Restriction. Something like this :
// Map of search keys (columns) to searching values
for ( String key : parms.keySet() ) {
String val = parms.get(key);
if ( HIBERNATE-SAYS-KEY-IS-FROM-DETAILS-TABLE ) {
key = "houseDetails." + key;
}
criteria.add(Restrictions.eq(key,val));
}
You can make method to find table name for passed column name.
By using SessionFactory.getClassMetaData() you can get all the information about that class. Once you have ClassMetaData then you can get all the property names. An demo method is shown below:
public String findTableName(String columnName)
{
boolean found=false;
Map<String, ClassMetadata> classMetaData = sessionFactory.getAllClassMetadata();
for (Entry<String, ClassMetadata> metaData : classMetaData.entrySet())
{
String[] propertyNames = metaData.getValue().getPropertyNames();
for (String property : propertyNames)
{
if(property == columnName)
{
return metaData.getKey() + "." + property;
found=true;
break;
}
}
if(found)
break;
}
}
The alias mechanism in hibernate and the Criteria API is pretty well specified. I suggest going through the documentation a little a bit.
I think what you want is something like this:
Criteria criteria = session.createCriteria(House.class);
criteria.createAlias("houseDetails.color", "houseColor");
criteria.add(Restrictions.eq("houseColor", "red"));
Related
I'm using https://github.com/leangen/graphql-spqr with spring-boot java application. I can reach to alias name easily but how can I reach to original fieldName?
class Food {
#GraphQLQuery(name = "aliasNameX", description = "A food's name")
private String originalName;
...
}
....
#GraphQLQuery(name = "foods") // READ ALL
#Override
public List<Food> getFoods(#GraphQLEnvironment ResolutionEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.dataFetchingEnvironment.getSelectionSet();
List<SelectedField> fields = selectionSet.getFields();
for (SelectedField f: fields)
{
System.out.println(f.getName());
}
return foodRepository.findAll();
}
When I run this code, Output looks like with alias fields: "aliasNameX", ..., but I need original name like "originalName". Is there a way to do it?
Solved, according to:
https://github.com/leangen/graphql-spqr/issues/381
Posting my original answer here as well.
You want the underlying field names, but from a level above. Still possible, but ugly :(
for (SelectedField selectedField : env.dataFetchingEnvironment.getSelectionSet().getImmediateFields()) {
Optional<Operation> operation = Directives.getMappedOperation(selectedField.getFieldDefinition());
String javaName = operation.map(op -> ((Member) op.getTypedElement().getElement()).getName()).orElse(null);
}
Be very careful though. If there's more than one Java element exposed per GraphQL field, getTypedElement().getElement() will explode. So to be sure you'd have to call getTypedElement().getElements() (plural) instead and decide what to do. ClassUtils#getPropertyMembers might also be useful, or the ClassUtils.findXXX family of methods.
You'd basically have to do this:
List<AnnotatedElement> elements = getTypedElement().getElements();
//Look for a field and use its name
Optional<String> field = Utils.extractInstances(elements, Field.class).findFirst().map(Field::getName);
//Look for a getter and find its associated field name
Optional<String> getter = Utils.extractInstances(elements, Method.class).findFirst().map(ClassUtils::getFieldNameFromGetter);
This API might have to change in future, as SDL-based tools are proliferating, so complex directives like the ones SPQR is using are causing problems...
I am using SolrJ to interact with Solr instance. I simply want to get search score of each document returned by Solr for a particular search query. If I add a score field in my POJO with #Field annotation it works totally fine while retrieving documents. When I try to index something through the same POJO, Solr returns an error saying unknown field "score", as I do not have any field named "score" in my Solr schema. But, if I add a field named "score" in my Solr schema it starts returning the default value of score instead of search score. Please suggest me how to solve this problem. I do not want to iterate through all the documents one by one and add the score to POJO. I am performing following steps :
public void getSolrResult(String query) {
SolrQuery query = new SolrQuery();
query.set("q", query);
query.set("fl", "*, score");
QueryResponse queryResponse = solrjClient.query(query);
solrResult = queryResponse.getBeans(PojoSolr.class);
}
Class PojoSolr {
//Other fields
#Field("score")
private float searchScore;
public float getSearchScore(){
return searchScore;
}
public void setSearchScore(float score) {
this.searchScore = score;
}
}
I solved a similar problem like this using inheritance.
Simply move the score field down to a sub-class. Use the base class for indexing and the sub-class for querying.
class PojoSolr {
//Other fields
}
class PojoSolrOutput extends PojoSolr {
#Field("score")
private float searchScore;
}
Note that there are also use cases for the other way round, e.g. fields that are not stored and can thus not be retrieved, but must be indexed.
In this case another sub-class PojoSolrInput containing these fields makes sense.
Hope this helps.
My 30-minutes search on this topic lead me only to this SO post and:
http://lucene.472066.n3.nabble.com/How-to-get-the-score-in-the-result-td493812.html
http://lucene.472066.n3.nabble.com/Scoring-using-POJO-SolrJ-td3235016.html
In the second it's suggested to remove the #Field annotation from score, but it does not work. In both places no valid solution has been proposed.
So I came up with the solution of cycling on the "raw" results, binding them as getBeans does, and adding the score:
SolrDocumentList sdl = response.getResults();
List<T> documents = new LinkedList<>();
for (SolrDocument sd : sdl) {
#SuppressWarnings("unchecked")
T sb = (T) getClient().getBinder().getBean(<T class>, sd);
sb.setScore((Float) sd.get("score"));
documents.add(sb);
}
I am trying to add a filter to check for duplicate values that a user might input. I am not sure where I am going going wrong in my query.
My query doesnot enter the loop to check if the name already exists.
I am fairly new to google-could. If someone can tell me on how I can fix my problem or if there is a better solution.
else if ( commandEls[0].equals( "add_director" ) ) {
String name = commandEls[1];
String gender = commandEls[2];
String date_of_birth = commandEls[3];
boolean duplicate = false;
//add a director record with the given fields to the datastore, don't forget to check for duplicates
Entity addDirectorEntity = new Entity("Director");
// check if the entity already exits
// if !duplicate add, else "Already exisits"
Query directorExists = new Query("Movies");
// Director Name is the primary key
directorExists.addFilter("directorName",Query.FilterOperator.EQUAL, name);
System.out.print(name);
PreparedQuery preparedDirectorQuery = datastore.prepare(directorExists);
System.out.print("outside");
for(Entity directorResult : preparedDirectorQuery.asIterable()){
// result already exists in the database
String dName = (String) directorResult.getProperty(name);
System.out.print(dName);
System.out.print("finish");
duplicate = true;
}
if(!duplicate){
addDirectorEntity.setProperty("directorName",name);
addDirectorEntity.setProperty("directorGender",gender);
addDirectorEntity.setProperty("directorDOB",date_of_birth);
try{
datastore.put(addDirectorEntity);
results = "Command executed successfully!";
}
catch(Exception e){
results = "Error";
}
}
else {
results = "Director already exists!";
}
}
Non-ancestor queries (like the one in your example) are eventually consistent, so they cannot reliably detect duplicate property values. Ancestor queries are fully consistent, but they require structuring your data using entity groups, and that comes at the cost of write throughput.
If the directorName property in your example is truly unique, you could use it as the name in the key of your Director entities. Then, when you are inserting a new Director entity, you can first check if it already exists (inside of a transaction).
There's no general, built-in way in Datastore to ensure the uniqueness of a property value. This related feature request contains discussion of some possible strategies for approximating a uniqueness constraint.
I'd also recommend reading up on queries and consistency in the Datastore.
That is a valid thing to do but i figured out my problem.
I am making an Entity for Director where as That should be for movies.
I need to know which physical column is associated to a persistent's attribute.
e.g.
Class LDocLine has this attribute
private Integer lineNumber;
which is mapped in hibernate like this :
<property name="lineNumber" column="LINENUMBER" type="integer"/>
The method I need is something like :
getColumn("LDocLine","lineNumber) => "LINENUMBER"
I assume its existence internally, but not sure if it's in the public api.
Thanks in advance
Do you have access to Configuration object you've used to build your session factory? If so, you can use the following:
Value v = configuration.getClassMapping(entityName).getProperty(propertyName).getValue();
for (Iterator it = v.getColumnIterator(); it.hasNext(); ) {
Column column = (Column) it.next();
column.getName(); // or .getQuotedName() or bunch of other useful stuff
}
Column documentation.
If you don't have access to configuration, you can obtain column data from SessionFactory instance, however in this case you're technically no longer using public API as you'll have to class cast to internal implementations:
AbstractEntityPersister persister = (AbstractEntityPersister) sessionFactory.getClassMetadata(entityName);
String[] columnNames = persister.getPropertyColumnNames(propertyName);
In both cases entityName is the name of your entity (its class name unless explicitly overridden)
As you mentioned in your reply, you are not having access to 'Configuration' object.In case you are having access to hibernate 'Session' object, then following code may be helpful to you.
Collection clsMetaData = session.getSessionFactory()
.getAllClassMetadata().values();
for (Iterator i = clsMetaData.iterator(); i.hasNext();) {
ClassMetadata cmd = (ClassMetadata) i.next();
System.out.println("cmd" + cmd.getEntityName());
for (String s : cmd.getPropertyNames()) {
System.out.println("prop:" + s);
}
}
In this way you can get details about Class metadata information.
This is something that Hibernate is not generally used for as you do not need to refer to column names in order to retreive objects through HQL or Criteria.
Why do you need this functionality?
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.