JPA persist many to many - java

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

Related

How to use hibernate search criteria on parameter with set

I have several entities where one of them has a parameter with Set containing other entities from many-to-many join like this (I am using only two of them for simplicity).
How can I use search criteria to filter out Users who have Avatar with id==1?
#Entity
public class User
{
#Id
private String loginId;
private String screenName;
#ManyToMany(mappedBy = "user")
private Set<Avatar> avatars;
}
#Entity
public class Avatar
{
#Id
private Integer id;
#ManyToMany
#JoinTable(
name = "user",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "loginId"))
private User user;
private String url;
}
You can use a native query in order to obtain what you want.
The syntax will look like this:
#Query(
value = "select * from ...",
nativeQuery = true
)
Maybe you can refactor the tables from database without affecting the business logic. I don't know what requirements do you have but I think you can use #ManyToOne.
You can use root.join to achieve this.
int avatarId = 1;
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class);
criteriaBuilder.and(criteriaBuilder.equal (
user.join("avatars").get("id"), avatarId));

Can we selectively fetch the columns of an associated joined object in Hibernate?

#Entity
#Table(name = "USER_INFO")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID")
private int userId;
#OneToOne
#JoinColumn(name = "CGH_SOE_ID", referencedColumnName = "CGH_SOE_ID", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
#Fetch(FetchMode.JOIN)
private UserDimension userDimension;
.......
#Entity
#Table(name = "USER_DIMENSION")
public class UserDimension {
#Column(name = "EMPLID", length = 11)
private String employeeLid;
#Id
#Column(name = "CGH_SOE_ID", length = 10, nullable = false)
private String CGHSOEId;
I use Criteria queries to fetch User objects from database and User dimenstion is associated along with it.
1) I want to fetch only 2-3 columns from UserDimension when i fetch user object from database.
2) Can i dynamically switch between all columns fetch and selected columns fetch ?
Thanks
The simplest answer you cannot. Hibernate retrieves all columns mentioned in the entity.
Possible workarounds:
You can define 2 more entities UserShort and UserDimensionShort based on the same tables but with limited columns.
Use JdbcTemplate where you can fire SQL query (with desired columns only) and add a mapping to get the columns data to the entity fields. (See the example)

hibernate path expected for join but path is set

I have an hql query:
"from User u inner join UserRole ur on ur.user_name = u.user_name and ur.user_role =ROLE_MANAGER "
And it shows an error though the path is set. I tried different variants of hql but error remains the same. I use those 2 entites for spring security login from db and it works fine. But when i'm trying to get user with specified role it doesn't work.
My entities:
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private int user_id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "passwort", nullable = false)
private String password;
#Column(name = "email")
private String email = "hromnikforever#gmail.com";
#Column(name = "enabled", nullable = false)
private int enabled = 1;
#Autowired
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserRole> userRoles = new HashSet<UserRole>(0);
UserRole entity:
#Entity
#Table(name = "USER_ROLES")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id",unique = true, nullable = false)
private int user_role_id;
#Column(name = "username")
private String username;
#Column(name = "user_role")
private String user_role;
If i change my HQL query to:
from User u inner join u.userRole ur on ur.user_name = u.user_name
and ur.user_role =ROLE_MANAGER "
it shows an error that
could not resolve property: userRole of: com.webproject.User [from com.webproject.User u inner join u.userRole ur on ur.user_name = u.user_name and ur.user_role =ROLE_MANAGER ]
Instead explicit JOIN try with comma notation, and change the second JOIN table with UserRole, as follow:
from User u, UserRole ur
where ur.user_name = u.user_name
and ur.user_role = ROLE_MANAGER
If you want only User elements complete your query as follow:
select u from User u, UserRole ur
where ur.user_name = u.user_name
and ur.user_role = ROLE_MANAGER
You have a typo in your query, since your User entity doesn't have a userRole, but userRoles
from User u
inner join u.userRoles ur on ur.user_name = u.user_name
and ur.user_role = ROLE_MANAGER

How to join fetch two associations with JPA/Hibernate

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

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”

Categories