今天本来时准备在mysql控制台中写一个一对一关联,外键放在连接表中,但是突然没有头绪到底该怎么写这样的表,于是借鉴hibernate高级“自动生成SQL语句”的方式看它是怎么写的,但是在做的过程中碰到了一个问题。
下面是错误链:
WARN: HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.
Unsaved transient entity: ([Person#<null>])
Dependent entities: ([[Address#<null>]])
Non-nullable association(s): ([Address.person])
Exception in thread "main" org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : Address.person -> Person
at org.hibernate.action.internal.UnresolvedEntityInsertActions.checkNoUnresolvedActionsAfterOperation(UnresolvedEntityInsertActions.java:123)
at org.hibernate.engine.spi.ActionQueue.checkNoUnresolvedActionsAfterOperation(ActionQueue.java:392)
at org.hibernate.internal.SessionImpl.checkNoUnresolvedActionsAfterOperation(SessionImpl.java:622)
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:684)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669)
at Main.main(Main.java:27)
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:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
相信hibernate使用者碰到这种情况还是很多的,不管是一对一、多对一等等都可能有这样的错误。先看错误链可以发现:在保存一个实体的时候,该保存实体关联的实体并没有保存或者说还是一个瞬态(transient)对象。这里我参考了这篇文章:hibernate One-To-One mapping using annotations
上面是一张一对一关联的UML图,该图中employee表有一个外键(ACCOUNT_ID)指向account表中的(ID),根据该图我写了一个demo(注意我这里是先建表后建类):
@Entity
@Table(name = "employee")
public class Employee {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "email")
private String email;
@Column(name = "first_name")
private String first_name;
@Column(name = "last_name")
private String last_name;
@OneToOne(targetEntity = Account.class)
@JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
private Account account;
public Employee() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFirst_name() {
return first_name;
}
public void setFirst_name(String first_name) {
this.first_name = first_name;
}
public String getLast_name() {
return last_name;
}
public void setLast_name(String last_name) {
this.last_name = last_name;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
@Entity
@Table(name = "account")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "account_number")
private String account_number;
public Account() {
}
public Account(String account_number) {
this.account_number = account_number;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAccount_number() {
return account_number;
}
public void setAccount_number(String account_number) {
this.account_number = account_number;
}
}
public class Main {
public static void main(final String[] args) throws Exception {
Configuration conf = new Configuration().configure();
SessionFactory sf = conf.buildSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
Account account = new Account();
account.setAccount_number("123456");
// session.save(account);
Employee e = new Employee();
e.setFirst_name("张");
e.setLast_name("三");
e.setEmail("hello@gmail.com");
e.setAccount(account);
session.save(e);
tx.commit();
session.close();
}
}
上面代码会出现异常,因为我注释了account保存会话,那么在程序进行到 session.save(e);时,发现e的account属性并没有保存:
Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Account
意思就是要保存Employee这个瞬态实体之前需要将Account刷新到数据库,解决办法有两种:
- 在Employee中@OneToOne增加级联属性(cascade = CasacadeType.ALL),这样在保存Employee实体前会保存它里面所有依赖的实体。
- 取消注释行。老老实实先保存Account后保存Employee。
注意:重复一遍,我是先建表后建类,那么此时我需要更改Account这个类,那么我必须要将之前存储在数据库中的数据清空。truncate employee,truncate account(错误,有外键依赖,那么只能老老实实delete from account where condition 了)。
一对一双向关联(无连接表)
根据前面给出的demo需要更改以下几个地方:
- Account 类中增加一:
@OneToOne(targetEntity = Employee.class, mappedBy = "account") private Employee employee; //这里省略 setter getter 方法
- Main方法修改如下:
// Account account = new Account(); // account.setAccount_number("123456"); // session.save(account); // Employee e = new Employee(); // e.setFirst_name("张"); // e.setLast_name("三"); // e.setEmail("hello@gmail.com"); // e.setAccount(account); // // session.save(e); Account a = session.get(Account.class, 4);//因为account在存储时,并未指定Employee,那么此时还能get到它吗? Employee e = a.getEmployee(); System.out.println(e.getFirst_name() + e.getLast_name());
先执行注释中的代码,然后执行下面代码,正确不。可能有些人看到开始的代码执行后一切正常没有问题,但是后面查询出错就蒙了,为什么呢?因为预先存储的account 实例时根本就没有执行setter设置它的person属性,所以这里hibernate执行getter方法自然就不行了。然后再看一下错误链:
在存储employee时并没有设置它的person属性,所以在取出该employee时想要获取依赖的person实体是无法做到的。
Hibernate: select account0_.id as id1_0_0_, account0_.account_number as account_2_0_0_, employee1_.id as id1_2_1_, employee1_.account_id as account_5_2_1_, employee1_.email as email2_2_1_, employee1_.first_name as first_na3_2_1_, employee1_.last_name as last_nam4_2_1_ from account account0_ left outer join employee employee1_ on account0_.id=employee1_.account_id where account0_.id=?
Exception in thread "main" java.lang.NullPointerException
at Main.main(Main.java:32)
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:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
修改方法:在session.save(e);后面增加account.setEmployee(e);session.save(account);OK!
mappedBy到底有什么作用呢?
这里我手画了一张图,但是看了该图应该有了一个最初的印象了。mappedBy的值一般是主表的属性名,那上面的例子来说吧,teacher作为主表,在Student中是这么写的:private Teacher teacher; 那么它的值就应该是“teacher”。这里我参考了这个问题:why to use mappedBy
参考地址:
hibernate One-To-One Unidirectional with foreign key association
hibernate one-to-one mapping with annotation
hibernate one to one relationship example