【问题标题】:What's the proper way of generating a unique field using Spring Boot / JPA?使用 Spring Boot / JPA 生成唯一字段的正确方法是什么?
【发布时间】:2018-02-16 01:58:12
【问题描述】:

首先,我不会考虑记录的主 ID。我说的是用户用来识别自动生成但用户可更改的记录的字段,不是顺序的,也不是 UUID。例如,从帐户实体开始:

@Entity
@Data
class Account {
    @Id
    @GeneratedValue
    private int id;

    @Column(unique=true)
    @NotNull
    private String slug;

    @Column
    private String name;
}

然后我简单地创建一个记录:

@Autowired
private AccountRepository accountRepository;

Account account = new Account();
account.setName("ACME");
accountRepository.saveAndFlush(account);

此时,slug 应该已经生成,或者完全随机生成,或者根据名称进行操作。应该怎么做?

我知道如果不锁定整个表,就不可能确保插入不会由于违反唯一性约束而导致异常。我实际上可以阻止整个表甚至让异常发生(您需要每秒大量请求才能在检查可用性和插入之间发生冲突)。

【问题讨论】:

  • java.util.UUID#randomUUID 怎么样?
  • 您为什么不覆盖AccountRepositorysave() 并在此处应用生成唯一属性的逻辑?为什么需要在回调中做呢?
  • @Pablo 你可以看看这里:stackoverflow.com/questions/13036159/…。我认为您应该决定是否要为数据访问层使用存储库或 DAO 模式。
  • 您并没有尝试对 Active Record 特定进行任何操作,但您的思维方式就像是在使用 Active Record。你很惊讶它比 Active Record 更难。您正在将您已完成并作为库发布的内容与您将使用具有不同设计理念的不同技术创建的内容进行比较。
  • 比较一个实现 (ActiveRecord) 与一个 API (JPA) 比较不稳定。 JPA 的任何实现——Hibernate、EclipseLink、OpenJPA,都可能确实提供您所追求的功能; JPA 规范只关注所有实现需要以兼容方式提供的 ORM 方面。您必须查看他们的文档才能了解真正可用的内容。

标签: java spring jpa


【解决方案1】:

我会像这样做一个单独的 Bean、助手或服务类。

public class SlugService {
    public String generateSlug(String slug)
    {                                                   
      if (accountRepo.getBySlug(slug) != null){ //check if it is already
        return slug
      } else {
       slug.append("-"); //whatever the syntax
       generateSlug();
      }
    }

    public String makeSlug()
    {
      String slug = split by " ", replace by "_"(accountObject.getName);
      generateSlug(slug)
    }   
}

调用 makeSlug();方法。

【讨论】:

    【解决方案2】:

    如果将 slug 与 Account 表分开并将其单独放入 (id, slug) 表中,则可以先生成 slug(重试直到成功),然后将 Account 与指向刚刚生成的 slug id。

    您无法在 @PrePersist 方法中实现此目的,因此每当您创建新的 Account 时,您的服务都需要创建 slug。然而,它确实简化了应用程序方面的事情(例如,您无需怀疑在持久化 Account 时违反了哪个约束)。

    根据您的其他代码,如果您采用乐观的方法,您还可以绕过锁定Account 表甚至Slug 表。

    创建新帐户的服务方法的伪代码示例(提供new Slug() 创建随机slug):

    @Autowired SlugRepository slugRepository;
    @Autowired AccountRepository accountRepository;
    
    public void createAccount(Account a) {
        Slug s = null;
        while(s == null) {
            try {
                s = slugRepository.save(new Slug());
            } catch(Exception e) {
            }
        }
        a.setSlug(s);
        accountRepository.save(a);
    }
    

    【讨论】:

    • 我认为当不需要时这会增加很多复杂性(另一个表)。但是,Account 实体将如何创建 Slug 条目呢?
    • 添加表不会产生复杂性。它创建了漂亮而有效的规范化(具有我在答案中提到的特权)。实体也不会创建其他实体。服务方法创建实体,所以你会有一个很好的AccountService.createAccount(),然后也会创建 slug。 CRUD 存储库方法仅用于简单操作。如果需要更复杂的逻辑,则需要合适的服务方法(当然可以使用SlugRepositoryAccountRepository
    • 我不同意。从语义上讲,slug 是一个帐户字段。额外的表增加了复杂性。无论如何,无论哪个类创建该 Slug 对象,都可以为创建帐户做同样的事情,因此,根本不需要单独的表。
    • @Pablo 从语义上讲,slug 是帐户的一部分,是的。你如何存储它是一个完全不同的野兽。如果您开始解释他们如何“设计错误的关系数据库”,因为您的应用程序模型并不相同,那么 DBA 会打败您。我假设您更习惯于通过编程语言(即 rails)处理数据库?我认为这里的问题是你从你的观点来看这个,希望你不会偏离你习惯的太多。这是一个不错的答案,没有黑客。如果有更好的(使用 JPA),我会告诉你。
    【解决方案3】:

    我可以想到 JPA callbacks 来生成 slug。在您的情况下,@PrePersist 可能很有用。

    也就是说,为什么在插入记录之前需要确保值可以通过选择获得,所以发生冲突的窗口很小?您确实对列有唯一约束,对吗?

    更新

    就个人而言,我更喜欢这样解决:

    1. 在生成 slug 时使用 JPA 回调 @PrePersist。用于随机 UUID 或时间戳以最大程度地减少冲突的可能性。不检查碰撞,因为可能性很小。
    2. 为用户生成的 slug 更新 Account 时,始终首先使用查询来检查冲突。此检查将在服务更新方法本身中发生。

    这样我就可以与数据库无关,也不必在实体或侦听器类中使用存储库/服务。

    【讨论】:

    • 是的,以减轻碰撞。可能会发生碰撞,如果我让异常发生,则处理起来会困难得多。一旦我减轻了冲突,它们就会变得如此罕见,以至于我从未见过它在拥有几十万用户的系统中发生过一次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多