hibernate order by nested entity field with possible null values - java

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

Related

Problems mapping Hibernate entities - native query containing left join with condition

This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id

Join tables in Hibernate

I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html

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.

Specifying criteria for child table in One To Many Relationship

I have two entity objects (A and B) that have a One-To-Many relationship. I am using JPA (Hibernate) to join these tables and query them for a specific result set, but the criteria I specify for the child table (B) are not applied when fetching my results. This is how I have defined the query:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> query = builder.createQuery(A.class);
Root<A> a = query.from(A.class);
Join<A, B> abJoined = a.join(A_.b);
query.distinct(true)
.where(builder.and(
builder.equal(a.get(A_.id), id),
builder.equal(a.get(A_.active), 1),
builder.equal(a.get(A_.location), 1011),
builder.equal(a.get(A_.status), "Pending"),
builder.equal(abJoined.get(B_.status), "Not Moved"),
builder.greaterThan(abJoined.get(B_.detailId), 0)
));
When I call entityManager.createQuery(query).getResultList(); I get one instance of entity 'A', but when I try to access 'B' through 'A' a.getB() the two criteria that I had specified for abJoined are not applied and I get all instances of 'B' that are joined to 'A'. Is there something more I need to do to get these criteria applied? If the criteria cannot be applied, is there a recommended method for removing the corresponding instances of 'B' from the result set?
I can provide more code or other details if necessary.
The query is used to select which A entities must be returned by the query. But the A entities will never be partial entities containing only a subset of their Bs. They will always be complete instances of A, reflecting the state of what A is in the database, and which B this A is related to.
BTW, even if this was possible (it's not, and explicitely forbidden by the JPA spec), your query would at least have to load the Bs as well, using a fetch join. Otherwise only the state of A is returned by the query, and the linked Bs are loaded lazily.
But in JPA, for a #OneToMany relationship, Query is fired first on the master, with all the Criteria and fetches just master records.
Later Individually the query is fired, BUT BUT BUT......the B table criteria will not contain all the conditions, but only one condition i.e. just the primary key VALUE of master records fetched in i.e just with one condition "b.A_id=[primarykeyfetched from master]"
SOLUTION is simple
Make an Independent class, in which we require the fields of #OneToMany relationship
STEP 1
public class AB {
private A a;
private B b; //#OneToMany with A in the Entity class definitions
public AB(){}
public AB(A a, B b){ ///Very Important to have this constructor
this.a=a;
this.b=b;
}
getter setter....
}
STEP 2
CriteriaQuery<AB> q = criteriaBuilder.createQuery(AB.class);
Root<A> fromA = criteriaQuery.from(A.class);
List<Predicate> predicates = new ArrayList<Predicate>();
Join<A,B> fromB = fromA.join("b", JoinType.INNER);/*"b",fieldName of B in entity Class A*/
criteriaQuery.select(criteriaBuilder.construct(AB.class,A,B));//looks AB's 2 param constr.
predicates.add(criteriaBuilder.equal(fromA.get("name"), filter.getName()));
predicates.add(criteriaBuilder.equal(fromB.get("salary"), filter.getSalary()));
..........
List<AB> ABDataList = typedQuery.getResultList();
for(AB eachABData :ABDataList){
....put in ur objects
obj.setXXX(eachABData.getA().getXXX());
obj.setYYY(eachABData.getB().getYYY());
}
This will give all the results applying all the criteria to master table A, and comparing the primary key of Table B instead of foreign key.

JPA : Is there any way to run a simple SELECT statement that only access a few columns?

I'm new to JPA so forgive me if my question seems silly.
We have used JPA in our project. I see that every entity object has a direct mapping with a table and each row in the table is an object of that entity type.
But, suppose I only want to access one or two columns of a table, how do i go about doing it ? The reason I'm asking is because of the task i have in hand.
There are two tables. The first table has everything set up with JPA so that each row can be cast into an object type. The first table has a column that is referenced in the second table i.e. say, table A has column CLOTH_ID and Table B has columns CLOTH_ID and CLOTH_DESCRIPTION. CLOTH_ID is used in both Table A and B; But B has the CLOTH_DESCRIPTION columns which corresponds to CLOTH_ID.
I'm displaying Table A in my webpage but I also need to display : CLOTH_DESCRIPTION in my webpage. Is there a JPA oriented way to do this or Am i better off using regular JDBC to extract the CLOTH DESCRIPTION values ?
I assume you have the following setup:
#Entity
#Table(name="A")
class A {
#ManyToOne
#JoinColumn(name="CLOTH_ID")
private B cloth;
//...
}
#Entity
#Table(name="B")
class B {
#Id
#Column(name="CLOTH_ID")
private int id;
#Column(name="CLOTH_DESCRIPTION")
private String description;
//...
}
If you don't... you're doing it wrong (i.e. it is not idiomatic JPA usage). You have the following options:
Simply fetch A
In this case #ManyToOne relationship will be fetched eagerly by default as well. Then simply call in Java:
a.getCloth().getDescription()
Prefer this approach as it is the simplest and most idiomatic unless the number of columns in B is huge.
Use JPA query with custom columns:
SELECT a, a.b.description
FROM A a
WHERE a.id = :id
In this case the query returns List<Object[]>, where Object[] actually contains two elements: A and String.
Same as above but with custom DTO:
class Adto {
private final A a;
private final String description;
public Adto(A a, String description) {
this.a = a;
this.description = description;
}
}
And slightly modified query:
SELECT new Adto(a, a.b.description)
FROM A a
WHERE a.id = :id

Categories

Resources