【问题标题】:Orchestrating Spring Boot CrudRepositories with foreign key relationships使用外键关系编排 Spring Boot CrudRepositories
【发布时间】:2017-09-10 12:12:30
【问题描述】:

我正在编写一个 Spring Boot 应用程序,它将使用 Hibernate/JPA 在应用程序和 MySQL 数据库之间持久化。

这里我们有以下 JPA 实体:

@MappedSuperclass
public abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonIgnore
    private Long id;

    @Type(type="uuid-binary")
    private UUID refId;
}

@Entity(name = "contacts")
@AttributeOverrides({
        @AttributeOverride(name = "id", column=@Column(name="contact_id")),
        @AttributeOverride(name = "refId", column=@Column(name="contact_ref_id"))
})
public class Contact extends BaseEntity {
    @Column(name = "contact_given_name")
    private String givenName;

    @Column(name = "contact_surname")
    private String surname;

    @Column(name = "contact_phone_number")
    private String phone;
}

@Entity(name = "assets")
@AttributeOverrides({
        @AttributeOverride(name = "id", column=@Column(name="asset_id")),
        @AttributeOverride(name = "refId", column=@Column(name="asset_ref_id"))
})
public class Asset extends BaseEntity {
    @Column(name = "asset_location")
    private String location;
}

@Entity(name = "accounts")
@AttributeOverrides({
    @AttributeOverride(name = "id", column=@Column(name="account_id")),
    @AttributeOverride(name = "refId", column=@Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
    private Contact contact;

    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "asset_id", referencedColumnName = "asset_id")
    private Asset asset;

    @Column(name = "account_code")
    private String code;
}

还有@RestController,其中将发布Account 实例(待创建):

public interface AccountRepository extends CrudRepository<Account, Long> {
    @Query("FROM accounts where account_code = :accountCode")
    public Account findByCode(@Param("accountCode") String accountCode);
}

@RestController
@RequestMapping(value = "/accounts")
public class AccountController {
    @Autowired
    private AccountRepository accountRepository;

    @RequestMapping(method = RequestMethod.POST)
    public void createNewAccount(@RequestBody Account account) {
        // Do some stuff maybe

        accountRepository.save(account);
    }
}

所以这里的想法是“Account JSON”将被发送到这个控制器,在那里它将被反序列化为Account 实例并(以某种方式)持久化到支持的 MySQL。我担心的是:Account 是几个其他实体的组合(通过外键)。我需要:

  • 要么为这些实体中的每一个创建CrudRepository impls,然后编排对这些存储库的save(...) 调用,以便在“外部”Account 实体之前首先保存“内部实体”?或
  • 我是否只保存Account 实体(通过AccountRepository.save(account)),Hibernate/JPA 会自动为我创建所有内部/依赖实体?

在这两种情况下,代码/解决方案会是什么样子?当BaseEntity#id 是数据库中的自动递增 PK 时,我们如何为它指定值?

【问题讨论】:

    标签: java hibernate jpa spring-boot spring-data-jpa


    【解决方案1】:

    这取决于您的设计和特定用例,以及您希望保持何种程度的灵活性。这两种方式都在实践中使用。

    在大多数 CRUD 情况下,您宁愿保存帐户并让 Hibernate 保存整个图表(第二个选项)。这里你通常有另一个你没有提到的情况,它正在更新图形,你可能会这样做,实际上 Spring 的存储库 save 方法可以做到这一点:如果实体是新的(瞬态) 一,它坚持它,否则它合并它。

    您需要做的就是告诉 Hibernate 将所需的实体生命周期操作从 Account 级联到相关实体:

    @Entity
    ...
    public class Account extends ... {
        @OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
        ...
        private Contact contact;
    
        @OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
        ...
        private Asset asset;
    
        ...
    }
    

    但是,在合并操作的情况下,您会从数据库中重新加载对象图,但如果您希望一切都自动完成,Hibernate 没有其他方法可以检查实际更改的内容,除了将其与当前进行比较数据库中的状态。

    级联操作总是被应用,所以如果你想要更多的灵活性,你显然必须手动处理这些事情。在这种情况下,您将省略级联选项(这是您当前的代码),并按照不破坏任何完整性约束的顺序手动保存和更新对象图的各个部分。

    虽然涉及一些样板代码,但手动方法可让您在更复杂或对性能要求更高的情况下灵活处理,例如当您不想加载或重新初始化您知道它们未更改的分离图部分时保存它的一些上下文。

    例如,假设存在用于更新帐户、联系人和资产的单独 Web 服务方法的情况。在帐户方法的情况下,使用级联选项,您需要加载整个帐户图表,只是为了合并帐户本身的更改,尽管联系人和资产没有更改(或者更糟糕的是,取决于您的操作方式,您可能如果您只使用帐户中包含的分离实例,则在此同时恢复其他人在其专用方法中对其所做的更改。

    关于自动生成的 id,您不必自己指定它们,只需从已保存的实体中获取它们(Hibernate 会将其设置在那里)。如果您打算之后使用更新的实体,那么获取存储库的save 方法的结果很重要,因为merge 操作总是返回传入实例的合并副本,并且如果有任何新持久的关联实体更新后分离图中的实例,它们的 id 将在副本中设置,原始实例不会被修改。

    【讨论】:

      猜你喜欢
      • 2017-03-08
      • 2020-05-17
      • 2021-06-30
      • 2019-03-09
      • 1970-01-01
      • 2020-09-03
      • 2021-08-24
      • 1970-01-01
      • 2018-04-30
      相关资源
      最近更新 更多