Introdução
REST API REST 100%功能性PostgreSQL数据库,Spring数据JPA参数,作为咨询机构,DTO数据包,Duals数据集,dadas总线数据集, MockMVC和Swagger的形式化文档,集成了有效的Nossos端点证明。
完全免费的Github播客:
Clone no Github
Dependências
正如dependênciasna nossaaplicaçãosão:
- 春季靴冬眠春季数据JPA模拟MVC昂首阔步
Todasserãotratadas pelo Maven,segundo o pom.xml abaixo:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
io.springfox
springfox-swagger2
2.6.1
io.springfox
springfox-swagger-ui
2.6.1
junit
junit
test
org.hamcrest
hamcrest-core
test
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-devtools
runtime
org.postgresql
postgresql
Spring boot
春季靴子是必需的,但必须由执行官签发,但必须由执行官负责,并必须执行生产性保护措施。
Java的Para isso basta criarmos uma classe com ométodomain:
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Hibernate e Spring data JPA
春季数据联合使用的冬眠和冬日课程。
Primeiramente,vamos criar uma classe Produto.java可以用作在地图上实现现实的必要工具。
As anotações de @ApiModelProperty serão usadas para a documentação com o Swagger, que veremos mais a frente.
@Entity
@Table(name = "produto")
public class Produto {
@Id
@SequenceGenerator(name = "produto_seq", sequenceName = "produto_seq", allocationSize = 1)
@GeneratedValue(generator = "produto_seq", strategy = GenerationType.AUTO)
private int id;
private String nome;
private double valor;
@ApiModelProperty(notes = "Identificador do produto")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@ApiModelProperty(notes = "Nome do produto")
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
@ApiModelProperty(notes = "Valor do produto")
public double getValor() {
return valor;
}
public void setValor(double valor) {
this.valor = valor;
}
}
Depois criaremos um'Repository',春季数据,JPA que facilita ainda mais,作为banco de dados顾问,alémde serútilmais a frente durante os证明人,ondeserápossívelcriar unsrepositóbandecaçãoque n dados durante essa etapa。
没有储存库,乌鲁木齐,多米尼加共和国找到所有()您可以通过以下方式访问您的网站:Passando um objeto depaginação,somente pelo nome do medodo,seráinferido uma busca por todos os elementos,e ummétodode busca com uma uma e a customizada。
O 'Repository' são interfaces, que o Spring irá tratar como injeção de dependências quando forem invocadas mais a frente.
@Repository
public interface ProdutoRepository extends PagingAndSortingRepository {
public Page findAll(Pageable pageable);
@Query("SELECT p FROM Produto p "
+ "WHERE lower(nome) like %:busca% ")
public Page busca(@Param("busca") String busca, Pageable pageable);
}
编辑arquivo application.properties,然后在com上进行配置,在api上配置,然后在caminhopadrão和modo deinicialização中进行配置。 不使用PostgreSQL的Devemos criartambémo banco de dados e um esquema。
#debug
debug=true
#api
server.servlet.context-path=/api
#conexão
javax.persistence.create-database-schemas=true
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/teste
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.properties.hibernate.default_schema=api
#define como o hibernate irá se comportar quanto a criação do esquema
#create: Apaga e recria todo o esquema
#update: Atualiza o mapeamento
spring.jpa.hibernate.ddl-auto=update
#define se irá executar o 'data.sql'
#always: Sempre irá executar o data.sql
#never: Nunca irá executar o data.sql
spring.datasource.initialization-mode=never
#codificação do data.sql
spring.datasource.sqlScriptEncoding=UTF-8
Controlador REST
Para disponibilizar nossa API publica,PromotoController.java para colocar todos os端点引用了essa class。 E com作为春季必不可少的东西。
阿诺塔@RequestMapping(“ / produtos”)irádefinir o caminhopadrãodesse端点,nesse caso / produtos。
准入门槛的端点许可,反作用的Ang角,precisamos habilitar或CORS(跨来源资源共享),一个反义词@CrossOrigin。
As anotações @Api e @ApiOperation servirão para documenta pelo swagger, que veremos mais a frente.
@RestController
@RequestMapping("/produtos")
@CrossOrigin
@Api(tags = "Produtos", description = "API de produtos")
public class ProdutoController {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Autowired
ProdutoRepository produtoRepository;
...
}
阿西玛(Acima),控制权的基础设施建设,必要的备件,生产资料的备件,生产资料的储备金,春天的依赖关系。
GET
可以在vamos crimar的首要端点处找到要处理的产品,并通过@GetMapping()通知响应者来获取GET请求。 Defino osparâmetrosque essa URL,podo receber,todos opcionais,para que possa paginar,ordenar e fazer uma busca。
@ApiOperation(value = "Lista os produtos")
@GetMapping()
public Page listar(
@RequestParam(
value = "page",
required = false,
defaultValue = "0") int page,
@RequestParam(
value = "size",
required = false,
defaultValue = "10") int size,
@RequestParam(
value = "sort",
required = false) String sort,
@RequestParam(
value = "q",
required = false) String q
) {
Pageable pageable = new PageableFactory(page, size, sort).getPageable();
Page resultPage;
if (q == null) {
resultPage = produtoRepository.findAll(pageable);
} else {
resultPage = produtoRepository.busca(q.toLowerCase(), pageable);
}
return resultPage;
}
Preciso criar um objeto Pageable para passar para o repositório, para isso criei uma fábrica para facilitar essa criação, a PageableFactory.
URL / api / produtos的示例性咨询:
{
"content": [
{
"id": 2,
"nome": "Processador Intel Core i7-9700K",
"valor": 2454
},
{
"id": 3,
"nome": "Headset Gamer HyperX Cloud Stinger - HX-HSCS-BK/NA ",
"valor": 189.37
},
{
"id": 4,
"nome": "Teclado Mecânico Gamer HyperX Mars, RGB, Switch Outemu Bluem, US - HX-KB3BL3-US/R4 ",
"valor": 284.11
},
{
"id": 5,
"nome": "Mouse Logitech M90 Preto 1000DPI ",
"valor": 26.9
},
{
"id": 6,
"nome": "Gabinete C3Tech Gamer ATX sem Fonte Preto MT-G50BK",
"valor": 119.6
},
{
"id": 7,
"nome": "Headphone Edifier Bluetooth W800BT Preto",
"valor": 250
},
{
"id": 8,
"nome": "Kindle Novo Paperwhite, 8GB, Wi-Fi, Preto - AO0705 ",
"valor": 418.99
},
{
"id": 9,
"nome": "SSD Kingston A400, 240GB, SATA, Leitura 500MB/s, Gravação 350MB/s - SA400S37/240G ",
"valor": 166
},
{
"id": 10,
"nome": "HD Seagate BarraCuda, 1TB, 3.5´, SATA - ST1000DM010",
"valor": 290
},
{
"id": 11,
"nome": "Cadeira Gamer DT3sports GT, Black - 10293-5",
"valor": 552.41
}
],
"pageable": {
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"pageSize": 10,
"pageNumber": 0,
"offset": 0,
"unpaged": false,
"paged": true
},
"last": false,
"totalPages": 2,
"totalElements": 19,
"numberOfElements": 10,
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"first": true,
"size": 10,
"number": 0,
"empty": false
}
可能的外部建议
- http://127.0.0.1:8080/api/produtos?page=2
- http://127.0.0.1:8080/api/produtos?page=1&size=30
- http://127.0.0.1:8080/api/produtos?q=teclado
- http://127.0.0.1:8080/api/produtos?sort=valor,asc
- http://127.0.0.1:8080/api/produtos?sort=valor,desc
- http://127.0.0.1:8080/api/produtos?page=0&size=3&ort=valor,desc&q=intel
在端点列表上添加端点列表,然后在端点上添加一个示例:
@ApiOperation(value = "Busca um produto pelo id")
@GetMapping(value = "/{id}")
public ResponseEntity listar(@PathVariable Integer id) {
Optional rastreador = produtoRepository.findById(id);
if (!rastreador.isPresent()) {
return ApiError.notFound("Produto não encontrado");
}
return new ResponseEntity<>(rastreador.get(), HttpStatus.OK);
}
POST e PUT
Proparatoer ou atualizar um novo produto,vamos primeiramente criar uma classe ProdutoDTO.java。
O padrão DTO (Data transfer object) é uma classe que representa a entidade com apenas os atributos necessários para serem expostos publicamente, no nosso exemplo apenas preciso, na criação ou atualização, informar no nome e valor do produto, e nunca o seu id, por isso a sua classe de DTO não possui esse atributo
public class ProdutoDTO {
private String nome;
private Double valor;
@ApiModelProperty(notes = "Nome do produto")
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public Double getValor() {
return valor;
}
@ApiModelProperty(notes = "Valor do produto")
public void setValor(Double valor) {
this.valor = valor;
}
}
Sendo assim,或其他代理人:
@ApiOperation(value = "Cria um novo Produto")
@PostMapping()
public ResponseEntity criar(@RequestBody ProdutoDTO dto, UriComponentsBuilder ucBuilder) {
try {
//Crio um objeto da entidade preenchendo com os valores do DTO e validando
Produto produto = new Produto();
if (dto.getNome() == null || dto.getNome().length() < 2) {
return ApiError.badRequest("Informe o nome do produto");
}
produto.setNome(dto.getNome());
if (dto.getValor() == null || dto.getValor() <= 0) {
return ApiError.badRequest("Valor do produto inválido");
}
produto.setValor(dto.getValor());
Produto novo = produtoRepository.save(produto);
//Se ocorreu algum erro, retorno esse erro para a API
if (novo == null) {
return ApiError.badRequest("Ocorreu algum erro na criação do produto");
}
//Se foi criado com sucesso, retorno o objeto criado
return new ResponseEntity<>(novo, HttpStatus.CREATED);
} catch (Exception e) {
LOGGER.error("Erro ao criar um produto", e);
return ApiError.internalServerError("Ocorreu algum erro na criação do produto");
}
}
产品信息和徽标,徽标,密码,密码,示例,密码:
{
"nome": "Teclado Microsoft",
"valor": 124
}
Ainda nessemétodo,根据需要确认的面额,要求保留的权利,以及最终证明书或原始产品的永久证明。
没有最终的评论。 Esse processo pode ser visto abaixo。
产品的身份信息会引起混乱,并通知他人。
@ApiOperation(value = "Atualiza um Rastreador Equipamento")
@PutMapping(value = "/{id}")
public ResponseEntity atualizar(@PathVariable("id") int id, @RequestBody ProdutoDTO dto) {
try {
Optional produtoAtual = produtoRepository.findById(id);
if (!produtoAtual.isPresent()) {
return ApiError.notFound("Produto não encontrado");
}
if (dto.getNome() != null) {
if (dto.getNome().length() < 2) {
return ApiError.badRequest("Nome do produto inválido");
}
produtoAtual.get().setNome(dto.getNome());
}
if (dto.getValor() != null) {
if (dto.getValor() <= 0) {
return ApiError.badRequest("Valor do produto inválido");
}
produtoAtual.get().setValor(dto.getValor());
}
//Atualizo o objeto utilizando o repositório
Produto atualizado = produtoRepository.save(produtoAtual.get());
//Se ocorreu algum erro, retorno esse erro para a API
if (atualizado == null) {
return ApiError.internalServerError("Erro na atualização do produto");
}
//Se foi criado com sucesso, retorno o objeto atualizado
return new ResponseEntity<>(atualizado, HttpStatus.CREATED);
} catch (Exception e) {
LOGGER.error("Erro ao atualizar um produto", e);
return ApiError.internalServerError("Erro na atualização do produto");
}
}
DELETE
一个简单的批注,简单,可验证的版本,一个可删除的版本库。
@ApiOperation(value = "Remove um produto")
@DeleteMapping(value = "/{id}")
public ResponseEntity deletar(@PathVariable Integer id) {
Optional produto = produtoRepository.findById(id);
if (!produto.isPresent()) {
return ApiError.notFound("Produto não encontrado");
} else {
produtoRepository.deleteById(id);
}
return new ResponseEntity<>(HttpStatus.OK);
}
Documentação com Swagger
O自动化,API作为动态DTO的类的控制对象。
请将SwaggerConfig com类作为必要的信息,以确保控制人员的需要。
@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurationSupport {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(apis())
.paths(PathSelectors.any())
.build();
}
private Predicate apis() {
return RequestHandlerSelectors.basePackage("br.com.paulocollares.api.controladores.rest");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SPRING REST API")
.description("Documentação das APIs REST")
.contact(new Contact("pcollares", "www.paulocollares.com.br", null))
.build();
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Acessando URL /api/swagger-ui.html,podemos ver adocumentaçãogerada,como no exemplo abaixo。
No projeto há uma classe, MainController, que redireciona a requisição da raiz para essa página, ou seja, se acessar http://127.0.0.1:8080/api/ é redirecionado para http://127.0.0.1:8080/api/swagger-ui.html.
Testes
Para testar todos esses endpoins vamos usar o MockMVC para automatizar esse processo。 EleSerá负责提供发票和发票。
MockMVC的基础测试产品目录。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
ProdutoController.class
})
public class ProdutoTest {
//URL base para acesso desse controlador
private final String BASE_URL = "/produtos";
//Instância do ObjectMapper para trabalhar com JSON
private ObjectMapper objectMapper;
//Controlador REST tratado por meio de injeção de dependências
@Autowired
private ProdutoController restController;
//Instância do MockMVC
private MockMvc mockMvc;
//Instância do mock repository
@MockBean
private ProdutoRepository mockRepository;
@Before
public void setUp() {
objectMapper = new ObjectMapper();
mockMvc = MockMvcBuilders
.standaloneSetup(restController)
.build();
}
...
}
验证协议的最终目的是什么,验证协议的必要性,验证协议的有效性,验证请求的有效性 o终结点端点。
@Test
public void buscar_id_200() throws Exception {
Produto produto = new Produto();
produto.setId(1);
produto.setNome("Teste");
produto.setValor(10.0);
when(mockRepository.findById(1)).thenReturn(Optional.of(produto));
mockMvc.perform(get(BASE_URL + "/1"))
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.nome", is("Teste")))
.andExpect(jsonPath("$.valor", is(10.0)));
verify(mockRepository, times(1)).findById(1);
}
Essa mesmalógicaseráusada nos outros端点。
@Test
public void buscar_id_404() throws Exception {
mockMvc.perform(get(BASE_URL + "/2")).andExpect(status().isNotFound());
}
@Test
public void criar_200() throws Exception {
ProdutoDTO dto = new ProdutoDTO();
dto.setNome("Teste");
dto.setValor(11.0);
Produto produto = new Produto();
produto.setId(1);
produto.setNome(dto.getNome());
produto.setValor(dto.getValor());
when(mockRepository.save(any(Produto.class))).thenReturn(produto);
mockMvc.perform(post(BASE_URL)
.content(objectMapper.writeValueAsString(dto))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.nome", is("Teste")))
.andExpect(jsonPath("$.valor", is(11.0)));
verify(mockRepository, times(1)).save(any(Produto.class));
}
@Test
public void atualizar_200() throws Exception {
ProdutoDTO dto = new ProdutoDTO();
dto.setNome("Teste");
dto.setValor(11.0);
Produto produto = new Produto();
produto.setId(1);
produto.setNome(dto.getNome());
produto.setValor(dto.getValor());
when(mockRepository.findById(1)).thenReturn(Optional.of(produto));
when(mockRepository.save(any(Produto.class))).thenReturn(produto);
mockMvc.perform(put(BASE_URL + "/1")
.content(objectMapper.writeValueAsString(dto))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id", is(1)));
}
@Test
public void deletar_200() throws Exception {
Produto produto = new Produto();
produto.setId(1);
when(mockRepository.findById(1)).thenReturn(Optional.of(produto));
mockMvc.perform(delete(BASE_URL + "/1"))
.andExpect(status().isOk());
verify(mockRepository, times(1)).deleteById(1);
}
Conclusão
Mostrei nesse post um simples exemplo de uma API REST com Spring, o código completo pode ser encontrado no Github: https://github.com/pcollares/api-rest-spring
Referências
Lista de links comreferênciaspara todos os assuntos abordados nesse post。
Conceitos
- https://restfulapi.net/resource-naming/
- https://blog.caelum.com.br/rest-principios-e-boas-praticas/
Spring
- https://spring.io/guides/gs/rest-service/
- https://blog.algaworks.com/como-criar-web-services-restful-com-spring-boot/
- https://www.mkyong.com/tutorials/spring-boot-tutorials/
- https://blog.algaworks.com/injecao-de-dependencias-com-spring/
Spring data JPA
- https://www.mkyong.com/spring-boot/spring-boot-spring-data-jpa-postgresql/
- https://www.mkyong.com/spring-boot/spring-boot-spring-data-jpa/
- https://domineospring.wordpress.com/2015/05/11/facilite-seus-daos-com-o-spring-data-jpa/
MockMVC
- http://marcelotozzi.com/teste/java/2014/09/10/usar-o-mockmvc-nos-testes-do-spring-e-mais-maneiro.html
- https://www.baeldung.com/integration-testing-in-spring
- https://howtodoinjava.com/spring-boot2/spring-boot-mockmvc-example/
- http://blog.marcnuri.com/mockmvc-spring-mvc-framework/
Swagger
- http://www.marcelferry.com.br/tutoriais/documentando-suas-api-com-swagger-usando-springfox/
- https://dzone.com/articles/swagger-generation-with-spring-boot
- https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
DTO
- https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
- https://medium.com/@msealvial/blindando-sua-api-spring-boot-com-o-padr%C3%A3o-dto-44f97020d1a0
Paginação e ordenação
- https://dzone.com/articles/pagination-and-sorting-with-spring-data-jpa
[]的