Retrieve many-to-many set and property via HQL - java

I have a classes like this:
class EntryDB {
#Id #GeneratedValue
int id;
#ManyToMany(cascade = CascadeType.ALL)
List<Category> cats;
}
class Category {
#Id #GeneratedValue
int id;
String name;
}
So each entry can be in Zero or more categories. It works fine. But i need to retrive a list of Entries in format entry_id->(cat_id, cat_id, cat_id)
I am trying
select id, cats from EntryDB
but it does not work and i see Exception like:
DEBUG OUTPUT: Hibernate: select entrydb0_.id as col_0_0_, {non-qualified-property-ref} as col_1_0_, category2_.id as id1_, category2_.name as name1_ from my_entry_table entrydb0_ inner join entrydb_category categories1_ on entrydb0_.id=categories1_.id inner join category category2_ on categories1_.id=category2_.id
org.hibernate.exception.SQLGrammarException: could not execute query
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL serve...near 'as col_1_0_, category2_.id as id1_, category2_.name as name1_ from my_entry_table' at line 1
But HQL like
"select cats from EntryDB"
works fine. But i need to know id's of Entries.
Just to use
session.load(EntryDB.class, id).getCats();
is not an option, because the "real" EntryDB is VERY heavy and i just want to know "wich entries are in wich category". It would be very simple, if i could direct access join table, but it's can not be done in HQL.
May be you know some walkaround, but using JDBC to query a join table.

select entry.id, category.id from EntryDB entry
left join entry.cats category

Related

Hibernate - How to select just the foreign key value using Criteria Query without doing a Join?

So there is this similar (almost identical) question: How to select just the foreign key value using Criteria Query? but I need to avoid doing the join.
So I have two tables, one with a FK to the other
(Service)
id
name
account_id
(Account)
id
name
Service class has the FK defined this way:
#Entity
public class Service extends BaseDbEntity implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Account account;
...
and what I want to do is query all the Service's that correspond to a given account, having the accountId, without doing a JOIN, because the id is already in the Service table.
Now, my criteria query looks like this:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Service> criteria = cb.createQuery(Service.class);
Root<Service> root = criteria.from(Service.class);
criteria
.select(root)
.where(cb.equal(root.join("account").get("id"), accountId));
session.createQuery(criteria).getResultStream();
This ends up generating this query:
Hibernate: select service0_.id as id1_3_, service0_.name as name4_3_, service0_.account_id as account_id6_3_ from Service service0_ inner join Account account1_ on service0_.account_id=account1_.id where account1_.id=?
Which doesn't make sense.. it does a join on a field and then just creates a where clause for that field.
If I do:
root.get("account_id")
it just throws an exception saying the field is not available.
What is the correct way to avoid this?
Ok I just found the answer to my question:
Instead of doing
root.join("account").get("id")
I just needed to do:
root.get("account").get("id")
which avoids performing the JOIN.

EntityNotFoundException LEFT JOIN JPA - Hibernate

Exception in thread "main" javax.persistence.EntityNotFoundException:
Unable to find CNPJ with id 00001388000307
I was reading jpa documentation, i read that this exception is thrown when it try to accesse(by the method getReference of EntityManger interface) and the entity doesn't exists
Thrown by the persistence provider when an entity reference obtained by EntityManager.getReference is accessed but the entity does not exist.
I have this entitys: Salesman e CNPJ. It's possible that exists many salesmen with the same CNPJ, in other words, a relationship #ManyToOne.
This relationship is working, OK.
But, when i try to execute the query
select r from Salesman r join fetch r.yearMonth left join fetch r.cnpj
to bring the salesmen with its yearMonth(it's working!) relationship and its CNPJ relationship, throws the Exception, when i try to do a LEFT JOIN, that i mentioned.
When i don't execute a LEFT JOIN, works great, all Salesmen with his CNPJs and nothin of Exceptions, BUUUUT, some salesmen don't have CNPJ nad i have to bring them too, there's a necessity to do a LEFT JOIN.
#Entity
public class Salesman{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JoinColumn(name = "year_month")
#ManyToOne
private YearMonth yearMonth;
private String cnpjS;
#JoinColumn(name = "cnpj")
#ManyToOne
private CNPJ cnpj;
public AnoMes getYearMonth() {
return yearMonth;
}
public CNPJ getCnpj() {
return cnpj;
}
}
#Entity
public class CNPJ {
#Id
#Column(name = "CCG", length = 14)
private String ccg;
public String getCcg() {
return ccg;
}
}
The select generated by Hibernate:
select *
from salesman s
inner join yearmonth y on s.ano_mes = y.id
left outer join cnpj c on s.cnpjS = c.CCG
This consult returns this values, rcnpj is cnpj from Salesman and bcnpj is from CNPJ. Some Salesmen came with CNPJ NULL, i think that it's the problem. I don't think so.
Try to enable hibernate sql logging with <property name="show_sql">true</property> to see how real query (native, invoked on db) looks like, maybe there is a inner join after hibernate processing.
To change it you can try #Column(nullable=true) annotation for your relationship or #Fetch(FetchMode.SELECT) to see how real query will change.
The reason is that you have a reference to cnpj with ID 00001388000307, but such row of cnpj doesn't exist.
You can try to use #NotFound(action=NotFoundAction.IGNORE) to skip such error, but it is recommended to fix your database of handle a exception.
You are using the cnpj column on the table Salesman as both a foreign key and a value column. In that case #staszko032's suggestion using #NotFound(action=NotFoundAction.IGNORE) looks like will work as you expect
But I think you should refactor your model if possible. You could create a CNPJ entity always, in that case you won't have Salesman without a realation to CNPJ and could use INNER JOIN, or use 2 columns, one that will be used when the Salesman doesn't have a concrete related CNPJ (the value as string) and the other when it has (the foreign key)

Spring JPA + Hibernate OneToOne relationships making n+1 queries

I am using Spring Boot, Groovy and JPA/Hibernate to migrate an old application. The only restriction is that the database model must not be changed and I found myself with a weird situation in which a OneOnOne relationship:
Please look at the following model setup:
#Entity
#Table(name='table1')
class Table1 {
#Id
Table1Id id
#Column(name='sequence_num')
Integer seqNum
#Column(name='item_source')
String itemSource
#Column(name='source_type')
String sourceType
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name='key_field_2', insertable=false, updatable=false)
#NotFound(action=NotFoundAction.IGNORE)
// #Fetch(FetchMode.JOIN)
Table2 table2
}
#Embeddable
class Table1Id implements Serializable {
#Column(name='key_field_1')
String key1
#Column(name='key_field_2')
String key2
}
#Entity
#Table(name='table2')
class Table2 {
#Id
#Column(name='key_id')
String keyId
#Column(name='field1')
String field1
#Column(name='field2')
String field2
#Column(name='field3')
String field3
}
My Spock test looks as follows:
def "Try out the JOIN select with Criteria API"() {
given:
CriteriaBuilder cb = entityManager.getCriteriaBuilder()
CriteriaQuery<Object[]> cQuery = cb.createQuery(Object[].class)
Root<Table1> t1 = cQuery.from(Table1.class)
Path<Table2> t2 = t1.get('table2')
Join<Table1, Table2> lanyonLeftJoin = t1.join('table2', JoinType.INNER)
Predicate where = cb.equal(t1.get('itemSource'), 'ABC')
cQuery.multiselect(t1, t2)
cQuery.where(where)
when:
List<Object[]> result = entityManager.createQuery(cQuery).getResultList()
then:
result.each{ aRow ->
println "${aRow[0]}, ${aRow[1]}"
}
}
This configuration successfully generates an INNER JOIN between Table1 and Table2, NOTE that even the constant on the "where" clause is correctly interpreted.
However for some strange reason Table2 gets re-queried for every row returned in the first query.
The output that I see is:
Hibernate:
select
table10_.key_field_1 as key_field_11_3_0_,
table10_.key_field_2 as key_field_22_3_0_,
table21_.key_id as key_id1_5_1_,
table10_.item_source as item_source3_3_0_,
table10_.sequence_num as sequence_num4_3_0_,
table10_.source_type as source_type5_3_0_,
table21_.field2 as field23_5_1_,
table21_.field3 as field34_5_1_,
table21_.field1 as field15_5_1_
from
table1 table10_
inner join
table2 table21_
on table10_.key_field_2=table21_.key_id
where
table10_.item_source=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
// 500+ more of these
As we can see the first query successfully returns all rows from both tables and it is actually the exact query I am looking for. However there is all those unnecessary extra queries being performed.
Is there any reason why JPA would do such thing and is there a way to prevent it??
I have the impression I am missing something very obvious here.
Thanks in advance for your help
Update 1
If I replace
cQuery.multiselect(t1, t2)
for
cQuery.multiselect(t1.get('id').get('key1'), t1.get('id').get('key2'),
t1.get('fieldX'), t1.get('fieldY'), t1.get('fieldZ'),
t2.get('fieldA'), t2.get('fieldB'), t2.get('fieldC') ...)
It generates the exact same inner join query and DOES NOT re-queries Table2 again.
In other words, looks like (at least for this case) I need to explicitly list all the fields from both tables. Not a nice workaround as it can get very ugly very quickly for tables with a lot of fields. I wonder if there is a way to retrieve all the #Column annotated fields/getters without resourcing to a bunch of reflection stuff?
I think I've got it!
#JoinFormula:
Primary Key in Table2 is INT and the field in Table1 that is used as FK is String (I completely missed that! duh!). So, the solution for that was to apply a #JoinFormula instead of a #JoinColumn in the form of:
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumnsOrFormulas([
#JoinColumnOrFormula(formula=#JoinFormula(value='CAST(key_field_2 AS INT)'))
])
#NotFound(action=NotFoundAction.IGNORE)
Table2 table2
This strangely returns a List<Object[]> each item of the List contains an array of 2 elements: one instance of Table1 and one instance of Table2.
Join Fetch:
Following your suggestion I added "join fetch" to the query, so it looks like:
select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
This causes Hibernate to correctly return a List<Table1>
Either with the #JoinFormula alone or the #JoinFormula + "join fetch" hibernate stopped generating the n+1 queries.
Debugging Hibernate code I've found that it correctly retrieves and stores in Session both entities the first time it queries the DB with the join query, however the difference between PK and FK data types causes Hibernate to re-query the DB again, once per each row retrieved in the first query.

Table is specified twice, both as a target for 'UPDATE' and as a separate source for data

I use spring-jpa with hibernate implementation. I use mariadb and I try to do an update subquery
My object structure
#Entity
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long roomId;
#ManyToOne
#JoinColumn(name = "appartment_id")
private Appartment appartment;
}
#Entity
public class Appartment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long appartmentId;
#OneToMany
#JoinColumn(name="appartment_id")
private Set<Room> roomList;
}
update Room r1
set r1.available = :availability
where r1.roomId in
( select r2.roomId from Room r2 JOIN r2.appartment a1 WHERE a1.appartmentId = :appartmentId )
I get this error
java.sql.SQLException: Table 'room' is specified twice, both as a target for 'UPDATE' and as a separate source for data
This is a restriction in MySQL:-
http://dev.mysql.com/doc/refman/5.7/en/update.html
You cannot update a table and select from the same table in a subquery.
There is a fudge you can do sometimes do to hide the sub query in a a further level of sub query that might work. Something like this (not tested):-
UPDATE Room r1
SET r1.available = :availability
WHERE r1.roomId IN
SELECT roomId
FROM
(
SELECT r2.roomId
FROM Room r2
JOIN r2.appartment a1
WHERE a1.appartmentId = :appartmentId
)
Note that your query possibly has an error. In the sub query you are joining the table Room aliased as r2 to a table called appartment on a database called r2. Also your sub query does a JOIN without a join condition.
However you can quite possibly just do the join in the UPDATE statement without the need for a sub query:-
UPDATE Room
INNER JOIN r2.appartment a1
ON Room.roomId = a1.roomId
SET r1.available = :availability
WHERE a1.appartmentId = :appartmentId
A solution will be to select roomIds seperatly and pass them as a parameter.
Select id list
List<Long> roomIdList = entityManager.createQuery(
"""SELECT r2.roomId
FROM Room r2
JOIN r2.appartment a1
WHERE a1.appartmentId = :appartmentId""", Long.class)
.setParameter("appartmentId", appartmentId)
.getResultList();
Pass it as a parameter
int updated = entityManager.createQuery(
"""UPDATE Room r1
SET r1.available = :availability
WHERE r1.roomId IN :roomIds""")
.setParameter("availability", availability)
.setParameter("roomIds", roomIdList)
.executeUpdate();

DOT node with no left-hand-side using HQL with join

I am trying to find the maximum value of a Date column in mySQL DB using hibernate query language with join
#Query("select o.techid, CAST(MAX(o.last_modified) AS DATE)
from com.dw.model.user.User as u
left join com.dw.model.order.Order as o
on u.username=o.techid group by o.techid")
List<User> findUsers();
Model class =
#Entity(name = "orders")
#Scope("prototype")
#Component
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
public class Order {
#Id
private long orderNumber;
private Date last_modified;
I am getting this error :-
Caused by: java.lang.IllegalStateException: DOT node with no left-hand-side!
Can anyone please help me out by telling me how to write this in Hibernate?
Remove your package names. An entity is defined by its name only. Dots are used for properties and links between tables (when defined as a #ManyToOne* property).
select o.techid, CAST(MAX(o.last_modified) AS DATE)
from User as u
left join Order as o
on u.username=o.techid group by o.techid
Think Classes and Properties, not columns, when you write HQL.
Try following solution, this should work
SELECT o.techid, CAST(MAX(o.last_modified) AS DATE)
FROM User u LEFT JOIN u.order o GROUP BY o.techid

Categories