如何使用JPA将嵌入类中的属性映射到单个数据库列?

时间:2015-08-27 13:20:36

标签: java jpa

由于标题可能有点不清楚,我会在这里解释一下。

我有一个用JPA映射的实体。在这个实体中,我使用了一个包含属性的可嵌入类。问题是我想在同一个数据库列上映射这两个属性(后面的例子)。为实现这一点,我重写了两个属性并将它们映射到列,对于其中一个我使用insertable = false和updatable = 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提出的候选人课程。但我不认为问题来自它(我已经检查过了,虽然我很想提到它抱歉^^)。但也许我错过了一些东西,如果能有所帮助,这就是上课!

@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 + "]";
    }
}

1 个答案:

答案 0 :(得分:2)

我的代码中没有发现问题。唯一不常见的是,您将嵌入式ID的两个属性映射到一个列,但我不会在那里看到问题。

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

现在可能的解决方案:

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

这将从主键中删除遗留实体Periode,并仍然可以从实体候选者中获取它。缺点是您无法修改Periode类,但总是需要修改CandidateId上的period字段。但是,您可以创建一个更新前的侦听器,并在保留实体之前将Periode类的值复制到新的句点字段中。

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

@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;