jpql, fetching row with nullable variable - java

#Entity
class A{
#OneToOne(optional=true)
#JoinColumn(foreignKey = #ForeignKey(name = "FK_USER"), nullable = true)
B b;
..other fields...
}
#Entity
class B{
....
}
I want to fetch rows of table A.
I'm making jpql request
select new ADTO(b,..other fields(don't necessarily all)...) from A
(ADTO - data transfer object)
And it's works, but just when A::b not null.
If A::b is null this row doesn't selects.
And if i Have another request, it works good, even if rows content null A::b
select new ADTO(..filds without A::b...) from A
How can i select row with nullable A::b?

To not getting confused with aliases and entity names, I rename your Entities A and B to Parent and Child then it would be queried with an outer join like this:
select new ParentDTO(c, p.otherfield) from Parent p left join p.child c

Related

Fetching a lazy OneToOne entity fetches every other OneToOne entities within the same object

Using Entity entity = hibernateTemplate.get(Entity.class, id); when I hit a entity.getChild() which is a OneToOne relation, every other OneToOne relations are loaded as well. I use hibernate 5.4.1-Final.
I use bytecode enhancement as below :
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>false</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
A.java
#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "ID_A")
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private B b;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private C c;
...
getters/setters
...
B.java
#Entity
#Table(name = "B")
public class B {
#Id
#Column(name = "ID_A")
private String id;
}
C.java
#Entity
#Table(name = "C")
public class C {
#Id
#Column(name = "ID_A")
private String id;
}
So when I do
A a = hibernateTemplate.get(A.class, "100");
// triggers an Hibernate query only on A entity. The B and C aren't fetched => OK
// Hibernate: select a0_.ID_A as ID_A_27_0_ from A a0_ where a0_.ID_A=?
a.getB(); // ERROR : triggers two queries : one on C and one on B
// Hibernate: select c0_.ID_A as ID_A _26_0_ from C c0_ where c0_.ID_A =?
// Hibernate: select b0_.ID_A as ID_A _13_0_ from B b0_ where b0_.ID_A =?
Even if I fetch the B in an HQLQuery, I still have a query to C :
Query<A> queryA = hibernateTemplate.createHQLQuery("from A a join fetch a.b where a.id=:id", A.class);
queryA.setParameter("id", "100");
A a = queryA.uniqueResult(); // triggers an inner join
// Hibernate: select a0_.as ID_A1_27_0_, b1_.ID_A as ID_A1_13_1_ from A a0_ inner join B b1_ on a0_.ID_A=b1_.ID_A where a0_.ID_A=?
a.getB(); // KO -> triggers a query to select C !
// Hibernate: select c0_.ID_A as ID_A1_26_0_ from C c0_ where c0_.ID_A=?
I've tried to do a double mapping (OneToOne with mappedBy specified) without success.
The PK of B and C are the same as A.
I expect the a.getB(); not to trigger the fetch of C. Is this a hibernate bug ? I can't find anything regarding this behaviour in their documentation.
Is my mapping correct ?
It seems that it works as designed :) b and c belong to the same default "LazyGroup". If any of b or c needs to be loaded, the whole group will.
Quote from the bytecode enhancer documentation :
Lazy attributes can be designated to be loaded together and this is
called a "lazy group". By default, all singular attributes are part of
a single group, meaning that when one lazy singular attribute is
accessed all lazy singular attributes are loaded. Lazy plural
attributes, by default, are each a lazy group by themselves. This
behavior is explicitly controllable through the
#org.hibernate.annotations.LazyGroup annotation.
Just add the #LazyGroup("b") on the b field, and #LazyGroup("c") on c and it should work as expected : b will be loaded only on getB(), anc c on getC().
More on this here and here.

JPA: The issue when deleting orphan grandchild elements

I have three classes, with each having a table:
Class A {
long id;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
B b;
}
Class B {
long id;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
C c;
}
Class C {
long id;
}
There is an entry in each of tables. The link between table B and C is based on the foreign keys. If I set A.b as null, and then update the table for the class A, it first deletes the entry of table for class B, and then the entry of table for class C, which causes a violation exception:
delete from B_table where id=? [23503-176]];
nested exception is:
org.hibernate.exception.ConstraintViolationException: could not
execute statement
Any ideas?
When having such deep hierarchies you have to "chase the pointers". When you set A.b to null, JPA correctly tries to delete the B entity in the table but you get the constraint violation because it still has a reference on C. You have to also set to null B.c first and according to your setting C will be removed from the table, along with the B.c reference (FK)
The deletion of B is implicitly applied by JPA during flush; you did not call a delete yourself so the cascade.ALL in B is not in effect. If you have a FK with cascade delete in database level then theoretically it might work but the order that the orphan removal is applied is implementation dependent and JPA does not recommend to rely on it.

hibernate order by nested entity field with possible null values

I've stumbled upon a problem with Hibernate. I've 2 entities - let's say A and B like so (Entity/Table annotations ommited):
class A {
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
class B {
#Column(name = "name")
private String name;
}
Now, I'm trying to query all A entities and ordering them by name field of B's entity like so:
SELECT q FROM A AS q ORDER BY q.b.name asc nulls last
The problem is, there are rows in A's table having null foreign-key (b is null) - in result the aforementioned query returns only rows that don't contain null in b field, and I'd like to have them all.
I guess hibernate joins the table without using LEFT JOIN (OUTER JOIN?) resulting in null values being skipped.
Is there any way to change this behaviour? It would be great, if I could solve it by using annotations in entity classes, because the query-generating mechanism is pretty locked up.
You can use CriteriaBuilder and set alias on entityRoot
Root<A> entityRoot = criteriaQuery.from(A);
entityRoot.join("b", JoinType.LEFT).alias("b");
criteriaQuery.select(entityRoot)
.orderBy(criteriaBuilder.asc(entityRoot.get("b").get("name"))
;
you can use criteria query for this but you will have to create session while using that, it is simpler to access database using criteria:
Criteria criteria = session.createCriteria(A.class)
//create alias of your other class to provide ordering according to foriegn key
criteria.createAlias("foreignkey","keyin table A(eg..b)");
criteria.addOrder(Order.asc(b.name));
List list = criteria.getlist();
hope this helps

Eager loading a lazy child collection

I have data model similar to this
class Office
{
#ManyToOne
#JoinColumn(name = "cafeteria_id")
Cafeteria cafeteria;
}
class Cafeteria
{
#OneToMany(mappedBy="cafeteria")
#LazyCollection(value=LazyCollectionOption.EXTRA)
List<Chair> chairs;
}
class Chair
{
#ManyToOne
#JoinColumn(name = "cafeteria_id")
Cafeteria cafeteria;
}
I have a JPQL query like this
select o from Office o where o.cafeteria.someThing = ?
Above query works fine but in one case I would like a query which could eagerly load all the chairs(o.cafeteria.chairs) as well. How should I modify query to eagerly fetch all chairs?
Use fetch attribute in OneToMany annotation. You can map the same relationship as many times as you want:
class Cafeteria {
#OneToMany(mappedBy="cafeteria")
#LazyCollection(value=LazyCollectionOption.EXTRA)
List<Chair> chairs;
#OneToMany(fetch = FetchType.EAGER, mappedBy="cafeteria")
List<Chair> eagerlyLoadedChairs;
}
And then you can use any of them:
// Lazy loading
select o from Office o inner join o.cafeteria c inner join c.chairs ch where c.someThing = ?
// Eager loading
select o from Office o inner join o.cafeteria c inner join c.eagerlyLoadedChairsch where c.someThing = ?
You have two options, either you change the fetch type from Lazy to Eager, but this will always load your List of Chair every time you load an object from Cafeteria.
like this:
#OneToMany(mappedBy="cafeteria", fetch=FetchType.EAGER)
#LazyCollection(value=LazyCollectionOption.FALSE)
List<Chair> chairs;
OR In your Service that call this query, after calling it, you simply load the Chair list in your specific use case, check more about Initializing Collections in Hibernate
Hibernate.initialize(cafeteria.getChairs());

JPA / EclipseLink: Joined WHERE query does not give expected result

Now that's very confusing... I have a JPA entity Order that references an entity User. The User can be either buyer or seller of the Order.
Because both buyer and seller can enter additional information for an order, I moved that to an extra entity OrderUserData. There might or might not be a corresponding OrderUserData object, but IF one exists, the user should only be able to see the entry they created (based on USER_ID) and not the one of the other party.
The entities look like this:
#Entity
#Table(name = "T_ORDER")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "SELLER_ID")
private User seller;
#ManyToOne
#JoinColumn(name = "BUYER_ID")
private User buyer;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
private List<OrderUserData> userData = new ArrayList<>();
//..
}
--
#Entity
#Table(name = "T_ORDER_USERDATA")
public class OrderUserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "ORDER_ID")
private Order order;
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
private String comment;
//...
}
( User is not very exciting, just ID and basic name fields )
Now when I'm trying to select the appropriate data to display in the website, I have a problem:
String qry = "SELECT o FROM Order o LEFT JOIN o.userData ud "
+ " WHERE (o.seller.id = :userId OR o.buyer.id = :userId)"
+ " AND ( ud.user IS NULL OR ud.user.id = :userId )";
TypedQuery<Order> query = em.createQuery(qry, Order.class);
query.setParameter("userId", userId);
Let's say I execute this, setting userId to 2:
My Database looks like this:
ORDER
=====
ID SELLER_ID BUYER_ID
1 1 2
2 2 3
3 3 1
ORDER_USERDATA
===============
ID ORDER_ID USER_ID COMMENT
1 1 1 Comment that only user 1 should see
2 1 2 Comment that only user 2 should see
But unlike you would expect, when executing the above query, both records are included in the userData list! It seems like JPA is executing two queries (despite the EAGER fetch) and ignoring the WHERE on the second one. Why is that? And what other solution than to loop through the userData list on Java level and kick out the entry that the appropriate user should not see?
There is no way to load OrderUserData objects inside an Order object using a query. Maybe you're confusing the ORM functionality, mapping rows in the database to Java objects, with the query functionality.
Mapping means 1-1 correspondence between rows and objects, hence Order objects always contain all OrderUserData objects for each OrderUserData row related to Order rows.
The fetch type is just a loading strategy, determining at which time are the related objects fetched, as soon as the containing object is loaded (EAGER) or as soon as the contained objects are accessed (LAZY).
You can obtain your list issuing a query on OrderUserData objects with the proper filters and getting Order objects from each of them, i.e.
SELECT ud FROM OrderUserData ud WHERE (ud.order.seller.id = :userId
OR ud.order.buyer.id = :userId) AND ( ud.user IS NULL OR ud.user.id =
:userId )
your query seems to work well as it selects properly Order entity. Then JPA fetch all the OrderUserData child of the selected Order : that's because oneToMany join is not filtered.
I don't think it is possible to modelize pre-filtered oneToMany with eclipseLink (like Hibernate #FILTER), so you should remove it and map orderUserDataId field only. Then you can fetch your entities in 1 query, but they will not be linked
SELECT o, ud FROM Order o, o.userData ud WHERE (o.seller.id = :userId OR o.buyer.id = :userId) AND ( ud.orderUserDataId = o.id and (ud.user IS NULL OR ud.user.id = :userId) )";
On the other hand, if the oneToMany is required by other use cases, then you can create 2 different Order entities :
1 "OrderLight" without the oneToMany
1 "OrderFull" with the oneToMany, derived from OrderLight.
While user3580357 and remigio have already given the correct answer as to why this doesn't work, might I suggest that you create a view on database level.
Something like (might need to be adapted for your needs or RDBMS):
CREATE OR REPLACE VIEW
ORDER_WITH_USERDATA
AS
SELECT o.*, oud.*
FROM ORDER o
LEFT JOIN ORDER_USERDATA oud
ON o.id = oud.order_id
This will essentially give you two different "logical" records for every order. You can then create an additional JPA entity that works on this view and do your SELECT/WHERE... without needing to (LEFT)JOIN at all.

Categories

Resources