Required Help to join three entity on different columns using Hibernate Criteria - java

Updated Question
I have 3 tables like
Section (id, title, url)//id is Primary key
Category (id, title)//id is Primary key
SubCategory (id, title)//id is Primary key
Here, I want to join these table as in simple query like
Select * From Category cat, Section se, SubCategory subCat WHERE
subCat.id=23456 AND subCat.category=cat.id AND subCat.section = se.id
How can I achieve the above query using Criteria in Hibernate? Can
anyone help me on this?
Adding my question here
My entity Files are as:
#Entity
#Table(name="section")
public class SectionEntity{
private int id;
private String title;
//Getter & setter method
}
#Entity
#Table(name="category")
public class CategoryEntity{
private int id;
private String title;
private Set<SubCategoryEntity> subCategory;
//Getter & setter method
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name="category", insertable=false, updatable=false)
public Set<SubCategoryEntity> getSubCategory(){
return this.subCategory;
}
}
Updated SubCategoryEntity
#Entity
#Table(name="subcategory")
public class SubCategoryEntity{
private int id;
private String title;
private Set<SectionEntity> sectionEntity;
//Getter & setter method
#OneToMany(fetch=FetchType.LAZY)
#JoinColumn(name="id", insertable=false, updatable=false)
public Set<SectionEntity> getSectionEntity(){
this.section;
}
}
Steps to use Hibernate Criteria as:
Step 1: Creating criteria for Category Entity
Criteria categoriesCriteria = session.createCriteria(CategoriesEntity.class, "categoryEntity");
Step 2: Creating aliases of SubCategoryEntity and SectionEntity
categoriesCriteria.createAlias("categoryEntity.subCategoryEntity", "subCatEntity");
categoriesCriteria.createAlias("subCatEntity.sectionsEntity", "subCatSectionEntity");
Step 3: Set the property in projection list
Step 4: Add Restriction as:
categoriesCriteria.add(Restrictions.eq("subCatEntity.id", primCategoryId));
Step 5: Set projection property into CategoryEntity Criteria
categoriesCriteria.setProjection(projPropList);
Step 6: Getting result
categoriesCriteria.list();
My resulting query displaying as:
select this_.id as y0_, this_.title as y1_, this_.sef_url as y2_, subcatenti1_.id as y3_, subcatenti1_.title as y4_, subcatenti1_.sef_url as y5_
from jos_categories this_
inner join jos_subcategories subcatenti1_ on this_.id=subcatenti1_.category
inner join jos_sections subcatsect2_ on subcatenti1_.id=subcatsect2_.id
where subcatenti1_.id=?
But I have required expected query as:
select this_.id as y0_, this_.title as y1_, this_.sef_url as y2_, subcatenti1_.id as y3_, subcatenti1_.title as y4_, subcatenti1_.sef_url as y5_
from jos_categories this_
inner join jos_subcategories subcatenti1_ on this_.id=subcatenti1_.category
inner join jos_sections subcatsect2_ on subcatenti1_.section=subcatsect2_.id
where subcatenti1_.id=?
How Can I achieve this, can anyone help me on this?

In your SubCategoryFile make change like this,
1) remove "private int section;" and all getter and setter method of this.
2) and use this
#JoinColumn(name="section", insertable=false, updatable=false)
public Set<SectionEntity> getSection(){
this.section;
}
and then try to run. I hope it will work

Related

How to filter select from table by another table by exclusion principle

My application under Spring Boot v1.5.7
I have 3 entities (schematically):
#Entity
public class Word {
#Id
#GeneratedValue
private Integer id
...
}
#Entity
public class UserWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
#Entity
public class UserAnotherWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
And now I need to select all Words for User, but exclude Words placed in user's lists
Native SQL for user_id=1 is
select *
from Word w
left join UserWordList uwl
on w.id = uwl.word_id and uwl.user_id = 1
left join UserAnotherWordList uawl
on w.id = uawl.word_id and uawl.user_id = 1
where uwl.word_id is NULL
and uawl.word_id is NULL
What is a best way to do it? Ideally I would like to use Spring Data features or HQL, but I don't understand how...
UPD
I solve my problem with native query:
#Entity
#NamedNativeQuery(
name = "User.getWordsToProcess",
resultClass = Word.class,
query = "<...native query to select Words...>"
)
public class User {...}
...
public interface UserRepository extends CrudRepository<User, Integer> {
List<Word> getWordsToProcess(Integer userId);
}
Fastest answer is Criteria api (but that is deprecated in hibernate 5.2 and above.)
So you can use Hql :
getSession().createQuery(" select * from UserWordList u left join fetch u.word
left join fetch u.user").list()
And you can use union or create another query to fetch UserAnotherWordList.
Also you can set any restrictions in Hql like below:
Query query = getSession().createQuery(" select * from UserWordList u left join fetch u.word left join fetch u.user us where us.user = :sample").list();
query.setParameter("sample",value);
query.list();

Hibernate #ManyToOne

It`s possible to create one map with hibernate #ManyToOne just like this:
public class IndicadorAtos {
#JsonIgnore
#Id
#Column(name="cod_ato_praticado")
private Integer codAtoPraticado;
#Column(name="descricao_ato")
private String ato;
#JoinColumn(name = "cod_ato", referencedColumnName = "cod_ato")
#ManyToOne
#Fetch(FetchMode.SUBSELECT)
private Atos atos;
}
But in some cases I dont have association or in my table IndicadorAtos have one code, that don`t existis in table Atos
this is my tables:
create table IndicadorAtos (
codAtoPraticado integer primary key,
ato varchar(250),
cod_ato integer
);
create table Atos(
cod_ato integer primary key.
name varchar(250)
)
I try to create this join:
Select t FROM IndicadorAtos t , Atos a where t.cod_ato = a.cod_ato, but I need to return all records from my IndicadorAtos, and with this select he only return all itens that have one item in Atos.
tks
It`s possible to create one map with hibernate #ManyToOne
Yes; it is called unidirectional relationship.
If I understood your question properly, you want to select all entries from IndicadorAtos with possibly associated entries from Atos. You can achieve this by using left join as follows:
SELECT t FROM IndicadorAtos t LEFT JOIN t.atos at
provided that you have an entity Atos defined like:
#Entity
public class Atos {
#Id #GeneratedValue
private int cod_ato;
private String name;
// getters and setters
}

Criteria Add all the tables from the entity class when it only needs part of it

when i create a count query with hibernate - Criteria - add all the possible table from the entity class as left join which is bad performance .
The entity :
#Entity
#Table(name = "employees")
Public Class Employees {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "lz_job_stat_id")
private Integer id;
#ManyToOne
#JoinColumn(name = "departments_id")
private Departments departments;
#ManyToOne
#JoinColumn(name = "managers_id")
private Managers managers;
}
And the criteria :
public class EmployeeDao {
public List<EmpDao> findIt(){
.....
Criteria crit = createEntityCriteria().setFetchMode("departments", FetchMode.SELECT);
crit.add(Restrictions.eq("managers.deleted", false));
crit.setProjection(Projections.count("id"));
return crit.list();
}
}
And the produced SQL :
select count() as y0_
from employees this_
left outer join departments department3_
on this_.department_id=department3_.department_id
left outer join managers manager2_
on this_.manager_id=manager2_.manager_id
now when i try the crit.list - it create a left join for all the possible tables.
when its not supposed to create a join for all of them.
isnt Criteria smart enought to know i dont need this tables ? only the one i use the "WHERE CLAUSE"
is there a way to explicitly tell Criteria "DO NOT JOIN THIS TABLES !!!"
without SQL
Specify fetch type on ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "departments_id")
private Departments departments;
or IMHO more preferably in criteria:
criteria.setFetchMode("departments", FetchMode.SELECT)

Hibernate: separate sql query for every collection

I have a Person class that has a collection of Contacts. Everything works ok, I get the list of persons with their contacts. However, in log I see that a separate query is made to read collection of every person. That is too bad.
How to make hibernate make a join to read all the data in one query? I use JPA.
This is the person class:
#Entity
#Table(name = "tbl1")
public class PersonItem implements Serializable{
#Id
#Column(name="col1")
private String guid;
.....
#ElementCollection(targetClass = ContactItem.class,fetch=FetchType.EAGER)
#CollectionTable(name="tbl2",joinColumns=#JoinColumn(name="col2"))
private List<ContactItem> contacts;
....
}
This is the contact class
#Embeddable
#Table(name = "tbl2")
public class ContactItem implements Serializable {
#Column(name="col1")
private String guid;
#Column(name="col3")
private String info;
}
This is the way I get the list of persons:
Query query = em.createQuery("Select p from PersonItem p WHERE p.guid IN (:guids)");
query.setParameter("guids", guids);
List<PersonItem> list=query.getResultList();
And this what I see in log (I have three persons in DB):
Hibernate: select personitem0_.col1 as col1_0_, personitem0_.col4 as col2_0_, personitem0_.col2 as col3_0_, personitem0_.col3 as col4_0_ from tbl1 personitem0_ where personitem0_.col1 in (? , ? , ?)
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Please, begin from a more simple mapping. Use plural names, and column prefixes.
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column(name = "f_guid")
private String guid;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Contact> contacts;
}
#Entity
#Table(name = "contacts")
public class Contact {
#Id
#Column(name = "f_guid")
private String guid;
#Column(name = "f_info")
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_person")
private Person person;
}
Person is associated to contacts by a foreign key fk_person in the contacts table.
Update
Looks like JPQL overrides a default fetching strategy. You need to specify a fetch explicitly
select p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
If you have duplicates, cause of joins, you can use distinct
select distinct p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
Try #Fetch on your relation.
Also i would suggest to use #OneToMany relation int this case
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN) //You can use SUBSELECT as well
private List<ContactItem> contacts;
You can read more about fetching strategies here
fetch-“join” = Disable the lazy loading, always load all the collections and entities.
fetch-“select” (default) = Lazy load all the collections and entities.
batch-size=”N” = Fetching up to ‘N’ collections or entities, Not record.
fetch-“subselect” = Group its collection into a sub select statement.

JPA Join on specific field

I have this scenario:
User and his related UserRole entity classes, as below:
#Entity
#Table(name="USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", unique=true, nullable=false)
private int id;
#Column(name="USERNAME", unique=true, nullable=false, length=255)
private String username;
#OneToMany(mappedBy="user")
private List<UserRole> userRoles;
}
and
#Entity
#Table(name="user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="user_role_id", unique=true, nullable=false)
private int userRoleId;
#Column(nullable=false, length=45)
private String role;
#ManyToOne
#JoinColumn(name="username", nullable=false)
private User user;
}
Now, I need to query all users that have a specific role. I'm trying making join with JPA Specifications, like that:
Join<User, UserRole> join = root.join(User_.userRoles);
Expression<String> match = join.get(UserRole_.role);
Predicate predicate = builder.equal(match, "ROLE_USER");
The problem is that the generated join will be between User.id and UserRole.username and the query will obviously have no results.
select count(user0_.ID) as col_0_0_ from USER user0_ inner join
user_roles userroles1_ on user0_.ID=userroles1_.username where
userroles1_.role='ROLE_USER'
I need instead to have the on clause both on username fields:
... from USER user0_ inner join
user_roles userroles1_ on user0_.USERNAME=userroles1_.username ...
I noticed that there is the .on method of Join class who:
Modify the join to restrict the result according to the specified ON
condition. Replaces the previous ON condition, if any. Return the join
object
Is this the correct approach? If so, how could I implement that?
Join<User, UserRole> join = root.join(User_.userRoles).on(????);
Thank you in advance.
UPDATE:
UserRole_ metamodel class
#StaticMetamodel(UserRole.class)
public class UserRole_ {
public static volatile SingularAttribute<UserRole, Integer> userRoleId;
public static volatile SingularAttribute<UserRole, String> role;
public static volatile SingularAttribute<UserRole, User> user;
}
User_ metamodel class:
#StaticMetamodel(User.class)
public class User_ {
public static volatile SingularAttribute<User, Integer> id;
public static volatile SingularAttribute<User, String> username;
public static volatile ListAttribute<User, UserRole> userRoles;
}
You need to use referencedColumnName:
#ManyToOne
#JoinColumn(name="username", referencedColumnName="username", nullable=false)
private User user;
With only #JoinColumn(name="username") you tell Hibernate that the join column in user_roles is named username - but it still expects that it contains the values of the #Id property of User. If you create the DDL for your schema you will see that Hibernate generates a number column for user_roles.username.
Once again, you should check what the user_roles.username column contains, because by default, it contains the #Id column of the referenced entity, here the referenced entity is User, and its #Id column is id (in your schema: ID) and not username (in your schema: USERNAME).
Although, here's an example of how to write what you described here:
The problem is that the generated join will be between User.id and
UserRole.username and the query will obviously have no results. I need
instead to have the on clause both on username fields:
In JPQL (using JPA 2.1)
// get entity manager as 'em'
TypedQuery<User> q = em.createQuery("SELECT u FROM User u INNER JOIN UserRole ur ON ur.user = u.username WHERE ur.role = 'ROLE_USER'", User.class);
List<User> results = q.getResultList();

Categories