I would like to create criteria or add restriction to my existing criteria (i think it has to be criteria) that will ignore "-" when searching for data's.
E.g I'm searching for number "888" and I would like to get "8-8-8" too.
I have bean that contains my number field
A.java
#Data
public class A {
[...]
#Length(max = 16)
#Column(length = 16)
private String number;
}
And here's my criteria
public Pair<Long, List<A>> filter(CompoundFilter filter, CompoundSort sort, int start, int count) {
Criteria criteria = getSession().createCriteria(A.class, "a_alias");
...
}
What should I add to criteria to achieve my goal?
My other idea is to create some hidden field (by hidden i mean the one that i wont use on UI, only fill with datas) that will hold "transformed" values and when filtering number i will filter on this field
try using regex ,the pattern would look like "[0-9]-[0-9]- ... [0-9]" , here instead of 0-9 build the pattern with your desired number. If i understood your question correctly then this could be an answer.
Related
Currently we have a class that looks something like that (depersonalised and non-relevant parts removed):
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractTable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#OrderBy("CREATED_ON DESC")
private Set<MainTableState> states;
...
public MainTableState getActiveState(){
if(this.states == null || this.states.isEmpty()){
return null;
}
MainTableState latest = states.iterator().next();
// The reason we use this for-loop, even though we have the #OrderBy annotation,
// Is because we can later add states to this list, which aren't automatically ordered
for(MainTableState state : states){
if(state.getCreatedOn() != null && latest.getCreatedOn() != null &&
state.getCreatedOn().after(latest.getCreatedOn()){
latest = state;
}
}
return latest;
}
...
}
So currently it will retrieve all MainTableStates from the DB by default, and if we need the activeState we use the for-loop method. Obviously this is pretty bad for performance. Currently we don't use this list at all (the purpose was to have a history of states, but this has been postponed to the future), but we do use the getActiveState() method quite a bit, mostly to show a String inside of the MainTableState-class in the UI.
In addition, even if we would always use a TreeSet and keep it sorted so we won't need the loop but only need states.iterator().next() instead, it will still initialize the list of states. With some heavy performance testing we had more than 1 million MainTableState-instances when it crashed with an java.lang.OutOfMemoryError: GC overhead limit exceeded.
So, we want to change it to the following instead:
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractEntity {
#???
private MainTableState activeState;
...
public MainTableStates getActiveState(){
return activeState;
}
...
}
So, my question, what should I put at the #??? to accomplish this? I'm assuming I need the #Formula or something similar, but how can I say to hibernate it should return a MainTableState object? I've seen #Formula being used with MAX for a date, but that was to get that date-property, not get an entire object based on that max date.
After #user2447161's suggestion I've used a #Where-annotation, which does indeed help to reduce the Collection size to 1 (sometimes), but I have two more related questions:
How to use #OnToMany and #Where but get a single object, instead of a list of objects of size one? Is this even possible? Here in a answer from December 2010 it is stated it isn't. Has this been fixed somewhere in the last six years?
How to deal with the random alias in the where clause? I could do something like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#Where(clause = "CREATED_ON = (SELECT MAX(mts.CREATED_ON) FROM MAIN_TABLE_STATES mts WHERE mts.FK_MAIN_ID = ???.MAIN_ID)")
private Set states; // TODO Get single object instead of collection with size 1
The problem with is that ??? is a random alias generated by hibernate (sometimes it's this_, sometimes it's something along the lines of mainTable_1_, etc.). How to set this alias for the entire query to the DB to use it here? I also tried MAIN_TABLE.MAIN_ID instead which doesn't work, and with no alias it also doesn't work because it uses the MainTableState-alias instead of MainTable-alias (like this below).
from
MAIN_TABLE this_
left outer join
MAIN_TABLE_STATUSES mainstat2_
on this_.main_id=mainstat2_.fk_main_id
and (
mainstat2_.created_on = (
SELECT
MAX(mts.created_on)
FROM
MAIN_TABLE_STATUSES mts
WHERE
-- mainstat2_.main_id should be this_.main_id instead here:
mts.fk_main_id = mainstat2_.main_id
)
)
Well, regarding your question #2, as it looks like you need a quick solution with minimal impact in your existing code, this may be acceptable: you can use an Interceptor to deal with the alias and generate the right sql statement. Do this:
use a unique string as alias placeholder in your #Where clause, for instance:
...WHERE mts.FK_MAIN_ID = ${MAIN_TABLE_ALIAS}.MAIN_ID...
if your application doesn't have one yet, create an Interceptor class extending EmptyInterceptor and configure it as a SessionFactory interceptor
override the onPrepareStatement method to replace the placeholder with the alias found after 'from MAIN_TABLE' with something like this:
public String onPrepareStatement(String sql) {
String modifiedSql = sql;
if (sql.contains("${MAIN_TABLE_ALIAS}")) {
String mainTableAlias = findMainTableAlias(sql);
modifiedSql = sql.replace("${MAIN_TABLE_ALIAS}", mainTableAlias);
}
return modifiedSql;
}
Be aware that this method will be called for every sql statement that hibernate generates in your application.
Additionaly, your #Where clause only works properly when a join is used, so you should set the fetch mode explicitly adding
#Fetch(FetchMode.JOIN)
to the states property to avoid that hibernate may use the select mode.
I have classes A and B that look like this:
public class A {
}
and
import org.hibernate.annotations.Formula;
import javax.persistence.Column;
public class B extends A {
#Formula("formula")
private String formula;
#Column(name = "col1")
#Formula("formula")
private String field;
}
I need to find fields that do not have the annotation #Column
I cannot use pure regular expressions, because I only need to look into classes that have class A in hierarchy.
I started with search
class $Class$ extends $superClass$ {
#$annotation$
private String $field$;
}
where $annotation$ is "Column" ("Invert condition" = true, "Whole words only" = true), but it matches field's formula annotation #Formula.
Annotation names are just an example (this case is not a common problem that could be covered by some inspection in IDEA).
Maybe you have ideas how to do this?
Change the constraints for the $annotation$ variable to:
Invert condition: false
Whole words only: false
Minimum count: 0
Maximum count: 0
"Invert condition" works like a boolean NOT. When enabled it matches names not like the specified pattern. In your case "Formula" is indeed not like "Column" and matches. By setting minimum and maximum count to zero instead, you're telling Structural Search you are searching for field with zero occurrences of an annotation with name "Column", which should get you the results you want.
I am starting with hibernate search and am struggling with a query on a List<Integer>
I created a bridge to translate the list<Integer> to a string. From this, I am able to search by keyword exact matches on any item on the list, but I don't seem to be able to query it using range.
My entity A has an attribute "b" defined as List.
I would like to know if anyone can help me to get to query all the A entities which have any of the b elements inside a defined range?
For example:
an A instance with the following collection {1,10, 15}, should come up in the following queries on "b" attribute:
below(20),
above(14),
below(2)
but not in a search like:
above(16), below(0).
I hope I made myself clear.
Thanks in advance!
Change your bridge to storing same field multiple times, each with value a of the Integer list. So assuming your field is called myInt, you would store myInt = 1, myInt = 10 and myInt = 15, example code:
public class MyBridge implements FieldBridge {
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if (value instanceof List){
for(Object myInt:(List)value){
Field myIntField = new Field(name, myInt.toString(), luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector());
myIntField.setBoost(luceneOptions.getBoost());
document.add(myIntField);
}
}
}
}
Alternately, you might be able to plugin some custom lucene Filter to do it, but Filters are a bit convoluted.
I have a model Item that has a name and description.I need to allow the user to search for a part of string in name or description.Instead of doing this using an sql query,I thought of using the search module that can be installed for playframework.
Looking at the documentation for search module ,I put these annotations to the model
#Entity
#Indexed
class Item{
#Field
public String name;
#Field
public String description;
public Date creationDate;
...
...
}
In application.conf ,I set
play.search.reindex=enabled
If I use an sql query like this
public static List<Item> getSearchResults(String kw){
List<Item> items = null;
if(kw!=null && kw.length()>0) {
String trimkw = kw.trim().toLowerCase();
String pattern = "%"+trimkw+"%";
String query="select distinct b from Item b where (lower(name) like :pattern or lower(description) like :pattern)";
items = Item.find(query).bind("pattern", pattern).fetch();
System.out.println("getSearchResults():: items="+items.size());
}
return items;
}
This works properly,and handles the cases where input string is uppercase or lowercase etc.Also it will get results for partial strings ..
For example ,
I have items JavaRing ,Android
when the kw="JAvA"
the search returns a list containing JavaRing
I tried using Search module like this
import play.modules.search.Search;
import play.modules.search.Query;
...
String qstr = "name:"+trimkw+" OR description:"+trimkw;
System.out.println("query string="+qstr);
Query q = Search.search(qstr, Item.class);
items = q.fetch();
System.out.println("items="+items.size());
But this returns an empty list for the same keyword as I used in the previous case.
keyword = "JAvA"
query string=name:java OR description:java
items=0
Is there something wrong with the way I have coded the search string?
Search module is based on Lucene. By default, Lucene searches for whole words. You didn't find anything because there isn't whole word 'java' in your fields.
Using wildcards, for instance name:java* OR description:java* you'll fit your needs. You can find more examples there
Updated link is http://lucene.apache.org/java/3_0_2/queryparsersyntax.html
In this case, if the keyword is to be found anywhere, I assume string pattern needs to be modified from % to *.
ie. String pattern = trimkw+"*";
The rest of the code could remain the same.
I have a data model that looks something like this:
public class Item {
private List<ItemAttribute> attributes;
// other stuff
}
public class ItemAttribute {
private String name;
private String value;
}
(this obviously simplifies away a lot of the extraneous stuff)
What I want to do is create a query to ask for all Items with one OR MORE particular attributes, ideally joined with arbitrary ANDs and ORs. Right now I'm keeping it simple and just trying to implement the AND case. In pseudo-SQL (or pseudo-HQL if you would), it would be something like:
select all items
where attributes contains(ItemAttribute(name="foo1", value="bar1"))
AND attributes contains(ItemAttribute(name="foo2", value="bar2"))
The examples in the Hibernate docs didn't seem to address this particular use case, but it seems like a fairly common one. The disjunction case would also be useful, especially so I could specify a list of possible values, i.e.
where attributes contains(ItemAttribute(name="foo", value="bar1"))
OR attributes contains(ItemAttribute(name="foo", value="bar2"))
-- etc.
Here's an example that works OK for a single attribute:
return getSession().createCriteria(Item.class)
.createAlias("itemAttributes", "ia")
.add(Restrictions.conjunction()
.add(Restrictions.eq("ia.name", "foo"))
.add(Restrictions.eq("ia.attributeValue", "bar")))
.list();
Learning how to do this would go a long ways towards expanding my understanding of Hibernate's potential. :)
Could you use aliasing to do this?
Criteria itemCriteria = session.createCriteria(Item.class);
itemCriteria.createAlias("itemAttributes", "ia1")
.createAlias("itemAttributes", "ia2")
.add(Restrictions.eq("ia1.name", "foo1"))
.add(Restrictions.eq("ia1.attributeValue", "bar1")))
.add(Restrictions.eq("ia2.name", "foo2"))
.add(Restrictions.eq("ia2.attributeValue", "bar2")))
Not sure how hibernate handles joining on the same property twice explicitly like that, maybe worth trying?
SELECT item FROM Item item JOIN item.attributes attr
WHERE attr IN (:attrList) GROUP BY item
and then in the Java code:
List<ItemAttribute> attrList = new ArrayList<ItemAttribute>();
attrList.add(..); // add as many attributes as needed
...// create a Query with the above string
query.setParameter("attrList", attrList);
Why wouldn't the following work?
return getSession().createCriteria(Item.class)
.createAlias("itemAttributes", "ia")
.add(Restrictions.or()
.add(Restrictions.conjunction()
.add(Restrictions.eq("ia.name", "foo1"))
.add(Restrictions.eq("ia.attributeValue", "bar1")))
.add(Restrictions.conjunction()
.add(Restrictions.eq("ia.name", "foo2"))
.add(Restrictions.eq("ia.attributeValue", "bar2"))))
.list();
That would be (name=foo1 && attributeValue=bar1) OR (name=foo2 && attributeValue=bar2)
I didn't test it, but this is how I should try to solve your problem if I would have to:
Map<String,String> map1 = new TreeMap<String,String>();
map1.put("ia.name","foo1");
map1.put("ia.value","bar1");
Map<String,String> map2 = new TreeMap<String,String>();
map2.put("ia.name","foo2");
map2.put("ia.value","bar2");
return getSession().createCriteria(Item.class)
.createAlias("itemAttributes", "ia")
.add(Restrictions.and()
.add(Restrictions.allEq(map1))
.add(Restrictions.allEq(map2))
)
.list();
Please, let me know if it worked. I think the same should work with or()...
Use LEFT_OUTER_JOIN to prevent "WHERE x = 1 AND x = 2" kind of issue
CreateAlias("itemAttributes", "ia", JoinType.LEFT_OUTER_JOIN)