I have the following entity:
#Entity(name = "game_users")
public class GameUser {
private GameUsersPK primaryKey;
#EmbeddedId
public GameUsersPK getPrimaryKey() {
return primaryKey;
}
...
}
With the following PK:
#Embeddable
public class GameUsersPK implements Serializable {
#ManyToOne
private Game game;
#ManyToOne
private User user;
...
}
When I query for a GameUser by executing:
GameUser gameUser = em.createQuery("from game_users", GameUser.class).setMaxResults(1).getSingleResult();
I notice Hibernate is executing two queries - one from game_users and one from games left outer join users.
Can I make Hibernate fetch all entities in one query - from game_users, games, users?
Thanks.
select gu from GameUser gu
left join fetch gu.primaryKey.game
left join fetch gu.primaryKey.user
Read the Hibernate documentation about HQL and associations.
Related
I have two (Hibernate-based) Spring Data JPA domain classes, the "One" side Customer.class:
#Entity
#Table(name = "sys_customer")
#Data
public class Customer implements Serializable {
#Id
#Column(name = "cust_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "cust_name")
private String customerName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
private Set<Order> orders;
}
and the "Many" side Order.class:
#Entity
#Table(name = "sys_order")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Order implements Serializable {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_name")
private String orderName;
#ManyToOne
#JoinColumn(name = "order_cust_id", referencedColumnName = "cust_id")
private Customer customer;
public Order( String orderName) {
this.orderName = orderName;
}
public Order(String orderName, Customer customer) {
this.orderName = orderName;
this.customer = customer;
}
}
I have OrderRepository interface which extends JpaRepository interface and JpaSpecificationExecutor interface:
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
}
I have a OrderSpecification.class with the static method searchByCustomerName:
public class OrderSpecification {
public static Specification<Order> searchByCustomerName(String customerName) {
return new Specification<Order>() {
#Override
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Join<Order, Customer> join = root.join("customer");
return criteriaBuilder.like(join.get("customerName"), "%" + customerName + "%");
//return criteriaBuilder.like(root.get("customer").get("customerName"), "%" + customerName + "%");
}
};
}
}
To find the differences between get("property") chain and Join, I wrote a simple test method and comment out the above OrderSpecificatin.class code
#Test
#Transactional
public void testFindOrderByCustomerName(){
String name = "adam";
List<Order> orders = orderRepository.findAll(OrderSpecification.searchByCustomerName(name));
for(Order order: orders){
Customer customer = order.getCustomer();
log.info(new StringBuilder().append(customer.getId()).append(" ").append(customer.getCustomerName()).toString());
}
}
I found that:
get("property") chain use a cross-join(which is very bad performancing) while Join use inner-join(since ManyToOne() by default is Fetch= FetchType.EAGER)
/* get("property") chain: Hibernate: select order0_.order_id as
order_id1_1_, order0_.order_cust_id as order_cu3_1_,
order0_.order_name as order_na2_1_ from sys_order order0_ cross join
sys_customer customer1_ where order0_.order_cust_id=customer1_.cust_id
and (customer1_.cust_name like ?) Hibernate: select customer0_.cust_id
as cust_id1_0_0_, customer0_.cust_name as cust_nam2_0_0_ from
sys_customer customer0_ where customer0_.cust_id=? */
/** * "Join": * Hibernate: select order0_.order_id as order_id1_1_,
order0_.order_cust_id as order_cu3_1_, order0_.order_name as
order_na2_1_ from sys_order order0_ inner join sys_customer customer1_
on order0_.order_cust_id=customer1_.cust_id where customer1_.cust_name
like ? * Hibernate: select customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_name as cust_nam2_0_0_ from sys_customer customer0_
where customer0_.cust_id=? */
My questions are:
Can I specify the Join type(inner, all three outers) or Fetch Type(LAZY, EAGER) when using get("property") chain approach to avoid cross-join?
What scenario/best practice should I use get("chain") or always stay in Join?
Does the approach OrderSpecification.class with static method obey a good OOP design pattern?
You can't specify the join type for paths. It will use INNER join semantics by default and that is mandated by the JPA specification. If you want a different join type, you will have to create joins explicitly. The fact that using get renders as cross joins is a limitation of the old query model of Hibernate, but Hibernate 6.0 will fix this. The semantics are the same though and the query planner of your database should be able to treat both queries the same way. Maybe you just need to update your database version?
There is no "best practice" i.e. this really depends on your needs. Explicit joins are just that, explicit. So multiple calls to join will create multiple joins in SQL.
As for the OOP question, I think this is fine, yes.
Model:
#Entity
public class User {
#Id
private Integer id;
#JoinColumn(name = "user_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
I am trying to use JPA's criteria api to query for User entities based on an attribute of an implementation of Project. For example, query all users that have a project with "SOME_NAME" department (that field does not exist on DesignProject).
I see there is a way of doing so via downcasting of the Project entity for the query. I am trying something similar to:
CriteriaBuilder cb...
Root<User> userRoot...
root = ((From) root).join("projects", JoinType.LEFT);
root = cb.treat(root, AdminProject.class);
root = root.get("departmentName");
Exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]
What am I missing? Is it something related to the join, or how the downcasting occurs afterwards?
Edit
After the answer by #K.Nicholas, I have managed to make the query work on an isolated scenario, but not on my app.
But, I noticed that the entityManager.createQuery(query) call throws the exception above when called for the first time, and it works if I call it again without changing the query object. Here is the query generated on the second call (this query finds the objects I want from the database):
select generatedAlias0 from User as generatedAlias0 left join generatedAlias0.projects as generatedAlias2 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0
Why is the entity manager creating two different queries when called two consecutive times?
I would do the Entitys a little different, as you will see. The main concern is that you are using User as your root with a join to a list of Projects. This is a concern because you should have the foreign key on the Project class and use the projects field as a query only field. That is what I have done. It works better that way. It is also a concern because you have to do a join fetch instead of a join so that the projects get fetched along with the users.
So, first, the entities are like so:
#Entity
public class User {
#Id
private Integer id;
#OneToMany(mappedBy="user")
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
#ManyToOne
private User user;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
After a bit a digging I found a JPQL query that does the trick. This was a starting point:
List<User> users = entityManager.createQuery("select distinct(u) from User u join fetch u.projects p where TYPE(p) = 'Administrative' and p.departmentName = 'dept1'", User.class).getResultList();
After a bit more digging I found that the treat worked fine if you do it correctly and that with JPA 2.1 you should use an EntityGraph do get the join to do a fetch.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, Project> join = root.join("projects");
query.select(root).where(builder.equal(builder.treat(join, AdminProject.class).get("departmentName"), "dept1"));
EntityGraph<User> fetchGraph = entityManager.createEntityGraph(User.class);
fetchGraph.addSubgraph("projects");
users = entityManager.createQuery(query.distinct(true)).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
As a side note the queries generated as slightly different but I didn't look that closely at them. You should.
I have the following entities:
#Entity
#Table(name="table1")
public class Entity1 {
#Id
private Integer id;
#OneToMany(mappedBy = "entity1")
private List<Entity2> entities2;
}
#Entity
#Table(name="table2")
public class Entity2 {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id")
private Entity3 entity3;
}
Using Criteria API I have tried:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity1> query = cb.createQuery(Entity1.class);
Root<Entity1> entity1= query.from(Entity1.class);
entity1.fetch("entities2", JoinType.LEFT);
entity1.fetch("entities2", JoinType.LEFT).fetch("entity3", JoinType.LEFT);
But when the query is executed:
query.select(entity1).where(cb.and(predicates.toArray(new Predicate[predicates.size()]))));
List<Entity1> entities1 = entityManager.createQuery(query).getResultList();
Multiple queries intead one are executed (related to Entity 3). I think the problem is beacuse the relationship is inside another one. Because when you fecth the first join, there are not several queries.
I would appreciate your help. Thank so much
By default the relationships wich are like Collections the hibernate deal how lazy,
if you defined relationship how Lazy and not received LazyLoadException check your config.
I would like to make a Join query by Jpa repository by annotation #Query I have three tables.
The native query is:
select application.APP_ID
from user, customer, application
where user.USE_CUSTOMER_ID = customer.CUS_ID
and application.APP_CUSTOMER_ID = customer.CUS_ID
and user.USE_ID=1;
Now I have Table Hibernate entity, so I tried in ApplicationRepository
#Query(SELECT application FROM Application a
INNER JOIN customer c ON c.customer.id = a.customer.id
INNER JOIN user u ON u.customer.id = c.customer.id
INNER JOIN application a ON a.user.id = u.id
WHERE
u.id = :user.id)
List<Application> findApplicationsByUser(#Param("User") User user);
The log says
unexpected token
Any ideas, please?
My table Entity
Application.java:
#Entity
#Table
public class Application extends BaseSimpleEntity {
...
#ManyToOne(optional = false)
private Customer customer;
...
}
Customer.java:
#Entity
#Table
public class Customer extends BaseSimpleEntity {
...
#OneToMany(mappedBy = "customer")
private List<User> users;
#OneToMany(mappedBy = "customer")
private List<Application> applications;
...
}
User.java:
#Entity
#Table
public class User extends BaseSimpleEntity {
...
#ManyToOne(optional = false)
private Customer customer;
...
}
You don't need ON clauses in JPA, because the JPA already know how entities are associated thanks to the mapping annotations.
Moreover, you're selecting application, which is not an alias defined in your query.
And your joins make no sense.
The query should simply be
select application FROM Application a
join a.customer c
join c.users u
where u.id = :userId
Read the Hibernate documentation to understand how HQL and joins work.
Two tables:
TABLE_1:
REC_ID
1
2
3
4
TABLE_2:
REC_ID REC_VAL
2 A
3 B
Entity classes (basic structure):
#Entity
#Table(name="TABLE_1")
public class Entity1 {
#Id
#Column(name="REC_ID")
private String recId;
//getters and setters
}
#Entity
#Table(name="TABLE_2")
public class Entity2 {
#Id
#Column(name="REC_ID")
private String recId;
#Column(name="REC_VAL")
private String recVal;
//getters and setters
}
SQL Query and result:
SELECT T1.REC_ID, T2.REC_VAL FROM TABLE_1 T1 LEFT OUTER JOIN TABLE_2 T2 ON T1.REC_ID = T2.RED_ID
Result:
REC_ID REC_VAL
1 null
2 A
3 B
4 null
JPQL Query:
SELECT e1.recId, e2.recVal FROM Entity1 e1 LEFT JOIN e1.<an Entity2 field in Entity1*>
* I know I don't have it in the given structure above, but I want to know how to do it right. And how do I choose from #ManyToOne, #OneToOne, etc.
How do I modify the Entity classes and the JPQL query to achieve the same result as the SQL query? I've been trying various things, nothing works. It wouldn't allow me to create two fields with the same column name, or to define the String as the #JoinColumn. I almost got it working, but the generated SQL query contains a reference to REC_ID_REC_ID column in TABLE_2 which doesn't exist. And after Googling so much, I can't find a proper guide for this (ignoring that JPQL does not support in-line join conditions!)
You need to have a OneToOne association between the entities. And the association, on the owning side, must be annotated with #MapsId. Here's an example taken from the Hibernate documentation, which maps to your use-case:
#Entity
public class Body {
#Id
public Long getId() { return id; }
#OneToOne(cascade = CascadeType.ALL)
#MapsId
public Heart getHeart() {
return heart;
}
...
}
#Entity
public class Heart {
#Id
public Long getId() { ...}
}
Once you have that, you can use a query such as
select b.foo, h.bar from Body b left join b.heart h where ...