关联关系注解
- Spring Data JPA 之 一对一,一对多,多对多 关系映射
[x] JPA实体关系映射:@ManyToMany多对多关系、@OneToMany@ManyToOne一对多多对一关系和@OneToOne的深度实例解析。(@ManyToMany那里MappedBy值得商榷)
@OneToMany和@ManyToOne
@joinColumn
指定外键列,代表该实体类为关系维护方。在一对多或者一对一的关系下,需要加上@JoinColumn来指定外键列,避免生成一张中间表。
-
mappedBy让出控制权
- hibernate里面@mappedBy的作用?(和1的例子差不多)
个人理解,结论是,应该要保存多的一方的对象。这个操作完成三项:
1. 创建(更新)多的一方到数据库;
2. 创建(更新)一的一方到数据库;
3. 维护了多对一(一对多)的关联关系。
注意:
由多端维护关系,不由一端维护。
为何要将维护权交给多的一方,可以这样考虑:要想一个国家的领导人记住所有人民的名字是不可能的,但可以让所有人民记住领导人的名字!
不能同时使用@JoinColumn和mappedBy;因为@JoinColumn本身就是自己来维护外键,和mappedBy冲突了。
- 添加:先保存一的一方(此时不设置一对多关系),再保存多的一方(设置多对一关系)。如果先保存多的一方,再保存一的一方,会多出一些update语句。
-
Entity demo
@Entitypublic class Author {@Id // 主键@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略private Long id; //id@NotEmpty(message = "姓名不能为空")@Size(min=2, max=20)@Column(nullable = false, length = 20)private String name;//姓名@OneToMany(mappedBy = "author",cascade=CascadeType.ALL,fetch=FetchType.LAZY)//级联保存、更新、删除、刷新;延迟加载。当删除用户,会级联删除该用户的所有文章//拥有mappedBy注解的实体类为关系被维护端//mappedBy="author"中的author是Article中的author属性private List<Article> articleList;//文章列表}
@Entitypublic class Article {@Id@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略@Column(name = "id", nullable = false)private Long id;@NotEmpty(message = "标题不能为空")@Size(min = 2, max = 50)@Column(nullable = false, length = 50) // 映射为字段,值不能为空private String title;@Lob // 大对象,映射 MySQL 的 Long Text 类型@Basic(fetch = FetchType.LAZY) // 懒加载@NotEmpty(message = "内容不能为空")@Size(min = 2)@Column(nullable = false) // 映射为字段,值不能为空private String content;//文章全文内容@ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false) //可选属性optional=false,表示author不能为空。删除文章,不影响用户@JoinColumn(name="author_id")//设置在article表中的关联字段(外键)private Author author;//所属作者}
案例
```java /**
级联添加,一的一方为操作主体 */ @Test @Transactional @Rollback(false) public void createCascadeAdd() {
LinkMan linkMan = new LinkMan(); linkMan.setLkmName(“Mendez”);
Customer customer = new Customer(); customer.setCustName(“Mbappe”);
customer.getLinkmans().add(linkMan); // 因为customer放弃了维护权,所以保存时linkman->customer的关联没有建立, // 但是link_man表的lkm_cust_id为NOT NULL,会报错,所以这里显式设置 linkMan.setCustomer(customer);
customerDao.save(customer); }
/**
更好的做法,多的一方为操作主体 */ @Test @Transactional @Rollback(false) public void createCascadeAdd2() {
/**
- 如果同时新建保存一的一方和多的一方,不用给一的一方add(多的一方);
反正都是在多的一方设置关系。 */ LinkMan linkMan = new LinkMan(); linkMan.setLkmName(“Edward”);
Customer customer = new Customer(); customer.setCustName(“Haland”);
linkMan.setCustomer(customer);
customerDao.save(customer); linkmanDao.save(linkMan); } ```
单向与双向关联
```java public class Address {
//…
@Column(name = “address”, nullable = true, length = 100) private String address;//地址
// 一对一,如果不需要根据Address级联查询People,可以注释掉 // @OneToOne(mappedBy = “address”, cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false) // private People people; } ```
@PrimaryKeyJoinColun
关联表只有两个外键字段,分别指向主表ID和从表ID。
总结一下,也是控制关系维护端。
1、多对多关系中一般不设置级联保存、级联删除、级联更新等操作。 2、可以随意指定一方为关系维护端,在这个例子中,我指定 User 为关系维护端,所以生成的关联表名称为: user_authority,关联表的字段为:user_id 和 authority_id 3、多对多关系的绑定由关系维护端来完成,即由 User.setAuthorities(authorities) 来绑定多对多的关系。关系被维护端不能绑定关系,即Authority不能绑定关系。 4、多对多关系的解除由关系维护端来完成,即由User.getAuthorities().remove(authority)来解除多对多的关系。关系被维护端不能解除关系,即Authority不能解除关系。 5、如果 User 和 Authority 已经绑定了多对多的关系,那么不能直接删除 Authority,需要由 User 解除关系后,才能删除 Authority。但是可以直接删除 User,因为 User 是关系维护端,删除 User 时,会先解除 User 和 Authority 的关系,再删除 Authority。
@Entity@Datapublic class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@NotEmpty(message = "账号不能为空")@Size(min=3, max=20)@Column(nullable = false, length = 20, unique = true)private String username; // 用户账号,用户登录时的唯一标识@NotEmpty(message = "密码不能为空")@Size(max=100)@Column(length = 100)private String password; // 登录时密码@ManyToMany@JoinTable(name = "user_authority",joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "authority_id"))//1、关系维护端,负责多对多关系的绑定和解除//2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User)//3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Authority)//4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,//即表名为user_authority//关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id//关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,即authority_id//主表就是关系维护端对应的表,从表就是关系被维护端对应的表private List<Author> authorList;}@Entity@Datapublic class Authority {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@Column(nullable = false)private String name; //权限名@ManyToMany(mappedBy = "authorList")private List<User> userList;}@Testpublic void manyToManyTest() {Authority authority = new Authority();authority.setName("管理员");User user = new User();user.setUsername("zhangsan");user.setPassword("123");// 设置一方即可user.getAuthorities().add(authority);userRepository.save(user);authorityRepository.save(authority);}
- 第一组数据(仅将被维护方对象添加进维护方对象Set中,对维护方对象的单独保存和删除):由于操作对象是维护方,成功地在student、course以及中间表student_courses中分别添加了数据并成功进行了删除。若将删除对象换成被维护方,同样能够成功删除。
- 第二组数据(仅将维护方对象添加进被维护方对象Set中,对被维护方对象的单独保存和删除):操作对象在这里换成了被维护方。不负众望,出问题了。保存的时候,student表和course表倒是都成功地插入了数据,但是中间表中,并未产生对两者数据的关联。因此,在删除的时候也只删除了course中的数据。
- 第三组数据( 将双方对象均添加进双方Set中,对被维护方对象进行保存和删除):操作对象是被维护方,操作结果与第一组相同。
链接:https://www.jianshu.com/p/54108abb070f因为LabelDictDomain中的memberDomains对象在MemberDomain中被配置为了关系的维护方。(即LabelDictDomain是关系维护方),所以labelDictDomains可以为空,而memberDomains一定不能为空。(维护方的集合不能为空)
否则就会出现关联关系被清空的情况。
也就是说,被维护方不会主动去维护关联关系。
真正的关系维护,掌握在维护方的手中。
// 维护方public class LabelDictDomain {@ManyToMany(cascade={CascadeType.DETACH} , fetch = FetchType.LAZY)@JoinTable(name="member_label",joinColumns = {@JoinColumn(name="LABEL_ID")},inverseJoinColumns = @JoinColumn(name="MEMBER_ID"))private Set<MemberDomain> memberDomains = new HashSet<>();}// 被维护方public class MemberDoamin {@ManyToMany(mappedBy = "memberDomains", cascade={CascadeType.MERGE,CascadeType.DETACH} , fetch = FetchType.EAGER)private Set<LabelDictDomain> labelDictDomains = new HashSet<>();}
级联操作
- 在实际开发中,级联删除请慎用!(在一对多、多对多的情况下)
级联是针对关联的对象是否一起被增、删、改到数据库,而mappedBy针对是否维护关系(操作放弃维护权的主体时,关联体的外键是否被设置上,放弃了则设为null)
public void createCascadeAdd() {LinkMan linkMan = new LinkMan();linkMan.setLkmName("Mendez");Customer customer = new Customer();customer.setCustName("Mbappe");customer.setCustIndustry("Football");customer.getLinkmans().add(linkMan);// 因为customer放弃了维护权,所以保存时linkman->customer的关联没有建立,// 但是link_man表的lkm_cust_id为NOT NULL,会报错,所以这里显式设置// 如果没有放弃维护权,就不需要setCustomerlinkMan.setCustomer(customer);customerDao.save(customer);}
一般首次单独保存一的一方,后面操作多的一方即可
【简单易懂】JPA概念解析:CascadeType(各种级联操作)详解。
- CascadeType.MERGE
- CascadeType.PERSIST
- CascadeType.REFRESH
- CascadeType.REMOVE
- CascadeType.ALL
cascade = CascadeType.ALL 只能写在 One 端,只有One端改变Many端,不准Many端改变One端。
删除操作的说明如下:
- 删除从表数据:可以随时任意删除。
删除主表数据:
有从表数据
1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。 2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null没有关系)因为在删除时,它根本不会去更新从表的外键字段了。 3、如果还想删除,使用级联删除引用
- 没有从表数据引用:随便删O
数据库上的级联设置
Foreign key constraints: When to use ON UPDATE and ON DELETE SQL层面设置级联关系
- 默认ON UPDATE RESTRICT: 修改一的一方的主键,如果有多的一方与之关联,是不被允许的
默认ON DELETE RESTRICT: 删除一的一方,如果有多的一方与之关联,是不被允许的
CREATE TABLE `book`(`id` int(11) NOT NULL AUTO_INCREMENT,`cover` varchar(255) DEFAULT '',`cid` int(11),PRIMARY KEY (`id`),INDEX `fk_book_category_on_cid` (`cid`),CONSTRAINT `fk_book_category_on_cid` FOREIGN KEY (`cid`) REFERENCES `category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE = InnoDBAUTO_INCREMENT = 102DEFAULT CHARSET = utf8;
数据库外键及为什么建议不要使用
CREATE TABLE cst_linkman (lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',PRIMARY KEY (`lkm_id`),KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),CONSTRAINT `FK_cst_linkman_lkm_cust_id`FOREIGN KEY (`lkm_cust_id`)REFERENCES `cst_customer` (`cust_id`)ON DELETE NO ACTIONON UPDATE NO ACTION) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
[x] 参考文章:大家设计数据库时使用外键吗?(结论)
- 为什么这么设计系列——为什么数据库不应该使用外键(详细)
数据库是有状态服务(自身保存了数据),很难水平扩展(数据同步)来负载均衡,造成性能瓶颈。
- 数据库需要维护外键的内部管理;
- 外键等于把数据的一致性事务实现,全部交给数据库服务器完成,增加数据库服务器的压力;
- 有了外键,当做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,而不得不消耗资源;
- 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
- 对分库分表不友好 :因为分库分表下外键是无法生效的。
@MapKey、@MapKeyColumn、@MapKeyJoinColumn
- Difference between @MapKey, @MapKeyColumn and @MapKeyJoinColumn in JPA and Hibernate
- Persisting Maps with Hibernate
[ ] JPA: Ways to map many-to-many relationships with additional information
主键
主键生成策略
查询方式不用看
[x] JpaPkGenerate、HibernatePkGenerate
联合主键
案例
Shiro demo
- 财务系统
- Flink demo (参考LE)
