Tạo Rest API trong Spring boot làm việc với database sử dụng JPA
Đăng lúc: 08:57 AM - 08/04/2025 bởi Charles Chung - 160Trong bài học này, chúng ta sẽ xây dựng một REST API sử dụng Spring Boot và JPA để làm việc với cơ sở dữ liệu. Chúng ta sẽ sử dụng một ví dụ với 2 bảng quan hệ One-to-Many trong cơ sở dữ liệu.

Yêu cầu: Tạo REST API thực hiện các thao tác CRUD với 2 bảng như hình dưới
Bước 1: Tạo project SpringBoot với tên session11example1 và chọn các dependency như hình dưới:
Bước 2: Tạo cấu trúc project như hình dưới
Bước 3: Code cho các lớp trong gói entities
- Code cho lớp Blog (biểu diễn thông tin bài viết - map với bảng blogs)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
package com.bkap.entities; import java.util.Date; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; @Entity @Table(name="blogs") public class Blog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="blogid") private Long blogId; @NotBlank(message = "Hãy nhập tiêu đề") @Column(name="title",length = 200) private String title; @NotBlank(message = "Hãy nhập mô tả") @Column(name="brief",length = 1000) private String brief; @Column(name="content",columnDefinition = "nvarchar(max)") private String content; @Column(name="picture",length = 1000) private String picture; @DateTimeFormat(pattern = "dd/MM/yyyy hh:mm a") @Column(name="createdate") private Date createDate; @Column(name="status") private int status; @ManyToOne @JoinColumn(name = "topicid",nullable = false) @JsonIgnoreProperties("blogs") private Topic topic=new Topic(); public Topic getTopic() { return topic; } public void setTopic(Topic topic) { this.topic=topic; } public Long getBlogId() { return blogId; } public void setBlogId(Long blogId) { this.blogId = blogId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } } |
- Code cho lớp Topic (biểu diễn thông tin chủ đề - map với bảng topics)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package com.bkap.entities; import java.util.Date; import java.util.HashSet; import java.util.Set; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; @Entity @Table(name="topics") public class Topic { @Id @GeneratedValue(strategy =GenerationType.IDENTITY) @Column(name="topicid") private Long topicId; @Column(name="topicname",length = 100,unique = true) @NotBlank(message = "Hãy nhập tên chủ đề") private String topicName; @Column(name="brief",length = 1000) private String brief; @Column(name="createdate") @DateTimeFormat(pattern = "dd/MM/yyyy hh:mm a") private Date createDate; @Column(name="status") private boolean status; @JsonIgnoreProperties("topic") @OneToMany(mappedBy = "topic") private Set<Blog> blogs=new HashSet<Blog>(); public Set<Blog> getBlogs(){ return blogs; } public Long getTopicId() { return topicId; } public void setTopicId(Long topicId) { this.topicId = topicId; } public String getTopicName() { return topicName; } public void setTopicName(String topicName) { this.topicName = topicName; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } } |
Bước 4: Code cho các lớp trong gói models
- Code cho lớp BlogModel (biểu diễn thông bài viết - trả về client)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
package com.bkap.models; import java.util.Date; import com.bkap.entities.Blog; public class BlogModel { private Long blogId; private String title; private String brief; private String content; private String picture; private Date createDate; private int status; private Long topicId; private String topicName; public BlogModel() { // TODO Auto-generated constructor stub } public BlogModel(Blog blog) { super(); this.blogId = blog.getBlogId(); this.title = blog.getTitle(); this.brief = blog.getBrief(); this.content = blog.getContent(); this.picture = blog.getPicture(); this.createDate = blog.getCreateDate(); this.status = blog.getStatus(); this.topicId = blog.getTopic().getTopicId(); this.topicName = blog.getTopic().getTopicName(); } public Long getTopicId() { return topicId; } public void setTopicId(Long topicId) { this.topicId = topicId; } public String getTopicName() { return topicName; } public void setTopicName(String topicName) { this.topicName = topicName; } public Long getBlogId() { return blogId; } public void setBlogId(Long blogId) { this.blogId = blogId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } } |
- Code cho lớp TopicModel (biểu diễn thông chủ đề- trả về client)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
package com.bkap.models; import java.util.Date; import com.bkap.entities.Topic; public class TopicModel { private Long topicId; private String topicName; private String brief; private Date createDate; private boolean status; public TopicModel() { // TODO Auto-generated constructor stub } public TopicModel(Topic topic) { this.topicId = topic.getTopicId(); this.topicName = topic.getTopicName(); this.brief = topic.getBrief(); this.createDate = topic.getCreateDate(); this.status = topic.isStatus(); } public Long getTopicId() { return topicId; } public void setTopicId(Long topicId) { this.topicId = topicId; } public String getTopicName() { return topicName; } public void setTopicName(String topicName) { this.topicName = topicName; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } } |
Bước 5: Code cho các interface trong gói repositories
- Code cho interface TopicRepository (biểu diễn các nghiệp vụ làm việc với bảng topics)
1 2 3 4 5 6 7 8 9 |
package com.bkap.repositories; import org.springframework.data.jpa.repository.JpaRepository; import com.bkap.entities.Topic; public interface TopicRepository extends JpaRepository<Topic,Long> { } |
- Code cho interface BlogRepository (biểu diễn các nghiệp vụ làm việc với bảng blogs)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.bkap.repositories; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.bkap.entities.Blog; public interface BlogRepository extends JpaRepository<Blog, Long> { @Query("SELECT b FROM Blog b WHERE b.topic.topicId=:id") public List<Blog> findByTopicId(@Param("id") Long topicId); @Query("SELECT count(b)>0 FROM Blog b WHERE b.topic.topicId=:id") Boolean existsTopic(@Param("id")Long topicId); } |
Bước 6: Code cho các interface trong gói services
- Code cho lớp TopicService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.bkap.services; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.bkap.entities.Topic; import com.bkap.models.TopicModel; import com.bkap.repositories.TopicRepository; @Service public class TopicService { @Autowired private TopicRepository repository; public List<TopicModel> getAll(){ return repository.findAll().stream().map(TopicModel::new).collect(Collectors.toList()); } public Topic insert(Topic topic) { return repository.save(topic); } public Topic update(Topic topic) { Optional<Topic> tp=repository.findById(topic.getTopicId()); if(tp.isPresent()) return repository.save(topic); else return null; } public TopicModel getById(Long topicId) { Optional<Topic> topic=repository.findById(topicId); if(topic.isPresent()) return new TopicModel(topic.get()); else return null; } public void delete(Long topicId) { repository.deleteById(topicId); } } |
- Code cho lớp BlogService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package com.bkap.services; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.bkap.entities.Blog; import com.bkap.models.BlogModel; import com.bkap.repositories.BlogRepository; @Service public class BlogService { @Autowired BlogRepository repository; public List<BlogModel> getAll(){ return repository.findAll().stream().map(BlogModel::new).collect(Collectors.toList()); } public List<BlogModel> getBlogs(Long topicId) { return repository.findByTopicId(topicId).stream().map(BlogModel::new).collect(Collectors.toList()); } public Boolean existTopic(Long topicId) { return repository.existsTopic(topicId); } public Blog insert(Blog blog) { return repository.save(blog); } public void delete(Long blogId) { repository.deleteById(blogId); } public Blog update(Blog blog) { Optional<Blog> temp=repository.findById(blog.getBlogId()); if(!temp.isPresent()) return null; return repository.save(blog); } public Blog getById(Long blogId) { Optional<Blog> blog=repository.findById(blogId); if(blog.isPresent()) return blog.get(); else return null; } } |
Bước 7: Code cho các lớp controller trong gói controllers
- Code cho lớp TopicController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
package com.bkap.controllers; import java.util.Date; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; 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.RestController; import com.bkap.entities.Topic; import com.bkap.models.TopicModel; import com.bkap.services.BlogService; import com.bkap.services.TopicService; import jakarta.validation.Valid; @RestController public class TopicController { @Autowired TopicService topicService; @Autowired BlogService blogService; @GetMapping("/topics") public ResponseEntity<List<TopicModel>> getAll() { return new ResponseEntity<>(topicService.getAll(), HttpStatus.OK); } @PostMapping("/topics") public ResponseEntity<Object> post(@RequestBody @Valid Topic topic, BindingResult result) { if (result.hasErrors()) return new ResponseEntity<>(result.getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST); else { topic.setCreateDate(new Date()); return new ResponseEntity<>(topicService.insert(topic), HttpStatus.OK); } } @GetMapping("/topics/{id}") public ResponseEntity<Object> get(@PathVariable Long id) { TopicModel topic = topicService.getById(id); if (topic == null) return new ResponseEntity<>("Không tồn tại topic", HttpStatus.BAD_REQUEST); else return new ResponseEntity<>(topic, HttpStatus.OK); } @PutMapping("/topics/{id}") public ResponseEntity<Object> put(@PathVariable Long id, @RequestBody @Valid Topic topic, BindingResult result) { if (topic.getTopicId() != id) return new ResponseEntity<>("Mã không khớp", HttpStatus.BAD_REQUEST); if (result.hasErrors()) return new ResponseEntity<>(result.getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST); else { topic.setCreateDate(new Date()); Topic tp = topicService.update(topic); if (tp == null) return new ResponseEntity<>("Không tồn tại mã cần sửa", HttpStatus.BAD_REQUEST); else return new ResponseEntity<>(tp, HttpStatus.OK); } } @DeleteMapping("/topics/{id}") public ResponseEntity<String> delete(@PathVariable Long id) { if(blogService.existTopic(id)) return new ResponseEntity<>("Topic đang có bài viết, không xóa được", HttpStatus.BAD_REQUEST); topicService.delete(id); return new ResponseEntity<>("Xóa thành công", HttpStatus.OK); } } |
- Code cho lớp BlogController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
package com.bkap.controllers; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; 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.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.bkap.entities.Blog; import com.bkap.models.BlogModel; import com.bkap.services.BlogService; import jakarta.validation.Valid; @RestController public class BlogController { // Cấu hình thư mục để lưu ảnh @Value("${upload.directory:src/main/resources/static/images}") private String uploadDir; @Autowired BlogService blogService; @GetMapping("/blogs") public ResponseEntity<List<BlogModel>> getBlogs() { return new ResponseEntity<>(blogService.getAll(), HttpStatus.OK); } @GetMapping("/blogs/{id}") public ResponseEntity<Object> getBlog(@PathVariable Long id) { Blog blog=blogService.getById(id); if(blog==null) return new ResponseEntity<>("Không tìm thấy Id",HttpStatus.NOT_FOUND); return new ResponseEntity<>(new BlogModel(blog), HttpStatus.OK); } @GetMapping("/topics/{id}/blogs") public ResponseEntity<List<BlogModel>> getBlogByTopic(@PathVariable Long id) { return new ResponseEntity<>(blogService.getBlogs(id), HttpStatus.OK); } @DeleteMapping("/blogs/{id}") public ResponseEntity<String> delete(@PathVariable Long id) { blogService.delete(id); return new ResponseEntity<>("Xóa thành công", HttpStatus.OK); } @PostMapping("/blogs") public ResponseEntity<Object> post( @Valid @ModelAttribute Blog blog, BindingResult result,@RequestPart MultipartFile file) { if (result.hasErrors()) return new ResponseEntity<>(result.getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST); else { //upload ảnh try { // upload ảnh if (!file.isEmpty()) { Path path=Paths.get(uploadDir,file.getOriginalFilename()); File imageFolder=new File(uploadDir); if(!imageFolder.exists()) imageFolder.mkdir(); System.out.println(path); Files.copy(file.getInputStream(),path); blog.setPicture("/images/"+file.getOriginalFilename()); } } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } blog.setCreateDate(new Date()); return new ResponseEntity<>(blogService.insert(blog), HttpStatus.OK); } } @PutMapping("/blogs/{id}") public ResponseEntity<Object> put( @Valid @ModelAttribute Blog blog, BindingResult result,@RequestPart MultipartFile file, @PathVariable Long id, String pictureOld) { if (blog.getBlogId() != id) return new ResponseEntity<>("Mã không khớp", HttpStatus.BAD_REQUEST); if (result.hasErrors()) return new ResponseEntity<>(result.getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST); else { //upload ảnh blog.setPicture(pictureOld); try { // upload ảnh if (!file.isEmpty()) { Path path=Paths.get(uploadDir,file.getOriginalFilename()); File imageFolder=new File(uploadDir); if(!imageFolder.exists()) imageFolder.mkdir(); System.out.println(path); Files.copy(file.getInputStream(),path); blog.setPicture("/images/"+file.getOriginalFilename()); } } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } blog.setCreateDate(new Date()); var bl=blogService.update(blog); if(bl==null) return new ResponseEntity<>("Không tồn tại mã cần sửa", HttpStatus.BAD_REQUEST); else return new ResponseEntity<>(bl, HttpStatus.OK); } } @GetMapping("/images/{filename}") public ResponseEntity<byte[]> getImage(@PathVariable String filename) throws IOException { // Đọc ảnh từ thư mục static/images Path path = Paths.get(uploadDir, filename); byte[] image = Files.readAllBytes(path); // Trả về ảnh // Định dạng MIME của ảnh (tùy thuộc vào loại ảnh, ví dụ JPEG, PNG, ...) String contentType = Files.probeContentType(path); // Trả về ảnh dưới dạng byte[] với header Content-Type thích hợp return ResponseEntity.ok() .header(HttpHeaders.CONTENT_TYPE, contentType) .body(image); } } |
Bước 8: Chạy ứng dụng và sử dụng Postman để kiểm tra kết quả
Source tải tại đây
Video quay trong buổi dạy C2308G
thay lời cảm ơn!
Các bài cũ hơn
- Giới thiệu Spring Boot Rest API (09:57 AM - 07/04/2025)
- Xử lý nghiệp vụ giỏ hàng-đặt hàng cơ bản trong Spring Boot (08:51 AM - 01/04/2025)
- Xây dựng chức năng đăng ký người dùng trong SpringBoot (03:50 PM - 29/03/2025)
- Bảo mật ứng dụng web với Spring Boot (09:12 AM - 28/03/2025)
- Tìm hiểu về Layout trong Thymeleaf (10:58 AM - 25/03/2025)