Skip to main content

One to Many Mapping using Spring boot


In this blog, I will explain how to use one-to-many mapping in Spring boot Application

What you need?
  • JAVA
  • MySql
  • Eclipse IDE ( whatever you like IDE, I'm using Eclipse for this example)
  • Maven ( you can use the Gradle as well)

Initial Plan

I will create a spring boot application project using the Spring Initializer web tool and import the project as a maven project. after configuring the all necessary setting, I will code for one-to-many mapping.

Below diagram is the database model diagram which we going to install using the spring boot application.




Let's Start to Code.

You need to configure the application.properties file for database connections. add the following content to the src/main/resources/application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/learning
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
Change the database username and password according to your MySql configurations.

1. BaseAuditModel class

This class will automatically create create_at and update_at fields while persisting the entities. Here is the Spring basic definition of the Auditing Class.


"Spring Data provides sophisticated support to transparently keep track of who created or changed an entity and the point in time this happened. To benefit from that functionality you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface."[1].

package com.hksoft.springbootonetomany.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
  value= {"createdAt", "updatedAt"},
  allowGetters=true
  )
public abstract class BaseAuditModel implements Serializable {

 @Temporal(TemporalType.TIMESTAMP)
 @Column(name="createdAt", nullable=false, updatable=false)
 @CreatedDate
 private Date createdAt;
 
 @Temporal(TemporalType.TIMESTAMP)
 @Column(name="updatedAt", nullable=false)
 @LastModifiedDate
 private Date updatedAt;

 public Date getCreatedAt() {
  return createdAt;
 }

 public void setCreatedAt(Date createdAt) {
  this.createdAt = createdAt;
 }

 public Date getUpdatedAt() {
  return updatedAt;
 }

 public void setUpdatedAt(Date updatedAt) {
  this.updatedAt = updatedAt;
 }
 
}
@JsonIgnoreProperties ignores the specified logical properties in JSON serialization and deserialization. It is annotated at the class level.[2]

Ok. now you need to enable the auditing in one of the configuration class. Open the main class and add the @EnableJpaAuditing annotation.

package com.hksoft.springbootonetomany;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class Application {
 public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
 }
}
Post Model


package com.hksoft.springbootonetomany.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "POSTS")
public class Post extends BaseAuditModel {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @NotNull
 @Size(max = 100)
 @Column(name = "title")
 private String title;

 @NotNull
 @Size(max = 200)
 @Column(name = "description")
 private String description;

 @NotNull
 @Lob
 private String content;

 // Getter and setters
}

Comment Model

package com.hksoft.springbootonetomany.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "COMMENTS")
public class Comment extends BaseAuditModel {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @NotNull
 @Lob
 @Column(name = "text")
 private String comment;

 @ManyToOne(fetch = FetchType.LAZY, optional = false)
 @JoinColumn(name = "postId", nullable = false)
 @OnDelete(action = OnDeleteAction.CASCADE)
 @JsonIgnore
 private Post post;

 //Getter and Setter

}
@JsonIgnore is used to ignore the logical property used in serialization and deserialization. @JsonIgnore can be used at setter, getter or field.[2]

Repositories.

Now we'll create the repository to access the data from database.

PostRepository 

package com.hksoft.springbootonetomany.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.hksoft.springbootonetomany.model.Post;

@Repository
public interface PostRepository extends JpaRepository<Post, Long>{
}
CommentRepository

package com.hksoft.springbootonetomany.repository;

import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import com.hksoft.springbootonetomany.model.Comment;

public interface CommentRepository extends JpaRepository<Comment, Long>{

 Page findByPostId(Long id, Pageable pageable);
 Optional findByIdAndPostId(Long id, Long postId);
}
Creating the Service package

Post Services interface

package com.hksoft.springbootonetomany.service;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.hksoft.springbootonetomany.model.Post;

public interface PostService {
 Post createPost(Post post);
 Page getAllPost(Pageable pageable);
 Post updatePost(Long id, Post post);
 ResponseEntity deletePost(Long id); 
}
PostServiceImpl Class

package com.hksoft.springbootonetomany.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.hksoft.springbootonetomany.exception.ResourceNotFoundException;
import com.hksoft.springbootonetomany.model.Post;
import com.hksoft.springbootonetomany.repository.PostRepository;

@Service
public class PostServiceImpl implements PostService{

 @Autowired
 private PostRepository postRepository;
 
 @Override
 public Post createPost(Post post) {  
  return postRepository.save(post);
 }

 @Override
 public Page getAllPost(Pageable pageable) {
  return postRepository.findAll(pageable);
 }

 @Override
 public Post updatePost(Long postId, Post postUpdate) {
  return postRepository.findById(postId).map(post -> {
   post.setTitle(postUpdate.getTitle());
   post.setDescription(postUpdate.getDescription());
   post.setContent(postUpdate.getContent());
   
   return postRepository.save(post);
  }).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " Not Found" ));
 }

 @Override
 public ResponseEntity deletePost(Long postId) {
  return postRepository.findById(postId).map(post -> {
   postRepository.delete(post);
   return ResponseEntity.ok().build();
  }).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " Not Found" ));
 }

}
Comment Service interface

package com.hksoft.springbootonetomany.service;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;

import com.hksoft.springbootonetomany.model.Comment;

public interface CommentService {
 Page getAllComments(Long postId, Pageable pageable);
 Comment createComment(Long postId, Comment comment);
 Comment updateComment(Long postId, Long commentId, Comment commentUpdate);
 ResponseEntity deleteComment(Long postId, Long commentId); 
}
CommentServiceImpl Class

package com.hksoft.springbootonetomany.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.hksoft.springbootonetomany.exception.ResourceNotFoundException;
import com.hksoft.springbootonetomany.model.Comment;
import com.hksoft.springbootonetomany.repository.CommentRepository;
import com.hksoft.springbootonetomany.repository.PostRepository;

@Service
public class CommentServiceImpl implements CommentService {

 @Autowired
 private CommentRepository commentRepository;
 
 @Autowired
 private PostRepository postRepository;
 
 @Override
 public Page getAllComments(Long postId, Pageable pageable) {
  return commentRepository.findByPostId(postId, pageable);
 }

 @Override
 public Comment createComment(Long postId, Comment comment) {
  return postRepository.findById(postId).map(post -> {
   comment.setPost(post);
   return commentRepository.save(comment);
  }).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
 }

 @Override
 public Comment updateComment(Long postId, Long commentId, Comment commentUpdate) {  
  if(!postRepository.existsById(postId)) {
   throw new ResourceNotFoundException("PostId " + postId + " not found");
  }
  
  return commentRepository.findById(commentId).map(comment -> {
   comment.setComment(commentUpdate.getComment());
   return commentRepository.save(comment);
  }).orElseThrow(() -> new ResourceNotFoundException("commentId " + commentId + " not found"));
 }

 @Override
 public ResponseEntity deleteComment(Long postId, Long commentId) {
  return commentRepository.findByIdAndPostId(commentId, postId).map(comment -> {
   commentRepository.delete(comment);
   return ResponseEntity.ok().build();
  }).orElseThrow(() -> new ResourceNotFoundException("commentId " + commentId + " not found"));
 }
}
Let's write REST API to perform CRUD operations.

PostController

package com.hksoft.springbootonetomany.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.hksoft.springbootonetomany.model.Post;
import com.hksoft.springbootonetomany.service.PostService;

@RestController
@RequestMapping("/rest/posts")
public class PostController {

 @Autowired
 private PostService postSevice;
 
 @PostMapping
 @ResponseStatus(value=HttpStatus.CREATED)
 public Post createPost(@Valid @RequestBody Post post) {
  return postSevice.createPost(post);
 }
 
 @GetMapping
 public Page getAllPage(Pageable pageable) {
  return postSevice.getAllPost(pageable);
 }
 
 @PutMapping("/{postId}")
 public Post updatePost(@PathVariable Long postId, @Valid @RequestBody Post postUpdate) {
  return postSevice.updatePost(postId, postUpdate);
 }
 
 @DeleteMapping("/{postId}")
 public ResponseEntity deletePost(@PathVariable Long postId) {
  return postSevice.deletePost(postId);
 }
}
Comment Controller

package com.hksoft.springbootonetomany.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.hksoft.springbootonetomany.model.Comment;
import com.hksoft.springbootonetomany.service.CommentService;

@RestController
@RequestMapping("/rest/posts/{postId}/comments")
public class CommentController {

 @Autowired
 private CommentService commentService;
 
 @GetMapping()
 public Page getAllComments(@PathVariable (value="postId") Long postId, Pageable pageable) {
  return commentService.getAllComments(postId, pageable);
 }
 
 @PostMapping()
 @ResponseStatus( value= HttpStatus.CREATED)
 public Comment createComment(@PathVariable (value="postId") Long postId, @RequestBody Comment comment) {
  return commentService.createComment(postId, comment);
 }
 
 @PutMapping("/{commentId}")
 public Comment updateComment(@PathVariable ( value= "postId") Long postId, @PathVariable ( value="commentId") Long commentId
   , @Valid @RequestBody Comment commentUpdate) {
  return commentService.updateComment(postId, commentId, commentUpdate);
 }
 
 @DeleteMapping("/{commentId}")
 public ResponseEntity deleteComment(@PathVariable ( value= "postId") Long postId, @PathVariable ( value="commentId") Long commentId) {
  return commentService.deleteComment(postId, commentId);
 }
 
}
Ok. we finished the coding. Now we need to test the REST API using postman.

You can download the code from my GitHub.

Rest API Details

References

[1]. https://docs.spring.io/spring-data/jpa/docs/1.7.0.DATAJPA-580-SNAPSHOT/reference/html/auditing.html
[2]. https://www.concretepage.com/jackson-api/jackson-jsonignore-jsonignoreproperties-and-jsonignoretype
[3]. https://vladmihalcea.com/how-to-inherit-properties-from-a-base-class-entity-using-mappedsuperclass-with-jpa-and-hibernate/
[4]. https://www.baeldung.com/java-optional
[5]. https://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html


Comments

Popular posts from this blog

Java Increment Operations: `n++` vs `n = n + 1` vs `n += 1`

In Java, incrementing a variable by one can be done in several ways: n++ , n = n + 1 , and n += 1 . While these expressions achieve the same end result, they differ slightly in syntax and use cases. Let's explore each one and discuss their performance. 1. n++ Post-Increment Operator : Increments the value of n by 1 after its current value has been used. Common Usage : Typically used in loops and other contexts where the current value needs to be used before incrementing. int n = 5; n++; // n is now 6 2. n = n + 1 Addition Assignment : Explicitly sets n to its current value plus 1. Readability : Straightforward and clear, though slightly more verbose. int n = 5; n = n + 1; // n is now 6 3. n += 1 Compound Assignment Operator : Equivalent to n = n + 1 , but more concise. Usage : Combines addition and assignment into one step. int n = 5; n += 1; // n is

Understanding C1 and C2 Compilers in Java

Understanding C1 and C2 Compilers in Java Understanding C1 and C2 Compilers in Java In Java, the Just-In-Time (JIT) compiler is a part of the Java Virtual Machine (JVM) that improves the performance of Java applications by compiling bytecode into native machine code at runtime. The JIT compiler includes two different compilers, known as the C1 and C2 compilers, each with distinct optimization strategies and purposes. C1 Compiler (Client Compiler) The C1 compiler, also known as the client compiler, is designed for fast startup times and lower memory consumption. It performs lighter and quicker optimizations, which makes it suitable for applications that require quick startup and responsiveness. Key characteristics of the C1 compiler include: Quick Compilation: Prioritizes fast compilation times over deep optimizations. Low Overhead: Consumes less memory and resources during compilation. Profile-Guided Optimization: Ca

When To Use Indexes In MySQL

When deciding when and how to create an index in your MySQL database, it's important to consider how the data is being used. Let's say you have a database of  students . We will create it like this: CREATE TABLE `students` ( `id` int ( 11 ) NOT NULL AUTO_INCREMENT , `first_name` varchar ( 255 ) DEFAULT NULL , `last_name` varchar ( 255 ) DEFAULT NULL , `class` varchar ( 255 ) DEFAULT NULL , PRIMARY KEY ( `id` ) ) ENGINE = InnoDB Indexes are best used on columns that are frequently used in where clauses, and in any kind of sorting, such as "order by". You should also pay attention to whether or not this information will change frequently, because it will slow down your updates and inserts. Since you wont frequently be adding students, you don't have to worry about the inserts Let's say that you will be looking up the students with a web interface and the end user will be typing in the students name to find them, since r