Hibernate 双向@OneToOne的问题
我有两个实体客户和地址如下: Customer.javaHibernate 双向@OneToOne的问题,hibernate,Hibernate,我有两个实体客户和地址如下: 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.GenerationT
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;
}
}
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)
我曾尝试在@OneToOne中添加@NotNull.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
07:54:37265信息版本:15-Hibernate注解3.2.0.CR3
07:54:37296信息环境:500-Hibernate 3.2.0.cr5
...
线程“main”org.hibernate.PropertyValueException中的异常:NOTNULL属性引用空值或暂时值:com.hibernaterecipes.chapter5.Address.customer
位于org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
位于org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:284)
位于org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
位于org.hibernate.event.def.AbstractSaveEventListener.SaveWithGenerateId(AbstractSaveEventListener.java:108)
在org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)中
位于org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
位于org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:98)
位于org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
位于org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
位于org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
位于org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
位于org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
位于org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
位于org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
位于org.hibernate.engine.Cascade.Cascade(Cascade.java:130)
位于org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:412)
位于org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:261)
位于org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
位于org.hibernate.event.def.AbstractSaveEventListener.SaveWithGenerateId(AbstractSaveEventListener.java:108)
在org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)中
位于org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
位于org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
位于org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
位于org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
位于org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
位于org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
位于org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
位于com.hibernaterecipes.chapter5.Launch5_6A.main(Launch5_6A.java:29)
07:54:39734信息模式更新:160-模式更新完成
07:54:39734调试驱动程序管理器连接提供程序:129-返回到池的连接,池大小:1
07:54:39734调试SessionFactoryImpl:390-检查0个命名的HQL查询
07:54:39734调试SessionFactoryImpl:410-检查0个命名SQL查询
07:54:39796调试会话MPL:220-在时间戳12963542797处打开的会话
07:54:39796调试JDBCTransaction:54-开始
07:54:39796调试连接管理器:415-打开JDBC连接
07:54:39796调试驱动器管理器连接提供程序:93-已签出的连接总数:0
07:54:39796调试DriverManager连接提供程序:99-使用池化JDBC连接,池大小:0
07:54:39796调试JDBCTransaction:59-当前自动提交状态:false
07:54:39796调试JDBCContext:210-事务开始后
07:54:39796调试DefaultSaveOrUpdateEventListener:161-保存临时实例
07:54:39796调试AbstractSaveEventListener:152-保存[com.hibernaterecipes.chapter5.Customer#]
07:54:39796调试AbstractSaveEventListener:240-执行插入
07:54:39812调试级联:115-处理级联操作\u保存\u更新:com.hibernaterecipes.chapter5.Customer
07:54:39812调试级联操作:216-级联到saveOrUpdate:com.hibernaterecipes.chapter5.Address
07:54:39812调试AbstractSaveEventListener:489-的瞬态实例:com.hibernaterecipes.chapter5.Address
07:54:39812调试DefaultSaveOrUpdateEventListener:161-保存临时实例
07:54:39812调试AbstractSaveEventListener:152-保存[com.hibernaterecipes.chapter5.Address#]
07:54:39812调试AbstractSaveEventListener:240-执行插入
我认为这是Hibernate当前的一个限制(它实际上并没有将空值持久化到DB,因为您使用的是JoinTable,但它认为它是)。我们已经能够在实体中不提取可空/可选标志,而是在数据库模式本身中强制非空性。虽然不太理想,但它可以正常工作,而且您确实可以获得引用完整性(您可以添加prePersist和postLoad侦听器,以确保您甚至不尝试DB写入)。我认为将地址设置为可嵌入对象会更好地处理这种情况,但这是您的选择
但无论如何,映射一个@OneToOnepublic 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();
Hibernate: insert into Customer (id, name) values (null, ?)
Hibernate: insert into Address (id, name) values (null, ?)
Hibernate: insert into CustomerAddress (address_id, id) values (?, ?)