zoukankan      html  css  js  c++  java
  • 自关联中@ManyToOne、@OneToMany的使用

    一、一对一关系

    拥有端:

    @Entity
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Person {
        /**
         * 关系的拥有端存储一个被控端的一个外键。
         * 在这个例子中 Person表 中的 address_id 就是指向 address表 的一个外键,
         * 缺省情况下这个外键的字段名称,是以它指向的表的名称加下划线“_”加“id”组成的。
         * 当然我们也可以根据我们的喜好来修改这个字段,修改的办法就是使用 @JoinColumn 这个注解。
         * 在这个例子中我们可以将这个注解标注在 Person 类中的 Address 属性上去。
         */
        @Id
        private Long id;
        private String firstName;
        private String lastName;
        @OneToOne
        private Address address;
    }

    被控端:

    @Entity
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Address {
        /**
         * mappedBy = (Optional) The field that owns the relationship,即指向拥有端的(变量名).
         */
        @Id
        private Long id;
        private String state;
        private String city;
        private String street;
        private String zipCode;
        @OneToOne(mappedBy = "address")
        private Person person;
    }

    表结构:

    二、一对多关系

    拥有端:

    @Entity
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Comment {
        /**
         * 一对多关系中,一般都是选择“多”这端作为拥有端,因为可以很方便把“一”这端作为一个属性包含进来。
         */
        @Id
        private Integer id;
        private Integer year;
        private boolean approved;
        private String content;
        @ManyToOne
        private Post post;
    }

    被控端:

    @Entity
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Post {
        /**
         * 一对多的被控端,往往是“一”这端,需要以List的方式将“多”端添加进来
         */
        @Id
        private Integer id;
        private String title;
        private String content;
        @OneToMany(mappedBy = "post")
        private List<Comment> comments;
    }

    表结构:

    三、自关联

    事实上,在国内互联网领域很少使用外键,database也不会交给ORM管理,table结构会保持一定程度的字段冗余。个人不太习惯用JPA管理映射关系,思维和经验都没有转变过来,但是在多级分类的表结构(省市区表、商品分类表)当中,往往是以自关联的方式组织树形结构数据的,不需要建立外键也可以发挥JPA的优势。

    @Entity
    @Table
    @Data
    public class Area {
        @Id
        @GeneratedValue
        private Long id;
    
        // 区域名
        private String name;
    
        // 父区域
        @ManyToOne(fetch = FetchType.LAZY)  // 相当于把2个Area写在一处;
        @JsonIgnore                         // 忽略父类属性JSON序列化;
        private Area parent;
    
        // 子区域,一个区域信息可以有多级子区域,比如 : 广东省 - 广州市 - 天河区
        @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
        private List<Area> children;
    }

    Repository接口:

    public interface AreaRepository extends JpaRepository<Area, Long> {}

    利用测试方法把数据写入数据库:

        @Autowired
        private AreaRepository areaRepository;
        
        @Test
        public void addArea() {
    
            // 广东省 (顶级区域)
            Area guangdong = new Area();
            guangdong.setName("广东省");
            areaRepository.save(guangdong);
    
            //广东省 下面的 广州市(二级区域)
            Area guangzhou = new Area();
            guangzhou.setName("广州市");
            guangzhou.setParent(guangdong);
            areaRepository.save(guangzhou);
    
            //广州市 下面的 天河区(三级区域)
            Area tianhe = new Area();
            tianhe.setName("天河区");
            tianhe.setParent(guangzhou);
            areaRepository.save(tianhe);
    
            //广东省 下面的 湛江市(二级区域)
            Area zhanjiang = new Area();
            zhanjiang.setName("湛江市");
            zhanjiang.setParent(guangdong);
            areaRepository.save(zhanjiang);
    
            //湛江市 下面的 霞山区(三级区域)
            Area xiashan = new Area();
            xiashan.setName("霞山区");
            xiashan.setParent(zhanjiang);
            areaRepository.save(xiashan);
        }

    最后我们可以得到如下的表结构:

    id     name        parent_id
    1    广东省         null
    2    广州市         1
    3    天河区         2
    4    湛江市         1
    5    霞山区         4

    添加一个最简单的RESTful接口,主要是实现JSON序列化查看结果,

    @RestController
    public class OutputController {
        @Autowired
        private AreaRepository areaRepository;
    
        @GetMapping("area")
        public Area getArea() {
            List<Area> areas = areaRepository.findAll();
            return areas.get(0);
        }
    
        @GetMapping("areas")
        public List<Area> getAreas() {
            return areaRepository.findAll();
        }
    }

    返回结果:

    // 20200611004952
    // http://localhost:8080/area
    
    {
      "id": 1,
      "name": "广东省",
      "children": [
        {
          "id": 2,
          "name": "广州市",
          "children": [
            {
              "id": 3,
              "name": "天河区",
              "children": [
                
              ]
            }
          ]
        },
        {
          "id": 4,
          "name": "湛江市",
          "children": [
            {
              "id": 5,
              "name": "霞山区",
              "children": [
                
              ]
            }
          ]
        }
      ]
    }
    View Code
    // 20200611005129
    // http://localhost:8080/areas
    
    [
      {
        "id": 1,
        "name": "广东省",
        "children": [
          {
            "id": 2,
            "name": "广州市",
            "children": [
              {
                "id": 3,
                "name": "天河区",
                "children": [
                  
                ]
              }
            ]
          },
          {
            "id": 4,
            "name": "湛江市",
            "children": [
              {
                "id": 5,
                "name": "霞山区",
                "children": [
                  
                ]
              }
            ]
          }
        ]
      },
      {
        "id": 2,
        "name": "广州市",
        "children": [
          {
            "id": 3,
            "name": "天河区",
            "children": [
              
            ]
          }
        ]
      },
      {
        "id": 3,
        "name": "天河区",
        "children": [
          
        ]
      },
      {
        "id": 4,
        "name": "湛江市",
        "children": [
          {
            "id": 5,
            "name": "霞山区",
            "children": [
              
            ]
          }
        ]
      },
      {
        "id": 5,
        "name": "霞山区",
        "children": [
          
        ]
      }
    ]
    View Code

    经过以上处理之后,我们很容易得到一个类似二叉树的递归结构:

    • 根据每一条数据库纪录进行递归查找,起点是自己,直到最后一级子区域;
    • 在多层级且层级数未知的情况,要用SQL语句获得类似结果,还蛮考验思维和SQL基础的;
    • 存在ORM的“N+1”问题
    Hibernate: 
        /* select
            generatedAlias0 
        from
            Area as generatedAlias0 */ select
                area0_.id as id1_0_,
                area0_.name as name2_0_,
                area0_.parent_id as parent_i3_0_ 
            from
                area area0_
    Hibernate: 
        select
            children0_.parent_id as parent_i3_0_0_,
            children0_.id as id1_0_0_,
            children0_.id as id1_0_1_,
            children0_.name as name2_0_1_,
            children0_.parent_id as parent_i3_0_1_ 
        from
            area children0_ 
        where
            children0_.parent_id=?
    Hibernate: 
        select
            children0_.parent_id as parent_i3_0_0_,
            children0_.id as id1_0_0_,
            children0_.id as id1_0_1_,
            children0_.name as name2_0_1_,
            children0_.parent_id as parent_i3_0_1_ 
        from
            area children0_ 
        where
            children0_.parent_id=?
    Hibernate: 
        select
            children0_.parent_id as parent_i3_0_0_,
            children0_.id as id1_0_0_,
            children0_.id as id1_0_1_,
            children0_.name as name2_0_1_,
            children0_.parent_id as parent_i3_0_1_ 
        from
            area children0_ 
        where
            children0_.parent_id=?
    Hibernate: 
        select
            children0_.parent_id as parent_i3_0_0_,
            children0_.id as id1_0_0_,
            children0_.id as id1_0_1_,
            children0_.name as name2_0_1_,
            children0_.parent_id as parent_i3_0_1_ 
        from
            area children0_ 
        where
            children0_.parent_id=?
    Hibernate: 
        select
            children0_.parent_id as parent_i3_0_0_,
            children0_.id as id1_0_0_,
            children0_.id as id1_0_1_,
            children0_.name as name2_0_1_,
            children0_.parent_id as parent_i3_0_1_ 
        from
            area children0_ 
        where
            children0_.parent_id=?
    View Code

    四、ORM框架的“N+1”问题 

    下面两篇文章很好的解释了ORM的“N+1”问题:

    • JPA:https://www.cnblogs.com/google4y/p/3455534.html
    • Django:https://www.the5fire.com/what-is-orm-n+1.html

    在解决N+1问题上,Django比JPA要方便很多,在ORM语法和查询灵活度上,感觉也是Django更胜一筹。后续,我们将利用@NamedEntityGraph来解决“N+1”问题。

    五、注意事项:

    • 关于@OntToMany等一对多映射属性,如果想要用Set<T>代替List<T>时,请在类上添加注解@EqualsAndHashCode(exclude = "children"),否则序列化时会因为@Data注解自动生成的equals和hashCode方法而发生Could not write JSON: Infinite recursion错误。
    • 如果在sout打印Area对象时,发生java.lang.StackOverflowError错误(堆栈溢出),可以用@ToString.Exclude排除掉children字段解决。

    参考链接

    https://www.cnblogs.com/ealenxie/p/9800818.html

    https://blog.csdn.net/qq_22327273/article/details/88578187

  • 相关阅读:
    MySQL基础语句【学习笔记】
    减一技术应用:生成排列与幂集
    Java实现动态规划法求解0/1背包问题
    LODOP中ADD_PRINT_TABLE、HTM、HTML表格自动分页测试
    LODOP设置判断后执行哪个
    Lodop删除语句Deleted只能内嵌设计维护可用
    Lodop、c-lodop注册与角色简短问答
    LODOP暂存、应用、复原 按钮的区别
    JS判断语句 注意多句时加大括号 回调函数LODOP兼顾写法
    LODOP、C-Lodop简短排查语句
  • 原文地址:https://www.cnblogs.com/echo1937/p/13089300.html
Copyright © 2011-2022 走看看