Problems with cascading in spring boot - spring

I want to add an address to a person, when I'm saving the address but it doesn't work. I have already tried all variants of cascading. Could anybody help me to solve this problem? In Entities, I left only fields related to my question.
Address Entity
public class Address {
#OneToOne(mappedBy = "address", cascade = CascadeType.ALL)
private Visitor visitor;
}
Visitor Entity
public class Visitor {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id")
private Address address;
}
Address Service
public class AddressService {
private final AddressRepository addressRepository;
private final VisitorRepository visitorRepository;
public ResponseEntity<Address> saveAddress(Address address, String visitorId) {
Visitor visitor = visitorRepository.findById(visitorId).orElseThrow(() -> new VisitorNotFoundException("Visitor with ID: " + visitorId + " not found"));
Address addressToSave = Address.builder()
.addressLine(address.getAddressLine())
.city(address.getCity())
.zip(address.getZip())
.visitor(visitor)
.build();
addressRepository.save(addressToSave);
return ResponseEntity.created(
ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(addressToSave.getId())
.toUri())
.body(addressToSave);
}
}
VisitorService
public class VisitorService {
private final VisitorRepository visitorRepository;
public ResponseEntity<Visitor> saveVisitor(Visitor visitor) {
Visitor visitorToSave = Visitor.builder()
.address(visitor.getAddress())
.name(visitor.getName())
.phoneNumber(visitor.getPhoneNumber())
.build();
visitorRepository.save(visitorToSave);
return ResponseEntity.created(
ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(visitorToSave.getId())
.toUri())
.body(visitorToSave);
}
}

Have you added #Autowired annotation as like this or not
IN AddressService
#Autowired
private final AddressRepository addressRepository;
#Autowired
private final VisitorRepository visitorRepository;
In VisitorService
#Autowired
private final VisitorRepository visitorRepository;

Related

Spring Controller Returns Object Incompletely

There are three classes (Course, Lesson, User).
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "usr")
#Data
public class User extends RepresentationModel<User> implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String username;
private String password;
#ElementCollection(targetClass = ERole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
private Set<ERole> roles;
}
#Data
#Entity
#NoArgsConstructor
public class Lesson extends RepresentationModel<Lesson> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String startTime;
private String endTime;
private String dayOfWeek;
#ManyToOne
private User teacher;
}
#EqualsAndHashCode(callSuper = true)
#Data
#Entity
public class Course extends RepresentationModel<Course> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date startDate;
private Date endDate;
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> teachers;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> students;
private String description;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Lesson> lessons;
}
And also RestController (CoursesController). When accessing the server at /courses, I get the correct server response with all fields
.
#RestController
#RequestMapping("/courses")
public class CoursesController {
private final CourseService courseService;
private final UserService userService;
private final LessonService lessonService;
#Autowired
public CoursesController(CourseService courseService, UserService userService, LessonService lessonService) {
this.courseService = courseService;
this.userService = userService;
this.lessonService = lessonService;
}
#GetMapping
#Operation(
summary = "getAllCourses",
description = "Returns all available courses"
)
public ResponseEntity<Page<Course>> getAllCourses(#PageableDefault(sort = "id", size = 5) Pageable pageable) {
try {
Page<Course> coursePage = courseService.findAll(pageable);
for (Course course : coursePage.getContent())
course.add(linkTo(methodOn(CoursesController.class).getCourse(course.getId().toString())).withSelfRel());
return ResponseEntity.ok(courseService.findAll(pageable));
}
catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/{course-id}")
#Operation(
summary = "getCourse",
description = "Returns course by ID"
)
public ResponseEntity<Course> getCourse(#PathVariable ("course-id") String courseId) {
try {
Course course = courseService.getCourseById(courseId);
course.add(linkTo(methodOn(CoursesController.class).getCourse(courseId)).withSelfRel());
return ResponseEntity.ok(course);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
Why, when requesting a course by ID (GET /courses/{id}), does Spring return an incomplete object (despite the fact that I manually added several teachers, students and lessons)?
I need to get all the fields of my object.
My CourseRepository below.
#Repository
#Transactional
public interface CourseRepository extends JpaRepository<Course, Long> {
}
My CourseService below.
#Service
public class CourseService {
private final CourseRepository courseRepository;
private final LessonRepository lessonRepository;
private final UserRepository userRepository;
#Autowired
public CourseService(CourseRepository courseRepository, LessonRepository lessonRepository, UserRepository userRepository) {
this.courseRepository = courseRepository;
this.lessonRepository = lessonRepository;
this.userRepository = userRepository;
}
public Page<Course> findAll(Pageable pageable) {
return courseRepository.findAll(pageable);
}
public Course createCourse(CourseDto courseDto) {
Course course = new Course(courseDto.getStartDate(), courseDto.getEndDate(), courseDto.getName(), courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Optional<Course> getCourseById(String id) {
return courseRepository.findById(Long.parseLong(id));
}
public Course updateCourse(CourseDto courseDto, String id) {
Course course = courseRepository.findById(Long.parseLong(id)).get();
course.setStartDate(courseDto.getStartDate());
course.setEndDate(courseDto.getEndDate());
course.setName(courseDto.getName());
course.setDescription(courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Page<Lesson> getLessonsByCourse(String courseId, Pageable pageable) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
return new PageImpl<>(new ArrayList<>(course.getLessons()), pageable, course.getLessons().size());
}
public Course addLesson(String courseId, LessonDto lessonDto) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
Lesson lesson = new Lesson();
lesson.setStartTime(lessonDto.getStartTime());
lesson.setEndTime(lessonDto.getFinishTime());
lesson.setDayOfWeek(lessonDto.getDayOfWeek());
lesson.setTeacher(userRepository.getUserById(lessonDto.getTeacherId()));
lessonRepository.saveAndFlush(lesson);
System.out.println(lesson);
course.getLessons().add(lesson);
return courseRepository.saveAndFlush(course);
}
public void deleteCourse(String id) {
courseRepository.deleteById(Long.parseLong(id));
}
}
Which I would (or might) expect as well. I would links to be generated for those additional relationshps (at least normally with Spring Data RESt handling this is what would happen). I wonder what happens if you ditch the RepresentationModel from your JPA model and just expose Course then. As stated you don't really want your JPA and HATEOAS stuff to be intertwined. You want to have a specialized projection/dto to expose. WHy does it work for your findAll. well you aren't adding links to it (although you think it does but your findAll executes twice!).
Removed RepresentationModel from User class.
Thx to #M.Deinum

Infinite recursion with #JsonManagedReference and #JsonBackReference

I have an entity class that is self referencing itself. For example, a document can be linked to a parent document.
#Entity
#Table(name = "documents")
public class DocumentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#JsonManagedReference
#ManyToOne(fetch = FetchType.LAZY)
private DocumentEntity parentDocument;
#JsonBackReference
#OneToMany(mappedBy = "parentDocument", fetch = FetchType.LAZY)
private Set<DocumentEntity> documents;
#Column(nullable = false, unique = true)
private String documentId;
#Column(nullable = false)
private String fileName;
}
In my entry point / controller layer :
#GetMapping(
path = "/{fileId}",
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
)
public DocumentResponse getParentDocument(#PathVariable("fileId") String fileId) {
modelMapper = createModelMapper();
DocumentDto documentDto = documentService.getParentDocument(fileId);
DocumentResponse documentResponse = modelMapper.map(documentDto, DocumentResponse.class);
documentResponse.getDocuments().forEach(document -> System.out.println(document.getDocumentId()));
return documentResponse;
}
In my Service layer :
#Override
public DocumentDto getParentDocument(String documentId) {
DocumentDto documentDtoResponse = new DocumentDto();
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
DocumentEntity storedDocumentEntity =
documentRepository.findByDocumentIdAndParentDocumentNull(documentId);
if(storedDocumentEntity.getDocumentId().isEmpty() || storedDocumentEntity.getDocumentId().isBlank()) {
throw new AppFileNotFoundException("Oops file not found");
}
documentDtoResponse = modelMapper.map(storedDocumentEntity, DocumentDto.class);
return documentDtoResponse;
}
In the repository:
Now I'm making a sql request in a repository interface that extends JpaRepository.
The application allow to have a parent document with child documents and child documents cannot have child documents.
#Repository
public interface DocumentRepository extends JpaRepository<DocumentEntity, Long> {
DocumentEntity findByDocumentIdAndParentDocumentNull(String documentId);
}
I also tried to implement the method using JPQL :
#Query("SELECT d FROM DocumentEntity d WHERE d.documentId = :documentId AND d.parentDocument IS NULL")
DocumentEntity findByDocumentIdAndParentDocumentNull(String documentId);
This query allow to get parent documents and child documents.
My code implementation separates response and database by using a DTO layer.
Issue:
My issue is that I obtain an infinite recursion. I think i'm using #JsonManagedReference and #JsonBackReference correctly. Even adding the same annotations to DTO pojo do not solve issue. If i add those annotation to response POJO, then I do not obtain child documents.
Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException
Inially I have a DTO class that also self refers to itself.
public class DocumentDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
private DocumentDto parentDocument;
Set<DocumentDto> documents;
}
I created a second class without properties that are causing problems;
public class DocumentChildDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
}
In the DocumentDto I simply replaced the DocumentDto with DocumentChildDto.
public class DocumentDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
private DocumentChildDto parentDocument;
Set<DocumentChildDto> documents;
}
It's more a hack than a technical solution but it works fine. Here childDocumentDto object won't load the parentDocument.

Limiting the visibility of results from the database on one page- spring boot

I create web application in spring boot using the postgress database.
I want to limit the number of records per page(now it's 30,000 records - it's loading a long time), so what should i do to limit it? I use thymeleaf.
Model:
#Entity(name="articles")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Articles {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long article_id;
private String title;
private String issn;
private String eissn;
private String title2;
private String issn2;
private String eissn2;
private Integer points;
#ManyToMany
#JoinTable(
name = "articles_categories",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name = "category_id"))
private List<Category> categories;
....
getters and setters
Repository:
public interface ArticlesRepository extends JpaRepository<Articles,Long> {
}
Controller:
#Controller
#RequestMapping("/articles")
public class ArticlesController {
private ArticleService articleService;
#Autowired
public void setArticleService(ArticleService articleService) {
this.articleService = articleService;
}
#GetMapping
public String getAll(Model model)
{
model.addAttribute("articles", articleService.list());
return "articles";
}
Service:
#Service
public class ArticleService {
#Autowired
private ArticlesRepository articlesRepository;
public ArticleService() {
}
public List<Articles> list(){
return articlesRepository.findAll();
}}
Use Pageable to limit the size of your articles.
public List<Articles> list(int page, int limit){
Page<Articles> pageableArticales = articlesRepository.findAll(PageRequest.of(page, limit);
return pageableArticales.getContent();
}
Note that repository.findAll(pageable) wraps the list of data on Page, which provides getNumber(), getSize(), getNumberOfElements(), getTotalPages(), getTotalElements, etc.
And consider exploring PageRequest and PagedResources as well.

JPA: ManyToMany issue - Spring Boot Project

I want to establish many-to-many relation between two table. One is called User and the other is Tag.
My goal is to add previously created Tag lists to newly created User objects. Tag must be added to the database first, and then merged with User after selecting from existing ones.
How can I solve this problem? Where am I doing wrong? Any advice?
Thank you.
All my codes are as follows.
Filename = Demo2Application.java
#SpringBootApplication
public class Demo2Application implements CommandLineRunner {
#Autowired
private UserService userService;
#Autowired
private TagService tagService;
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
#Override
public void run(String... args) throws Exception {
//Scenario: Tag list is already created.
Tag tag1 = new Tag("strong", true); //id = 1
Tag tag2 = new Tag("weak", true); //id = 2
Tag tag3 = new Tag("nice", true); //id = 3
Tag tag4 = new Tag("clever", true); //id = 4
tagService.save(tag1);
tagService.save(tag2);
tagService.save(tag3);
tagService.save(tag4);
//Scenario: Defining a new user.
User user1 = new User("foo", "foo#gmail.com");
//Scenario: Appropriate predefined tags are being added to the new user object.
user1.addTag(tagService.findById(1));
user1.addTag(tagService.findById(4));
//Scenario: Registering a User object to the database.
userService.save(user1);
}
}
Filename = entity/Model.java
#MappedSuperclass
public class Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#CreationTimestamp
private Date createdAt;
#UpdateTimestamp
private Date updatedAt;
// standard constructors, getters, and setters
}
Filename = entity/User.java
#Entity
#Table(name = "user")
public class User extends Model {
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
private List<Tag> tags = new ArrayList<>();
//
public void addTag(Tag tag) {
tags.add(tag);
}
// standard constructors, getters, and setters
Filename = entity/Tag.java
#Entity
#Table(name = "tag")
public class Tag extends Model {
#Column(name = "tag_name")
private String tagName;
#Column(name = "tag_active")
private boolean tagActive;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = {#JoinColumn(name = "tag_id")},
inverseJoinColumns = {#JoinColumn(name = "user_id")})
private List<User> users = new ArrayList<>();
// standard constructors, getters, and setters
Repositories = TagRepository, UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface TagRepository extends JpaRepository<Tag, Long> {
}
Filename = service/UserService.java
#Service
public class UserService {
private UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(long id) {
Optional<User> result = userRepository.findById(id);
return result.orElse(null);
}
public void save(User user) {
userRepository.save(user);
}
}
Filename = service/TagService.java
#Service
public class TagService {
private TagRepository tagRepository;
#Autowired
public TagService(TagRepository tagRepository) {
this.tagRepository = tagRepository;
}
public Tag findById(long id) {
Optional<Tag> result = tagRepository.findById(id);
return result.orElse(null);
}
public void save(Tag tag) {
tagRepository.save(tag);
}
}
Filename = resources/application.properties
server.port=7070
## Database (PostgreSQL + Hikari + JPA)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.maximumPoolSize=5
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
#
spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.username=admin
spring.datasource.password=1234
#
spring.jpa.hibernate.ddl-auto=create-drop
## Database ##

Why the record is posted twice in the database?

Can you tell me, why the record is posted twice in the database. I think. this happens because I use save() method. But shouldn't I save the master-entity and dependent-entity separately?
Controller method:
#RequestMapping(value = "/addComment/{topicId}", method = RequestMethod.POST)
public String saveComment(#PathVariable int topicId, #ModelAttribute("newComment")Comment comment, BindingResult result, Model model){
Topic commentedTopic = topicService.findTopicByID(topicId);
commentedTopic.addComment(comment);
// TODO: Add a validator here
if (!comment.isValid() ){
return "//";
}
// Go to the "Show topic" page
commentService.saveComment(comment);
return "redirect:../details/" + topicService.saveTopic(commentedTopic);
}
Services:
#Service
#Transactional
public class CommentService {
#Autowired
private CommentRepository commentRepository;
public int saveComment(Comment comment){
return commentRepository.save(comment).getId();
}
}
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
}
Model:
#Entity
#Table(name = "T_TOPIC")
public class Topic {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
#Enumerated(EnumType.STRING)
private Tag topicTag;
private String name;
private String text;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
}
#Entity
#Table(name = "T_COMMENT")
public class Comment
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="TOPIC_ID")
private Topic topic;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
private String text;
private Date creationDate;
}
In this concrete case, you do not need to save the master and the client.
Saving the master or the client would be enough (with this concrete mapping)
But I think the main problem is that you do not have a good equals method in your Comment so your ORM Provider think that there are two different comments, and therefore store them twice.

Resources