Spring EntityGraphType.FETCH is not working - spring

I am using Spring Boot 2.2.2 . I have faced an unusual issue that EntityGraphType.FETCH did not work whenever I fetch User entity.
It fetches all the FetchType.EAGER (Company, UserFile) entities regardless the EntityGraph type. I tried with attributePaths in repository, it has the same issue.
#Entity
#NamedEntityGraph(name = "User.noAssociation")
#NamedEntityGraph(name = "User.profilePic", attributeNodes = {#NamedAttributeNode("profilePic")})
#Getter
#Setter
public class User extends TimeAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Email
private String email;
#ManyToOne(fetch = FetchType.EAGER)
private Company company;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private UserFile profilePic;
}
and
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#EntityGraph(value = "User.profilePic", type = EntityGraphType.FETCH)
#Query("SELECT u FROM User u WHERE u.email="email")
User findByEmail(String email);
}

Change
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#EntityGraph(value = "User.profilePic", type = EntityGraphType.FETCH)
#Query("SELECT u FROM User u WHERE u.email="email")
User findByEmail(String email);
}
to
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#EntityGraph(value = "User.profilePic", type = EntityGraphType.LOAD)
#Query("SELECT u FROM User u WHERE u.email="email")
User findByEmail(String email);
}

Related

How to get a list of "count an entity by an area" in JPA?

I want to get a list containing the number of an entity (count) by an area (each value existing in the table), in JPA.
I found that there is a method that gives the number of an entity by a given value of an area
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
public int countByArea_Libelle(String l);
}
user
#Entity(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String address;
private String name;
}
Is there a method that count an entity with all the value of an area or should i implement it ?
Try this one
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "select new map( count(u.address) as numberOfUsers, u.address as city ) FROM User u group by u.address")
List<Object> countByAddressList();
}
Try like this
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
long countByAddress(String address);
}

How get User to email?

I need get user to email with jpa
i have this code:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(#Param("email") String email);
User findById(#Param("id") Long id);
}
When i tried this :
User owner = userRepository.findById(user.getId()); //yes
User user=userRepository.findByEmail(email);//no
My class User is:
#Entity
#Table(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = -3009157732242241606L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
public User() {
}
//getters and setters
}
I want to obtain a user through the user's email, if I can obtain it through his id but I also need to obtain it through email
In you Model class "email" is not an unique so that it will return multiple rows to avoid that add
#Entity
#Table(name = "User")
public class User implements Serializable {
.....
#Column(unique=true,...)// .. add your extra
private String email;
.....
}
If you want top 1 result to be returned you could change the method
User findByEmail(#Param("email") String email);
with
User findOneByEmail(String email);

MultipleBagFetchException whent I try load entity with 2 collections use JPA EntityGraph

I have user entity:
#ToString
#Data
#Entity
#Table(name = "users")
#NamedEntityGraph(name = "UserWithItems",
attributeNodes = {
#NamedAttributeNode("items"),
#NamedAttributeNode("roles")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Item> items;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Role> roles;
}
item:
#ToString(exclude = "user")
#Data
#Entity
#Table(name = "items")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private User user;
}
role:
#ToString
#Data
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private User user;
}
I want load user with items and roles. I use #NamedEntityGraph. It is my repository:
#EntityGraph(value = "UserWithItems", type = EntityGraph.EntityGraphType.LOAD)
#Query("select u from User u where u.id = ?1 and u.name =?2")
User getOneById(Long id, String name);
But I get an error:
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.example.egerload.entity.User.roles, com.example.egerload.entity.User.items]
at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:75) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.hql.QueryLoader.<init>(QueryLoader.java:108) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:212) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:143) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:119) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:85) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.query.internal.AbstractProducedQuery.makeQueryParametersForExecution(AbstractProducedQuery.java:1350) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1539) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1505) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
... 41 common frames omitted
You can split your "UserWithItems" #NamedEntityGraph into two #NamedEntityGraphs, resulting in two queries, as described in Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags - answer of Vlad Mihalcea.
User
#ToString
#Data
#Entity
#Table(name = "users")
#NamedEntityGraphs(
{
#NamedEntityGraph(
name = "UserWithItems",
attributeNodes = {
#NamedAttributeNode("items")
}
),
#NamedEntityGraph(
name = "UserWithRoles",
attributeNodes = {
#NamedAttributeNode("roles")
}
),
}
)
public class User {
...
}
I assume you have a repository class. For example with extends JpaRepository. Use each NamedEntityGraph on an extra method. (I have omitted the name condition and #Query("..."). The id condition should be sufficient, since it is the user's identifier. #Query("...") is not needed.)
UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
#EntityGraph(value = "UserWithItems", type = EntityGraph.EntityGraphType.LOAD)
Optional<User> getOneWithItemsById(Long id);
#EntityGraph(value = "UserWithRoles", type = EntityGraph.EntityGraphType.LOAD)
Optional<User> getOneWithRolesById(Long id);
....
}
Finally, you can call both methods in a service.
UserService
public interface UserService {
Optional<User> readById(Long id);
}
UserServiceImpl
#Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public Optional<User> readById(Long id) {
// Load user with items into persistence contex
userRepository.getOneWithItemsById(id);
// Load user with roles into persistence context
// (There is only one user instance by id within the persistence context)
return userRepository.getOneWithRolesById(id);
}
}

Spring Data JPA, parametrize #EntityGraph in CrudRepository interface

Is it possible with Spring Data JPA to do smth. like this:
public interface UserDao extends CrudRepository<User, Long> {
#EntityGraph(value = :graphName, type = EntityGraph.EntityGraphType.LOAD)
#Query(value = "SELECT DISTINCT u FROM User u")
List<User> findAllWithDetailsByGraphName(#Param(value="graphName") String graphName);
}
to be able to pass the graphName into method at run-time and invoke the load with a need set of collections? This construction is not working, producing compile time error. Any plays around it also failed...
So, I have multiple collections in User class, which I want to load on condition;
#Entity
#Table(name="user")
#NamedEntityGraphs({
#NamedEntityGraph(name = "User.details", attributeNodes = {
#NamedAttributeNode("phones"), #NamedAttributeNode("emails"), #NamedAttributeNode("pets")}),
#NamedEntityGraph(name = "User.phones", attributeNodes =
{#NamedAttributeNode("phones")}),
#NamedEntityGraph(name = "User.emails", attributeNodes =
{#NamedAttributeNode("emails")}),
#NamedEntityGraph(name = "User.pets", attributeNodes =
{#NamedAttributeNode("pets")})
})
public class User {
#Id
#GeneratedValue(strategy= GenerationType.AUTO, generator="native")
#GenericGenerator(name = "native", strategy = "native")
#Column(name="user_id")
private Long userId;
#Column(name="name")
private String name;
// more fields omitted
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Phone> phones;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Email> emails;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Pet> pets;
}
Now, I only must declare all the methods implicitly, like this:
#EntityGraph(value = "User.phones", type = EntityGraph.EntityGraphType.LOAD)
#Query(value = "SELECT DISTINCT u FROM User u")
List<User> findAllWithPhones();
Thnaks you for suggestions!
You can define a base JPA repository that accepts the entity graph as a parameter. This is particularly useful in combination with Specifications. Therefore, here follows an example based on Specifications. It's also possible to construct other queries, using different types of arguments.
#NoRepositoryBean
public interface MyBaseRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
T findOne(Specification<T> spec, EntityGraphType entityGraphType, String entityGraphName);
List<T> findAll(Specification<T> spec, Sort sort, EntityGraphType entityGraphType, String entityGraphName);
}
Implement the base repository:
#NoRepositoryBean
public class MyBaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyBaseRepository<T, ID> {
private EntityManager em;
public MyBaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.em = entityManager;
}
public MyBaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.em = em;
}
#Override
public T findOne(Specification<T> spec, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
TypedQuery<T> query = getQuery(spec, (Sort) null);
query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
return query.getSingleResult();
}
#Override
public List<T> findAll(Specification<T> spec, Sort sort, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
TypedQuery<T> query = getQuery(spec, sort);
query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
return query.getResultList();
}
}
Specify the custom base repository:
<jpa:repositories base-package="my.domain" base-class="my.repository.MyBaseRepositoryImpl" />
Extend from custom base repository:
public interface UserRepository extends JpaRepository<User, Long>, MyBaseRepository<User, Long>, JpaSpecificationExecutor<User> {
}
Use the custom repository's method:
Specification mySpecs = ...
List<User> user = picklistRepository.findAll(mySpecs, EntityGraphType.LOAD, "User.phones");

How to join multiple queryDSL tables

I have some tables and I want to get result using queryDSL join, but haven't found any examples on multiple joins using queryDSL.
I have these tables:
Account table: accountId (PK) | email | password
account_profile table: accountId (PK)(fk to account) | nickname
Community table: articleId (PK) | accountId (fk to account) | title | content
Now I want below JPQL to be queryDSL code
select r from community r join r.account.profile a where a.nickname = :nickname
I have entity metamodels - QAccount, QAccountProfile, QCommunity
Additionally, I have to get the result with pagination, so the query should be called with pageable object.
Here is my work that doesn't work yet.
JPAQuery</*What generic type expected?*/> query = new JPAQuery</*???*/>(entityManager);
Predicate predicate = query.from(QCommunity.community).join(/*join directly accountProfile? or account? is it QEntity or real entity?*/);
// where should I place nickname matching condition ?
...
list = (repository.findAll(predicate, pageable)).getContent();
Where should I place the nickname matching condition?
EDIT: Appended entity information
Account.java
#Entity
#Table(name="account", uniqueConstraints={
#UniqueConstraint(columnNames="account_seq"),
#UniqueConstraint(columnNames="email")
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class Account implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="account_seq", nullable=false, unique=true)
private Integer accountId;
#Column(name="email", nullable=false, unique=true)
#NonNull
private String email;
#NonNull
private String password;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountProfile profile;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountSecurity security;
}
AccountProfile.java
#Entity
#Table(name="account_profile", uniqueConstraints={
#UniqueConstraint(columnNames={"account_seq"}),
#UniqueConstraint(columnNames={"nickname"})
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class AccountProfile implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="nickname", nullable=false)
#NonNull
private String nickname;
}
Community.java
#Entity
#Table(name="community", uniqueConstraints = {
#UniqueConstraint(columnNames="article_seq")
})
#DynamicInsert
#DynamicUpdate
#Data
#NoArgsConstructor
#EqualsAndHashCode
#ToString(includeFieldNames=true)
public class Community {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="article_seq", nullable=false, unique=true)
private Long articleId;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="title", nullable=false)
private String title;
#Column(name="content", nullable=false)
private String content;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="reg_dt")
private Date date;
#Column(name="read_cnt", nullable=false)
private int readCount;
#Column(name="attach_url")
private String attachUrl;
#Column(name="attach_filename")
private String attachFileName;
#OneToMany(cascade=CascadeType.ALL, mappedBy="article")
private Set<CommunityReply> replies;
}
EDIT: PROBLEM SOLVED
To help others who is facing the problem like me, I am gonna post my working code. the code is searching any community articles with matching specific nickname.
#PersistenceContext
private EntityManager entityManager;
private List<Community> getList(int pageNo, String keyword, int rowsOnPage){
int offset = (pageNo -1) * rowsOnPage;
int limit = rowsOnPage;
JPAQuery<Community> query = new JPAQuery<Community>(entityManager);
QCommunity qCommunity = QCommunity.community;
QAccount qAccount = QAccount.account;
QAccountProfile qAccountProfile = QAccountProfile.accountProfile;
return query
.from(qCommunity)
.innerJoin(qCommunity.account ,qAccount)
.innerJoin(qAccount.profile, qAccountProfile)
.where(qAccountProfile.nickname.like("%"+keyword+"%"))
.orderBy(qCommunity.articleId.desc())
.offset(offset)
.limit(limit)
.fetch();
}
First of all, declare a custom extended base repository class for QueryDSL queries.
First the interface:
#NoRepositoryBean
public interface ExtendedQueryDslJpaRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable);
}
And then the implementation:
public class ExtendedQueryDslJpaRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID> implements ExtendedQueryDslJpaRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
private EntityManager entityManager;
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder(this.path.getType(), this.path.getMetadata());
this.querydsl = new Querydsl(entityManager, this.builder);
this.entityManager = entityManager;
}
#Override
public <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable) {
// Count query
final JPQLQuery<?> countQuery = jpqlQuery;
// Apply pagination
JPQLQuery<T1> query = querydsl.applyPagination(pageable, jpqlQuery);
// Run query
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
}
Define the new class as base for base and repositories in a #Configuration class.
#Configuration
#EnableJpaRepositories(basePackageClasses = ..., repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class)
Your repositories then should extend from the new interface (which of course extends JpaRepository):
#Repository
public interface CommunityRepository extends ExtendedQueryDslJpaRepository<Community, Long> {
}
Then, you can try the following code:
String nickname = "nick";
QAccount account = QAccount.account;
QAccountProfile accountProfile = QAccountProfile.accountProfile;
QCommunity community = QCommunity.community;
JPQLQuery query = new JPAQuery(entityManager);
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(accountProfile.nickname.eq(nickname));
// select r from community r join r.account.profile a where a.nickname = :nickname
query.from(community)
.join(community.account, account)
.join(account.accountProfile, accountProfile)
.where(predicate);
repository.findAll(query, pageable);
Hope that helps.
I found one solution as
QEntity qEntity1 = new QEntity("qEntity1");
QEntity qEntity2 = new QEntity("qEntity2");
so while querying you can use
new JPAQueryFactory(entityManager).from(qSampleBO)
.innerJoin(qEntity1).on(qEntity1.id.eq(qSampleBO.address.id))
.innerJoin(qEntity2).on(qEntity2.id.eq(qSampleBO.secondary_address.id))
...

Resources