Accessing multiple tables - java

I am learning EJB,JPA and have very basic doubt
I have 3 Tables A,B,C
A - ID, Name
B - ID, Name
C - ID, A_ID, B_ID
When i create a Entity Class from Database, i get 3 EJB classes with JPA stuff.
Now in my Managed bean i get either A.Name or B.Name and i need to find the matching entries using C.
Normal SQL Query will look like (may not be the best query)
SELECT a.name FROM schema.A a, schema.B b, schema.C c where b.Name='ABC' and c.B_ID=b.ID and a.ID = c.A_ID;
Now where do i do the above query in my classes.
I came across #SecondaryTable but could not understand how exactly its used.
I also saw em.createQuery( SQL query).getResultList().
Now is the above the best way or is there something available in EJB/JPA which is better.
UPDATE 1:
I was trying to execute the query in em.CreateQuery
em.CreateQuery(SELECT a.name FROM A a, B b, C c where b.Name='ABC' and c.B_ID=b.ID and a.ID = c.A_ID).getResultList();
but i get following error in my GlassFish Server (i am using EclipeLink JPA)
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: syntax error at or near ")"
Position:
Error Code: 0
Call: SELECT t0.given_name FROM test.a t3, test.a_b t2, test.b t1, test.a t0 WHERE ((((t1.Name = ?) AND (t2.b_id = t1.id.t1.id)) AND (t0.id = )) AND (t3.id = t2.a_id))
bind => [1 parameter bound]
Query: ReportQuery(referenceClass=Consultant sql="SELECT t0.given_name FROM test.a t3, test.a_b t2, test.b t1, test.a t0 WHERE ((((t1.Name = ?) AND (t2.b_id = t1.id.t1.id)) AND (t0.id = )) AND (t3.id = t2.a_id))")
Now why is the SQl statement is messed up in the error log
1.There is an extra entry test.a t0
2.t2.b_id = t1.id.t1.id
3.t0.id=
How does the error log SQL statement gets generated.

As C is a many to many relationship between A and B it shouldn't be an entity. However I don't think JPA likes it that your join table (C) has it's own ID column. If possible remove the ID column from C and make the combination of A_ID, B_ID your primary key.
Then the entity class A could have:
#JoinTable(name = "C",
joinColumns = { #JoinColumn(name = "A_ID", referencedColumnName = "ID") },
inverseJoinColumns = { #JoinColumn(name = "B_ID", referencedColumnName = "ID") })
#ManyToMany
private Collection<B> bCollection;
I think it is clear what all those annotations mean.
Class B would have:
#ManyToMany(mappedBy = "bCollection")
private Collection<A> aCollection;
The mappedBy attribute tells JPA to use the JoinTable defenition of A::bCollection (A is deduced from the type Collection<A> of the field).
Now if you have an instance of A you can easily get all the B's for that A by getting the property. No need for any SQL/JPQL.
Now as for executing queries you should know that you have JPQL and SQL. JPQL is Java Persistence Query Language and is the language of JPA. SQL is the native language of your database. To execute SQL you need to use the createNativeQuery family of functions. The createQuery functions are for JPQL.
You should prefer JPQL over SQL. The most important difference between the two is that JPQL works on your entities and expect all identifiers to correspond to the names used for your classes and properties. For example if the Name column (first letter uppercase) is mapped to a property called name (lower case) then in a JPQL query you should use name (lower case). Same for entity class names and corresponding table names. JPQL also has build in support for the join table of the many to many relation ship the JPQL query for what you want would be
SELECT a
FROM B b JOIN b.aCollection a
WHERE b.name='ABC'
No need to specify all the join conditions JPA knows them from the annotations on your classes.
#SecondaryTable is not relevant here it is used when a single entity is split into more then one table.

Related

Hibernate criteria - Inner Join OR Condition

I want to ignore default Join Restriction in createAlias. I have a OnetoOne relationship.
My Problem is Hibernate generates default restriction for the join relationship.
Pojo
Note : No column for diagnosticTemplate in charge table.
Charge.java
#OneToOne(mappedBy = "charge")
private DiagnosticTemplate diagnosticTemplate;
DiagnosticTemplate.java
#OneToOne
#JoinColumn(name = "charge")
#Exclude
private Charge charge;
Query
select
*
from
charges c
inner join diagnostic_template dt
on (dt.charge = c.id and dt.status=1) or (dt.status=0)
Criteria
Criteria criteria = getSession().createCriteria(Charge.class, "charge")
.createAlias("charge.diagnosticTemplate", "diagnosticTemplate",
JoinType.INNER_JOIN,
Restrictions.or(
Restriction.and(Restrictions.eqProperty("charge.id",
"diagnosticTemplate.charge"),
Restrictions.eq("diagnosticTemplate.status",true)),
Restrictions.eq("diagnosticTemplate.status",false) ))
Hibernate Query
select
*
from
charges c
inner join diagnostic_template dt
on dt.charge = c.id and (dt.charge = c.id and dt.status=1) or (dt.status=0)
How to avoid this condition? or anything wrong with my relationship?
Please help..!
When you join a charge with charge.diagnosticTemplate, it means that Hibernate will try to lookup a DiagnosticTemplate that is linked with this charge. Thus, the generated condition dt.charge = c.id does make sense.
Remember that you have define the relationship using foreign key. So, soft relation like dt.status=0 cannot be understood by Hibernate.
If you still wish to achieve you query, you can consider joining the two instance indirectly (not using association path). Refer to How to join Multiple table using hibernate criteria where entity relationship is not direct?.

Spring framework data JPA Inner Join on multiple columns

I would like to do the following query using spring jpa. I am able to build the where clause for my statement with Predicate.toPredicate. However, I don't know how to join on more than one column. On a single column it can be done in the repository using #Query and #Param
SELECT a.name, a.column_x, b.column_y
FROM table_a a
INNER JOIN table_b b
ON b.name = a.name
AND b.pk_2 = a.pk_2
AND b.pk_3 = a.pk_3
WHERE ...;
Another question I have is, is an intermediate tableA_tableB association beneficial if I have something like this, oneToMany relations.
Table 1: thing
thing_name
type
tenant
other1
other2
Table 2: thing_sub_prop
prop_name
value
Association table: thing_thing_sub_prop
type
thing_name
tenant
prop_name
value
Or is it better to just have two tables, thing and thing_sub_prop with the primary key columns of thing repeated in thing_sub_prop as a foreign key?

How can I annotate this complex join in JPA?

Let's say I have the following tables:
Locations
================
LocationKey, LocationName
Employees
================
EmployeeKey, FirstName, LastName
EmployeeLocationXRef
================
EmployeeLocationXRefKey, LocationKey, EmployeeKey
TimeSheets
=================
TimeSheetKey, EmployeeLocationXRefKey, Minutes
OK, so as you can see, in order for me to get all of the TimeSheets for a Location and Employee, I can do the following in SQL
select
*
from TimeSheets ts
join EmployeeLocationXRef ex on (ex.EmployeeLocationXRefKey = ts.EmployeeLocationXRefKey)
join Locations l on (l.LocationKey = ex.LocationKey)
join Employees e on (e.EmployeeKey = ex.EmployeeKey)
where
l.LocationKey = 'xxxx'
and e.EmployeeKey = 'yyyy'
But I have tried every way I can think of to map with (with annotations) in JPA using #JoinTable, etc.
Any ideas how this could be done? Unfortunately, this is a legacy system and the schema cannot be changed.
EDIT
Forgot to mention the EmployeeLocationXRef entity has not been directly mapped. Which could be the real problem. I will see about creating that entity mapping and see if it makes it easier.
I don't see anything particularly difficult or strange about this schema. There should simply be one entity per table and (unless there are unique constraints):
a ManyToOne between EmployeeLocationXRef and Location, using a JoinColumn
a ManyToOne between EmployeeLocationXRef and Employee, using a JoinColumn
a ManyToOne between Timesheet and EmployeeLocationXRef, using a JoinColumn
(these associations can of course be made bidirectional, or be in the other direction if you prefer so).
The JPQL query would simply be
select ts from Timesheet ts
join ts.employeeXRef ex
join ex.location l
join ex.employee e
where
l.locationKey = 'xxxx'
and e.employeeKey = 'yyyy'
which is a straightforward translation of the SQL query.

How to fetch all data in one query

I have multiple entities that are queried via JPA2 Criteria Query.
I am able to join two of these entities and get the result at once:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<LadungRgvorschlag> criteriaQuery = criteriaBuilder.createQuery(LadungRgvorschlag.class);
Root<LadungRgvorschlag> from = criteriaQuery.from(LadungRgvorschlag.class);
Join<Object, Object> ladung = from.join("ladung");
from.fetch("ladung", JoinType.INNER);
Then i try to join an additional table like that:
ladung.join("ladBerechnet");
ladung.fetch("ladBerechnet", JoinType.LEFT);
i get the following error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias3,role=null,tableName=ladberechnet,tableAlias=ladberechn3_,origin=ladungen ladung1_,columns={ladung1_.id ,className=de.schuechen.beans.tms.master.LadBerechnet}}] [select generatedAlias0 from de.schuechen.beans.tms.master.LadungRgvorschlag as generatedAlias0 inner join generatedAlias0.ladung as generatedAlias1 inner join generatedAlias1.ladBerechnet as generatedAlias2 left join fetch generatedAlias1.ladBerechnet as generatedAlias3 inner join fetch generatedAlias0.ladung as generatedAlias4 where ( generatedAlias0.erledigt is null ) and ( generatedAlias0.belegart in (:param0, :param1) ) and ( generatedAlias1.fzadresse in (:param2, :param3) ) and ( generatedAlias1.zudatum<=:param4 ) and ( 1=1 ) order by generatedAlias0.belegart asc, generatedAlias1.fzadresse asc, generatedAlias1.zudatum asc, generatedAlias1.zulkw asc]
How can i tell JPA/Hibernate, that it should select all the entities at once?
With JPA 'some dialects of JPA' you can chain join fetches, but I don't think you can/should do both a join and a join fetch.
For instance, if we have a Program that has a one-to-many relation to a Reward that has a relation to a Duration, the following JPQL would get a specific instance with the rewards and duration pre-fetched:
SELECT DISTINCT
program
FROM
Program _program
LEFT JOIN FETCH
_program.rewards _reward
LEFT JOIN FETCH
_reward.duration _duration
WHERE
_program.id = :programId
}
With the equivalent Criteria code:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Program> criteriaQuery = criteriaBuilder.createQuery(Program.class);
Root<Program> root = criteriaQuery.from(Program.class);
Fetch<Program, Reward> reward = root.fetch("rewards", JoinType.LEFT);
Fetch<Reward, Duration> duration = reward.fetch("duration", JoinType.LEFT);
criteriaQuery.where(criteriaBuilder.equal(root.get("id"), programId));
TypedQuery<program> query = entityManager.createQuery(criteriaQuery);
return query.getSingleResult();
Note that the intermediate variables reward and duration are not needed here, but they're just for informational purposes. root.fetch("rewards", JoinType.LEFT).fetch("duration", JoinType.LEFT) would have the same effect.
What it comes to JPA, you cannot chain join fetches in Criteria API queries (citation from specification):
An association or attribute referenced by the fetch method must be
referenced from an entity or embeddable that is returned as the result
of the query. A fetch join has the same join semantics as the
corresponding inner or outer join, except that the related objects are
not top-level objects in the query result and cannot be referenced
elsewhere by the query.
And it is also not supported in JPQL queries:
The association referenced by the right side of the FETCH JOIN clause
must be an association or element collection that is referenced from
an entity or embeddable that is returned as a result of the query.
It is not permitted to specify an identification variable for the
objects referenced by the right side of the FETCH JOIN clause, and
hence references to the implicitly fetched entities or elements cannot
appear elsewhere in the query.
With HQL it seems to be possible: Hibernate documentation EclipseLink does not provide such a extension, so syntax of following query is accepted by Hibernate, but not by EclipseLink:
SELECT a FROM A a LEFT JOIN FETCH a.bb b LEFT JOIN FETCH b.cc
In EclipseLink same can be done via query hints.

Insert fails for #OneToMany Hibernate mapping with inheritance

I'm using Java and Hibernate 3.6.4.Final.
TABLE_B is a mapping table for a specific type of A entities (B) which have a OneToMany relationship with X entities.
I'm having problems inserting a B entity in the DB.
Reading of B entities from the DB is not a problem and the related X entities are correctly loaded as well.
DB model:
TABLE_A (id, active, info)
TABLE_B (a_id, x_id)
TABLE_X (id, info)
An "A" entity might have zero or more relations with X entities.
An "A" entity with one or more relations to Xs are called "B" entities and have their own behavior in the code.
(The class and table names has been changed for simplicity)
#Entity
#Table(name = "TABLE_A")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class A {
...
}
#Entity
#Table(name="TABLE_B")
#PrimaryKeyJoinColumn(name="A_ID", referencedColumnName = "ID")
public class B extends A implements Serializable {
#OneToMany(cascade={CascadeType.ALL})
#JoinTable(
name="TABLE_B",
joinColumns = #JoinColumn( name="A_ID"),
inverseJoinColumns = #JoinColumn( name="X_ID", insertable = true, nullable = false)
)
private List<X> Xs;
...
}
The log:
SQLStatementLogger - insert into TABLE_A (active, info) values (?, ?)
SQLStatementLogger - insert into TABLE_B (A_ID) values (?)
JDBCExceptionReporter - could not insert: [com.test.B] [insert into TABLE_B (A_ID) values (?)]
java.sql.SQLException: Field 'X_ID' doesn't have a default value
I would understand if the problem is caused by specifying the entity table as "TABLE_B" and using the same for the OneToMany join table but this is what my DB model looks like.
To me it seems like Hibernate first tries to just insert the inheritance and if that would work the next insert would be the mapping between B and X. The problem is that for me the inheritance and mapping table are the same.
How should I map my DB model correctly in Hibernate? Help is much appreciated.
You can't have TABLE_B as both 'entity' table (e.g. table to which B is mapped) and 'join' table (e.g. table that holds joins between B and X).
In the first case, TABLE_B needs to have at most one record for each TABLE_A record (that is, for those As that are also Bs); in the second case TABLE_B needs to have as many records as you have X elements in B's collection which presents an obvious contradiction.
What you can do, therefore, is either of the following:
Map your Xs collection without join table (#ManyToOne on X side; #OneToMany mappedBy="X" on B side). Your 'X_TABLE' will have to have an a_id (or b_id, whatever you call it) FK to owner.
Use another table (TABLE_B_X for B-to-X mapping)

Categories