You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:35:31