转载

QQA: Hibernate 为什么需要手工管理双向关联

Hibernate/JPA 中如果两个 Entity 之间的关联是双向的(不论是 @ManyToMany@OneToMany 还是 @OneToOne ),都需要手动管理关联,为什么?

  • 调用 entityManager.persist 保存对象时 Hibernate/JPA 不会直接执行 SQL,而会等到 entityManager.flush 或事务 commit 时完成。
  • 同理 entityManager.load 也可能只会从内存中获取对象(可以认为是某种缓存)。
  • 如果不手动管理双向关联,则从内存获取的对象并不会反映数据库中的映射关系。

双向关联的本质是告诉 Hibernate 让两个实体共用一张数据库表(或表结构)。

这里以 @ManyToMany 为例(参考 Hibernate User Guide ) :有两个实体 PersonAddress ,一个 Person 可以拥有多个 Address,而一个 Address 也可以属于多个 Person。于是设计实体如下:

@Entity
public static class Person {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany
    private List<Address> addresses = new ArrayList<>();

    // ... omit all other stuff
}

@Entity
public static class Address {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany
    private List<Person> owners = new ArrayList<>();

    // ... omit all other stuff
}

问题来了,我们应该创建一张关联表还是两张呢?其实取决于使用业务含义。即如果 Personaddresses 的含义是“人的居住地址”,而 Address 中的 owners 与之对应,表达的是“地址上居住的人”,则它们应该是一张关联表。但如果 Addressowners 表达的是“地址的主人(如房东)”,则二者就不应该共用一张关联表。

如何告诉 Hibernate 需要共用一张表呢?通过 mappedBy

@Entity
public static class Person {
    @ManyToMany
    private List<Address> addresses = new ArrayList<>();
    // ... omit all other methods
}

@Entity
public static class Address {
    @ManyToMany(mappedBy = "addresses")
    private List<Person> owners = new ArrayList<>();

    // ... omit all other methods
}

(mappedBy = "addresses") 的含义是这个字段与 Person 中的 addresses 字段共用表结构。

这里最后重点是双向关系一定是从属关系,有一方是 owner,另一方是 follower(标记了 mappedBy 的一方)。只有在 owner 这方添加关联并保存时,Hibernate 才会存入关联表,反之不会。例如我们只能通过 person.addAddress() 并保存 person 的方式来完成添加关联而不能用 address.addPerson() 后保存 address 的方式。

手工管理关联是什么意思

例如我们在实现 Person.addAddress 时,需要这样实现:

@Entity
public static class Person {
    //...omit other fields

    @ManyToMany
    private List<Address> addresses = new ArrayList<>();

    public void addAddress(Address address) {
        addresses.add( address );
        address.getOwners().add( this );
    }

    public void removeAddress(Address address) {
        addresses.remove( address );
        address.getOwners().remove( this );
    }
    // ... omit all other methods
}

即在为 person 添加 address 时,我们需要将当前的 person 添加到 address的 owners 字段中;删除时相似。“管理关联”表示需要在代码级别来管理关联双方实体的联系。

如果从数据库的角度思考,我们知道 PersonAddress 的关系是存储在一张关联表里的,一个关联存入这张表后,不论哪一方读取,都应该反映出新的关联关系,而在 Hibernate 这一层,却需要我们显式地(从另一方的 set )中添加/删除这个关联,显得不可思议。

另外,注意我们往 set 中添加 addressperson 时,需要我们正确的实现 PersonAddressequalshashCode 方法,这是另一个坑,这里就不深入了。

为什么需要手工管理

终于到了“为什么”部分了,首先是如果不手工管理会发生什么。考虑下面的测试:

@Test
@Transactional
public void test() {
    Person person = repository.findPersonById(1);
    Address address = repository.findAddressById(20);
    person.getAddresses.add(address);
    repository.save(person);

    System.out.println(address.getOwners().size()) // what is the result?

    Address address = repository.findAddressById(20);
    System.out.println(address.getOwners().size()) // what is the result?
}

答案是两个 size 都为 0

save
find

注意上面说的是一般的情况,什么时候执行 SQL 取决于具体的配置,一般会在事务前的 commit

因此,如果在 save 之后还需要使用到 address ,就不要期待它会立即反映出数据库中的修改;反之,如果 save 之后就不再使用到 address ,那即使不手工管理(同步) 关联关系也不会有多大影响。

原文  http://lotabout.me/2018/QQA-Why-should-bidirectional-associations-be-managed-manually/
正文到此结束
Loading...