hibernate 一对一 一对多 多对多
依赖导入
<!-- 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>
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 会话工厂 -->
<session-factory>
<!-- 链接参数 -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/jpa_study?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">a1b2c3</property>
<!-- 显示sql语句 -->
<property name="show_sql">false</property>
<!-- 格式化sql -->
<property name="format_sql">false</property>
<!-- sql方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 自动操作:创建-删除 -->
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- 配置sessionFactory.getCurrentSession()的上下文 -->
<property name="current_session_context_class">thread</property>
<!-- 导入实体类和映射关系 -->
<mapping class="org.example.entity.User"/>
<mapping class="org.example.entity.Address"/>
<mapping class="org.example.entity.Vlog"/>
<mapping class="org.example.entity.Role"/>
</session-factory>
</hibernate-configuration>
一对一
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
: 外键列
- 在一对一中
@JoinColumn
声明在那个实体类中,生成数据库表时外键列就在那个类。 - 在一对多中
@JoinColumn
必须声明在多的那个实体类中 - 在多对多中
@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字段。