Kotlin
Camada Repository e Spring Data
A camada Repository é à camada responsável por fornecer uma interface entre o código da aplicação e a fonte de dados, como um banco de dados. Essa camada é responsável por executar operações de leitura e gravação de dados no banco de dados, encapsulando a lógica de acesso aos dados.
Spring Data (projeto do Spring Framework) facilita a criação desses Repository, o acesso a dados de bancos de dados relacionais e não relacionais é realizado por meio de uma abordagem baseada em interfaces e convenções. Ele fornece uma camada de abstração sobre o mecanismo de acesso a dados subjacente, simplificando o desenvolvimento de operações de persistência de dados.
Na maioria dos casos, não precisamos conhecer sintaxe do banco de dados em uso, o Spring Data abstrai essa parte e podemos usar um mesmo código para vários tipos de banco de dados, devendo apenas conhecer a sintaxe do Spring Data (em alguns casos será necessário fazer coisas manuais, mas em casos específicos).
Configurando o banco de dados
Antes de sair usando o projeto do Spring Data JPA, devemos incluir ele como uma dependência do nosso projeto. Dessa forma, poderemos facilmente usar recursos oferecidos pelo Spring Data JPA no seu aplicativo Spring Boot.
Primeiro nas dependências, precisamos adicionar: spring-boot-starter-data-jpa
No Maven ficaria assim:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Agora vamos adicionar o driver do banco de dados, para que o Spring Data saiba como interagir com o banco de dados. Não se esqueça de definir como runtime
para seja necessário apenas durante a execução do aplicativo, deixando ele fora durante a compilação do código. No Maven ficaria assim (Exemplo com Postgresql):
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
Agora vamos configurar o application.properties
para o Spring Boot usar o banco de dados PostgreSQL.
# Nome da aplicação Spring Boot
spring.application.name=myname
## DataBase
# Define a URL de conexão com o banco de dados PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/myname
# Define o nome de usuário do banco de dados:
spring.datasource.username=postgres
# Define a senha do usuário do banco de dados
spring.datasource.password=postgres
# Define o dialeto do Hibernate para PostgreSQL. Isso é necessário para que o Hibernate gere as consultas SQL corretamente para o PostgreSQL:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# A cada reinicialização da aplicação, o esquema do banco de dados será criado e, em seguida, descartado quando a aplicação for desligada:
spring.jpa.hibernate.ddl-auto=create-drop
# Habilita a exibição de consultas SQL executadas pelo Hibernate no console:
spring.jpa.show-sql=true
Para não ficar criando o banco de dados, podemos fazer:
spring.datasource.url=jdbc:postgresql://localhost:5432/myname?createDatabaseIfNotExist=true
.
Criando nossa Entity
Para conectar nosso código com o banco de dados precisamos definir uma @Entity
. Uma entidade (entity) indica que uma classe está associada a uma tabela no banco de dados, ou seja, uma @Entity
para uma classe chamada dbAccess
significa que no banco de dados existe uma tabela chamada dbAccess.
package meu.pacote.model
import javax.persistence.*
@Entity(name = "tableName")
data class tableNameModel(
@Id // Primary Key da tabela!
@GeneratedValue(strategy = GenerationType.IDENTITY) // Auto incremento para o campo ID. IDENTITY = cada tabela vai ter seu próprio ID.
var id: Int? = null,
@Column // indica que é uma coluna na tabela
var name: String,
@Column // indica que é uma coluna na tabela
var email: String
)
Criando nosso Repository
Como já foi explicado, o Repository é responsável por executar operações de leitura e gravação de dados no banco de dados.
package meu.pacote.repository
import meu.pacote.model.CustomerModel // "Modelo do banco"
import org.springframework.data.repository.CrudRepository // importa o CrudRepository para simplificar a realização de operações CRUD (Create, Read, Update, Delete)
interface CustomerRepository : CrudRepository<CustomerModel, Int> { // Int = tipo do ID usado
fun findByNameContaining(name: String): List<CustomerModel>
}
// : List<CustomerModel>: Indica que o método retornará uma lista de objetos CustomerModel.
Melhorando nosso Service
Agora vamos melhorar nosso service para salvar no banco.
package meu.pacote.service
import meu.pacote.model.CustomerModel // "Modelo do banco"
import meu.pacote.repository.CustomerRepository // importa a interface de comunicação com o banco
import org.springframework.stereotype.Service
import java.lang.Exception
@Service
class CustomerService(
val customerRepository: CustomerRepository // CustomerRepository é a interface de comunicação com o banco
) {
fun getAll(name: String?): List<CustomerModel> {
name?.let { // Se nao for vazio, executa o cod abaixo:
return customerRepository.findByNameContaining(it)
}
return customerRepository.findAll().toList()
}
fun create(customer: CustomerModel) { // 'customer' representa o modelo do banco
customerRepository.save(customer) // 'customerRepository' é a interface com o banco que chama '.save' para gravar info no banco.
}
fun getCustomer(id: Int): CustomerModel { // retorna m objeto do tipo CustomerModel
return customerRepository.findById(id).orElseThrow()
// .findById(id): Tenta encontrar um registro de cliente no banco de dados com base no 'id' fornecido.
// .orElseThrow(): Se um cliente for encontrado não faz nada e o código continua processando com o objeto CustomerModel recuperado, caso contrário, lança uma exceção.
}
fun update(customer: CustomerModel) { // 'customer' representa o modelo do banco
if(!customerRepository.existsById(customer.id!!)){ // Se o ID não existir (!! = temos certeza que ID não é null)
throw Exception() // lança uma exceção
}
customerRepository.save(customer) // Se tiver tudo certo, "atualiza" os dados.
}
fun delete(id: Int) {
if(!customerRepository.existsById(id)){ // Se o ID não existir (ID do banco)
throw Exception() // lança uma exceção
}
customerRepository.deleteById(id) // Deleta o ID da tabela
}
}
Fluxo da comunicação
Vamos ver como é o fluxo de comunicação durante um Post para gravar dados no banco. A imagem abaixo demonstra como é realizado o fluxo da comunicação durante um POST.
O cliente faz uma requisição POST para o servidor, enviando os dados no corpo da requisição, a configuração do POST está dentro do
Controller
.O Controller fala com
PostCustomerRequest
para obter os dados do body do HTTP e salva numa variável chamadacustomer
. O objetoPostCustomerRequest
contém atributos comoname
eemail
.O Controller chama
customerService
para o método.create
, passando para elecustomer.toCustomerModel()
, que é uma extensão que converte o objetoPostCustomerRequest
em um objetoCustomerModel
.A variável
customerService
é uma instância da classeCustomerService
, nesse contexto, representando uma injeção de dependência doCustomerService
no construtor da classeCustomerController
.Já o método
.create
decustomerService
começa fazendo uma injeção de dependência para a classeCustomerModel
(ele é nossa entity) que é nosso modelo de tabela dentro do banco de dados. Isso tudo acontece por causa dessa linha:fun create(customer: CustomerModel) {
.Isso é necessário porque
CustomerService.create
precisa receber um dado que seja do tipoCustomerModel
, por isso a gente converte os dados no passo 3. A variávelcustomer
é uma instância do modelo do banco de dados que foi convertida no passo 3, aqui só definimos que ele tem realmente que ser do tipoCustomerModel
para não haver problemas.Com os dados do POST no formato correto, o
CustomerService.create
acionacustomerRepository.save
(que é a interface de comunicação com o banco) passando os dados para serem gravados no banco de dados.
PostCustomerRequest
O arquivo abaixo é o modelo para requisições do tipo post.
package meu.pacote.controller.request
import meu.pacote.model.CustomerModel
data class PostCustomerRequest (
var name: String,
var email: String
)
CustomerController
Vou deixar apenas os campo relacionados ao POST.
package meu.pacote.controller
import meu.pacote.controller.request.PostCustomerRequest
import meu.pacote.extension.toCustomerModel
import meu.pacote.model.CustomerModel
import meu.pacote.service.CustomerService
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("customer")
class CustomerController(
val customerService : CustomerService // injeção de dependência do 'CustomerService' no construtor da classe 'CustomerController'
) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun create(@RequestBody customer: PostCustomerRequest) { // customer recebe os dados extraídos do body da requisição HTTP.
customerService.create(customer.toCustomerModel()) // usando o '.create' da classe 'CustomerService'
}
}
CustomerService
Abaixo segue o arquivo com a classe CustomerService.
package meu.pacote.service
import meu.pacote.model.CustomerModel // "Modelo do banco"
import meu.pacote.repository.CustomerRepository // importa a interface de comunicação com o banco
import org.springframework.stereotype.Service
import java.lang.Exception
@Service
class CustomerService(
val customerRepository: CustomerRepository // faz composição de classe, onde 'CustomerRepository' é a interface de comunicação com o banco
) {
fun create(customer: CustomerModel) { // 'customer' recebe o modelo do banco
customerRepository.save(customer) // 'customerRepository' é a interface com o banco que chama '.save' para gravar info no banco.
}
fun getCustomer(id: Int): CustomerModel {
return customerRepository.findById(id).orElseThrow()
}
fun update(customer: CustomerModel) {
if(!customerRepository.existsById(customer.id!!)){
throw Exception()
}
customerRepository.save(customer)
}
fun delete(id: Int) {
if(!customerRepository.existsById(id)){
throw Exception()
}
customerRepository.deleteById(id)
}
}
CustomerModel
Segue o modelo da tabela do banco de dados.
package meu.pacote.model
import javax.persistence.*
@Entity(name = "mytable")
data class CustomerModel(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null,
@Column
var name: String,
@Column
var email: String
)
CustomerRepository
Segue o repository, que é a interface com banco de dados.
package meu.pacote.repository
import meu.pacote.model.CustomerModel // "Modelo do banco"
import org.springframework.data.repository.CrudRepository // importa o CrudRepository para simplificar a realização de operações CRUD (Create, Read, Update, Delete)
interface CustomerRepository : CrudRepository<CustomerModel, Int> { // Int = tipo do ID usado
fun findByNameContaining(name: String): List<CustomerModel>
}
// : List<CustomerModel>: Indica que o método retornará uma lista de objetos CustomerModel.