Hibernate更新JPA外键。

9

我的jpa代码如下:

public class TESTClass implements Serializable {

    ...

    private String name;

    @EmbeddedId
    protected IssTESTPK issTESTPK;

    @ManyToOne(optional=false)
    @JoinColumns({
        @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE", nullable=false, insertable=false, updatable=false), 
        @JoinColumn(name="SURVEY_NUM", referencedColumnName="SURVEY_NUM", nullable=false, insertable=false, updatable=false)})
    private IssDivision issDivision;

}

如果我更改了“名称”并调用合并,它可以更新到数据库,但是当我更改issDivision并调用合并时,它不会更新数据库。如何解决这个问题?


这是否与我正在使用的嵌入式ID(复合主键)有关?

更新

如果我设置updated = true,我会收到以下错误

ERROR - ContextLoader.initWebApplicationContext(215) | Context initialization fa
iled
org.springframework.beans.factory.BeanCreationException: Error creating bean wit
h name 'sessionFactory' defined in ServletContext resource [/WEB-INF/application
Context.xml]: Invocation of init method failed; nested exception is org.hibernat
e.MappingException: Repeated column in mapping for entity: com.compay.test.model
.TESTClass column: SURVEY_NUM (should be mapped with insert="false" update="fa
lse")
        at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.initializeBean(AbstractAutowireCapableBeanFactory.java:1338)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory$1.run(AbstractAutowireCapableBeanFactory.java:409)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.createBean(AbstractAutowireCapableBeanFactory.java:380)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getOb
ject(AbstractBeanFactory.java:264)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr
y.getSingleton(DefaultSingletonBeanRegistry.java:222)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBe
an(AbstractBeanFactory.java:261)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean
(AbstractBeanFactory.java:185)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean
(AbstractBeanFactory.java:164)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.
preInstantiateSingletons(DefaultListableBeanFactory.java:423)
        at org.springframework.context.support.AbstractApplicationContext.finish
BeanFactoryInitialization(AbstractApplicationContext.java:728)
        at org.springframework.context.support.AbstractApplicationContext.refres
h(AbstractApplicationContext.java:380)
        at org.springframework.web.context.ContextLoader.createWebApplicationCon
text(ContextLoader.java:255)
        at org.springframework.web.context.ContextLoader.initWebApplicationConte
xt(ContextLoader.java:199)
        at org.springframework.web.context.ContextLoaderListener.contextInitiali
zed(ContextLoaderListener.java:45)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContex
t.java:3843)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4
342)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase
.java:791)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:77
1)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:525)

        at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.ja
va:627)
        at org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.j
ava:553)
        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:488
)
        at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1149)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java
:311)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(Lifecycl
eSupport.java:117)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)

        at org.apache.catalina.core.StandardHost.start(StandardHost.java:719)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)

        at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443
)
        at org.apache.catalina.core.StandardService.start(StandardService.java:5
16)
        at org.apache.catalina.core.StandardServer.start(StandardServer.java:710
)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:578)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.

请提供整个类(实体和嵌入式键)。 - Bozho
@Bozho,你能发布一个带有主键的实体的工作示例吗? - cometta
我在 https://forum.hibernate.org/viewtopic.php?t=973585&highlight=repeated+column+mapping+composite+fk 上看到了这篇帖子,你理解最后两个关于私有 get/set 的帖子吗? - cometta
这个对你有用过吗?请发布解决方案。 - saran3h
3个回答

15

好的,让我们看看

你的异常(和著名的)信息是

repeated column in mapping for entity:
column: SURVEY_NUM (should be mapped with insert="false" update="false")

SURVEY_NUM列在哪里?

issDivision字段存储了一个名为SURVEY_NUM的外键列。

@ManyToOne
@JoinColumns({
    @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE", insertable=false, updatable=false), 
    @JoinColumn(name="SURVEY_NUM", referencedColumnName="SURVEY_NUM", insertable=false, updatable=false)})
private IssDivision issDivision;

现在看下面的映射关系 (注意id和accountNumber共用同一列)

@Entity
public class Account {

    private Integer id;

    private Integer accountNumber;

    @Id
    @Column(name="ACCOUNT_NUMBER")
    public Integer getId() {
        return this.id;
    }

    @Column(name="ACCOUNT_NUMBER")
    public Integer getAccountNumber() {
        return this.accountNumber;
    }

}

现在让我们按照以下步骤进行

Account account = new Account();
account.setId(127359);
account.setAccountNumber(null);

entityManager.persist(account);

Hibernate将询问您,无论这两个属性是否共享同一列,应将哪个属性持久化?并且我可以看到,id属性存储非空值,而accountNumber属性存储null值。

我是否应该执行像下面这样的查询?

INSERT INTO ACCOUNT (ACCOUNT_NUMBER, ACCOUNT_NUMBER) VALUES (127359, NULL);

这是错误的SQL查询语句,因此没有意义;

由于这个原因,你看到了这个友好的提示信息

重复的列... 啦啦啦... (应该使用 insert="false" update="false" 进行映射)

因此,我猜你的复合主键称为IssTESTPK,还存储一个名为SURVEY_NUM的列。定义一个复合主键属性为insert="false",update="false"不是一个好主意,会引起很多麻烦。

请记住:当多个属性共享同一列时,将它们中的一个定义为insertable=false,updatable=false。仅此而已。

我认为你的复合主键类应该像这样:

@Embeddable
public class IssTESTPK implements Serializable {

    // Ops... Our missing field which causes our Exception (repeated column... blah, blah, blah...)
    @Column(name="SURVEY_NUM", nullable=false)
    private Integer property;

    private Integer otherProperty;

    private Integer anotherProperty;

    // required no-arg constructor
    public IssTESTPK() {}

    // You must implement equals and hashcode
    public boolean equals(Object o) {
        if(o == null)
            return false;

        if(!(o instanceof IssTESTPK))
            return false;

        IssTESTPK other = (IssTESTPK) o;
        if(!(getProperty().equals(other.getProperty())))
            return false;
        if(!(getOtherProperty().equals(other.getOtherProperty())))
            return false;
        if(!(getAnotherProperty().equals(other.getAnotherProperty())))
            return false;

        return true;
    }

    // NetBeans or Eclipse will worry about it
    public int hashcode() {
        // hashcode code goes here
    }

}

更新


在继续之前

Hibernate不支持自动生成复合主键

在保存之前,您必须提供其值。请记住这一点

让我们看看员工的复合主键

@Embeddable
public class EmployeeId implements Serializable {

    @Column(name="EMPLOYEE_NUMBER")
    private String employeeNumber;

    @Column(name="SURVEY_NUMBER")
    private BigInteger surveyNumber;

    // getter's and setter's

    // equals and hashcode

}

在保存员工之前,您必须提供其值。如上所述,Hibernate不支持自动生成复合主键。

Hibernate不允许您更新(复合)主键,这是没有意义的。

其值不能为空。

因此,根据上述描述,我们的EmployeeId可以编写为

@Embeddable
public class EmployeeId implements Serializable {

    @Column(name="EMPLOYEE_NUMBER", nullable=false, updatable=false)
    private String employeeNumber;

    @Column(name="SURVEY_NUMBER", nullable=false, updatable=false)
    private BigInteger surveyNumber;

    // getter's and setter's

    // equals and hashcode

}

如先前所述:

当多个属性共享同一列时,将其中一个定义为 insertable=false, updatable=false。 没有别的

但是我们无法将复合主键属性标记为 insertable=false, updatable=false因为Hibernate使用它来保存我们的实体

由于Hibernate将使用名为surveyNumber的复合主键属性(及其SURVEY_NUMBER列)在数据库上执行SQL操作,因此我们需要重新编写@ManyToOne division属性(及其名为SURVEY_NUMBER的外键列),并将其定义为insertable=false, updatable=false。

// Employee.java

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
    @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE"),
    @JoinColumn(name="SURVEY_NUMBER", referencedColumnName="SURVEY_NUMBER", insertable=false, updatable=false)})
private Division division;

第四点 当您拥有复合外键时,我们不能混合使用可插入-不可插入或可更新-不可更新。

类似于这样的情况

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
    // I can be updatable
    @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE", insertable=false),
    // And i can be insertable
    @JoinColumn(name="SURVEY_NUMBER", referencedColumnName="SURVEY_NUMBER", updatable=false)})
private Division division;
否则,Hibernate 将会报错。
混合插入和非插入列在属性中是不被允许的。
因此,它的复合外键列 DIVISION_CODE 也应该被标记为 insertable=false, updatable=false,以避免上述异常的出现。
// Employee.java

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
    @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE", insertable=false, updatable=false),
    @JoinColumn(name="SURVEY_NUMBER", referencedColumnName="SURVEY_NUMBER", insertable=false, updatable=false)})
private Division division;

我们无法再更新DIVISION_CODE列,因此我们的division属性表现得像一个常量。然后,你考虑创建一个名为divisionCode的新属性,以某种方式更改DIVISION_CODE列,如下所示:

// Employee.java

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
    @JoinColumn(name="DIVISION_CODE", referencedColumnName="DIVISION_CODE", insertable=false, updatable=false),
    @JoinColumn(name="SURVEY_NUMBER", referencedColumnName="SURVEY_NUMBER", insertable=false, updatable=false)})
private Division division;

// Wow, now i expect i can change the value of DIVISION_CODE column
@Column(name="DIVISION_CODE")
private BigInteger divisionCode;

好的,让我们来看一下。假设我们有以下除法表:

DIVISION TABLE
DIVISION_CODE     SURVEY_NUMBER
1                 10
2                 11
3                 12
4                 13
5                 14

请记住:Division和Employee之间存在外键约束

您想到:

由于insertable=false、updatable=false,我无法更改division属性。但是,我可以更改divisionCode属性(及其DIVISION_CODE列)作为更改名为DIVISION_CODE的外键列的方法。

您执行以下代码:

employee.setDivisionCode(7);

哇,看上面的DIVISION_TABLE。 DIVISION_CODE列中是否有与7相等的值?

答案很明显:没有(您将看到一个CONSTRAINT VIOLATION)

因此,这是不一致的映射。Hibernate不允许这样做。

敬礼,


嗨,我按照你提到的更改进行了修改,但它没有起作用。我发布了我的原始实体 http://paste.ideaslabs.com/show/fvCSMNYlQt(IssEmployee.java)http://paste.ideaslabs.com/show/zJ14BVMqNq(IssEmployeePK.java) - cometta

2

这是因为您使用了updatable = false。如果删除它,可能会导致其他问题,但我不知道您的完整映射,无法做出假设。

Embeddable类中将updatableinsertable设置为false,并从联接列中删除它们。


@Bozho,当设置updatable=true时,我遇到了其他错误,我已经更新了我的问题。这就是为什么我必须将updatable设置为false的原因。 - cometta
TESTClass http://paste.ideaslabs.com/show/wuSaE04bvZ测试类 http://paste.ideaslabs.com/show/wuSaE04bvZcomposite keys http://paste.ideaslabs.com/show/ScH64kt9EY复合键 http://paste.ideaslabs.com/show/ScH64kt9EY - cometta
@Bozho,我看到了你的更新,请给我一个示例..我不太明白什么是可嵌入类?哪个类是sDivision? - cometta
1
@cometta - @IssDivision 不是可嵌入的吗?我假定了可能不正确的东西,但是,能给出 IssDivision 的代码吗! - Bozho
我发布了我的原始实体 http://paste.ideaslabs.com/show/fvCSMNYlQt (IssEmployee.java)http://paste.ideaslabs.com/show/zJ14BVMqNq (IssEmployeePK.java) - cometta

1
我找到了一种解决方法,但我想听听大家是否可以使用这种技术。 我修改了实体类以...
public class TESTClass implements Serializable {

...
private String name;
@EmbeddedId
protected IssTESTPK issTESTPK;

    @JoinColumns({@JoinColumn(name = "DIVISION_CODE", referencedColumnName = "DIVISION_CODE", nullable = false , insertable = false, updatable = false), @JoinColumn(name = "SURVEY_NUM", referencedColumnName = "SURVEY_NUM", nullable = false, insertable = false, updatable = false)})
    @ManyToOne(optional = false)
    private IssDivision issDivision;   //since this is not merge

       @Basic(optional = false)
    @Column(name = "DIVISION_CODE")
    private String divisionCode;  //i add this

}

我添加了一个名为“divisionCode”的属性,尽管它在issDivision对象内部重复。但这是我能够在合并期间更新“DIVISION_CODDE”的解决方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接