I have two Objects, Entries and Samples. Each entry has an associated set of Samples, and each Sample has a vote associated with it. I need to query the database for all Entries, but for each Entry I need the associated set of Samples sorted according to their vote attribute:
public Class Entry{
Set<Sample> samples;
}
public Class Sample{
int vote;
}
I tried to sort the list of Samples after I had performed the query, but this turned out to be a mess because can't cast between a hibernate set and a java set. Can somebody help me alter my query to have the result I need?
List<Entry> entries = jpaTemplate.find("from Entry");
I found an embarrassingly simple solution to this problem. There is an #OrderBy JPA annotation that works perfectly:
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#OrderBy("votes DESC")
public Set<Sample> getSamples() {
return samples;
}
According to the hibernate faq:
There are three different approaches:
Use a SortedSet or SortedMap, specifying a comparator class in the sort attribute or or . This solution does a sort in memory.
Specify an order-by attribute of , or , naming a list of table columns to sort by. This solution works only in JDK 1.4+.
Use a filter session.createFilter( collection, "order by ...." ).list()
Related
Let's say I have a Person JPA entity:
#Entity
public class Person {
#Id
private Long id;
#Column(name="name")
private String name;
#Column(name="age")
private Integer age;
#Column(name="hobbies")
private List<String> hobbies;
public Person() {};
}
Using a Criteria Query, is it possible to retrieve a List<Person>, but only include each Person's name?
Looking at Java Persistence with Hibernate, I see that there's a Criteria#setResultTransformer method.
I think that I could transform my results to a ReducedPerson class that only contained a name. However, I'd like to select only the Person's name, but still get Person objects back.
Is this possible?
You're correct, you'll need to set a ResultTransformer. For what I know in this area of hibernate, which is not much, the transformers provided by Hibernate are strict and will fail if a value is missing, so I think you'll need to create your own instance of ResultTransformer. I would suggest to look at the code from AliasToBeanResultTransformer and make a lenient version of it.
You can do this using Constructor Expressions:
http://en.wikibooks.org/wiki/Java_Persistence/JPQL#Constructors
You can use your existing Person class (instances returned from your query being, of course, unmanaged) but would however need to a constructor taking the name.
SELECT NEW sample.Person(p.name) FROM Person p
Single Projection
You use an existing ResultTransformer like this
Criteria query = session.createCriteria(Person.class)
.setProjection(Projections.property("name").as("name"))
.setResultTransformer(Transformers.aliasToBean(Person.class));
List personNames = query.list();
Multiple Projections
The example List above now contains only Person objects with their name. Regullary, you want also to retrieve the id for the persons at least. You can setup multiple properties with this
ProjectionList colProjection = Projections.projectionList();
colProjection.add(Projections.property("id"), "id");
colProjection.add(Projections.property("name"), "name");
Criteria query = session.CreateCriteria(Person.class)
.setProjection(colProjection)
.setResultTransformer(Transformers.aliasToBean(Person.class));
List persons = query.list();
Key in this approach is to set the same aliases as their original property names, so we dont need to build our own ResultTransformer.
Is it possible to get as a result from hibernate directly this...
List<Map<Sth, List<SthLog> list;
when classes are mapped in such a way (not bidirectional)?:
#Entity
public class SthLog {
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private Sth sth; }
I know that this maybe seems to be a silly question, because normally you can add bidirectional mapping, but in this case I can change entities definition and I wonder if I can get required structure directly from hibernate.
Just don't expect Hibernate to do everything for you. Transforming a List into a Map (or a Guava Multimap, which is easier and more appropriate here) is a matter of 3 lines of codes:
// get all the SthLog with their sth
List<SthLog> list =
session.createQuery("select s from SthLog s inner join fetch s.sth").list();
// index them by their sth
ListMultimap<Sth, SthLog> result = ArrayListMultimap.create();
for (SthLog sthLog : list) {
result.put(sthLog.getSth(), sthLog);
}
I'm observing what I think is an unexpected behaviour in JPA 2 when fetching a list attribute with a criteria query.
My query is as follows (an extract of it):
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<MainObject> c = b.createQuery(MainObject.class);
Root<MainObject> root = c.from(MainObject.class);
Join<MainObject, FirstFetch> firstFetch = (Join<MainObject, FirstFetch>) root.fetch(MainObject_.firstFetch);
firstFetch.fetch(FirstFetch_.secondFetch); //secondFetch is a list
c.select(root).distinct(true);
(So let's say I'm fetching a list as a property of the property of an object.)
The thing is when the query returns multiple results, secondFetch values are duplicated as many times as rows are returned. Each firstFetch should have just one secondFetch but has n instead.
The only particularity i see in this case is all MainObjects happen to have the same FirstFetch instance.
So my guess is the join is being crossed, which is normal, but then JPA fails to assign its secondFetch object to each one of the firstFetchs.
Mappings shouldn't be too special, the're more or less like this
#Entity
#Table(name="mainobject")
public class MainObject{
//...
private FirstFetch firstFetch;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="mainObject_column")
public FirstFetch getFirstFetch() {
return firstFetch;
}
}
and
#Entity
#Table(name="firstFetch")
public class FirstFetch{
//...
private List<SecondFetch> secondFetch;
#OneToMany(mappedBy="secondFetch")
public List<SecondFetch> getSecondFetch() {
return secondFetch;
}
}
& finally
#Entity
#Table(name="secondFetch")
public class SecondFetch {
//....
private FirstFetch firstFetch; //bidirectional
#ManyToOne
#JoinColumn(name="column")
public FirstFetch getFirstFetch() {
return firstFetch;
}
}
I've been looking for some sort of distinct sentence to apply to the fetch but there's none (would have been a 'patch' anyway...)
If i change
List<SecondFetch>
for
Set<SecondFetch>
i'll get the expected result thanks to Sets' Keys, so I do feel this is kind of a misbehaviour in JPA's lists.
I'm not an expert, though, so i could perfectlly be making some mistake in the mappings or query.
Any feeback is very welcome to help clear this out.
Thanks.
I had the exact same problem though I was using JPA criteria API to do the query.
After some research I found a solution which you already mentioned (but was not available, since your not using criteria API): Using distinct.
With JPA criteria it would look like this:
CriteriaQuery<FirstFetch> query = cb.createQuery(FirstFetch.class);
Root<AbschnittC> root = query.from(FirstFetch.class);
root.fetch(FirstFetch_.secondFetch, JoinType.LEFT);
query.distinct(true);
Without using query.distinct(true); the resultset was multiplied with the amount of objects in the secondFetch list.
Hibernate does have something like DISTINCT_ROOT_ENTITY which sound more adequate than just setting a query distinct. But I have not further investigated this. I am also using Hibernate as the JPA provider. Maybe setting the query distinct in JPA ends up using the same code as Hibernates DISTINCT_ROOT_ENTITY would?
We have the following JPA class:
#Entity
class Supplier {
// ... id property etc.
#OneToMany
#OrderBy("someProperty")
private List<Region> regions;
}
This works fine in the normal case. However, we have some multi-lingual data where the values are stored in properties like nameEn, nameDe, nameZh. The exact property to use depends on the logged in user. For example, a German speaking user should see the regions as if it had been annotated with #OrderBy("nameDe").
How can I achieve this?
I am aware I could sort the collection in my code after it has been loaded, but this makes pagination of the results quite difficult.
You could sort them in java. Possibly in a getter:
List<Region> getRegions(){
sorted = new List<Regions>(regions);
Collections.sort(sorted, new RegionComparator(getUserLanguage()));
return sorted;
}
I have a class called SynonymMapping which has a collection of values mapped as a CollectionOfElements
#Entity(name = "synonymmapping")
public class SynonymMapping {
#Id private String keyId;
//#CollectionOfElements(fetch = FetchType.EAGER)
#CollectionOfElements
#JoinTable(name="synonymmappingvalues", joinColumns={#JoinColumn(name="keyId")})
#Column(name="value", nullable=false)
#Sort(type=SortType.NATURAL)
private SortedSet<String> values;
public SynonymMapping() {
values = new TreeSet<String>();
}
public SynonymMapping(String key, SortedSet<String> values) {
this();
this.keyId = key;
this.values = values;
}
public String getKeyId() {
return keyId;
}
public Set<String> getValues() {
return values;
}
}
I have a test where I store two SynonymMapping objects to the database and then ask the database to return all saved SynonymMapping objects, expecting to receive the two objects I stored.
When I change the mapping of values to be eager (as shown in in the code by the commented out line) and run the test again, I receive four matches.
I have cleared out the database between runs and I can duplicate this problem swapping between eager and lazy.
I think it has to do with the joins that hibernate creates underneath but I can't find a definite answer online.
Can anyone tell me why an eager fetch is duplicating the objects?
Thanks.
I stepped into the same problem - when you set the FetchType.EAGER for a #CollectionOfElements, the Hibernate tries to get everything in one shot, i.e. using one single query for each entry of element linked to a "master" object. This problem can be successfully solved at a cost of N+1 query, if you add the #Fetch (FetchMode.SELECT) annotation to your collection.
In my case I wanted to have a MediaObject entity with a collection of its metadata items (video codec, audio codec, sizes, etc.). The mapping for a metadataItems collection looks as follows:
#CollectionOfElements (targetElement = String.class, fetch = FetchType.EAGER)
#JoinTable(name = "mo_metadata_item", joinColumns = #JoinColumn(name = "media_object_id"))
#MapKey(columns = #Column(name = "name"))
#Column (name = "value")
#Fetch (FetchMode.SELECT)
private Map<String, String> metadataItems = new HashMap<String, String>();
It's generally not a good idea to enforce eager fetching in the mapping - it's better to specify eager joins in appropriate queries (unless you're 100% sure that under any and all circumstances your object won't make sense / be valid without that collection being populated).
The reason you're getting duplicates is because Hibernate internally joins your root and collection tables. Note that they really are duplicates, e.g. for 2 SynonymMappings with 3 collection elements each you would get 6 results (2x3), 3 copies of each SynonymMapping entity. So the easiest workaround is to wrap results in a Set thereby ensuring they're unique.
I have faced this problem and I solved it using
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This clears out the duplicates which are caused by the join made to the child tables.
You could use a SELECT DISTINCT (Hibernate Query Language) clause as follows
SELECT DISTINCT synonym FROM SynonymMapping synonym LEFT JOIN FETCH synonym.values
DISTINCT clause removes duplicate references in Hibernate.
Although both component and value-type collection has its lifecycle bound to the owning entity class, you should declare them in select clause in order to retrieve them. (LEFT JOIN FETCH synonym.values)
ChssPly76's answer is another approach, but does not forget override equals and hashcode method according to Set semantic
regards,
Instead of FetchMode.SELECT with N+1 queries it is better using BatchSize e.q. #BatchSize(size = 200).
DISTINCT and Criteria.DISTINCT_ROOT_ENTITY doesn't help, if you have to fetch more than 1 association. For this case see other solutions: https://stackoverflow.com/a/46013654/548473
I have achieved it via simply add
session.createCriteria(ModelClass.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This help to remove duplicate.