如何在MySQL等关系型数据库中存储DDD值对象?
DDD值对象Address在MySQL中的存储方案
首先明确DDD值对象的核心特性:无独立身份标识、不可变性、通过属性值判断相等、生命周期完全依附所属实体。你当前把Address定义为带ID的@Entity,这是实体的做法,不符合值对象的设计,下面给出两种贴合值对象规则的存储实现方案:
方案一:嵌入User表(推荐)
将Address作为User的一部分,直接嵌入到User表中存储,这是最符合值对象“依附实体”特性的方式,用JPA的@Embeddable和@Embedded注解实现。
修改后的Address值对象代码
@Getter @Embeddable // 标记为可嵌入的值对象,无需@Entity和ID public class Address { // 字段设为final,保证不可变性 private final String endereco; private final String bairro; private final String numero; private final String complemento; private final String cidade; // 仅保留Hibernate反射用的protected无参构造(业务代码禁止调用) protected Address() { this.endereco = null; this.bairro = null; this.numero = null; this.complemento = null; this.cidade = null; } // 业务侧仅使用全参构造创建地址 public Address(String endereco, String bairro, String numero, String complemento, String cidade) { this.endereco = endereco; this.bairro = bairro; this.numero = numero; this.complemento = complemento; this.cidade = cidade; } // 重写equals和hashCode,基于属性值判断地址是否相等 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; return Objects.equals(endereco, address.endereco) && Objects.equals(bairro, address.bairro) && Objects.equals(numero, address.numero) && Objects.equals(complemento, address.complemento) && Objects.equals(cidade, address.cidade); } @Override public int hashCode() { return Objects.hash(endereco, bairro, numero, complemento, cidade); } }
修改后的User实体代码
@Getter @Setter @NoArgsConstructor @Entity @Table(name="user") public class User { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @JsonProperty("userid") @Column(name="userid") private Long user_id; @JsonProperty("user_nome") private String nome; @JsonProperty("user_email") @Column(unique = true, nullable = false) private String email; @JsonProperty("user_cpf") private String cpf; @JsonProperty("user_telefone") private String telefone; @JsonProperty("user_celular") private String celular; @JsonProperty("user_senha") private String senha; @Column(name="createdAt", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") @Temporal(TemporalType.TIMESTAMP) @JsonProperty("user_createdAt") private Date createdAt; @Column(name="updateAt", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") @Temporal(TemporalType.TIMESTAMP) @JsonProperty("user_updateAt") private Date updateAt; // 嵌入Address值对象,通过@AttributeOverride指定数据库列名前缀 @Embedded @JsonProperty("user_address") @AttributeOverrides({ @AttributeOverride(name = "endereco", column = @Column(name = "address_endereco")), @AttributeOverride(name = "bairro", column = @Column(name = "address_bairro")), @AttributeOverride(name = "numero", column = @Column(name = "address_numero")), @AttributeOverride(name = "complemento", column = @Column(name = "address_complemento")), @AttributeOverride(name = "cidade", column = @Column(name = "address_cidade")) }) private Address address; // 新增带地址的全参构造,方便业务侧创建完整用户 public User(Long user_id, String nome, String email, String cpf, String telefone, String celular, String senha, Date createdAt, Date updateAt, Address address) { this.user_id = user_id; this.nome = nome; this.email = email; this.cpf = cpf; this.telefone = telefone; this.celular = celular; this.senha = senha; this.createdAt = createdAt; this.updateAt = updateAt; this.address = address; } }
不可变性的业务实现
如果需要修改用户地址,不能直接修改现有Address对象的属性(因为字段是final),而是创建一个新的Address实例,再通过user.setAddress(newAddress)替换原地址。
方案二:单独表存储(多地址场景适用)
如果用户需要关联多个地址,可以用@ElementCollection实现单独表存储,Address依然是无ID的值对象,生命周期依附User。
User实体中修改地址字段
@ElementCollection @CollectionTable(name = "address", joinColumns = @JoinColumn(name = "userid")) @AttributeOverrides({ @AttributeOverride(name = "endereco", column = @Column(name = "address_endereco")), @AttributeOverride(name = "bairro", column = @Column(name = "address_bairro")), @AttributeOverride(name = "numero", column = @Column(name = "address_numero")), @AttributeOverride(name = "complemento", column = @Column(name = "address_complemento")), @AttributeOverride(name = "cidade", column = @Column(name = "address_cidade")) }) private List<Address> addresses;
此时address表的主键是userid+字段组合(Hibernate会自动生成隐式主键),User删除时关联的所有地址会被级联删除。
内容的提问来源于stack exchange,提问作者Rafael Souza
相关产品推荐
相关产品推荐

