首页 > 解决方案 > Spring Data Mongo - 在嵌入文档中应用唯一的组合字段

问题描述

我正在努力Spring Boot v2.1.3.RELEASE & Spring Data Mongo。在此示例中,我想对电子邮件和部门名称应用唯一性。email 和 deptName的组合必须是唯一的,并且有什么方法可以将电子邮件发送出去,因为它在每个数组对象中重复出现?

我在下面尝试过,但它不起作用!

@CompoundIndexes({
    @CompoundIndex(name = "email_deptName_idx", def = "{'email' : 1, 'technologyEmployeeRef.technologyCd' : 1}")
})

样本数据

{
    "_id" : ObjectId("5ec507c72d8c2136245d35ce"),
    ....
    ....
    "firstName" : "John",
    "lastName" : "Doe",
    "email" : "john.doe@gmail.com",
    .....
    .....
    .....
    "technologyEmployeeRef" : [ 
        {
            "technologyCd" : "john.doe@gmail.com",
            "technologyName" : "Advisory",
            ....
            .....
            "Status" : "A"
        }, 
        {
           "technologyCd" : "john.doe@gmail.com",
           "technologyName" : "Tax",
           .....
           .....
           "Status" : "A"
       }
    ],
    "phoneCodes" : [ 
        "+352"
    ],
    ....
    ....
}

技术.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document
public class Technology {
    @Indexed(name = "technologyCd", unique = true, sparse = true)
    private String technologyCd;

    @Indexed(name = "technologyName", unique = true, sparse = true)
    private String technologyName;
    private String status;
}

EmployeeTechnologyRef.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeTechnologyRef {
    private String technologyCd;
    private String primaryTechnology;
    private String status;
}

雇员.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document
@CompoundIndexes({
    @CompoundIndex(name="emp_tech_indx", def = "{'employeeTechnologyRefs.primaryTechnology' : 1, 'employeeTechnologyRefs.technologyCd' : 1}" ,unique = true, sparse = true)
})
public class Employee {
    private String firstName;
    private String lastName;
    private String email;
    private List<EmployeeTechnologyRef> employeeTechnologyRefs;
}

我使用了下面的代码,但它没有给我任何重复的错误。我们应该怎么做 ?

Technology java8 = Technology.builder().technologyCd("Java").technologyName("Java8").status("A").build();
Technology spring = Technology.builder().technologyCd("Spring").technologyName("Spring Boot2").status("A").build();
List<Technology> technologies = new ArrayList<>();
technologies.add(java8);
technologies.add(spring);

technologyRepository.saveAll(technologies);

EmployeeTechnologyRef t1 = EmployeeTechnologyRef.builder().technologyCd("Java").primaryTechnology("Y")
        .status("A")
        .build();
EmployeeTechnologyRef t2 = EmployeeTechnologyRef.builder().technologyCd("Spring").primaryTechnology("Y")
        .status("A")
        .build();
List<EmployeeTechnologyRef> employeeTechnologyRefs = new ArrayList<>();
employeeTechnologyRefs.add(t1);
employeeTechnologyRefs.add(t2);
employeeTechnologyRefs.add(t1);

Employee employee = Employee.builder().firstName("John").lastName("Kerr").email("john.kerr@gmail.com")
        .employeeTechnologyRefs(employeeTechnologyRefs).build();
employeeRepository.save(employee);

标签: mongodbspring-data-mongodb

解决方案


在 MongoDB 中,唯一索引可确保字段中的特定值不会出现在多个文档中。它不能保证一个值在单个文档中的数组中是唯一的。在 MongoDB 手册中进行了解释,其中讨论了唯一的多键索引。

因此,唯一索引将无法满足您的要求。它将防止单独的文档包含重复的组合,但它仍然允许单个文档包含数组中的重复值。

最好的选择是更改数据模型,以便将 technologyEmployeeRef 对象数组拆分为单独的文档。将其拆分为单独的文档将允许您使用唯一索引来强制唯一性。

此数据模型更改应采取的特定实现将取决于您的访问模式(这超出了此问题的范围)。


可以做到这一点的一种方法是创建一个 TechnologyEmployee 集合,该集合具有当前存在于 technologyEmployeeRef 数组中的所有字段。此外,此 TechnologyEmployee 集合将有一个字段,例如电子邮件,它允许您将其与 Employee 集合中的文档相关联。

样本员工文件

{
  ....
  ....
  "firstName" : "John",
  "lastName" : "Doe",
  "email" : "john.doe@gmail.com",
  .....
  .....
  .....
}

员工技术文档样本

{
  "email" : "john.doe@gmail.com",
  "technologyCd" : "Java",
  "technologyName" : "Java8",
  ....
  .....
  "status" : "A"
}

EmployeeTechnology 集合中的索引

{'email' : 1, 'technologyCd' : 1}, {unique: true}

这种方法的缺点是您需要从两个集合中读取所有数据。如果您很少需要同时从两个集合中检索数据,则此缺点可能不是什么大问题。如果您确实需要所有数据,可以通过使用索引来加快速度。使用索引,可以通过使用覆盖查询进一步加快速度。


另一种选择是对数据进行非规范化。您可以通过复制需要与技术数据同时访问的员工数据来做到这一点。

样本文件

[
  {
    ....
    "firstName" : "John",
    "lastName" : "Doe",
    "email" : "john.doe@gmail.com",
    .....
    "technologyCd" : "Java",
    "technologyName" : "Java8",
    ....
    "status" : "A"
  },
  {
    ....
    "firstName" : "John",
    "lastName" : "Doe",
    "email" : "john.doe@gmail.com",
    .....
    "technologyCd" : "Spring",
    "technologyName" : "Spring Boot2",
    ....
    "status" : "A"
  }
]

这篇 MongoDB 博客文章中,他们说

您只对经常读取的字段执行此操作,读取频率远高于更新频率,并且不需要强一致性,因为更新非规范化值更慢、更昂贵且不是原子的。


或者正如您已经提到的,保持数据模型不变并在应用程序端执行唯一性检查可能是有意义的。这可能会给你最好的读取性能,但它确实有一些缺点。首先,它会减慢写入操作,因为应用程序在更新数据库之前需要运行一些检查。

这可能不太可能,但也有可能你仍然会得到重复。如果有两个连续的请求将同一个 EmployeeTechnology 对象插入到数组中,那么第二个请求的验证可能会在第一个请求写入数据库之前完成(并通过)。我自己在我处理的应用程序中看到了类似的场景。即使应用程序正在检查唯一性,如果用户双击提交按钮,数据库中最终会出现重复条目​​。在这种情况下,在第一次单击时禁用按钮会大大降低风险。这个小风险可能是可以容忍的,具体取决于您的要求和重复条目的影响。


哪种方法最有意义在很大程度上取决于您的访问模式和要求。希望这可以帮助。


推荐阅读