Pageable not giving expected results with #ManyToMany relationship - spring

We are dealing with #ManyToMany relation with Users and Roles and want to have pagination to get all the Users with associated Roles by using Pageable interface. It is only considering the records count for pagination on the User Table and Roles table record is not considered. But ideally in RDBMS the actual record count would be after flattening the result of join between Users and Roles table.
When working with Pageable in findAll method and passing the page configuration as below :
pageno: 0 and pageSize:1
Pageable paging = PageRequest.of(0, 1);
userRepository.findAll(paging);
It is giving the result as below 
Technically there are 3 records when we flatten the result but pageable is considering this as 1 record which is not correct. Is this intended behavior?
Is there a way where we can get the pagination after flattening the result set of query?

Yes. This is intended. Data is mapped to Java objects as nested objects. Hence, pageable of 5 user records will return 5 users irrespective of number of roles each user has.
To restrict pagination based on record count by combination of user and role, you have to add join between user and role to the query in repository method and fetch columns from both user and role (like we do in SQL).
Below code works for me
User entity
public class User
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#NonNull
#Column(unique = true, name= "user_name")
private String userName;
#NonNull
private String password;
#NonNull
private boolean status;
#NonNull
private boolean passwordExpired;
#ManyToMany(fetch=FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = {
#JoinColumn(name = "userId", referencedColumnName = "userId") }, inverseJoinColumns = {
#JoinColumn(name = "role_name", referencedColumnName = "name") })
#BatchSize(size = 20)
private Set<Role> roles = new HashSet<>();
//Get and set
}
Role Entity
public class Role {
private static final long serialVersionUID = 1L;
#NotNull
#Size(max = 50)
#Id
#Column(length = 50,unique=true)
private String name;
//get and set
}
Repository
#Repository
public interface UserRepo extends JpaRepository<User, Long>
{
#Query(value="SELECT u.userName,r.name FROM User u left join u.roles r")
public ArrayList<User> findByrole(Pageable paging);
}
Service method
public ArrayList<User> findByrole()
{
// TODO Auto-generated method stub
Pageable paging = PageRequest.of(0, 4);
return uRepo.findByrole(paging);
}

Related

Confused why getting a User from Repository fixed "failed to lazily initialize a collection of role" compared to using SecurityContextHolder

My goal was to pass a List of Businesses to the model from the controller to display it in a view and I have succeeded, but have a bit of confusion.
When I initially tried using:
public User getCurrentAuthenticatedUser() {
UserDetailsImpl user = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user.getUser();
}
#GetMapping("")
public String list(Model model) {
model.addAttribute("businesses", userService.getCurrentAuthenticatedUser().getBusinesses());
return "business/list";
}
I got this error: "failed to lazily initialize a collection of role: com.xyz.User.businesses could not initialize proxy - no Session"
Then I tried:
#GetMapping("")
public String list(Model model) {
int userId = userService.getCurrentAuthenticatedUser().getId();
User user = userService.getById(userId); // gets User using Spring Data JPA UserRepository
List<Business> businesses = user.getBusinesses();
model.addAttribute("businesses", businesses);
return "business/list";
}
And this worked perfectly fine.
What was the issue using the first method. It seemed more simple rather than calling a User from the UserRepository. I've seen some posts that say you should use EAGER fetching, but that's just seems like a bandaid solution.
From the beginner's understanding: Since fetch type is LAZY the businesses don't exist yet in the User but are fetched on demand later on so there shouldn't be an issue.
Edit: After more thought I remembered that with basic Hibernate you would have to create Transactions and commit transactions. I'm assuming that User is not within a Transaction that's why I can't get businesses using the 1st method.
What would be a better solution to fetch the current Authenticated user? And that user's attributes such as a list of businesses.
Model Classes:
Business:
#Entity
#Table(name = "businesses")
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private LocalDate date;
#ManyToOne(cascade={CascadeType.MERGE})
#JoinColumn(name="user_id")
private User user;
public Business() {
}
public Business(String name, String description, LocalDate date, User user) {
...
}
public Business(Long id, String name, String description, LocalDate date, User user) {
...
}
... getters/setters
}
USER:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable( name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", cascade={CascadeType.MERGE})
private List<Business> businesses;
... getters/setters
}

Join Column between entities get NULL value instead of parent entity id number

I am Using Spring Boot on Java to create user's order on his checkout. A new Orders object is created which has a Linked Set of Items. Those items are user's cart contents.
Order is created, but its set of Items is null. The set size is 0. I checked that in JUnit tests. Can you help me to find out what is wrong? Maybe I have defined entities incorrectly? Have a look at the picture of the database:
And check the entities, Orders:
#Entity
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime submitedAt;
#NotEmpty
private String orderName;
#NotEmpty
#Column(name="`User`")
private String username;
#Enumerated(EnumType.STRING)
#Column
private OrderStatus status;
#OneToMany(mappedBy = "orders", cascade = { CascadeType.ALL}, fetch = FetchType.LAZY)
private Set<Item> items;
Item:
#Entity
public class Item {
#Id
private Integer id;
#Column(name="`NAME`")
private String dishName;
#Column(name = "`DESCRIPTION`", length = 2000)
private String dishDescription;
#Column(name = "`QUANTITY`")
private Integer quantityInCart;
#Column(name = "`USER`")
private String username;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
#JoinColumn(name = "ORDERS_ID")
private Orders orders;
How to do entities relation correctly? Should it be one direction or bi-directional relationship?
What are differences of these relations? And what kind of relationship I should use? Why?
I was doing JUnit tests for the Orders service methods. It turns out that it can create orders. And Order items from user's cart.
But when it is time to show order (GetMapping) then it returns Orders entity with empty items set.
I think it happens because JPA cannot find foreign key of items for its designated order. It is null.
Why is it null?
And this is the service method that creates such order by user request:
#Transactional
public ResponseEntity<String> createOrder (String username) {
User user = userService.findByUsername(username);
List<CartItem> items = cartRepo.findByUser(user);
if(items.size() > 0) {
Orders newOrder = new Orders();
Set<Item> orderItems = new LinkedHashSet<>();
for(CartItem item : items) {
// new Item(Integer id, String dishName, String dishDescription, Integer quantityInCart, String username)
Item orderItem = new Item(item.getId(), item.getDish().getName(),
item.getDish().getDescription(), item.getQuantity(), item.getUser().getUsername());
orderItems.add(orderItem);
}
newOrder.setItems(orderItems);
newOrder.setOrderName(user.getUsername()+"'s order");
newOrder.setStatus(OrderStatus.SUBMIT);
newOrder.setSubmitedAt();
newOrder.setUsername(username);
orderDao.save(newOrder);
cartService.removeAllUserProducts(username);
LOG.info("[{}]: A new order is created successfully.", username);
return new ResponseEntity<String>("A new order is created successfully.", HttpStatus.CREATED);
}
//...
}
I tried to do one direction relationship for other entities and it really created foreign keys on joined column fields. But I want to find out why my bidirectional way of joining is wrong. Maybe someone who really knows can explain.
The Order class should be like this:
#Entity
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime submitedAt;
#NotEmpty
private String orderName;
#NotEmpty
#Column(name="`User`")
private String username;
#Enumerated(EnumType.STRING)
#Column
private OrderStatus status;
#OneToMany(cascade = { CascadeType.ALL}, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name="ORDERS_ID")
private Set<Item> items;
And Item class without Orders class and its ManyToOne relationship.
Now relationship is unidirectional. Item entity has foreign keys column name ORDERS_ID that has id's of Orders for which Items belong.

Spring Boot Entity how to check value if exist in another table by custom field

The user can search for products if any product shown in the result exists in the user_favorites table so the show flag tells the front-end this product was added for this user by user_id and product_id. with spring boot and spring data.
My Entity :
#Id
#Column(name = "catId")
private Integer catId;
#Column(name = "cat_no")
private String catNo;
#Column(name = "cat_sn")
private String catSn;
#Column(name = "doc_ref")
private String docRef;
#Column(name = "user_id")
private Integer userId;
#Column(name = "updated_at")
private String updatedAt;
#Column(name = "created_at")
private String createdAt;
I tried that using #Formula but nothing happing always returns null. and if it's done by #Formula how can i add parameters to #Formula
#Formula(value = "SELECT count(*) as checker FROM fb_user_favorites WHERE cat_id = 34699 AND user_id = '52') ")
#Transient
private String checker;
#Transient is part of JPA spec. In Hibernate fields marked with this annotation just simply ignored/excluded from any JPA engine/runtime logic.
#Formula is part of Hibernate. Fields, marked with it, don't persisted by Hibernate (first argument do not use #Transient as redundant), values are calculated by provided SQL when executing query for entity.
So for Hibernate to see this fields, they should not be excluded by #Transient
TL;DR remove #Transient annotation
Complicated but fast working way.
Adding isFavorite field to the entity:
#Transient
private boolean isFavorite;
Create an entity linking Product and User:
public class ProductFavorite {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#ManyToOne(optional = false, fetch = LAZY)
private Product product;
#ManyToOne(optional = false, fetch = LAZY)
private User user;
}
Then create a repository with a method to find the user's favorite products:
#Repository
public interface ProductLikeRepository extends JpaRepository<ProductFavorite, Long> {
#Query("select f.product.id from ProductFavorite f where f.product in ?1 and f.user = ?2")
Set<Integer> findProductIdsByIdsAndUser(List<Product> products, User user);
}
And at the end, write a method that will fill in the isFavorite field:
public void fillFavorite(List<Product> products, User user) {
if (products.isEmpty()) {
return;
}
var likedIds = favoriteRepository.findProductIdsByIdsAndUser(products, user);
for (Product product : products) {
product.setFavorite(likedIds.contains(product.getId()));
}
}
You need to call it manually:
List<Product> products = productRepository.findAll();
fillFavorite(products, currentUser());

How can I retrieve all the children of a record in this Hibernate #ManyToOne relation?

I am working on a Spring Boot project using Spring Data JPA and Hibernate mapping. I have the following doubt about how can I implement the following query.
I have an User entity class like this:
#Entity
#Table(name = "portal_user")
#Getter
#Setter
public class User implements Serializable {
private static final long serialVersionUID = 5062673109048808267L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
#NotNull(message = "{NotNull.User.firstName.Validation}")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "surname")
#NotNull(message = "{NotNull.User.surname.Validation}")
private String surname;
#Column(name = "sex")
#NotNull(message = "{NotNull.User.sex.Validation}")
private char sex;
#Column(name = "birthdate")
#NotNull(message = "{NotNull.User.birthdate.Validation}")
private Date birthdate;
#Column(name = "tax_code")
#NotNull(message = "{NotNull.User.taxCode.Validation}")
private String taxCode;
#Column(name = "e_mail")
#NotNull(message = "{NotNull.User.email.Validation}")
private String email;
#Column(name = "pswd")
#NotNull(message = "{NotNull.User.pswd.Validation}")
private String pswd;
#Column(name = "contact_number")
#NotNull(message = "{NotNull.User.contactNumber.Validation}")
private String contactNumber;
#Temporal(TemporalType.DATE)
#Column(name = "created_at")
private Date createdAt;
#Column(name = "is_active")
private boolean is_active;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference
private Set<Address> addressesList = new HashSet<>();
#ManyToMany(cascade = { CascadeType.MERGE })
#JoinTable(
name = "portal_user_user_type",
joinColumns = { #JoinColumn(name = "portal_user_id_fk") },
inverseJoinColumns = { #JoinColumn(name = "user_type_id_fk") }
)
Set<UserType> userTypes;
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
public User() {
super();
}
public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
super();
this.firstName = firstName;
this.middleName = middleName;
this.surname = surname;
this.sex = sex;
this.birthdate = birthdate;
this.taxCode = taxCode;
this.email = email;
this.pswd = pswd;
this.contactNumber = contactNumber;
this.createdAt = createdAt;
this.is_active = is_active;
}
}
The instances of this class represents users of my system. An user can have a single specific parent (the concept is similar to that of a referral: an user can bring another user in the system). This is handled by this ManyToOne recursive relationship:
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
Basically an user contains is parent (who bring him\her into the platform). It works fine. So retrieving an user I can easily retrieve the information of who is its parent (it is contained into the retrieved User object).
Now I need to implement the inverse behavior: I have to define a "query" that starting from a parent retrieve all its children.
The previous User entity class maps the following DB table:
The highlighter parent_id contains the FK that define this recursive relationship. So it contains the PK of another user that is the parent.
I have this UserRepository repository interface (it extents the JpaRepository interface)
public interface UsersRepository extends JpaRepository<User, Integer> {
User findByemail(String email);
List<User> findByUserTypes_TypeName(String typeName);
}
As you can see I am using a "query by method" style. Is it possiblem implement a behavior like this using "query by method" style? (in case also JPQL could be fine)
You can do this
List<User> findByParent_Id(Integer id);
Or you can do this
#Query("SELECT u FROM User u WHERE u.id = ?1")
List<User> getReferredUsers(Integer id);
The relationship between the user and the parent is unidirectional in the given code. By making it bidirectional, it is easy to query the data in either ways.
Refer to below code to make it bidirectional. Also ensure the relevant FetchType to avoid the performance risk. Here FetchType.LAZY is used for one to many association so it queries the data using the proxy reference when needed.
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
#JsonBackReference
private User parent;
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
private Set<User> userSet = new HashSet<>();
Child entities are fetched only when parent.getUserSet is used because of the FetchType.Lazy
public Set<User> getUsers(int id) {
User parent = userRepository.getById(id);
return parent.getUserSet();
}

How to retrieve only a specific field from child entity on #OneToOne relationship, not all fields?

When I use jpa's #OneToOne annotation, I want to get the userName field from the table, not all fields. What should I do instead?
#Setter
#Getter
#Entity
public class Menu implements Serializable {
private static final long serialVersionUID = 4462798713783196961L;
/**
* id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToOne
#JoinColumn(name = "createUserId",referencedColumnName = "userId")
private User createUser;
#Column(nullable = false)
private LocalDateTime createTime;
}
What do I need to do, can I get the userName field in the User object, but not all of it? Thank you in advance.
You can create a POJO with required fields. e.g. You only want id from Menu and userName from User:
public class CustomMenu {
private Long menuId;
private String userName;
public CustomMenu(Long menuId, String userName) {
this.menuId = menuId;
this.userName = userName;
}
// getters, setters
}
Then you can write a query with hql using the constructor in the CustomMenu with parameters new com.yourpackage.CustomMenu(m.id, m.createUser.userName) and join User entity (join m.createUser) :
TypedQuery<CustomMenu> query = entityManager.createQuery("select new com.yourpackage.CustomMenu(m.id, m.createUser.userName)"
+ "from com.yourpackage.Menu m join m.createUser", CustomMenu.class);
List<CustomMenu> menus = query.getResultList();
This generates one sql query with inner join fetching only required fields :
select menu0_.id as col_0_0_, user1_.user_name as col_1_0_ from menu menu0_ inner join user user1_ on menu0_.create_user_id=user1_.user_id

Resources