Hibernate Criterion - join between more tables - java

I've got a problem with this situation (i'll try to make it easier) - there are users in my DB with list of roles and list of statuses.
public class User implements Serializable {
#ManyToMany(cascade = CascadeType.ALL, fetch= FetchType.LAZY)
#JoinTable(name = "role_to_user",
joinColumns={#JoinColumn(name = "user_id")},
inverseJoinColumns={#JoinColumn(name = "role_id")})
private Set<Role> roles = new LinkedHashSet<Role>();
#ManyToMany(cascade = CascadeType.ALL, fetch= FetchType.LAZY)
#JoinTable(name = "status_to_user",
joinColumns={#JoinColumn(name = "user_id")},
inverseJoinColumns={#JoinColumn(name = "status_id")})
private Set<Status> statuses = new LinkedHashSet<Status>();
}
I am trying to create a hibernate criteria, which was abble to return users (after join with role table and status table) with specified roles and statuses. Something like :
return users with role = 1 or role = 2 and status = 1
I googled something and now I can create a query which returns me users only with specified roles, but not statuses.
Criteria cr = session.createCriteria(User.class)
.createCriteria("roles")
.add(Restrictions.or(Restrictions.eq("roletype", 1), Restrictions.eq("roletype", 2)));
Table user and role are connected through role_to_user table with two columns (user_id, role_id), similarly in the same way table user and status through status_to_role table
Thanks for advice :)

Criteria cr = session.createCriteria(User.class, "user")
.createAlias("user.roles", "role", Criteria.INNER_JOIN)
.createAlias("user.statuses", "status", Criteria.LEFT_JOIN);
cr.add(Restrictions.or(
Restrictions.eq("role.roletype", 1),
Restrictions.and(
Restrictions.eq("role.roletype", 2),
Restrictions.eq("status.statusType", 1))));

Related

How can i save related entity in Database using dual Many-To-Many relationship?

I have two entities:
User
Work
they have many-to-many relationship with each-over.
#ManyToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinTable(
name = "user_work",
joinColumns = #JoinColumn(name = "work_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> workUsers;
when i add new work using JPA :
List<UserDto> users = new ArrayList<>();
users.add(new User(something something));
WorkDto work1 = new WorkDto(1, users));
workRepository.save(workMapper.fromDto(work1));
problem : when i save my work entity in the database it does not save user, so when i extract it , work says that is has no users.
How can i insert work into database and add users to it as well? I have mutual table with work_id and user_id of course
Are you executing it within a transaction with #Transactional? Have you #Override the equals method?
More detail is needed like the relationship in the User class

Hibernate nested JoinColumns results in a big query from the database with unnecessary data

I'm working on some personal project but i have a question about hibernate.
I have a class structure like this :
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyUser")
private Company company = new Company();
}
But inside the company i have another join.
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
}
Now i do a query to get the estimate options.
But if i do it like this it loads lots of unnecessary stuff .
#RequestMapping(value = "/estimateoptions")
public EstimateOptions getCompanyEstimateOptions(#AuthenticationPrincipal Principal user) {
User getuser = userDao.findByEmail(user.getName());
EstimateOptions estimateOptions = getuser.getCompany().getEstimateOptions();
return estimateOptions;
}
is there a better approach for this ?
There are a lot of ways to do such optimization. The simplest one is add bidirectional associations to Company and EstimateOptions with lazy loading.
An example for Company ( I don't test. It is just a sketch.)
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
#OneToOne(mappedBy="company", fetch = FetchType.LAZY)
private User user;
}
And do something like this (this is HQL but you can use a criteria API too)
from EstimateOptions options inner join options.company company inner join company.user user where user.name = :userName
You can see HQL joined query to eager fetch a large number of relationships for additional thoughts.
Updated
I am not sure but may be you can do something like this (without additional associations)
select options from User user inner join user.company company inner join company.estimateOptions options where user.name = :userName

JPA 2 multiple #CollectionTable in Entity causing unrelated outer joins

Hi I have the following JPA entity class. It has two #CollectionTable mapping for Groups and Contact Id's. I am interesting in getting back a list of unique contact id's from the contact_details table and having a reference to them in my Users class variable contacts below:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(generator="uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private UUID id;
#NotBlank
private String username;
#NotBlank
#Column(name = "name")
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "users_groups", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[group]")
private List<String> groups = new ArrayList<>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "contact_detail", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[id]")
private List<String> contacts = new ArrayList<String>();
}
When I get a user from the database it produces the following query in the hibernate logs
select user0_.id, user0_.password, user0_.username, contacts1_.user_id as user_id8_4_1_, contacts1_."id" as id1_2_1_, groups2_.user_id as user_id1_4_2_, groups2_."group" as group2_5_2_
from users user0_
left outer join contact_detail contacts1_ on user0_.id=contacts1_.user_id
left outer join users_groups groups2_ on user0_.id=groups2_.user_id
where user0_.id=?
Because of the Left Outer Join on contact_detail and users_groups, it actually retrieves back the same contact_id multiple times. This is a JSON representation of the entity with multiple "47e5b98a-2116-4ad9-b773-3acc99e2c83c" contact id's
{
user: {
id: "d3b3be2a-8a2a-48ac-94dd-fd85faf1a8ff",
username: "shiv",
firstName: "Shivam",
lastName: "Sinha",
groups: [
"ADMIN",
"USER",
"ROOT"
],
expired: false,
locked: false,
credentialsExpired: false,
enable: true,
birthday: "2015-05-18",
joined: "2015-05-18",
gender: "M",
contactDetails: null,
contacts: [
"47e5b98a-2116-4ad9-b773-3acc99e2c83c",
"47e5b98a-2116-4ad9-b773-3acc99e2c83c",
"47e5b98a-2116-4ad9-b773-3acc99e2c83c"
]
}
}
However when I completely remove the following groups class variable:
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "users_groups", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[group]")
private List<String> groups = new ArrayList<>();
And attempt to retrieve the the user from the database. Hibernate generates the following sql:
select user0_.id, user0_.password, user0_.username, contacts1_.user_id as user_id8_4_1_, contacts1_."id" as id1_2_1_
from users user0_ left outer join contact_detail contacts1_ on user0_.id=contacts1_.user_id
where user0_.id=?
This is the JSON representation of the entity:
{
user: {
id: "d3b3be2a-8a2a-48ac-94dd-fd85faf1a8ff",
username: "shiv",
firstName: "Shivam",
lastName: "Sinha",
expired: false,
locked: false,
credentialsExpired: false,
enable: true,
birthday: "2015-05-18",
joined: "2015-05-18",
gender: "M",
contacts: [
"47e5b98a-2116-4ad9-b773-3acc99e2c83c"
]
}
}
It only contains unique contact id "47e5b98a-2116-4ad9-b773-3acc99e2c83c". Which is what is expected.
So my question is how can i achieve the same thing without having to remove groups class variable OR changing the datatype from List<String> contacts to Set<String> contacts
The behaviour that is happening now is as expected as can be seen from the hibernate FAQ.The solution is mentioned in the FAQ here as well
If you are looking to do this via JPA refer here.
Following is the implementation in Spring data using the following Specification
public class UserSpecifications {
public static Specification<User> findUniqueUser(Integer userId){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> userRoot,
CriteriaQuery<?> criteriaQ, CriteriaBuilder critieriaB) {
criteriaQ.distinct(true);
return critieriaB.equal(userRoot.get(User_.id), 1);
}
};
}
}
To find the record
User user = userRepository.findOne(UserSpecifications.findUniqueUser(1));
Also have a look at this
https://developer.jboss.org/wiki/AShortPrimerOnFetchingStrategies .
If you use fetch="join" for more than one collection role for a
particular entity instance (in "parallel"), you create a Cartesian
product (also called cross join) and two (lazy or non-lazy) SELECT
would probably be faster.
This Cartesian product is what you are describing (multiple values). Try attempting to change it to use select as suggested:
#Fetch(FetchMode.JOIN) or Alternatively also try the batch Mode.
This is a further explanation of the fetching strategies:
http://www.mkyong.com/hibernate/hibernate-fetching-strategies-examples/

why i can't persist object by openjpa? - incorrect validation

In my web applicaton I use OpenJPA on Apache Tomcat (TomEE)/7.0.37 server. I use Netbeans to auto generate class ("Entity Class from database..." and "Session Beans From Entity Class...").
my User.class:
#Entity
#Table(name = "USER")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(name = "User.findByIdUser", query = "SELECT u FROM User u WHERE u.idUser = :idUser"),
#NamedQuery(name = "User.findByLogin", query = "SELECT u FROM User u WHERE u.login = :login"),
#NamedQuery(name = "User.findByPassword", query = "SELECT u FROM User u WHERE u.password = :password")})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_user")
private Short idUser;
#Size(max = 8)
#Column(name = "login")
private String login;
#Size(max = 64)
#Column(name = "password")
private String password;
#JoinTable(name = "USER_has_ROLES", joinColumns = {
#JoinColumn(name = "USER_id", referencedColumnName = "id_user")}, inverseJoinColumns = {
#JoinColumn(name = "ROLES_id", referencedColumnName = "id_roles")})
#ManyToMany
private List<Roles> rolesList;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<Lecturer> lecturerList;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<Student> studentList;
//constructors, getters, setters
}
when I create new user by ManagedBean:
private void addUser() {
User user = new User();
user.setLogin(registerLog);
user.setPassword(registerPass);
Roles r = new Roles();
r.setIdRoles(new Short("2"));
List<Roles> roleList = new ArrayList<Roles>();
roleList.add(r);
user.setRolesList(roleList);
userFacade.create(user); //<------here i create by abstract facade by em.persist(user);
}
i get exception:
javax.el.ELException: javax.ejb.EJBException: The bean encountered a non-application exception; nested exception is: javax.validation.ConstraintViolationException: A validation constraint failure occurred for class "model.entity.User".
viewId=/pages/register.xhtml
location=/home/jakub/Projekty/Collv2/build/web/pages/register.xhtml
phaseId=INVOKE_APPLICATION(5)
Caused by:
javax.validation.ConstraintViolationException - A validation constraint failure occurred for class "model.entity.User".
at org.apache.openjpa.persistence.validation.ValidatorImpl.validate(ValidatorImpl.java:282)
/pages/register.xhtml at line 26 and column 104 action="#{registerController.register}"
it's look like I my user id is not correct. What is wrong ?
I think your problem is your id generation type - GenerationType.IDENTITY. Usually when using identity a special database column is used to generate the id. The id is not generated until the data is inserted into the database and the id itself is not available to the entity until after commit. However, Bean Validation occurs on the pre-persist callback using the current state of the entity. This will fail, because the id is still null.
I probably would just change the generation type.
I won't do this:
Roles r = new Roles();
r.setIdRoles(new Short("2"));
List<Roles> roleList = new ArrayList<Roles>();
roleList.add(r);
This basically is a misused, if this Role is exist and able to link to User you're creating, you should retrieve it and set; otherwise, if you want to create a new Role, don't set #Id for it, or it will cause error as OpenJPA will look for an existing item instead of creating new one.
And, yes, it's possible the cause of your error. The fix is retrieve Role or just don't set #Id attribute. Plus, .setIdRoles() naming is quite strange naming to me.

#ElementCollection, #CollectionTable and Enum - Strange delete/insert behavior

#Entity
public class User{
#ElementCollection
#Enumerated(EnumType.STRING)
#CollectionTable(name = "SEC_USER_ROLES",
joinColumns =
#JoinColumn(name = "USER_ID", referencedColumnName = "ID"))
#Column(name = "ROLE_NAME")
private List<Role> roles;
[...]
}
public enum Role {
ROLE_SUPER_ADMIN,
ROLE_ADMIN,
ROLE_ARB,
ROLE_AP;
[...]
}
With this mapping, when I try do delete one ROLE, for example ROLE_ARB, it always ends up with deleting the role and inserting it once again.
DELETE FROM SEC_USER_ROLES WHERE ((USER_ID = ?) AND (ROLE_NAME = ?))
bind => [9451, ROLE_ADMIN]
INSERT INTO SEC_USER_ROLES (USER_ID, ROLE_NAME) VALUES (?, ?)
bind => [9451, ROLE_ADMIN]
I tried to solve the problem with #OrderColumn (name="USER_ID") but then the mapping of the User_id is not correct.
Any idea would be appreciated.
The Roles are represented as selectManyCheckbox
The ManagedBean prepares the entity (User)
...
List<String> selectedroles = this.getSelectedItems();
List<Role> newroles = new ArrayList<Role>();
if (selectedroles != null) {
for (String r : selectedroles) {
newroles.add(Role.valueOf(r));
}
getEntity().setRoles(newroles);
...
security.save(getEntity());
and the EJB makes updates if it is an existing Entity
EntityManager em;
...
this.em.merge(user);
So when someone deselects all (previous selected) Roles there is always one Role left in the database which is not deleted, because of the delete/insert behavior I described before.
#OrderColumn solved the problem

Categories

Resources