I am new in spring boot. When I tried to learn entity mapping (simple CRUD project), I got some problems in ManyToMany mapping and cannot solve that. I followed many solution and tried but can not solve this problem.
There are 2 entity. One is Student and One is Subject.
Student Entity:
package com.example.demoschoolrelational.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Data //lombok
#Entity
public class Student implements Comparable<Student>{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long roll;
private String studentName;
#ManyToMany(mappedBy = "enrolledStudent",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
// #JsonIgnoreProperties("enrolledStudent")
// #JsonIgnore
#JsonManagedReference(value = "enrolledStudent")
private Set<Subject> enrolledSubject = new HashSet<>();
public void enrollSubject(Subject sub1) {
enrolledSubject.add(sub1);
}
#Override
public int compareTo(Student thatStudent) {
long rollCompare = ((Student) thatStudent).getRoll();
return (int)(this.roll-rollCompare);
}
}
Subject Entity:
package com.example.demoschoolrelational.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Data
#Entity
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String subjectName;
#ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
#JoinTable(
name = "student_enrolled_sub",
joinColumns = #JoinColumn(name = "subject_id"),
inverseJoinColumns = #JoinColumn(name = "student_id")
)
#JsonBackReference(value = "enrolledSubject")
// #JsonIgnoreProperties("enrolledSubject")
private Set<Student> enrolledStudent = new HashSet<>();
}
Error Msg:
2022-10-06 15:48:23.839 WARN 21576 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.example.demoschoolrelational.entity.Student["enrolledSubject"])]
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#10e0ce3e<rs=HikariProxyResultSet#2034992128 wrapping org.postgresql.jdbc.PgResultSet#29eb4bd>
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#62b79155<rs=HikariProxyResultSet#821266690 wrapping org.postgresql.jdbc.PgResultSet#3a4ffafb>
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#2c4852b2<rs=HikariProxyResultSet#1276455940 wrapping org.postgresql.jdbc.PgResultSet#5b0420da>
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#5673fd6<rs=HikariProxyResultSet#1015611142 wrapping org.postgresql.jdbc.PgResultSet#40ba95a3>
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#2e8992c4<rs=HikariProxyResultSet#263607175 wrapping org.postgresql.jdbc.PgResultSet#5471653f>
2022-10-06 15:48:23.841 WARN 21576 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#685469e8<rs=HikariProxyResultSet#1949976072 wrapping org.postgresql.jdbc.PgResultSet#7c57fecc>
I tried #JsonIgnoreProperties("enrolledStudent"), #JsonIgnore , #JsonManagedReference(value = "enrolledStudent"). I got all subject list.
[
{
"id": 1,
"subjectName": "subject1"
},
{
"id": 2,
"subjectName": "subject2"
},
{
"id": 3,
"subjectName": "subject3"
}
]
But can not get the student list. Got error and that is:
{
"timestamp": "2022-10-06T09:48:23.868+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/students"
}
How can I solve this error? Thank You.
Related
I have a Spring boot project with 3 entities, UserEntity which is a standalone and Person which is inherited by lawyer.
I set the automatic indexing strategy to sync, When I insert a new user into the database, the new user is picked immediately, but a new lawyer or person are indexed but the don't appear in the result until I restart the mass indexer.
UserEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#Indexed
#SyncAnnotation(convertor = UserConvertor.class, repoClass = UserDetailServiceImplementation.class)
public class UserEntity implements UserDetails {
#Id
#Basic
#Column(name = "id", columnDefinition = "uniqueidentifier")
#Type(type = "uuid-char")
private UUID id;
#Column(name = "user_name", length = 20)
#Basic
private String username;
#Basic
#Column(name = "email")
#Email
private String email;
#Basic
#Column(name = "full_name", length = 50, nullable = false, columnDefinition = "nvarchar(50)")
#NotNull
#FullTextField(termVector = TermVector.WITH_POSITIONS_OFFSETS)
private String fullName;
PersonEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "person_type", discriminatorType = DiscriminatorType.INTEGER)
#DiscriminatorValue("1")
#Indexed
#SyncAnnotation(convertor = ClientConvertor.class, repoClass = PersonRepository.class)
public class PersonEntity implements Serializable {
public PersonEntity(){
this.personType=1;
}
#Id
#Basic
#Column(name = "id", columnDefinition = "uniqueidentifier")
#Type(type = "uuid-char")
private UUID id;
#Basic
#Column(name = "first_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
private String firstName;
#Basic
#Column(name = "last_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
private String lastName;
#Basic
#Column(name = "father_name", length = 50, columnDefinition = "nvarchar(50)")
private String fatherName;
#Basic
#FullTextField(termVector = TermVector.YES)
#Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
private String fullName;
#Basic
#Column(name = "person_type", insertable = false, updatable = false)
#GenericField
private Integer personType;
And a LawyerEntity that inherits PersonEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#DiscriminatorValue("2")
#Indexed
#SyncAnnotation(convertor = ClientConvertor.class, repoClass = LawyerRepository.class)
public class LawyerEntity extends PersonEntity {
public LawyerEntity(){
this.setPersonType(2);
}
#Basic
#Column(name = "bar_id")
#GenericField
private Integer barId;
#Basic
#Column(name = "bar_card_number")
private Long barCardNumber;
#Basic
#Column(name = "bar_regisration_date")
private LocalDate barRegistrationDate;
#ManyToOne(targetEntity = BarEntity.class)
#JoinColumn(foreignKey = #ForeignKey(name = "fk_lawyer_bar"),
name = "bar_id", referencedColumnName = "id", insertable = false, updatable = false)
#JsonIgnore
private BarEntity bar;
}
When using Sync hibernate search automatic indexing strategy, the UserEntity index updates and includes the newly inserted entities in the index , the TRACE output:
2022-12-22 10:16:06.112 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#5193eb5f.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#5193eb5f. Executing indexing plan.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] o.h.s.e.b.o.spi.SingletonTask : Scheduling task 'Lucene indexing orchestrator for index 'User' - 9'.
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask : Running task 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor : Processing 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor : Processed 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask : Completed task 'Lucene indexing orchestrator for index 'User' - 9'
However, when entering a new person or a lawyer, the index doesn't reflect the changes, not even after awhile, I need to restart the massindexer for it work, it has a similar output to the previous log, but it doesn't reflect the changes on the index until I restart the mass indexer
2022-12-22 10:14:38.086 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#6b9d9f5e.
2022-12-22 10:14:38.089 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#6b9d9f5e. Executing indexing plan.
2022-12-22 10:14:38.091 TRACE 68869 --- [nio-8080-exec-6] o.h.s.e.b.o.spi.SingletonTask : Scheduling task 'Lucene indexing orchestrator for index 'Person' - 8'.
2022-12-22 10:14:38.091 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask : Running task 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.092 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor : Processing 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.137 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor : Processed 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.138 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask : Completed task 'Lucene indexing orchestrator for index 'Person' - 8'
How can I make it detect show the change in the index without restart mass index ?
I also tried calling hibernate search indexing plan but to no success
I am using Hibernate search 6.1.6.Final with lucene backend and spring boot 2.7.5
As per request:
The code used to search for UserEntity (user can belong to bar):
public List<UserEntity> findInAnyRole(String name, Integer barId, UUID[] role) {
var session = sessionProvider.getSearchSession();
var search = session.search(UserEntity.class);
var res = search.where(f -> f.bool(
b -> {
b.must(f.match().field("fullName").matching(name).fuzzy(2));
if (role != null && role.length > 0) {
b.must(f.bool(b2 -> {
for (var roleValue : role)
b2.should(f.match().field("roles.id").matching(roleValue));
}));
}
if (barId != null)
b.must(f.match().field("barId").matching(barId));
}
));
return res.fetchHits(10);
}
As for PersonEntity:
public List<PersonEntity> findSimilar(#NotNull String name, String[] ids) {
var session = sessionProvider.getSearchSession();
var search = session.search(PersonEntity.class);
var q=search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
.must(f.match().field("personType").matching(1))).sort(searchSortFactory -> searchSortFactory.score().desc());
log.info(q.toQuery().queryString());
return q.fetchHits(10);
}
and LawyerEntity:
public List<LawyerEntity> findSimilar(#NotNull String name, Integer barId) {
var session = sessionProvider.getSearchSession();
var search = session.search(LawyerEntity.class);
var res = search.where(f -> f.match().field("fullName").matching(name).fuzzy(2));
if (barId != null)
res = search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
.must(f.match().field("barId").matching(barId)));
var list = res.fetchHits(10);
return list;
}
I suspect your problem is here:
#Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
private String fullName;
As you're defining the full name on the database side, it will only be populated correctly when it's loaded from the database. The first time you create your entity, or anytime you change the first name or last name on your Java object, the fullName property in your Java object will not have the correct value, until it's loaded back from the database.
I think that when you create your entity, fullName is null, so Hibernate Search is indexing your entities with a fullName index field set to null, which explains that your queries (with predicates on the fullName field) do not match anything.
When you use the mass indexer, on the other hand, entities are loaded from the database and fullName is populated correctly from the database.
As to solutions, either:
Always update fullName manually whenever you update firstName or lastName. That might be inconvenient.
OR, if you don't need to use fullName in SQL queries, do not persist fullName in the database, do not add a fullName property to your entity, and just declare a getFullName() getter annotated with #javax.persistence.Transient that does the concatenation in Java:
#Transient
#FullTextField(termVector = TermVector.YES)
#IndexingDependency(derivedFrom = {
#ObjectPath(#PropertyValue(propertyName = "firstName")),
#ObjectPath(#PropertyValue(propertyName = "fatherName")),
#ObjectPath(#PropertyValue(propertyName = "lastName"))
})
public String getFullName() {
return firstName
+ ( fatherName == null ? "" : " " + fatherName )
+ " " + lastName;
}
See this section of the documentation for #IndexingDependency.
I am relatively inexperienced in using Java Spring/Hibernate. This problem may be easy to solve, but I feel like I have done hours of error look ups at this point. Here are some overall specs of my program: Using h2 database, database is locally stored, using spring-boot-starter, spring-boot-security.
Problem
I am trying to create an online Wiki. So far, I have not had any hiccups. I am able to save, update, and delete entities (which are called WikiPages). However, I noticed that if I try to read/save/update/delete a WikiPage around 5 times, the application is no longer able to communicate with my locally stored h2 server, the program hangs, and I am unable to view any webpages at all. I did not encounter this problem on my previous project which has a very similar DAO.
I have a suspicion that there is some sort of problem with my DAO interacting with the database. Any help would be appreciated. Please let me know if you would like to see a different file in the program. I enabled debug console logging
Application Properties
spring.datasource.url = jdbc:h2:tcp://localhost/~/Downloads/Data/Database
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=none
spring.h2.console.enabled=true
server.error.include-message=always
server.port=80
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR
WikiPage
package com.costi.csw9.Model;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
public class WikiPage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(nullable = false, unique = true)
private String title;
#Column(nullable = false)
private LocalDateTime lastEdited;
#Column(nullable = false)
private String subtitle;
private boolean enabled = false;
private String category;
#Column(columnDefinition="text")
private String body;
#OneToOne
#JoinColumn(name = "user_id")
private User author;
public WikiPage(String title, String subtitle, User author, String category, String body) {
this.title = title;
this.subtitle = subtitle;
this.author = author;
this.category = category;
this.body = body;
}
public WikiPage(User author){
this.author = author;
}
}
WikiDaoImpl
package com.costi.csw9.Repository;
import com.costi.csw9.Model.User;
import com.costi.csw9.Model.UserRole;
import com.costi.csw9.Model.WikiCategory;
import com.costi.csw9.Model.WikiPage;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
#Repository
public class WikiDaoImpl implements WikiRepository{
#Autowired
private SessionFactory sessionFactory;
#Override
public WikiPage findById(Long id) {
Session session = sessionFactory.openSession();
WikiPage wikiPage = session.get(WikiPage.class, id);
session.close();
return wikiPage;
}
#Override
public List<WikiPage> findByCategory(WikiCategory category) {
Session session = sessionFactory.openSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<WikiPage> cr = cb.createQuery(WikiPage.class);
Root<WikiPage> root = cr.from(WikiPage.class);
cr.select(root);
cr.select(root).where(cb.like(root.get("category"), category.name()));
Query<WikiPage> query = session.createQuery(cr);
List<WikiPage> results = query.getResultList();
return results;
}
#Override
public List<WikiPage> getByApproval(boolean enabled) {
Session session = sessionFactory.openSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<WikiPage> cr = cb.createQuery(WikiPage.class);
Root<WikiPage> root = cr.from(WikiPage.class);
cr.select(root);
if(enabled){
cr.select(root).where(cb.isTrue(root.get("enabled")));
}else{
cr.select(root).where(cb.isFalse(root.get("enabled")));
}
Query<WikiPage> query = session.createQuery(cr);
List<WikiPage> results = query.getResultList();
return results;
}
#Override
public List<WikiPage> findAll() {
// Open a session
Session session = sessionFactory.openSession();
// Get all people with a Hibernate criteria
List<WikiPage> all = session.createCriteria(WikiPage.class).list();
// Close session
session.close();
return all;
}
#Override
public void save(WikiPage wikiPage) {
//Add in last edited
wikiPage.setLastEdited(LocalDateTime.now());
// Open a session
Session session = sessionFactory.openSession();
// Begin a transaction
session.beginTransaction();
// Save the person
session.saveOrUpdate(wikiPage);
// Commit the transaction
session.getTransaction().commit();
// Close the session
session.close();
}
#Override
public void delete(WikiPage wikiPage) {
// Open the session
Session session = sessionFactory.openSession();
// Not completley sure why I have to do this, but I need to find the page via id in this function
WikiPage page = session.get(WikiPage.class, wikiPage.getId());
// Begin translation
session.beginTransaction();
// Delete Page
session.delete(page);
// Commit the transaction
session.getTransaction().commit();
// Close the session
session.close();
}
}
Error
2022-06-28 20:35:22.933 DEBUG 10820 --- [p-nio-80-exec-4] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
2022-06-28 20:35:22.934 DEBUG 10820 --- [p-nio-80-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
2022-06-28 20:35:22.935 DEBUG 10820 --- [p-nio-80-exec-4] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
2022-06-28 20:35:22.935 DEBUG 10820 --- [p-nio-80-exec-4] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500
although I did not find what I did wrong in my code. I eventually go it to work with some light testing by turning as many DAO methods into JPA queries as I can. Hope this helps others!
I am having one-to-many relationship between Order and LineItem entities. I am exposing them as json response from the RestController.
Order:
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "orders")
#XmlRootElement
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long orderId;
private String name;
private String email;
private double price;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<LineItem> lineItems;
public void addLineItem(LineItem lineItem){
if(this.lineItems == null){
this.lineItems = new HashSet<>();
}
this.lineItems.add(lineItem);
lineItem.setOrder(this);
}
}
LineItem:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Entity
#ToString(exclude = "order")
#EqualsAndHashCode(exclude = "order")
public class LineItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private int qty;
private String name;
private double price;
#ManyToOne
#JoinColumn(name="order_id", nullable = false)
#JsonIgnore
private Order order;
}
The below is my Confiuguration class
#Configuration
public class ApplicationWebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//set path extension to true
configurer.favorPathExtension(false).
//set favor parameter to false
favorParameter(true).
parameterName("mediaType").
//ignore the accept headers
ignoreAcceptHeader(true).
//dont use Java Activation Framework since we are manually specifying the mediatypes required below
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
When I am invoking the request from the browser http://localhost:8222/api/v1/orders?mediaType=xml, I am getting the below error:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.util.ImmutableCollections$SetN] with preset Content-Type 'null']
2022-03-21 22:58:57.511 WARN 153436 --- [nio-8222-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
2022-03-21 22:58:59.931 WARN 153436 --- [nio-8222-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.util.ImmutableCollections$SetN] with preset Content-Type 'null']
2022-03-21 22:58:59.933 WARN 153436 --- [nio-8222-exec-3] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
I have added the below dependency as well in the pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
Where am I going wrong?
I have one class which is entity and use the same class as a property:
#Entity
public class Employee {
private String name;
#OneToOne
#JoinColumn(name = "supervisor_id", referencedColumnName = "id")
private Employee supervisor;
//getters and setters
}
I want to get the supervisor of an employee, but not the supervisor of the supervisor. Can I manage this somehow?
{
"name": "PersonName",
"supervisor": {
"name": "Supervisor name",
"supervisor": null // i don't want this one
}
}
In the end I just used nested classes for both - dto and entity with fields needed.
True that I duplicated properties of the classes, but at least it's clean and simple.
Just ignore the nulls in the Json. The following works for me:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import javax.persistence.*;
#Getter
#Setter
#ToString
#Entity
#JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "supervisor_id", referencedColumnName = "id")
private Employee supervisor;
}
Here's the test
#Test
public void test2() throws Exception {
Employee employee = makeEmployee("employee 1");
Employee supervisor1 = makeEmployee("supervisor 1");
employee.setSupervisor(supervisor1);
Employee save = employeeRepository.save(employee);
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(save));
}
Here's my test output:
{
"id" : 1,
"name" : "employee 1",
"supervisor" : {
"id" : 2,
"name" : "supervisor 1"
}
}
Make sure you use the correct JsonSerialize org.codehaus.jackson.map.annotate.JsonSerialize not com.fasterxml.jackson.databind.annotation.JsonSerialize
I have many to many relation between user to role
USER ENTITY:
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
#Entity
#Data
#EqualsAndHashCode(callSuper = false, exclude = {"roles"})
#ToString( exclude = {"roles"})
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String lanId;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<UserRole> roles = new HashSet<>();
public User(String lanId) {
this.lanId = lanId;
}
// Utility Method to sync on both sides
public void addRole(Role role, boolean isPrivileged) {
UserRole userRole = new UserRole();
userRole.setUser(this);
userRole.setRole(role);
userRole.setPrivileged(isPrivileged);
roles.add(userRole);
role.getUsers().add(userRole);
}
}
ROLE ENTITY:
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
#Entity
#Data
#EqualsAndHashCode(callSuper = false, exclude = {"users"})
#ToString( exclude = {"users"})
#NoArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String roleName;
#OneToMany(mappedBy = "role" ,cascade = CascadeType.ALL)
private Set<UserRole> users = new HashSet<>();
public Role(String roleName) {
this.roleName =roleName;
}
}
USERROLE ENTITY:
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
#Entity
#Data
#EqualsAndHashCode(callSuper = false ,exclude = {"privileged"})
#NoArgsConstructor
public class UserRole implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
private User user;
#Id
#ManyToOne
private Role role;
private boolean privileged;
}
SERVICE CLASS:
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.sample.m2m.dto.RolesDto;
import com.sample.m2m.repository.Role;
import com.sample.m2m.repository.RoleRepository;
import com.sample.m2m.repository.User;
import com.sample.m2m.repository.UserRepository;
#Service
public class SampleService {
#Autowired
private UserRepository userRepo;
#Autowired
private RoleRepository roleRepo;
public void addEntity(String lanId,List<RolesDto> roles) {
// adding roles to DB first
addNewRoles(lanId,roles);
addUserRole(lanId,roles);
}
#Transactional
public void addNewRoles(String lanId,List<RolesDto> roles) {
//Set<String> roles = Set.of("admin", "read","write");
// Set<String> roles = Set.of("opr");
Set<Role> roleSet = new HashSet<Role>();
for(RolesDto role :roles)
{
Role roleDB = roleRepo.findByRoleName(role.getRoleName());
if(roleDB ==null) {
roleDB = new Role(role.getRoleName());
roleSet.add(roleDB);
}
}
roleRepo.saveAll(roleSet);
}
#Transactional
public void addUserRole(String lanId,List<RolesDto> roles) {
//Set<String> roles = Set.of("admin", "read","write");
//Set<String> roles = Set.of("opr");
User userDB = userRepo.findByLanId(lanId);
if(userDB == null) {
userDB = new User(lanId);
for(RolesDto role : roles) {
Role roledb = roleRepo.findByRoleName(role.getRoleName());
userDB.addRole(roledb, true);
}
}
else
{
for(RolesDto role : roles) {
Role roledb = roleRepo.findByRoleName(role.getRoleName());
userDB.addRole(roledb, true);
}
}
userRepo.save(userDB);
}
}
SAMPLE INPUT: 1 : Saving for first time - SUCCESS
{
"lanId":"ABC123",
"roles" :[{
"roleName" : "opr"
}
]
}
SAMPLE INPUT 2 : Saving additional roles to the same user - FAILURE
{
"lanId":"AB123",
"roles" :[{
"roleName" : "admin"
},{
"roleName" : "read"
},
{
"roleName" : "write"
}
]
}
Exception: (Trying to insert null into user and role in link entity)
2020-09-18 11:12:34.379 DEBUG 24862 --- [nio-8080-exec-5] org.hibernate.SQL :
select
userrole0_.user_id as user_id2_2_0_,
userrole0_.role_id as role_id3_2_0_,
userrole0_.privileged as privileg1_2_0_
from
pam.user_role userrole0_
where
userrole0_.user_id=?
and userrole0_.role_id=?
2020-09-18 11:12:34.379 TRACE 24862 --- [nio-8080-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1739260]
2020-09-18 11:12:34.379 TRACE 24862 --- [nio-8080-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1739261]
2020-09-18 11:12:34.393 ERROR 24862 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session : [com.sample.m2m.repository.UserRole#UserRole(user=null, role=null, privileged=true)]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.sample.m2m.repository.UserRole#UserRole(user=null, role=null, privileged=true)]] with root cause
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.sample.m2m.repository.UserRole#UserRole(user=null, role=null, privileged=true)]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]
Am I missing something in the mapping or in the utility method of user entity . Any help is much appreciated.
UserRole attributes aren’t the table’s primary ID so #Id should not be on these 2.
You should add an ID attribute to UserRole and annotate that with #Id and #GeneratedValue.
The #ManyToOne will result in a foreign key in the database
try saving the role also, you haven't stored the role after changing it in addrole function in user entity.