Estou usando a seguinte interface para fazer a persistência de dados na minha aplicação:
@Repository
public interface PessoaRepository extends JpaRepository<Pessoa, Integer> {
Pessoa findById(int id);
List<Pessoa> findAll();
**String findByName(String name);**
}
Eu li que é uma boa prática implementar essa interface numa classe service. Sobrescreveria os métodos da seguinte maneira:
@Service
public class PessoaService implements pessoaRepository {
@Autowired
PessoaRepository pessoaRepository;
@Override
public Pessoa findById(int id) {
pessoaRepository.Pessoa findById(int id);
}
*...Implementação dos outros métodos...*
}
O problema é: como vou implementar o método findByName? Pois o método findById já vem com o JpaRepository.
Ramon, acredito que vc interpretou errado a boa prática.
Primeiro porque não faz sentido nós, os programadores, implementarmos os repositórios porque quem implementa por nós automáticamente é o Spring Data.
Segundo que a boa prática, até onde entendo, diz pra gente dividir as camadas. Os repositórios ficam só com o acesso ao banco, os controladores lidam com as requisições e, no meio destes dois, nós temos os serviços.
Então vc teria um cenário assim:
@Repository
interface PessoaRepository extends JpaRepository<Pessoa, Integer> {
Pessoa findById(int id);
List<Pessoa> findAll();
String findByName(String name);
}
@Service
class PessoaService {
@Autowired
private PessoaRepository repository;
List<Pessoa> findAll() {
// Aqui vai qualquer lógica de negócio
return repository.findAll();
}
}
@RestController
class PessoaController {
@Autowired
private PessoaService service;
@GetMapping
List<Pessoa> index() {
return service.findAll();
}
}
Atente para o comentário indicando o lugar da lógica de negócio. Com esta configuração evitamos que o controlador faça mais do que deveria.
Uma coisa comum também é o seguinte: Ao invés de criar PessoaService
como uma classe concreta, vc pode separa em 2 partes, a interface e a implementação. Neste caso seria assim:
interface PessoaService {
List<Pessoa> findAll();
}
@Service
class PessoaServiceImpl implements PessoaService {
@Autowired
private PessoaRepository repository;
@Override
public List<Pessoa> findAll() {
// Aqui vai qualquer lógica de negócio
return repository.findAll();
}
}
Mesmo separando desta forma, o código do seu controlador não mudará, ele continuará dependendo de PessoaService
e o Spring se encarregará de encontrar sua implementação e injetá-la corretamente.
1 curtida
Eu fiz o que voce fez. Criei uma interface “Pessoaservice”, implementei em uma classe implementation. Para usar no controller, por exemplo, eu uso a interface(anotação autowired num atributo private, tudo certinho) PessoaService para persistir os dados. Mas me gera esse erro aqui:
- Field UserEntityService in br.com.InitialPopulation required a bean of type ‘br.com.service.UserEntityService’ that could not be found.
- The injection point has the following annotations:
-
@org.springframework.beans.factory.annotation.Autowired(required=true)
OBS: quando troco a interface “PessoaService” por "PessoaRepository a persistência funciona.
Do jeito como vc descreveu eu não faço ideia do que pode ter dado de errado, mas se vc mandar o código certinho eu posso tentar descobrir.
Segue os códigos das interfaces e classes abaixo abaixo:
OBS - 1: estou usando o JUnit para testes
//interface repository
@Repository
public interface ArticleRepository extends JpaRepository<Article, Integer> {
Article findByTitle(String title);
@Query("select p from Article p where p.title like %?1%")
List<Article> findAllByTitle(String title);
}
//interface service
public interface ArticleService {
Optional<Article> findById(Integer id);
Article findByTitle(String title);
List<Article> findAll();
List<Article> findAllByTitle(String title);
Article save(Article article);
}
//classe service implementation
@Service
public class ArticleServiceImpl implements ArticleService{
@Autowired
ArticleRepository articleRepository;
@Override
public Optional<Article> findById(Integer id) {
return articleRepository.findById(id);
}
@Override
public Article findByTitle(String title) {
return articleRepository.findByTitle(title);
}
@Override
public List<Article> findAll() {
return articleRepository.findAll();
}
@Override
public Article save(Article article) {
return articleRepository.save(article);
}
@Override
public List<Article> findAllByTitle(String title) {
return articleRepository.findAllByTitle(title);
}
}
//classe teste
@DataJpaTest
@DisplayName("Classe de teste do ArticleService")
public class ArticleServiceTest {
@Autowired
private ArticleService articleService;
@Test
@DisplayName("Metodo save persiste um novo aluno no banco de dados quando bem sucedido")
void save_Persist_WhenSuccessful() {
final Article result = articleService.save(new Article("article 1", "autor 1", LocalDate.of(1998, 05, 12), "O lorem ipsum é um texto bastante conhecido dos profissionais de design e editoração. Trata-se de um poema em latim, escrito por Cícero em 45 a.C, com as letras embaralhadas. É utilizado no mundo todo para preencher espaços em publicações antes da inserção do conteúdo real."));
assertAll(
() -> assertThat(result).isNotNull(),
() -> assertThat(result.getAuthor()).isNotBlank(),
() -> assertThat(result.getTitle()).isNotBlank(),
() -> assertThat(result.getText()).isNotBlank(),
() -> assertThat(result.getText()).hasSizeGreaterThan(200),
() -> assertThat(result.getDate()).asInstanceOf(InstanceOfAssertFactories.LOCAL_DATE)
);
}
...outros métodos
Entendi. Se vc não estiver usando a anotação SpringBootTest
, o JUnit não injeta os beans automaticamente mesmo. Vc tem que ver como configurar para ele injetar para vc.
Ramon, achei uma possível solução para vc bem aqui:
https://stackoverflow.com/a/41084739/3334365
Basicamente ele orienta a usar a anotação @Import
na sua classe de teste. Eu testei aqui e deu certo, minha classe de teste ficou assim:
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DataJpaTest
@Import(ArticleServiceImpl.class)
class DemoApplicationTests {
@Autowired
private ArticleService articleService;
@Test
void test() {
assertTrue(articleService.findAll().isEmpty());
}
}
Perceba que no @Import
eu passei a classe que Implementa ArticleService
, mas ali no @Autowired
eu usei a própria interface ArticleService
.
Obrigado. Então no meu código normal(a aplicação real, usando o banco de dados real) eu não preciso dessa anotação “import” correto?
Não deveria precisar.
Normalmente vc teria uma classe anotada com @SpringBootApplication
e só isso já bastaria. Mesmo que não tenha, há outras formas de lidar.
1 curtida
Amigo @wldomiciano, Poderia me tirar uma dúvida sobre testes. Seguindo essa organização de código que você me informou(interface repository, interface service e uma classe implementation). Digamos que eu vá fazer testes(com o objetivo de saber se meus métodos estão funcionando) qual devo testar? Tipo, eu testo o implementation, ou o service ou o repository?
Vc não vai testar sua interface service porque ela só teria métodos abstratos. Vc testaria apenas a classe que implementa esta interface.
Sobre testar o repository, eu sei que é importante testar quando vc tem queries complexas. Vc não precisaria testar um findById
, por exemplo. Mas eu ainda não sei muito sobre boas práticas de testes, então é bom vc pesquisar mais a fundo sobre isso.
1 curtida
Mas tem diferença entre testar a interface repository e a serviceImpl? Não são a mesma coisa, quero dizer, elas não persistem os dados no banco?
Desculpa pelo tanto de pergunta. Mas tenho outra duvida, na interface service vi la em cima que você coloca o método “save” para retornar um objeto, não poderia colocar esse método para retornar nada(void)? tem diferença entre retornar e não retornar nada?
Nem sempre ServiceImpl
e Repository
farão a mesma coisa.
Em sistemas onde a lógica de negócio é simples, pode até ser ServiceImpl
apenas repasse as chamadas para o Repository
sem fazer nada mais.
Porém, há situações em que vc precisa fazer calculos, ajustes e validações antes de salvar os dados no banco. É neste cenário que ter um ServiceImpl
vale mais a pena.
Por exemplo, digamos que na sua regra de negócio não é permitido o cadastro de usuários com menos de 18 anos. Vc faria a verificação de idade no service.
class ServiceImpl implements Service {
void save(String userName, int userAge) {
if (userAge < 18) {
throw new UserAgeNotAllowedException();
} else {
this.repository.save(new User(userName, userAge));
}
}
}
O que eu já vi sobre testar o service e testar o repository é que quando vc for testar o repository, vai precisar levantar um banco de dados, nem que seja um H2, para isso vc usaria o @DataJpaTest
.
E quando vc for testar o service, vc vai focar apenas na lógica adicional que este service faz. Para isso vc apenas mockaria o repository ao invés de acessar um banco de dados real. Mas claro que tudo depende do seu cenário e das suas necessidades.
Poderia usar void. Tudo depende do que vc precisa.
Imagina uma situação em que o cliente da sua API quer cadastrar um certo item e, se o cadastro for bem sucedido, ele precisa saber o id com o qual este item foi cadastrado.
Como é o banco que vai gerar este id, o jeito mais fácil de o cliente ter esta informação imediatamente é se eu retorná-la após o salvamento. Foi este cenário que eu imaginei quando pensei naquele código.