Spring boot OnetoMany with JPA - java

I have started testing with spring boot to create a Restful webservice that has
simple crud functions.i have two entity classes
Company.java
#Entity
#Table(name="Company_new")
public class Company {
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
public void setId(int id) {
this.id = id;
}
#NotNull
#Column(name="name")
private String name;
#OneToMany(mappedBy="company",cascade=CascadeType.ALL)
private Set<User> users;
public Company(String name){
this.name = name;
}
public Company(){
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public int getId(){
return id;
}
}
and User.java
#Entity
#Table(name="user_new")
public class User {
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int idUser;
#NotNull
#Column
private String name;
#NotNull
#Column
private String userName;
#NotNull
#Column
private String authLevel;
#Column
private String password;
#ManyToOne
#JoinColumn(name="idCompany")
private Company company;
// Public methods
public Company getCompany(){
return company;
}
public void setCompany(Company company){
this.company = company;
}
public void setName(String name){
this.name =name;
}
public void setUserName(String userName){
this.userName = userName;
}
public String getName(){
return this.name;
}
public String getUsername(){
return this.userName;
}
public String getPassword(){
return this.password;
}
public String getAuthLevel() {
return authLevel;
}
public void setAuthLevel(String authLevel) {
this.authLevel = authLevel;
}
public String getUserName() {
return userName;
}
public void setId(int idUser) {
this.idUser = idUser;
}
public void setPassword(String password) {
this.password = password;
}
public int getId(){
return this.idUser;
}
}
i want to have a relationship with a Company having many users.
I have tried presisting a user record like this
#Autowired
CompanyDao companyDao;
#RequestMapping(value = "/user/create", method = RequestMethod.POST)
public #ResponseBody ResponseEntity createUser(#RequestBody User user) {
try {
Company c = companyDao.findOne(user.getCompany().getId());
user.setCompany(c);
userDao.save(user);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(user, HttpStatus.OK);
}
my data is presisting in the database the way i want
but when i try to access a company record it loads like this
obviously it loads relationships in a cycle and eventually gives stack overflow error. how to solve this ?

Depending on your desired outcome you can:
use #JsonIgnore on public Set<User> getUsers() to prevent serializing the users collection or #JsonIgnore on public Company getCompany() to prevent company serialization
use #JsonManagedReference on public Set<User> getUsers() and #JsonBackReference on public Company getCompany() to let Jackson know that it's a bidirectional relation.
PS. If the API you're exposing will be consumed by code you do not control consider not exposing entities directly but mapping them to DTOs

Related

JPA mapping table could not delete

I have entity Account, Role, AccountRole.
#Entity
public class Account {
#Id
private String loingId;
private String username;
private String password;
private String email;
private boolean enable;
#OneToMany(mappedBy = "account", orphanRemoval = true)
private List<AccountRole> accountRoles = new ArrayList<>();
public String getLoingId() {
return loingId;
}
public void setLoingId(String loingId) {
this.loingId = loingId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public List<AccountRole> getAccountRoles() {
return accountRoles;
}
public void setAccountRoles(List<AccountRole> accountRoles) {
this.accountRoles = accountRoles;
}
public void addAccountRoles(AccountRole accountRoles) {
if (this.accountRoles == null){
this.accountRoles = new ArrayList<>();
}
this.accountRoles.add(accountRoles);
accountRoles.setAccount(this);
}
public void removeAccountRoles(){
this.accountRoles = null;
}
}
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
private boolean enable;
#OneToMany(mappedBy = "role")
private List<AccountRole> accountRoles = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public List<AccountRole> getAccountRoles() {
return accountRoles;
}
public void setAccountRoles(List<AccountRole> accountRoles) {
this.accountRoles = accountRoles;
}
}
#Entity
public class AccountRole implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
To create account with role is OK.
There is a problem in update.
I want to delete the existing Role and only add the changed Role when the Role of the Account is changed. However, existing data is not deleted from the AccoutRole table.
How can I solve the problem?
springBootVersion = '1.5.3.RELEASE'
java 1.8
gradle dependencies
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
testCompile('org.springframework.boot:spring-boot-starter-test')
runtime ('org.mariadb.jdbc:mariadb-java-client')
}
A couple of ideas:
Thought 1: Try using cascade
Yes, JPA 2.0 should handle this with orphanRemoval = true, but let's just see if that works. I think that it is not because you aren't creating an orphan here. The mapping is still "valid" from a relational perspective.
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL) // or CascadeType.REMOVE
private List<AccountRole> accountRoles = new ArrayList<>();
Thought 2: Try setting the account roles to an empty hashmap instead first:
account.setAccountRoles(new HashMap<AccountRole>());
account.getAccountRoles().add(accountRole);;

Spring boot , CrudRepository findbyid or findOne is not getting One to many table details of role table

#Entity
#NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String email;
private String password;
private String username;
#Transient
private String passwordConfirm;
//bi-directional many-to-one association to Role
#ManyToOne(fetch=FetchType.LAZY ,cascade = {CascadeType.PERSIST})
private Role role;
public User() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
#Transient
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}
#Entity
#NamedQuery(name="Role.findAll", query="SELECT r FROM Role r")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String name;
//bi-directional many-to-one association to User
#OneToMany(fetch=FetchType.LAZY, mappedBy="role", cascade = {CascadeType.PERSIST})
private List<User> users;
public Role() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return this.users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public User addUser(User user) {
getUsers().add(user);
user.setRole(this);
return user;
}
public User removeUser(User user) {
getUsers().remove(user);
user.setRole(null);
return user;
}
}
public interface UserRepository extends CrudRepository <User, Long> {
public UserDto getUserDetailsById(Long userId) throws commonException {
//ArrayList<UserDto> arr = new ArrayList<>();
User user = userRepository.`findOne`(userId);
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setEmail(user.getEmail());
userDto.setPassword(user.getPassword());
userDto.setRoleId(user.getRole().getId());
userDto.setRoleName(user.getRole().getName());
// arr.add(userDto);
return userDto;
}
find by Id is not getting role details by using user object.lazy initialization is not happening.When I use to get user ID from user object, I can not get role details.
userDto.setRoleId(user.getRole().getId()); is having null value
In your User entity, you have configured the Role collection to load lazily. Therefore when you first call user.getRole(), you get a proxy object. If you want to call methods on the proxy object that need to fetch data, you should initialize the proxy. There is a technique to initialize it. Try the following:
Before the following line of code
userDto.setRoleId(user.getRole().getId());
add the following;
user.getRole().size();
For situations where you know that you need a particular association, its generally recommended that you specify that the association be join-fetched.
If the association is optional, you'd use something like:
FROM User u LEFT JOIN FETCH u.role WHERE u.id = :userId
If the association is not-optional, you could improve the above by specifying an inner join like:
FROM User u JOIN FETCH u.role WHERE u.id = :userId
With Spring data, you can use the #Query annotation on methods and specify the JPQL/HQL above to suit your needs.

Spring Data Rest FetchType

I have a problem with Rest response when I use ManyToMany annotation.
Problem is this answer:
Problem accessing /json2/1. Reason:
Server Error
Caused by:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"])
Without ManyToMany annotation(like in the Model class Role) I response JSON format
[{"id":1,"name":"ROLE_USER"},{"id":2,"name":"ROLE_ADMIN"}]
RestController
#Controller
public class RestController {
#Autowired
private UserService userService;
#Autowired
private BlogService blogService;
#Autowired
private RoleService roleService;
#RequestMapping("/json")
public String JsonLink(Model model){
model.addAttribute("result", blogService.findAll());
return "json";
}
#RequestMapping(value = "/json2/{id}", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List<Role> ShowJson(#PathVariable int id) {
Hibernate.initialize(roleService.findAll());
List<Role> role = roleService.findAll();
System.out.println(role.toString());
return role;
}
Model Role (comment #ManyToMany)
#Entity
#JsonAutoDetect
public class Role {
#Id
#GeneratedValue
private Integer id;
private String name;
/*#ManyToMany(fetch = FetchType.EAGER,mappedBy = "roles")
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(String name) {
this.name = name;
}
public Role() {
}
#Override
public String toString() {
return "Role [id=" + id + ", name=" + name + "]";
}
}
Model User
#Entity
#JsonAutoDetect
public class User {
#Id
#GeneratedValue
private Integer id;
private String name;
private String email;
private String password;
private boolean enabled;
#ManyToMany
#JoinTable
private List<Role> roles;
#OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Blog> blogs;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Service
#Service
public class RoleService {
#Autowired
private RoleRepository roleRepository;
public List<Role> findAll(){
return roleRepository.findAll();
}
JSP
<c:forEach items="${result}" var="item">
json
</c:forEach>
The problem is with serializing property roles of User entity. When you load your entity in RoleService and then return result to controller the hibernate session is ended. You are not able to load it outside hibernate session.
You cannot load these property eagerly too. Then there would be a chance to load large tree of objects.
In my opinion to solve your proplem you have to create 3 REST services and 3 normal service method which would load flat data structures:
/roles/{id} - it loads single role (without users property)
/roles/{id}/users - it loads all users that have role with given id (without roles property)
/users/{id}/roles - it loads roles for user with given id
Additionaly you have to annotate your collection properties (roles, users) with annotation #JsonIgnore to ignore them during serialization to json.

Hibernate Automatically load relationships

I have the following Entity classes UserEntity and TicketEntity. A User has many tickets and many tickets can belong to a user. My question is, is there a way to automatically load all the tickets belonging to a pertaining user by using Hibernate or do I have to manually load all the entity relationships from the DB? I think the .load() does this but I'm not quite sure. In my case could I do something like
userEntity.load()
Any help is appreciated, thanks
UserEntity.java
package com.issuetracking.domain;
/**
*/
import java.util.List;
import javax.persistence.*;
#Entity
#Table(name="user")
public class UserEntity {
#Id
#Column(name="user_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="firstname")
private String firstname;
#Column(name="lastname")
private String lastname;
#Column(name="username")
private String username;
#Column(name="email")
private String email;
#Column(name="password")
private String password;
#Transient
private String confirmpassword;
#Column(name="verified")
private boolean verified;
#Column(name="role_id")
private int role_id;
#OneToMany(fetch = FetchType.LAZY)
private List<TicketEntity> tickets;
//Getters/Setters
public List<TicketEntity> getTickets() {
return tickets;
}
public void setTickets(List<TicketEntity> tickets) {
this.tickets = tickets;
}
public int getRole_id() {
return role_id;
}
public void setRole_id(int role_id) {
this.role_id = role_id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmpassword() {
return confirmpassword;
}
public void setConfirmpassword(String confirmpassword) {
this.confirmpassword = confirmpassword;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
TicketEntity.java
package com.issuetracking.domain;
import java.util.Date;
import javax.persistence.*;
#Entity
#Table(name="ticket")
public class TicketEntity {
#Id
#Column(name="ticket_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="title")
private String title;
#Column(name="description")
private String description;
#Column(name="date_created")
#Temporal( TemporalType.TIMESTAMP )
private Date date_created;
#Column(name="status_id")
private int status_id;
//private TicketStatus status;
#Column(name="urgency_id")
private int urgency_id;
#ManyToOne
#JoinColumn(name="user_id", insertable=false, updatable=false)
private UserEntity belongs_to;
#ManyToOne
#JoinColumn(name="user_id", insertable=false, updatable=false)
private UserEntity assigned_to;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getDate_created() {
return date_created;
}
public void setDate_created(Date date_created) {
this.date_created = date_created;
}
public int getStatus_id() {
return status_id;
}
public void setStatus_id(int status_id) {
this.status_id = status_id;
}
public int getUrgency_id() {
return urgency_id;
}
public void setUrgency_id(int urgency_id) {
this.urgency_id = urgency_id;
}
public UserEntity getBelongs_to() {
return belongs_to;
}
public void setBelongs_to(UserEntity belongs_to) {
this.belongs_to = belongs_to;
}
public UserEntity getAssigned_to() {
return assigned_to;
}
public void setAssigned_to(UserEntity assigned_to) {
this.assigned_to = assigned_to;
}
}
A User has many tickets and many tickets can belong to a user.
In this case relationship should be ManyToMany
My question is, is there a way to automatically load all the tickets belonging to a pertaining user
Use EAGER FetchType instead of LAZY , Like
#OneToMany(fetch = FetchType.EAGER)
private List<TicketEntity> tickets;

Serializing JPA Entity - saving only IDs for fetched entities

There are two entities: User and Employee. User has field with type Employee.
#Entity
#Table(name="user")
public class User extends AuditableEntity {
Long idUser;
String username;
String password;
Employee employee;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getIdUser() { return idUser; }
public void setIdUser(Long idUser) { this.idUser = idUser; }
#Column(name = "username")
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
#Column(name = "password")
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
#ManyToOne
#JoinColumn(name = "idemployee")
public Employee getEmployee() { return employee; }
public void setEmployee(Employee employee) { this.employee = employee; }
}
And
#Entity
#Table(name = "employee")
public class Employee extends AuditableEntity {
Long idEmployee;
String surname;
String name;
String patronymic;
Date birthdate;
#Id
#GeneratedValue (strategy=GenerationType.IDENTITY)
#Column(name = "idemployee")
public Long getIdEmployee() { return idEmployee; }
public void setIdEmployee(Long idEmployee) { this.idEmployee = idEmployee; }
#Column(name = "surname")
public String getSurname() { return surname; }
public void setSurname(String surname) { this.surname = surname; }
#Column(name = "name")
public String getName() { return name; }
public void setName(String name) { this.name = name; }
#Column(name = "patronymic")
public String getPatronymic() { return patronymic; }
public void setPatronymic(String patronymic) { this.patronymic = patronymic; }
#JsonFormat(pattern = "dd.MM.yyyy")
#Column(name = "birthday")
public Date getBirthdate() { return birthdate; }
public void setBirthdate(Date birthdate) { this.birthdate = birthdate; }
}
I need to serialize User to XML/JSON. I'm using JAXB but it's serializing Employee too:
<User>
<idUser>15</idUser>
<username>user15</username>
<password>password15</password>
<employee>
<idEmployee>23</idEmployee>
<surname>Smith</surname>
<name>John</name>
<patronymic>H.</patronymic>
<birthdate>01.01.1970</birthdate>
</employee>
<User>
I need in result something like this:
<User>
<idUser>15</idUser>
<username>user15</username>
<password>password15</password>
<idEmployee>23</idEmployee>
<User>
I tried to use #XmlID, #XmlIDREF - but it's usable only with String id column.
Also tried to use #XmlTransient - but it's only excluding Employee.
How i can serialize User without Employee, only with idEmployee?
And second question is deserialization. Are there any standard ways to do that?
Annotate property employee in class User with #XmlTransient. This annotation can be applied to either field or its getter.
You can add additional getter for Employee's id to User entity
public class User
{
...
#Transient // for JPA
#XmlElement
Long getIdEmployee()
{
return employee.getIdEmployee();
}
}
Or you can do it with #XmlValue
public class User
{
...
#XmlElement(name = "idEmployee")
public Employee getEmployee() { return employee; }
public void setEmployee(Employee employee) { this.employee = employee; }
}
#XmlAccessorType(XmlAccessType.NONE) // to prevent marshalling of all properies
public class Employee
{
...
#XmlValue
public Long getIdEmployee() { return idEmployee; }
...
}

Categories