I have table A which have relation with table B as one-to-many.
What I want to do is to do select with іщьу limit from table A and after join to selected results table B.
So I have faced with a problem how do that in the right way bu the hibernate?
Typical Criteria.selectMaxResults it is not that I need, cause limit will accept after join and instead of getting 10 different rows from table A I will get, for example, just 1 row from table 'A' with joined 10 different rows from table B.
(1) So what is the best way to do that? Select just unique rows from A in one query and in the other query do select from A with join B?
In general, on the native SQL language, I expect to execute the next query:
String sQuery = SELECT a.*,b* FROM (SELECT * FROM parentTable WHERE c.id=777 LIMIT 0,10) AS a, b.* WHERE a.id=b.a_id;
So, according to hibernate tutorial and docs I've tried to use session.createSQLQuery(sQuery). My method looks in the next way:
#Override
#Transactional
public List<VocabularyWord> getWords(int vocId, int firstResult, int pageSize) {
Session s = this.template.getSessionFactory().getCurrentSession();
SQLQuery query = s.createSQLQuery("SELECT {vW.*}, m.* FROM (SELECT * FROM vocabulary_words AS vw WHERE vw.vocabulary_id=:id LIMIT :from,:size) AS vW, meaning AS m WHERE vW.id=m.vocabulary_words_id;");
query.setInteger("id", vocId);
query.setInteger("from", firstResult);
query.setInteger("size", pageSize);
query.addEntity("vW", VocabularyWord.class);
query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
// If unccoment the next word I get error: Column 'id1_1_1_' not found. But as I'have understood I don't need this.
// query.addEntity("m", Meaning.class);
List l = query.list();
return l;
}
And this method returns exactly what I want. BUT to return this result hibernate executes a huge amount of queries. (He try to do something like (1)?). Being exactly Hibernate executes 5 next queries:
Hibernate:
SELECT
vW.id as id1_7_0_,
vW.original_text as original2_7_0_,
vW.transcription as transcri3_7_0_,
vW.vocabulary_id as vocabula4_7_0_,
m.*
FROM
(SELECT
*
FROM
vocabulary_words AS vw
WHERE
vw.vocabulary_id=? LIMIT ?,?) AS vW,
meaning AS m
WHERE
vW.id=m.vocabulary_words_id;
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
My VocabularyWord entity:
public class VocabularyWord implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private int id;
#Column(name = "vocabulary_id", updatable = false, nullable = false)
private int vocId;
#Column(name = "original_text", nullable = false)
private String originalText;
#Column(name="transcription", nullable = true)
private String transcription;
#OneToMany (mappedBy = "vocWord", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Meaning> meaning;
// + getters and setters
}
My Meaning entity:
public class Meaning implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "vocabulary_words_id", nullable = false, updatable = false)
private VocabularyWord vocWord;
#JsonIgnore
#Column(name = "vocabulary_words_id", updatable = false, insertable = false)
private int vocWordId;
#Column(name = "w_type", nullable = true, unique = false)
private String wType;
#Column(name = "example", nullable = true, unique = false)
private String example;
#Column(name = "definition", nullable = true, unique = false)
private String definition;
#Column(name = "translation", nullable = true, unique = false)
private String translation;
//+ getters and setters
}
How can I fix this? Or, probably, have I choose the other way to realize that query?
I will appreciate any help, propositions, ideas and related links. Thanks in advance.
I have resolved my problem by changing my query and adding .addJoin() and .addRootEntity(). So now my method getWords looks next:
#Override
#Transactional
public List<VocabularyWord> getWords(int vocId, int firstResult, int pageSize) {
Session s = this.template.getSessionFactory().getCurrentSession();
SQLQuery query = s.createSQLQuery("SELECT {vW.*}, {m.*} FROM (SELECT * FROM vocabulary_words AS vw WHERE vw.vocabulary_id=:id LIMIT :from,:size) AS vW LEFT JOIN meaning AS m ON (m.vocabulary_words_id = vW.id);");
query.addEntity("vW", VocabularyWord.class)
.addJoin("m", "vW.meaning")
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.setInteger("id", vocId)
.setInteger("from", firstResult)
.setInteger("size", pageSize);
query.addRoot("vW", VocabularyWord.class);
List l = query.list();
return l;
}
This method executes exactly one query:
Hibernate:
SELECT
vW.id as id1_7_0_,
vW.original_text as original2_7_0_,
vW.transcription as transcri3_7_0_,
vW.vocabulary_id as vocabula4_7_0_,
m.vocabulary_words_id as vocabula6_1_0__,
m.id as id1_1_0__,
m.id as id1_1_1_,
m.definition as definiti2_1_1_,
m.example as example3_1_1_,
m.translation as translat4_1_1_,
m.vocabulary_words_id as vocabula6_1_1_,
m.w_type as w_type5_1_1_
FROM
(SELECT
*
FROM
vocabulary_words AS vw
WHERE
vw.vocabulary_id=? LIMIT ?,?) AS vW
LEFT JOIN
meaning AS m
ON (
m.vocabulary_words_id = vW.id
);
Related
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)
I have the following entity (example):
#Entity
#Table(name = "person")
public class Person implements Serializable {
#Id
#Column(name = "person_id", columnDefinition = "UUID")
private UUID userId;
#Column(name = "name")
private String name;
#ElementCollection
#MapKeyColumn(name = "phonetype")
#Column(name = "number")
#CollectionTable(name = "person_phones", joinColumns = #JoinColumn(name = "userId"))
private Map<String, String> phoneNumbers;
}
Now, the phoneNumbers are String, String in this example. Let's assume the key is the type (like "mobile", "home", "office", "fax", "pager"...) and the value is the actual number in any text format.
I'd like to query for a person which has two phone numbers:
Select * From person where
in his phone_numbers exists phonetype = 'home' and number = '0-123-456'
and also in his phone_numbers exists phonetype = 'mobile' and number = '9-876-421'
(and possibly, dynamically others)
and name = 'John'
I already constructed a sql subquery which works:
select home.userId from
(
(SELECT userId from person_phones
where (phonetype = 'home' and number = '0-123-456'))
) as home,
(
(SELECT userId from person_phones
where (phonetype = 'mobile' and number = '9-876-421'))
) as mobile
where home.userId = mobile.userId
As said, this is just a sql subquery. I'm writing JPA 2.1 criteria query in my project. And this seems oddly complicated. Can anyone give me a hint?
Had a similar problem, solved it using multiple inner joins rather than sub queries.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Person-Test");
EntityManager em = emf.createEntityManager();
Map<String, String> phoneNumbers = new HashMap<>();
phoneNumbers.put("home","0-123-456");
phoneNumbers.put("mobile","9-876-421");
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> personRoot = query.from(Person.class);
query.select(personRoot);
phoneNumbers.forEach((k, v) -> {
MapJoin<Person, String, String> phoneNrJoinJoin = personRoot.joinMap("phoneNumbers");
phoneNrJoinJoin.on(cb.equal(phoneNrJoinJoin.key(), k), cb.equal(phoneNrJoinJoin.value(), v));
});
query.where(cb.equal(personRoot.get("name"), "John"));
List<Person> people = em.createQuery(query).getResultList();
This results in the following hibernate query (renamed aliases for clarity)
SELECT person.person_id, person.name
FROM person
INNER JOIN person_phones a
ON person.person_id = a.userid
AND (a.phonetype = ? AND a.NUMBER = ?)
INNER JOIN person_phones b
on person.person_id=b.userId
and (b.phonetype=? and b.number = ? )
WHERE
person.name = ?;
Which returns all tuples of type person where all the mentioned phone numbers match.
I have following class in one to one relationship
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PERSON_ID")
private int personId;
#Column(name = "PERSON_NAME", nullable = false, length = 30)
private String personName;
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
private DrivingLicense drivingLicense;
}
#Entity
#Table(name = "DRIVING_LICENSE")
public class DrivingLicense {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "LICENSE_NUMBER")
private int licenseNumber;
#Column(name = "DATE_OF_ISSUE")
private Date dateOfIssue;
#OneToOne
#JoinColumn(name = "PERSON_ID", unique = true)
private Person person;
}
currently there are 3 rows in each table
but when I do a query on person like below
Query query = entityManager.createQuery("from Person p");
after getting resultlist its resulting in too many queries like below;
Hibernate: select person0_.PERSON_ID as PERSON_ID1_1_, person0_.PERSON_NAME as PERSON_NAME2_1_ from PERSON person0_
Hibernate: select drivinglic0_.LICENSE_NUMBER as LICENSE_NUMBER1_0_1_, drivinglic0_.DATE_OF_ISSUE as DATE_OF_ISSUE2_0_1_, drivinglic0_.PERSON_ID as PERSON_ID3_0_1_, person1_.PERSON_ID as PERSON_ID1_1_0_, person1_.PERSON_NAME as PERSON_NAME2_1_0_ from DRIVING_LICENSE drivinglic0_ left outer join PERSON person1_ on drivinglic0_.PERSON_ID=person1_.PERSON_ID where drivinglic0_.PERSON_ID=?
Hibernate: select drivinglic0_.LICENSE_NUMBER as LICENSE_NUMBER1_0_1_, drivinglic0_.DATE_OF_ISSUE as DATE_OF_ISSUE2_0_1_, drivinglic0_.PERSON_ID as PERSON_ID3_0_1_, person1_.PERSON_ID as PERSON_ID1_1_0_, person1_.PERSON_NAME as PERSON_NAME2_1_0_ from DRIVING_LICENSE drivinglic0_ left outer join PERSON person1_ on drivinglic0_.PERSON_ID=person1_.PERSON_ID where drivinglic0_.PERSON_ID=?
Hibernate: select drivinglic0_.LICENSE_NUMBER as LICENSE_NUMBER1_0_1_, drivinglic0_.DATE_OF_ISSUE as DATE_OF_ISSUE2_0_1_, drivinglic0_.PERSON_ID as PERSON_ID3_0_1_, person1_.PERSON_ID as PERSON_ID1_1_0_, person1_.PERSON_NAME as PERSON_NAME2_1_0_ from DRIVING_LICENSE drivinglic0_ left outer join PERSON person1_ on drivinglic0_.PERSON_ID=person1_.PERSON_ID where drivinglic0_.PERSON_ID=?
clearly for fetching 3 rows hibernate fired 4 queries, how to solve this problem? am I doing something wrong with relationships?
update
now if I fetch with Driving license like
Query query = entityManager.createQuery("from DrivingLicense dl");
its even more worse, 7 queries are fired.
To have a JPA vendor independent solution you could use following JPQL queries.
get all Person with a DrivingLicense
Query query = entityManager.createQuery("from Person p join fetch p.drivingLicense");
get all Person idependent if they have a DrivingLicense or not
Query query = entityManager.createQuery("from Person p left join fetch p.drivingLicense");
or using the Criteria API
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteriaQuery.from(Person.class);
root.fetch("drivingLicense", JoinType.INNER);
criteriaQuery.select(root);
List<Person> resultList = em.createQuery(criteriaQuery).getResultList();
resp.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteriaQuery.from(Person.class);
root.fetch("drivingLicense", JoinType.LEFT);
criteriaQuery.select(root);
List<Person> resultList = em.createQuery(criteriaQuery).getResultList();
This seems due to your query, i.e
Query query = entityManager.createQuery("from Person p");
Instead use:
session.createCriteria();
This will fire only 1 query to fetch Person:
Hibernate: select this_.id as id1_1_1_, this_.name as name2_1_1_,
drivinglic2_.id as id1_0_0_, drivinglic2_.DL_no as DL_no2_0_0_,
drivinglic2_.PERSON_ID as PERSON_I3_0_0_ from PERSON this_ left outer
join DRIVING_LICENSE drivinglic2_ on this_.id=drivinglic2_.PERSON_ID
order by this_.id asc
My code looks like:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final TypedQuery<ObjectTO> createQuery = criteriaBuilder.createQuery(ObjectTO.class);
...
// Logic to create search query
...
// On this line performance is very low. Takes around 200 seconds for 3000 records.
final List<ObjectTO> inhibits = createQuery.getResultList();
Generated SQL (example with reduced columns in select clause) looks like:
SELECT t1.COL1 ,
t1.COL2 ,
t1.COL3 ,
t2.COL1 ,
t2.COL2 ,
t3.COL2
FROM Table1 t1
INNER JOIN Table2 t2,
ON t1.COL1 = t2.COL1
LEFT OUTER JOIN Table3 t3
ON t1.COL1 = t3.COL1
WHERE (t1.COL2 LIKE 'test')
AND ( t1.COL3 >= SYSDATE
OR t1.COL3 = to_Date('1901.01.01 00:00:00', 'yyyy-mm-dd HH24:mi:ss') )
ORDER BY t1.COL1 ASC ,
t1.COL3 ASC ,
t2.COL2 ASC;
Same query when executed in SQL developer, takes around 0.3 seconds. Please note that same query if executed using java.sql.Statement.executeQuery(), takes around 0.4 seconds to fetch data from database. Please note that database is Oracle 11g. JDK version is 1.7.0_51.
JPA entity for Table1 looks like:
#Entity
#Table(name = "Table1")
public class Table1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "COL1", insertable = false, updatable = false)
private String col1;
#Column(name = "COL3", insertable = false, updatable = false)
private Timestamp col3;
#Column(name = "COL2")
private String col2;
#OneToMany(mappedBy = "col1", cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
private Set<Table2> table2;
#OneToMany(mappedBy = "col1", cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
private Set<Table3> table3;
...
...
}
Can you please provide me with an hint regarding where shall I start looking to improve performance in this scenario? Please let me know, if you require more information.
I have the following two different HQL statements.
My Data Structure looks like this:
User
#Entity (name = "User")
public class User
{
#Id
#GeneratedValue
#Column (name = "id")
private int id;
#Column (name = "user_name")
private String username;
#Column (name = "password")
private String password;
#Column (name = "enabled")
private boolean enabled;
#ManyToMany (targetEntity = Role.class, cascade =
{
CascadeType.ALL
})
#JoinTable (name = "user_role", joinColumns =
{
#JoinColumn (name = "user_id")
}, inverseJoinColumns =
{
#JoinColumn (name = "role_id")
})
private Set<Role> roles;
/* getters and setters)
}
To cut it short the only difference between the two queries is that one is ASC the other is DESC
#NamedQuery (name = "user.getUsersOrderByRoleAsc",
query = "FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name ASC"),
#NamedQuery (name = "user.getUsersOrderByRoleDesc",
query = "FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name DESC"),
The query for ASC returns: A list of Users -> As I would expect.
The query of DESC returns: An List of Object[], and in each object the [0] is the User, while the [1] is just another null object.
That does not make any sense to me. How can simply changing ASC to DESC change the structure of the result set ?
I am using Hibernate 4.3.6.Final.
The fastest way to determin, what went wrong is to set the show_sql flag to true in you hibernate configuration file. This will log every rendered query.
See Hibernate show real SQL
Probably there is some Hibernate bug you bumped into, but because you are join fetching a one to many children collections, it's safer to use distinct as well:
#NamedQuery (name = "user.getUsersOrderByRoleAsc",
query = "select distinct u FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name ASC"),
#NamedQuery (name = "user.getUsersOrderByRoleDesc",
query = "select distinct u FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name DESC")