一、数据准备

        在管理系统中,角色和用户是典型一对多关系,一个角色可以分配给多个用户,一个用户对应一个角色。准备两个实体类TestRole、TestUser类,并在数据库建好对应表结构z_test_roles、z_test_users两张表。

1、mysql语句

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for z_test_roles
-- ----------------------------
DROP TABLE IF EXISTS `z_test_roles`;
CREATE TABLE `z_test_roles` (
  `id` varchar(50) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of z_test_roles
-- ----------------------------
INSERT INTO `z_test_roles` VALUES ('1', '军师');
INSERT INTO `z_test_roles` VALUES ('2', '将军');
INSERT INTO `z_test_roles` VALUES ('3', '主将');
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for z_test_users
-- ----------------------------
DROP TABLE IF EXISTS `z_test_users`;
CREATE TABLE `z_test_users` (
  `id` varchar(50) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  `rid` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of z_test_users
-- ----------------------------
INSERT INTO `z_test_users` VALUES ('1', '刘备', '3');
INSERT INTO `z_test_users` VALUES ('2', '曹操', '3');
INSERT INTO `z_test_users` VALUES ('3', '诸葛亮', '1');
INSERT INTO `z_test_users` VALUES ('4', '关羽', '2');
INSERT INTO `z_test_users` VALUES ('5', '张飞', '2');

2、实体类

@Entity
@Getter
@Setter
@Table(name = "z_test_roles")
public class TestRole implements Serializable {
    @Id
    @Column(name = "id")
    @ApiModelProperty(value = "ID")
    private String id;

    @ApiModelProperty(value = "名称")
    private String name;

    //一对多
    @OneToMany(mappedBy = "testRole")
//    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
//    @JoinColumn(name = "rid")
    private List<TestUser> userList;

    @Override
    public String toString() {
        return "TestRole{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
@Entity
@Getter
@Setter
@Table(name = "z_test_users")
public class TestUser implements Serializable {
    @Id
    @Column(name = "id")
    @ApiModelProperty(value = "ID")
    private String id;

    @ApiModelProperty(value = "名称")
    private String name;
    
    //多对一
    @ManyToOne(targetEntity = TestRole.class)
    @JoinColumn(name = "rid", referencedColumnName = "id")
    private TestRole testRole;

    @Override
    public String toString() {
        return "TestUser{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

二、注解

1、@OneToMany

        一对多@OneToMany,该注解包含以下五个属性:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToMany {
    Class targetEntity() default void.class;

    CascadeType[] cascade() default {};

    FetchType fetch() default FetchType.LAZY;

    String mappedBy() default "";

    boolean orphanRemoval() default false;
}

        targetEntity:属性表示默认关联的实体类型。如果集合类中指定了具体类型了,不需要使用targetEntity.否则要指定targetEntity=xxx.class;
        cascade:该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。举个例子: role和 user有级联关系,那么删除 role时将同时删除它所对应的 user对象。而如果 user还和其他的对象之间有级联关系,那么这样的操作会一直递归执行下去。cascade 的值只能从 CascadeType.PERSIST (级联新建)、 CascadeType.REMOVE (级联删除)、 CascadeType.REFRESH (级联刷新)、 CascadeType.MERGE (级联更新)中选择一个或多个。还有一个选择是使用 CascadeType.ALL ,表示选择全部四项。(2.0版本后新增了DETACH)
        fetch:可选择项包括: FetchType.EAGER 和 FetchType.LAZY 。前者表示关系类 (本例是user类) 在主类 (本例是role类) 加载的时候同时加载,后者表示关系类在被访问时才加载。默认值是 FetchType.LAZY 。
        mappedBy:定义类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要使用这个属性进行定义,否则可能引起数据一致性的问题。该属性的值是“多”方class里的“一”方的变量名。添加该属性的一端为被控方(role类),表示将关系维护权交给该属性值指向的类那方(user类)。
        orphanRemoval:默认false,orphanRemoval为true,表示会先直接删除对应的子表数据,级联更新此配置最为关键。

2、@ManyToOne

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface ManyToOne {

    Class targetEntity() default void.class;

    CascadeType[] cascade() default {};

    FetchType fetch() default EAGER;

    boolean optional() default true;
}

        @ManyToOne注解有四个属性:targetEntity、cascade、fetch 和optional,前三个属性的具体含义和@OneToMany的同名属性相同,但@ManyToOne的fetch 属性默认值是FetchType.EAGER。
        optional :属性是定义该关联类是否必须存在,值为false时,关联类双方都必须存在,不能为空。如果关系被维护端不存在,查询的结果为null。值为true时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端中指向关系被维护端的属性为null。optional属性的默认值是true。optional 属性实际上指定关联类与被关联类的join 查询关系,如optional=false 时join 查询关系为inner join, optional=true 时join 查询关系为left join。

3、@JoinColumn

        joinColumns属性表示,在保存关系中的表中,所保存关联的外键字段。z_test_users表中的rid关联z_test_roles表中的id。

4、@JoinTable

        @JoinTable 描述了多对多关系的数据表关系。
       name 属性指定中间表名称;joinColumns定义中间表与Teacher 表的外键关系;上面的代码中,中间表Teacher_Student的Teacher_ID 列是Teacher 表的主键列对应的外键列;inverseJoinColumns 属性定义了中间表与另外一端(Student)的外键关系。

5、注意事项

        1.ManyToOne(多对一)单向
        不产生中间表,但可以用@Joincolumn(name=" “)来指定生成外键的名字,外键在多的一方表中产生。
        2.OneToMany(一对多)单向
        会产生中间表,此时可以用@onetoMany @Joincolumn(name=” ")避免产生中间表,并且指定了外键的名字(别看@joincolumn在一中写着,但它存在在多的那个表中)
        3.OneToMany , ManyToOne 双向(两个注解一起用的)
        如果不在@OneToMany中加mappedy属性就会产生中间表。
        4、orphanRemoval = true和Cascade.REMOVE有什么区别?
        对于单向OneToMany。 两种设置之间的区别在于对断开关系的响应。例如当对role类的某一个角色进行更新时。(1)如果指定了 orphanRemoval = true,则会自动删除断开连接user类实例。这对于清理没有所有者对象(user对象)的引用不应该存在的依赖对象(role对象)很有用。(2)如果仅指定了 cascade = CascadeType.REMOVE, 则仅仅会将失去了连接的user实例的rid置为null,因为断开关系不是删除操作。
        对于OneToMany , ManyToOne 双向。 one的一方设置了mappedBy后,one 的一方更新,(1)如果指定了 orphanRemoval = true,则会自动删除断开连接user类实例。(2)如果仅指定了 cascade = CascadeType.REMOVE, 则不对多的一方造成任何影响。     
        5、@Transactional注解
       
在新增操作时候,方法上带此注解会导致回滚操作,新增失败;在查询时候,加上该注解可以解决懒加载数据无法获取的问题。
        6、多对一,如果关联数据不存在,查询整条结果会为空
        加入@NotFound(action = NotFoundAction.IGNORE)可显示除了关联字段的其他字段

三、数据库操作

1、单向一对多

        只需在one的一方添加以下注释,因为没有mappedBy,此时关系维护为one的一方,同时必须加上JoinColumn注解指定关联的表的字段,否则会自动去找中间表,如果中间表不存在会报错。CascadeType默认为全部操作都为级联模式。one方新增、查询不会对many方造成影响,删、改则会有影响。

    /**
     * 单向一对多
     */
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "rid")
    private List<TestUser> userList;

(1)新增:会自动填充关联表(user)中的关联字段(rid)

    /**
     * 单向一对多,新增
     */
    @Test
    public void insert() {
        //创建两个人物,不需要设置rid
        TestUser user1 = new TestUser();
        user1.setId("1");
        user1.setName("嵇康");
        TestUser user2 = new TestUser();
        user2.setId("2");
        user2.setName("曹植");

        //创建一个角色,将人物的list与角色绑定,自动更新user表的rid
        TestRole role = new TestRole();
        List<TestUser> list = new ArrayList<>();
        list.add(user1);
        list.add(user2);
        role.setId("1");
        role.setName("文人");
        role.setUserList(list);

        //保存到数据库
        testUserRepository.save(user1);
        testUserRepository.save(user2);
        testRoleRepository.save(role);
    }

(2)查询:会将关联表(user)的相关字段一并查出,如果fetch设置为懒加载,并不会显示。将fetch设置为Fetch.EAGER或者在业务代码上添加@Transcational注解即可实现立即加载。

 /**
     * 单向一对多,查询
     */
    @Test
    @Transactional
    public void find() {
        Optional<TestRole> byId = testRoleRepository.findById("1");
        System.out.println(byId);
    }

 (3)修改:对one的一方(role)进行修改,会导致many方(user)的关联字段变为null,即关系断开。如果orphanRemoval = true,则many方(user)的关联字段会被直接删除。

    /**
     * 单向一对多修改
     */
    @Test
    public void update() {
        TestRole role = new TestRole();
        role.setName("武将");
        role.setId("1");
        role.setUserList(new ArrayList<>());
        testRoleRepository.save(role);
    }

更新前更新后 

(4)删除:删除one方(role),many方(user)会关联字段被关联删除

    /**
     * 单向一对多删除
     */
    @Test
    public void delete() {
        testRoleRepository.deleteById("1");
    }

2、单向多对一

         只需在many的一方添加以下代码,此时不需要rid这个属性,同样由于没有mappedBy注解指定关系维护方,会去中间表查找对应关系,此时如果不加@JoinColumn注解会报错。
        注意:该注解默认为立即加载。

    /**
     * 单向多对一
     */
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "rid", referencedColumnName = "id")
    private TestRole testRole;

(1)新增:会根据JoinColumn配置自动将角色的id映射到rid

    /**
     * 单向多对一新增
     */
    @Test
    public void add1() {
        //创建一个角色
        TestRole role = new TestRole();
        role.setId("1");
        role.setName("文人");

        //创建两个人物,设置角色后,会根据JoinColumn配置自动将角色的id映射到rid
        TestUser user1 = new TestUser();
        user1.setId("1");
        user1.setName("嵇康");
        user1.setTestRole(role);
        TestUser user2 = new TestUser();
        user2.setId("2");
        user2.setName("曹植");
        user2.setTestRole(role);

        //保存到数据库
        testUserRepository.save(user1);
        testUserRepository.save(user2);
        testRoleRepository.save(role);
    }

(2)查询:默认立即加载,所以不需要加@Transactional

    /**
     * 单向多对一,查询
     */
    @Test
//    @Transactional
    public void find1() {
        Optional<TestUser> byId = testUserRepository.findById("1");
        System.out.println(byId);
    }

(3)修改:

    /**
     * 单向一对多修改
     */
    @Test
    public void update() {
        TestUser user=new TestUser();
        user.setId("2");
        user.setName("祝融");
        user.setTestRole(null);
        testUserRepository.save(user);
    }

修改前 修改后

(4)删除:删除many一方的数据,会将对应的one一方的数据同时删除,所以不建议在many一方设置为CascadeType.ALL,设置为支持新增、查询、修改即可

    /**
     * 单向多对一
     */
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "rid", referencedColumnName = "id")
    private TestRole testRole;

3、双向一对多、多对一

        两个注解同时使用,注意此时,cascade一般应在one 方定义,否则会造成级联重复,比如双方都定义了该字段的新增级联,则会导致进行两次新增操作
        
        

参考链接

https://blog.csdn.net/qq_42158122/article/details/119644107

        

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐