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

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

上面是一张一对一关联的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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值