How to use hibernate query on two tables (join table)? - java

I have these tables: cont(id_cont, user, pass)
emp(emp_id, name, cont_id_cont (fk))
#Entity
#Table(name = "emp", catalog = "", uniqueConstraints = {
#UniqueConstraint(columnNames = "cont_id_cont") })
public class Emp implements java.io.Serializable{
private int id_emp;
private ContUser contUser;
private String name;
and
#Entity
#Table(name = "cont", catalog = "", uniqueConstraints = {
#UniqueConstraint(columnNames = "pass") })
public class Cont implements java.io.Serializable{
private int id_cont;
private String user;
private String pass;
private Set<Emp> empForCont = new HashSet<Emp>(0);
}
Now: I want this query:
select cont.user, emp.name, emp.cont_id_cont from cont
inner join emp on cont.id_cont= emp.cont_id_cont where cont.user = 'gbs04405';

This style of accessing data is not what ORM is intended for. If you are using Hibernate, you should (usually) access data through objects. To make this work, instead of embedding SQL like constraints, define relations #OneToMany, #ManyToOne, and/or #ManyToMany between objects where necessary.
In addition, you should consider using HQL (or JPQL) instead of pure SQL, to achieve what you want.
It should be something like this:
SELECT e FROM Emp e JOIN e.contUser u WHERE u.user = :userstring
You can check here for further JPQL syntax.

Related

Join a many to many relation with QueryDSL and JPASQLQuery

I have the following entities:
#AllArgsConstructor
#EqualsAndHashCode(of = {"name"})
#Data
#NoArgsConstructor
#Entity
#Table(schema = "eat")
public class Pizza {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="pizza_id_seq")
private Integer id;
#NotNull
private String name;
#NotNull
#Positive
private Double cost;
#ManyToMany
#JoinTable(schema = "eat",
name = "pizza_ingredient",
inverseJoinColumns = { #JoinColumn(name = "ingredient_id") })
private Set<Ingredient> ingredients;
}
#AllArgsConstructor
#EqualsAndHashCode(of = {"name"})
#Data
#NoArgsConstructor
#Entity
#Table(schema = "eat")
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="ingredient_id_seq")
private Integer id;
#NotNull
#Size(min=1, max=64)
private String name;
}
I'm using JPASQLQuery object provided by QueryDSL (4.2.2) to create some native queries in PostgreSQL:
public JPASQLQuery<T> getJPASQLQuery() {
return new JPASQLQuery<>(
entityManager,
PostgreSQLTemplates.builder().printSchema().build()
);
}
The problem comes trying to use join functions, for example:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath("pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
JPASQLQuery subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
// The error is in next innerJoin
.innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
.where(ingredient.name.in(ingredientNames));
If I keep the current innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient) I receive:
class com.querydsl.core.types.dsl.SetPath cannot be cast to class com.querydsl.core.types.SubQueryExpression
I cannot remove current (SubQueryExpression<?>) because innerJoin doesn't accept SetPathas parameter.
On the other hand, the following:
.from(pizza)
.innerJoin(ingredient)
Doesn't work due to pizza_ingredient is not included in the generated query.
How can I use innerJoin in JPASQLQuery with a many to many relationship like above?
Basically, there are two main approaches trying to solve it:
Include required native functions
As suggest one QueryDSL developer here, replacing JPASQLQuery by JPA alternatives.
Create required Path for many to many table
First is important to add name property into every #Table annotation because internally is the one used by QueryDSL NativeSQLSerializer class to generate from and join clauses.
So, for example:
#Table(schema = "eat")
public class Pizza ...
Should be replaced by:
#Table(name = "pizza", schema = "eat")
public class Pizza ...
Next, create for custom Path for the many to many table:
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
So the complete code would be:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath( "pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
NumberPath<Long> rnk = Expressions.numberPath(Long.class, "rnk");
SubQueryExpression subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
.innerJoin(pizzaIngredient).on(pizzaIngredient_PizzaId.eq(pizza.id))
.innerJoin(ingredient).on(ingredient.id.eq(pizzaIngredient_IngredientId))
.where(ingredient.name.in(ingredientNames));
return getJPASQLQuery()
.select(ingredientPath, pizzaPath, costPath)
.from(
subQuery,
Expressions.stringPath("temp")
)
.where(rnk.eq(1l))
.fetch();

How to fetch only columns that satisfy the condition in one-to-many relationship in QueryDSL?

I have two following entities:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "affiliate_programs")
#SequenceGenerator(name = AbstractEntity.GENERATOR, sequenceName = "affiliate_programs_seq", allocationSize = 1)
public class AffiliateProgram extends AbstractAuditableDeletableEntity {
private static final int DESCRIPTION_LENGTH = 512;
#Column(nullable = false)
private String title;
#OneToMany(mappedBy = "affiliateProgram", fetch = FetchType.LAZY)
private Set<AffiliateProgramStatistics> statistics;
public enum SortType implements ISortType {
ID(QAffiliateProgram.affiliateProgram.id),
TITLE(QAffiliateProgram.affiliateProgram.title),
#Getter
private ComparableExpressionBase[] expressions;
SortType(final ComparableExpressionBase... expressions) {
this.expressions = expressions;
}
}
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "affiliate_programs_statistics")
#SequenceGenerator(name = AbstractEntity.GENERATOR, sequenceName = "affiliate_programs_statistics_seq", allocationSize = 1)
public class AffiliateProgramStatistics extends AbstractAuditableEntity {
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
private AffiliateProgram affiliateProgram;
#Enumerated(EnumType.STRING)
private EventType eventType;
private LocalDate date;
public enum EventType {
MERCHANTS,
PRIORITY_MERCHANTS,
COUPONS,
CLICKS
}
}
I am trying to fetch only the columns from AffiliateProgramStatistics that match the SQL between condition. My SQL query looks like this:
select *
from affiliate_programs ap
left join affiliate_programs_statistics aps on ap.id = aps.affiliate_program_id
where ap.deleted = false and aps.date between '2020-07-20' and '2020-08-20';
And the result of this query is exactly what I need - I get only columns that are NOT marked as deleted AND columns with date BETWEEN required dates.
I tried to write that query in QueryDSL and that's what I came up with:
#Repository
#RequiredArgsConstructor
public class AffiliateProgramsCustomRepositoryImpl implements AffiliateProgramsCustomRepository {
private final EntityManager entityManager;
#Override
public Page<AffiliateProgram> search(final AffiliateProgramSearchForm form) {
final QAffiliateProgram affiliateProgram = QAffiliateProgram.affiliateProgram;
final QAffiliateProgramStatistics affiliateProgramStatistics = QAffiliateProgramStatistics.affiliateProgramStatistics;
final JPAQuery<AffiliateProgram> query = new JPAQuery<AffiliateProgram>(entityManager)
.distinct()
.from(affiliateProgram)
.leftJoin(affiliateProgram.statistics, affiliateProgramStatistics)
.where(AffiliateProgramsRepositoryHelper.getPredicates(form))
.orderBy(AffiliateProgramsRepositoryHelper.getOrders(form.getSorting()))
.limit(form.getLimit())
.offset(form.getOffset());
return AffiliateProgramsRepositoryHelper.pageBy(query, form);
}
}
public class AffiliateProgramsRepositoryHelper extends RepositoryHelper {
public static Predicate[] getPredicates(final AffiliateProgramSearchForm form) {
final QAffiliateProgramStatistics affiliateProgramStatistics = QAffiliateProgramStatistics.affiliateProgramStatistics;
final List<Predicate> predicates = new ArrayList<>();
final String formattedQuery = form.getFormattedQuery();
if (!isNullOrEmpty(formattedQuery)) {
predicates.add(affiliateProgramStatistics.affiliateProgram.title.likeIgnoreCase(formattedQuery));
}
predicates.add(affiliateProgramStatistics.date.between(form.getFrom(), form.getTo()));
predicates.add(affiliateProgramStatistics.affiliateProgram.deleted.isFalse());
return predicates.toArray(new Predicate[0]);
}
}
But the result of this is not satisfying. If at least one of the columns in AffiliateProgramStatistics match the between() condition, it fetches every single column from the table that matches tje leftJoin() condition.
How can I fetch only the columns that I need?
P.S. Hibernate generates the following query:
Hibernate:
select distinct
affiliatep0_.id as id1_0_,
affiliatep0_.created_date_time as created_2_0_,
affiliatep0_.last_modified_date_time as last_mod3_0_,
affiliatep0_.deleted as deleted4_0_,
affiliatep0_.clicks_count as clicks_c5_0_,
affiliatep0_.coupons_count as coupons_6_0_,
affiliatep0_.description as descript7_0_,
affiliatep0_.merchants_count as merchant8_0_,
affiliatep0_.priority_merchants_count as priority9_0_,
affiliatep0_.priority_order as priorit10_0_,
affiliatep0_.title as title11_0_
from affiliate_programs affiliatep0_
inner join affiliate_programs_statistics statistics1_ on affiliatep0_.id=statistics1_.affiliate_program_id
cross join affiliate_programs affiliatep2_
where statistics1_.affiliate_program_id=affiliatep2_.id
and (statistics1_.date between ? and ?)
and affiliatep2_.deleted=?
order by affiliatep0_.title desc nulls last limit ?
which works perfectly and fetches only the data I need if i run it in console
JPA supports the ON clause in JPQL since 2.1, and QueryDSL is able to generate that ON clause in queries. Hibernate had a precedessor for the ON clause in the form of the now deprecated WITH clause. The ON clause can be used in more occasions.
Just use .on(Predicate) immediately after the join on which it should be applied:
final JPAQuery<AffiliateProgram> query = new JPAQuery<AffiliateProgram>(entityManager)
.distinct()
.from(affiliateProgram)
.leftJoin(affiliateProgram.statistics, affiliateProgramStatistics)
.on(AffiliateProgramsRepositoryHelper.getPredicates(form))
.orderBy(AffiliateProgramsRepositoryHelper.getOrders(form.getSorting()))
.limit(form.getLimit())
.offset(form.getOffset());

hibernate native query complex constructor mapping

Java, Spring Data JPA
I have 2 entities:
class Source {
Integer id;
String name;
}
class Item {
Integer id;
String name;
Integer sourceId;
}
I need statistic native query result like this:
select s.id source_id, s.name source_name, count(i.id) item_count
from source s
left join item i on s.id = i.source_id
group by s.id
And i want to have result in Java object MyResult:
class MyResult {
Source source;
Integer itemCount;
MyResult(Source source, Integer itemCount) {...}
}
The closest solution is using #SqlResultSetMapping like this:
#SqlResultSetMapping(
name = "MyResultMapping",
entities = {
#EntityResult(
entityClass = Source.class,
fields = {
#FieldResult(name = "id", column = "source_id"),
#FieldResult(name = "name", column = "source_name"),
}
),
...
???
}
)
OR
#SqlResultSetMapping(
name = "MyResultMapping",
classes = {
#ConstructorResult(
targetClass = MyResult.class,
columns = {
#ColumnResult(name = "???"),
???
}
)
}
)
With second variant i can use something like this:
MyResult(Integer sourceId, String sourceName, Integer itemsCount) {
this.source = new Source(sourceId, sourceName);
this.itemsCount = itemsCount;
}
but i want it to automate with #SqlResultSetMapping... (because my real objects more complex)
With Spring Data JPA it's better to use projections to achieve you need, for example:
public interface SourceWithItemCount {
Source getSource();
Integer getItemCount();
}
Then in your Source repository create HQL query method, like this:
public interface SourceRepo extends JpaRepository<Source, Integer> {
#Query("select s as source, count(i) like itemCount from Source s left join Item i on i.sourceId = s.id group by s"
List<SourceWithItemCount> getSourcesWithItemCount();
}
Important note is to use aliases for returned values (s as source etc.) it's allows Spring Data JPA to map them to projections properties.
Join on <condition> works from Hibernate version 5.1+ (if I'm not mistaken) so I recommend you to create classic one-to-many relation between your objects, like this, for example:
#Entity
class Source {
#Id private Integer id;
private String name;
#OneToMany #JoinColumn(name = "source_id") private List<Item> items;
}
#Entity
class Item {
#Id private Integer id;
private String name;
}
Then create JPQL query method supported by all versions of Hibernate (and other ORM providers):
#Query("select s as source, count(i) like itemCount from Source s left join s.items i group by s"
List<SourceWithItemCount> getSourcesWithItemCount();

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.

How Join hibernate Value objects using HQL query?

Hi i'm New to write HQL Query please help me.....
my Hibernate have three ValueObjects ie.
#Entity
#Table(name="user")
public class UserVO {
#Id
#Column(name="S_ID")
private String s_id;
#Column(name="FIRSTNAME")
private String firstName;
private String email;
}
CourseVO class
#Entity
#Table(name="course")
public class CourseVO
{
#Id
#Column(name="S_ID")
public String s_id;
#Column(name="NAME")
public String name;
}
Skillset VO
#Entity
#Table(name="skillset")
public class SkillsetVO
{
#Id
#Column(name="S_ID")
public String s_id;
#Column(name="COURSE_ID")//Foreign Key "USER"
public String course_id;
#Column(name="USER_ID")//Foreign key "COURSE"
public String user_id;
#Column(name="TEACH_EXP")
public String teach_Exp;
}
Now How to get Values of FirstName,NAME,TEACH_EXP values using EMAIL of USER table using HQL query
If you want to use join syntax in HQL you must work out your mapping accordingly as joins are enabled by mapping.
#Entity class A { #OneToMany private List<B> bs; }
#Entity class B { #Basic int n; }
Enables
select b from A a inner join a.b where a = :id
But with your mapping this is not possible. Also bear in mind that in terms of efficiency most of the RDBMs will perform an inner join on a where a.id = b.id.
select u.firstName, c.name, s.teach_Exp
from UserVO u, CourseVO c, SkillsetVO s
where
u.s_id = s.user_id
and c.s_id = s.course_id
and u.email = :email
But I think that you must review you association. Since looks to me that SkillsetVO.user_id should be SkillsetVO.User (an association to a UserVO entity and same for CourseVO).

Categories