How to join fetch two associations with JPA/Hibernate - java

I am new to Hibernate and really need help from you guys....
I have following class: UserActionPerUserResult
#Entity
#Table(name = "user_action_per_user_result")
public class UserActionPerUserResult {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "user_email", nullable = false)
private String userEmail;
#ManyToOne
#JoinColumn(name = "user_action_id", nullable = false)
private UserAction userAction;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="user_action_per_user_result_email_message",
joinColumns={#JoinColumn(name="user_action_per_user_result_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="email_message_id", referencedColumnName="id")})
#JsonIgnore
private List<EmailMessage> emailMessages;
....getters/setter....
}
In MYSQL, the UserActionPerUserResult is one table and for email Messages, there is another table called as UserActionPerUserResultEmailMessage which has email messages associate with id of the table UserActionPerUserResult.
I have all the data stored in the MySQL table, however I am not able to query it. I need to write a query to fetch the list of emailMessages. I am trying the following, but it is throwing exception.
TypedQuery<UserActionPerUserResult> messagesQuery = entityManager.createQuery(
"SELECT e from UserActionPerUserResult e JOIN UserActionPerUserResult.emailMessages e1 WHERE e.id = 1 and e.userAction = 1", UserActionPerUserResult.class);
List<UserActionPerUserResult> resultList = messagesQuery.getResultList();

Try writing the query like this:
TypedQuery<UserActionPerUserResult> messagesQuery = entityManager.createQuery(
"SELECT e from UserActionPerUserResult e JOIN FETCH e.emailMessages em WHERE e.id = 1 and e.userAction.id = 1", UserActionPerUserResult.class);
List<UserActionPerUserResult> resultList = messagesQuery.getResultList();
The join is using the root entity alias
The userAction.id is used against userAction, when matching against a numerical value

Related

JPA: how to select data by child ID in OneToMany relation

I have two models Quality and ExpertsData:
#Entity(name = "QualityDepartmentData")
#Table(name = "quality")
public class QualityDepartmentData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "qualityId")
private List<ExpertsData> ListOfExpertsData;
#Entity(name = "ExpertsData")
#Table(name = "experts")
public class ExpertsData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private Long qualityId;
private Integer expertId;
I need to fetch list of QualityDepartmentData by the expertId. Right now I can do it by native MySQL query, like this:
#Query(value = "SELECT * FROM quality INNER JOIN experts ON quality.id = experts.quality_id WHERE experts.expert_id = ?1", nativeQuery = true)
Page<QualityDepartmentData> findAllForExpertId(long id, Pageable pageable);
That is working, but the problem is that the native query can't be dynamically sorted, so I need to write query in JPQL for ability to use org.springframework.data.domain.Pageable.
Can't find any examples for such event in official JPA docs. Also I tryed many vatiants like this, but it's not working:
#Query(value = "SELECT d FROM QualityDepartmentData d INNER JOIN ExpertsData c ON d.id = c.qualityId WHERE c.expertId = ?1")
#Query(value = "SELECT distinct q FROM QualityDepartmentData q JOIN q.ListOfExpertsData e WHERE e.expertId = ?1")
Page<QualityDepartmentData> findAllForExpertId(Integer id, Pageable pageable);
That is working. Thanks for link to example to JB Nizet!

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

NamedQuery miss places table in OnetoOne relation

i have a problem with named query in my project. I have 2 entities with OneToOne relation.
#Entity
#Table(name = "SL_BRANCH_PARAMS")
public class BranchParams implements Identifiable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "SL_BRANCH_PARAM_SEQ_GEN")
#SequenceGenerator(name = "SL_BRANCH_PARAM_SEQ_GEN", sequenceName = "SL_BRANCH_PARAM_SEQ")
private Long id;
#JoinColumn(name = "INTEREST_ACCOUNT_ID")
#OneToOne(fetch = FetchType.EAGER)
private AccountDef interestAccount;
}
and second class with named query
#Entity
#Table(name = "SL_ACCOUNT_DEF")
#NamedQueries({
#NamedQuery(name = "AccountDef.getAvaibleInterestAccountsForBranch",
query = "SELECT ad FROM AccountDef ad LEFT JOIN FETCH ad.branchParamsInterest WHERE ad.branchParamsInterest = NULL ORDER BY ad.id ASC"),
})
public class AccountDef implements Identifiable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "SL_ACCOUNT_DEF_SEQ_GEN")
#SequenceGenerator(name = "SL_ACCOUNT_DEF_SEQ_GEN", sequenceName = "SL_ACCOUNT_DEF_SEQ")
private Long id;
#OneToOne(fetch = FetchType.EAGER, mappedBy = "interestAccount")
private BranchParams branchParamsInterest;
}
When i execute the named query I get this query to database
select
accountdef0_.ID as ID1_10_0_,
branchpara1_.ID as ID1_13_1_,
branchpara1_.INTEREST_ACCOUNT_ID as INTERES16_13_1_
from
SL_ACCOUNT_DEF accountdef0_
left outer join
SL_BRANCH_PARAMS branchpara1_
on accountdef0_.ID=branchpara1_.INTEREST_ACCOUNT_ID
where
accountdef0_.ID is null //this is incorrect
order by
accountdef0_.ID ASC
which is not correct because it gives me no rows as it checks if the ID in AccountDef is null instead in BranchParams.
The correct query should look like this
select
accountdef0_.ID as ID1_10_0_,
branchpara1_.ID as ID1_13_1_,
branchpara1_.INTEREST_ACCOUNT_ID as INTERES16_13_1_
from
SL_ACCOUNT_DEF accountdef0_
left outer join
SL_BRANCH_PARAMS branchpara1_
on accountdef0_.ID=branchpara1_.INTEREST_ACCOUNT_ID
where
branchpara1_.ID is null //this is correct
order by
accountdef0_.ID ASC
and such query returns the rows i want. And the question from me is, why named query checks null id value for AccountDef instead for BranchParams?
Perhaps something like this?
#NamedQuery(name = "AccountDef.getAvaibleInterestAccountsForBranch",
query = "SELECT ad FROM AccountDef ad LEFT JOIN FETCH ad.branchParamsInterest bp WHERE bp.interestAccount = NULL ORDER BY ad.id ASC")

How can I construct a SetJoin in JPA when there's no Set in my entity member field?

I’m using JPA 2.0, Hibernate 4.1.0.Final, and MySQL 5.5.37. I have the following entities
#Entity
#Table(name = "user_subscription",
uniqueConstraints = { #UniqueConstraint(columnNames = { "USER_ID", “SUBSCRIPTION_ID" }) }
)
public class UserSubscription
{
#Id
#Column(name = "ID")
#GeneratedValue(generator = "uuid-strategy")
private String id;
#ManyToOne
#JoinColumn(name = "USER_ID", nullable = false, updatable = true)
private User user;
#ManyToOne
#JoinColumn(name = “SUBSCRIPTION_ID", nullable = false, updatable = true)
private Subscription subscription;
and
#Entity
#Table(name = "Subscription")
public class Subscription implements Serializable
{
#Id
#Column(name = "ID")
#GeneratedValue(generator = "uuid-strategy")
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PRODUCT_ID")
#NotNull
private Product product;
Without altering the entities, how do I construct a JPA CriteriaBuilder query in which I look for User entities that do not have a particular Subscription entity “A”, but have other subscription entities that match the same product as entity “A”? I have tried this to no avail …
public List<User> findUsersWithSubscriptions(Subscription Subscription)
{
final List<User> results = new ArrayList<User>();
final CriteriaBuilder builder = m_entityManager.getCriteriaBuilder();
final CriteriaQuery<UserSubscription> criteria = builder.createQuery(UserSubscription.class);
final Root<UserSubscription> root = criteria.from(UserSubscription.class);
Join<UserSubscription, Subscription> SubscriptionRoot = root.join(UserSubscription_.subscription);
criteria.select(root).where(builder.equal(root.get(UserSubscription_.Subscription).get(Subscription_.product),subscription.getProduct()),
builder.notEqual(root.get(UserSubscription_.subscription), subscription));
I thought if I could build a SetJoin from the user -> subscription entities, I could say something like “not.in”, but I’m not sure how to do that given the constraints.
Edit: This is the SQL produced by Vlad's post:
SELECT user1_.id AS id97_,
user1_.creator_id AS CREATOR15_97_,
user1_.dob AS DOB97_,
user1_.enabled AS ENABLED97_,
user1_.expiration AS EXPIRATION97_,
user1_.first_name AS first5_97_,
user1_.grade_id AS GRADE16_97_,
user1_.incorrect_logins AS INCORRECT6_97_,
user1_.last_name AS last7_97_,
user1_.middle_name AS middle8_97_,
user1_.organization_id AS organiz17_97_,
user1_.password AS password97_,
user1_.reset_state AS RESET10_97_,
user1_.salutation AS salutation97_,
user1_.temporary_password AS temporary12_97_,
user1_.url AS url97_,
user1_.user_demographic_info_id AS USER18_97_,
user1_.user_name AS user14_97_
FROM sb_user_subscription subscription0_
INNER JOIN sb_user user1_
ON subscription0_.user_id = user1_.id
INNER JOIN cb_subscription subscription2_
ON subscription0_.subscription_id = subscription2_.id
INNER JOIN sb_product product3_
ON subscription2_.product_id = product3_.id
AND product3_.id = ?
AND subscription2_.id <>?
Check this query:
final CriteriaBuilder builder = m_entityManager.getCriteriaBuilder();
final CriteriaQuery<User> criteria = builder.createQuery(User.class);
final Root<UserSubscription> root = criteria.from(UserSubscription.class);
Join<UserSubscription, User> userJoin = root.join(UserSubscription_.user);
Join<UserSubscription, Subscription> subscriptionJoin = root.join(UserSubscription_.subscription);
Join<Subscription, Product> productJoin = subscriptionJoin.join(Subscription_.product);
criteria
.select(userJoin)
.where(cb.and(
builder.equal(productJoin, subscription.getProduct()),
builder.notEqual(subscriptionJoin, subscription)
);
return entityManager.createQuery(criteria).getResultList();
The output query looks fine and it should select Users with a given subscription.product and with a different subscription than the product parent's one.
You could try it in your SQL console, but it looks fine and it validates the initial requirement:
that do not have a particular Subscription entity “A”, but have other
subscription entities that match the same product as entity “A”

JPA persist many to many

I have a pretty standard scenario whereby I have a table of Users with user_id as the PK and a table of Roles with role_id as the PK. The two tables are related via a many to many relationship (ie. Users can have many roles and a role can be applied to many users) and subsequently I have a joining table called users_has_roles. The only two columns in users_has_roles are users_user_id and roles_role_id.
I have generated the entity classes (see below) and I have no problem persisting data to the users and roles tables but I have failed miserably persist anything to the users_has_roles joining table so currently none of my users are being assigned a role. Before I go crazy could somebody put me out of my misery and show me how I should go about adding a users_user_id with a corresponding roles_role_id to the users_has_roles table so my users can have roles?
My Users.java entity class:
#Entity
#Table(name = "users")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Users.findAll", query = "SELECT u FROM Users u"),
#NamedQuery(name = "Users.findByUserId", query = "SELECT u FROM Users u WHERE u.userId = :userId"),
#NamedQuery(name = "Users.findByUsername", query = "SELECT u FROM Users u WHERE u.username = :username"),
#NamedQuery(name = "Users.findByPassword", query = "SELECT u FROM Users u WHERE u.password = :password")})
public class Users implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 60)
#Column(name = "user_id")
private String userId;
#Basic(optional = false)
#NotNull
#Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")
#Size(min = 1, max = 45)
#Column(name = "username")
private String username;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 120)
#Column(name = "password")
private String password;
#JoinTable(name = "users_has_roles", joinColumns = {
#JoinColumn(name = "users_user_id", referencedColumnName = "user_id")}, inverseJoinColumns = {
#JoinColumn(name = "roles_role_id", referencedColumnName = "role_id")})
#ManyToMany
private Collection<Roles> rolesCollection;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserAccount> userAccountCollection;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserDetails> userDetailsCollection;
...
All the getter and setter methods etc.
My Roles.java entity class:
#Entity
#Table(name = "roles")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Roles.findAll", query = "SELECT r FROM Roles r"),
#NamedQuery(name = "Roles.findByRoleId", query = "SELECT r FROM Roles r WHERE r.roleId = :roleId"),
#NamedQuery(name = "Roles.findByRoleName", query = "SELECT r FROM Roles r WHERE r.roleName = :roleName"),
#NamedQuery(name = "Roles.findByRolePermission", query = "SELECT r FROM Roles r WHERE r.rolePermission = :rolePermission"),
#NamedQuery(name = "Roles.findByRoleDescription", query = "SELECT r FROM Roles r WHERE r.roleDescription = :roleDescription")})
public class Roles implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 60)
#Column(name = "role_id")
private String roleId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "role_name")
private String roleName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "role_permission")
private String rolePermission;
#Size(max = 45)
#Column(name = "role_description")
private String roleDescription;
#ManyToMany(mappedBy = "rolesCollection")
private Collection<Users> usersCollection;
...
All the getter and setter methods etc.
Thanks
---- UPDATE ----
// New Users
Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);
getUsersFacade().create(currentUser);
Ok first off thanks to Mikko for leading me to the answer. I just wanted to post an answer that might be directly helpful to anybody else that might be in the position I was in. Also this is based on a Eureka moment so it might not be technically correct but this is how I see it.
The big issue that I faces was that in MySQL I could see the bridging table as an individual table! (sorry I can't post an image of my EER diagram but I don't seem to have enough privileges at the moment) So I assumed that Java would also see the bridging table as a table! Well it doesn't. That bridging table doesn't really exist in Java as a conventional table it is in fact represented by the opposing tables collection type that you associate with it.
The easiest way to see it for me was to completely forget the bridging table and concentrate on the two 'real' tables and associating the data in those. The following code is NOT best practice as I'm simply setting the role_id but it's fine just to show my point.
List<Roles> userRoleList = new ArrayList<Roles>();
Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);
Roles userRole = new Roles();
userRole.setRoleId("2");
userRoleList.add(userRole);
currentUser.setRolesCollection(userRoleList);
getUsersFacade().create(currentUser);
Hope that helps anybody else that is struggling with many to many relationships.
(NB. I've edited the original question code to use a List instead of a Collection for ease but you can just as well use any other type that fits your needs.)
Your example works fine (EclipseLink 2.3, MySQL). Likely problem is in part of the code that you do not show. For example in adding element to rolesCollection. Typical mistake is for example to add element only to the non owning side.
For persisting it you have to keep care about relation in owning side (one without mappedBy), for keeping also in-memory object graph consistent with database, you should always modify both sides of relation.
I tried it with following:
// method to add element to rolesCollection
public void addRoles(Roles r) {
rolesCollection.add(r);
}
//first created new instances of Users and Roles
// then:
tx.begin();
users.addRoles(r);
//it is not needed for persisting, but here you can also
//add user to roles.
em.persist(users);
em.persist(r);
tx.commit();

Categories