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.
Related
I am using spring-sata-mongodb 1.8.2 with MongoRepository and I am trying to use the mongo $slice option to limit a list size when query, but I can't find this option in the mongorepository.
my classes look like this:
public class InnerField{
public String a;
public String b;
public int n;
}
#Document(collection="Record")
punlic class Record{
public ObjectId id;
public List<InnerField> fields;
public int numer;
}
As you can see I have one collection name "Record" and the document contains the InnerField. the InnerField list is growing all the time so i want to limit the number of the selected fields when I am querying.
I saw that: https://docs.mongodb.org/v3.0/tutorial/project-fields-from-query-results/
which is exactly what I need but I couldn't find the relevant reference in mongorepository.
Any ideas?
Providing an abstraction for the $slice operator in Query is still an open issue. Please vote for DATAMONGO-1230 and help us prioritize.
For now you still can fall back to using BasicQuery.
String qry = "{ \"_id\" : \"record-id\"}";
String fields = "{\"fields\": { \"$slice\": 2} }";
BasicQuery query = new BasicQuery(qry, fields);
Use slice functionality as provided in Java Mongo driver using projection as in below code.
For Example:
List<Entity> list = new ArrayList<Entity>();
// Return the last 10 weeks data only
FindIterable<Document> list = db.getDBCollection("COLLECTION").find()
.projection(Projections.fields(Projections.slice("count", -10)));
MongoCursor<Document> doc = list.iterator();
while(doc.hasNext()){
list.add(new Gson().fromJson(doc.next().toJson(), Entity.class));
}
The above query will fetch all documents of type Entity class and the "field" list of each Entity class document will have only last 10 records.
I found in unit test file (DATAMONGO-1457) way to use slice. Some thing like this.
newAggregation(
UserWithLikes.class,
match(new Criteria()),
project().and("likes").slice(2)
);
I am working on a legacy database where modifying the table schema is not an option. Most records are unique but there are some duplicate entries. For that reason I have modified the RecordRepository.java interface to perform a #Query with map(). Otherwise JPA will return the same data if it thinks it's the same record.
RecordRepository.java:
#Query("select new map(field1 as field1, field2 as field2) from Record where year = ?1")
List<Record> findByYear(String year);
RecordController.java:
#RestController
public class RecordController {
#Autowired
private RecordRepository recordRepository;
#RequestMapping(value = "/record/{year}", method = RequestMethod.GET)
public List<Record> recordByYear(#PathVariable("year") String year) {
List<Record> l = recordRepository.findByYear(year);
System.out.println(l.getClass());
System.out.println(l.get(1967));
return l;
}
}
The output from getClass() is class java.util.ArrayList. Printing item 1967 from the ArrayList is {field1=2018-01-15, field2=201801}.
But when trying to get the string-value of field1 using String tmp_r = l.get(1967).getField1() I get the error java.util.HashMap cannot be cast to Record.
I have tried various suggestions from SO. My head is spinning, I must be overlooking something simple explanation to this.
Regards
Claus
Well l.getClass() is an ArrayList, but that doesn't mean that all its elements are Record-s (maybe casts were done somewhere else).
When you call l.get(1967) the resulting item is an HashMap (right?), so maybe you can check the actual type of the expression l.get(1967) first.
If the map is indexed by Strings, then String tmp_r = l.get(1967).get("field1") will print your field.
Danieles answer, and STaefi's comment, led me in the rigth direction. The solution ended up being quite simple. My error can be attributed to the facts it's been a while I worked with this. I changed the return type in the interface from List to List.
RecordRepository.java:
List<HashMap<String, Record>> findByYear(String year);
RecordController:
List<HashMap<String, Record>> l = recordRepository.findByYear(year);
System.out.println(l.get(1967).get("field1"));
will give me the value field1 has.
I am trying to replace element in collection with new modified version. Below is short code that aims to demonstrate what I'd like to achieve.
The whole idea is that I have one object that consists of collections of other objects. At some point in time I am expecting that this objects in collections (in my example phones) might require some modifications and I'd like to modify the code in one place only.
I know that in order to update the object's attributes I can use setters while iterating through the collection as demonstrated below. But maybe there is better, more general way to achieve that.
public class Customer {
private int id;
private Collection<Phone> phoneCollection;
public Customer() {
phoneCollection = new ArrayList<>();
}
//getters and setters
}
and Phone class
public class Phone {
private int id;
private String number;
private String name;
//getters and setters
}
and
public static void main(String[] args) {
Customer c = new Customer();
c.addPhone(new Phone(1, "12345", "aaa"));
c.addPhone(new Phone(2, "34567", "bbb"));
System.out.println(c);
Phone p = new Phone(2, "9999999", "new name");
Collection<Phone> col = c.getPhoneCollection();
for (Phone phone : col) {
if (phone.getId() == p.getId()) {
// This is working fine
// phone.setNumber(p.getNumber());
// phone.setName(p.getName());
// But I'd like to replace whole object if possible and this is not working, at least not that way
phone = p;
}
}
System.out.println(c);
}
}
Is this possible to achieve what I want?
I tried copy constructor idea and other methods I found searching the net but none of them was working like I would expect.
EDIT 1
After reading some comments I got an idea
I added the following method to my Phone class
public static void replace(Phone org, Phone dst){
org.setName(dst.getName());
org.setNumber(dst.getNumber());
}
and now my foreach part looks like that
for (Phone phone : col) {
if (phone.getId() == p.getId()) {
Phone.replace(phone, p);
}
}
And it does the job.
Now if I change the Phone class attributes I only need to change that method. Do you think it is OK solving the issue that way?
You should not modify the collection while you're iterating through it; that's likely to earn you a ConcurrentModificationException. You can scan the collection for the first object that matches your search criterion. Then you can exit the loop, remove the old object, and add the new one.
Collection<Phone> col = c.getPhoneCollection();
Phone original = null;
for (Phone phone : col) {
if (phone.getId() == p.getId()) {
original = phone;
break;
}
}
if (original != null) {
Phone replacement = new Phone(original);
replacement.setNumber(p.getNumber());
replacement.setName(p.getName());
col.remove(original);
col.add(replacement);
}
Alternatively, you could declare a more specific type of collection, such as a List, that would allow you to work with indexes, which would make the replacement step much more efficient.
If your phone IDs are unique to each phone, you should consider using a Map<Integer, Phone> that maps each phone ID to the corresponding phone. (Alternatively, you could use some sort of third-party sparse array structure that doesn't involve boxing each ID into an Integer.) Of course, if your IDs aren't unique, then you might want to modify the above to gather a secondary collection of all matching phones (and reconsider the logic of your existing code as well).
You can also use a Set (HashSet), this is only when you don't want to do the way Mike suggested.
Use the Phone as an item in the set. Don't forget to implement hashCode() and equals() in Phone. hashCode() should return the id, as it is supposed to be unique.
Since you are concerned about replacing the item, here's how HashSet will help you :
Create an instance of your object.
Remove the object you want to replace from the set.
Add the new object (you created in step 1) back to the set.
Both these operations 2 & 3 are guaranteed in O(1) / constant time.
You don't need to maintain a map for this problem, that's redundant.
If you want to get the object from the collection itself and then modify it, then HashMap would be better, search is guaranteed in O(1) time.
Instead of a list, use a map with the Phone's id as the key. Then your code looks like this:
public static void main(String[] args) {
Customer c = new Customer();
c.addPhone(new Phone(1, "12345", "aaa"));
c.addPhone(new Phone(2, "34567", "bbb"));
System.out.println(c);
Phone p = new Phone(2, "9999999", "new name");
Map<Integer, Phone> phoneMap = c.getPhoneMap();
phoneMap.put(p.getId(), p);
System.out.println(c);
}
If you take the object out from the collection and update its properties, it will get reflected in the same object in collection too.. Hence, you dont have to technically replace object after updating it.
As "Mike M." pointed out, you can use hashmap to retrieve the object quickly without iteration and update the object values.
If order matters to you, you can change Collection to List (Since you're always using an ArrayList anyway) and then:
int index = col.indexOf(phone);
col.remove(phone);
col.add(p, index);
consider following classes:
class Basic{
String id;
Double val;
//some other member variables
}
class NodeBO{
List<String> id;
Type type;
// list of id from objects of Basic class in data below
Map<ChEnum, Basic> data;
addBeans(NodeBO nodeBO, Node node){
// in transaction...
node.setProperty("priperties", nodeBO.toString());
// is it ok to convert to array? or should be converted to JSON string?
node.setProperty(GraphElementProps.id,toArray(nodeBO.id));
node.setProperty(GraphElementProps.type, nodeBO.type);
}
#override
toString(){
//return json of this object
}
}
enum ChEnum{
CH1(1), CH2(2);
// constructor and some methods
}
nodes are indexed using autoIndexer:
AutoIndexer<Node> nodeAutoIndexer = GRAPH_DB.index().getNodeAutoIndexer();
nodeAutoIndexer.startAutoIndexingProperty(GraphElementProps.id);
nodeAutoIndexer.setEnabled(true);
GRAPH_NODE_AUTO_INDEX = nodeAutoIndexer.getAutoIndex();
Here I'm storing GraphElementProps.id as node property (by converting to array). Does it take array (of string) as property? Or should I convert list to JSON-string and then store?
I want to be able to query on this array given with queryId. e.g. query on node-index to get nodes in which node.getProperty(GraphElementProps.id) contain given queryId? i.e. something like:
// how to do this?
GRAPH_NODE_AUTO_INDEX.get(/*Nodes whose id contain queryId*/);
Or is it (somehow) possible to make id property of Basic class indexable and searchable? How to index such properties, if possible? and how to query them?
I'm unable to understand but is it something related to Spring-data-neo4j? I'm completely new to Spring-data-neo4j.
I think the best solution is to use Spring-data-neo4j. This will allow to index embedded fields and query on them.
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)