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);
}
Related
I have an issue with mapping HQL query to complex DTO. By complex DTO I mean DTO that composites another DTOs / collection DTOs. I tried to find solution but didn't find anything that can suit my requirements. For instance there is a DTO (I omit properties for simplicity):
public class Consignment {
private List<OrderData> orderData;
private List<AttributesData> attributesData;
private CostData costData;
public Consignment(List<OrderData> orderData, List<AttributesData> attributesData, CostData costData) {
//setting fields
}
}
The HQL lets to create DTO object through constructor by passing columns from result set as parameters. Is it possible to create subqueries or smth. Else to fetch data in collection and then pass it as arguments in main DTO? It looks that it is impossible but maybe I missed something.
Otherwise there is only the way to do that is to fetch data in separate HQL queries and then create main DTO as plain Java object. If anyone has alternative ideas how to do that - please share your ideas.
You can fetch other data in the same query like this:
FROM Consignment cons JOIN FETCH cons.orderData ord
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for you example could look like this
#EntityView(ConsignmentEntity.class)
interface Consignment {
List<OrderData> getOrderData();
List<AttributesData> getAttributesData();
CostData getCostData();
}
#EntityView(OrderDataEntity.class)
interface OrderData {
// attributes of OrderDataEntity that you need
}
#EntityView(AttributesDataEntity.class)
interface AttributesData {
// attributes of AttributesDataEntity that you need
}
#EntityView(CostDataEntity.class)
interface CostData {
// attributes of CostDataEntity that you need
}
Querying could look like this
List<Consignment> dtos = entityViewManager.applySetting(
EntityViewSetting.create(Consignment.class),
criteriaBuilderFactory.create(em, ConsignmentEntity.class)
).getResultList();
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 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()
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.