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.
Related
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.
Assume I have the next data model:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Item> items;
... getters, setters, equals and hashcode.
}
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne
#JoinColumn(name = "user")
private User user;
private Boolean deleted;
... getters, setters, equals and hashcode.
}
I need to query a certain user by id with non-deleted items. Can I do it via Spring Data Repositories?
I tried something like:
public interface UserRepository extends CrudRepository<User, Long> {
#Query("from User u left join u.items i on i.deleted = false where u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
}
But this approach generates 2 separate SQL queries:
select user0_.id as id1_1_0_, items1_.id as id1_0_1_, items1_.deleted as deleted2_0_1_, items1_.user as user3_0_1_ from user user0_ left outer join item items1_ on user0_.id=items1_.user and (items1_.deleted=0) where user0_.id=?
select items0_.user as user3_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.deleted as deleted2_0_1_, items0_.user as user3_0_1_ from item items0_ where items0_.user=?
And as result, I receive user with deleted items in the item list.
Nothing wrong with creation of two separete queries. One is to get users from user table, other is to get items of related user from items table.
join clause is used to combine columns from one or more tables.
join u.items i on i.deleted = false is not a proper use. It should be on the where clause.
You should change the query this way:
#Query("from User u left join u.items i where i.deleted = false and u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
I have two classes wiht relation one to many. User:
#Entity
public class User {
#Id
private Long id;
...
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Note> notes = new HashSet<>();
...
}
and Note:
#Entity
public class Note {
#Id
private Long id;
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
...
}
In the UserRepository I want to override findAll(Pageable<T> va1) method from PagingAndSortingRepository<T, ID> to avoid N+1 query problem. Everything works fine, with this code:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#Query(value = "select distinct u from User u left join fetch u.notes")
Page<User> findAll();
}
But when I add pagination:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#Query(value = "select distinct u from User u left join fetch u.notes",
countQuery = "select count(u) from User u")
Page<User> findAll(Pageable page);
}
I see warn in console:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
My question is, how to fix it?
When you want to join fetch child with pagination, Hibernate do SQL query without pagination means fetch full resultset. And do pagination in memory.
The easier way to fix this using two query
Fetch ids of user only with pagination
Then user join fetch notes with IN query by those ids
Code Example
#Query("select u.id from User u")
List<Long> getAllIds(Pageable page);
#Query(value = "select distinct u from User u left join fetch u.notes where u.id IN (:ids)")
List<User> findAll(#Param("ids")List<Long> ids);
This is a perfect use case for Blaze-Persistence.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. The pagination support it comes with handles all of the issues you might encounter.
It also has a Spring Data integration, so you can use the same code like you do now, you only have to add the dependency and do the setup: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-setup
Blaze-Persistence has many different strategies for pagination which you can configure. The default strategy is to inline the query for ids into the main query. Something like this:
select u
from User u
left join fetch u.notes
where u.id IN (
select u2.id
from User u2
order by ...
limit ...
)
order by ...
I have a problem to create query with TypedQuery interface, NamedQuery and many-to-many relationship.
Here is my Report entity:
#Entity
#Table(name = "REPORT")
#NamedQueries({
#NamedQuery(name = Report.NAMED_QUERY.FIND_USERS, query = "SELECT r.users FROM Report r WHERE r = :report")})
public class Report {
public interface NAMED_QUERY {
String FIND_USERS = "Report.findUsers";
}
#ManyToMany
#JoinTable(name = "REPORT_USER", joinColumns = #JoinColumn(name = "REPORT_ID"), inverseJoinColumns = #JoinColumn(name = "USER_ID"))
private Set<User> users;
//another fields, getters and setters
}
And User Entity. Here i have no field that maps many-to-many relation.
#Entity
#Table(name = "USER")
public class User {
//fields, getters and setters
}
I have no idea how to use this named query.
public List<User> findUsersRelatedToReport(Report report) {
TypedQuery<User> query = entityManager.createNamedQuery(Report.NAMED_QUERY.FIND_USERS, User.class)
.setParameter("report", report);
return query.getResultList();
}
In the end I have exception:
Type specified for TypedQuery [package_name.User] is incompatible with query return type [interface java.util.Set]
Any help would be appreciated.
You cannot use collection valued attributes (in JPA specification terminology: collection_valued_path_expression) in SELECT.
That's why query is bit more complex, one way is following:
SELECT DISTINCT(u)
FROM User u
WHERE EXISTS (
SELECT r
FROM Report r
WHERE r = :report AND u MEMBER OF r.users)
Try changing data type of Users in your report class to List.
private List<User> users;
instead of
private Set<User> users;
You are trying to return set of users as a column in your select that is causing the error.
I think you could try to select data from User table. Try something like that:
SELECT u FROM User u WHERE u.report = :report
It's an old question, but I've also hit this recently and came up with more elegant solution: it's true you cannot use collection_valued_path in select expression, but you definitely can do joins over this path:
SELECT u FROM Report r JOIN r.users u where r = :report
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.