hibernate createAlias with clause generates wrong query - java

I have the following tables:
A: id
B: id, text
AB: aID, bID
I want to joib A and B where B.text contains the word 'cat'.
This is the hibernate query I do:
Criteria c = session.createCriteria(TableA.class, "A");
c.createAlias("A.bs", "B", JoinType.INNER_JOIN, Restrictions.like("b.text", "%cat%"));
c.setProjection(Projections.property("id"));
The generated query is:
Select id
FROM A a
INNER JOIN AB ab ON a.id=ab.aID AND (b.text like ?)
INNER JOIN B b ON b.id=ab.bID AND (b.text like ?)
For some reason AND (b.text like ?) appears in both inner joins. I far as I understand its supposed to be only in the second on.
This causes the following exception:
java.sql.SQLException: No value specified for parameter 2
I guess it's happening because it has only one parameters and two '?'.
What am I missing?
EDIT:
Adding the persistent classes:
#Entity
#Table(name="A")
Class A {
#Id
#Column(name="id", length=255)
protected String id;
#OneToMany
#JoinTable(name="AB", joinColumns = #JoinColumn( name="aID"), inverseJoinColumns = #JoinColumn( name="bID"))
#LazyCollection(LazyCollectionOption.FALSE)
protected List<B> bs;
}
#Entity
#Table(name="B")
Class B {
#Id
#Column(name="id", length=255)
protected String id;
#Column(name="text", length=255)
protected String text;
}

I think you need to create an alias
c.createAlias("A.bs", "b", JoinType.INNER_JOIN, Restrictions.like("b.text", "%cat%"));
Updated
I think It is a Hibernate bug. You can fix it by using a restriction in the where clause
c.createAlias("A.bs", "b", JoinType.INNER_JOIN);
c.add(Restrictions.like("b.text", "%cat%"));
or don't use join table and do association by foreign key in B.

Related

Create a hibernate relation between parent and children objects (lists) where both tables have a column pointing to a 3rd table

I'm sitting in this peculiar situation where I have the following database design
What this image illustrates is the following:
Table A has a_c_id which is "C id" but without a foreign key reference
Table B has c_id which is "C id" but without a foreign key reference.
Table C has id as primary key.
I'm trying in my java code to create following entities using hibernate and hibernate annotations
#Entity
#Table(name = "A")
Class A {
private Long id;
private List<B> bList; // This is the relation i'm struggling with
}
#Entity
#Table(name = "B")
Class B {
private Long Id;
private Long c_id;
}
I've tried alot of difference combinations using:
#JoinColumn(name = "a_c_id", referencedColumnName = "c_id")
private List<B> bList;
And:
#JoinTable(
name = "C",
joinColumns = {
#JoinColumn(
name = "id",
referencedColumnName = "a_c_id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "id",
referencedColumnName = "c_id"
),
}
)
private List<B> bList;
This last example with #JoinTable gives me an exception:
Repeated column in mapping for collection: id
which makes me wonder if mapping to the same PK from 2 different tables is not possible.
But i'm starting to run out of ideas on how to do this. I didn't make this database and is wondering if it's even possible to achieve making a relation from A to B without a foreign key to reference.
I thought I could perhaps use #JoinTable, however they refer to the same ID rather than 2 different ones as you would in a JoinTable.
If anyone has some input on what to read up on, or perhaps could come with an idea on how to achieve this without changing the database design, I'd apppreciate it!
EDIT: I've added images of table records:
EDIT 2: I've tried to trace binding values based on me and Nikos conversation:
Hibernate: select a0_.id as id1_0_0_, a0_.a_c_id as a_c_id2_0_0_ from a a0_ where a0_.id=?
2022-07-27 10:25:06.101 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2022-07-27 10:25:06.107 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicExtractor : extracted value ([a_c_id2_0_0_] : [BIGINT]) - [41]
2022-07-27 10:25:06.113 TRACE 16404 --- [nio-8080-exec-8] org.hibernate.type.CollectionType : Created collection wrapper: [eu.sos.auditing.models.HibernateTestModels.A.bList#1]
Hibernate: select blist0_.c_id as c_id2_3_0_, blist0_.id as id1_3_0_, blist0_.id as id1_3_1_, blist0_.c_id as c_id2_3_1_ from b blist0_ where blist0_.c_id=?
2022-07-27 10:25:06.149 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
EDIT 3: What solved my issue.
Due to Nikos help I was able to solve my issue by changing my class A to the following:
#Entity
#Table(name = "A")
#Data
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "a_c_id")
private Long aCId;
#OneToMany
#JoinColumn(name="c_id", referencedColumnName = "a_c_id")
private List<B> bList;
}
Kind regards
The solution can be as straightforward as pretending that B.c_id points to A.a_c_id:
Class A {
#OneToMany
// remember, the JoinColumn is in the other table (here B)
// while the referencedColumnName is in this table
#JoinColumn(name="c_id ", referencedColumnName = "a_c_id")
private List<B> bList;
}
EDIT: It is important to include the referencedColumnName, which I has forgotten before the edit. Also see full solution below for another caveat with Serializable.
If the DB constraints are different, you will have to make sure a C record exists before inserting into A and B.
The other solution is NOT to map the relation in the entity classes. Or, rather, map the existing relations, i.e. any of: (1) "A relates to 1 C", (2) "B relates to 1 C", (3) "C relates to many As", (4) "C relates to many Bs". And fetch the Bs that correspond to an A with an independent query.
FULL WORKING SOLUTION (with slightly different names - omitting getters/setters for brevity)
The entities:
#Entity
#Table(name = "AA")
public class Alpha implements Serializable {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_c_id")
private Charlie charlie;
#OneToMany
#JoinColumn(name="c_id", referencedColumnName = "a_c_id")
private List<Bravo> bList;
}
CAVEAT: I had to make Alpha implements Serializable because of HHH-7668. It applies only to the Hibernate implementation of JPA.
#Entity
#Table(name = "BB")
public class Bravo {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "c_id")
private Charlie charlie;
}
#Entity
#Table(name = "CC")
public class Charlie {
#Id
private Long id;
}
Sample code to read, assuming the contents of the tables are as in the question:
EntityManager em = ...;
Alpha a = em.find(Alpha.class, 2L);
a.getbList().forEach(b -> System.out.println(b.getId()));
// prints 3, 4

How to prevent JPA query related entity

Firstly, let me thank to who want to give the answer,
I am a new person using JPA for now, suppose that I have two tables, table A and B, for table A, we have the column
id_A,
id_B,
etc...
For table B, we have
id_B
And we have the entity classes,
#Data
class A {
#Id
#Column(name = "ID_A")
private String id;
#ManyToOne
#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")
private B b;
}
#Data
class B {
#Id
#Column(name = "ID_B")
private String id;
#JsonManagedReference
#OneToMany(mappedBy = "b", cascade = CascadeType.ALL)
#BatchSize(size = BATCH_SIZE)
private List<A> as;
}
Now, in order to only use the id of B, I firstly select all the relevant entity of A by ARepository.findAllByIds().
with the above query, I want to get all the ID_Bs, but when I do it by looping the result of the above query - a.getB, I noticed that it will get the B from Database again.
I know that we can fetch the A and B together by EAGER or EntityGraph, etc...
I am just wondering, is there any a way that I can only get the id_b from the ARepository.findAllByIds()?
To answer your question I think you will have to annotate your findAllByIds method with something like...
#Query(value = "SELECT a, b.id FROM A a LEFT JOIN a.b b where a.id in :ids") then your Bs will be loaded within the same query as the As

Optional OneToOne relation in Hibernate

Currently working on a project where we want to extract with Hibernate the following datamodel (model is a little bit simplified). We have a class A which contains some optional data which is stored in class B
#Entity
#Data
#Table(name = "A")
public class Country {
#Id
private UUID id;
private String someCommon;
#PrimaryKeyJoinColumn
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private B details;
}
#Entity
#Data
#Table(name = "B")
public class B {
#Id
private UUID id;
private String someDetail;
}
Fetching data works fine, except that when class B is not found for some instance of A, Hibernate does an extra query for that specific instance to retrieve the details of A. I.e in the logs these are the queries executed:
select a0_.id as id1_0_0_, b1_.id as id1_1_1_, a0_.some_common as some_common2_0_0_, b1_.some_detail as some_detail_2_1_1_ from a a0_ left outer join b b1_ on a0_.id=b1_.id
select b0_.id as id1_1_0_, b0_.some_detail as some_detail_2_1_0_ from b b0_ where b0_.id=?
Where in the second query the id is set to the id of the instance which does not have details.
So it looks like Hibernate is not supporting optional OneToOne relationships in an efficient manner. Any ideas on how to force Hibernate not doing the second query but just accepting the details are null?
There is no way to get rid of second query as you mentioned in hibernate because If the association is optional, Hibernate has no way to know if an address exists for a given person without issuing a query. so closest thing you can do is to call for second query only when its intended:
So as to avoid second query you have to opt for Lazy Loading:
To do that change your mapping to set optional to false and lazy loading will be on on details:
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
private B details;
Lazy loading makes sure details will be fetched only when its intended.

Workaround for HHH-2772 (hibernate querydsl)

I'm trying to perform a query to find cars by their foo property. The properties are stored in a different table.
I have two classes
#Embeddable
#Table(name = "PROPERTY")
public class Property {
#Column(name = "type", nullable = false)
private String type;
#Column(name = "string_value", nullable = true)
private String stringValue;
...
}
#Entity
#Table(name = "CAR")
public class Car {
#Id
...
private String id;
#ElementCollection(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#CollectionTable(name = "PROPERTY", joinColumns = #JoinColumn(name = "car_ID") )
private Set<Property> properties = new HashSet<Property>();
...
}
I'm trying to perform a query
QueryDsl:
.from(car)
.leftJoin(car.properties, foo)
.on(foo.type.eq("foo"))
.where(predicate)
Resulting HQL:
select
car
from
com....Car car
left join
car.properties as foo with foo.type = :a1
where
...
This doesn't work because of: https://hibernate.atlassian.net/browse/HHH-2772
Before that, it was possible to write HQL:
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner=:owner
Now the HQL is raising an exception:
org.hibernate.hql.ast.InvalidWithClauseException: with clause can only reference columns in the driving table
Workaround is to explicitly use primary key (ownerId):
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner.ownerId=:ownerId
The problem is that I don't have the ownerId, or an owner, since it's an element collection.
If I were to turn the element collection into a #oneToMany #manyToOne, the property could not longer be embeddable and would require an id. This is not an option. (I can't define a composite ID (this is a requirement), and I don't want to add a new column )
What do you recommend?
Is it possible to add the Car or Car Id as a field into an embaddable class?
Can I create the criteria in a different way?
I'm interested in any workaround that doesn't require database changes. (Hibernate changes or ok)
Thank you

Hibernate Criteria Left Excluding JOIN

I have no ideas how to do it using Hibernate Criteria
SELECT *
FROM Table_A A
LEFT JOIN Table_B B
ON A.Key = B.Key
WHERE B.Key IS NULL
there is Hibernate mapping like
#Entity
class A{
#Id
#Column(name = "ID")
private String ID;
... // fields
}
#Entity
class B{
... // fields
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "A_ID", referencedColumnName = "ID")
#Cascade(CascadeType.DETACH)
private A a;
... // fields
}
So I need to get list of all A which are not referred by B
Not tried it before, but something like this should work:
select * from Table_A a
where a not in (
select b.a from Table_B b )
This is of course in HQL
Criteria might look like this:
DetachedCriteria subquery = DetachedCriteria.forClass(B.class)
.setProjection( Property.forName("a.ID") )
.add(Restrictions.isNotNull("a.ID"));
session.createCriteria(A.class)
.add ( Property.forName("ID").notIn(subquery) )
.list();

Categories

Resources