• Spring Data JPA 学习记录1 -- 单向1:N关联的一些问题


    开新坑

    开新坑了(笑)....公司项目使用的是Spring Data JPA做持久化框架....学习了一段时间以后发现了一点值得注意的小问题.....与大家分享

    主要是针对1:N单向关联产生的一系列问题.

    @PrePersistent

    @PrePersist和@PreUpdate2个注解是我在公司项目里遇到的...公司是在save对象或者update对象的时候去影子表里同时做一个备份时用到的(公司项目很多地方我现在还是不懂...这里我是觉得他们是这么用的...)...然后公司的实体间的关系大部分是单向1:N.

    我在自己测试的时候发现这样做是有一点问题的...

    比如Student:Phone是单向1:N的关系...在Student里配置Phone的引用:

    1     @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    2     @JoinColumn(name = "id_student")
    3     List<Phone> phones;

    然后我照着公司的用法.写了个保存影子表的方法...

     1 public class ShadowAspect {
     2 
     3     @PrePersist
     4     public void saveShadow(Object o) {
     5         EntityManager em = SpringUtil.getApplicationContext().getBean(EntityManager.class);
     6         Shadowable entity = (Shadowable) o;
     7         Object saved = entity.getShadowObject();
     8         em.persist(saved);
     9     }
    10 
    11     @PreUpdate
    12     public void updateShadow(Object o) {
    13         EntityManager em = SpringUtil.getApplicationContext().getBean(EntityManager.class);
    14         Shadowable entity = (Shadowable) o;
    15         Object saved = entity.getShadowObject();
    16         em.merge(saved);
    17     }
    18 }

    要保存的Eneity都实现了Shadowable接口..能够得到相应的影子表的Entity..

    然后我想保存Student和Phone的时候同时保存相应的影子表...

    我在Service里创建了一个Student并关联Phone然后保存Student.....

     1     public Student saveOne() {
     2         Student s = new Student();
     3         s.setName("NO.99");
     4         Phone p1 = new Phone();
     5         Phone p2 = new Phone();
     6         p1.setPhoneName("NO91");
     7         p2.setPhoneName("NO92");
     8         s.setPhones(Arrays.asList(p1, p2));
     9         return studentRepository.save(s);
    10     }

    Hibernate打印出来的SQL如下:

    Hibernate: 
        insert 
        into
            StudentShadow
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            Student
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            PhoneShadow
            (phoneName) 
        values
            (?)
    Hibernate: 
        insert 
        into
            Phone
            (phoneName) 
        values
            (?)
    Hibernate: 
        insert 
        into
            PhoneShadow
            (phoneName) 
        values
            (?)
    Hibernate: 
        insert 
        into
            Phone
            (phoneName) 
        values
            (?)
    Hibernate: 
        update
            Phone 
        set
            id_student=? 
        where
            id=?
    Hibernate: 
        update
            Phone 
        set
            id_student=? 
        where
            id=?

    大家发现没有...前面的插入都很统一.影子表插一条数据,实体表插一条数据.

    但是最后2条update语句影子表没有执行..只有实体表被执行了...查看数据库可以发现就是影子表外键没有被update (StudentShadow表的主键)

    总结下:

    就是1:N单向关联的时候保存1端的时候做了这么几件事情..

    1.保存1端对象

    2.保存N端对象(没写外键)

    3.更新外键

     

    保存影子表的时候...1和2是可以在@PrePresistent里利用EntityManager做掉的.但是最后那个update语句不会触发@PreUpdate..并且在@PrePresistent方法里不能做到update...因为@PrePresistent方法里传进来的实体是没有任何关联的单一实体...在这个例子中传进来的Student是不带Phone关联的..所以你不可能去调用merge方法更新Phone的外键.。有点问题。

    这是一个坑爹的地方....

    级联删除

    级联删除时候遇到的问题与前面那个级联保存的问题是类似的.

    在删除1端的时候触发的SQL如下.

     1 Hibernate: 
     2     select
     3         student0_.id as id1_2_,
     4         student0_.name as name2_2_ 
     5     from
     6         Student student0_ 
     7     where
     8         student0_.id=?
     9 Hibernate: 
    10     select
    11         phones0_.id_student as id_stude3_2_0_,
    12         phones0_.id as id1_0_0_,
    13         phones0_.id as id1_0_1_,
    14         phones0_.phoneName as phoneNam2_0_1_ 
    15     from
    16         Phone phones0_ 
    17     where
    18         phones0_.id_student=?
    19 Hibernate: 
    20     update
    21         Phone 
    22     set
    23         id_student=null 
    24     where
    25         id_student=?
    26 Hibernate: 
    27     delete 
    28     from
    29         Phone 
    30     where
    31         id=?
    32 Hibernate: 
    33     delete 
    34     from
    35         Phone 
    36     where
    37         id=?
    38 Hibernate: 
    39     delete 
    40     from
    41         Student 
    42     where
    43         id=?

    因为删除的是1端..调用的是deleteById方法,所以会先根据ID找到对应的实体..然后因为设置了级联删除.还需要把多端找出来..因此还需要做个关联查询..

    然后很关键的一步..update操作..解除外键关联..

    再删除多端,最后删除1端...

     

    这么看起来没什么问题..但是当表结构比较特殊的时候会发生一些问题...

    在实际项目中多张表是这么设计的:

    A与B有关联...但是它们没有用外键约束....而是共享主键....

    意思就是A,B的主键是一样的(比如是同一个UUID)...但是从表结构上看他们之间没有任何关联(没有任何外键约束)...这样的设计我以前在其他地方实习的时候也看到过...可能设计的时候觉得不用外键关联会比较简单吧...

     

    然后级联删除的时候就会报错....因为JPA要先update,把关联设置成null以后才会delete..然后因为关联在表中是主键,所以这条语句会报错...这就导致级联删除的操作完成不了..只能手动一个个删除实体...比较麻烦...

    这也是很坑的....非常容易错...

    总结

    总的来说就是单向1:N关联的情况下..不管是save还是delete...都要触发update操作...而这个update语句要十分小心..可能会使关联出现null...导致操作失败....

    我觉得原因是因为单向关联的N端并不知道1端的情况(类中没有引用)..所以并不会去操作外键..而这个外键就需要JPA去消耗一条额外的update语句...

    这些问题在双向关联中就没有出现(因为双向关联的时候1端有N端的引用,所以会去维护外键..并且需要设置mappedBy属性)

    比如删除1端的时候的SQL(级联删除):

     1 Hibernate: 
     2     select
     3         student0_.id as id1_2_,
     4         student0_.name as name2_2_ 
     5     from
     6         Student student0_ 
     7     where
     8         student0_.id=?
     9 Hibernate: 
    10     select
    11         phones0_.id_student as id_stude3_2_0_,
    12         phones0_.id as id1_0_0_,
    13         phones0_.id as id1_0_1_,
    14         phones0_.phoneName as phoneNam2_0_1_,
    15         phones0_.id_student as id_stude3_0_1_ 
    16     from
    17         Phone phones0_ 
    18     where
    19         phones0_.id_student=?
    20 Hibernate: 
    21     delete 
    22     from
    23         Phone 
    24     where
    25         id=?
    26 Hibernate: 
    27     delete 
    28     from
    29         Phone 
    30     where
    31         id=?
    32 Hibernate: 
    33     delete 
    34     from
    35         Phone 
    36     where
    37         id=?
    38 Hibernate: 
    39     delete 
    40     from
    41         Student 
    42     where
    43         id=?

    没有出现解除外键关联的update....

     

    在级联保存的时候:

    Hibernate: 
        insert 
        into
            StudentShadow
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            Student
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            PhoneShadow
            (phoneName, id_student) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            Phone
            (phoneName, id_student) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            PhoneShadow
            (phoneName, id_student) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            Phone
            (phoneName, id_student) 
        values
            (?, ?)

    也没有出现更新关联的update

  • 相关阅读:
    [PHP动态]0001.关于 PHP 7 你必须知道的五件事
    [PHP工具推荐]0001.分析和解析代码的7大工具
    Java数据结构和算法(八)——递归
    Java数据结构和算法(七)——链表
    Java数据结构和算法(六)——前缀、中缀、后缀表达式
    Java数据结构和算法(五)——队列
    Java数据结构和算法(四)——栈
    Java数据结构和算法(三)——冒泡、选择、插入排序算法
    Java数据结构和算法(二)——数组
    Java数据结构和算法(一)——简介
  • 原文地址:https://www.cnblogs.com/abcwt112/p/4849416.html
Copyright © 2020-2023  润新知