spring-data-jpa 入门二:常用技术使用之关联关系查询配置

系列文章:
spring-data-jpa 入门
spring-data-jpa 入门三:常用技术使用之复杂查询

在上文中我们介绍了spring-data-jpa简单的实现原理、简单的增删改查、以及简单的分页查找、排序。基本上通过上文介绍,我们可以写一些简单的增删改查了!但是日常开发中肯定不是仅仅单表查询。那么我们将继续探讨spring-data-jpa一些其他使用放法:

  • 常用技术使用
    • 多表关联关系查询
    • 原生sql查询
    • 动态sql(两种方式:Criteria、继承JpaSpecificationExecutor)
    • 多表多条件复杂查询
    • 动态条件查询(复杂条件 in、join 等)
  • 批量操作、EntityManager状态分析
  • 常用注解总结

多表关联关系查询

说道orm框架基本上离不开多表关联关系查询,例如:一对一,一对多,多对多。无论在mybatis或者hibernate中都是支持注解或者配置查询的,jpa也不例外,本文我们将着重说下spring-data-jpa关联关系查询,已经我在配置中碰到的一些坑。


oneToOne双向查询

有两张表 user、user_details,用户信息表和用户详情表是一对一的关系,用户详情表关联了用户信息表的ID,并配置了外键,具体表结构如下:

CREATE TABLE `user_info` (
  `id` int(11) NOT NULL auto_increment,
  `uname` varchar(20) default NULL,
  `unumber` varchar(20) default NULL,
  `password` varchar(255) default NULL,
  `address` varchar(255) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `user_details` (
  `id` int(11) NOT NULL auto_increment,
  `user_id` int(11) NOT NULL,
  `email` varchar(50) default NULL,
  `address` varchar(255) default NULL,
  PRIMARY KEY  (`id`),
  KEY `FKbp1gkvqqbmw0a9j4w53c9q5gm` (`user_id`),
  CONSTRAINT `FKbp1gkvqqbmw0a9j4w53c9q5gm` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户详情表';

Entity层
为了节省篇幅我就不把全部代码贴进来了,只贴关键代码
UserDetails中:

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private UserInfo userInfo;

在UserDetails实体中除了正常的结构属性外,我们添加了一个UserInfo对象,并使用了注解@OneToOne,其中cascade(串联、级联)配置了级联属性为所有,当然对应的还有保存、更新、删除、刷新、分离等

package javax.persistence;

public enum CascadeType {
    ALL,
    PERSIST,
    MERGE,
    REMOVE,
    REFRESH,
    DETACH;

    private CascadeType() {
    }
}

@JoinColumn 设置关联对应的字段,referencedColumnName 是设置关联表的具体某个字段,如果关联字段外键为主键的话这个属性可以忽略,整体这句话就是在说根据UserDetails中的user_id(外键),是与UserInfo 中的id(主键)是一一对应的。
UserInfo中:

    @JsonIgnoreProperties(value = "userInfo")//避免递归死循环
    @OneToOne(mappedBy = "userInfo", cascade = CascadeType.ALL)
    private UserDetails userDetails;

@OneToOne不用多说,mappedBy 这个属性就是表示该实体是在整个一对一关系中属于被维护端,想对应的userDetails是处于维护端(维护一对一的关系)。
@JsonIgnoreProperties(value = “userInfo”) 这个注解就是在一对一查询的时候出现递归死循环的,例如 我查询用户信息时候会自动加载用户详情信息,但是尴尬的是用户详情信息中也有用户信息,如此这般就成了递归死循环,报错StackOverflowError。

到这一对一关系配置就搞定了,查询用户信息的时候会执行查询对应的用户详情信息,我们配置的是双向查询,如果要单项的将对应属性注释掉就行。

OneToMany/ManyToOne双向

一对多和多对一关系演示我们用 用户(作者)、文章。一个作者可以发表多篇文章,一篇文章只能有一个作者。
用户表结构和一对一的用户信息表一样,文章表结构如下

CREATE TABLE `article` (
  `id` int(11) NOT NULL auto_increment,
  `user_id` int(11) default NULL,
  `context` longtext,
  `create_time` datetime default NULL,
  `update_time` datetime default NULL,
  PRIMARY KEY  (`id`),
  KEY `FKgfkys9w7qv3xcubq0drrayuu3` (`user_id`),
  CONSTRAINT `fore_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章信息表';

ArticleInfo 实体代码关键配置如下:

     @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, 
                 optional = false,fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private UserInfo userInfo;

@ManyToOne 表示这是个多对一的关系,cascade对应的级联操作,optional(任选)表示是否接受为空,false不接受。fetch( 取来) 加载模式,可选的有懒加载、立即加载。配置懒加载之后会有一定问题,需要另行配置,方案大概有四种,我选择的是在web.xml中配置过滤器。

<!-- 解决懒加载的问题,该过滤器放置到SpringMVC的过滤器的前面 -->
    <filter>
        <filter-name>OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

具体原因以及解决方法后面在解释。

@JoinColumn 同一对一解释。

对应的UserInfo实体中也需要添加配置:

    @JsonIgnoreProperties(value = "userInfo")//避免递归死循环
    @OneToMany(mappedBy = "userInfo", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<ArticleInfo> articleInfoList;

注意了因为用户是一 所以用@OneToMany注解,统同样的mappedBy表示在一对多的关系中,它是关系被维护着。

这样一对多双向关联就配置完毕,查询会将对应关系的实体也查出来。

ManyToMany

多对多的关系的演示选择的是 用户、权限。 一个用户可用有多个权限,一个权限对应多个用户。
用户表结构并没有变,权限表结构如下:

CREATE TABLE `role` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(25) NOT NULL,
  `create_time` datetime default NULL,
  `update_time` datetime default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限表';

可用看出权限表并没有与用户表有任何关联的地方,这是因为在JPA中规定@ManyToMany表示多对多的关系,维护与被维护关系给谁都行,谁是主表谁的表名在前,并且使用一个第三方的关联表来维护。关联关系表不需要我们建立,只需要使用@JoinTabl注解,jpa会自动根据规则进行关联表的生成。
关联关系的规则
表名默认是:主表名+下划线+从表名。(主表是指关系维护端对应的表,从表指关系被维护端对应的表)。这个关联表只有两个外键字段,分别指向主表ID和从表ID。
字段的名称默认为:主表名+下划线+主表中的主键列名,从表名+下划线+从表中的主键列名

对应用户实体实体配置:

    @JsonIgnoreProperties(value = "userInfoList")//避免递归死循环
    @ManyToMany
    @JoinTable(name = "user_info_role",
            joinColumns = {@JoinColumn(name = "user_info_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
    private List<Role> roleList;

@JsonIgnorePropertie 避免递归死循环
@ManyToMany 表示的是多对多的关系
@JoinTable 就是上面我们说的生成第三方关联关系表,其中name就是表名,joinColumns、inverseJoinColumns都是配置对应的列名,这个关联关系表中只有两列(上面生成规则说明)。

对应的权限实体关键配置如下:

     @ManyToMany(mappedBy = "roleList")
     private List<UserInfo> userInfoList;

这个配置就更简单了,到这一对多配置就搞定了,这里需要注意的是:

  1. 可以随意指定一方为关系维护端
  2. 多对多关系中一般不设置级联保存、级联删除、级联更新等操作
  3. 多对多的绑定关系、或者说第三方关联关系表中的数据是由维护的来写入的
  4. 解除关系仅仅只需要由维护端删除即可,第三方关联关系表中数据会自动删除
  5. 如果关联关系还在,是不能直接删除被维护端的

其实jpa本身就是开发方便,所有在这种关联关系配置中,非常简单。当然,中间在配置懒加载时候还是出现了点问题的。

到此,我们通过两篇文章的说明了解到spring-data-jpa是什么、简单的实现原理、简单的增删改查、以及简单的分页查找、排序、常用技术中的关联关系配置(一对一、一对多、多对多),下文我们将继续探讨:

  • 常用技术
    • 原生sql查询
    • 动态sql(两种方式:Criteria、继承JpaSpecificationExecutor)
    • 多表多条件复杂查询
    • 动态条件查询(复杂条件 in、join 等)
  • 批量操作、EntityManager状态分析
  • 常用注解总结
  • json解析时延迟加载问题
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章