Spring Data JPA: query ManyToMany - java

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

Related

Hibernate warn durring query - left join fetch with Pageable

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 ...

How to perform the "in" query on jpa's #ManyToOne JoinColumn without querying the entity

I have an entity like:
#Entity
class Blog{
#Id
private Long id;
// ...
#ManyToOne
#JoinColumn(name = "author_id")
private User author;
}
And I want to perform an "in" query on the author column, so I wrote my BlogRepository like:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUserIn(Collection<User> users, Pageable pageable);
}
This works, however, I need to perform two queries for one request, that is to query the User entity from UserRepository to get Collection<User> users.
Because in many situation, all I want is semantic like:
select * from blog where author_id in (some_id_list);
So is there anyway in jpa to let me perform query like below without querying the User entity?
The Order part of your method gets in the way. Since you don't want the results ordered, you can use this:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUser_IdIn(Collection<Long> userId, Pageable pageable);
}
Yes you can also write custom JPQL
public interface BlogRepository extends JpaRepository, CustomizedBlogRepository {
#Query("select b from Blog b LEFT JOIN b.author.authorId in :authorIds")
Page<Blog> getByUsersByIds(List<Integer> authorIds, Pageable pageable);
}
Here you can change authorId to any Id of User table, which you have created.And you can also try JOIN instead of LEFT JOIN

JpaRepository find User with Role in list of roles

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

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 ?

JPA: How to get entity based on field value other than ID?

In JPA (Hibernate), when we automatically generate the ID field, it is assumed that the user has no knowledge about this key. So, when obtaining the entity, user would query based on some field other than ID. How do we obtain the entity in that case (since em.find() cannot be used).
I understand we can use a query and filter the results later. But, is there a more direct way (because this is a very common problem as I understand).
It is not a "problem" as you stated it.
Hibernate has the built-in find(), but you have to build your own query in order to get a particular object. I recommend using Hibernate's Criteria :
Criteria criteria = session.createCriteria(YourClass.class);
YourObject yourObject = criteria.add(Restrictions.eq("yourField", yourFieldValue))
.uniqueResult();
This will create a criteria on your current class, adding the restriction that the column "yourField" is equal to the value yourFieldValue. uniqueResult() tells it to bring a unique result. If more objects match, you should retrive a list.
List<YourObject> list = criteria.add(Restrictions.eq("yourField", yourFieldValue)).list();
If you have any further questions, please feel free to ask. Hope this helps.
if you have repository for entity Foo and need to select all entries with exact string value boo (also works for other primitive types or entity types). Put this into your repository interface:
List<Foo> findByBoo(String boo);
if you need to order results:
List<Foo> findByBooOrderById(String boo);
See more at reference.
Basically, you should add a specific unique field. I usually use xxxUri fields.
class User {
#Id
// automatically generated
private Long id;
// globally unique id
#Column(name = "SCN", nullable = false, unique = true)
private String scn;
}
And you business method will do like this.
public User findUserByScn(#NotNull final String scn) {
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> from = criteria.from(User.class);
criteria.select(from);
criteria.where(builder.equal(from.get(User_.scn), scn));
TypedQuery<User> typed = manager.createQuery(criteria);
try {
return typed.getSingleResult();
} catch (final NoResultException nre) {
return null;
}
}
Best practice is using #NaturalId annotation. It can be used as the business key for some cases it is too complicated, so some fields are using as the identifier in the real world.
For example, I have user class with user id as primary key, and email is also unique field. So we can use email as our natural id
#Entity
#Table(name="user")
public class User {
#Id
#Column(name="id")
private int id;
#NaturalId
#Column(name="email")
private String email;
#Column(name="name")
private String name;
}
To get our record, just simply use 'session.byNaturalId()'
Session session = sessionFactory.getCurrentSession();
User user = session.byNaturalId(User.class)
.using("email","huchenhai#qq.com")
.load()
This solution is from Beginning Hibernate book:
Query<User> query = session.createQuery("from User u where u.scn=:scn", User.class);
query.setParameter("scn", scn);
User user = query.uniqueResult();
I solved a similar problem, where I wanted to find a book by its isbnCode not by your id(primary key).
#Entity
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String isbnCode;
...
In the repository the method was created like #kamalveer singh mentioned. Note that the method name is findBy+fieldName (in my case: findByisbnCode):
#Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Book findByisbnCode(String isbnCode);
}
Then, implemented the method in the service:
#Service
public class BookService {
#Autowired
private BookRepository repo;
public Book findByIsbnCode(String isbnCode) {
Book obj = repo.findByisbnCode(isbnCode);
return obj;
}
}
Write a custom method like this:
public Object findByYourField(Class entityClass, String yourFieldValue)
{
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<Object> root = criteriaQuery.from(entityClass);
criteriaQuery.select(root);
ParameterExpression<String> params = criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(root.get("yourField"), params));
TypedQuery<Object> query = entityManager.createQuery(criteriaQuery);
query.setParameter(params, yourFieldValue);
List<Object> queryResult = query.getResultList();
Object returnObject = null;
if (CollectionUtils.isNotEmpty(queryResult)) {
returnObject = queryResult.get(0);
}
return returnObject;
}
Edit: Just realized that #Chinmoy was getting at basically the same thing, but I think I may have done a better job ELI5 :)
If you're using a flavor of Spring Data to help persist / fetch things from whatever kind of Repository you've defined, you can probably have your JPA provider do this for you via some clever tricks with method names in your Repository interface class. Allow me to explain.
(As a disclaimer, I just a few moments ago did/still am figuring this out for myself.)
For example, if I am storing Tokens in my database, I might have an entity class that looks like this:
#Data // << Project Lombok convenience annotation
#Entity
public class Token {
#Id
#Column(name = "TOKEN_ID")
private String tokenId;
#Column(name = "TOKEN")
private String token;
#Column(name = "EXPIRATION")
private String expiration;
#Column(name = "SCOPE")
private String scope;
}
And I probably have a CrudRepository<K,V> interface defined like this, to give me simple CRUD operations on that Repository for free.
#Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> { }
And when I'm looking up one of these tokens, my purpose might be checking the expiration or scope, for example. In either of those cases, I probably don't have the tokenId handy, but rather just the value of a token field itself that I want to look up.
To do that, you can add an additional method to your TokenRepository interface in a clever way to tell your JPA provider that the value you're passing in to the method is not the tokenId, but the value of another field within the Entity class, and it should take that into account when it is generating the actual SQL that it will run against your database.
#Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> {
List<Token> findByToken(String token);
}
I read about this on the Spring Data R2DBC docs page, and it seems to be working so far within a SpringBoot 2.x app storing in an embedded H2 database.
No, you don't need to make criteria query it would be boilerplate code you just do simple thing if you working in Spring-boot:
in your repo declare a method name with findBy[exact field name].
Example-
if your model or document consist a string field myField and you want to find by it then your method name will be:
findBymyField(String myField);
All the answers require you to write some sort of SQL/HQL/whatever. Why? You don't have to - just use CriteriaBuilder:
Person.java:
#Entity
class Person {
#Id #GeneratedValue
private int id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
...
}
Dao.java:
public class Dao {
public static Person getPersonByName(String name) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Person> cr = cb.createQuery(Person.class);
Root<Person> root = cr.from(Person.class);
cr.select(root).where(cb.equal(root.get("name"), name)); //here you pass a class field, not a table column (in this example they are called the same)
Query query = session.createQuery(cr);
query.setMaxResults(1);
List<Person> resultList = query.getResultList();
Person result = resultList.get(0);
return result;
}
}
example of use:
public static void main(String[] args) {
Person person = Dao.getPersonByName("John");
System.out.println(person.getAge()); //John's age
}
Have a look at:
JPA query language: The Java Persistence Query Language
JPA Criteria API: Using the Criteria API to Create Queries
I've written a library that helps do precisely this. It allows search by object simply by initializing only the fields you want to filter by: https://github.com/kg6zvp/GenericEntityEJB
Refer - Spring docs for query methods
We can add methods in Spring Jpa by passing diff params in methods like:
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
In my Spring Boot app I resolved a similar type of issue like this:
#Autowired
private EntityManager entityManager;
public User findByEmail(String email) {
User user = null;
Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.email=:email");
query.setParameter("email", email);
try {
user = (User) query.getSingleResult();
} catch (Exception e) {
// Handle exception
}
return user;
}
This is very basic query :
Entity : Student
#Entity
#Data
#NoArgsConstructor
public class Student{
#Id
#GeneratedValue(generator = "uuid2", strategy = GenerationType.IDENTITY)
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
#Column(nullable = false)
#Version
#JsonIgnore
private Integer version;
private String studentId;
private String studentName;
private OffsetDateTime enrollDate;
}
Repository Interface : StudentRepository
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
List<Student> findByStudentName(String studentName);
List<Student> findByStudentNameOrderByEnrollDateDesc(String studentName);
#Transactional
#Modifying
void deleteByStudentName(String studentName);
}
Note:
findByColumnName : give results by criteria
List findByStudentName(String studentName)
Internally convert into query : select * from Student where name='studentName'
#Transactional
#Modifying
Is useful when you want to remove persisted data from database.
Using CrudRepository and JPA query works for me:
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface TokenCrudRepository extends CrudRepository<Token, Integer> {
/**
* Finds a token by using the user as a search criteria.
* #param user
* #return A token element matching with the given user.
*/
#Query("SELECT t FROM Token t WHERE LOWER(t.user) = LOWER(:user)")
public Token find(#Param("user") String user);
}
and you invoke the find custom method like this:
public void destroyCurrentToken(String user){
AbstractApplicationContext context = getContext();
repository = context.getBean(TokenCrudRepository.class);
Token token = ((TokenCrudRepository) repository).find(user);
int idToken = token.getId();
repository.delete(idToken);
context.close();
}

Categories

Resources