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

Hibernate可选一对一关联问题:增删改查异常解决求助

问题:Hibernate可选一对一映射无法同时支持增删改查

场景很简单:Company表中的公司可以关联一份PIA表中的PIA协议,但并非必须。我花了一整天在Stack Overflow和谷歌上搜相关内容,愣是没找到能同时支持SELECT、INSERT和UPDATE的解决方案。找到了Hibernate一对一和一对多映射的文档,但示例太模糊了。

表结构

create_company.sql

CREATE TABLE company (
    id SMALLINT AUTO_INCREMENT,
    name varchar(60) default NULL,
    PRIMARY KEY (id)
);

create_pia.sql

CREATE TABLE pia (
    company_id smallint,
    agreement_number varchar(50),
    PRIMARY KEY  (company_id),
    CONSTRAINT pia_ibfk_1 FOREIGN KEY (company_id) REFERENCES company (id)
);

注意:看起来得加cascade="all"才能让UPDATE生效。

尝试过的配置及错误

配置1:one-to-one映射

<one-to-one name="pia" class="something.something.Pia" cascade="all"/>
  • 结果:SELECT和UPDATE正常,但INSERT失败,报错:
java.sql.SQLException: Cannot add or update a child row: a foreign key constraint fails (`iti101088_15112`.`pia`, CONSTRAINT `pia_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`))

配置2:many-to-one映射

<many-to-one name="pia" class="something.something.Pia" cascade="all"/>
  • 结果:查询公司时出错:
Caused by: java.sql.SQLException: Unknown column 'company0_.pia' in 'field list'

配置3:带property-ref的many-to-one

<many-to-one name="pia" class="something.something.Pia" cascade="all" property-ref="companyId"/>
  • 结果:同样报错找不到company0_.pia列

配置4:指定column的many-to-one

<many-to-one name="pia" class="something.something.Pia" cascade="all" property-ref="companyId" column="id"/>
  • 结果:查询时出现NullPointerException

配置5:参考建议用伪many-to-one

看到有建议说“Hibernate不支持可选一对一(参考HHH-2007),得用not-null="false"的伪many-to-one代替”,用了如下配置:

<many-to-one name="pia" class="something.something.Pia" cascade="all" not-null="false"/>
  • 结果:还是报找不到company0_.pia列

配置6:嵌套column的many-to-one

<many-to-one name="pia" class="something.something.Pia" cascade="all">
    <column name="id" not-null="false"/>
</many-to-one>
  • 结果:出现序列化错误:
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: could not initialize proxy [something.something.Pia#1] - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy [something.something.Pia#1] - no Session (through reference chain: something.something.Company["pia"]->something.something.Pia$HibernateProxy$54WqyDsD["agreementNumber"])

相关代码

Company.hbm.xml

<class name="something.something.Company" table="COMPANY">
    <id name="id" column="ID">
        <generator class="increment"/>
    </id>
    <property name="name" column="NAME"/>
    <many-to-one name="pia" class="something.something.Pia" cascade="all"/>
</class>

Pia.hbm.xml

<class class="something.something.Pia" table="PIA">
    <id name="companyId" column="COMPANY_ID"/>
    <property name="agreementNumber" column="AGREEMENT_NUMBER"/>
</class>

Company.java

public class Company implements Serializable {

    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private Pia pia; // 原代码漏定义该属性

    public Company() { }
    public Company(int id, String name, Pia pia) {
        super();
        this.id = id;
        this.name = name;
        this.pia = pia; // 原代码赋值错误,已修正
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Pia getPia() { return pia; }
    public void setPia(Pia pia) { this.pia = pia; }
}

Pia.java

public class Pia implements Serializable {
    private int companyId;
    private String agreementNumber;
    
    public Pia() { }
    public Pia(int companyId, String agreementNumber) {
        this.companyId = companyId;
        this.agreementNumber = agreementNumber;  
    }

    public int getCompanyId() { return companyId; }
    public void setCompanyId(int companyId) { this.companyId = companyId; }

    public String getAgreementNumber() { return agreementNumber; }
    public void setAgreementNumber(String agreementNumber) { this.agreementNumber = agreementNumber; }
}

DAO代码

查询

@Override
public Company findCompanyByCompanyId(int companyId) {
    return (Company) DataAccessUtils.singleResult(template.find("from Company where id=?0", companyId));
}

新增

@Override
@Transactional
public int insertCompany(Company company) {
    int result = -1;
    try {
        template.save(company);
        result = company.getId();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return result;
}

更新

@Override
@Transactional
public int updateCompany(Company myCompany) {
    int result = 0;
    try {
        template.update(myCompany);
        result = 1;
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
        result = -1;
    }
    return result;
}

解决方案

核心问题是未正确配置可选一对一的关联逻辑,结合你的表结构(PIA以company_id为主键关联Company的id),正确配置如下:

1. 修正映射文件

Company.hbm.xml

将many-to-one配置为带唯一约束的关联,模拟一对一关系,同时解决懒加载序列化问题:

<class name="something.something.Company" table="COMPANY">
    <id name="id" column="ID">
        <generator class="increment"/>
    </id>
    <property name="name" column="NAME"/>
    <!-- 可选一对一配置:唯一约束保证一对一,column对应PIA的company_id即当前实体id -->
    <many-to-one name="pia" 
                 class="something.something.Pia" 
                 cascade="all" 
                 column="id" 
                 unique="true" 
                 not-null="false"
                 fetch="join"/>
</class>

Pia.hbm.xml

配置主键生成策略为foreign(依赖Company的id),并添加反向关联:

<class name="something.something.Pia" table="PIA">
    <id name="companyId" column="COMPANY_ID">
        <generator class="foreign">
            <param name="property">company</param>
        </generator>
    </id>
    <property name="agreementNumber" column="AGREEMENT_NUMBER"/>
    <!-- 反向关联Company -->
    <one-to-one name="company" class="something.something.Company" constrained="true"/>
</class>

2. 修正实体类

给Pia类添加Company关联属性,实现双向绑定:

public class Pia implements Serializable {
    private int companyId;
    private String agreementNumber;
    private Company company; // 新增反向关联

    public Pia() { }
    public Pia(int companyId, String agreementNumber) {
        this.companyId = companyId;
        this.agreementNumber = agreementNumber;  
    }

    // 新增getter/setter
    public Company getCompany() { return company; }
    public void setCompany(Company company) { 
        this.company = company;
        if(company != null) {
            this.companyId = company.getId(); // 同步主键
        }
    }

    // 原有getter/setter不变
    public int getCompanyId() { return companyId; }
    public void setCompanyId(int companyId) { this.companyId = companyId; }

    public String getAgreementNumber() { return agreementNumber; }
    public void setAgreementNumber(String agreementNumber) { this.agreementNumber = agreementNumber; }
}

3. 调整新增逻辑

插入带PIA的Company时,必须双向绑定关联关系:

// 示例代码
Company company = new Company();
company.setName("Test Corp");
Pia pia = new Pia();
pia.setAgreementNumber("ABC123");
pia.setCompany(company); // 绑定反向关联
company.setPia(pia);
companyDao.insertCompany(company);

完成以上配置后,SELECT、INSERT、UPDATE操作均可正常执行,同时支持可选一对一的关联需求。


内容的提问来源于stack exchange,提问作者the_overflowing_stack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 18:40:45