您好,欢迎来到华佗健康网。
搜索
您的当前位置:首页SpringDataJPA:关联关系(外键)

SpringDataJPA:关联关系(外键)

来源:华佗健康网
SpringDataJPA:关联关系(外键)

JAVA 8

Spring Boot 2.5.3MySQL 5.7.21--- ⽬录

0、概述

表关联关系,即外键.

包括:⼀对⼀、⼀对多(多对⼀)、多对多。难点:级联更新、级联删除在MySQL中,仅InnoDB⽀持 外键。本⽂分别介绍各种关联关系的使⽤。

外键:被引⽤的列,要么是 主键 ,要么 具有 唯⼀性约束(UNIQUE)。

MySQL的外键:

创建语法:1)CREATE TABLE、2)ALTER TABLE

查看:1)SHOW CREATE TABLE tbl;、2)SHOW INDEX FROM tbl;、3)DESC tbl; 删除:1)ALTER TABLE tbl DROP FOREIGN KEY fk_id;使⽤ mysql> help create table; 可以查看创建外键的语法:

CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name (create_definition,...) [table_options] [partition_options]...

create_definition:

col_name column_definition ...

| [CONSTRAINT [symbol]] FOREIGN KEY

[index_name] (index_col_name,...) reference_definition...

reference_definition:

REFERENCES tbl_name (index_col_name,...)

[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE] [ON DELETE reference_option] [ON UPDATE reference_option]...

reference_option:

RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT

外键部分简版如下:

[CONSTRAINT [symbol]] FOREIGN KEY [index_name] (index_col_name,...) REFERENCES tbl_name (index_col_name,...)

[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE]

[ON DELETE [RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT]] [ON UPDATE [RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT]]

ON DELETE 级联删除配置,ON UPDATE 级联更新配置。相关参数说明:

默认值

RESTRICTCASCADESET NULLNO ACTIONSET

DEFAULT

spring boot⼯程:jpa-mysqlMySQL配置:

# MySQL on Ubuntu

spring.datasource.url=jdbc:mysql://mylinux:3306/jpa?serverTimezone=Asia/Shanghaispring.datasource.username=springuserspring.datasource.password=ThePassword

#spring.datasource.driver-class-name =com.mysql.jdbc.Driver # This is deprecatedspring.datasource.driver-class-name =com.mysql.cj.jdbc.Driverspring.jpa.hibernate.ddl-auto=update

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect# 打开使⽤过程中执⾏的SQL语句spring.jpa.show-sql: true

拒绝主表删除或修改级联删除 或 级联更新

主表删除或更新时,使⽤NULL值替代从表中对应的记录

注意,从表字段设置为 NOT NULL时⽆效。同 默认值 RESTRICT设置默认值

参考⽂档1 说 InnoDB不⽀持(待确定)

建⽴数据库(jpa)语句:

CREATE DATABASE IF NOT EXISTS jpa DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;

1、⼀对⼀

三个实体类:OtoBasic、OtoExt、OtoName,

其中,OtoBasic是主表(被关联表),OtoExt、OtoName是从表;OtoExt和OtoBasic的id字段建⽴ ⼀对⼀关联,OtoName和OtoBasic的name字段建⽴ ⼀对⼀关联。建⽴关联使⽤的注解:

@OneToOne、@JoinColumn、@ForeignKey

@Entity@Data

public class OtoBasic {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /**

* 名称:唯⼀、⾮空 */

@Column(columnDefinition = \"VARCHAR(100) UNIQUE NOT NULL\") private String name;

@OneToOne(mappedBy = \"basic\ private OtoExt ext;

@OneToOne(mappedBy = \"basic\ private OtoName otoName; }

@Entity@Data

public class OtoExt { /** * ⾃增 */

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * 详情 */

@Column(columnDefinition = \"VARCHAR(100) NOT NULL\") private String detail;

// 不能有!不需要!// private Long basicId;

// 1、⽆法删除

// @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}) // 2、⽆法删除 发⽣异常

// @OneToOne(cascade = {CascadeType.REFRESH, CascadeType.REMOVE}) // 3、⽆法删除 发⽣异常

// @OneToOne(cascade = {CascadeType.REMOVE})

// 在主表使⽤ CascadeType.REMOVE 才有效:删除主表记录,级联删除从表,,⽽不是在从表设置 @OneToOne

@JoinColumn(name = \"basic_id\ value=ConstraintMode.CONSTRAINT)) private OtoBasic basic; }

@Entity@Data

public class OtoName {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

// 注意,需要⼿动设置外键,添加选项:ON DELETE CASCADE ON UPDATE CASCADE // 才可以级联更新 @OneToOne

@JoinColumn(name = \"basic_name\

foreignKey = @ForeignKey(name=\"fk_basic_name\ private OtoBasic basic; }

上⾯的 关联关系 是双向的:主表⽤到了 @OneToOne 的 mappedBy 属性,其值为 从表的属性名(对象名)。

除了上⾯这种 双向 使⽤,也可以和从表⼀样 使⽤ @JoinColumn 的name、referencedColumnName 实现,但取值和从表相反(未试验)。

建⽴实体类的Repository:新建接⼝,继承JpaRepository接⼝即可。

public interface OtoBasicDAO extends JpaRepository {}

public interface OtoExtDAO extends JpaRepository {}

public interface OtoNameDAO extends JpaRepository {}

启动项⽬,即可在⼯程中建⽴三个实体类对应的表&外键:

启动⽇志的SQL语句:建表、建约束等

Hibernate: create table oto_basic (id bigint not null auto_increment, name VARCHAR(100) UNIQUE NOT NULL, primary key (id)) engine=InnoDBHibernate: create table oto_ext (id bigint not null auto_increment, detail VARCHAR(100) NOT NULL, basic_id bigint, primary key (id)) engine=InnoDBHibernate: create table oto_name (id bigint not null auto_increment, basic_name VARCHAR(100) UNIQUE NOT NULL, primary key (id)) engine=InnoDBHibernate: alter table oto_basic drop index UK_qamvvb8udcjeoj4q2mv7r63tk

Hibernate: alter table oto_basic add constraint UK_qamvvb8udcjeoj4q2mv7r63tk unique (name)

Hibernate: alter table oto_ext add constraint fk_basic_id foreign key (basic_id) references oto_basic (id)

Hibernate: alter table oto_name add constraint fk_basic_name foreign key (basic_name) references oto_basic (name)数据表结构:show create table XXXmysql> show create table oto_basic \\G

*************************** 1. row *************************** Table: oto_basic

Create Table: CREATE TABLE `oto_basic` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`),

UNIQUE KEY `name` (`name`),

UNIQUE KEY `UK_qamvvb8udcjeoj4q2mv7r63tk` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)

mysql>

mysql> show create table oto_ext \\G

*************************** 1. row *************************** Table: oto_ext

Create Table: CREATE TABLE `oto_ext` (

`id` bigint(20) NOT NULL AUTO_INCREMENT, `detail` varchar(100) NOT NULL,

`basic_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`),

KEY `fk_basic_id` (`basic_id`),

CONSTRAINT `fk_basic_id` FOREIGN KEY (`basic_id`) REFERENCES `oto_basic` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)

mysql>

mysql> show create table oto_name \\G

*************************** 1. row *************************** Table: oto_name

Create Table: CREATE TABLE `oto_name` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `basic_name` varchar(100) NOT NULL, PRIMARY KEY (`id`),

UNIQUE KEY `basic_name` (`basic_name`),

CONSTRAINT `fk_basic_name` FOREIGN KEY (`basic_name`) REFERENCES `oto_basic` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)mysql>

说明:1、⾃定义外键名称外键体现在 从表 oto_ext、oto_name 中:fk_basic_id、fk_basic_name (名称 由 注解 @JoinColumn 的属性 @ForeignKey 确定,否则是⼀串⽆意义字符串,如oto_baisc的 UK_qamvvb8udcjeoj4q2mv7r63tk 键)。2、从表的新字段建⽴外键后,从表 多了响应的字段:oto_ext 中的 basic_id,oto_name 中的 basic_name,此时不能在 从表实体类中 添加对应的 属性。3、级联删除删除主表oto_baisc中的记录,此时,级联删除 从表oto_ext中的内容。

需要使⽤ @OneToOne注解 的 cascade属性,其值为 CascadeType.ALL 或 包括 CascadeType.REMOVE 都可以。另外,还有⼀个 orphanRemoval 属性,默认为false——不级联删除,设置为true后,即可 级联删除。cascade 或 orphanRemoval 只要有⼀个 允许级联删除,即可执⾏ 级联删除。4、级联更新更新主表oto_baisc 的 name字段时,希望从表 oto_name 对应的 basic_name 也更新。

但是,默认建⽴的外键 没有配置 ON UPDATE CASCADE,导致⽆法级联更新,也会阻⽌ 主表更新。尝试多种 JPA⽅式 添加 ON UPDATE CASCADE 选项都失败了,最终只能⼿动操作:先删除oto_name的外键 fk_basic_name,再新建⼀个 同名的外键。

ALTER TABLE oto_name DROP FOREIGN KEY fk_basic_name;

ALTER TABLE oto_name ADD CONSTRAINT fk_basic_name FOREIGN KEY (basic_name) REFERENCES oto_basic(name) ON DELETE CASCADE ON UPDATE CASCADE;

这样就可以级联删除了。

⼀对⼀案例:1)⼀夫⼀妻2)

2、⼀对多(多对⼀)

两个实体类:OtmBasic、OtmMany,其中,⼀个OtmBasic可以对应多个OtmMany。建⽴关联使⽤的注解:

@ManyToOne、@OneToMany、@JoinColumn、@ForeignKey、@OrderBy(结合@OneToMany使⽤)

@Entity@Data

public class OtmBasic {

/** * ⾃增 */ @Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

@Column(columnDefinition = \"VARCHAR(100) UNIQUE NOT NULL\") private String name;

@Column(insertable = false, columnDefinition = \"DATETIME DEFAULT NOW()\") private Date createTime;

// 1、不能删除

// @OneToMany(mappedBy = \"basic\ // 2、可以删除

@OneToMany(mappedBy = \"basic\ // 按 创建时间倒序 排列

@OrderBy(value = \"create_time DESC\") private List manyList;

// 构造函数

public OtmBasic() {}

public OtmBasic(String name) { this.name = name; } }

@Entity@Data

public class OtmMany {

/** * ⾃增 */ @Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

/**

* house:房⼦ */

@Column(columnDefinition = \"VARCHAR(100) UNIQUE NOT NULL\") private String house;

@Column(insertable = false, columnDefinition = \"DATETIME DEFAULT NOW()\") private Date createTime;

@ManyToOne

@JoinColumn(name=\"basic_id\ private OtmBasic basic; }

建⽴实体类的Repository:新建接⼝,继承JpaRepository接⼝即可。public interface OtmBasicDAO extends JpaRepository {}

public interface OtmManyDAO extends JpaRepository {}

启动项⽬,数据库中建⽴了实体类相关的表和外键:

项⽬⽇志:

Hibernate: create table otm_basic (id bigint not null auto_increment, create_time DATETIME DEFAULT NOW(), name VARCHAR(100) UNIQUE NOT NULL, primary key (id)) engine=InnoDB

Hibernate: create table otm_many (id bigint not null auto_increment, create_time DATETIME DEFAULT NOW(), house VARCHAR(100) UNIQUE NOT NULL, basic_id bigint, primary key (id)) engine=InnoDB

Hibernate: alter table otm_many add constraint fk_basic_id_otm foreign key (basic_id) references otm_basic (id)数据表:

mysql> show create table otm_basic \\G

*************************** 1. row *************************** Table: otm_basic

Create Table: CREATE TABLE `otm_basic` ( `id` bigint(20) NOT NULL AUTO_INCREMENT,

`create_time` datetime DEFAULT CURRENT_TIMESTAMP, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`),

UNIQUE KEY `name` (`name`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)

mysql> show create table otm_many \\G

*************************** 1. row *************************** Table: otm_many

Create Table: CREATE TABLE `otm_many` ( `id` bigint(20) NOT NULL AUTO_INCREMENT,

`create_time` datetime DEFAULT CURRENT_TIMESTAMP, `house` varchar(100) NOT NULL, `basic_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`),

UNIQUE KEY `house` (`house`), KEY `fk_basic_id_otm` (`basic_id`),

CONSTRAINT `fk_basic_id_otm` FOREIGN KEY (`basic_id`) REFERENCES `otm_basic` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)mysql>

说明:

1、外键名称冲突在前⾯试验 ⼀对⼀关联 时,建⽴了外键 fk_basic_id。在本试验中,最开始也想建⽴⼀个 同名的外键的,但是,发⽣了冲突:

Hibernate: alter table otm_many add constraint fk_basic_id foreign key (basic_id) references otm_basic (id)2021-10-18 20:46:44.332 WARN 8768 --- [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl :

GenerationTarget encountered exception accepting command : Error executing DDL \"alter table otm_many add constraint fk_basic_id foreign key (basic_id) references otm_basic (id)\" via JDBC Statement

org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL \"alter table otm_many add constraint fk_basic_id foreign key (basic_id) references otm_basic (id)\" via JDBC Statement

将其更改为 fk_basic_id_otm 后,冲突消失:

// OtmMany.java@ManyToOne

@JoinColumn(name=\"basic_id\private OtmBasic basic;

2、级联删除使⽤ 主表的@OneToMany 的 cascade属性,或 orphanRemoval属性,同 @OneToOne。3、数据排序⼀对多中,“⼀”的⼀⽅可以使⽤@OrderBy 对结果排序。// OtmBasic.java

@OneToMany(mappedBy = \"basic\@OrderBy(value = \"create_time DESC\")private List manyList;

⼀对多案例:1)⼀个家庭 多套房产2)

3、多对多

两个实体类:MtmBlog、MtmBlogTag,其中,博客 和 标签 是 多对多的关系。建⽴关联使⽤的注解:@ManyToMany、@JoinTable、@JoinColumn、@ForeignKey、@OrderBy

@Entity@Data

public class MtmBlog {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

@Column(columnDefinition = \"VARCHAR(100) NOT NULL\") private String title;

@Column(columnDefinition = \"VARCHAR(1000) NOT NULL\") private String content;

@ManyToMany(cascade = {CascadeType.ALL}) @JoinTable(

name = \"mtm_blog_tag_rel\

joinColumns = @JoinColumn(name = \"blog_id\ foreignKey = @ForeignKey(name=\"fk_blog_tag_rel_blog\")),

inverseJoinColumns = @JoinColumn(name = \"tag_id\ foreignKey = @ForeignKey(name=\"fk_blog_tag_rel_tag\")) ) // 排序

@OrderBy(value = \"tag DESC\") private List tags; }

@Entity@Data

public class MtmBlogTag {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

// 唯⼀性约束

@Column(columnDefinition = \"VARCHAR(100) UNIQUE NOT NULL\") private String tag;

// 双向多对多

@ManyToMany(mappedBy = \"tags\") // 排序

@OrderBy(value = \"title DESC\") private List blogs; }

建⽴实体类的Repository:新建接⼝,继承JpaRepository接⼝即可。

public interface MtmBlogDAO extends JpaRepository {}

public interface MtmBlogTagDAO extends JpaRepository {

MtmBlogTag findByTag(String tag); }

启动项⽬,⾃动建⽴数据表:2个实体类,但是建⽴了3个数据表,其中⼀个为 数据关联表,多对多时需要。

# 项⽬⽇志

Hibernate: create table mtm_blog_tag_rel (blog_id bigint not null, tag_id bigint not null) engine=InnoDBHibernate: create table mtm_blog (id bigint not null auto_increment, content VARCHAR(1000) NOT NULL, title VARCHAR(100) NOT NULL, primary key (id)) engine=InnoDB

Hibernate: create table mtm_blog_tag (id bigint not null auto_increment, tag VARCHAR(100) UNIQUE NOT NULL, primary key (id)) engine=InnoDB

Hibernate: alter table mtm_blog_tag_rel add constraint fk_blog_tag_rel_tag foreign key (tag_id) references mtm_blog_tag (id)Hibernate: alter table mtm_blog_tag_rel add constraint fk_blog_tag_rel_blog foreign key (blog_id) references mtm_blog (id)数据表:3个

mysql> show create table mtm_blog \\G

*************************** 1. row *************************** Table: mtm_blog

Create Table: CREATE TABLE `mtm_blog` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `content` varchar(1000) NOT NULL, `title` varchar(100) NOT NULL, PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)

mysql> show create table mtm_blog_tag \\G

*************************** 1. row *************************** Table: mtm_blog_tag

Create Table: CREATE TABLE `mtm_blog_tag` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `tag` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `tag` (`tag`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec)

mysql>

mysql> show create table mtm_blog_tag_rel \\G

*************************** 1. row *************************** Table: mtm_blog_tag_rel

Create Table: CREATE TABLE `mtm_blog_tag_rel` ( `blog_id` bigint(20) NOT NULL, `tag_id` bigint(20) NOT NULL,

KEY `fk_blog_tag_rel_tag` (`tag_id`), KEY `fk_blog_tag_rel_blog` (`blog_id`),

CONSTRAINT `fk_blog_tag_rel_blog` FOREIGN KEY (`blog_id`) REFERENCES `mtm_blog` (`id`), CONSTRAINT `fk_blog_tag_rel_tag` FOREIGN KEY (`tag_id`) REFERENCES `mtm_blog_tag` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.01 sec)mysql>

说明:1、关联表不能建⽴实体类两个实体类,⽣成了3个表:mtm_blog、mtm_blog_tag、mtm_blog_tag_rel,其中,mtm_blog_tag_rel 是 数据关联表。注,在 参考⽂档1 中,关联表 也需要建⽴实体类,但在试验中发现,⼯程⾥⾯不能有 关联表的实体类!否则,启动报错:

2021-10-20 14:26:12.8 ERROR 10496 --- [ main] j.LocalContainerEntityManagerFactoryBean :

Failed to initialize JPA EntityManagerFactory: No identifier specified for entity: org.lib.jpamysql.mtm.MtmBlogTagRel2021-10-20 14:26:12.8 WARN 10496 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource

[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: org.lib.jpamysql.mtm.MtmBlogTagRel

⼯程没有启动成功,⼀个数据表也没有建⽴成功。2、数据排序使⽤@OrderBy,双⽅都可以使⽤。3、关联表默认名称@JoinTable 可以不使⽤ name属性,此时,建⽴的 关联表名称是 ⾃动的。在本试验中,会⾃动建⽴ 关联表 mtm_blog_tags:

多对多案例:来⾃博客园1)博客、标签2)⽤户、⾓⾊3)⽼师、学⽣4)游客、度假胜地5)

》》》全⽂完《《《来⾃博客园

源码:,其中oto、otm、mtm包下的代码。来⾃博客园

外键 很好⽤,但是,使⽤不当会造成严重问题。

在《阿⾥巴巴Java开发⼿册》(参考⽂档3 可以下载)中,不建议使⽤外键:

参考⽂档

1、书《Spring Data JPA 从⼊门到精通》 by 张振华2、书《MySQL数据库 原理、设计与应⽤》 by ⿊马程序员3、4、

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.com 版权所有 湘ICP备2023021991号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务