A Simple Tagging Implementation with JPA – 用JPA实现一个简单的标签

最后修改: 2018年 2月 24日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Tagging is a standard design pattern that allows us to categorize and filter items in our data model.

标签是一种标准的设计模式,允许我们在数据模型中对项目进行分类和过滤。

In this article, we’ll implement tagging using Spring and JPA. We’ll be using Spring Data to accomplish the task. Furthermore, this implementation will be useful if you want to use Hibernate.

在这篇文章中,我们将使用Spring和JPA实现标记。我们将使用Spring Data来完成这一任务。此外,如果你想使用Hibernate,这个实现也会很有用。

This is the second article in a series on implementing tagging. To see how to implement it with Elasticsearch, go here.

这是关于实现标签的系列文章中的第二篇。要了解如何使用Elasticsearch实现它,请访问这里

2. Adding Tags

2.添加标签

First, we’re going to explore the most straightforward implementation of tagging: a List of Strings. We can implement tags by adding a new field to our entity like this:

首先,我们将探索标签的最直接的实现:字符串列表。我们可以通过向我们的实体添加一个新的字段来实现标签,就像这样。

@Entity
public class Student {
    // ...

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

    // ...
}

Notice the use of the ElementCollection annotation on our new field. Since we’re running in front of a data store, we need to tell it how to store our tags.

注意在我们的新字段上使用了ElementCollection注解。由于我们是在数据存储的前面运行,我们需要告诉它如何存储我们的标记。

If we didn’t add the annotation, they’d be stored in a single blob which would be harder to work with. This annotation creates another table called STUDENT_TAGS (i.e., <entity>_<field>) which will make our queries more robust.

如果我们不添加注解,它们就会被存储在一个单一的blob中,这样就更难操作了。这个注释创建了另一个名为STUDENT_TAGS的表(即<entity>_<field>),这将使我们的查询更加稳健。

This creates a One-To-Many relationship between our entity and tags! We’re implementing the simplest version of tagging here. Because of this, we’ll potentially have a lot of duplicate tags (one for each entity that has it). We’ll talk more about this concept later.

这在我们的实体和标签之间创建了一个一对多的关系!我们在这里实现了最简单的标签版本。正因为如此,我们可能会有很多重复的标签(每个实体都有一个)。我们稍后会进一步讨论这个概念。

3. Building Queries

3.建立查询

Tags allow us to perform some interesting queries on our data. We can search for entities with a specific tag, filter a table scan, or even limit what results come back in a particular query. Let’s take a look at each of these case.

标签允许我们对我们的数据进行一些有趣的查询。我们可以搜索带有特定标签的实体,过滤表的扫描,甚至限制在特定查询中出现的结果。让我们来看看这些案例中的每一个。

3.1. Searching Tags

3.1.搜索标签

The tag field we added to our data model can be searched similar to other fields on our model. We keep the tags in a separate table when building the query.

我们添加到数据模型中的tag字段可以与我们模型上的其他字段类似进行搜索。在建立查询时,我们将标签保存在一个单独的表中。

Here is how we search for an entity containing a specific tag:

下面是我们如何搜索包含特定标签的实体。

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

Because the tags are stored in another table, we need to JOIN them in our query – this will return all of the Student entities with a matching tag.

因为标签存储在另一个表中,我们需要在查询中对它们进行连接 – 这将返回所有具有匹配标签的Student实体。

First, let’s set up some test data:

首先,让我们设置一些测试数据。

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);

Next, let’s test it and make sure it works:

接下来,让我们测试一下,确保它能工作。

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

We’ll get back the first student in the repository with the full time tag. This is exactly what we wanted.

我们将拿回资源库中第一个带有full time标签的学生。这正是我们想要的。

In addition, we can extend this example to show how to filter a larger dataset. Here is the example:

此外,我们可以扩展这个例子来展示如何过滤更大的数据集。下面是这个例子。

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

With a little refactoring, we can modify the repository to take in multiple tags as a filter so we can refine our results even more.

通过一点重构,我们可以修改资源库,将多个标签作为一个过滤器,这样我们就可以更加细化我们的结果。

3.2. Filtering a Query

3.2.对查询进行过滤

Another useful application of our simple tagging is applying a filter to a specific query. While the previous examples also allowed us to do filtering, they worked on all of the data in our table.

我们的简单标签的另一个有用的应用是对一个特定的查询应用过滤器。虽然前面的例子也允许我们做过滤,但它们对我们表中的所有数据都有效。

Since we also need to filter other searches, let’s look at an example:

由于我们也需要过滤其他搜索,让我们看一个例子。

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

We can see that this query is nearly identical to the one above. A tag is nothing more than another constraint to use in our query.

我们可以看到,这个查询与上面的查询几乎是一样的。一个tag只不过是在我们的查询中使用的另一个约束条件。

Our usage example is also going to look familiar:

我们的使用例子也会看起来很熟悉。

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

Consequently, we can apply the tag filter to any query on this entity. This gives the user a lot of power in the interface to find the exact data they need.

因此,我们可以将标签filter应用于该实体的任何查询。这使用户在界面上有了很大的权力,可以找到他们需要的确切数据。

4. Advanced Tagging

4.高级标签

Our simple tagging implementation is a great place to start. But, due to the One-To-Many relationship, we can run into some issues.

我们的简单标签实现是一个很好的开始。但是,由于 “一对多 “的关系,我们会遇到一些问题。

First, we’ll end up with a table full of duplicate tags. This won’t be a problem on small projects, but larger systems could end up with millions (or even billions) of duplicate entries.

首先,我们最终会得到一个充满重复标签的表格。这对小项目来说不是问题,但更大的系统可能最终会有数百万(甚至数十亿)的重复条目。

Also, our Tag model isn’t very robust. What if we wanted to keep track of when the tag was initially created? In our current implementation, we have no way of doing that.

另外,我们的Tag模型不是很健全。如果我们想跟踪标签最初是什么时候创建的呢?在我们目前的实现中,我们没有办法做到这一点。

Finally, we can’t share our tags across multiple entity types. This can lead to even more duplication that can impact our system performance.

最后,我们不能在多个实体类型中共享我们的tags。这可能会导致更多的重复,影响我们的系统性能。

Many-To-Many relationships will solve most of our problems. To learn how to use the @manytomany annotation, check out this article (since this is beyond the scope of this article).

多对多的关系将解决我们的大部分问题。要学习如何使用@manytomany注解,请查看这篇文章(因为这已经超出了本文的范围)。

5. Conclusion

5.结论

Tagging is a simple and straightforward way to be able to query data and combined with the Java Persistence API, we’ve got a powerful filtering feature that is easily implemented.

标签是一种简单明了的方式,能够查询数据,结合Java Persistence API,我们得到了一个强大的过滤功能,很容易实现。

Although the simple implementation may not always be the most appropriate, we’ve highlighted the routes to take to help resolve that situation.

虽然简单的实施不一定是最合适的,但我们已经强调了帮助解决这种情况的路线。

As always, the code used in this article can be found on over on GitHub.

一如既往,本文中使用的代码可以在GitHub上找到。

Next »

An Advanced Tagging Implementation with JPA

« Previous

A Simple Tagging Implementation with Elasticsearch