java - 如何使用 JPA 将属性从嵌入式类映射到单个数据库列?

标签 java jpa

由于标题可能有点不清楚,我将在这里解释很多。

我有一个使用 JPA 映射的实体。在这个实体中,我使用一个包含属性的可嵌入类。问题是我想将两个属性映射到同一数据库列上(示例如下)。为了实现这一目标,我重写了这两个属性并将它们映射到列,对于这两个属性之一,我使用 insertable = false 和 updatetable = false。

问题是这不起作用(得到“重复列映射...”,我找不到这个问题的解决方案。我也没有找到有类似问题的人。我希望我搜索得很好但我搜索了很多,我认为还可以。

供您引用,我正在使用 JPA 2.1 和 Hibernate 实现(但我不想使用 Hibernate 特定功能)。

这是我这个类的源代码。问题在于代表一个时期(多种:月、年)的“Periode”对象。问题是我想使用此类来表示年份(作为单列中的整数存储在数据库中),以便能够访问 periode 开始和结束日期属性。

Periode 来自遗留代码,我对它的“权力”相对有限(因为它在许多其他项目中使用)。数据库也是遗留的,所以我无法更改它以放置两个日期而不仅仅是年份。

@SuppressWarnings("serial")
@Embeddable
public class CandidateId implements Serializable {
    // Fields
    private Periode period;
    private IdentifiantContrat contractId;

    // Constructors
    @SuppressWarnings("unused")
    private CandidateId() {}

    public CandidateId(Periode period, IdentifiantContrat contractId) {
        this.period = period;
        this.contractId = contractId;
    }

    public CandidateId(Periode period, IdentifiantAffilie employerId) {
        this(period, new IdentifiantContrat(employerId, 0));
    }

    // Getters
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "debut", column = @Column(name = "PERIODE", nullable = false) ),
        @AttributeOverride(name = "fin", column = @Column(name = "PERIODE", nullable = false, insertable = false, updatable = false))
    })
//  @Converts({
//      @Convert(attributeName = "debut", converter = FiscalStartDateConverter.class),
//      @Convert(attributeName = "fin", converter = FiscalEndDateConverter.class)
//  })
    public Periode getPeriod() {
        return period;
    }

    @Embedded
    public IdentifiantContrat getContractId() {
        return contractId;
    }

    // Setters
    public void setPeriod(Periode period) {
        this.period = period;
    }

    public void setContractId(IdentifiantContrat contractId) {
        this.contractId = contractId;
    }

    // Methods
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((contractId == null) ? 0 : contractId.hashCode());
        result = prime * result + ((period == null) ? 0 : period.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        CandidateId other = (CandidateId) obj;
        if (contractId == null) {
            if (other.contractId != null)
                return false;
        } else if (!contractId.equals(other.contractId))
            return false;
        if (period == null) {
            if (other.period != null)
                return false;
        } else if (!period.equals(other.period))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "CandidatDeclarationId [period=" + period + ", contractId=" + contractId + "]";
    }
}

如您所见,我计划使用转换器将 int 值从 db 转换为两个匹配的日期,但一次有一个问题。

测试我的代码时,我得到以下信息:

javax.persistence.PersistenceException: [PersistenceUnit: belgium-fiscal-model] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1249)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.access$600(EntityManagerFactoryBuilderImpl.java:120)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:860)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:425)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:849)
    at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:75)
    at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
    at be.groups.kernel.utils.persistence.JpaUtil.initEntityManagerFactory(JpaUtil.java:168)
    at be.groups.kernel.utils.persistence.JpaUtil.createEntityManagerFactory(JpaUtil.java:65)
    at be.groups.belgium.fiscal.model.services.CandidateServiceTest.setUp(CandidateServiceTest.java:54)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: be.groups.belgium.fiscal.model.domain.Candidate column: PERIODE (should be mapped with insert="false" update="false")
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:709)
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:750)
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:506)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
    at org.hibernate.cfg.Configuration.validate(Configuration.java:1360)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1851)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
    ... 25 more

java.lang.NullPointerException
    at be.groups.belgium.fiscal.model.services.CandidateServiceTest.tearDown(CandidateServiceTest.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

如果有不清楚的地方,请告诉我,我会尽力精确说明。

编辑: 这是 OndrejM 要求的 Candidate 类。但我不认为问题来自于此(我已经检查过了,尽管我错过了抱歉^^)。但也许我错过了一些东西,所以如果它可以帮助的话,这就是类(class)!

@Entity
@Table(name = "ECHEANCIER_FISCAL_PERSONNE")
public class Candidate {
    // Fields
    private CandidateId id;

    private Date datePassage;
    private String justification;

    private Integer actionProgramme;
    private Integer actionGestionnaire;

    private BaseSignaturePersistance persistenceSignature;

    // Constructors
    private Candidate() {}

    // Getters
    @EmbeddedId
    public CandidateId getId() {
        return id;
    }

    @Column(name = "ACTION_PROGRAMME", nullable = true)
    public Integer getActionProgramme() {
        return actionProgramme;
    }

    @Column(name = "ACTION_GESTIONNAIRE", nullable = true)
    public Integer getActionGestionnaire() {
        return actionGestionnaire;
    }

    @Column(name = "DT_PASSAGE", nullable = true)
    public Date getDatePassage() {
        return datePassage;
    }

    @Column(name = "JUSTIFICATION", nullable = true)
    public String getJustification() {
        return justification;
    }

    @Embedded
    public BaseSignaturePersistance getPersistenceSignature() {
        return persistenceSignature;
    }

    // Setters
    public void setId(CandidateId id) {
        this.id = id;
    }

    public void setActionProgramme(Integer actionProgramme) {
        this.actionProgramme = actionProgramme;
    }

    public void setActionGestionnaire(Integer actionGestionnaire) {
        this.actionGestionnaire = actionGestionnaire;
    }

    public void setDatePassage(Date datePassage) {
        this.datePassage = datePassage;
    }

    public void setJustification(String justification) {
        this.justification = justification;
    }

    public void setPersistenceSignature(BaseSignaturePersistance persistenceSignature) {
        this.persistenceSignature = persistenceSignature;
    }

    // Methods
    /**
     * Tells if this concerns a single contract or not
     * 
     * @return true if this concerns a single contract, else false
     *      
     * @since 1.0.0
     */
    @Transient
    public boolean isSingleContract() {
        return id.getContractId().getNumero() != 0;
    }

    /**
     * Tells if this concerns an employer (multiple contracts) or not
     * 
     * @return true if this concerns an employer, else false
     *      
     * @since 1.0.0
     */
    @Transient
    public boolean isWholeEmployer() {
        return !isSingleContract();
    }

    /**
     * Tells if the candidate is blocked
     * 
     * @return true if this is blocked, else false
     *      
     * @since 2.0.0
     */
    @Transient
    public boolean isBlocked() {
        return actionGestionnaire == null
                ? (actionProgramme == null || actionProgramme == StatutCloture.PAS_PRET_POUR_PREPARATION.getValeur())
                : (actionGestionnaire == StatutCloture.PAS_PRET_POUR_PREPARATION.getValeur());
    }
}

这里还有嵌入 Candidate 类中的 BaseSignaturePersistence 类。很抱歉混合了英语和法语代码,但正如我所说,我使用遗留代码,因此有些类是法语的(最近才决定使用英语)。

@Embeddable            
public class BaseSignaturePersistance {

    /**
     * Auteur creation des données.
     */
    private String auteurCreation;
    /**
     * Auteur modification des données.
     */
    private String auteurModification;
    /**
     * Date creation des données
     */
    private Date dateCreation;
    /**
     * Date modification des données.
     */
    private Date dateModification;

    /**
     * Nouvel Objet Signature.
     *
     * @param lAuteurCreation
     * @param lDateCreation
     * @param lAuteurModification
     * @param lDateModification
     */
    @Requires("auteurCreation!=null && dateCreation!=null")
    @Ensures({"this.auteurCreation == auteurCreation && this.dateCreation == dateCreation",
        "this.dateModification!=null ? this.auteurModification!=null : true",
        "this.auteurModification!=null ? this.dateModification!=null : true"})
    public BaseSignaturePersistance(String auteurCreation, Date dateCreation, String auteurModification, Date dateModification) {
        this.auteurCreation = auteurCreation;
        this.dateCreation = dateCreation;
        this.auteurModification = auteurModification;
        this.dateModification = dateModification;
    }

    /**
     * Nouvel objet signature contenant seulement la partie creation
     * @param unAuteurCreation
     * @param uneDateCreation 
     */
    @Requires({
        "unAuteurCreation!=null",
        "uneDateCreation!=null"
    })
    public BaseSignaturePersistance(String unAuteurCreation, Date uneDateCreation){
        this(unAuteurCreation, uneDateCreation, null, null);
    }

    /**
     * Nouvel objet signature contenant seulement la partie creation
     * @param unAuteurCreation 
     */
    @Requires({
        "auteurCreation!=null"
    })
    public BaseSignaturePersistance(String unAuteurCreation){
        this(unAuteurCreation, DateUtility.getNow(), null, null);
    }

    /**
     * Nouvel objet signature.
     * Utiliser setModification ou SetSuppression pour compléter ce qui est nécessaire.
     */
    public BaseSignaturePersistance(){
        this.auteurCreation = null;
        this.dateCreation = null;
        this.auteurModification = null;
        this.dateModification = null;
    }

    /**
     * Affecte {@code auteur} et {@code date} à {@code auteurModification} et {@code dateModification}.
     *
     * @param auteur
     * @param date
     */
    @Requires("auteur!=null && date!=null")
    @Ensures("auteurModification == auteur && dateModification == date")
    public void setModification(String auteur, Date date) {
        auteurModification = auteur;
        dateModification = date;
    }

    public void copy(BaseSignaturePersistance other) {
        auteurCreation = other.auteurCreation;
        dateCreation = other.dateCreation;
        auteurModification = other.auteurModification;
        dateModification = other.dateModification;
    }

    @Column(name = "AUTEUR_CREATION", nullable = false)  
    public String getAuteurCreation() {
        return auteurCreation;
    }

    @Column(name = "AUTEUR_MODIFICATION")
    public String getAuteurModification() {
        return auteurModification;
    }

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "DT_CREATION", nullable = false)
    public Date getDateCreation() {
        return dateCreation;
    }

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "DT_MODIFICATION")
    public Date getDateModification() {
        return dateModification;
    }

    public void setAuteurCreation(String auteurCreation) {
        this.auteurCreation = auteurCreation;
    }

    public void setAuteurModification(String auteurModification) {
        this.auteurModification = auteurModification;
    }

    public void setDateCreation(Date dateCreation) {
        this.dateCreation = dateCreation;
    }

    public void setDateModification(Date dateModification) {
        this.dateModification = dateModification;
    }

    /**
     * Gives most recent date the object has been modified
     * 
     * @return modification date if any, else creation date
     */
    @Transient
    public Date getDateDerniereModification() {
        return dateModification != null ? dateModification : dateCreation;
    }

    /**
     * Gives most recent author that has modified the object
     * 
     * @return modification author if any, else creation author
     */
    @Transient
    public String getAuteurDerniereModification() {
        return auteurModification != null ? auteurModification : auteurCreation;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((auteurCreation == null) ? 0 : auteurCreation.hashCode());
        result = prime
                * result
                + ((auteurModification == null) ? 0 : auteurModification
                        .hashCode());
        result = prime * result
                + ((dateCreation == null) ? 0 : dateCreation.hashCode());
        result = prime
                * result
                + ((dateModification == null) ? 0 : dateModification.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        BaseSignaturePersistance other = (BaseSignaturePersistance) obj;
        if (auteurCreation == null) {
            if (other.auteurCreation != null)
                return false;
        } else if (!auteurCreation.equals(other.auteurCreation))
            return false;
        if (auteurModification == null) {
            if (other.auteurModification != null)
                return false;
        } else if (!auteurModification.equals(other.auteurModification))
            return false;
        if (dateCreation == null) {
            if (other.dateCreation != null)
                return false;
        } else if (!dateCreation.equals(other.dateCreation))
            return false;
        if (dateModification == null) {
            if (other.dateModification != null)
                return false;
        } else if (!dateModification.equals(other.dateModification))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "BaseSignaturePersistance [auteurCreation=" + auteurCreation
                + ", auteurModification=" + auteurModification
                + ", dateCreation=" + dateCreation + ", dateModification="
                + dateModification + "]";
    }
}

最佳答案

我没有发现您的代码有问题。唯一不常见的事情是您将嵌入 id 的两个属性映射到单个列上,但我没有看到那里有问题。

然而,事实是 hibernate 不支持嵌入式主键中的这种映射(可能是一个错误)。我检查了hibernate的代码,发现嵌入的id不考虑可插入/可更新,仅考虑属性。比较代码如何properties are checked以及 primary key are checked 的列(第 750 行)。

现在一个可能的解决方案:

尝试从 CandidadeId 中删除 periode 类并将 PERIODE 列单独映射到新字段。然后将Periode类直接嵌入到Candidate实体中,并覆盖映射到PERIODE列的两个属性的属性以使其只读(insertable/updatable = false)。

这将从主键中删除旧实体Period,但仍可从实体Candidate中使用它。缺点是您无法修改 periode 类,但始终需要修改 CandidateId 上的 period 字段。不过,您可以创建一个预更新监听器,并在实体持久化之前将值从 periode 类复制到新的 period 字段中。

类似于以下内容(我使用字段映射而不是属性来使其更具可读性):

@Embeddable
public class CandidateId implements Serializable {
    @Column(name = "PERIODE")
    // instead of class Periode put here field period of the same type as field debut in class Periode
    private Date period; 


@Table(name = "ECHEANCIER_FISCAL_PERSONNE")
public class Candidate {
    // Fields
    @EmbeddedId
    private CandidateId id;
    // add following:
    @Embedded
        @AttributeOverrides({
            @AttributeOverride(name = "debut", column = @Column(name = "PERIODE", nullable = false, insertable = false, updatable = false) ),
            @AttributeOverride(name = "fin", column = @Column(name = "PERIODE", nullable = false, insertable = false, updatable = false))
        })
    Periode period;

关于java - 如何使用 JPA 将属性从嵌入式类映射到单个数据库列?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32250542/

相关文章:

jpa - 元组结果标准 API 子查询

java - 不能在 CrudRepository 上使用 delete()

java.lang.IllegalArgumentException : Not a managed type - Multiple Databases in Spring Boot

java - Spring Boot - 不覆盖服务器端口属性

java - 从 SOAP 信封中检索类对象?

java - 使用 Selenium 的页面对象设计模式是否创建页面类并通过它们的对象调用它们的构造函数?

java - 奇怪的字符串比较

java - Servlet 发送旧版本的实体 (JPA)

java - join fetch 在 Hibernate 查询语言中不起作用

java - 方法解析中的 SimpleDateFormat 可能出错