将 PostgreSQL 数据库添加到 Spring Boot 上的 RESTful 服务。第 1 部分 因此,在上一部分中,我们学习了如何在计算机上安装 PostgresSQL 数据库,在 pgAdmin 中创建数据库,以及如何手动和以编程方式在其中创建和删除表。在这一部分中,我们将重写我们的程序,以便它学习如何使用该数据库和表。为什么是我们?因为我自己正在和你们一起从这个材料中学习。然后,我们不仅会解决手头的任务,还会在更有经验的程序员的建议的帮助下纠正路上出现的错误。可以这么说,我们将学习如何在团队中工作;)首先,让我们
com.javarush.lectures.rest_example
在文件夹中创建一个新包并将其命名为repository
. 在此包中,我们将创建一个新界面ClientRepository
:
package com.javarush.lectures.rest_example.repository;
import com.javarush.lectures.rest_example.model.Client;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ClientRepository extends JpaRepository<Client, Integer> {
}
这个界面将“神奇地”与我们的数据库和表格进行交互。为什么神奇?因为我们不需要编写它的实现,Spring框架会提供给我们。你只需要创建这样一个界面,你就已经可以使用这个“魔法”了。下一步是Client
像这样编辑类:
package com.javarush.lectures.rest_example.model;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "phone")
private String phone;
//… getters and setters
}
我们在这个类中所做的只是添加一些注释。让我们来看看它们:
- @Entity - 表明这个bean(类)是一个实体。
- @Table - 指示将在该实体中显示的表的名称。
- @Id - 列id(主键 - 将用于确保当前表中数据的唯一性的值。注:Andrei)
- @Column - 指示映射到实体属性的列的名称。
- @GenerateValue - 表示该属性将根据指定的策略生成。
firstName
,那么我们将在表中命名该字段first_name
。这些注释可以直接在字段及其 getter 上设置。但如果您选择其中一种方法,请尝试在整个程序中保持这种风格。我使用第一种方法只是为了缩短列表。可以在此处找到更完整的数据库注释列表。现在我们去类中ClientServiceImpl
重写一下,如下:
package com.javarush.lectures.rest_example.service;
import com.javarush.lectures.rest_example.model.Client;
import com.javarush.lectures.rest_example.repository.ClientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private ClientRepository clientRepository;
@Override
public void create(Client client) {
clientRepository.save(client);
}
@Override
public List<Client> readAll() {
return clientRepository.findAll();
}
@Override
public Client read(int id) {
return clientRepository.getOne(id);
}
@Override
public boolean update(Client client, int id) {
if (clientRepository.existsById(id)) {
client.setId(id);
clientRepository.save(client);
return true;
}
return false;
}
@Override
public boolean delete(int id) {
if (clientRepository.existsById(id)) {
clientRepository.deleteById(id);
return true;
}
return false;
}
}
正如您从列表中看到的,我们所做的就是删除不再需要的行:
// Хранorще клиентов
private static final Map<Integer, Client> CLIENT_REPOSITORY_MAP = new HashMap<>();
// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
相反,我们声明了我们的接口ClientRepository
,并在其上方放置了@Autowired注释,以便 Spring 会自动将此依赖项添加到我们的类中。我们还将所有工作委托给这个接口,或者更确切地说是它的实现,Spring 将添加它。让我们进入最后也是最有趣的阶段 - 测试我们的应用程序。让我们打开 Postman 程序(请参阅此处如何使用它)并向此地址发送 GET 请求:http://localhost:8080/clients。我们得到这样的答案:
[
{
"id": 1,
"name": "Vassily Petrov",
"email": "vpetrov@jr.com",
"phone": "+7 (191) 322-22-33)"
},
{
"id": 2,
"name": "Pjotr Vasechkin",
"email": "pvasechkin@jr.com",
"phone": "+7 (191) 223-33-22)"
}
]
我们发送一个 POST 请求:
{
"name" : "Amigo",
"email" : "amigo@jr.com",
"phone" : "+7 (191) 746-43-23"
}
而且......我们发现了程序中的第一个错误:
{
"timestamp": "2020-03-06T13:21:12.180+0000",
"status": 500,
"error": "Internal Server Error",
"message": "could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/clients"
}
我们查看日志,发现如下错误:
org.postgresql.util.PSQLException: ОШИБКА: повторяющееся meaning ключа нарушает ограничение уникальности "clients_pkey"
Detail: Ключ "(id)=(1)" уже существует.
我们再次发送相同的 POST 请求,结果是相同的,但有一个区别:Ключ "(id)=(2)" уже существует.
我们第三次发送相同的请求,我们得到 Status: 201 Created。我们再次发送 GET 请求并收到响应:
[
{
"id": 1,
"name": "Vassily Petrov",
"email": "vpetrov@jr.com",
"phone": "+7 (191) 322-22-33)"
},
{
"id": 2,
"name": "Pjotr Vasechkin",
"email": "pvasechkin@jr.com",
"phone": "+7 (191) 223-33-22)"
},
{
"id": 3,
"name": "Amigo",
"email": "amigo@jr.com",
"phone": "+7 (191) 746-43-23"
}
]
这表明我们的程序忽略了这个表已经被预先填充的事实,并再次从 1 开始分配一个 id。好吧,错误是一个工作时刻,不要绝望,这种情况经常发生。因此,我会向更有经验的同事寻求帮助:“亲爱的同事,请在评论中指出如何解决这个问题,以便程序正常运行。” 帮助很快就到达了,斯塔斯·帕辛科夫在评论中告诉我需要朝哪个方向看。在此特别感谢他!但问题是,在课堂上我错误地指定了字段Client
注释的策略。 这种策略适合MySQL。如果我们使用 Oracle 或 PostrgeSQL,那么我们需要设置不同的策略。您可以在此处阅读有关主键策略的更多信息。我选择了 GenerationType.SEQUENCE 策略。为了实现它,我们需要稍微重写 initDB.sql 文件,当然还有 Client 类的 id 字段的注释。重写initDB.sql: @GeneratedValue(strategy = GenerationType.IDENTITY)
id
CREATE TABLE IF NOT EXISTS clients
(
id INTEGER PRIMARY KEY ,
name VARCHAR(200) NOT NULL ,
email VARCHAR(254) NOT NULL ,
phone VARCHAR(50) NOT NULL
);
CREATE SEQUENCE clients_id_seq START WITH 3 INCREMENT BY 1;
发生了什么变化:我们表的 id 列的类型发生了变化,稍后会详细介绍。我们在下面添加了一行,在其中创建了一个新序列clients_id_seq,指示它应以 3 开头(因为 populateDB.sql 文件中的最后一个 id 是 2),并指示增量应为 1。让我们回到 id 列类型。这里我们指定了INTEGER,因为如果我们离开SERIAL,序列将自动创建,具有相同的名称clients_id_seq,但将从1开始(这导致了程序错误)。但是,现在如果要删除表,则需要通过 pgAdmin 界面手动或使用以下命令通过 .sql 文件另外删除此序列:
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
但是,如果您不使用像 populateDB.sql 这样的文件来初始填充表,那么您可以使用 SERIAL 或 BIGSERIAL 类型作为主键,并且您不必手动创建序列,因此不必删除分开它。您可以在 的网站上阅读有关序列的更多信息。PostgreSQL 文档。让我们继续讨论id
类字段注释Client
并将其格式化如下:
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
我们做了什么:我们安装了一个新的注释@SequenceGenerator
来创建一个序列生成器,为其分配一个名称clientsIdSeq
,表明这是一个序列生成器clients_id_seq
,并添加了一个属性。allocationSize = 1
这是一个可选属性,但如果我们不这样做,当我们运行程序时,会出现以下错误:
org.hibernate.MappingException: The increment size of the [clients_id_seq] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1]
以下是用户Andrei在评论中对此的评论: allocationSize 主要是为了减少 hibernate 为“新 id”而访问数据库的次数。如果值 == 1,则对每个新实体进行休眠,在将其保存到数据库之前,“运行”到数据库中查找 id。如果该值> 1(例如5),hibernate将较少频率地联系数据库获取“新”id(例如5次),并且在联系时,hibernate将要求数据库保留这个号码(在我们的案例,5)值。您描述的错误表明 hibernate 希望接收 50 个默认 id,但在数据库中,您表示您已准备好仅根据第一个 id 为该实体颁发 id。用户Nikolya Kudryashov发现了另一个错误:如果您从原始文章 http://localhost:8080/clients/1 运行请求,将返回错误:
{
"timestamp": "2020-04-02T19:20:16.073+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.javarush.lectures.rest_example.model.Client$HibernateProxy$ZA2m7agZ[\"hibernateLazyInitializer\"])",
"path": "/clients/1"
}
这个错误与Hibernate的延迟初始化有关,为了消除它,我们需要在Client类中添加一个额外的注释:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
这样:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
现在让我们运行我们的程序(从数据库中删除客户端表(如果上次还存在的话)),并从 application.properties 文件中注释掉 3 行:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
上次我们只评论了最后一行,但是...... 由于我们已经创建并填写了表格,目前这对我来说似乎更合乎逻辑。让我们继续测试,通过 Postman 执行 GET、POST、PUT 和 DELETE 请求,我们将看到错误已经消失,一切正常。就这样,工作完成了。现在我们可以简要总结并思考我们所学到的内容:
- 在您的计算机上安装 PostgreSQL
- 在 pgAdmin 中创建数据库
- 手动和编程创建和删除表
- 通过 .sql 文件填充表
- 我们了解了 Spring 框架的“神奇”JpaRepository 接口
- 我们了解了创建此类程序时可能出现的一些错误
- 我们意识到向同事寻求建议不应该感到尴尬
- 我们已经确认 JavaRush 社区是一支始终能够提供救援的力量;)
com.javarush.lectures.rest_example
为com.javarush.lectures.rest.example
,以及项目的名称,以免违反 Java 中的命名约定。UPD2 用户Alexander PyanovClientRepository
建议,要初始化类中的字段,ClientServiceImpl
最好使用构造函数而不是注释@Autowired
。这是因为在极少数情况下您可以获得NullPointerException
,并且一般来说,这是最佳实践,我同意这一点。从逻辑上讲,如果一个对象的初始功能需要一个字段,那么最好在构造函数中初始化它,因为没有构造函数的类不会被组装成对象,因此,该字段将在阶段初始化的对象创建。我将添加一个带有更正的代码片段(需要用什么替换):
@Autowired
private ClientRepository clientRepository;
private final ClientRepository clientRepository;
public ClientServiceImpl(ClientRepository clientRepository) {
this.clientRepository = clientRepository;
}
链接到第一部分:将 PostgreSQL 数据库添加到 Spring Boot 上的 RESTful 服务。第 1 部分 PS 如果你们中的任何人想继续开发此教育应用程序,那么我将很高兴在本文中添加指向您的指南的链接。也许有一天,这个程序将发展成为类似于真正的业务应用程序的东西,您可以将工作添加到您的投资组合中。PPS 关于这篇谦虚的文章,我决定将这次笔试献给我们亲爱的女孩、妇女和女士们。谁知道,如果没有这个女人,也许现在就没有 Java,没有 JavaRush,就没有编程本质。祝贺您节日快乐,我们亲爱的聪明人!3 月 8 日快乐!快乐又美丽!
GO TO FULL VERSION