关联关系注解

个人理解,结论是,应该要保存多的一方的对象。这个操作完成三项:
1. 创建(更新)多的一方到数据库;
2. 创建(更新)一的一方到数据库;
3. 维护了多对一(一对多)的关联关系。
注意:

  1. 由多端维护关系,不由一端维护。

    为何要将维护权交给多的一方,可以这样考虑:要想一个国家的领导人记住所有人民的名字是不可能的,但可以让所有人民记住领导人的名字!

  2. 不能同时使用@JoinColumn和mappedBy;因为@JoinColumn本身就是自己来维护外键,和mappedBy冲突了。

  3. 添加:先保存一的一方(此时不设置一对多关系),再保存多的一方(设置多对一关系)。如果先保存多的一方,再保存一的一方,会多出一些update语句。
  4. 更新:保存多的一方即可。

    Entity demo

    1. @Entity
    2. public class Author {
    3. @Id // 主键
    4. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    5. private Long id; //id
    6. @NotEmpty(message = "姓名不能为空")
    7. @Size(min=2, max=20)
    8. @Column(nullable = false, length = 20)
    9. private String name;//姓名
    10. @OneToMany(
    11. mappedBy = "author",
    12. cascade=CascadeType.ALL,
    13. fetch=FetchType.LAZY
    14. )
    15. //级联保存、更新、删除、刷新;延迟加载。当删除用户,会级联删除该用户的所有文章
    16. //拥有mappedBy注解的实体类为关系被维护端
    17. //mappedBy="author"中的author是Article中的author属性
    18. private List<Article> articleList;//文章列表
    19. }
    1. @Entity
    2. public class Article {
    3. @Id
    4. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    5. @Column(name = "id", nullable = false)
    6. private Long id;
    7. @NotEmpty(message = "标题不能为空")
    8. @Size(min = 2, max = 50)
    9. @Column(nullable = false, length = 50) // 映射为字段,值不能为空
    10. private String title;
    11. @Lob // 大对象,映射 MySQL 的 Long Text 类型
    12. @Basic(fetch = FetchType.LAZY) // 懒加载
    13. @NotEmpty(message = "内容不能为空")
    14. @Size(min = 2)
    15. @Column(nullable = false) // 映射为字段,值不能为空
    16. private String content;//文章全文内容
    17. @ManyToOne(
    18. cascade={CascadeType.MERGE,CascadeType.REFRESH},
    19. optional=false
    20. ) //可选属性optional=false,表示author不能为空。删除文章,不影响用户
    21. @JoinColumn(name="author_id")//设置在article表中的关联字段(外键)
    22. private Author author;//所属作者
    23. }

    案例

    ```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。

  1. @Entity
  2. @Data
  3. public class User {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Long id;
  7. @NotEmpty(message = "账号不能为空")
  8. @Size(min=3, max=20)
  9. @Column(nullable = false, length = 20, unique = true)
  10. private String username; // 用户账号,用户登录时的唯一标识
  11. @NotEmpty(message = "密码不能为空")
  12. @Size(max=100)
  13. @Column(length = 100)
  14. private String password; // 登录时密码
  15. @ManyToMany
  16. @JoinTable(
  17. name = "user_authority",
  18. joinColumns = @JoinColumn(name = "user_id"),
  19. inverseJoinColumns = @JoinColumn(name = "authority_id")
  20. )
  21. //1、关系维护端,负责多对多关系的绑定和解除
  22. //2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User)
  23. //3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Authority)
  24. //4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
  25. //即表名为user_authority
  26. //关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id
  27. //关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,即authority_id
  28. //主表就是关系维护端对应的表,从表就是关系被维护端对应的表
  29. private List<Author> authorList;
  30. }
  31. @Entity
  32. @Data
  33. public class Authority {
  34. @Id
  35. @GeneratedValue(strategy = GenerationType.IDENTITY)
  36. private Integer id;
  37. @Column(nullable = false)
  38. private String name; //权限名
  39. @ManyToMany(mappedBy = "authorList")
  40. private List<User> userList;
  41. }
  42. @Test
  43. public void manyToManyTest() {
  44. Authority authority = new Authority();
  45. authority.setName("管理员");
  46. User user = new User();
  47. user.setUsername("zhangsan");
  48. user.setPassword("123");
  49. // 设置一方即可
  50. user.getAuthorities().add(authority);
  51. userRepository.save(user);
  52. authorityRepository.save(authority);
  53. }
  • 第一组数据(仅将被维护方对象添加进维护方对象Set中,对维护方对象的单独保存和删除):由于操作对象是维护方,成功地在student、course以及中间表student_courses中分别添加了数据并成功进行了删除。若将删除对象换成被维护方,同样能够成功删除。
  • 第二组数据(仅将维护方对象添加进被维护方对象Set中,对被维护方对象的单独保存和删除):操作对象在这里换成了被维护方。不负众望,出问题了。保存的时候,student表和course表倒是都成功地插入了数据,但是中间表中,并未产生对两者数据的关联。因此,在删除的时候也只删除了course中的数据。
  • 第三组数据( 将双方对象均添加进双方Set中,对被维护方对象进行保存和删除):操作对象是被维护方,操作结果与第一组相同。


链接:https://www.jianshu.com/p/54108abb070f

因为LabelDictDomain中的memberDomains对象在MemberDomain中被配置为了关系的维护方。(即LabelDictDomain是关系维护方),所以labelDictDomains可以为空,而memberDomains一定不能为空。(维护方的集合不能为空)
否则就会出现关联关系被清空的情况。
也就是说,被维护方不会主动去维护关联关系。
真正的关系维护,掌握在维护方的手中。

  1. // 维护方
  2. public class LabelDictDomain {
  3. @ManyToMany(cascade={CascadeType.DETACH} , fetch = FetchType.LAZY)
  4. @JoinTable(name="member_label",
  5. joinColumns = {@JoinColumn(name="LABEL_ID")},
  6. inverseJoinColumns = @JoinColumn(name="MEMBER_ID"))
  7. private Set<MemberDomain> memberDomains = new HashSet<>();
  8. }
  9. // 被维护方
  10. public class MemberDoamin {
  11. @ManyToMany(mappedBy = "memberDomains", cascade={CascadeType.MERGE,CascadeType.DETACH} , fetch = FetchType.EAGER)
  12. private Set<LabelDictDomain> labelDictDomains = new HashSet<>();
  13. }

级联操作

  • 在实际开发中,级联删除请慎用!(在一对多、多对多的情况下)
  • 级联是针对关联的对象是否一起被增、删、改到数据库,而mappedBy针对是否维护关系(操作放弃维护权的主体时,关联体的外键是否被设置上,放弃了则设为null)

    1. public void createCascadeAdd() {
    2. LinkMan linkMan = new LinkMan();
    3. linkMan.setLkmName("Mendez");
    4. Customer customer = new Customer();
    5. customer.setCustName("Mbappe");
    6. customer.setCustIndustry("Football");
    7. customer.getLinkmans().add(linkMan);
    8. // 因为customer放弃了维护权,所以保存时linkman->customer的关联没有建立,
    9. // 但是link_man表的lkm_cust_id为NOT NULL,会报错,所以这里显式设置
    10. // 如果没有放弃维护权,就不需要setCustomer
    11. linkMan.setCustomer(customer);
    12. customerDao.save(customer);
    13. }
  • 一般首次单独保存一的一方,后面操作多的一方即可

【简单易懂】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: 删除一的一方,如果有多的一方与之关联,是不被允许的

    1. CREATE TABLE `book`
    2. (
    3. `id` int(11) NOT NULL AUTO_INCREMENT,
    4. `cover` varchar(255) DEFAULT '',
    5. `cid` int(11),
    6. PRIMARY KEY (`id`),
    7. INDEX `fk_book_category_on_cid` (`cid`),
    8. CONSTRAINT `fk_book_category_on_cid` FOREIGN KEY (`cid`) REFERENCES `category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
    9. ) ENGINE = InnoDB
    10. AUTO_INCREMENT = 102
    11. DEFAULT CHARSET = utf8;

    数据库外键及为什么建议不要使用

    1. CREATE TABLE cst_linkman (
    2. lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
    3. lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',
    4. PRIMARY KEY (`lkm_id`),
    5. KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
    6. CONSTRAINT `FK_cst_linkman_lkm_cust_id`
    7. FOREIGN KEY (`lkm_cust_id`)
    8. REFERENCES `cst_customer` (`cust_id`)
    9. ON DELETE NO ACTION
    10. ON UPDATE NO ACTION
    11. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  • [x] 参考文章:大家设计数据库时使用外键吗?(结论)

  • 为什么这么设计系列——为什么数据库不应该使用外键(详细)

数据库是有状态服务(自身保存了数据),很难水平扩展(数据同步)来负载均衡,造成性能瓶颈

  1. 数据库需要维护外键的内部管理;
  2. 外键等于把数据的一致性事务实现,全部交给数据库服务器完成,增加数据库服务器的压力;
  3. 有了外键,当做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,而不得不消耗资源;
  4. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
  5. 对分库分表不友好 :因为分库分表下外键是无法生效的。

    @MapKey、@MapKeyColumn、@MapKeyJoinColumn

查询方式不用看