JpaRepository find User with Role in list of roles - java

I'm using Spring with Hibernate and JpaRepository as database repository.
I have two classes for user storage:
#Entity
public class User {
#Id
private Long id;
private String username;
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserRole> roles;
}
#Entity
public class UserRole {
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Enumerated(EnumType.STRING)
private Role role;
}
public enum Role {
ADMIN,
MEMBER;
/* some others in the future */
}
As you can see User can have multiple roles assigned. So user1 can have ADMIN and MEMBER roles and user2 only MEMBER role.
I would like to user with ADMIN role (among others) could list all users in database (JpaRepository findAll() method is enough) but user with only MEMBER role could list only users with MEMBER role.
How to write method in JpaRepository to achieve that? I tried some below but it's not working:
List<User> findByRoles_RoleIn(Collection<Role> roles);
or
List<User> findByRoles_Role(Role role);
Maybe some custom #Query?

If you can go for a custom query then try following:
#Query( "select u from User u inner join u.roles r where r.role in :roles" )
List<User> findBySpecificRoles(#Param("roles") List<Role> roles);

Here is if you wish not to use #query
Query by role object
List<User> findByRoles_(Role role);
Query by role object id
List<User> findByRoles_Id(Long id);

You need to use a custom jpql query for this case. And implement two methods with custom query one for admin and one for simple users.
Something like
#Query("SELECT u FROM User u JOIN u.roles r WHERE r.role=:rolename")

If you want to retrieve the users with list of role names in JPA, you can add below method in the repository.
List<User> findByRoles_NameIn(List<String> roles);

Related

Make request with subcondition for child list via Spring Data JPA

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);

How to perform a LEFT JOIN on 2 related tables

So I have 2 tables. A users table and a finished_exams table. The finished_exams table has a user_id as foreign key and is related as OneToOne with the users table.
I made the relation in Users model like this:
#OneToOne(fetch = FetchType.EAGER, mappedBy = "user")
private FinishedExam finishedExam;
And in the FinishedExams model like this:
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
When I call all finished exams like this in the controller:
#GetMapping("/all")
public Iterable<FinishedExam> getAllFinishedExams()
{
return finishedExamRepository.findAll();
}
I get all Finishedexams linked with the users which is good.
My question is, is it possible to get ALL users with their finishedExam if they have it and without if they don't (So basically a LEFT JOIN)
Is this possible in Hibernate?
EDIT:
I just tried this but it only returns all users without the exams.
#GetMapping("/allUsersWithExams")
#Fetch(FetchMode.SELECT)
public Iterable<User> getAllUsersWithTheirExams()
{
return userRepository.findAll();
}
First of all sure is possible, you just have to write the query yourself:
"Select u from Users u LEFT JOIN u.finishedExam"
simple enough.
But what's stopping you to just select all user and get all their finished exam?
public Iterable<Users> getAllUsers()
{
return UsersRepository.findAll();
}
It will give you a list of all Users independently from having or not any finishedExam

how to construct spring JPA/JPQL query in JpaRepository

I've got a DB structure like this:
topics 1:n posts 1:n comments and users n:n roles.
I want to get all comments that user have an access.
Giving access for me means (when I create post object I automaticly create role object called PostName with prefix role_comment_ e.g. post called abc have role called: role_comment_abc)
Now I try to create jpa/jpql query like below:
find all comments by User where user_id is =:1 and role_name contaings =:2
findByUserIdAndRoleNameContaining(Integer userId, String roleName);
This is how my User, Role and comment tables looks like:
Roles table:
#Entity
#Table(name = "roles")
public class Role {
#Id
private Integer id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
Users and user_role tables:
#Entity
#Table(name = "users")
public class User {
#Id
private Integer id;
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(
name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") }
)
private Set<Role> roles = new HashSet<>();
And this is comments table:
#Entity
#Table(name = "comments")
public class Comments{
#Id
private Integer id;
private String name;
private String description;
#ManyToOne(optional = false)
#JoinColumn(nullable = false, name = "user_id")
private User user
Unfortunetly if I create query in JpaRepository called:
List<Comments> findByUserId(Integer id);
If i'm not wrong it will print comments created by that specific user.
So what I really want to achive? Let me show you this on example data:
roles:
100;"role_comment_ab"
101;"role_comment_cd"
102;"role_comment_ef"
103;"something_else"
Comments in post with name ab:
1;"Test1";"Test description";10
2;"Test2";"Test description";10
comments in post with name cd:
3;"Test3";"Test description";10
4;"Test4";"Test description";10
comments in post with name ef:
5;"Test5";"Test description";10
6;"Test6";"Test description";10
users:
10;"Thomas" (logged user)
11;"John"
users_roles:
10;100
11;101
10;102
10;103
input:
findByUserIdAndRoleNameContaining(10, "role_comment_");
output:
1;"Test1";"Test description";10
2;"Test2";"Test description";10
5;"Test5";"Test description";10
6;"Test6";"Test description";10
I'm really out of clue how my query should look like. Pleast atleast give me a small hint.
UPDATE:
After adding #Bohdan Petrenko solution:
#Query("select c from Comment c join c.user u join u.roles r where u.id = :userId and lower(r.name) like lower(:roleName)")
List<Comment> findByUserIdAndRoleNameContaining(#Param("userId") Integer userId, #Param("roleName") String roleName);
roleName = "%" + roleName.trim() + "%";
I noticed that this solution prints all comments if #Param roleName contains "roleName" String.
So if I have role_postName1 and role_postName2
it prints:
comment1FromPost1
comment2FromPost1
comment1FromPost2
comment2FromPost2
comment1FromPost1
comment2FromPost1
comment1FromPost2
comment2FromPost2
It would've be great to find solution to print comments from posts only if user have role called role_postName.
#Query("select t from Topic t
join t.user u
where u.id = :userId
and u.roles in :roleNames")
List<Topic> findByUserIdAndRoleNameContainedIn(#Param("userId") Integer userId, #Param("roleNames") List<String> roleNames);
Please be more specific in your questions.
If I understood you correctly - You want to select Topics by user id and Role name. If yes you may try something like this:
#Query("select t from Topic t join t.user u join u.roles r where u.id = :userId and lower(r.name) like lower(:roleName)")
List<Topic> findByUserIdAndRoleNameContaining(#Param("userId") Integer userId, #Param("roleName") roleName);
But you'll also need to change role name before passing it to the repository method findByUserIdAndRoleNameContaining:
roleName = "%" + roleName.trim() + "%";
UPDATE
This will work without any custom SQL and roleName modifications
List<Comment> findByUser_IdAndUser_Roles_NameContainingIgnoreCase(Integer userId, String roleName);
And also I don't understand the trouble you are faced with now. So, provide us some examples with a test data (from the data base), correct and incorrect expected results of the query. As I don't understand the reason of your problem I can't help you to solve it.

Spring Data JPA: query ManyToMany

I have entities User and Test
#Entity
public class User {
private Long id;
private String userName;
}
#Entity
public class Test {
private Long id;
#ManyToMany
private Set<User> users;
}
I can get all tests by User entity:
public interface TestRepository extends JpaRepository<EventSettings, Long> {
List<Test> findAllByUsers(User user);
}
But which query can I use for finding all tests by userName?
The following method signature will get you want to want:
List<Test> findByUsers_UserName(String userName)
This is using the property expression feature of Spring Data JPA. The signature Users_UserName will be translated to the JPQL x.users.userName. Note that this will perform an exact match on the given username.
Other answer shows how to achieve desired functionality using function naming technique. We can achieve same functionality using #Query annotation as follows:
#Query("select t from Test t join User u where u.username = :username")
List<Test> findAllByUsername(#Param("username")String username);
I was using #JoinTable and I got it working with this :
#Query("select t from Test t join t.users u where u.username = :username")
List<Test> findAllByUsername(#Param("username") String username);
t.users u instead of User u

Map two classes to one table using JPA to speed up spring security UserDetailsService

I want to make a secured restful service using Spring, JPA and Hibernate.
Each endpoint must be secured and I use spring security for that purpose using a specific UserDetailsService as describe in the spring security documentation :
http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/#userdetailsservice-implementations
Here the point : as each request will be authentified, it means that for each request, my UserDetailsService will load a user form my database and need to get its password and its roles.
The default JdbcDaoImpl use 2 requests to find a user and its roles.
I don't use it because :
I want to have my users' Id in a UserDetails object as my business controller use it.
I want that my users are loaded with only one request
My user business object look like :
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "aa_user")
public class User
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#NotNull
#Column(unique = true)
protected String login;
protected String password;
#ManyToMany(targetEntity = Role.class)
protected List<Role> roles;
//getter/setter
}
And my repository :
public interface UserRepository extends JpaRepository<User, Long>
{
#Query("SELECT u FROM User u JOIN FETCH u.roles where u.login = :login")
User findByLogin(#Param("login") String login);
}
My UserDetailsService :
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User user = null;
try
{
user = userRepository.findByLogin(username);
} catch (Throwable e) {
e.printStackTrace();
throw new UsernameNotFoundException("Can't find username: " + username);
}
if (user == null) {
throw new UsernameNotFoundException("Can't find username: " + username);
}
UserDetailsImpl userDetails = new UserDetailsImpl(user);
return userDetails;
}
I have a lot of User subclasses (like Seller, ...) which have associations to other objects fetch eagerly for business purpose and with this implementation, the userRepository.findByLogin(username) make something like 3 or more heavy joins to give me the right full fetch user object (which is "normal" of course), but I only want one light query with only the User's field initialized.
According to this question :
Avoiding outer joins across tables when using joined inheritance with Spring Data JPA what I want to do seems complicated, but I found that I could use #Polymorphism Hibernate annotation with PolymorphismType.EXPLICIT :
https://stackoverflow.com/a/18358863/1661338
#Polymorphism is not JPA compliant and brake parts of my business logic or need that I refactor a lot of query.
To avoid that, I add a second class mapped on the same Table :
#Entity
#Table(name = "aa_user")
public class LightUser implements SimpleUser
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#NotNull
#Column(unique = true)
protected String login;
protected String password;
#ManyToMany(targetEntity = Role.class)
protected List<Role> roles;
//getter
}
And my repository :
public interface LightUserRepository extends JpaRepository<User, Long>
{
#Query("SELECT u FROM LightUser u JOIN FETCH u.roles where u.login = :login")
LightUser findByLogin(#Param("login") String login);
}
Both Userand LightUser implements the same SimpleUser interface with all getter needed for my UserDetailsImpl.
Now, lightUserRepository.findByLogin(username) make the smartest query possible and get only what I want.
I still have questions :
Is it JPA compliant ?
hbm2ddl.SchemaExport work but try to put 2 times the same foreign key between table aa_user and role table. How to avoid that ?
It could be less painful to write if I can make a query with the same behavior as PolymorphismType.EXPLICIT. Does anyone know if it's possible ?
Can LightUser be "readOnly" object to avoid mistakes ?

Categories

Resources