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

Hibernate/JPA不同类型字段关联问题求助

解决Spring Boot 3.2.3 + Hibernate 6.4.1中JPA关联转换器失效及@JoinFormula报错问题

针对你遇到的主键转换器未生效、@JoinFormula报错的问题,以下是几个可行的解决思路:

1. 确保主键转换器符合Hibernate 6的配置要求

Hibernate 6对主键字段的AttributeConverter有严格绑定要求,需避免全局自动应用,且必须在主键字段显式声明:

步骤1:修正转换器定义

// 禁用自动应用,避免全局冲突
@Converter(autoApply = false)
public class PaddingConverter implements AttributeConverter<String, BigDecimal> {
    // 实体属性(带前导零的字符串)转数据库字段(DECIMAL(8))
    @Override
    public BigDecimal convertToDatabaseColumn(String attribute) {
        return attribute != null ? new BigDecimal(attribute) : null;
    }

    // 数据库字段转实体属性(补前导零到8位)
    @Override
    public String convertToEntityAttribute(BigDecimal dbData) {
        return dbData != null ? String.format("%08d", dbData.longValue()) : null;
    }
}

步骤2:在Table1主键显式绑定转换器

@Entity
@Table(name = "table_1")
public class Table1 {
    @Id
    @Column(name = "tt_id")
    // 显式指定转换器,确保Hibernate处理主键时应用转换逻辑
    @Convert(converter = PaddingConverter.class)
    private String ttId;

    // 后续关联定义先依赖mappedBy,避免手动关联冲突
    @OneToMany(mappedBy = "table1")
    private List<Table2> table2List;

    // 其他字段、getter/setter
}

2. 修正Table2的关联映射,显式处理类型匹配

由于Table2的联合主键ts_id是VARCHAR(8)(自带前导零),需在关联字段显式指定转换器,让Hibernate匹配转换后的主键值:

步骤1:定义嵌入式联合主键

@Embeddable
public class Table2Id implements Serializable {
    @Column(name = "ts_id", length = 8)
    private String tsId;
    // 其他联合主键字段
    // 必须重写equals()和hashCode()
}

步骤2:在Table2中配置关联

@Entity
@Table(name = "table_2")
public class Table2 {
    @EmbeddedId
    private Table2Id id;

    @ManyToOne
    // 关联字段绑定ts_id,引用Table1的tt_id
    @JoinColumn(name = "ts_id", referencedColumnName = "tt_id", insertable = false, updatable = false)
    // 显式指定转换器,确保关联查询时转换逻辑生效
    @Convert(converter = PaddingConverter.class)
    private Table1 table1;

    // 其他字段、getter/setter
}

3. 用数据库函数替代@JoinFormula,规避报错

若@JoinFormula报"Values involves formula"错误,可直接在关联查询中使用数据库函数实现字段匹配(以MySQL为例):

@Entity
@Table(name = "table_2")
public class Table2 {
    @EmbeddedId
    private Table2Id id;

    @ManyToOne
    // 用数据库LPAD函数将DECIMAL类型的tt_id转为8位带前导零的字符串,与ts_id匹配
    @Formula("(SELECT t FROM table_1 t WHERE LPAD(CAST(t.tt_id AS CHAR), 8, '0') = id.ts_id)")
    private Table1 table1;

    // 其他字段、getter/setter
}

4. 自定义Hibernate Type(终极方案)

若AttributeConverter仍无法满足关联场景的转换需求,可实现自定义Hibernate Type,直接绑定字段的SQL类型处理逻辑:

步骤1:实现自定义UserType

public class PaddedDecimalType implements UserType<String> {
    @Override
    public int getSqlType() {
        return Types.DECIMAL;
    }

    @Override
    public Class<String> returnedClass() {
        return String.class;
    }

    @Override
    public boolean equals(String x, String y) {
        return Objects.equals(x, y);
    }

    @Override
    public int hashCode(String x) {
        return Objects.hashCode(x);
    }

    @Override
    public String nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
        BigDecimal value = rs.getBigDecimal(position);
        return value != null ? String.format("%08d", value.longValue()) : null;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, String value, int index, SharedSessionContractImplementor session) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.DECIMAL);
        } else {
            st.setBigDecimal(index, new BigDecimal(value));
        }
    }

    // 实现剩余方法:deepCopy、isMutable、disassemble、assemble、replace
    @Override
    public String deepCopy(String value) {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(String value) {
        return value;
    }

    @Override
    public String assemble(Serializable cached, Object owner) {
        return (String) cached;
    }

    @Override
    public String replace(String original, String target, Object owner) {
        return original;
    }
}

步骤2:在Table1主键绑定自定义Type

@Entity
@Table(name = "table_1")
public class Table1 {
    @Id
    @Column(name = "tt_id")
    @Type(PaddedDecimalType.class)
    private String ttId;

    @OneToMany(mappedBy = "table1")
    private List<Table2> table2List;

    // 其他字段、getter/setter
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 07:47:40