Hibernate criteria, selecting from entity A where someId in B - java

Given entity classes:
class A { String name ... }
class B { #OneToOne A a = ... ; String gender; ... }
I am able to create a hibernate criteria and query it and all of it's associations.
However, B is not referenced from A at all. It's populated in a separate fashion and I would like to keep it as such.
Is there a way to perform a hibernate criteria query on A, and say, where this also is in B's relation, or where it is in B's relation and some field in B is this and that?
Here is some non-working pseudo code:
criteria(A.class).add(
Restrictions.eq("name", "something")
).createAlias(
B.class, "..."
).add(
Restrictions.eq("gender", "Female")
);
Note, I would prefer not having to create a collection on A containing B's and using addAll.
I might consider adding a dead reference though, meaning something that is never intended to be accessed or updated but needed for hibernate to pull this off. It could be something like saying that this is maintained by another table.

You can use a subquery, as follows:
// create a 'DetachedCriteria' query for all the items in 'B'
DetachedCriteria dc = DetachedCriteria.forClass(B.class).setProjection(
Projections.projectionList().add(Projections.property("propertyInB"))
);
// then: search for A.id by adding 'dc' as asubquery:
session.createCriteria(A.class).add(
Subqueries.propertiesIn( new String[]{"propertyInA"}, dc)
).list();
This is roughly equivalent to: 'SELECT * FROM A a WHERE a.id in(SELECT id FROM B).
I hope my SQL is valid ;) .
See hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/querycriteria.html

Related

How do Spring (Boot) specifications work when I need joins on multiple occasions?

so I have an SQL query like:
SELECT stuff
FROM a
JOIN x ON a.id = x.a_id
JOIN (SELECT ... FROM a join b ON ...) as foo
WHERE foo.c = 'bar'
AND foo.d = true
AND ...
Though I want to convert that to Java code using, for example, Spring Specifications. I know how to use them in general but I've read, for example, that subqueries are only allowed in the WHERE part? Is that right?
But nonetheless: how would I make that SQL query in Specifications? So far I've used a special Specification class where I do stuff like:
public static Specification<Foo> hasStuff(long stuffId) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get(Foo_.stuff), stuffId);
}
But this wouldn't work for more complex situations, right? Cause if I split up the line with
foo.c = 'bar'
and
foo.d = true
into different methods, I would do the join twice, right?
Edit: Oh right, and another important thing. If I want to aggregate all the checks of the WHERE, how can I do that? Previously I just created a List<Specification<SomeClass>> but since I now need to use joins, I can't just put SomeClass in the generics since my result will be arbitrary.
You can use nativeQuery = true parameter to the #Query.
If you don't add the nativeQuery = true parameter to the #Query annotation in a Spring Repository, the query will be considered as written in JPQL.
#Query(
value = "SELECT stuff
FROM a
JOIN x ON a.id = x.a_id
JOIN (SELECT ... FROM a join b ON ...) as foo
WHERE foo.c = 'bar'
AND foo.d = true
AND ...",
nativeQuery = true)
Collection<Entity> findAllRecords();
From the JPQL you can check here:
Subqueries may be used in the WHERE or HAVING clause.
So add natuveQuery attribute with true value. It will help you.

Hibernate is making extra SQL statement with #ManyToOne and #Lazy fetching object

I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).

JPA NamedQuery Select specific column and return class type

I am using JPA, I have the basic named query like this
#NamedQuery(name="Manifest.findAll", query="SELECT m FROM Manifest m")
When I call this,
entityManager.createNamedQuery("Manifest.getManifestInfo").getResultList()
I get the object back in a list like this
[{id:"1", name:"foo"},{id:"2", name:"bar"}]
But then I have another named query where I just get the names
#NamedQuery(name="Manifest.getManifestName", query="SELECT m.name FROM Manifest m")
When I run that query I get
[["foo"],["bar"]]
How can I get the second query to return the object type so its liek
[{name:"foo"},{name:"bar"}]
I made this simpler to show what I am trying to do here, the actual table has more columns but it is the same principal.
How can I return the object type?
The feature for that use case in JPA is an Entity Graph.
Define it on your entity like this:
#NamedEntityGraph(name="nameOnly", attributeNodes={
#NamedAttributeNode("name")
})
#Entity
public class Manifest {
...
}
You'd leave your query itself unchanged, but apply the Entity Graph to the query before running it, either as a Load or a Fetch Graph (see link above for explanation):
EntityGraph<EmailMessage> eg = em.getEntityGraph("nameOnly");
entityManager.createNamedQuery("Manifest.getManifestInfo").setHint("javax.persistence.loadgraph", eg).getResultList()

query that should return entities qith specific related entities

Generally my questian is very simple I think, nevertheless I couldn't find a good solution. Let's say I have an Entity class called MyEntity which has a OneToMany relation to an Entity class called EntityAttribute, so it has a list or set attributes with objects of this class. EntityAttribute has an attribute name of type String.
Now I want to implement a method which takes attribute names and returns all entities that contains for each name in attributes at least one attribute with that name. Although this sounds very straight forward, the only solution I found was to execute a query for each attribute name and merge the results like this:
for (String name : attributeNames) {
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
cq.where(attributeJoin.get(EntityAttribute_.name).equals(name));
cq.select(entity);
... // get result list and merge
}
This code isn't tested but generally is one solution. This doesn't seem to be the most efficient one.
Another solution I testet was to use multiple joins like
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
List<Predicate> predicates = new ArrayList<>();
for (String name : attributeNames) {
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
predicates.add(attributeJoin.get(EntityAttribute_.name).equals(name));
}
cq.select(predicates.toArray(new Predicate[] {}));
... // get result list
This seems to be more efficient, but it iterates over the cartesian products... So it's highly inefficient.
I could also imagine to nest subqueries, but this seems to be very complicated.
The question simply is: What is the best solution for this problem? Afterwards I would also like to implement AND and OR, so I can query for all entities with attributes x and (y or z) or something like that. But for now I only want to make the AND case.
Thanks in advance
Maybe you could achieve this using in clause + group by + having + count, if I understand your question correctly. The idea is to count the number of matches for each MyEntity. If the count is equal to the number of attributes passed in, it means that each of them was found for that entity (assuming they are unique). In JPQL the query would look like this:
select e from MyEntity e join e.attributes a
where a.name in (:attributeNames)
group by e having count(*) = :attributeCount
where :attributeCount is the value of attributeNames.size().
I'm not very familiar with the criteria API, but you can experiment with something like this:
...
cq.groupBy(entity);
cq.having(cb.equal(cb.count(entity), attributeNames.size()));
// TODO: add IN clause
...

How to add property counted in DB to #Entity class?

I have an Entity. And sometimes I need this object also contains some value, call it 'depth'. The query may look like 'select b.id, b.name, b..., count(c.id) as depth from Entity b, CEntity c where ...'. So I've created class NonHibernateEntity which extends Entity. And then results of query written above are perfectly stored as List of NonHibEntity, which consists of all the fields of Entity (as they are extended), and property 'depth'. I make it by setting aliasToBean results transformer: .setResultTransformer(Transformers.aliasToBean(NHEntity.class)).
But, it is annoying and inconvenient - to specify all the aliases of all the needed fields.
And then, if I want to save one of this object to DB - session.saveOrUpdate((Enity)nHibEntity) - there are an exception about nHibEntity isn't Hibernate Entity.
I heard about storing 'entity' as field in NonHibEntity (aggregation, not inheritance). But it seems this is rather inconvenient too.
What do you think? What is an appropriate solution?
A Formula column mapping may be suitable for your needs. I would try this first.
If this causes performance issues as you fear, you might try a mapped class hierarchy with this field only in the child, and mapping both to the same table. Not sure this will actually work though...
As a last resort, do what you've got now using a non-mapped class, but with the entity as a field in your other class - aggregation instead of inheritance as you say, and make sure there's a way of retrieving the mapped entity from the unmapped one so that you can save. It be sensible to make it a Decorator, so that it's both a subclass and aggregate and you can continue to ignore the distinction in much of your code.
With the non-mapped subclass and/or aggregate, however, you'll have to pull out the entity in order to save.
If somebody want to know - I solved this problem in such way:
just made this calculated field as #Transient, and then
List<BaseEntry> result = new ArrayList<BaseEntry>();
Iterator it = session()
.createQuery("select b, (select count(p.id) as depth from BaseEntry p " +
" where ... ) as d" +
" from BaseEntry b " +
" where ... ")
.list().iterator();
while ( it.hasNext() ) {
Object[] row = (Object[]) it.next();
BaseEntry entry = (BaseEntry) row[0];
Long d = (Long) row[1];
entry.setD(d);
result.add(entry);
}
return result;
It works good, and seems that it can be easily supported in future

Categories