Spring Boot JPA Many to many relationship. Getting unexpected response - java

I have two entity User and Group and the relationship between both entities are many-to-many. When I call view-group/groupName, I am getting the list of users of group as expected. But when I call view-user/userEmail, I am not getting the list of groups with user details of which user is part of.
Group.java
#Entity
#Table(name = "group_")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String groupName;
#ManyToMany
#JoinTable(name = "group_user",
joinColumns = { #JoinColumn(name = "group_id") },
inverseJoinColumns = {#JoinColumn(name = "user_id") })
public Set<User> usersOfgroup = new HashSet<>();
public Group() {
}
}
User.java
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name="name")
private String name;
#NotBlank
#Column(name="email")
private String email;
#JsonIgnore
#ManyToMany(mappedBy = "usersOfgroup")
public Set<Group> memberInGroups =new HashSet<>();
public User() {
}
localhost:8080/view-group/groupName
{
"id": 1,
"groupName": "Group1",
"usersOfgroup": [
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
]
}
localhost:8080/view-user/Abhishek#abc.com
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
Expected response :
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
"memberInGroups":[
{
"id": 1,
"groupName": "Group1",
}
]
}

You have added #JsonIgnore on public Set<Group> memberInGroups =new HashSet<>();, thats why the json response doesn't have the data for this. Remove the annotation and you will see the expected response
The #JsonIgnore annotation is used to ignore the logical property used in serialization and deserialization.

You can go with below. It will address your immediate need for this specific case. Usually for a many-to-many, bi-directional relationship with lists, usually the solution is to decide which side of your relation is dominant and to have a JsonManagedReference and JsonBackReference combo, or use #JsonIdentityInfo if you have a list. Below is very good read on bidirectional cases to avoid infinite loops..
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Coming to the solution I am referring to, you will have to override the getter of the list attribute and also use a #JsonInclude
In your Group class - use #JsonIgnore as shown and also put the getter as below to manually kill the loop
#ManyToMany
#JoinTable(name = "group_user", joinColumns = { #JoinColumn(name = "group_id") }, inverseJoinColumns = {
#JoinColumn(name = "user_id") })
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<User> usersOfgroup = new HashSet<>();
public Set<User> getUsersOfgroup() {
return this.usersOfgroup.stream().map(user -> {
user.memberInGroups = new HashSet<>();
return user;
}).collect(Collectors.toSet());
}
and in your User class, too do the same.
#ManyToMany(mappedBy = "usersOfgroup")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Group> memberInGroups = new HashSet<>();
public Set<Group> getMemberInGroups() {
return this.memberInGroups.stream().map(group -> {
group.usersOfgroup = new HashSet<>();
return group;
}).collect(Collectors.toSet());
}

Related

Many to many json request

A Contract has several AbsenceType, and an AbsentType can be in several different Contracts. So I made a manyToMany relation in both classes.
Entity
#Table(name = "contract")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#EntityListeners(AuditingEntityListener.class)
public class Contract {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "employee")
private int employee;
#ManyToMany(mappedBy = "contracts")
private List<AbsenceType> absence_types;
// ... getteur setteur contructor
#Entity
#Table(name = "absence_type")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class AbsenceType {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
// hexa : #FFFFFF
#Column(name = "color")
private String color;
#Column(name = "is_paid")
private boolean is_paid;
#ManyToMany
#Column(name = "contracts")
private List<Contract> contracts;
// ... getteur setteur contructor
I want to be able to create empty Absence types and then when I create a contract, give in my json, the previously created absenceType and not create a new AbsenceType.
In the idea I do this:
{
"id": 1 # Exemple, in real in don't had id on json
"name": "Congé sans solde",
"color": "ff4040",
"is_paid": false,
"contracts": []
}
and after ->
{
"name": "Contract4",
"employee": 3,
"absence_types" : [
{
"id":1
}
]
}
But the response when i get all contracts is:
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": []
}
But i want :
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": [
{
"id": 1,
"name": "Congé sans solde",
"color": "ff4040",
"contracts" : [ dudo, i don't think about infinite recursion problem for the moment haha]
}
}
For all of my DAO i have a generic class, how look like this
public void save(T obj) {
Session session = openSession();
Transaction transaction = session.beginTransaction();
session.saveOrUpdate(obj);
transaction.commit();
session.close();
}
and on my Contract controller
#PostMapping("")
public ResponseEntity<String> create(#RequestBody Contract contract) {
// Error 422 if the input role variable is null
if (contract == null)
new ResponseEntity<String>("Can't create null absenceType", HttpStatus.UNPROCESSABLE_ENTITY);
contractDAO.save(contract);
return new ResponseEntity<>("absenceType saved !", HttpStatus.OK);
}

I just want to use entity values not to update from other entity

I am using spring boot rest api, data jpa, I just want to use event class and not to update from person class.
Following are entity classes,
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer person_id;
private Integer age;
String first_name;
String last_name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "person_event",
joinColumns = { #JoinColumn(name = "person_id") },
inverseJoinColumns = { #JoinColumn(name = "event_id") })
private Set<Event> event;
}
Event class is
#Entity
#Table(name = "event")
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer event_id;
private String event_date;
private String title;
}
I am adding the values from postman as json string, I want to add the event values from event entity, But I dont want to update those values from person entity. From person entity I just want to use the event id but I dont want to update the event table.
{
"age": 32,
"first_name": "Srinath",
"last_name": "murugula",
"event": [
{
"event_id": 1
},
{
"event_id": 3
}
]
}
The above string is to adding the person but also updating the other fields of entity class(i.e., event_date and title as null.Because I am not mentioning those fields.In Person class I just want use id but dont want to update event table fields.
I have used:
#Cascade({CascadeType.Detach})
It solved my problem. Working fine.
You can use:
#Cascade({CascadeType.SAVE_UPDATE})
https://mkyong.com/hibernate/cascade-jpa-hibernate-annotation-common-mistake/

Hibernate: One to many mapping is leading to circular objects

I have an use case where a single user can have multiple houses. This is how the models look like
ApplicationUser.java
#Getter
#Setter
#Entity
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#Column(nullable = false)
private String emailId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "applicationUser", cascade = CascadeType.REMOVE)
private List<House> houses;
}
House.Java
#Getter
#Setter
#Entity
public class House {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#ManyToOne()
#JoinColumn(name = "application_user")
private ApplicationUser applicationUser;
#Column(name = "House_Name")
private String houseName;
}
HouseRepository
public interface HouseRepository extends JpaRepository<House, Long> {
public House findByHouseName(String houseName);
public List<House> findAllByApplicationUser_Username(String userName);
}
Whenever I try to retrieve any house, the house object contains the user object and the user object again contains the house object. This goes on infinitely.
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
How do I stop that from happening?
Since your House has an ApplicationUser and your ApplicationUser has a list of Houses, you've defined the classes to be circular.
Odds are that your Object Oriented model is 100% identical to the database model. This is probably a bad idea; as in a model of the home application, you wouldn't generally hold the application within the user embedded within the application.
Read What is “the inverse side of the association” in a bidirectional JPA OneToMany/ManyToOne association? for more details.

Spring Boot Can't get the relation entity with findAll or findByColumnName method

I'm just trying to test the relation #ManyTonOne in Spring Boot (Spring Data JPA)n so I've created two simple Class Book and Author
Here is the Class Book and Author :
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "author_id", nullable = false)
#JsonIgnore
//#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Author author;
Class Author:
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "fullname")
private String fullame;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "author", fetch = FetchType.LAZY)
private Set<Book> books;
I just want when i try to call findAll() for books i get author also, when i make it by default i get this result without the author :
"_embedded": {
"books": [
{
"title": "Book1",
"_links": {
"self": {
"href": "http://localhost:8080/api/books/1"
},
"book": {
"href": "http://localhost:8080/api/books/1"
}
}
},
Or when i write directly method findAll in controller :
#RestController
public class BookRestController {
#Autowired
BookRepository bookRepo;
#RequestMapping("/books1/")
public List<Book> createInvoice() {
List<Book> list = bookRepo.findAll();
System.out.println(list);
return list;
}
i get this result :
[
{
"id": 1,
"title": "Book1"
},
{
"id": 2,
"title": "Book2"
},
I've tried also to search by title findByTitle(string), I don't get the author also
A different example that I found is about the second relation #OneToMany, not the other way
What I must add in my Entity or repository or controller to retrieve (with a good way) the author id?
I think without the JsonIgnore maybe you're just going right into recursion hell, since the book has an author and the author has minimum this book, and the book has the author...
Try a getter on the AuthorId, something like
public Long getAuthorId() {
return (author == null) ? null : author.getId());
}
EDIT:
wrote the maybe before your comment, now I'm quite sure :-)
Remove #JsonIgnore and use #JsonIdentityInfo on both classes to get author also for every book.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Book {
...
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Author {
...
}

Spring MVC/Jackson - Nested Entity Deserialization Weirdness

I'm having a weird problem with Jackson serialization - I have a Role entity have a nested Permission entity which, in turn, contains a nested Metadata entity. When these entities are retrieved from a Spring MVC #RestController as a list, Jackson serializes the Permission collection into a JSON array. The problem is that sometimes the element placed in this array is just the id of the Permission rather than a serialized representation of the object.
Role.class:
#Entity
#Table(name = "t_db_roles")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Role.class)
public class Role implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "auto_id")
private int id;
#Column(name = "role", length = 50)
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_db_role_permissions",
joinColumns = {#JoinColumn(name = "roleid", referencedColumnName = "auto_id")},
inverseJoinColumns = {#JoinColumn(name = "permid", referencedColumnName = "auto_id")}
)
private Set<Permission> permissions;
// getters and setters omitted
}
Permission.class:
#Entity
#Table(name = "t_db_permissions")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Permission.class)
public class Permission implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "auto_id")
private int id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "permission")
private Metadata metadata;
}
Metadata.class
#Entity
#Table(name = "t_report_data")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Metadata.class)
public class Metadata {
#Id
#Column(name = "id", insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "file_name")
private String fileName;
#Column(name = "human_name")
private String humanName;
#Column(name = "links_to")
#JsonIgnore
private Integer linksTo;
#Column(name = "is_subreport")
#JsonIgnore
private Boolean isSubreport;
#OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "permid")
private Permission permission;
}
The controller:
#RestController
public class RoleRestController {
private final static Logger log = LoggerFactory.getLogger(PermissionRestController.class);
private RoleService roleService;
private MetadataService metadataService;
#Autowired
public void setRoleService(RoleService service) {
this.roleService = service;
}
#Autowired
public void setMetadataService(ReportMetadataService service) { this.metadataService = service; }
#RequestMapping(value = "/admin/roles/", method = RequestMethod.GET)
public List<Role> getRoles() {
return roleService.getRoles();
}
}
I'm fairly sure that the problem is in serialization - echoing the List<Role> to the console works as expected, but here is the JSON returned (note the first element of the permissions array is an integer rather than a JSON object):
{
"id": 10,
"name": "ROLE_TESTER",
"permissions": [
14,
{
"id": 7,
"name": "ViewDailySettlementSummaryGL",
"metadata": {
"id": 41,
"fileName": "acct_summary_gl.rptdesign",
"humanName": "Daily Settlement Summary GL",
"permission": 7
},
"authority": "ViewDailySettlementSummaryGL"
},
{
"id": 6,
"name": "ViewDailySettlementSummary",
"metadata": {
"id": 24,
"fileName": "acct_summary_os.rptdesign",
"humanName": "Daily Settlement Summary",
"permission": 6
},
"authority": "ViewDailySettlementSummary"
}
],
"authority": "ROLE_TESTER"
}
I can work around this by handling Role serialization manually, but since the SpringMVC/Jackson serialization works for other classes in the project it seems like there must be a problem in these classes that i'm overlooking. Any ideas?

Categories