Hibernate: queries by example and many-to-one relationships - java

Let's say those are my entities:
Table1.java
#Entity
public class Table1 {
// Many to one
private Table2 table2;
// Raw attributes
// ...
}
Table2.java
#Entity
public class Table2 {
// Many to one
private Table3 table3;
// Raw attributes
// ...
}
Table3.java
#Entity
public class Table3 {
// Raw attributes
private String lang; // "fr", "en", "de"...
}
I wanna list all the Table1 rows that have a table2.table3.lang that equals en. I tried to use a query by example:
Table3 table3Example = new Table3();
table3Example.setLang("en");
Table2 table2Example = new Table2();
table2Example.setTable3(table3Example);
Table1 table1Example = new Table1();
table1Example.setTable2(table2Example);
table1Repository.findByExample(table1Example);
The problem is that .findByExample(table1Example) returns all the rows of the database, no matter of the lang, which means that the filter isn't considered at all :(
Any help would be appreciated :)
PS: no exception is thrown, .findByExample(table1Example) just returns all of the Table1 rows.

Try something like this:
Query q = entityManager.createQuery("Select o from Table1 o where o.table2.table3.lang = :lang");
q.setParameter("lang", "en");
List<Table1> r = (List<Table1>)q.getResultList();
To see why you get all the rows in Table1, make sure that you have
<property name="hibernate.show_sql" value="true"/>
in your persistence.xml and then watch the log to see the actual select that hibernate executes.

Related

How to Use Multiple Join on Hibernate?

I have these following Classes:
class Person(){
#OneToMany(mappedBy="person")
private List<PersonRoles> roles;
}
class PersonRoles(){
#ManyToOne
#JoinColumn(name = "person_id", nullable = false)
private Person person;
#ManyToOne
#JoinColumn(name = "request_id")
private Request request;
}
class Request(){
#OneToMany(mappedBy="request")
private List<PersonRoles> roles;
}
Now I am going to fetch all person based on a given request id and his roles by using hibernate and inner join but my log is telling me that my table doesn't exist. This is my query so far:
sql = "SELECT p.* FROM person AS p INNER JOIN p.roles ON p.roles.personId = p.id
INNER JOIN request AS r ON p.roles.requestId = r.id AND p.roles.role like :item
AND r.id = :id";
query = session.createSQLQuery(sql);
query.addEntity(Person.class);
query.setParameter("item", "Members");
query.setParameter("id", id);
person = (Person) query.uniqueResult();
and this is what i received on the log:
Table 'p.roles' doesn't exist
Did i forget some hibernate annotation? or My query has something wrong?
Brief reason
your syntax of SQL is wrong
Detailed explanation
here is the syntax of inner join example
SELECT column_name(s)
FROM table1
INNER JOIN table2
ON table1.column_name = table2.column_name;
for multiple inner join
SELECT *
FROM table1
INNER JOIN table2
ON table1.primaryKey=table2.table1Id
INNER JOIN table3
ON table1.primaryKey=table3.table1Id
but you have used INNER JOIN p.roles there should be a table name after the INNER JOIN, not a column name.
that's why you got an error, moreover, use HQL instead of SQL in hibernate it is a good practice.
happy coding!

eclipselink AdditionalCriteria ignored in child class

If I setup a parent/child relationship with both parent and child having additionalcriteria constraints, and then use #JoinFetch then childs additionalcriteria are ignored.
For example:
TableA.java:
#javax.persistence.Entity
#Table(name = "TABLE_A")
#AdditionalCriteria("this.tableAfield2=:propA")
public class TableA {
#Id
#Column(name = "TABLEAFIELD1")
private String tableAfield1;
#Column(name = "TABLEAFIELD2")
private String tableAfield2;
#JoinColumn(name = "TABLEAFIELD2", referencedColumnName = "TABLEBFIELD1", insertable = false, updatable = false)
#OneToOne(fetch = FetchType.EAGER)
// #JoinFetch(JoinFetchType.OUTER)
private TableB tableAtableB;
}
TableB.java:
#javax.persistence.Entity
#Table(name = "TABLE_B")
#AdditionalCriteria("this.tableBfield2=:propB")
public class TableB {
#Id
#Column(name = "TABLEBFIELD1")
private String tableBfield1;
#Column(name = "TABLEBFIELD2")
private String tableBfield2;
public String getTableBfield1() {
return tableBfield1;
}
public String getTableBfield2() {
return tableBfield2;
}
}
Main:
em.setProperty("propA", "propertyAValue");
em.setProperty("propB", "propertyBValue");
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<TableA> criteriaQuery = cb.createQuery(TableA.class);
Root<TableA> tableA = criteriaQuery.from(TableA.class);
Predicate pred = cb.equal(tableA.get("tableAfield1"), "keyA1");
criteriaQuery.where(pred);
List<TableA> results = em.createQuery(criteriaQuery).getResultList();
With tableA set as per the example (with JoinFetch commented out)
the applications creates 2 SQLs
SELECT TABLEAFIELD1, TABLEAFIELD2 FROM TABLE_A WHERE ((TABLEAFIELD1 = ?) AND (TABLEAFIELD2 = ?))
bind => [keyA1, propertyAValue]
SELECT TABLEBFIELD1, TABLEBFIELD2 FROM TABLE_B WHERE ((TABLEBFIELD1 = ?) AND (TABLEBFIELD2 = ?))
bind => [propertyAValue, propertyBValue]
which is fine, as eclipselink is loading the table_b on demand.
but for our application we need to have a single SQL, as there maybe 1000s of rows and we need a single join.
So, if I put back the #JoinFetch then the sql generated is;
SELECT t1.TABLEAFIELD1, t1.TABLEAFIELD2, t0.TABLEBFIELD1, t0.TABLEBFIELD2 FROM TABLE_A t1 LEFT OUTER JOIN TABLE_B t0 ON (t0.TABLEBFIELD1 = t1.TABLEAFIELD2) WHERE ((t1.TABLEAFIELD1 = ?) AND (t1.TABLEAFIELD2 = ?))
bind => [keyA1, propertyAValue]
the additionalCriteria from TableB is not added (there is no t0.tableBField1=? (propertyBValue) )
Any suggestions? Its driving me mad.
Many thanks
For completeness here are the tables
create table TABLE_A (
TABLEAFIELD1 varchar2(20),
TABLEAFIELD2 varchar2(30),
CONSTRAINT tableApk PRIMARY KEY (TABLEAFIELD1)
) ;
create table TABLE_B (
TABLEBFIELD1 varchar2(20),
TABLEBFIELD2 varchar2(30),
CONSTRAINT tableBpk PRIMARY KEY (TABLEBFIELD1)
) ;
insert into TABLE_A (TABLEAFIELD1,TABLEAFIELD2) values ('keyA1','propertyAValue');
insert into TABLE_A (TABLEAFIELD1,TABLEAFIELD2) values ('keyA2','propertyAValue');
insert into TABLE_A (TABLEAFIELD1,TABLEAFIELD2) values ('keyA3','random');
insert into TABLE_B (TABLEBFIELD1,TABLEBFIELD2) values ('propertyAValue','propertyBValue');
So this is a long term bug with eclipselink and doesn't look like it will be fixed.
The solution was to change
#JoinFetch(JoinFetchType.OUTER)
to
#BatchFetch(BatchFetchType.JOIN)
This doesn't exactly have the result I was hoping for, originally wanted the generated sql to include an OUTER JOIN,
but BatchFetch results in only 2 SQLs, one to get the Table_A items, then another to fetch all the Table_B items (including the additionalcriteria requirements)

jdbc left join 3 tables

There are three tables <Table 1>, <Table 2> and <Table 3>
My SQL is something like this:
"Select table1.col1, table1.col2, table1.col3, table2.col4, table2.col5, table2.col6,
table3.col7, table3.col8 from Table 1 as table1
LEFT JOIN Table 2 as table2 on (table1.col1 = table2.col4)
LEFT JOIN Table 3 as table3 on (table1.col1 = table3.col8)"
The normal way to get the resultset is:
public List getExportDataList() throws ClassNotFoundException, SQLException {
Connection connect = null;
String url = "jdbc:.....";
String username = "username ";
String password = "password ";
try {
connect = DriverManager.getConnection(url, username, password);
} catch (SQLException ex) {
System.out.println("in exec");
System.out.println(ex.getMessage());
}
List dataList = new ArrayList<>();
PreparedStatement pstmt = connect.prepareStatement(
THE SQL CODE SHOWN ABOVE
}
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
Table1 table1 = new Table1();
table1.setCOL1(rs.getString("col1"));
table1.setCOL2(rs.getString("col2"));
dataList.add(table1);
}
rs.close();
pstmt.close();
connect.close();
return dataList;
}
so that the "dataList" can be used to display the data in Primefaces dataTable.
However, this way only can save the columns in Table1 to the "dataList". I tried to dataList.add(table2) as well as dataList.add(table3) at the same time, but there is an error : "/reportGenerate.xhtml #50,75 value="#{reportData.dateCreated}": The class 'net.picary.model.Liaison' does not have the property 'dateCreated'."
Can someone tell me how to save all the selected columns from the three tables into "dataList"? Or any alternative way to achieve it?
error: "/reportGenerate.xhtml #50,75
value="#{reportData.dateCreated}": The class
'net.picary.model.Liaison' does not have the property 'dateCreated'."
Your problem is not with Query or JDBC, you should to make sure that attribute dateCreated exist in your net.picary.model.Liaison class with getter and setter like this :
private Date dateCreated;
public String name;
public String experience;
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
So when your page reportGenerate.xhtml try to load this attribute it not find it, because :
Not exist in your class
It exist but it is private and without getter and setter.
So make sure that your attribute exist and have gtter and setter, this can solve your error.
EDIT
You have two choices :
When you get your result you should to use 3 List of type table1, table2, table3 and fill them in the same loop like this :
List dataList1 = ...;
List dataList2 = ...;
List dataList3 = ...;
while(rs.next()){
table1 = new Table1();
table1.setCOL1(rs.getString("col1"));
...
dataList1.add(table1);
table2 = new Table2();
table2.setCOL1(rs.getString("col1"));
...
dataList2.add(table2);
table3 = new Table3();
table3.setCOL1(rs.getString("col1"));
...
dataList3.add(table3);
}
and in your xhtml page you had to use this three List instead to one
Create a new Object which combine this three Table like this :
class MyObject {
private Table1 table1;
private Table2 table2;
//constructor
//getters and setters
}
Then create a List<MyObject> list = ....; and set information in each table.
Hope you get my point, good luck
First, Check whether column names (col1,col2,col3,..) are with same name in your code in java with all the three db tables (table1,table2,table3).
(ie)
...
Table1 table1 = new Table1();
table1.setCOL1(rs.getString("col1"));
table1.setCOL2(rs.getString("col2"));
dataList.add(table1);
....
In the above code check you have "col1" is in same name with db column in Table1,Table2,Table3.If the column name(s) is different in database and in your code ,add aliases to your MySQL query and match it with same name in your java code.
If the column name matches, then set the column value for your table 2 and 3.
Then, add it to your Datalist.
....
....
table2.setCOL1(rs.getString("col1"));
dataList.add(table2);
....
and so on. Also, check you have added try/catch for your code to avoid exceptions like Nullpointer,SQLException etc.. which may arise during SQL transactions.

JPA CriteriaBuilder left outer join for table with no relations?

Initially I had requirement to write code using JPA CriteraiBuilder for following SQL:
SELECT ve.col_1,
(SELECT vm.col_4
FROM table2 vm
WHERE vm.col_2 = ve.col_2
AND vm.col_3 = ve.col_3
) as col_a
FROM table1 ve;
But I learnt that, it is not possible to add subquery in select clause. So I changed my query to use left outer join like this.
SELECT ve.col_1,
vm.col_4 as col_a
FROM table1 ve,
table2 vm
WHERE
vm.col_2 (+) = ve.col_2
AND vm.col_3 (+) = ve.col_3;
Now table1 and table2 do not have direct relations using foreign keys. Corresponding JPA entities look like:
Table1.java ->
#Column(name = "COL_1")
private String col_1;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="COL_2"),
#JoinColumn(name="COL_3")
})
private Table2 table2;
Table2.java ->
#Column(name = "COL_4")
private String col_4;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
My code looks like:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<SearchTO> query = criteriaBuilder.createQuery(
SearchTO.class);
Root<Table1> root = query.from(Table1.class);
final Join<Table1, Table2> joinTable2 = root.join(Table1_.table2,
JoinType.LEFT);
Then I am trying to fetch value using:
joinTable2.get(Table2_.col_4)
Then now I am getting error as:
A Foreign key refering com.Table2 from com.Table1 has the wrong number of column
Table2 has around 6 columns with annotation #Id and I can not change change it to have only two columns with #Id annotation.
Please let me know:
If it is possible to write code using CriteriaBuilder for my approach 1 (subquery in select clause).
If thats not possible, how can I implement this left outer join as mentioned for approach 2. Please note that Table2 does not have any references of Table1.
Please note that I am using plain JPA APIs. DB is Oracle11g. JDK version is 1.7.
One solution is to create view and query against it using criteria builder.
You can see my answer in Joining tables without relation using JPA criteria
Only change you need to do is use left join in your view definition.
Hope it solves your use case.
For Approach 1: You can write subquery for criteria query
Subquery<Entity1> subquery = cq.subquery( Entity1.class );
Root fromSubQuery = subquery.from( Entity1.class );
subquery.select( cb.max( fromSubQuery.get( "startDate" ) ) );
subquery.where( cb.equal( fromSubQuery.get( "xyzId" ), fromRootOfParentQuery.get( "xyzId" ) ) );
Use it as :
Root<Entity2> entity2 = cq.from( Entity2.class );
Predicate maxDatePredicate = cb.and( cb.equal( entyty2.get( "startDate" ), subquery ) );
For Approach 2:
There is no other way than having relationship between two entities for left join. You can define private variable for relationship without getter & setter and use that variable for setting left join.
Then add the predicate to criteriaBuilder

JPA 2 + Criteria API - Defining a subquery

I try to convert a sql query to Criteria API without success so far. I can create two separate queries which return the values I need, but I don't know how to combine them in a single query.
Here is the sql statement which works:
select company.*, ticketcount.counter from company
join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
This Criteria query returns the inner query results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));
qTicket.select(cb.construct(
intCompany.class, cb.count(from),from.<Company>get("company")))
.where(state).groupBy(groupBy);
em.createQuery(qTicket).getResultList();
In the application I defined a small wrapper/helper class:
public class intCompany{
public Company comp;
public Long opentickets;
public intCompany(Long opentickets,Company comp){
this.comp = comp;
this.opentickets = opentickets;
}
public intCompany(){
}
}
So does anyone has an idea how to get this working?
Update
Thank you. I changed my criteria query as you suggested. I just had to add a loop at the end to get the information I wanted.
List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
for(intCompany ic: result){
ic.comp.setOpentickets(ic.opentickets.intValue());
cresult.add(ic.comp);
}
return cresult;
Maybe it is just not possible to convert the original sql to Criteria API.
Another update
I figured out I had to change the original sql expression to
select company.*, ticketcount.counter from company
left join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
Otherwise I do not get companies with no entries in the ticket table.
So are there any other suggestions?
You have almost everything done.
//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();
This should work as expected.
I had similar problem. My solution was to use left outer joins.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
cb.construct(
EntityDTO.class,
root.get( Entity_.id ),
root.get( Entity_.name ),
cb.count(join)
));
This JoinType.LEFT guarantees that you will get Entity records (companies) even if it doesn't have any child entities (tickets).
Entity class:
#Entity
public class Entity {
...
#OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
private Set<ChildEntity> objects;
...
}
Static model:
#StaticMetamodel( Entity.class )
public class Entity_ {
public static volatile SingularAttribute<Entity, Long> id;
public static volatile SingularAttribute<Entity, String> name;
...
public static volatile SetAttribute<Entity, ChildEntity> objects;
}

Categories

Resources