I want to write a join query for joining two tables in Spring Data, using Hibernate.
sql: select * from a join b on a.B_Id=b.id;
My classes are as below:
In class A, I don't have reference of B, I have only B_id. Since its a large project I can't change the class definitions. But need to migrate the join queries to Hibernate.
Any way I can do it?
#Entity
#Table(name = "a")
public class A {
#Id
private Integer id;
// ...
private Integer B_Id;
}
#Entity
#Table(name = "b")
public class B {
#Id
private Integer id;
// ...
}
Many to one mapping -> many A can have the same B.
Related
I was trying to optimise number of calls for my schema creational endpoint and get rid of n+1 problem. And I actually did it replacing them with only 2 calls. I have achieved it with Named Queries but I wonder if it is possible to do it with nativeQuery=true.
So, here is the situation:
Class A:
#Entity
#IdClass(AId.class)
#Table(name ="A")
#Data
class A implements Serializable{
#Id
#Column(name = "ID")
private Integer id;
// code omitted...
#ManyToOne(fetch=FetchType.Lazy)
#JoinColumns(
...
)
private B b;
}
Class B:
#Entity
#IdClass(AId.class)
#Table(name ="B")
#Data
class B implements Serializable{
#Id
#Column(name = "ID")
private Integer id;
// code omitted...
#OneToMany(mappedBy="b", cascade=Cascade.ALL, fetch=FetchType.Lazy)
#JoinColumns(
...
)
private List<A> aList;
}
The problem occurs when I try to fetch the data with generated or native queries.
It grabs B entities when I fetch A though it's Lazy for A.
Expected behaviour is the executing a query as follows:
Hibernate:
/* dynamic native SQL query */ SELECT
*
FROM
A a
WHERE
a.ID IN (
?
)
But right after this (no matter how I played with dynamic native query) I have one more query for B entity that I don't need:
Hibernate:
/* load com.example.to.B */ select
b0_.id as id1_1_0_,
from
B b0_
where
b0_.id=?
Why is this so and is there any way to avoid it?
Thanks in advance.
Set a breakpoint in e.g. org.hibernate.resource.jdbc.internal.EmptyStatementInspector#inspect to see which code triggers the lazy initialization. Maybe it's your debugging that triggers this through a toString implementation.
Hibernate cannot fetch lazily for ToOne relationships.
The field b can be null in A, and hibernate has to check with the table b before confirming to populate the field with null. The query for B you observed is the result.
You may consider using #MapsId, e.g.:
#ManyToOne
#MapsId(“id”)
private B b;
You may read further about #MapsId here:
https://www.objectdb.com/api/java/jpa/MapsId
Assume I have dozen of one-to-one relation using common id carId and I can't touch schema or merge them into one.
#Entity
public class Car {
#Id
private Integer id;
#OneToOne(mappedBy="car")
private Registration regist;
#OneToOne(mappedBy="car")
private Detail detail;
#OneToOne(mappedBy="car")
private License license;
}
#Entity
public class Registration {
#OneToOne
private Car car;
private String governor;
}
#Entity
public class Detail {
#OneToOne
private Car car;
private Date manufacturingDate;
}
#Entity
public class License {
#OneToOne
private Car car;
private String licenseNumber;
}
// and more ...
When I create a query begin with License to get data from Detail and Registration, the generated sql looks like
select
car_detail.manufacturing_date,
car_registration.governor
from
car_license
left outer join
car
on car_license.car_id=car.id
left outer join
car_detail
on car.id=car_detail.car_id
left outer join
car
on car_license.car_id=car.id
left outer join
car_registration
on car.id=car_registration.car_id
where
and car_license.licenseNumber=?
It's reasonable because JPA doesn't understand column car_id is common across entities. However, the result is hard to debug and error prone on complicated join.
How can I define those entities so that JPA knowns they don't need additional join on table car and can be joined directly, or just join to table car only once?
Group by entities is possible in HQL, but it seems that by entities mapped with joined inheritance doesn't.
select a, count(r) from Root r join r.a a group by a
Executing the above query results in a sql with insufficient columns in group by:
select
suba1_.id as col_0_0_,
count(root0_.id) as col_1_0_,
suba1_.id as id1_12_,
suba1_1_.super_data as super_da2_12_,
suba1_.sub_data as sub_data1_10_
from root root0_
inner join suba suba1_ on root0_.a_id=suba1_.id
inner join supera suba1_1_ on suba1_.id=suba1_1_.id
group by suba1_.id
Which gives the following error message:
o.h.engine.jdbc.spi.SqlExceptionHelper: expression not in aggregate or GROUP BY columns: SUPERA1_.SUPER_DATA in statement
The entities are described as followed:
#Entity
public class Root {
private #Id Integer id;
#ManyToOne
private SubA a;
}
#Entity
public class SubA extends SuperA {
private String subData;
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class SuperA {
private #Id Long id;
private String superData;
}
Also if changed the type of Root::a to SuperA, the sql generated by the hql will have the same problem.
So is group by entities with joined inheritance type possible or am I missing something?
PS. This hql query does work in case if SuperA is mapped with table per class inheritance type but then fails with the same problem if type of Root::a is changed to SuperA as well.
Hibernate version: org.hibernate:hibernate-core:jar:5.4.32.Final:compile
Yes, with a bit of trickery, to anwser my own question. The missing column for the group by in the sql is the column id of SuperA, so the solution would be to include it. I found that possible by mapping the superclass's id with an extra column and reference it in the group by along side the entity.
So, in SuperA an extra column mapping superId would be added:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class SuperA {
private #Id Long id;
#Column(name="id", updatable = false, insertable = false)
private Long superId;
private String superData;
}
And referenced in the group by
select a, count(r) from Root r join r.a a group by a, a.superId
This solution was tested only for HSQLDB and PostgreSQL.
I have 3 entities:
#Entity
#Table(appliesTo = "A", indexes = {#Index(name = "a_uuid_idx", columnNames = {"uuid"})})
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EntityID")
#Access(AccessType.PROPERTY)
private Integer id;
}
#Entity
#DiscriminatorValue("local.stored")
public class B extends A {
private String name;
}
And data from B successfully persists in table A.
#Entity
#DiscriminatorValue("second.stored")
public class C extends B {
private String lastname;
}
I expected, data from C to be persisted to table A (so, 3 etitties in one table). But it does not, despite the fact that DiscriminatorValue is used.
I tried also to aplly to class B annotation #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) in the hope that separate table for C will be created (so, 2 tables: 1 - for A/B and 2 - for C). But without success.
I have two hibernate classes: a base class, and an extended class that has additional fields. (These fields are mapped by other tables.)
For example, I have:
#Entity
#Table(name="Book")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
public class B extends A {
public String node_ID;
// ...
}
public class Node {
public String ID; // maps to B.node_ID
// ...
}
How do I map this in Hibernate? The hibernate documentation states three types of inheritence configurations: one table per class, one table with a type column, and a join table -- none of which apply here.
The reason I need to do this is because class A is from generic framework that's reused over multiple projects, and class B (and Node) are extensions specific to one project -- they won't be used again. In the future, I may have perhaps a class C with a house_ID or some other field.
Edit: If I try the above pseudo-code configuration (two entities mapped to the same table) I get an error that the DTYPE column doesn't exist. The HQL has a "where DTYPE="A" appended.
This is possible by mapping the #DiscriminatorColumn and #DiscriminatorValue to the same values for both classes; this can be from any column you use that has the same data regardless of which type (not sure if it works with null values).
The classes should look like so:
#Entity
#Table(name="Book")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="published")
#DiscriminatorValue(value="true")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
#DiscriminatorValue(value="true")
public class B extends A {
public String node_ID;
// ...
}
For anyone who got here like me and does not want to have the dtype column but instead want to use the same table for more than one entity as is I would recommend using this
Basically you can create a Base like this
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
... any other variables, getters + setters
}
#Entity
#Table(name= "book")
public class BookA extends BaseBook<BookA>{
//Default class no need to specify any variables or getters/setters
}
#Entity
#Table(name= "book")
public class BookB extends BaseBook<BookB>{
#Column(name = "other_field")
private String otherFieldInTableButNotMapedInBase
... Any other fields, getter/setter
}
From the above we have created base super class which does not have any entity or table mapping. We then create BookA to be default with the Entity + Table mapping. From there we can create other Entities all extending from BaseBook but pointing to one table