使用Hibernate存储具有OneToOne映射的实体

时间:2012-11-16 14:58:10

标签: hibernate foreign-keys

我有以下两个实体:

Person.java:

@Entity
@Table(name="PERSON")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private int id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "person", optional = false)
    private Address address;

    public Person(final String firstName, final String lastName, final Address address)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.address = address;
    }

    //Required by hibernate
    public Person()
    {
    }

    public int getId()
    {
        return id;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public String getLastName()
    {
        return lastName;
    }

    public Address getAddress() {
        return address;
    }

    public void setId(final int id)
    {
        this.id = id;
    }

    public void setFirstName(final String firstName)
    {
        this.firstName = firstName;
    }

    public void setLastName(final String lastName)
    {
        this.lastName = lastName;
    }

    public void setAddress(final Address address)
    {
        this.address = address;
    }

}

Address.java:

@Entity
@Table(name = "ADDRESS")
public class Address
{
    @Id
    private int personId;

    @MapsId
    @OneToOne()
    private Person person;

    @Column(name = "street")
    private String street;

    @Column(name = "town")
    private String town;

    @Column(name = "postcode")
    private String postcode;

    public Address()
    {
    }

    public Address(final String street, final String town, final String postcode)
    {
        this.street = street;
        this.town = town;
        this.postcode = postcode;
    }

    public Person getPerson()
    {
        return person;
    }

    public void setPerson(final Person person)
    {
        this.person = person;
        this.personId = person.getId();
    }

    public String getStreet()
    {
        return street;
    }

    public void setStreet(final String street)
    {
        this.street = street;
    }

    public String getTown()
    {
        return town;
    }

    public void setTown(final String town)
    {
        this.town = town;
    }

    public String getPostcode()
    {
        return postcode;
    }

    public void setPostcode(final String postcode)
    {
        this.postcode = postcode;
    }

}

我想将这两个实体映射到以下架构:

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(50) NULL DEFAULT NULL,
    last_name VARCHAR(50) NULL DEFAULT NULL,
    PRIMARY KEY (id)
);
CREATE TABLE address (
    person_id INT(10) NOT NULL,
    street VARCHAR(50) NULL DEFAULT NULL,
    town VARCHAR(50) NULL DEFAULT NULL,
    postcode VARCHAR(50) NULL DEFAULT NULL,
    FOREIGN KEY (person_id) REFERENCES person(id) ON DELETE CASCADE
);

请注意,地址表没有主键,只有person表上的外键。这意味着如果没有与之关联的人员,地址就不能存在,并且映射必须是一对一的。

不幸的是,Hibernate注释无法正常工作。这是我用来测试上面的代码:

public static Person save(Person person) {
    SessionFactory sf = new AnnotationConfiguration().configure().buildSessionFactory();
    Session session = sf.openSession();
    session.beginTransaction();
    Integer id = (Integer)session.save(person);
    person.setId(id);
    session.getTransaction().commit();
    session.close();
    return person;
}

@Test
public void testWritePerson() {
    Person person = new Person("Jack", "Bauer", new Address("123 Fake Street", "Springfield", "SP1F 123"));
    person.getAddress().setPerson(person);

    Person savedPerson = PersonTestUtil.save(person);
    assertFalse(
}

但我得到的错误是:

org.hibernate.id.IdentifierGenerationException: null id generated for:class Address

我尝试过各种不同注释的许多排列而没有成功。任何想法我如何修复上述代码中的注释,以便它正确地持久化人员和地址实体?如果您想自己测试完整代码,请参阅如何设置数据库的说明:

https://github.com/alexspurling/hibernate-test

3 个答案:

答案 0 :(得分:2)

我发现解决方案是从地址字段上Person的OneToOne注释中删除“optional = false”属性:

@OneToOne(cascade = CascadeType.ALL, mappedBy = "person")
private Address address;

我相信这是有效的,因为它可以防止Hibernate在生成Person的id之前尝试持久化地址。

请查看更新后的github存储库以获取完整的工作示例:https://github.com/alexspurling/hibernate-test

答案 1 :(得分:0)

我认为Address对象中的personID字段应该镜像person对象属性的状态。尝试对地址实体进行此更改:

public void setPerson(final Person person)
{
    this.person = person;
    this.personId = person.getId();
}

您需要确保在设置另一个时保持同步。

修改
好的我现在可以看到我的建议不起作用。基本问题是您需要使用Id of Person设置地址ID,但是人员ID是在持久化时自动生成的,因此您还没有该值。 Id on Address没有注释告诉JPA它实际上是Person的外键,所以它无法知道用生成的人ID设置它。
您需要做的是为JPA提供足够的信息,以便它知道保存Person然后使用该Id保存地址。我会尝试这样的事情。

@Entity
@Table(name = "ADDRESS")
public class Address
{
    //*** Use the Person object to get and set this value
    //*** @Id
    //*** private int personId;

    @Id
    @OneToOne()
    private Person person;

现在JPA应该有足够的注释信息来确定保存对象的顺序以及如何在地址上设置personId。
最后,在测试用例中将地址上的Person设置的行应该移动到Person构造函数,因为它是必不可少的步骤。如果没有这一行,JPA就不会有对象引用来链接这两者。

public Person(final String firstName, final String lastName, final Address address)
{
     this.firstName = firstName;
     this.lastName = lastName;
     this.address = address;
     this.address.setPerson(this);
} 

我认为应该这样做。

答案 2 :(得分:0)

尝试更改映射(其他任何问题都可以)。读这个

http://viralpatel.net/blogs/hibernate-one-to-one-mapping-tutorial-using-annotation/

这个人没问题

地址应该以这种方式加载

public class Address {

  @Id
  @Column(name="personId", unique=true, nullable=false)
  @GeneratedValue(generator="gen")
  @GenericGenerator(name="gen", strategy="foreign"
           , parameters=@Parameter(name="property", value="person"))
  private int personId;

  @OneToOne
  @PrimaryKeyJoinColumn
  private Person person
  ...

插入新值时,我们可以明确设置两端: