Fetch Many to Many collection using Hibernate Criteria - java

I have two entities User and Attribute and they have Many to Many relation. like below.
public class User implements java.io.Serializable {
private Set<Attribute> attributes = new HashSet<>();
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "user_attributes", joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "attribute_id") })
public Set<Attribute> getAttributes() {
return attributes;
}
}
User_Attributes table may or may not contain data. I am trying to fetch user like below
public User getUserByUserNameAndStatus(String userName, int status) {
Criteria criteria = getSession().createCriteria(User.class);
criteria.createAlias("attributes", "attr", JoinType.LEFT_OUTER_JOIN);
criteria.setFetchMode("roles", FetchMode.JOIN);
criteria.add(Restrictions.eq("attr.status", 1));
criteria.add(Restrictions.eq("userName", userName));
criteria.add(Restrictions.eq("status", status));
User user = (User) criteria.uniqueResult();
return user;
}
This code return me user object only if there is an entry in User_Attributes table otherwise it is returning me null. But what I want to fetch user object also if there is no entry in User_Attributes table.

Related

java jpa one to one relation is always null for one side?

I have two entity booking and travelAgentBooking, booking could exist by itself while travelAgentBooing must have one booking of it.
TABookingEntity is below
#Entity
#ApplicationScoped
#Table(name = "TABooking")
#NamedQuery(name = "TABooking.findAll", query = "SELECT t FROM TABookingEntity t ORDER BY t.id ASC")
public class TABookingEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABookingId_seq")
#SequenceGenerator(name = "TABookingId_seq", initialValue = 1, allocationSize = 1)
private Long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "booking_id", nullable = false)
private BookingEntity flightbooking;
// belong to upstream booking so we just store id here
private Long taxibookingid;
private Long hotelbookingid;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BookingEntity getFlightbooking() {
return flightbooking;
}
public void setFlightbooking(BookingEntity flightbooking) {
this.flightbooking = flightbooking;
if (flightbooking != null) {
flightbooking.setTravelAgentBooking(this);
}
}
public Long getTaxibookingId() {
return taxibookingid;
}
public void setTaxibookingId(Long taxibookingid) {
this.taxibookingid = taxibookingid;
}
public Long getHotelbookingId() {
return hotelbookingid;
}
public void setHotelbookingId(Long hotelbookingid) {
this.hotelbookingid = hotelbookingid;
}
BookingEntity is below
#Entity
#ApplicationScoped
#Table(name = "booking")
#NamedQueries({ #NamedQuery(name = "Booking.findAll", query = "SELECT b FROM BookingEntity b ORDER BY b.d ASC"),
#NamedQuery(name = "Booking.findByFlight", query = "SELECT b FROM BookingEntity b WHERE b.flight = :flight"),
#NamedQuery(name = "Booking.findByDate", query = "SELECT b FROM BookingEntity b WHERE b.d = :d") })
public class BookingEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookingId_seq")
#SequenceGenerator(name = "bookingId_seq", initialValue = 1, allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id", nullable = false)
private CustomerEntity customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "flight_id", nullable = false)
private FlightEntity flight;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "travelAgentBooking_id", nullable = true)
private TABookingEntity travelAgentBooking;
#NotNull
#Column(name = "date")
private Date d;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public CustomerEntity getCustomer() {
return customer;
}
public void setCustomer(CustomerEntity customer) {
this.customer = customer;
if(customer != null)
customer.addBooking(this);
}
public FlightEntity getFlight() {
return flight;
}
public void setFlight(FlightEntity flight) {
this.flight = flight;
}
public Date getDate() {
return new Date(d.getTime());
}
public void setDate(Date d) {
this.d = d;
}
public TABookingEntity getTravelAgentBooking() {
return travelAgentBooking;
}
public void setTravelAgentBooking(TABookingEntity travelAgentBooking) {
this.travelAgentBooking = travelAgentBooking;
}
here is the code I creating booking firstly, and then set it to tabooking.
then I'm trying to update the booking since when it is created, there is no travelAngentBooking for it to associate.
Booking booking = flightService.createBooking(tabooking.getFlightbooking());
tabooking.setFlightbooking(booking);
,,,,,,,,,,,
,,,,,,,,,,,
tabookingService.create(tabooking);
flightService.updateBooking(tabooking.getFlightbooking().getId(), tabooking.getFlightbooking());
After running it the table of travelAgentBooking is perfect.
But booking table column referred to travelAgentBooking is always null for any booking object.
UPDATE:
#PUT
#Path("/{id:[0-9]+}")
#Operation(description = "Update a Booking in the database")
#APIResponses(value = { #APIResponse(responseCode = "200", description = "Booking updated successfully"),
#APIResponse(responseCode = "400", description = "Invalid Booking supplied in request body"),
#APIResponse(responseCode = "404", description = "Booking with id not found"),
#APIResponse(responseCode = "409", description = "Booking details supplied in request body conflict with another existing Booking"),
#APIResponse(responseCode = "500", description = "An unexpected error occurred whilst processing the request") })
#Transactional
public Response updateBooking(
#Parameter(description = "Id of Booking to be updated", required = true) #Schema(minimum = "0") #PathParam("id") Integer id,
#Parameter(description = "JSON representation of Booking object to be updated in the database", required = true) Booking booking) {
Customer customer = customerService.findById(booking.getCustomer().getId())
.orElseThrow(() -> new RestServiceException("We can't found customer", Response.Status.BAD_REQUEST));
if (!customer.equals(booking.getCustomer()))
throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);
Flight flight = flightService.findById(booking.getFlight().getId())
.orElseThrow(() -> new RestServiceException("We can't found flight", Response.Status.BAD_REQUEST));
if (!flight.equals(booking.getFlight()))
throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);
try {
bookingService.validateBooking(booking);
} catch (ConstraintViolationException ce) {
// Handle bean validation issues
Map<String, String> responseObj = new HashMap<>();
for (ConstraintViolation<?> violation : ce.getConstraintViolations()) {
responseObj.put(violation.getPropertyPath().toString(), violation.getMessage());
}
throw new RestServiceException("Bad Request", responseObj, Response.Status.BAD_REQUEST, ce);
} catch (UniqueFlightWithDateException e) {
// we are updating an existence flight, so ignore this as expected
}
try {
bookingService.update(id);
} catch (ServiceException e) {
Map<String, String> responseObj = new HashMap<>();
responseObj.put("id", "please ensure the id is associated with this number");
throw new RestServiceException("Bad Request", responseObj, Response.Status.NOT_FOUND, e);
}
bookingService.update(id);
return Response.ok(booking).build();
}
BookingEntity update(BookingEntity booking) {
log.info("BookingRepository.update() - Updating " + booking.getId());
em.merge(booking);
return booking;
}
From the original posted code, the problem is that you have two very independent unidirectional relationships and only setting one of them. Since they are independent, the other remains null and can not be anything other than null until it is set.
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "booking_id", nullable = false)
private BookingEntity flightbooking;
The join column sets a foreign key in the "TABooking" table to point at the bookingEntity. It requires this relationship reference be set to populate that foreign key value. Same thing with:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "travelAgentBooking_id", nullable = true)
private TABookingEntity travelAgentBooking
It creates its own travelAgentBooking_id foreign key column in the "booking" table that will remain null until you update a booking instance and set this reference. If you only set one side, the other will always remain null in the database.
But there are two problems with the model and your expectations. First, from the comments, you didn't intend this to be two separate relationships and instead expect it to be a single bidirectional relationship. For that, you need a single foreign key, and to pick a side that 'owns' it. The side that owns it controls it:
#OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
private TABookingEntity travelAgentBooking
Using mappedBy tells JPA that the other side owns the relationship. The foreign key column then is only set when you set the TABookingEntity.flightbooking reference and save/merge the TABookingEntity instance.
Second is you are using JSON and so Json serialization and assuming it abides by your object model and the JPA mappings. It does not. JPA annotations are for your persistence provider to tell it how to serialize/deserialize your model into the database but mean nothing for JSON serialization (or xml or any other REST formats). You need to tell your JSON tool how to handle your relationships, and that completely depends on how you are going to be expecting and sending the JSON. There are many tutorials and different strategies to deal with this (see this link for a good primer), but easiest is usually just to pick parts of the graph and exclude them with #JsonIgnore:
#OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
#JsonIgnore
private TABookingEntity travelAgentBooking
This will mean that any JSON you receive representing a booking will have a null travelAgentBooking. So if you need to see or set this relationship, your api would have to send/receive TABookingEntity which would still have the flightbooking reference serialized. I picked this way because flightbooking owns the relationship, so it matches JPA, but it doesn't need to. You can and should figure out what works for your client application and it may be different from the JPA mappings. I would expect that bookings always need to know the TABookingEntity and you'll want that sent to the client, so you might put the #JsonIgnore annotation on the other side. If you do, you'll just have to be sure that when you want to change or add TABookingEntity, that you fix the TABookingEntity.flightbooking reference appropriately so that you don't null out the foreign key.

Fix adding entites to ManyToMany

I need to check if user's email is already added to list of students and if so, connect this user with course.
java.lang.UnsupportedOperationException
at java.util.Collections$1.remove(Collections.java:4684)
at java.util.AbstractCollection.clear(AbstractCollection.java:436)
at org.hibernate.collection.internal.PersistentSet.clear(PersistentSet.java:318)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:581)
at org.hibernate.type.CollectionType.replace(CollectionType.java:757)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:167)
User Entity
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(
name = "Student_Courses",
joinColumns = {#JoinColumn(name = "student_id")},
inverseJoinColumns = {#JoinColumn(name = "course_id")}
)
private Set<Course> availableCourses = new HashSet<>();
Course Entity
#ManyToMany(mappedBy = "availableCourses")
private Set<User> users = new HashSet<>();
UserService
public void bindStudentWithCoursesAfterRegistration(String email) {
User user = userRepo.findFirstByEmail(email);
List<CourseStudentEmails> studentEntriesInCourses = courseStudentEmailsRepo.findAllByEmail(email);
if (studentEntriesInCourses.size() != 0){
for (CourseStudentEmails entry : studentEntriesInCourses) {
Course course = entry.getCourse();
user.getAvailableCourses().add(course);
}
}
userRepo.save(user);//Exception throws here
}
Registration Controller
#PostMapping("/registration")
public String addUser(User user, #RequestParam(value = "checkboxTeacher", required = false) String checkboxValue, Model model) {
[//chek if user is already registered]
userRepo.save(user);
courseService.bindStudentWithCoursesAfterRegistration(user);
return "redirect:/login";
}
I guess you're missing a proper query to find appropriate CourseStudentEmails.
If you keep a collection of String as emails in your entity (CourseStudentEmails), you can query entities based on specific email as this:
#Query("SELECT c FROM CourseStudentEmails c WHERE ?1 member of c.emails")
List<CourseStudentEmails> findAllByEmail(String email);

Retrieve data from many-to-many relationship - Hibernate

I have a 'User' class:
public class User implements Serializable {
#Id
#GeneratedValue
int id;
String nome;
#Column(unique = true)
String email;
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> roles;
I have a 'Role' class:
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
int id;
#Column(unique = true)
String role;
Relationship :An User has roles, and roles has users. I have a many-to-many relationship here. The relation is unidirectional, and my dominant entity is User.
In my database has a table named "user_roles", created automatically. I have a User registered with two roles.
When I retrieve the User from my database using a "UserDao" class, and I try to access the roles of the user, I get NullPointerException. I got all others informations (name, email, etc), but the roles I can not access.
My 'UserDao' class:
public class UserDaoImpl implements UserDao{
private static final transient Logger log = LoggerFactory.getLogger(UserDaoImpl.class);
private final String HIBERNATE_CFG = "data.cfg.xml";
#Override
public int insertUser (User user){
int code = 0;
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
try {
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
code = 1;
}
catch (Exception e) {
log.error(e.getMessage());
session.getTransaction().rollback();
code = 0;
}
return code;
}
#Override
public List<User> getAll() {
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
List<User> users = null;
try
{
session.beginTransaction();
String queryString = "SELECT * FROM USER;";
SQLQuery consulta = session.createSQLQuery(queryString);
consulta.setResultTransformer(Transformers.aliasToBean(User.class));
users = (List<User>) consulta.list();
session.getTransaction().commit();
}
catch(ConstraintViolationException e)
{
log.error(e.getMessage());
session.getTransaction().rollback();
}
return users;
}
I tried using iterator:
List roles = user.getRegras();
for (Iterator it = roles.iterator(); it.hasNext();){
Role r = (Role) it.next();
out.println("Role:" + r.getRole());
}
I tried to instantiate the variable "roles" in the 'User' class like below. But every time the variable returns size 0:
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> regras = new ArrayList<Role>();
I tried getting the roles from Object User, but I receive a NullPointerException when the method size() is called.
List<Role> roles = (List<Role>)user.getRoles();
out.println("size roles: " + roles.size());
I'm trying to acess my table 'user_roles' to retrieve the data from database, but I can't dothis. Can anyone help me?
Thanks in Advance
You're using a SQL query to load the users, and mapping the result to the entity. So what you get is detached entities, containing only what the SQL query returned.
Learn to use JPQL/HQL, and to load entities, which will then be managed:
String queryString = "SELECT u FROM User u";
Query consulta = session.createQuery(queryString);
users = (List<User>) consulta.list();

Hibernate 4 automatically delete many-to-many relationship

I update hibernate from 3.5.6 to 4.10, I have a problem with many-to-many relationship:
User Class
#Entity
#Table(name="user")
public class User {
#ManyToMany(fetch=FetchType.LAZY)
#Cascade(value={CascadeType.PERSIST, CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name="usersroles", joinColumns = { #JoinColumn(name ="usersroles_user" )}, inverseJoinColumns = { #JoinColumn(name = "usersroles_role") })
#JSON(serialize=false)
public Set<Role> getRoles() {
return roles;
}
}
Role Class
#Entity
#Table(name="role")
public class Role {
#ManyToMany(cascade {CascadeType.REFRESH},fetch=FetchType.LAZY,mappedBy="roles")
#JSON(serialize=false)
public Set<User> getUsers() {
return users;
}
}
That is a query method:
public List<User> getUserAndRoleList() {
String jpql = "SELECT DISTINCT u FROM User u , Role r, UserRole ur WHERE u.id = ur.userId AND r.id = ur.roleId ORDER BY u.username";
QueryResult<User> qr = super.getDataWithOriginalJPQL(jpql);
userList = qr.getResultList();
return userList;
}
When I want to get the Role:
userList = userService.getUserAndRoleList();
for(int i=0;i<userList.size();i++){
Iterator<Role> it = userList.get(i).getRoles().iterator();
Role role ;
}
This works. But when code executes
userList.get(i).getRoles()
Hibernate 4.1 will automatically delete User and Role relationship in usersroles table. But with Hibernate 3.6.5, it didn't delete the relationship.
In fact, I don't delete anything at all and I don't ask hibernate to delete anything. Something that I don't understand might occur. How do I prevent Hibernate from deleting? Or is there anything that I did wrong?

Hibernate remove relation manyToMany

I have 3 tables Role, User, and UserRole. The table UserRole contains the mapping between user and role along with the two corresponding index columns. I am using hibernate with annotations, and would like to be able to "Revoke" a role from the user, but this is turning out to be somewhat difficult.
In my User Class I have
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, targetEntity = Role.class)
#IndexColumn(name = "role_index", base = 0)
#NotFound(action=NotFoundAction.IGNORE)
#JoinTable(name = "tblUser_Role", joinColumns={
#JoinColumn(name = "UID")}, inverseJoinColumns={
#JoinColumn(name = "roleid", nullable = false)})
private List<Role> roles = new ArrayList<Role>(0);
In my Role class I have
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, mappedBy="roles")
private List<User> users = new ArrayList<User>(0);
and the DAO method I am calling to "Revoke" the role(s) is
#Override
public boolean revokeRolesFromUserAccount(User user, List<Role> userRoles) {
if (log.isInfoEnabled()) {
log.info("Roles revoked from the User " + user.getUsername());
}
if (user == null) {
return false;
}
if (userRoles == null) {
return false;
}
Iterator<Role> iter = userRoles.iterator();
List<Role> newroles = new ArrayList<Role>(0);
Role role = null;
while (iter.hasNext()) {
role = (Role) this.sessionFactory.getCurrentSession().load(
Role.class, iter.next().getRoleid());
newroles.add(role);
}
User newUser = null;
newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
newUser.getRoles().removeAll(newroles);
this.sessionFactory.getCurrentSession().saveOrUpdate(newUser);
return true;
}
for some reason this does not work as expected, when breaking through I noticed the roles were not being initialized I guess due to the LazyLoading, and I tried doing something like Hibernate.initialize(newUser.getRoles()) but this did not change anything. I am still learning the ropes with hibernate and am not sure what I am missing, maybe something very obvious?? Thank you so much for your time and thoughts in advance!
UPDATE: After trying the fixes suggested by Paujo and Matin Kh and doing further debugging I still have not seen any differences in the Roles being loaded in after line newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
Here is a copy of my tblUser_Role, not sure if this helps. Thanks again!
(Adding roles works just fine)
I'm having the exact situation here. Your question has a very simple solution.
For one of your classes use EAGER, and for the other one use LAZY. Try this:
Role:
#ManyToMany(fetch=FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, mappedBy="roles")
private List<User> users = new ArrayList<User>(0);
User:
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, targetEntity = Role.class)
#IndexColumn(name = "role_index", base = 0)
#NotFound(action=NotFoundAction.IGNORE)
#JoinTable(name = "tblUser_Role", joinColumns={
#JoinColumn(name = "UID")}, inverseJoinColumns={
#JoinColumn(name = "roleid", nullable = false)})
private List<Role> roles = new ArrayList<Role>(0);
I think your approach should be different.
UserRole should have a role name.
Your User should have a list of UserRole items or just one UserRole but with a hierarchy of UserRole. But as you have a list that is ok.
So basically you have references in User to UserRole so there is no need to have references to User in UserRole.
That way your table becomes very simple and you dont need any joins to do it and you can then just select UserRole by name.
After that you should be able to remove the reference from the list of UserRole in User and merge the User.
Finally got this working, mostly by sheer luck, this is what I ended up with in my RoleDaoImpl class.
#Override
#Transactional
public boolean revokeRolesFromUserAccount(User user, List<Role> userRoles) {
if (user == null) {
return false;
}
if (userRoles == null) {
return false;
}
User newUser = null;
newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
List<Role> newRoleList = newUser.getRoles();
newRoleList.removeAll(userRoles);
if(newUser.getRoles().retainAll(newRoleList)){
if (log.isInfoEnabled()) {
log.info("Roles revoked from the User " + user.getUsername());
}
this.sessionFactory.getCurrentSession().saveOrUpdate(newUser);
return true;
}else{
return false;
}
Hope this can be of some use to others in the future!
Thanks again for everyone's help!

Categories

Resources