Mulitple JOIN in Spring JpaRepository #Query - spring

I have a User entity which has Role. The Role entity has Privileges. My entities look like this :
#Entity
public class User {
#ManyToOne
public Role role;
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
#Entity
public class Role {
#ManyToMany
private Set<Privilege> privileges;
public Set<Privilege> getPrivileges() {
return privileges;
}
public void setPrivileges(Set<Privilege> privileges) {
this.privileges = privileges;
}
}
#Entity
public class Privilege {
private String action;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
I would like to load a User with his Role and Privileges of this Role.
I tried to define a method in my JpaRepository with the #Query annotation
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
#Query("SELECT u FROM User u " +
"INNER JOIN u.role r " +
"INNER JOIN r.privileges p " +
"WHERE u.id = ?1")
Optional<User> findByIdWithRoleAndPrivileges(Long id);
}
My User is loaded with his Role object, but not With the Privileges of the Role.
Any idea on what i'm doing wrong ?

By default most associations are fetched lazily. To eagerly fetch the many-to-many Role-Privilege association change your query to:
#Query("SELECT u FROM User u " +
"INNER JOIN FETCH u.role r " +
"INNER JOIN FETCH r.privileges p " +
"WHERE u.id = ?1")
The 'JOIN FETCH' is what causes JPA/Hibernate to eagerly fetch the associated entities.

Related

Insert into Multiple tables using JPA #Query

Insert JSON values into multiple tables using JPA and spring-boot.
User Table
#Entity
class User {
private #Id #GeneratedValue Long id;
private String name;
#OneToOne(cascade = {
CascadeType.All
})
#JoinColumn(referencedColumnName = "productid")
private Product product;
public User() {}
public User(String name, Product product) {
this.name = name;
this.product = product;
}
}
Product Table
#Entity
class Product {
private #Id #GeneratedValue Long productid;
private String productName;
public Product() {}
public Product(String productName) {
this.productName = productName;
}
}
Repository
#Repository
public interface UserRepo extends JpaRepository < User, Long > {}
Json Input
{
"name": "John",
"product": {
"productName": "Product 1"
}
}
Rest Controller
UserRepo usrRepo;
#PostMapping("/user")
User addEmployee(#RequestBody User user) {
return usrRepo.save(user);
}
When I use the above, both User and Product tables get updated with the new values from JSON. But I want to have the same functionality using #Query. Using the below code, I can update one table but not both.
Help me to insert JSON values into multiple tables using #Query. I am using cockroach db, please suggest if there is any other way to achieve this instead of spring-data-JPA.
Query
#Modifying
#Transactional
#Query(value = "insert into user (name, productid) values (:#{#user.name}, :#{#user.productid})", nativeQuery = true)
void insert(#Param("user) User user);

Why cannot I delete User from standard JDBC Repository - Spring Boot JPA

I'm trying to delete an entity in User table, but when I call this method
companyRepository.deleteById(userID);
NOTHING happens. Absolutely nothing.
My entities are:
User:
#Entity
#Getter
#Setter
public class User {
#Id
private long user_id;
private String username;
private String password;
private String email;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
Role:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Role {
#Id
private long role_id;
private String role;
}
What I have tried with:
Change the CascadeType.ALL to different cascade type, or cascade types.
Change FetchType.EAGER to FetchType.LAZY
But when I don't include CascadeType.PRESIST or FetchType.EAGER, I cannot login. This is because these entities are standard configurations for JDBC JPA. So I need them.
Question:
Why can I not delete my user?
Do you know another setup of entities with role and user that might work for deleting?
I cannot change an user because then Spring Boot complains that I trying to "overwrite" an entity with a same id-number, in this case, column index 0.
I can add a new user, no problem.
Edit:
Update method:
#PostMapping("/updateuser")
public HTTPMessage uppdateUser(#RequestBody User user, #RequestParam("updatepassword") boolean updatepassword) {
// Check if user exist
if (userRepository.findById(user.getUser_id()) != null) {
// Check if our email/username is right
boolean valid = EmailValidator.getInstance().isValid(user.getUsername());
if(valid == false)
return new HTTPMessage("Email: " + user.getUsername() + " is not valid", HttpStatus.NOT_ACCEPTABLE.value());
// Update the password if we say that it should be updated
if (updatepassword == true)
user.setPassword(passwordEncoder.encode(user.getPassword()));
// Count and user id
long rows = userRepository.count();
long userID = user.getUser_id();
// Delete
roleRepository.deleteById(userID);
// Delete the user and the online
userRepository.deleteById(userID);
// Set new ID and save
user.setUser_id(rows);
for(Role role : user.getRoles())
role.setRole_id(user.getUser_id());
userRepository.saveAndFlush(user);
// Update the compant table too
rows = companyRepository.count();
companyRepository.deleteById(userID);
Company saveCompany = new Company();
saveCompany.setId(rows);
saveCompany.setUsername(user.getEmail().split("#")[0]);
saveCompany.setCompany(user.getEmail().split("#")[1].split("\\.")[0]); // From "myUser#MyCompany.com" to "MyCompany"
saveCompany.setEmail(user.getEmail());
companyRepository.saveAndFlush(saveCompany);
return new HTTPMessage("User: " + user.getUsername() + " is updated", HttpStatus.OK.value());
} else {
return new HTTPMessage("User: " + user.getUsername() + " does not exist", HttpStatus.NOT_FOUND.value());
}
}
User repository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Role repository
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
Company repository
#Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {
Company findByUsername(String username);
}
The result is that I can add new user, but not remove or override user.

Eager fetch property in Mapped Super class

Our Mapped Super class has createdBy column which is defined to be lazily loaded
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractAuditingEntity implements Serializable {
#CreatedBy
#ManyToOne(fetch = FetchType.LAZY)
#XmlTransient
#JoinColumn(name = "created_by", updatable = false, columnDefinition = "bigint")
protected User createdBy;
public User getCreatedBy() {
return createdBy;
}
public void setCreatedBy(User createdBy) {
this.createdBy = createdBy;
}
I need to load this property eagerly in one of the sub class that inherits this aforementioned class.
#Override
#XmlElement(name = "createdBy")
#JsonProperty("createdBy")
public User getCreatedBy() {
return super.getCreatedBy();
}
How can I do that?
I tried the following (used NamedEntityGraph and HQL), but, both did not return createdBy from MappedSuperClass that is defined as lazy
//defined at the top of Model
#NamedEntityGraphs({
// eagerly fetches created by and program names when used
#NamedEntityGraph(
name = "graphWithCreatedBy",
attributeNodes = {
#NamedAttributeNode("createdBy")
}
)
})
//Repository method
#EntityGraph(value = "Program.createdBy", type = EntityGraph.EntityGraphType.FETCH) //tried both LOAD and FETCH
Program findOne(Specification<Program> specification);
---Using HQL FETCH JOIN --
//Repository Implementation
private static final String PROGRAM_USER_QUERY = "SELECT " +
" sp FROM Program sp " +
" LEFT JOIN FETCH sp.createdBy where sp.id = :id";
Query query = entityManager.createQuery(PROGRAM_USER_QUERY ).
setParameter("id", id);
query.getSingleResult();
Both approaches returns program, but not the createdBy User
What am I doing wrong?

detached entity passed to persist Spring JPA

Getting error as "detached entity passed to persist: com.technople.domain.Role; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.technople.domain.Role" while saving data
RolePrivilegesX Code Snippets
#Entity
#Table(name="role_privileges_X")
#NamedQuery(name="RolePrivilegesX.findAll", query="SELECT r FROM RolePrivilegesX r")
public class RolePrivilegesX extends AbstractBaseEntity{
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
private String privileges;
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Role Snippets
#Entity
#Table(name = "role")
public class Role extends AbstractBaseEntity {
#OneToMany(mappedBy = "role", cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
private List<RolePrivilegesX> rolePrivilegesXs;
public void setRolePrivilegesXs(List<RolePrivilegesX> rolePrivilegesXs) {
this.rolePrivilegesXs = rolePrivilegesXs;
}
public void setRecordAssignment(RecordsAssignment recordAssignment) {
this.recordAssignment = recordAssignment;
}
public RolePrivilegesX addRolePrivilegesX(RolePrivilegesX rolePrivilegesX) {
if (getRolePrivilegesXs() == null) {
setRolePrivilegesXs(new ArrayList<>());
}
getRolePrivilegesXs().add(rolePrivilegesX);
rolePrivilegesX.setRole(this);
return rolePrivilegesX;
}
public RolePrivilegesX removeRolePrivilegesX(RolePrivilegesX rolePrivilegesX) {
List<RolePrivilegesX> rolePrivilegesXs = getRolePrivilegesXs();
if (null == rolePrivilegesXs) {
rolePrivilegesXs = Lists.newArrayList();
}
rolePrivilegesXs.remove(rolePrivilegesX);
rolePrivilegesX.setRole(null);;
return rolePrivilegesX;
}
}
AbstractBaseEntity Code Snippets
#MappedSuperclass
public class AbstractBaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
RoleServiceImpl code snippets
#Override
#Transactional(readOnly = false)
public RoleForm saveOrUpdate(RoleForm roleForm) {
Company company = loggedInCompany();
Employee loggedInEmployee = loggedInEmployee();
Role role = null;
if (null != roleForm.getRoleId()) {
role = roleRepository.findOne(roleForm.getRoleId());
}
if (null == role) {
role = new Role();
role = assignNewRolePrivileges(roleForm, role);
} else {
List<RolePrivilegesX> rolePrivilegesXs = role.getRolePrivilegesXs();
if (null == rolePrivilegesXs || rolePrivilegesXs.isEmpty()) {
role = assignNewRolePrivileges(roleForm, role);
} else {
rolePrivilegesXs.forEach(rolePrivilegesX -> rolePrivilegesX.setDeleted(Boolean.TRUE));
RolePrivilegesX rolePrivilegesX = createNewRolePrivileges(roleForm, role);
rolePrivilegesXs.add(rolePrivilegesX);
}
}
role.setCompany(company);
role.setCreatedBy(loggedInEmployee.getName());
role.setDeleted(Boolean.FALSE);
role.setName(roleForm.getName());
role.setParent(findRole(roleForm.getReportsTo()));
Role savedRoles = roleRepository.save(role);
return new RoleForm(savedRoles);
}
Role Repository Snippet
package com.technople.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.technople.domain.Company;
import com.technople.domain.Role;
public interface RoleRepository extends JpaRepository<Role, Long>{
List<Role> findByCompanyAndDeleted(Company company, Boolean deleted);
}
I have gone through lot of similar questions but that didn't solve my problem. Please suggest me what I'm doing wrong in this.
I feel most likely the problem is with the cascade type where in you need to use the CascadeType.MERGE instead of CascaCadeType.ALL.
Never mind . My Issue got resolved when i set the cascade type properly on bi directional relationship. Earlier i only had the cascade type set on Parent and after setting it on the child everything is working as expected.
Thanks
You can set the cascade type to Persist instead of All.
This will solve the issue, as you have already annotated the function with transactional

SpringDataJPA: custom data mapping with Native Query

public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
Let's say I have the code above where I select * from user. What should I do if I don't want this method to return User object. Is there a way I can manually map the data to a custom object MyUser? Can I do all this in the UserRepository interface?
Thanks!
You can do something like this
#Query(value = "SELECT YOUR Column1, ColumnN FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
List<Object[]> findByEmailAddress(String emailAddress);
You have to do the mapping. Take a look at the Spring Data Repository as well. Source
What about interface based projection?
Basically you write interface with getters that correspond to SQL query parameters.
In this way you even don't need to force #Id parameter on projection:
#Entity
public class Book {
#Id
private Long id;
private String title;
private LocalDate published;
}
public interface BookReportItem {
int getYear();
int getMonth();
long getCount();
}
public interface BookRepository extends Repository<Book, Long> {
#Query(value = "select " +
" year(b.published) as year," +
" month(b.published) as month," +
" count(b) as count," +
" from Book b" +
" group by year(b.published), month(b.published)")
List<BookReportItem> getPerMonthReport();
}
It uses org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap underneath as proxy for interface in current Spring implementation.
It works for nativeQuery = true too.

Resources