首页 > 解决方案 > 部分主键列上的 JPA OneToOne 映射

问题描述

我有两个表,如下所述:

+-------------------+        +------------------------+        
| TB_CONFIG         |        | TB_CONFIG_LANG         |        
+-------------------+        +------------------------+        
| Customer_Id  (PK) |        | Product_Id         (PK)|        
| Company_Id   (PK) |        | Config_Id          (PK)|        
| Product_Id   (PK) |        | Language_Id        (PK)|        
| Config_Id    (PK) |        | Config_Description     |        
| Config_Value      |        | Config_Hint            |        
+-------------------+        | Config_Help            |   
                             +------------------------+  

TB_CONFIG数据是关于由指定的“客户”和“公司”在我们拥有的“产品(解决方案)”上设置的“配置”。

TB_CONFIG_LANG存储有关我们在各种语言上拥有的所有配置的描述、提示和帮助的数据,例如“EN_US”、“PT_BR”等。

我们总是将语言作为参数传递给JPQL将用于加载数据的,但我无法实现映射此连接的“正确方法”......

实际上,这些表映射如下:

TB_CONFIG_LANG 主键

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Embeddable
@MappedSuperclass
public class PkConfigurationLang implements Serializable {

    @Column(name = "PRODUCT_ID")
    @EqualsAndHashCode.Include
    private int productId;

    @Column(name = "CONFIG_ID")
    @EqualsAndHashCode.Include
    private String configId;

    @Column(name = "LANGUAGE_ID")
    @EqualsAndHashCode.Include
    private String languageId;

}

TB_CONFIG_LANG 表

@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@Table(schema = "CLOUD_CATALOG", name = "TB_CONFIG_LANG")
@Cache(region = "oConfigurationsCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class ConfigurationLanguage {

    @EmbeddedId
    private PkConfigurationLang id;

    @Column(name = "CONFIG_DESCRIPTION")
    private String name;

    @Column(name = "CONFIG_HINT")
    private String description;

    @Column(name = "CONFIG_HELP")
    private String help;

}

TB_CONFIG 主键

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Embeddable
@MappedSuperclass
public class PkConfiguration implements Serializable {

    @Column(name = "CUSTOMER_ID")
    @EqualsAndHashCode.Include
    private int customerId;

    @Column(name = "COMPANY_ID")
    @EqualsAndHashCode.Include
    private int companyId;

    @Column(name = "PRODUCT_ID")
    @EqualsAndHashCode.Include
    private int productId;    
    
    @Column(name = "CONFIG_ID")
    @EqualsAndHashCode.Include
    private String configId;

}

TB_CONFIG 表

@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@Table(schema = "CLOUD_CATALOG", name = "TB_CONFIG")
@Cache(region = "oConfigurationsCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Configuration {

    @EmbeddedId
    private PkConfiguration id;

    @Column(name = "CONFIG_VALUE")
    private String configValue;

}

我的第一次尝试是映射@OneToOne一个类的关系Configuration,如下所示:

TB_CONFIG 具有 OneToOne 关系的表

@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@Table(schema = "CLOUD_CATALOG", name = "TB_CONFIG")
@Cache(region = "oConfigurationsCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Configuration {

    @EmbeddedId
    private PkConfiguration id;

    @Column(name = "CONFIG_VALUE")
    private String configValue;

    @OneToOne
    @JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID", insertable = false, updatable = false)
    @JoinColumn(name = "CONFIG_ID", referencedColumnName = "CONFIG_ID", insertable = false, updatable = false)
    private ConfigurationLanguage language;

}

像这样写一个JPQL:

TypedQuery<ConfigurationCompany> query = manager.createQuery("select c from ConfigurationCompany c " +
        "join c.language l " +
        "where c.id.customerId = :customerId " +
        "and c.id.companyId = :companyId " +
        "and c.id.productId = :productId " +
        "and c.id.configId = :configId " +
        "and l.id.languageId = :languageId", ConfigurationCompany.class);

但我在尝试运行应用程序时收到以下异常:

org.springframework.orm.jpa.JpaSystemException: referencedColumnNames(PRODUCT_ID, CONFIG_ID) of com.mycompany.api.configuration.domain.model.ConfigurationCompany.language referencing com.mycompany.api.configuration.domain.model.ConfigurationLanguage not mapped to a single property; nested exception is org.hibernate.AnnotationException: referencedColumnNames(PRODUCT_ID, CONFIG_NAME) of com.mycompany.api.configuration.domain.model.ConfigurationCompany.language referencing com.mycompany.api.configuration.domain.model.ConfigurationLanguage not mapped to a single property

我认为这是因为我在创建@OneToOne映射时没有引用 Language_Id 列......

所以,问题是:这是实现这种关系的正确方式吗?如果是,我错过了什么?如果没有,我该如何正确配置此映射?

提示:我无法更改数据库架构。

非常感谢!

标签: hibernatejpaspring-data-jpahibernate-mappingone-to-one

解决方案


像这样的一对一映射是 Hibernate 不支持的 AFAIK。除此之外,这样做是不安全的,除非您为TB_CONFIG(PRODUCT_ID, CONFIG_ID)and定义了唯一键TB_CONFIG_LANG(PRODUCT_ID, CONFIG_ID)。像这样使用@OneToMany关联有什么问题:

@OneToMany
@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID", insertable = false, updatable = false)
@JoinColumn(name = "CONFIG_ID", referencedColumnName = "CONFIG_ID", insertable = false, updatable = false)
private Set<ConfigurationLanguage> languages;

这样,您只需要一个唯一的密钥TB_CONFIG_LANG(PRODUCT_ID, CONFIG_ID)。为了避免这种情况,您需要使用地图:

@OneToMany
@MapKeyColumn(name = "LANGUAGE_ID")
@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID", insertable = false, updatable = false)
@JoinColumn(name = "CONFIG_ID", referencedColumnName = "CONFIG_ID", insertable = false, updatable = false)
private Map<String, ConfigurationLanguage> languages;

但请注意,从数据库的角度来看,这仍然不安全。外键必须始终引用唯一键。因此,要么将Company_Idand也Customer_Id放入,要么TB_CONFIG_LANG从主键中删除这两个TB_CONFIG


推荐阅读