filter lazy initialized collection - java

My objects: user and credential - many to many relationship, however user has a param
I want to get all users with certain param for every credential in a loop
requirement: users have to be loaded in batch.
simple right?
so i have 3 tables:
#Table(name = "CRED")
public class Credential {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name="CRED_ID")
Long credentialId;
#OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "credential")
#BatchSize(size = Search.DEFAULT_PAGE_SIZE)
private Set<UserCredentialMapping> userCredentialMappingSet;
}
#Table(name = "USER_CRED")
public class UserCredentialMapping {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne
#Filter(name="paramFilter", condition="param = :param")
private User user;
#JoinColumn(name = "cred_id", referencedColumnName = "cred_id")
#ManyToOne
private Credential credential;
}
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name="USER_ID")
Long userId;
#Column(name = "PARAM")
String param
}
i'm making a query in one place and return results:
String hqlQuery = "select c from UserCredentialMapping m " +
" inner join m.credential c" +
" inner join m.user u" +
" where u.param = :param" +
" and c.user_id in (:users)" ;
Session session = getSession();
//desparetly trying to set filter
session.enableFilter("paramFilter").setParameter("param", param);
Query query = session.createQuery(hqlQuery);
query.setParameterList("users", USERLIST);
query.setParameter("param", someparam);
List<Credential> credentialList = (List<Credential>)query.list();
return credentialList;
some processing on each credential in mean time and now i need to get list of users with given param:
for(Credential credential : credentialList){
//following line makes hibernate query for users
Iterator<CredentialMapping> mappingIterator = e.getUserCredentialMappingSet().iterator();
while (mappingIterator.hasNext()){
UserCredentialMapping userCred = mappingIterator.next();
User user = userCred.getUser();
DOEVILSTUFFTOINNOCENT(user);
}
My problem is that iterator generates SQL query that gets all users for credential and not all users with specified param for credential (in other words filter is not being applied)
Any advise how to make it work?
Thanks !

I've solved it by adding ManyToMany mapping to Credential class:
#ManyToMany(fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinTable(name = "USER_CRED",
joinColumns = {
#JoinColumn(name = "CRED_ID") },
inverseJoinColumns = {
#JoinColumn(name = "USER_ID") })
#Filter(name="param", condition="PARAM = :param")
#BatchSize(size = Search.DEFAULT_PAGE_SIZE)
private Set<User> users;
i couldn't get it working through UserCredentialMapping...

Related

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

How to join two entities with ManyToMany relationship in the jpa query

I have two entities User and Role. Each user can have multiple roles.
User class
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
Role class:
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
private String name;
}
So a new joined table is being created called: user_role
I want to create a query for returning a list of users with role_id of 4, for example.
The query that I already tried:
#Override
public List<User> getArtists() {
return em.createQuery(
"from User u, Role r where u.roles='4'",
User.class
).getResultList();
}
How can I fix this query in order to retrieve a list of users with role_id of 4?
You can do something like this:
List<User> users = em.createQuery(
"select distinct u from User u join fetch u.roles rl where rl.id = :id",
User.class)
.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, false )
.setParameter("id", 1L)
.getResultList();
The QueryHints.HINT_PASS_DISTINCT_THROUGH is added as an additional performance optimization. But please note that this optimization will work only with hibernate 5.2.2.Final ... 5.2.11.Final. It was broken in the 5.2.12.Final.
If I were you, I will get the benfit of using SpringdataJpa with hibernate and just use this statment :
If you don't want to use query :
List<User> findByRoles_Id(Long id);
In your User Repository :
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByRoles_Id(Long id);
}

Hibernate If the result of 'left join fetch' of #Manytoone is null, select it again

I have tried many.
But the cause could not be found.
I need your help
CardNews Entity
#Entity
#Table(name = "card_news", schema = "pikeul", indexes = #Index(name = "IDX_CARD_NEWS_1", columnList = "user_seq, reg_date"))
public class CardNews implements Serializable {
private static final long serialVersionUID = 8912318097356401738L;
#Id
#Column(name = "card_seq", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long cardSeq;
#Column(name = "user_seq", nullable = false)
private long userSeq;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_seq", referencedColumnName = "user_seq", insertable = false, updatable = false, foreignKey = #ForeignKey(name = "none"))
private User user;
#Fetch(FetchMode.SELECT)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns(value = {
#JoinColumn(name = "card_seq", referencedColumnName = "card_seq", insertable = false, updatable = false),
#JoinColumn(name = "user_seq", referencedColumnName = "user_seq", insertable = false, updatable = false)
}, foreignKey = #ForeignKey(name = "none"))
private LikeCard likeCard = new LikeCard();
// Getter & Setter
}
CardLike Entity
#Entity
#Table(name = "card_like", schema = "pikeul")
public class LikeCard implements Serializable {
#EmbeddedId
private LikeCardPk pk;
public LikeCard(){
this.pk = new LikeCardPk(0, 0);
}
public LikeCard(LikeCardPk pk) {
this.pk = pk;
}
public LikeCardPk getPk() {
return pk;
}
public void setPk(LikeCardPk pk) {
this.pk = pk;
}
}
Spring Data JPA Query Case-1
#EntityGraph(attributePaths = {"user", "likeCard"})
#Query( "SELECT card FROM CardNews card " +
"JOIN card.user user ON user.useYn = true " +
"LEFT JOIN card.likeCard likeCard ON likeCard.pk.userSeq = ?#{principal.user.userSeq ?: '0'} " +
"WHERE card.cardSeq = :cardSeq AND card.useYn = true")
Spring Data JPA Query Case-2
#Query( "SELECT card, user, likeCard FROM CardNews card " +
"JOIN card.user user ON user.useYn = true " +
"LEFT JOIN card.likeCard likeCard ON likeCard.pk.userSeq = ?#{principal.user.userSeq ?: '0'} " +
"WHERE card.cardSeq = :cardSeq AND card.useYn = true")
The problem is as follows. When inquiring CardLike Entity through ManyToOne in CardNews Entity, CardLike Entity exists or does not exist.
CardLike may be null when fetching data through left join fetch when executing Query for Case1 and Case2.
Look at the log.
Hibernate:
select
cardnews0_.card_seq as card_seq1_5_0_,
user1_.user_seq as user_seq1_12_1_,
likecard2_.card_seq as card_seq1_4_2_,
likecard2_.user_seq as user_seq2_4_2_,
cardnews0_.contents as contents2_5_0_,
cardnews0_.game_type as game_typ3_5_0_,
cardnews0_.hash_tag as hash_tag4_5_0_,
cardnews0_.user_seq as user_seq8_5_0_,
cardnews0_.like_count as like_cou5_5_0_,
cardnews0_.reg_date as reg_date6_5_0_,
cardnews0_.use_yn as use_yn7_5_0_,
user1_.agree_date as agree_da2_12_1_,
user1_.agree_yn as agree_yn3_12_1_,
user1_.email as email4_12_1_,
user1_.mod_date as mod_date5_12_1_,
user1_.nickname as nickname6_12_1_,
user1_.night_push_yn as night_pu7_12_1_,
user1_.profile_image as profile_8_12_1_,
user1_.provider as provider9_12_1_,
user1_.provider_id as provide10_12_1_,
user1_.push_date as push_da11_12_1_,
user1_.push_yn as push_yn12_12_1_,
user1_.reg_date as reg_dat13_12_1_,
user1_.thumbnail_image as thumbna14_12_1_,
user1_.use_yn as use_yn15_12_1_
from
card_news cardnews0_
inner join
enter_user user1_
on cardnews0_.user_seq=user1_.user_seq
and (
user1_.use_yn='Y'
)
left outer join
card_like likecard2_
on cardnews0_.card_seq=likecard2_.card_seq
and cardnews0_.user_seq=likecard2_.user_seq
and (
likecard2_.user_seq=?
)
where
cardnews0_.card_seq=?
and cardnews0_.use_yn='Y'
Hibernate:
select
likecard0_.card_seq as card_seq1_4_0_,
likecard0_.user_seq as user_seq2_4_0_
from
card_like likecard0_
where
likecard0_.card_seq=?
and likecard0_.user_seq=?
As above, if data is retrieved through left join fetch but CardLike Entity does not exist, you can see the log to select again.
If this is the result of the list, select 1 + N.
I tried to downgrade but the result was the same
The development environment is as follows
Spring 5.0.4, Spring-Data-JPA 2.0.4, Hibernate 5.2.13

how to write hql query for hibernate OneToMany relationship?

I have the following classes laid out like so:
class User {
#Id
private long id;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Set<UserRole> userRoles;
}
class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "role", nullable = false, length = 45)
private String role;
}
I'm attempting to query for users that have a specific role (in this case, all users with the role ROLE_ADMIN), using the following query:
org.hibernate.query.Query<User> userQuery=session.createQuery("from User as mc where mc.userRoles in (from UserRole as ur where ur.role in (:uRoles))",User.class);
Set<String> roles = new HashSet<String>();
roles.add("ROLE_ADMIN");
userQuery.setParameterList("uRoles", roles);
List<User> admins = userQuery.getResultList();
However, the query is not returning any results.
session.createQuery(
"from User as user join user.userRoles as userRole where userRole.role in :roles")
.setParameter("roles", Arrays.asList("ROLE_ADMIN"))
.getResultList();

Top 5 records by size of collection

I have following entity:
public final class Stock implements Serializable {
#JsonIgnore
#ManyToMany(mappedBy = "stocks", fetch = FetchType.LAZY)
private Set<User> users = new HashSet<>();
[Other fileds]
[getters/setters]
}
And i would like write query in jpql to get top5 Stock entity based on size of set users. So far i write native query in sql and it looks like:
SELECT s.ticker, COUNT(s.ticker)
FROM t_stock s INNER JOIN t_user_stocks us ON s.id = us.stock_id
INNER JOIN t_user u on us.user_id = u.id GROUP BY s.ticker ORDER BY count DESC
And i want a jqpl query which return top 5 stocks entity. Could someone help me?
Assuming your entity is mapped as follows.
#Entity
public class Stock {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String ticker;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "stock_user", joinColumns = { #JoinColumn(name = "STOCK_ID", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "USER_ID", nullable = false, updatable = false) })
private Set<User> users = new HashSet<User>();
}
I did the following using native SQL to get the result.If you insist on using JPQL, the answer here is your friend.
public interface StockRepository extends JpaRepository<Stock, Integer> {
#Query(value = "SELECT s.ticker, COUNT(s.ticker) FROM stock s INNER JOIN "
+ "stock_user us ON s.id = us.stock_id INNER JOIN user u on us.user_id = u.id GROUP BY s.ticker order by count(*) desc limit 1", nativeQuery = true)
public List<Object[]> findStock();
}

Categories