使用外键关系编排Spring Boot CrudRepositories

sme*_*eeb 3 java hibernate jpa spring-data-jpa spring-boot

我正在编写一个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;
}
Run Code Online (Sandbox Code Playgroud)

并且@RestController,Account实例将被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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Dra*_*vic 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;

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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