如何使用JPA将属性从嵌入式类映射到单个数据库列? [英] How to map to attributes from embedded class to a single database column with JPA?

查看:83
本文介绍了如何使用JPA将属性从嵌入式类映射到单个数据库列?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于标题可能不清楚,因此我将在此处进行解释.

As the title could be a bit unclear I will explain much here.

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

I have an entity mapped with JPA. In this entity I use an embeddable class which contains to attributes. The problem is that I want to map the two attributes on the same database column (example comes after). To achieve that, I override the two attributes and map them to the column and for one of the two I use insertable = false and updatable = false.

问题是不起作用(获取重复的列映射...",并且我找不到该问题的解决方案.我也没有找到遇到类似问题的人.我希望我搜索得很好但是我进行了很多搜索后,我认为还可以.

The thing is that doesn't work (getting "repeated column mapping..." and I can't find a solution to this problem. I also didn't find someone with a similar problem. I hope I searched well but as I searched a lot I think it's ok.

为供您参考,我正在将JPA 2.1与Hibernate实现配合使用(但我不想使用Hibernate的特定功能).

For your information, I'm using JPA 2.1 with Hibernate implementation (but I don't want to use Hibernate specific features).

这是我此类的源代码.问题出在代表一个期间(许多类型:月,年)的期间"对象.问题是我想使用此类来表示年份(以整数形式存储在数据库中的单列中),以便能够访问Periode的开始和结束日期属性.

Here is my source code for this class. The problem is with the "Periode" object which represent a period (of many kind : month, year). The thing is I want to use this class to represent a year (stored in database as an integer in a single column) and so be able to access Periode start and end date properties.

期间来自旧代码,我对其的功能"相对有限(因为它在许多其他项目中都使用过).数据库也是遗留的,因此我不能为了放置两个日期而不只是年份来更改它.

Periode comes from legacy code and I have relatively limited "power" over it (as it's used in many other projects). Database is also legacy so I can't change it in order to put two dates instead of just the year.

@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转换为两个匹配的日期,但是一次出现一个问题.

As you can see, I plan to use converters to convert int value from db to the two matching dates, but one problem at a time.

在测试代码时,我会收到以下信息:

When testing my code, I get this information:

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)

如果有不清楚的地方,请告诉我,我会尽力而为.

If something is unclear, just tell me I'll try to precise it.

这是OndrejM要求的候选课程.但是我不认为问题出在这里(我已经检查过了,尽管我很抱歉提到它^^).但是也许我想念一些东西,所以如果可以帮忙,这是课程!

EDIT : Here is the Candidate class as asked by OndrejM. But I don't think the problem come from it (I already checked that, though I miss to mention it sorry^^). But maybe I'm missing something so if it can help, here is the 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());
    }
}

这里也是BaseSignaturePersistence类,它嵌入在Candidate类中.抱歉,我无法将英语和法语代码混合使用,但是正如我所说的,我使用的是遗留代码,因此某些类是法语的(决定使用英语只是在最近才做出的决定.)

Here is also the BaseSignaturePersistence class which is embedded in Candidate class. Sorry for mixing english and french code but as I said I work with legacy code and so some classes are in french (decision to use english has only be made recently).

@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的两个属性映射到单个列上,但是我在那里看不到问题.

I don't see a problem in your code. The only uncommon thing is that you are mapping two properties of an embedded id onto a single column, but I don't see a problem there.

但是,事实是休眠不支持嵌入式主键中的此映射(可能是错误).我检查了休眠的代码,发现嵌入的ID仅考虑属性,而不考虑可插入/可更新.比较代码

However, the fact is that hibernate does not support this mapping in embedded primary keys (probably a bug). I checked the code of hibernate and I found out that insertable/updatable is not considered for embedded ids, only for properties. Compare the code how properties are checked and how columns for primary key are checked (line 750).

现在是可能的解决方案:

And now a possible solution:

尝试从CandidadeId中删除Periode类,然后将PERIODE列单独映射到一个新字段.然后将Periode类直接嵌入到Candidate实体中,并覆盖映射到PERIODE列的两个属性的只读属性(可插入/可更新= false).

Try to remove Periode class from CandidadeId and map PERIODE column to a new field alone. Then embed Periode class directly into Candidate entity, and override attributes of both properties that map to PERIODE column to be read only (insertable/updatable = false).

这将从主键中删除旧版实体Periode,并仍可从实体候选中使用它.缺点是您不能修改Periode类,但始终需要修改CandidateId上的period字段.但是,您可以创建一个更新前的侦听器,然后在持久化实体之前将Periode类中的值复制到新的period字段中.

That would remove the legacy entity Periode from the primary key, and still make it available from the entity Candidate. The drawback is that you cannot modify Periode class, but always need to modify period field on CandidateId instead. You may however create a pre-update listener and copy the value from Periode class into the new period field before the entity is persisted.

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

Something like following (I used mapping on fields instead of properties to make it more readable):

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

这篇关于如何使用JPA将属性从嵌入式类映射到单个数据库列?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆