Uma implementação de marcação simples com JPA

Uma implementação de marcação simples com JPA

1. Visão geral

A marcação é um padrão de design padrão que nos permite categorizar e filtrar itens em nosso modelo de dados.

Neste artigo, implementaremos a marcação usando Spring e JPA. Estaremos usando Spring Data para realizar a tarefa. Além disso, essa implementação será útil se você quiser usar o Hibernate.

Este é o segundo artigo de uma série sobre a implementação da marcação. Para ver como implementá-lo com Elasticsearch, váhere.

2. Adicionando Tags

First, we’re going to explore the most straightforward implementation of tagging: a List of Strings. Podemos implementar tags adicionando um novo campo à nossa entidade como este:

@Entity
public class Student {
    // ...

    @ElementCollection
    private List tags = new ArrayList<>();

    // ...
}

Observe o uso da anotaçãoElementCollection em nosso novo campo. Como estamos executando em frente a um armazenamento de dados, precisamos dizer a ele como armazenar nossas tags.

Se não adicionássemos a anotação, eles seriam armazenados em um único blob que seria mais difícil de trabalhar. Esta anotação cria outra tabela chamadaSTUDENT_TAGS (ou seja,<entity> _) que tornará nossas consultas mais robustas.

This creates a One-To-Many relationship between our entity and tags! Estamos implementando a versão mais simples de marcação aqui. Por causa disso, teremos potencialmente muitas tags duplicadas (uma para cada entidade que as possui). Falaremos mais sobre esse conceito mais tarde.

3. Criação de consultas

As tags nos permitem realizar algumas consultas interessantes em nossos dados. Podemos procurar entidades com uma tag específica, filtrar uma varredura de tabela ou até limitar quais resultados retornam em uma consulta específica. Vamos dar uma olhada em cada um desses casos.

3.1. Pesquisando Tags

O campotag que adicionamos ao nosso modelo de dados pode ser pesquisado de forma semelhante a outros campos em nosso modelo. Mantemos as tags em uma tabela separada ao criar a consulta.

Aqui está como pesquisamos uma entidade que contém uma tag específica:

@Query("SELECT s FROM Student s JOIN s.tags t WHERE t = LOWER(:tag)")
List retrieveByTag(@Param("tag") String tag);

Como as tags são armazenadas em outra tabela, precisamos JOIN-las em nossa consulta - isso retornará todas as entidadesStudent com uma tag correspondente.

Primeiro, vamos configurar alguns dados de teste:

Student student = new Student(0, "Larry");
student.setTags(Arrays.asList("full time", "computer science"));
studentRepository.save(student);

Student student2 = new Student(1, "Curly");
student2.setTags(Arrays.asList("part time", "rocket science"));
studentRepository.save(student2);

Student student3 = new Student(2, "Moe");
student3.setTags(Arrays.asList("full time", "philosophy"));
studentRepository.save(student3);

Student student4 = new Student(3, "Shemp");
student4.setTags(Arrays.asList("part time", "mathematics"));
studentRepository.save(student4);

A seguir, vamos testar e verificar se funciona:

// Grab only the first result
Student student2 = studentRepository.retrieveByTag("full time").get(0);
assertEquals("name incorrect", "Larry", student2.getName());

Retornaremos o primeiro aluno no repositório com a tagfull time. Isto é exatamente o que queríamos.

Além disso, podemos estender este exemplo para mostrar como filtrar um conjunto de dados maior. Aqui está o exemplo:

List students = studentRepository.retrieveByTag("full time");
assertEquals("size incorrect", 2, students.size());

Com um pouco de refatoração, podemos modificar o repositório para incluir várias tags como um filtro, para refinar ainda mais nossos resultados.

3.2. Filtrando uma consulta

Outra aplicação útil de nossa marcação simples é a aplicação de um filtro a uma consulta específica. Enquanto os exemplos anteriores também nos permitiram fazer a filtragem, eles trabalharam em todos os dados em nossa tabela.

Como também precisamos filtrar outras pesquisas, vejamos um exemplo:

@Query("SELECT s FROM Student s JOIN s.tags t WHERE s.name = LOWER(:name) AND t = LOWER(:tag)")
List retrieveByNameFilterByTag(@Param("name") String name, @Param("tag") String tag);

Podemos ver que essa consulta é quase idêntica à acima. Umtag nada mais é do que outra restrição para usar em nossa consulta.

Nosso exemplo de uso também parecerá familiar:

Student student2 = studentRepository.retrieveByNameFilterByTag(
  "Moe", "full time").get(0);
assertEquals("name incorrect", "moe", student2.getName());

Consequentemente, podemos aplicar a tagfilter a qualquer consulta nesta entidade. Isso dá ao usuário muita energia na interface para encontrar os dados exatos de que eles precisam.

4. Etiquetagem Avançada

Nossa implementação de marcação simples é um ótimo lugar para começar. Mas, devido ao relacionamento Um-para-Muitos, podemos ter alguns problemas.

Primeiro, acabaremos com uma tabela cheia de tags duplicadas. Isso não será um problema em projetos pequenos, mas sistemas maiores podem acabar com milhões (ou até bilhões) de entradas duplicadas.

Além disso, nosso modeloTag não é muito robusto. E se quiséssemos acompanhar quando a tag foi criada inicialmente? Em nossa implementação atual, não temos como fazer isso.

Finalmente, não podemos compartilhar nossotags entre vários tipos de entidade. Isso pode levar a ainda mais duplicação que pode afetar o desempenho do sistema.

Many-To-Many relationships will solve most of our problems. Para aprender a usar a anotação@manytomany, verifiquethis article (pois isso está além do escopo deste artigo).

5. Conclusão

A marcação é uma maneira simples e direta de consultar dados e, combinada com a Java Persistence API, temos um recurso de filtragem poderoso que é facilmente implementado.

Embora a implementação simples nem sempre seja a mais apropriada, destacamos as rotas a seguir para ajudar a resolver essa situação.

Como sempre, o código usado neste artigo pode ser encontrado emover on GitHub.