【问题标题】:Problem with bi-directional @OneToOne双向@OneToOne 的问题
【发布时间】:2011-01-28 02:55:32
【问题描述】:

我有 2 个实体客户和地址,如下所示: 客户.java

package com.hibernaterecipes.chapter5;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="CUSTOMER")
public class Customer {
@Column (name="ID")
@Id
@GeneratedValue (strategy=GenerationType.AUTO)
private Long id;

@Column (name="COUNTRY_CODE")
private String countryCode;

@Column (name="ID_CARD_NO")
private String idCardNo;

@Column (name="FIRST_NAME")
private String firstName;

@Column (name="LAST_NAME")
private String lastName;

@Column (name="EMAIL")
private String email;

@OneToOne (cascade=CascadeType.ALL)
@JoinTable (name="CustomerAddress", 
        joinColumns=@JoinColumn(name="ID"),
        inverseJoinColumns=@JoinColumn(name="ADDRESS_ID"))
private Address address;

public Long getId() {
    return id;
}
public void setId(Long id) {
    this.id = id;
}
public String getCountryCode() {
    return countryCode;
}
public void setCountryCode(String countryCode) {
    this.countryCode = countryCode;
}
public String getIdCardNo() {
    return idCardNo;
}
public void setIdCardNo(String idCardNo) {
    this.idCardNo = idCardNo;
}
public String getFirstName() {
    return firstName;
}
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
public String getLastName() {
    return lastName;
}
public void setLastName(String lastName) {
    this.lastName = lastName;
}

public String getEmail() {
    return email;
}
public void setEmail(String email) {
    this.email = email;
}
public Address getAddress() {
    return address;
}
public void setAddress(Address address) {
    this.address = address;
}

}

地址.java:

package com.hibernaterecipes.chapter5;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="ADDRESS")
public class Address {
@Id
@Column (name="ADDRESS_ID")
@GeneratedValue (strategy=GenerationType.AUTO)
private Long id;

@Column(name="CITY")
private String city;

@Column(name="STREET")
private String street;

@Column(name="DOOR_PLATE")
private String doorplate;

@OneToOne (mappedBy="address")
private Customer customer;

public Customer getCustomer() {
    return customer;
}
public void setCustomer(Customer customer) {
    this.customer = customer;
}
public Long getId() {
    return id;
}
public void setId(Long id) {
    this.id = id;
}
public String getCity() {
    return city;
}
public void setCity(String city) {
    this.city = city;
}
public String getStreet() {
    return street;
}
public void setStreet(String street) {
    this.street = street;
}
public String getDoorplate() {
    return doorplate;
}
public void setDoorplate(String doorplate) {
    this.doorplate = doorplate;
}

 }

当我尝试保留客户时,我得到以下堆栈跟踪:

Exception in thread "main" org.hibernate.PropertyValueException: not-null property references a null or transient value: com.hibernaterecipes.chapter5.Address.customer
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:284)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:98)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:412)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:261)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
at com.hibernaterecipes.chapter5.Launch5_6A.main(Launch5_6A.java:29)

我尝试将@NotNull 添加到双方。@OneToOne 中的 optional = false 和 @JoinColumn 中的 unique = true, nullable = false 但没有任何效果。

图。帮助。

根据建议,我更改了地址类别

package com.hibernaterecipes.chapter5;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="ADDRESS")
public class Address {
@Id
@Column (name="ADDRESS_ID")
@GeneratedValue (strategy=GenerationType.AUTO)
private Long id;

@Column(name="CITY")
private String city;

@Column(name="STREET")
private String street;

@Column(name="DOOR_PLATE")
private String doorplate;

@OneToOne(mappedBy="address")
private Customer customer;

public Customer getCustomer() {
    return customer;
}
public void setCustomer(Customer customer) {
    if (null != customer) {
        customer.setAddress(this);
        this.customer = customer;
    }
}
public Long getId() {
    return id;
}
public void setId(Long id) {
    this.id = id;
}
public String getCity() {
    return city;
}
public void setCity(String city) {
    this.city = city;
}
public String getStreet() {
    return street;
}
public void setStreet(String street) {
    this.street = street;
}
public String getDoorplate() {
    return doorplate;
}
public void setDoorplate(String doorplate) {
    this.doorplate = doorplate;
}

}

客户类别保持不变。

用于测试代码助手类 JpaUtil.java

package persistence;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

public class JpaUtil {
    private static SessionFactory sessionFactory;

      static {
        try {
           sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        } catch (Throwable ex) {
           throw new ExceptionInInitializerError(ex);
        }
      }

      public static SessionFactory getSessionFactory() {
          // Alternatively, we could look up in JNDI here
          return sessionFactory;
      }

      public static void shutdown() {
          // Close caches and connection pools
          getSessionFactory().close();
      }
}

测试Launch5_6A.java的主类

package com.hibernaterecipes.chapter5;

import org.hibernate.Session;
import org.hibernate.Transaction;

import persistence.JpaUtil;

public class Launch5_6A {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.setCountryCode("India");
        customer.setEmail("subhendu_mahanta@yahoo.com");
        customer.setFirstName("Subhendu");
        customer.setLastName("Mahanta");
        customer.setIdCardNo("43223");
        Address address = new Address();
        address.setCity("Pune");
        address.setDoorplate("E1/33");
        address.setStreet("Paud Road");
        address.setCustomer(customer);
        customer.setAddress(address);

        Session session = JpaUtil.getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        session.save(customer);
        tx.commit();
        session.close();

    }

}

结果保持不变。

07:54:37,265  INFO Version:15 - Hibernate Annotations 3.2.0.CR3
07:54:37,296  INFO Environment:500 - Hibernate 3.2.0.cr5
...
Exception in thread "main" org.hibernate.PropertyValueException: not-null property references a null or transient value: com.hibernaterecipes.chapter5.Address.customer
    at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:284)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:98)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
    at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
    at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
    at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
    at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
    at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
    at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:412)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:261)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
    at com.hibernaterecipes.chapter5.Launch5_6A.main(Launch5_6A.java:29)
07:54:39,734  INFO SchemaUpdate:160 - schema update complete
07:54:39,734 DEBUG DriverManagerConnectionProvider:129 - returning connection to pool, pool size: 1
07:54:39,734 DEBUG SessionFactoryImpl:390 - Checking 0 named HQL queries
07:54:39,734 DEBUG SessionFactoryImpl:410 - Checking 0 named SQL queries
07:54:39,796 DEBUG SessionImpl:220 - opened session at timestamp: 12963542797
07:54:39,796 DEBUG JDBCTransaction:54 - begin
07:54:39,796 DEBUG ConnectionManager:415 - opening JDBC connection
07:54:39,796 DEBUG DriverManagerConnectionProvider:93 - total checked-out connections: 0
07:54:39,796 DEBUG DriverManagerConnectionProvider:99 - using pooled JDBC connection, pool size: 0
07:54:39,796 DEBUG JDBCTransaction:59 - current autocommit status: false
07:54:39,796 DEBUG JDBCContext:210 - after transaction begin
07:54:39,796 DEBUG DefaultSaveOrUpdateEventListener:161 - saving transient instance
07:54:39,796 DEBUG AbstractSaveEventListener:152 - saving [com.hibernaterecipes.chapter5.Customer#<null>]
07:54:39,796 DEBUG AbstractSaveEventListener:240 - executing insertions
07:54:39,812 DEBUG Cascade:115 - processing cascade ACTION_SAVE_UPDATE for: com.hibernaterecipes.chapter5.Customer
07:54:39,812 DEBUG CascadingAction:216 - cascading to saveOrUpdate: com.hibernaterecipes.chapter5.Address
07:54:39,812 DEBUG AbstractSaveEventListener:489 - transient instance of: com.hibernaterecipes.chapter5.Address
07:54:39,812 DEBUG DefaultSaveOrUpdateEventListener:161 - saving transient instance
07:54:39,812 DEBUG AbstractSaveEventListener:152 - saving [com.hibernaterecipes.chapter5.Address#<null>]
07:54:39,812 DEBUG AbstractSaveEventListener:240 - executing insertions

【问题讨论】:

  • 我建议你在你的帖子中添加一个 java 标签
  • 我认为,使 nullable=true 或从两侧设置对象,例如 cutomer.setAddress(addr) 和 address.setCustomer(cust)。

标签: hibernate


【解决方案1】:

我相信这目前是 Hibernate 中的一个限制(它实际上并没有将空值持久化到数据库,因为您使用的是 JoinTable,但它认为它是)。我们已经能够不在实体中提取可空/可选标志,而是在数据库模式本身中强制执行不可空性。它不太理想,但它可以工作,并且您确实获得了引用完整性(您可以添加一个 prePersist 和 postLoad 侦听器以确保您甚至不尝试数据库写入)。

【讨论】:

  • 所以在hibernate中使用连接表和使用@OneToOne的双向一对一关联是不可能的。我们选择JoinColumn,即外键关联或共享主键。
  • FK 不起作用,因为您会在数据库中看到先有鸡还是先有蛋的问题(您首先保存哪个实体?)。共享主键应该可以工作。同样,在休眠中使用连接表和@OneToOne works 进行双向一对一关联,您只是不能在代码中声明它们为非空(但只能在数据库中)。跨度>
  • 这不是鸡蛋问题,因为有一方“拥有”这种关系。在这种情况下,它是客户。
  • 当然,我确信双向一对一映射在休眠模式下使用正确的模式工作,如果你想要一个单向映射,即地址可能有也可能没有一个唯一的客户绑定对它,你当然可以做到。您的回答不会强制执行 nullable=false,如果您尝试这样做,hibernate 会抱怨。
  • ic,也许我明天应该试一试,也许那时我们正在运行旧版本
【解决方案2】:

我认为将地址设置为可嵌入对象会更好地处理这种情况,但这是您的选择。

但无论如何,将@OneToOne@JoinTable 映射是完全可能的。您只需要分两步完成。请参阅此代码:

public class Address {
    @Id @GeneratedValue
    private Long id;

    @Column
    private String name;

    @OneToOne(mappedBy="address")
    private Customer customer;

    public void setCustomer(Customer c) {
        if (null != c) {
            c.setAddress(this);
            this.customer = c;
        }
    }
}

public class Customer {
    @Id @GeneratedValue
    private Long id;

    @Column
    private String name;

    @OneToOne
    @JoinTable(name = "CustomerAddress")
    private Address address;
}

在您的业务方法中:

    Customer c = new Customer();
    c.setName("c1");

    Address a = new Address();
    a.setName("a1");

    em.getTransaction().begin();

    em.persist(c);

    a.setCustomer(c);
    em.persist(a);

    em.getTransaction().commit();

此代码将生成这些 SQL 指令:

Hibernate: insert into Customer (id, name) values (null, ?)
Hibernate: insert into Address (id, name) values (null, ?)
Hibernate: insert into CustomerAddress (address_id, id) values (?, ?)

【讨论】:

  • 嗨,Partenon,根据您的建议,我更改了地址类并保留客户原样。仍然无法正常工作。请参阅我编辑的帖子。谢谢。
  • 再次查看我关于业务方法的示例(在您的情况下为 Launch5_6A)。先坚持客户,再给客户设置地址,再坚持地址。另外,请保持您的问题干净,目前很难使用如此多的样板代码和重复示例...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-28
  • 1970-01-01
  • 1970-01-01
  • 2012-03-14
  • 2012-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多