hibernate 一对一 一对多 多对多

发布时间 2023-08-11 10:37:22作者: 黑人的乔丹鞋

依赖导入

<!-- hibernate 核心 -->
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.2.7.Final</version>
</dependency>

<!--  jdbc 实现 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>

一对一

User 实体类

@Entity
@Table(name = "user")
@Data
public class User {
  ...
  
  // 关联列的名称:address_id
  @JoinColumn(name = "address_id")
  @OneToOne(
          // 懒加载
          fetch = FetchType.LAZY
  )
  private Address address;
}

Address 实体类

@Entity
@Setter
@Getter
@Table(name = "address")
public class Address {
  ...
  @OneToOne(
      // 实体关联的被拥有者,只想User.address字段
      mappedBy = "address"
  )
  private User user;
}

测试

@Test
public void oneToOneTest() {
    AddressDao addressDao = new AddressDao();// Dao层已经在https://www.cnblogs.com/heirem/p/17616689.html说过了,这里就放代码了
    Address address = new Address();
    address.setDetail("广东中山");
    addressDao.save(address);

    UserDao userDao = new UserDao();
    User user = new User();
    user.setName("张三");
    user.setEmail("zhangsan@email.com");
    userDao.save(user);
    user.setAddress(address);
    userDao.update(user);

    userDao.findAll().forEach(System.out::println);
}
User(id=1, name=张三, email=zhangsan@email.com, address=org.example.entity.Address@74bdfa0b, vLog=[], roles=[])

一对多

User实体类

@Entity
@Table(name = "user")
@Data
public class User {
  ...
  @OneToMany(
    // 懒加载
    fetch = FetchType.LAZY,
    // 映射字段,指的是Vlog实体类中映射此实体类的字段,实体关联的被拥有者
    mappedBy = "user"
  )
  private List<Vlog> vLog;
}

Vlog实体类

@Entity
@Table(name = "vlog")
@Getter
@Setter
public class Vlog {
  ...
  @JoinColumn(name="user_id")// join列,即外键列
  @ManyToOne(
          fetch = FetchType.LAZY
  )
  private User user;
}

测试

@Test
public void oneToManyTest() {
    UserDao userDao = new UserDao();
    User user = new User();
    user.setName("张三");
    user.setEmail("zhangsan@email.com");
    userDao.save(user);

    VlogDao vlogDao = new VlogDao();
    Vlog v1 = new Vlog();
    v1.setUrl("www.bilirubin.com/video/1");
    vlogDao.save(v1);

    Vlog v2 = new Vlog();
    v2.setUrl("www.bilirubin.com/video/2");
    vlogDao.save(v2);

    v1.setUser(user);
    v2.setUser(user);
    vlogDao.update(v1);
    vlogDao.update(v2);

    userDao.findAll().forEach(System.out::println);
    vlogDao.findAll().forEach(System.out::println);
}
User(id=1, name=张三, email=zhangsan@email.com, address=null, vLog=[org.example.entity.Vlog@6f7a20da, org.example.entity.Vlog@77ba583], roles=[])
org.example.entity.Vlog@4cd7e993
org.example.entity.Vlog@685e6a68

多对多

@Entity
@Table(name = "user")
@Data
public class User {
  ...
  @ManyToMany(
    // 懒加载
    fetch = FetchType.LAZY,
    // 关联操作,不过因为多对多只会影响关联表和本表的数据,即user_role表和user
    cascade = {CascadeType.ALL}
  )
  @JoinTable(
    // User Role表关联表的名称
    name = "user_role",
    // joinColumns 链接表的外键,记录此类的的id
    joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")},
    // inverseJoinColumns 链接表的外键,记录链接类的id
    inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}
  )
  private List<Role> roles;
}
@Entity
@Table(name = "role")
@Setter
@Getter
public class Role {
  ...
  @ManyToMany(
      fetch = FetchType.LAZY,
      // 实体关系的拥有者,即在User.roles已经声明过关联规则,这边直接关联User.roles就好了
      mappedBy = "roles"
  )
  private List<User> users;
}

测试

@Test
public void manyToManyTest() {
    RoleDao roleDao = new RoleDao();
    Role roleUser = new Role();
    roleUser.setName("user");
    roleUser.setDescription("普通用户");
    roleDao.save(roleUser);

    Role roleAdmin = new Role();
    roleAdmin.setName("admin");
    roleAdmin.setDescription("超级用户");
    roleDao.save(roleAdmin);


    UserDao userDao = new UserDao();
    User u1 = new User();
    u1.setName("张三");
    u1.setName("张三@email.com");
    userDao.save(u1);

    User u2 = new User();
    u2.setName("李四");
    u2.setName("李四@email.com");
    userDao.save(u2);

    u1.setRoles(List.of(roleUser));
    u2.setRoles(List.of(roleUser,roleAdmin));
    userDao.update(u1);
    userDao.update(u2);


    userDao.findAll().forEach(System.out::println);
}
User(id=1, name=张三@email.com, email=null, address=null, vLog=[], roles=[org.example.entity.Role@48d44b46])
User(id=2, name=李四@email.com, email=null, address=null, vLog=[], roles=[org.example.entity.Role@48d44b46, org.example.entity.Role@dd20ebc])

mappedBy属性 和 JoinColumn注解

mappedby : 属性指向实体关联表的拥有者,声明在被拥有者。简单说就是另一边定义了关联规则,这边不用再定义一遍了,直接引用就行。
@JoinColumn: 外键列

  1. 在一对一中@JoinColumn 声明在那个实体类中,生成数据库表时外键列就在那个类。
  2. 在一对多中@JoinColumn 必须声明在多的那个实体类中
  3. 在多对多中@JoinColumn 必须配合@JoinTable使用

toString()造成的栈溢出StackOverflowError

hibernate中实体类如果有互相关联,比如:一对一关系中 User实体中声明了Address的字段private Address address,Address实体中声明了User的字段private User user。在序列化或者打印时,可能出现循环加载关联字段。比如打印User的实例,User的toString中又调用了Address的toString,这是去加载Address实体类的信息,加载出来后Address的toString中有引用了User的toString方法,完了就这样不断的互相调用,循环且套直至StackOverflowError
解决方法:
打印-删除掉User实体类toString引用的Address.toString的代码。
序列化-跳过User的Address字段。