在本文中,您将学习如何配置 Spring Boot 以使用 PostgreSQL 数据库并从头开始构建 RESTful CRUD API。
您还将了解 Spring Data JPA 和 Hibernate 如何与 PostgreSQL 一起使用。
我们将为 Quora 等问答应用程序编写 REST API。 Q&A 应用程序将有两个域模型 - Question
和 Answer
。由于一个问题可以有多个答案,我们将定义 Question
和 Answer
实体之间的一对多关系。
我们将首先引导项目并配置 PostgreSQL 数据库。之后,我们将定义用于从 PostgreSQL 访问数据的域模型和存储库。最后,我们将编写 REST API 并使用 Postman 测试这些 API。
酷,让我们开始吧!
您可以使用 Spring CLI 工具通过在终端中键入以下命令来引导 Spring Boot 项目。
$ spring init --name=postgres-demo --dependencies=web,jpa,postgresql,validation postgres-demo
或者,您可以按照以下说明使用 Spring Initializr Web 工具生成项目 -
就是这样!您现在可以将项目导入您喜欢的 IDE 并开始工作。
###项目的目录结构
我在这里包含了项目的完整目录结构供您参考。我们将在接下来的部分中一一创建所需的包和类。
第一件事 第一。让我们配置 Spring Boot 以使用 PostgreSQL 作为我们的数据源。你可以简单地通过在 src/main/resources/application.properties
文件中添加 PostgreSQL 数据库 url、用户名和密码来做到这一点 -
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres_demo
spring.datasource.username= rajeevkumarsingh
spring.datasource.password=
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
上述文件中的最后两个属性用于 Hibernate。它是 spring-data-jpa 附带的默认 JPA 供应商。
尽管 Hibernate 与数据库无关,但我们可以指定当前的数据库方言,让它为该数据库生成更好的 SQL 查询。
ddl-auto
属性用于根据应用程序中的实体类自动创建表。
域模型是映射到数据库中相应表的类。我们的应用程序中有两个主要的域模型 - Question
和 Answer
。这两个领域模型都有一些常见的审计相关字段,如 createdAt
和 updatedAt
。
最好在一个单独的基类中抽象出这些公共字段,这就是我们在这篇文章中要做的。我们将创建一个名为 AuditModel
的抽象类来保存这些字段。
此外,我们将使用 Spring Boot 的 JPA 审计功能在数据库中插入/更新特定实体时自动填充 createdAt
和 updatedAt
值。
以下 AuditModel
类将被其他实体扩展。它包含 @EntityListeners(AuditingEntityListener.class)
注释,当实体被持久化时,它会自动填充 createdAt
和 updatedAt
值。
package com.example.postgresdemo.model;
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 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;
// Getters and Setters (Omitted for brevity)
}
请注意,要启用 JPA 审计,您需要将 @EnableJpaAuditing
注释添加到配置类之一。所以打开主类 PostgresDemoApplication.java
并像这样添加 @EnableJpaAuditing
注释 -
package com.example.postgresdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class PostgresDemoApplication {
public static void main(String[] args) {
SpringApplication.run(PostgresDemoApplication.class, args);
}
}
以下是 Question
实体。它映射到数据库中名为 questions
的表。
package com.example.postgresdemo.model;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Entity
@Table(name = "questions")
public class Question extends AuditModel {
@Id
@GeneratedValue(generator = "question_generator")
@SequenceGenerator(
name = "question_generator",
sequenceName = "question_sequence",
initialValue = 1000
)
private Long id;
@NotBlank
@Size(min = 3, max = 100)
private String title;
@Column(columnDefinition = "text")
private String description;
// Getters and Setters (Omitted for brevity)
}
请注意,我使用 @SequenceGenerator
来生成问题的 id
。您还可以通过指定 @GeneratedValue(strategy=GenerationType.IDENTITY)
注释来使用 PostgreSQL SERIAL
列。但是 SequenceGenerator 在这种情况下表现更好。
以下是 Answer
类的定义。它包含一个 @ManyToOne
注释,以声明它与 Question
实体具有多对一的关系。
package com.example.postgresdemo.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
@Table(name = "answers")
public class Answer extends AuditModel {
@Id
@GeneratedValue(generator = "answer_generator")
@SequenceGenerator(
name = "answer_generator",
sequenceName = "answer_sequence",
initialValue = 1000
)
private Long id;
@Column(columnDefinition = "text")
private String text;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "question_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonIgnore
private Question question;
// Getters and Setters (Omitted for brevity)
}
以下存储库将用于访问数据库中的问题和答案。
package com.example.postgresdemo.repository;
import com.example.postgresdemo.model.Question;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {
}
package com.example.postgresdemo.repository;
import com.example.postgresdemo.model.Answer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AnswerRepository extends JpaRepository<Answer, Long> {
List<Answer> findByQuestionId(Long questionId);
}
最后,让我们在控制器中编写 REST API,用于对问题和答案执行 CRUD 操作。
package com.example.postgresdemo.controller;
import com.example.postgresdemo.exception.ResourceNotFoundException;
import com.example.postgresdemo.model.Question;
import com.example.postgresdemo.repository.QuestionRepository;
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 QuestionController {
@Autowired
private QuestionRepository questionRepository;
@GetMapping("/questions")
public Page<Question> getQuestions(Pageable pageable) {
return questionRepository.findAll(pageable);
}
@PostMapping("/questions")
public Question createQuestion(@Valid @RequestBody Question question) {
return questionRepository.save(question);
}
@PutMapping("/questions/{questionId}")
public Question updateQuestion(@PathVariable Long questionId,
@Valid @RequestBody Question questionRequest) {
return questionRepository.findById(questionId)
.map(question -> {
question.setTitle(questionRequest.getTitle());
question.setDescription(questionRequest.getDescription());
return questionRepository.save(question);
}).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId));
}
@DeleteMapping("/questions/{questionId}")
public ResponseEntity<?> deleteQuestion(@PathVariable Long questionId) {
return questionRepository.findById(questionId)
.map(question -> {
questionRepository.delete(question);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId));
}
}
package com.example.postgresdemo.controller;
import com.example.postgresdemo.exception.ResourceNotFoundException;
import com.example.postgresdemo.model.Answer;
import com.example.postgresdemo.repository.AnswerRepository;
import com.example.postgresdemo.repository.QuestionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
public class AnswerController {
@Autowired
private AnswerRepository answerRepository;
@Autowired
private QuestionRepository questionRepository;
@GetMapping("/questions/{questionId}/answers")
public List<Answer> getAnswersByQuestionId(@PathVariable Long questionId) {
return answerRepository.findByQuestionId(questionId);
}
@PostMapping("/questions/{questionId}/answers")
public Answer addAnswer(@PathVariable Long questionId,
@Valid @RequestBody Answer answer) {
return questionRepository.findById(questionId)
.map(question -> {
answer.setQuestion(question);
return answerRepository.save(answer);
}).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId));
}
@PutMapping("/questions/{questionId}/answers/{answerId}")
public Answer updateAnswer(@PathVariable Long questionId,
@PathVariable Long answerId,
@Valid @RequestBody Answer answerRequest) {
if(!questionRepository.existsById(questionId)) {
throw new ResourceNotFoundException("Question not found with id " + questionId);
}
return answerRepository.findById(answerId)
.map(answer -> {
answer.setText(answerRequest.getText());
return answerRepository.save(answer);
}).orElseThrow(() -> new ResourceNotFoundException("Answer not found with id " + answerId));
}
@DeleteMapping("/questions/{questionId}/answers/{answerId}")
public ResponseEntity<?> deleteAnswer(@PathVariable Long questionId,
@PathVariable Long answerId) {
if(!questionRepository.existsById(questionId)) {
throw new ResourceNotFoundException("Question not found with id " + questionId);
}
return answerRepository.findById(answerId)
.map(answer -> {
answerRepository.delete(answer);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Answer not found with id " + answerId));
}
}
当在数据库中找不到问题或答案时,问答 REST API 会抛出 ResourceNotFoundException
。以下是 ResourceNotFoundException
类的定义 -
package com.example.postgresdemo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
异常类包含一个 @ResponseStatus(HttpStatus.NOT_FOUND)
注释,用于告诉 Spring Boot 在抛出此异常时以 404 NOT FOUND
状态进行响应。
我们已经完成了 REST API 的构建。是时候运行应用程序并测试这些 API 了。
启动终端并从应用程序的根目录键入以下命令以运行它 -
mvn spring-boot:run
应用程序将在默认端口 8080
上启动。
以下为creenshots 向您展示了如何使用 Postman 测试 API。
POST /questions
GET /questions?page=0&size=2&sort=createdAt,desc
POST /questions/{questionId}/answers
GET /questions/{questionId}/answers
您可以类似地测试其他 API。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.callicoder.com/spring-boot-jpa-hibernate-postgresql-restful-crud-api-example/
内容来源于网络,如有侵权,请联系作者删除!