In this tutorial, we will learn how to build REST APIs for one to many mapping in the REST controller.
We will implement one-to-many mapping using JPA/Hibernate and also build REST APIs for one-to-many mapping in the Spring Boot REST controller.
We use MySQL database to store and retrieve the data.
1. Add Maven Dependencies
Open your pom.xml file and add the following dependencies if not added:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. Configure MySQL Database
We’ll need to configure MySQL database URL, username, and password so that Spring can establish a connection with the database on startup.
Open src/main/resources/application.properties and add the following properties to it -
logging.pattern.console=%clr(%d{yy-MM-dd E HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(%logger{0}){blue} %clr(%m){faint}%n
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Make sure that you will create a demo database and change the database URL, username, and password as per your MySQL installation on your machine.
3. Creating JPA Entities with One-to-Many Mapping
AuditModel
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
value = {"createdAt", "updatedAt"},
allowGetters = true
)
public abstract class AuditModel implements Serializable {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", nullable = false, updatable = false)
@CreatedDate
private Date createdAt;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated_at", 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;
}
}
Post Entity
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Entity
@Table(name = "posts")
public class Post extends AuditModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(max = 100)
@Column(unique = true)
private String title;
@NotNull
@Size(max = 250)
private String description;
@NotNull
@Lob
private String content;
public Long getId() {
return id;
}
public void setId(Long 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 String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
Comment Entity
import com.fasterxml.jackson.annotation.*;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
@Table(name = "comments")
public class Comment extends AuditModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Lob
private String text;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "post_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
@JsonIdentityReference(alwaysAsId=true)
@JsonProperty("post_id")
private Post post;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
}
4. Create Spring Data JPA Repositories
PostRepository
import com.example.jpa.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}
CommentRepository
import com.example.jpa.model.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface CommentRepository extends JpaRepository<Comment, Long> {
Page<Comment> findByPostId(Long postId, Pageable pageable);
Optional<Comment> findByIdAndPostId(Long id, Long postId);
}
5. Creating Custom Exception
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
6. Creating Spring Boot REST Controllers
PostController
import com.example.jpa.exception.ResourceNotFoundException;
import com.example.jpa.model.Post;
import com.example.jpa.repository.PostRepository;
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.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class PostController {
@Autowired
private PostRepository postRepository;
@GetMapping("/posts")
public Page<Post> getAllPosts(Pageable pageable) {
return postRepository.findAll(pageable);
}
@PostMapping("/posts")
public Post createPost(@Valid @RequestBody Post post) {
return postRepository.save(post);
}
@PutMapping("/posts/{postId}")
public Post updatePost(@PathVariable Long postId, @Valid @RequestBody Post postRequest) {
return postRepository.findById(postId).map(post -> {
post.setTitle(postRequest.getTitle());
post.setDescription(postRequest.getDescription());
post.setContent(postRequest.getContent());
return postRepository.save(post);
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
@DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(@PathVariable Long postId) {
return postRepository.findById(postId).map(post -> {
postRepository.delete(post);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
}
CommentController
import com.example.jpa.exception.ResourceNotFoundException;
import com.example.jpa.model.Comment;
import com.example.jpa.repository.CommentRepository;
import com.example.jpa.repository.PostRepository;
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.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class CommentController {
@Autowired
private CommentRepository commentRepository;
@Autowired
private PostRepository postRepository;
@GetMapping("/posts/{postId}/comments")
public Page<Comment> getAllCommentsByPostId(@PathVariable (value = "postId") Long postId,
Pageable pageable) {
return commentRepository.findByPostId(postId, pageable);
}
@PostMapping("/posts/{postId}/comments")
public Comment createComment(@PathVariable (value = "postId") Long postId,
@Valid @RequestBody Comment comment) {
return postRepository.findById(postId).map(post -> {
comment.setPost(post);
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
@PutMapping("/posts/{postId}/comments/{commentId}")
public Comment updateComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId,
@Valid @RequestBody Comment commentRequest) {
if(!postRepository.existsById(postId)) {
throw new ResourceNotFoundException("PostId " + postId + " not found");
}
return commentRepository.findById(commentId).map(comment -> {
comment.setText(commentRequest.getText());
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("CommentId " + commentId + "not found"));
}
@DeleteMapping("/posts/{postId}/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId) {
return commentRepository.findByIdAndPostId(commentId, postId).map(comment -> {
commentRepository.delete(comment);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Comment not found with id " + commentId + " and postId " + postId));
}
}
7. Running the Application and Testing the APIs via a Postman
You can run the application by typing the following command in the terminal -mvn spring-boot:run
REST APIs Details for Testing
Use below endpoint URLs for testing REST APIs.
Create new Post:
URL - http ://localhost:8080/posts
HTTP Method - POST
HTTP Method - POST
Update existing Post:
URL - http ://localhost:8080/posts/{postId}
HTTP Method - PUT
HTTP Method - PUT
Delete existing Post:
URL - http ://localhost:8080/posts/{postId}
HTTP Method - DELETE
HTTP Method - DELETE
Get all paginated posts:
URL - http ://localhost:8080/posts?page=0&size=2&sort=createdAt,desc
HTTP Method - GET
HTTP Method - GET
Create Comment
URL - http ://localhost:8080/posts/{postId}/comments
HTTP Method - POST
Update existing Comment
URL - http ://localhost:8080/posts/{postId}/comments/{commentId}
HTTP Method - PUT
Delete existing Comment
HTTP Method - DELETE
Get all paginated comments
URL - http ://localhost:8080/posts/{postId}/comments?page=0&size=3&sort=createdAt,desc
HTTP Method - GET
HTTP Method - GET