JPAQuery join intermediate many to many table - java

I'm using QueryDSL JPA, and want to do a join between two tables. I found many similar questions here already, but my case is different from all of them by one little detail. Here is a simplified version of my classes:
#Entity(name = "TABLE_A")
public class TableA {
#Id
#Column(name = "ID_A", nullable = false)
private Long idA;
}
#Entity(name = "TABLE_B")
public class TableB {
#Id
#Column(name = "ID_B", nullable = false)
private Long idB;
}
#Entity(name = "TABLE_C")
public class TableC {
#Id
#Column(name = "ID_C", nullable = false)
private Long idC;
#JoinColumn(name = "ID_A", referencedColumnName = "ID_A")
#ManyToOne
private TableA tableA;
#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")
#ManyToOne
private TableB tableB;
}
Now what I want to do is join Tables A, C and B, to find the Bs which are linked to A. I know this seems like a useless step between, why not add a relation from A to B directly. In my case this is needed, these are just example classes to illustrate.
I tried this:
QTbTableA tableA = QTbTableA.tableA;
QTbTableB tableC = QTbTableC.tableC;
JPAQuery query = new JPAQuery(entityManager).from(tableA);
query.leftJoin(tableA, tableC.tableA);
The join throws an Exception because tableC.tableA is not a root path, only a property. But how do I join these tables correctly then?
Thanks in advance!

If you want to keep your current impl, you could start from TableC and then join the other tables:
query.from(tableC)
.innerJoin(tableC.tableA, tableA)
.innerJoin(tableC.tableB, tableB)
.where(tableA.idA.eq(myId)
.list(tableB);

Related

Spring data jpa operation between table views

in my spring data application i have two TABLE VIEW mapped:
the first view
#Entity
#Immutable
#Table(name="VD_CONT")
#NamedQuery(name="VdContr.findAll", query="SELECT d FROM VdContr d")
public class VdContr {
#Id
#Column(name="CONTR_ID")
private Long id;
#Column(name="CF")
private String cf;
#OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="vdcontr")
private List<VdArr> vdArr;
}
and the second view
#Entity
#Immutable
#Table(name="VD_ARR")
#NamedQuery(name="VdArr.findAll", query="SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name="ARR_ID")
private Long id;
#Column(name="FK_CONTR_ID")
private Long fkContrId;
#ManyToOne(fetch=FetchType.LAZY)
public VdContr vdcontr;
}
If i put a relationship "OneToMany" and "ManyToOne" (1, first view : many, second view), i receive errors.
My question is: is it possibile create a relationship between two table view?
you need to add a #JoinColumn to VdContr.
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "vdcontr_id", nullable = false)
In general, views are mapped in the same way as tables.
By looking at your classes, the problem is that Hibernate cannot find the correct join column. You need to specify it.
Also, in your VdArr you should delete the fkContrId, because hibernate will need to use this column to map the VdContr relationship.
By looking at your code, the join column is FK_CONTR_ID, so you need to specify it by using #JoinColumn.
#Entity
#Immutable
#Table(name = "VD_ARR")
#NamedQuery(name = "VdArr.findAll", query = "SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name = "ARR_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_CONTR_ID")
public VdContr vdcontr;
}

JPQL Query to select entity from one-to-one relationship where it's relative has a field matching a certain condition

I have three entities: EntityA maps to table_a, EntityB maps to table_b, and Catalog maps to catalog. In the database, there's a many-to-many table between table_b and catalog, b_catalog_xref. EntityB has a field: Long aId, and a field: List<Catalog> catalogs. The Catalog entity has a field: String name. Given a list of IDs for EntityB, and a string representing a catalog name, I need to retrieve all occurrences of EntityA whose ID matches that of an EntityB's aId, and where the given catalog name matches that of one of EntityB's catalogs.
I've successfully grabbed the correct data via regular SQL, but I'm struggling to recreate the query in JPQL. Here's the SQL query:
SQL:
SELECT
*
FROM
table_a a
WHERE
a.table_a_id in (
SELECT
b.table_a_id
FROM
table_b b
INNER JOIN b_catalog_xref bcx ON bcx.table_b_id = b.table_b_id
INNER JOIN catalog c ON c.catalog_id = bcx.catalog_id
WHERE
c.catalog_name = 'Example Catalog Name'
);
Java:
#Entity
#Table(name = "table_a")
public class EntityA {
#Id
#Column(name = "table_a_id")
private Long aId;
...
}
#Entity
#Table(name = "table_b")
public class EntityA {
#Id
#Column(name = "table_b_id")
private Long bId;
#Column(name = "table_a_id")
private Long aId;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
#JoinTable(name = "b_catalog_xref",
joinColumns = {#JoinColumn(name = "table_b_id")},
inverseJoinColumns = {#JoinColumn(name = "catalog_id")})
#Fetch(FetchMode.SELECT)
#OrderBy("name ASC")
List<Catalog> catalogs
...
}
#Entity
#Table(name = "catalog")
public class Catalog {
#Id
#Column(name = "catalog_id")
private Long catalogId;
#Column(name = "catalog_name")
private String name;
...
}
Yes, you can use something like
TypedQuery<TableA> q = entityManager.createQuery
("Select a from TableA a where a.aId in(Select b.aId from TableB b " +
"join b.catalogs c where c.name=:name)", TableA.class);
q.setParameter("name", "some2");
I advise you to consider creating a relationship between tables A and B instead of copying the key of table A to table B
#Entity
#Table(name = "table_b")
public class EntityB {
...
// #Column(name = "table_a_id")
// private Long aId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "table_a_id")
private TableA tableA;
...
}
Then JPQL will look like:
TypedQuery<TableA> q = entityManager.createQuery
("Select distinct b.tableA from TableB b join b.catalogs c " +
"where c.name=:name", TableA.class);
q.setParameter("name", "some2");
Pay attention to the keyword distinct, it removes all duplicates in the result list.
And do not use FetchType.EAGER unless absolutely necessary, use FetchType.LAZY.

Hibernate JOIN of Unrelated Entities Out of Scope in the Generated SQL

Since Hibernate 5.1, it started to offer the feature for us to join two unrelated entities as we do in native SQL. It is a fantastic feature! However, I recently encountered an unexpected behavior of this feature. I had a query with mixed JOINs (Left Joins and Inner Joins) with both related entities and unrelated entities. In the generated SQL, all of the unrelated entities JOINs are place in the bottom of the query, which caused this exception:
com.microsoft.sqlserver.jdbc.SQLServerException: The multi-part identifier "tlterm6_.term_id" could not be bound.
I'm baffled about how that happened and why was the feature implemented in that way (They must have a good explanation, but I have not found any solutions or explanations online yet).
Does anyone have an idea of a workaround or how to fix that?
The application is running on Hibernate 5.4.6 and SQL Server database.
Sample Entity Definition:
#Entity
#Table(name = "student")
Public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Integer id;
#Column
private String first_name;
#Column
private String first_name;
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
private List<College> colleges = new ArrayList<>();
// ...Other details and getters/setters omitted
}
#Entity
#Table(name = "college")
Public class College implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Integer id;
#Column
private String name;
#Column
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id")
private Student student;
// ...Other details and getters/setters omitted
}
Example:
FROM Student student
JOIN Class clazz ON student.id = clazz.student_id
JOIN student.colleges college
Generated SQL:
FROM dbo.student AS student
INNER JOIN dbo.college AS college ON student.id = college.student_id
INNER JOIN dbo.class AS clazz ON student.id = clazz.student_id
The expected generated SQL should be following the same order of the JOINs, however, it places the Related/Mapped entities Joins to the top and moves the Unrelated/Unmapped entities Joins to the bottom.

hibernate one to one join using primary key not working

I have 2 entities in hibernate A and B. Here is the relevant code.
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#OneToOne(mappedBy = "a", cascade = CascadeType.ALL)
private B b;
}
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "a_id")
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name = "property", value = "a"))
private Integer aId;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn
private A a;
}
I did the same as mentioned in the below mentioned link
one to one mapping using primary key join column
However, when I do the following hql query,
"from A a left join a.b"
the join is taken on the following condition
a.id = b.id
although what I desire is the following condition
a.id = b.aId
You must use #JoinColumn(name = "a_id") instead of #PrimaryKeyJoinColumn.
By the way, you can't define two fields on the same column. However, if you need to do so you must make one of them not insertable and not updateable like this:
#JoinColumn(name = "a_id", insertable = false, updatable = false)
You have given the reference of class A to field a in class B.
#OneToOne(fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn
private A a;
And class B to field b in class A
#OneToOne(mappedBy="a", cascade=CascadeType.ALL)
private B b;
so by default Hibernate create a join query with referenced field. So hibernate by default perform the join on a.id = b.id.
But i think you can create your own query and use native query for performing join with a.id = b.aId.

JPA count related entities without joining them

I have two entities:
#Entity
class X {
#Id
int id;
}
#Entity
class Y {
#Id
int id;
#ManyToOne
#JoinColumn(name = "x_id")
X x;
}
I would like to count distinct values of x_id in y table. I've tried:
select count(distinct Y.x) from Y;
It works but in sql i get join to x table which is uneccesery:
select count(distinct x.id) from y, x where y.x_id = x.id;
This join is unnecessary and quite costly for me. Is there any way to avoid it without native query?
You may try with select count(distinct Y.x.id) from Y (T.x.id instead of Y.x). I am not sure, but intelligent JPA implementation should find out that only id is necessary and would not add the join.
Alternative is to add a int field to Y with a read-only mapping to x_id column:
#Entity
class Y {
#Id
int id;
#ManyToOne
#JoinColumn(name = "x_id")
X x;
#Column(name = "x_id", insertable = false, updatable = false, nullable = false)
int xId;
}
And the your query would be simply select count(distinct Y.xId) from Y
For count inside the JPA repository, you can even use:
Suppose there are two entities: EntityA and EntityB. If EntityA has any relation with EntityB then you can use count in your
#Entity
#Table(name = "entity_a")
public class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ea_id")
private Long eaId;
#ManyToOne
#JoinColumn(name = "eb_id")
private EntityB entityB;
...
}
And another EntityB
#Entity
#Table(name = "entity_b")
public class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "eb_id")
private Long ebId;
...
}
For this, you can use the following method in your JPARepository of EntityA for getting the count. Remember _ is the replacement of . for method signature in the repository.
int countByEntityB_EbId(long ebId);

Categories