Hibernate中一对一,一对多,多对多使用
环境搭建
创建maven项目,引入依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.2.4.Final</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.1_spec</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
创建hibernate.cfg.xml
文件
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--数据库连接 配置-->
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate-test</property>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<!--配置hibernate 基本信息-->
<!--hibernate 表生成策略 -->
<property name="hbm2ddl.auto">update</property>
<!--数据库方言-->
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!--执行操作时是否在控制台打印 sql-->
<property name="show_sql">true</property>
<!--是否格式化打印的sql-->
<property name="format_sql">true</property>
</session-factory>
</hibernate-configuration>
一对一关系
举个栗子!一个登录账号对应一个用户信息。那么在一对一关系中,表的创建无非是两种。
- 在登录账号的表中添加用户信息表的主键id
- 在用户信息表中添加登录账号表的主键id
这两种方式都符合一对一的数据库表关系设计方式。具体图例如下所示
那么,我们接下来需要做什么?
在user
的实体中,我们要告诉它,有一个实体loginInfo
跟你有关系,你有一个外键,是人家的主键id。这个外键是唯一的。
创建实体类user
public class User {
private Long userId;
private String userName;
private Integer age;
}
创建user
的表映射文件user.hbm.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.User" table="sys_user">
<id name="userId" type="long">
<column name="USER_ID" />
<generator class="native"/>
</id>
<property name="userName" type="java.lang.String">
<column name="USER_NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE"/>
</property>
</class>
</hibernate-mapping>
创建实体类loginInfo
public class LoginInfo {
private Long loginInfoId;
private String loginId;
private String password;
}
创建loginInfo
的表映射关系
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.LoginInfo" table="sys_login_info">
<id name="loginInfoId" type="long">
<column name="LOGIN_INFO_ID" />
<generator class="native"/>
</id>
<property name="loginId" type="java.lang.String">
<column name="LOGIN_ID"/>
</property>
<property name="password" type="java.lang.String">
<column name="PASSWORD"/>
</property>
</class>
</hibernate-mapping>
在hibernate.cfg.xml
中添加这两个映射文件
<mapping resource="com/machoul/entity/User.hbm.xml"/>
<mapping resource="com/machoul/entity/LoginInfo.hbm.xml"/>
那么怎么给这两个实体添加关联呢?
在user
中添加一个属性loginInfo
public class User {
...
private LoginInfo loginInfo;
}
在user
的表映射关系中去指定它和loginInfo
的关系
- name:就是在
user
实体中定义的loginInfo
属性名 - class:对应
loginInfo
的类定义 - column:在user表中添加的外键列名
- unique:这个列是不是唯一的
<many-to-one name="loginInfo" class="com.machoul.entity.LoginInfo" column="LOGIN_INFO_ID" unique="true"/>
在loginInfo
中添加user
属性
public class LoginInfo {
...
private User user;
}
在loginInfo
的表映射关系中指定它和user
的关系
- name:就是在
loginInfo
中定义的user
属性名 - class:关联的
user
的类定义
<one-to-one name="user" class="com.machoul.entity.User"/>
一对一的保存方法
在一对一关系中,我们单独保存user
或者单独保存loginInfo
都是可以直接执行的。即
- 注释的前后为读取配置,创建session工厂,获取session以及事务的提交,之后会略过这些重复代码。
public void testSave() {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//=====================================
User user = new User();
user.setUserName("test01");
user.setAge(18);
session.save(user);
//=====================================
transaction.commit();
session.close();
sessionFactory.close();
}
public void testSave() {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//=====================================
LoginInfo loginInfo = new LoginInfo();
loginInfo.setLoginId("machoul");
loginInfo.setPassword("machoulpassword");
session.save(loginInfo);
//=====================================
transaction.commit();
session.close();
sessionFactory.close();
}
当我们创建一个user
对象,并为其指定一个loginInfo
对象的时候
- 如果先保存
loginInfo
对象,再保存user
对象,那么会生成两条insert语句 - 如果先保存
user
对象,再保存loginInfo
对象,那么会生成两条insert语句和一条update语句
为什么会这样?
当先保存loginInfo
的时候,
User user = new User();
user.setUserName("test02");
user.setAge(18);
LoginInfo loginInfo = new LoginInfo();
loginInfo.setLoginId("machoul");
loginInfo.setPassword("machoulpassword");
user.setLoginInfo(loginInfo);
session.save(loginInfo);
session.save(user);
查看执行日志,发现
当先保存user
的时候,
User user = new User();
user.setUserName("test02");
user.setAge(18);
LoginInfo loginInfo = new LoginInfo();
loginInfo.setLoginId("machoul");
loginInfo.setPassword("machoulpassword");
user.setLoginInfo(loginInfo);
session.save(user);
session.save(loginInfo);
查看执行日志发现
所以,当我们保存一个一对一关系的数据的时候
- 先维护没有外键列的表数据(
loginInfo
) - 再维护有外键列的表数据(
user
)
一对一的查询方法
User user = (User) session.get(User.class, 1L);
System.out.println(user.getUserName());
System.out.println(user.getLoginInfo().getLoginId());
查看执行的sql
表间关联关系是错误的!使用 property-ref
属性来解决这个问题
- property-ref:指定使用被关联实体主键以外的字段作为关联字段
<one-to-one name="user" class="com.machoul.entity.User" property-ref="loginInfo"/>
再次执行查询方法,查看日志
一对一的删除方法
- 单独删除
user
,可以直接删除(但是不会删除关联的loginInfo
) - 单独删除
loginInfo
- 如果当前的
loginInfo
没有被user
关联外键,那么可以直接删除 - 如果当前的
loginInfo
被user
关联外键,那么删除失败
- 如果当前的
使用级联删除来同时删除loginInfo
和user
中的数据
<one-to-one name="user" class="com.machoul.entity.User" property-ref="loginInfo" cascade="delete"/>
主键映射的一对一
一对一还有一种方式,就是主键映射。这种方式就是再其中的一个表的主键生成策略是依赖于另一张表的主键。说白了,就是这俩共用一个主键id。
在user
中,添加loginInfo
属性
public class User {
...
private LoginInfo loginInfo;
}
在loginInfo
中,添加user
属性
public class LoginInfo {
...
private User user;
}
在user
的表映射关系中,指定主键的生成策略,和一对一的关系
- foreign:使用外键的方式来生成当前的主键
- property:执行使用当前持久化类的哪一个属性的主键作为外键
- constrained:给当前的主键添加外键约束
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.User" table="sys_user">
<id name="userId" type="long">
<column name="USER_ID" />
<!-- 指定主键的生成策略-->
<generator class="foreign">
<param name="property">loginInfo</param>
</generator>
</id>
<property name="userName" type="java.lang.String">
<column name="USER_NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE"/>
</property>
<!--指定一对一的关系-->
<one-to-one name="loginInfo" class="com.machoul.entity.LoginInfo" constrained="true"/>
</class>
</hibernate-mapping>
在loginInfo
表映射关系中
- 指定一对一的关联关系
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.LoginInfo" table="sys_login_info">
<id name="loginInfoId" type="long">
<column name="LOGIN_INFO_ID" />
<generator class="native"/>
</id>
<property name="loginId" type="java.lang.String">
<column name="LOGIN_ID"/>
</property>
<property name="password" type="java.lang.String">
<column name="PASSWORD"/>
</property>
<one-to-one name="user" class="com.machoul.entity.User"/>
</class>
</hibernate-mapping>
一对多关系
单方维护
我再举个栗子,一个订单总单和订单细单是一对多关系。订单总单里面包含了订单的客户信息,运费啊等等,细单可能就是商品类型,数量,金额啊等等。一般这样的数据,在数据库中,我们的表设计为
- 细单表中保存了总单的主键id
具体图示如下所示
创建order
实体类
public class Order {
private Long orderId;
private String customerCode;
private Date orderTime;
}
创建orderItem
实体类
public class OrderItem {
private Long orderItemId;
private Double price;
}
创建order.hbm.xml
表映射文件
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.Order" table="prd_order">
<id name="orderId" type="long">
<column name="ORDER_ID" />
<generator class="native"/>
</id>
<property name="customerCode" type="java.lang.String">
<column name="CUSTOMER_CODE"/>
</property>
<property name="orderTime" type="java.util.Date">
<column name="ORDER_TIME"/>
</property>
</class>
</hibernate-mapping>
创建orderItem.hbm.xml
表映射文件
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.OrderItem" table="prd_order_item">
<id name="orderItemId" type="long">
<column name="ORDER_ITEM_ID" />
<generator class="native"/>
</id>
<property name="price" type="java.lang.Double">
<column name="PRICE"/>
</property>
</class>
</hibernate-mapping>
如何给order
和orderItem
添加关联关系呢?
在orderItem
中添加order
属性
public class OrderItem {
...
private Order order;
}
给orderItem
添加映射关系
<many-to-one name="order" class="com.machoul.entity.Order" column="ORDER_ID"/>
- 在保存的时候,依然遵循先保存一方,再保存多方的原则
- 由于外键关联,如果不设置级联关系,那么在还有外键关联的情况下不能删除一方的数据。
双方维护
在上述一对多关系中,我们是在orderItem
中添加了order
属性来维护表间关系。
如果order
中也需要去维护一对多关系,那么给需要给order
也添加映射关系
修改order
实体类
public class Order {
...
private Set<OrderItem> orderItems;
}
修改order
映射关系
- name :对应
order
中的orderItems
,private Set<orderItem> orderItems
- table:多的一方对应的表名
- key:元素设定于所关联的持久化类对应的表的外键
- column:对应
many-to-one
中指定的column
- column:对应
- one-to-many:
- class:对应多的一方
orderItem
的实体类
- class:对应多的一方
<set name="orderItems" table="prd_order_item">
<key column="ORDER_ITEM_ID"></key>
<one-to-many class="com.machoul.entity.OrderItem"/>
</set>
set的属性
inverse
-
在
hibernate
中通过对inverse
属性的来决定是由双向关联的哪一方来维护表和表之间的关系.inverse = false
的为主动方,inverse = true
的为被动方, 由主动方负责维护关联关系 -
在没有设置
inverse=true
的情况下,父子两边都维护父子关系 -
在
1-n
关系中,将n
方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多) -
在
1-n
关系中,如果将1方设置为主控方- 会额外多出
update
语句 - 插入数据时无法插入外键列,因而无法为外键添加非空约束
- 会额外多出
cascade
- 在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素,
<set>
,<many-to-one>
和<one-to-one>
都有一个cascade
属性, 它用于指定如何操纵与当前对象关联的其他对象.
cascade 属性 | 描述 |
---|---|
none | 当session 操纵当前对象时,忽略其他关联的对象,它是cascade 属性的默认值 |
save-update | 当通过Session 的save() ,update() 及saveOrUpdate() 方法来保存或更新当前对象时,级联保存所有关联的新建的临时对象,并且级联更新所有关联的游离对象 |
persist | 当通过Session 的persist() 方法来保存当前对象时,会级联保存所有关联的新建的临时对象 |
merge | 当通过Session 的merge() 方法来保存当前对象时,会级联融合所有关联的游离对象 |
delete | 当通过Session 的delete() 方法删除当前对象时,会级联删除所有关联的对象 |
lock | 当通过Session 的lock() 方法把当前游离对象加入到Session 缓存中时,会把所有关联的游离对象也加入到Session 缓存中 |
replicate | 当通过Session 的replicate() 方法复制当前对象时,会级联复制所有关联的对象 |
evict | 当通过Session 的evict() 方法从Session 中清除当前对象时,会级联清除所有关联的对象 |
refresh | 当通过Session 的refresh() 方法刷新当前对象时,会级联刷新所有关联的对象,所谓刷新是指读取数据库中对应数据,然后根据数据库中的最新数据去同步更新Session 缓存中的相应对象 |
all | 包含save-update ,persist ,merge ,delete ,lock ,replicate ,evict ,refresh 的行为 |
delete-orphan | 删除所有和当前对象解除关联关系的对象 |
all-delete-orphan | 包含all 和delete-orphan 的行为 |
order_by
<set>
元素有一个order-by
属性, 如果设置了该属性, 当Hibernate
通过select
语句到数据库中检索集合对象时, 利用order by
子句进行排序order-by
属性中还可以加入SQL
函数
多对多关系
我又来举栗子了!
多对多关系的栗子有很多,用户和角色,角色和资源(权限)等等都是。在针对多对多关系中,我们通常使用的方法就是中间表!
- 通过中间表来关联双方的主键id
具体图例如下所示
同样,创建Role
实体和User
实体
public class User {
private Long userId;
private String userName;
}
public class Role {
private Long roleId;
private String roleName;
}
创建对应的表映射关系文件
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.Role" table="sys_role">
<id name="roleId" type="long">
<column name="ROLE_ID" />
<generator class="native"/>
</id>
<property name="roleName" type="java.lang.String">
<column name="ROLE_NAME"/>
</property>
</class>
</hibernate-mapping>
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.machoul.entity.User" table="sys_user">
<id name="userId" type="long">
<column name="USER_ID" />
<generator class="native"/>
</id>
<property name="userName" type="java.lang.String">
<column name="USER_NAME"/>
</property>
</class>
</hibernate-mapping>
那么接下来,如何建立多对多的关系?
在Role
实体中添加User
集合属性
public class Role {
...
private Set<User> users = new HashSet<>();
}
在Role.hbm.xml
中添加映射关系
- name:对应
user
集合的属性名 - table:指定中间表的表名
- key:
- column:当前实体在中间表需要维护多对多关系的列名
- many-to-many
- class:多对多关系的对象类型
- column:多对多关系的对象在中间表维护的列名
<set name="users" table="sys_user_role">
<key column="ROLE_ID"/>
<many-to-many class="com.machoul.entity.User" column="USER_ID" />
</set>
在User
实体中添加Role
集合属性
public class User {
...
private Set<Role> roles = new HashSet<>();
}
在User.hbm.xml
中添加映射关系
- inverse:放弃维护多对多关系
<set name="roles" table="sys_user_role" inverse="true">
<key>
<column name="USER_ID"/>
</key>
<many-to-many class="com.machoul.entity.Role" column="ROLE_ID" />
</set>
在Role.hbm.xml
中添加映射关系
- name:对应
user
集合的属性名 - table:指定中间表的表名
- key:
- column:当前实体在中间表需要维护多对多关系的列名
- many-to-many
- class:多对多关系的对象类型
- column:多对多关系的对象在中间表维护的列名
<set name="users" table="sys_user_role">
<key column="ROLE_ID"/>
<many-to-many class="com.machoul.entity.User" column="USER_ID" />
</set>
在User
实体中添加Role
集合属性
public class User {
...
private Set<Role> roles = new HashSet<>();
}
在User.hbm.xml
中添加映射关系
- inverse:放弃维护多对多关系
<set name="roles" table="sys_user_role" inverse="true">
<key>
<column name="USER_ID"/>
</key>
<many-to-many class="com.machoul.entity.Role" column="ROLE_ID" />
</set>
- 如果多对多关系是
Role
和User
双向的,那么其中一方必须要放弃维护关联,不然会造成主键冲突