双向@OneToOne的问题

时间:2011-01-28 02:55:33

标签: hibernate

我有2个实体客户&地址如下: Customer.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;
}

}

Adress.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。

PL。帮助

根据建议,我改变了我的地址类

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:-1)

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

但无论如何,很有可能将@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 (?, ?)