【问题标题】:Spring Data Mongo - apply unique combination fields in embedded documentSpring Data Mongo - 在嵌入文档中应用唯一的组合字段
【发布时间】:2020-09-08 22:26:57
【问题描述】:

我正在处理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"
    ],
    ....
    ....
}

Technology.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;
}

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

【问题讨论】:

  • 有一些关于嵌入文档中字段唯一索引的有用信息:How to set unique constraint for field in document nested in array?
  • 我已经浏览过了,许多其他类似的链接并不能解决我的问题
  • @prasad_ - 您能否在此处提供有关此查询的明确答案?因为这不能解决我的目的
  • 您的问题可以进行一些编辑。目前还不清楚你在问什么。 1)第一部分与第二部分不匹配。首先,您询问的是电子邮件和部门名称,然后您突然询问了 primaryTechnology 和 technologyCd。如果问题涉及一致的数据,问题会更清楚。 2) 你所说的“以任何方式发送电子邮件”是什么意思?您是否在询问如何通过一些清理查询从现有数据中删除该字段?您是否担心如果将其从数组中删除,可能无法在 2 个字段的组合中强制执行唯一性?
  • @jcarter - 我道歉了。我已经更正了所有细节

标签: mongodb spring-data-mongodb


【解决方案1】:

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

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

最好的选择是更改数据模型,以便将 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}

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


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

示例文档

[
  {
    ....
    "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"
  }
]

this MongoDB blog post,他们这么说

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


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

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


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

【讨论】:

  • 我同意我有这个选项,但为了带来数据,我需要继续调用三个不同的集合并合并数据以创建 DTO。如果用户尝试添加该员工已经存在的技术,我决定以编程方式处理。它是否适合 Mongo 建模来维护单独的集合以保持唯一性?谢谢你的好解决方案
  • 维护单独的集合是否有意义实际上取决于应用程序的访问模式和要求。您甚至可能会对获取数据的速度感到惊讶。另外,我在答案中添加了一些其他选项。
  • 同意!我喜欢,这很有意义
猜你喜欢
  • 2021-02-04
  • 2015-02-19
  • 2018-06-16
  • 2016-05-15
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多