Skip to main content

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.

src/main/resources/application.properties
# 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.

src/main/kotlin/meu/pacote/model/CustomerModel.kt
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.

src/main/kotlin/meu/pacote/repository/CustomerRepository.kt
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.

src/main/kotlin/meu/pacote/controller/service/CustomerService.kt
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.


Fluxo de comunicação no POST


  1. 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.

  2. O Controller fala com PostCustomerRequest para obter os dados do body do HTTP e salva numa variável chamada customer. O objeto PostCustomerRequest contém atributos como name e email.

  3. O Controller chama customerService para o método .create, passando para ele customer.toCustomerModel(), que é uma extensão que converte o objeto PostCustomerRequest em um objeto CustomerModel.

    A variável customerService é uma instância da classe CustomerService, nesse contexto, representando uma injeção de dependência do CustomerService no construtor da classe CustomerController.

  4. Já o método .create de customerService começa fazendo uma injeção de dependência para a classe CustomerModel (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 tipo CustomerModel, por isso a gente converte os dados no passo 3. A variável customer é 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 tipo CustomerModel para não haver problemas.

  5. Com os dados do POST no formato correto, o CustomerService.create aciona customerRepository.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.

src/main/kotlin/meu/pacote/controller/request/PostCustomerRequest.kt
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.

src/main/kotlin/meu/pacote/controller/CustomerController.kt
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.

src/main/kotlin/meu/pacote/controller/service/CustomerService.kt
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.

src/main/kotlin/meu/pacote/controller/model/CustomerModel.kt
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.

src/main/kotlin/meu/pacote/repository/CustomerRepository.kt
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.


Fontes


https://spring.io/guides/gs/accessing-data-mysql