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.
Related
I currently have a system in place which can filter and sort records in the database and return them as a Paged object. One of the lines is like this:
final PageRequest request = new PageRequest(this.pagingSettings.getPageNumber(),
this.pagingSettings.getPageSize(), sortDirection, sortedBy);
This works correctly, but now I'm having the following situation. I'm trying to sort on a house number, which is a varchar in my Postgres database. For example, we have 1, 12, 111, 1004 but also 1A or 36-BASEMENT. When sorting on these (character) values, these would sort on: 1, 1004, 111, 12, 1A, ...
So, sortedBy is now a String, which in this case is houseNumber. I found out that using the ORDER BY argument ... ORDER BY NULLIF(regexp_replace(container_number, E'\\D', '', 'g'), '')::int"; in Postgres, the sorting would be exactly like I wanted: 1, 1A, 12, 111, ...
However, just changing the sortedBy String to sortedBy = "NULLIF(regexp_replace(container_number, E'\\D', '', 'g'), '')::int"; does not seem to work.
Does anyone have a suggestion on how to sort the character values in a PageRequest numerical, without changing the database?
So basically you need to do two things:
Implement custom comparator,
Annotate the entity class.
Ad.1.
public class HouseComparator implements Comparator<House> {
#Override
public int compare(House h1, House h2) {
String s1 = h1.getHouseNumber().split("[^0-9]")[0];
String s2 = h2.getHouseNumber().split("[^0-9]")[0];
return s1.compareTo(s2);
}
}
You need to add some better handling of your cases. The above comparator says, that h1 is less than h2 when it begins with a smaller number and vise versa. For this comparator 12A is equal 12B but it's up to you.
Ad.2.
#SortComparator(HouseComparator.class)
List<House> findByHouseNumber(Pageable pageable);
Assume that you have entity with a String filed and you want to sort it like Long with jpa Pageable.
So you need to do following things:(Remember this only works with Oracle Database)
Add a new filed in your entity with Long type
for the new filed use #Formula in getter method and evoke to_number()
#Entity
public class testEntity{
private String oldField; //Getter and Setter
private Long newField; //Setter
#Formula(value = "to_number(oldField)")
public Long getNewField() {
return newField;
}
}
in your service find sorted filed and change it to the newfiled
if (Objects.nonNull(pagingRequest.getSort()) && pagingRequest.getSort().getFieldName().equals("oldField")) {
pagingRequest.getSort().setFieldName("newField");
}
I think you could try the Spring Data JpaSort class which allows function calls.
As stated in the documentation you will have something like :
#Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)"));
You could also use it with a Pageable object.
I'm trying to find a list of objects that are after a certain DateTime. In order to do so, I've created the following query:
return foos.retrieve(QueryFactory.equal(EXPIRY_INDEX, new DateTime()));
I then created the following index:
public static final Attribute<Foo, DateTime> EXPIRY_INDEX = new SimpleAttribute<Foo, DateTime>() {
#Override
public DateTime getValue(Foo foo, QueryOptions queryOptions) {
return foo.getEXPIRY();
}
};
So far all good, except, the equal(...) method will invoke DateTime.equals(...) as far as I know which will ultimately return false all the time. What I need is a way to call DateTime.isAfterNow(...) or DateTime.isAfter(...).
How can I find all elements in a collection that have a DateTime after right now?
If I understand correctly, you should use a greaterThan() query instead of an equals() query.
These will rely on Comparable.compareTo(), instead of Object.equals(). So it should work if your DateTime object implements the Comparable interface properly.
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've a class -
public class Data implements Identifiable{
private Integer id;
public Integer getId(){
return id;
}
}
now I've two collections-
List<Data> data1 = // few hundred Objects
Set<Integer> dataIds = // few object ids
I would like to extract the List<Data> from data1 which has ids in dataIds
How should be my approach? I'va guava in my classpath so can go with guava's Functional approach if comparable in performance/efficiency .
Unless all you want to do is iterate through the result once or you need a reusable live filtered view, you probably want a non-view list containing the matches. Creating a List or Set to store the result and then iterating through the data list and adding matches is a perfectly good approach and easy to understand!
List<Data> result = Lists.newArrayList();
for (Data data : data1) {
if (dataIds.contains(data.getId()))
result.add(data);
}
I see your Data class implements an Identifiable interface. Given that, you could create a Function<Identifiable, Integer> that gets the ID... Identifiables.getIdFunction() or something. This is nice because it'd likely be useful in various other places (I talk about that approach in a blog post here). With that in place, doing this with Guava would be fairly simple as well:
Predicate<Identifiable> predicate = Predicates.compose(
Predicates.in(dataIds), Identifiables.getIdFunction());
List<Data> filtered = Lists.newArrayList(Iterables.filter(data1, predicate));
This is basically functionally equivalent to the first example, but seems like it'd be harder to understand. Since there isn't any clear benefit to doing this (unlike in a situation where you want to just use the live view), my recommendation would be to just go with the first.
How about
Collections2.filter(
data1,
new Predicate<Data>() {
public boolean apply(Data d) {
return dataIds.contains(d.getId());
}
}
)
p.s. remember not to overcomplicate things, unless truly necessary.
With LambdaJ you could write:
List<Data> result = extract(data1, on(Data.class).getId());
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)