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
Related
I am trying to write simple Pet-project, I use Hibernate in DAO layer. I have entity Group with field Students.
#Data
#AllArgsConstructor
#RequiredArgsConstructor
#EqualsAndHashCode
#ToString
#Builder
#Entity
#Table(name = "groups")
#NamedQueries({
#NamedQuery(name = "get all groups", query = "from Group"),
#NamedQuery(name = "find group by id", query = "select g from Group g join fetch g.students where g.id=:groupId"),
#NamedQuery(name = "find group by name", query = "from Group g where g.name like :groupName"),
#NamedQuery(name = "find number of groups", query = "select count(*) from Group"),
#NamedQuery(name = "find group and sort by name", query = "from Group order by name, id"),
#NamedQuery(name = "find group and sort by id", query = "from Group order by id")
})
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column
private String name;
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
#JoinTable(
name="studentsGroups",
joinColumns=#JoinColumn(name="groupId"),
inverseJoinColumns=#JoinColumn(name="studentId")
)
#LazyCollection(LazyCollectionOption.FALSE)
private List<Student> students;
}
in DAO layer for exaple I have method
#Transactional
public Optional<Group> findById(int groupId) {
Session session = sessionFactory.getCurrentSession();
return Optional.ofNullable(session.get(Group.class, groupId));
}
I want to check my DAO and I am writing junit test. I use assertThat to compare entity from database and entity from test data. In test data, field students have type ArrayList but from Hibernate get type PersistentBag and I can't compare these fields although all data are same.
#Test
public void givenFindGroupById_whenFindGroupById_thenGroupWasFound() {
Optional<Group> actual = groupDao.findById(2);
assertThat(actual).isPresent().get().isEqualTo(expectedGroups.get(1));
}
List<Group> expectedGroups = Arrays.asList(
Group.builder().id(1).name("a2a2").students(expectedStudentsForGroupA2A2Name).build(),
Group.builder().id(2).name("b2b2").students(expectedStudentsForGroupB2B2Name).build(),
Group.builder().id(3).name("c2c2").students(expectedStudentsForGroupC2C2Name).build(),
Group.builder().id(4).name("d2d2").students(expectedStudentsForGroupD2D2Name).build());
Is there some way to convert PersistentBag to ArrayList, or I am doing something wrong?
PersistentBag uses Object's equals method for equals() and hashCode() Bug.
FIX:
Exclude students field for equals check by adding #EqualsAndHashCode.Exclude over it.
Now assert the Students values: assertThat(actual.get().getStudents()).containsOnlyElementsOf(expectedGroups.get(1).getStudents())
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 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'm working on some personal project but i have a question about hibernate.
I have a class structure like this :
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyUser")
private Company company = new Company();
}
But inside the company i have another join.
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
}
Now i do a query to get the estimate options.
But if i do it like this it loads lots of unnecessary stuff .
#RequestMapping(value = "/estimateoptions")
public EstimateOptions getCompanyEstimateOptions(#AuthenticationPrincipal Principal user) {
User getuser = userDao.findByEmail(user.getName());
EstimateOptions estimateOptions = getuser.getCompany().getEstimateOptions();
return estimateOptions;
}
is there a better approach for this ?
There are a lot of ways to do such optimization. The simplest one is add bidirectional associations to Company and EstimateOptions with lazy loading.
An example for Company ( I don't test. It is just a sketch.)
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
#OneToOne(mappedBy="company", fetch = FetchType.LAZY)
private User user;
}
And do something like this (this is HQL but you can use a criteria API too)
from EstimateOptions options inner join options.company company inner join company.user user where user.name = :userName
You can see HQL joined query to eager fetch a large number of relationships for additional thoughts.
Updated
I am not sure but may be you can do something like this (without additional associations)
select options from User user inner join user.company company inner join company.estimateOptions options where user.name = :userName
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.