使用外键关系编排Spring Boot CrudRepositories

时间:2017-04-14 08:36:41

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

我正在编写一个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;
}

@RestControllerAccount实例将被POST(待创建):

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

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

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

在任一场景中,代码/解决方案会是什么样子?当数据库中的自动递增PK时,我们如何指定BaseEntity#id的值?

1 个答案:

答案 0 :(得分:5)

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

在大多数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;

    ...
}

但是,在合并操作的情况下,您需要支付从db重新加载对象图的惩罚,但是如果您希望自动完成所有操作,Hibernate除了将其与当前内容进行比较之外没有其他方法来检查实际发生了什么变化。 db中的状态。

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

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

例如,我们假设有一个单独的Web服务方法来更新帐户,联系人和资产。在帐户方法的情况下,使用级联选项,您需要加载整个帐户图表以合并帐户本身的更改,尽管联系人和资产不会更改(或者更糟糕的是,取决于您的操作方式,您可能如果您只是使用帐户中包含的分离实例,请在此处恢复由其他人在其专用方法中对其进行的更改。

关于自动生成的ID,您不必自己指定它们,只需从保存的实体中获取它们(Hibernate会将其设置在那里)。如果您计划之后使用更新的实体,那么获取存储库的save方法的结果非常重要,因为merge操作始终返回传入实例的合并副本,如果有的话更新的分离图中新保留的关联实体实例,其ID将在副本中设置,原始实例不会被修改。