org.hibernate.PropertyValueException: not-null property references a null or transient value : xxx

来源:互联网 发布:星型网络 编辑:程序博客网 时间:2024/05/22 04:50

今天本来时准备在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 -> Personat 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图

上面是一张一对一关联的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刷新到数据库,解决办法有两种:

  1. 在Employee中@OneToOne增加级联属性(cascade = CasacadeType.ALL),这样在保存Employee实体前会保存它里面所有依赖的实体。
  2. 取消注释行。老老实实先保存Account后保存Employee。
但是细心的朋友会发现我写的这个demo太简单了,只是一个简单的一对一关联,还是单向的,意思就是说我只能从Employee这一段访问到Account,但是从Account这一段无法访问到Employee,因为我只在Employee中定义了Account的setter、getter方法。那如果我想要知道一对一双向关联碰到这样的异常问题该怎么办呢?

注意:重复一遍,我是先建表后建类,那么此时我需要更改Account这个类,那么我必须要将之前存储在数据库中的数据清空。truncate employeetruncate account(错误,有外键依赖,那么只能老老实实delete from account where condition 了)。

一对一双向关联(无连接表)

根据前面给出的demo需要更改以下几个地方:

  1. Account 类中增加一:
    @OneToOne(targetEntity = Employee.class, mappedBy = "account")private Employee employee;//这里省略 setter getter 方法
  2. 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作用

这里我手画了一张图,但是看了该图应该有了一个最初的印象了。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



0 0