首页 > 技术文章 > Hibernate 学习笔记

windowsxpxp 2020-07-30 11:08 原文

Hibernate 学习笔记

1. 简介

1.1 ORM 思想

1. 什么是持久化?

持久(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML 数据文件中等等。

2. 什么是持久?

持久层(Persistence Layer),即专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。

3.什么是 ORM

ORM ,即 Object-Relational Mapping ,它的作用是在关系型数据库和对象之间一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的 SQL 语句打交道,只要像平时操作对象一样操作它就可以了。

4. 为什么要做持久化和 ORM 设计?(重要)

在目前的企业应用系统设计中,MVC,即 Model(模型)-View(视图)-Controller(控制)为主要的系统架构模式。MVC 中的 Model 包含了复杂的业务逻辑和数据逻辑,以及数据存取机制(如 JDBC 的连接、SQL 生成和 Statement 创建、还有 ResultSet 结果集的读取等)等。将这些复杂的业务逻辑和数据逻辑分离,以及将系统的紧耦合关系转化为松耦合关系(即解耦合),是降低系统耦合度迫切要做的,也是持久化要做的工作。MVC 模式实现了架构上将表现层(即View)和数据处理层(即 Model)分离的解耦合,而持久化的设计则实现了数据处理层内部的业务逻辑和数据逻辑分离的解耦合。而 ORM 作为持久化设计中的最重要也是最复杂的技术,也是目前业界热点技术。

简单来说,按通常的系统设计,使用 JDBC 操作数据库,业务处理逻辑和数据存储逻辑是混杂在一起的。

一般都是如下几个步骤:

  1. 建立数据库连接,获得 Connnection 对象。
  2. 根据用户的输入组装查询 SQL 语句。
  3. 根据 SQL 语句简历 Statement 对象,或者 PrepareStatement 对象。
  4. 用Connection 对象执行 SQL 语句,获得结果集 ResultSet 对象。
  5. 然后一条一条读取结果集 ResultSet 对象中的数据。
  6. 根据读取到的数据,按特定的业务逻辑进行计算。
  7. 根据计算得到的结果再组装更新 SQL 语句。
  8. 再使用 Connection 对象执行更新 SQL 语句,以更新数据库中的数据。
  9. 最后一次关闭各个 Statement 对象和 Connection 对象。

由上可看出代码逻辑非常复杂,这还不包括某条语句执行失败的处理逻辑。其中的业务处理逻辑和数据存取逻辑完全混杂再一起。而一个完整的系统要包含成千上万个这样重复的而又混杂的处理过程,加入要对其中某些业务逻辑或者一些相关联的业务流程做修改,要改动的代码量将不可想象。另一方面,加入要换数据库、产品或者运行环境也可能是个不可能完成的任务。而用户的运行环境和要求却千差万别,我们不可能为每一个用户每一种运行环境设计一套一样的系统,所以就要净一样的处理代码即业务逻辑和可能不一样的处理即数据存取逻辑分离开来。另一方面,关系型数据库中数据基本都是以一行行的数据进行存取的,而程序运行却是一个个对象进行处理。而且目前大部分数据库驱动技术(如ADO.NET、JDBC、ODBC等等)均是以行集的结果集一条条进行处理的。所以为解决这一困难,就出现 ORM 这一个对象和数据之间映射技术。

举例来说,比如要完成一个购物打折促销的程序,用 ORM 思想将如下实现。(引自《深入浅出Hibernate》)

1.2 介绍

Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,它将 POJO 与数据库表简历映射关系,是一个全自动的 orm 框架。Hibernate 可以自动生成 SQL 语句,自动执行,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。Hibernate 可以应用在任何使用 JDBC 的场合,既可以在 Java 的苦厄护短程序使用,也可以在 Servlet/JSP 的 WEB 应用中使用,最具有革命意义的是, Hibernate 可以在应用 EJB 和 JavaEE 架构中取代 CMP, 完成数据持久化的重任。

2. 第一个 Hibernate 工程项目

环境

JDK 1.8

Maven 3.6.1

mysql 5.7.27

Hibernate 5.4.18

idea 2019.3

1. 创建 Maven 工程

跟我们以前常规创建maven工程一样,创建完后引入我们的需要的依赖

<dependencies>
    <!-- hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.4.18.Final</version>
    </dependency>
    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.39</version>
    </dependency>
    <!-- log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <!-- junit 测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>

2. 创建表和实体类

创建表的SQL语句

DROP TABLE if EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user`(
	id INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT,
	`name` VARCHAR(50) NOT NULL,
	`password` VARCHAR(50) NOT NULL
);

实体类

这里使用了 Lombok 插件

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    /**
     * id
     */
    private Integer id;

    /**
     * 用户名
     */
    private String name;

    /**
     * 密码
     */
    private String password;

}

3. 使用 mapper.xml 进行 orm 映射

在 resources 目录下创建 User.hbm.xml 文件,并配置如下

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <!-- orm 映射 实体类和数据表进行映射 -->
    <class name="com.xp.model.entity.User" table="user">
        <!-- 映射主键,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
        <id name="id" column="id">
            <!-- 设置主键策略 -->
            <generator class="native"/>
        </id>
        <!-- 映射字段,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
        <property name="name" column="name"/>
        <property name="password" column="password"/>
    </class>
</hibernate-mapping>

4. 配置 Hibernate 核心配置文件

在 config 目录下创建 Hibernate 核心配置文件 hibernate.cfg.xml ,当然也可以直接在resources 目录下创建(我们后面点进源码发现它是默认配置资源目录下 hibernate.cfg.xml 文件的)

<!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.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?useTimezone=true&amp;serverTimezone=GMT%2b8&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</property>

        <!-- 可选项 -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <!-- 配置方言 -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</property>
        
        <!-- 配置建表策略 -->
        <!-- 建表策略有 create-drop:先删除原来的表再重新创建,create:如果没有表则创建表,
			update:如果没有表则创建,有的话则更新数据。validate:创建前校验
		-->
        <property name="hibernate.hbm2ddl.auto">update</property>

        <!-- 配置 mapping -->
        <mapping resource="mapper/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

这里分享个找配置文件的小技巧

我们点进 hibernate-core 这个jar包中,可以找到配置文件的头部部分

进入 hibernate-core 的jar包

image-20200728111031404

然后往下拉,找到 dtd 文件

image-20200728111332294

5. 测试

编写我们的测试类 MyTest

public class MyTest {

    @Test
    public void test() {
        // 1.读取 Hibernate 核心配置文件,得到配置对象
        Configuration configuration = new Configuration().configure("config/hibernate.cfg.xml");
        // 2.获取 session(会话) 工厂
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        // 3.从 session(会话)工厂中获取 session(会话)
        Session session = sessionFactory.openSession();
        // 4.开启事务
        Transaction transaction = session.beginTransaction();
        // 5.需要插入的 User 信息
        User user = new User(null, "张三", "666");
        // 6.执行插入操作
        session.save(user);
        // 7.提交事务
        transaction.commit();
        // 8.关闭 session
        session.close();
    }

}

上面 Configuration configuration = new Configuration().configure("config/hibernate.cfg.xml"); 处,我们点进 configure() 方法的源码,会发现它默认是找资源目录下的 hibernate.cfg.xml 文件

// 默认获取 Hobernate 核心配置文件的目录
public static final String DEFAULT_CFG_RESOURCE_NAME = "hibernate.cfg.xml";
/**
 * Use the mappings and properties specified in an application resource named <tt>hibernate.cfg.xml</tt>.
 *
 * @return this for method chaining
 *
 * @throws HibernateException Generally indicates we cannot find <tt>hibernate.cfg.xml</tt>
 *
 * @see #configure(String)
 */
public Configuration configure() throws HibernateException {
   return configure( StandardServiceRegistryBuilder.DEFAULT_CFG_RESOURCE_NAME );
}

如果我们需要使用 log4j 来查看测试时的sql语句以及执行情况,可以编写 log4j配置

将 log4j.properties 文件放在 resources 根目录下

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/xp.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=【%p】【%d{yy-MM-dd}】【%c】%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

运行测试,查看控制台输出以及数据库表的数据。

如果报了 Field 'id' doesn't have a default value 的错误,则需要检查主键是否自增,如果没有,则需要我们自己手动将主键改成自增,然后重新运行测试即可。

到这里,我们第一个 Hibernate 工程项目就搭建测试完毕

3. CRUD

3.1 编写 Hibernate 工具类

运行完刚刚的测试类后,我们会发现我们的一些对象只需要使用一次(比如配置对象和session(对话)工厂)。那么我们可以封装成一个工具类来让我们不需要重复创建这些只使用一次的对象,并且让我们不用写重复的代码。

public class HibernateUtil {

    private static Configuration configure = null;

    private static SessionFactory sessionFactory = null;

    static {
        configure =new Configuration().configure("config/hibernate.cfg.xml");
        sessionFactory = configure.buildSessionFactory();
    }

    /**
     * 防止 new
     */
    private HibernateUtil() {
    }

    /**
     * 获得Hibernate会话对象
     * @return  Hibernate 会话对象
     */
    public static Session getSession() {
        return sessionFactory.openSession();
    }

}

然后将我们刚刚的测试类进行修改

@Test
public void test() {
    // 通过我们自己创建的 Hibernate 工具类获取 session(会话)对象
    Session session = HibernateUtil.getSession();
    // 开启事务
    Transaction transaction = session.beginTransaction();
    // 插入数据
    session.save(new User(null, "张三", "123"));
    // 提交事务
    transaction.commit();
    // 关闭 session (会话)
    session.close();
}

3.2 Hibernate 原生提供的CRUD

3.2.1 增加

就是刚刚我们一开始写的那个代码

session.save(Object object)。传入一个需要增加的对象

@Test
public void update() {
    // 通过工具类获取 session(会话)对象
    Session session = HibernateUtil.getSession();
    // 开启事务
    Transaction transaction = session.beginTransaction();
    // 插入数据
    session.save(new User(null, "张三", "123"));
    // 提交事务
    transaction.commit();
    // 关闭 session (会话)
    session.close();
}

3.2.2 删除

session.delete(Class entityType, Serializable id)。

第一个参数为需要删除的数据映射的对象的 Class 对象,第二个为需要删除的数据在表中的id

@Test
public void delete(){
    // 获取会话
    Session session = HibernateUtil.getSession();
    // 开启事务
    Transaction transaction = session.beginTransaction();
    // 先查询要删除的数据
    User user = session.get(User.class, 1);
    // 删除数据
    session.delete(user);
    // 提交事务
    transaction.commit();
    // 关闭会话
    session.close();
}

3.2.3 修改

session.update(Class entityType, Serializable id)。

第一个参数为需要修改的数据映射的对象的 Class 对象,第二个为需要修改的数据在表中的id

@Test
public void update(){
    // 获取会话
    Session session = HibernateUtil.getSession();
    // 开启事务
    Transaction transaction = session.beginTransaction();
    // 先查询要更新的数据
    User user = session.get(User.class, 1);
    // 设置修改的内容
    user.setName("李四");
    // 更新数据
    session.update(user);
    // 提交事务
    transaction.commit();
    // 关闭会话
    session.close();
}

3.2.4 查询

session.get(Class entityType, Serializable id)。

第一个参数为需要查询的数据映射的对象的 Class 对象,第二个为需要查询的数据在表中的id

@Test
public void query(){
    Session session = HibernateUtil.getSession();
    // session.get(User.class, 1) 这里的 get() 方法的第一个参数是想要返回对象的Class对象,第二个参数是id
    System.out.println("查询得到的结果:"+session.get(User.class, 1));
    // 关闭会话
    session.close();
}

3.3 HQL

3.3.1 简介

HQL 是 Hibernate Query Language 的缩写,提供丰富灵活、更为强大的查询能力;

HQL 更加接近 SQL 语句查询语法

3.3.2 语法步骤

  1. 编写 HQL 语句
  2. 根据 HQL 语句创建查询对象
  3. 根据查询对象获得查询结果返回

3.3.3 简单使用

基于我们刚刚搭建的环境,我们再编写一个测试方法

@Test
public void test2(){
    // 获取会话
    Session session = HibernateUtil.getSession();
    // 编写 HQL 的 SQL 语句,这里也可以简写成 from User,只不过为了更好区分表名和报包名,建议加上全限定的包名
    String HQL = "from com.xp.model.entity.User";
    // 执行SQL语句
    Query query = session.createQuery(HQL);
    // 获取查询结果
    List<User> list = query.list();
    for (User user : list) {
        System.out.println(user);
    }
}

可以发现,相比于我们以前使用 JDBC 查询数据库,写的 SQL 要短那么一些

3.3.4 预编译

Hibernate 也支持自己手写 SQL 时使用占位符进行预编译,需要时再替换占位符

@Test
public void test2(){
    // 获取会话
    Session session = HibernateUtil.getSession();

    String HQL = "from com.xp.model.entity.User where id = ?0";

    Query query = session.createQuery(HQL1);
    query.setParameter(0,1);
    // 获取一个查询结果
    User user = (User) query.uniqueResult();

    System.out.println(user);
}

3.3.5 分页

Hibernate 中,可以使用 setFirstResult(int startPosition) 方法设置分页查询的开始下标,使用 setMaxResults(int maxResult) 方法设置分页查询的个数

@Test
public void test3(){
    // 获取会话
    Session session = HibernateUtil.getSession();
    // 编写 HQL 的 SQL 语句
    String HQL = "from com.xp.model.entity.User";
    // 执行SQL语句
    Query query = session.createQuery(HQL);
    query.setFirstResult(0);
    query.setMaxResults(10);
    // 获取并输出查询结果
    List<User> list = query.list();
    for (User user : list) {
        System.out.println(user);
    }

}

3.4 Criteria

Hibernate 在 5.2 版本后,对于 Criteria 查询重点放在了 JPACriteriaQuery API 上

这个父路涵盖了旧 Hibernate.org.hibernate.Criteria.API,它应该被认为是不赞成使用的。
This appendix covers the legacy Hibernate org.hibernate.Criteria API, which should be considered deprecated. 
新的开发应该集中再 JPA javax.persistence.criteria.CriteriaQuery API 。最终, Hibernate 的特定标准特性将作为拓展移植到 JPA javax.persistence.criteria.CriteriaQuery。有关 JPA API 的详细信息,请参见 Criteria
New development should focus on the JPA javax.persistence.criteria.CriterQuery API. Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA javax.persisitence.criteria.CriteriaQuery . For details on the JPA APIs, see Criteria

所以我们这里只讲符合 JPA 规范的 Criteria 写法

3.4.1 简单使用

@Test
public void test(){
    // 获取会话
    Session session = HibernateUtil.getSession();
    // 获取 criteriaBuilder
    CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
    // 创建 CriteriaQuery
    CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
    // 获取 root
    Root<User> root = criteriaQuery.from(User.class);
    // 给 CriteriaQuery 对象添加查询条件,并执行查询
    criteriaQuery.where(criteriaBuilder.equal(root.get("id"), 1));
    // 获取查询的结果集
    List<User> resultList = session.createQuery(criteriaQuery).getResultList();
    for (User user : resultList) {
        System.out.println(user);
    }
}

由上可得,Hibernate 5.2后使用 Criteria 为:

  1. 创建一个 CriteriaBuilder
  2. 获取 CriteriaQuery
  3. 指定实体类 criteria.from(User.class)
  4. 执行查询 session.createQuery 获取结果集 .getResultList()

3.4.2 条件查询

我们需要条件查询时,可以事用 criteriaBuilder 中方法来进行查询:

关系 方法
大于(>) gt
大于等于(>=) ge
小于() lt
小于等于() le
等于 eq
不等于 ne
in in
between and between
like like
not null isNotNull
is not null isNull
or or
and and

在处理 OR 或者 AND 时,需要和其他方法嵌套使用

3.5 原生 SQL

前面的几种方法,都是不需要编写 sql 语句就可以实现 CRUD ,那么如果我们想要写原生的 SQL 呢?

在 Hibernate 中,也提供了我们写原生 SQL 的方式

@Test
public void test2(){
    String sql = "select * from user";

    Session session = HibernateUtil.getSession();
    // 注意:.addEntity(User.class) 是为了告诉我们的 Hibernate ,我们查询的结果封装成什么对象进行存储
    // 如果不这么做的话,query.getResultList() 获得的是 Object 的集合,而不是我们真正的 User对象集合
    Query query = session.createSQLQuery(sql).addEntity(User.class);

    List<User> resultList = query.getResultList();
    for (User user : resultList) {
        System.out.println(user);
    }

}

  • 使用原生 SQL 的方式操作数据库,使用的方法是 createSQLQuery(String queryString)
  • .addEntity() 方法是为了告诉我们的 Hibernate,我们查询的结果封装成什么对象进行存储
  • .addEntity() 有多个重载方法,传 Class 对象时,直接 类命.class 即可,传字符串时,要传该类的全限定名,比如 "com.xp.model.entity.User"

4. 一对多

4.1 什么是一对多关系

一对多关系是关系数据库中两个表之间的一种关系,该关系中第一个表中的单个行可以与第二个表中的一个或多个行相关,但第二个表中的一个行只可以与第一个表中的一个行相关。

4.2 一对多实例

场景:一个用户可以有多辆车,而每辆车只有一个用户。

  1. 创建实体类

    我们在原先的环境上创建多一个实体类 Car

    这里要注意,两个实体类都不要使用 lombok ,若在实体类中使用了有对应关系的类的对象,则会栈内存溢出

    public class Car {
    
        /**
         * 车辆id
         */
        private Integer id;
    
        /**
         * 车辆名
         */
        private String name;
    
        /**
         * 车辆颜色
         */
        private String color;
    
        /**
         * 拥有这辆车的用户
         */
        private User user;
    
        public Car() {
        }
    
        public Car(Integer id, String name, String color, User user) {
            this.id = id;
            this.name = name;
            this.color = color;
            this.user = user;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    
        @Override
        public String toString() {
            return "Car{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
    
  2. 编写和修改mapping

    我们先将原来 User 的 mapping User.hbm.xml 增加关系映射

    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="com.xp.model.entity">
        <!-- orm 映射 实体类和数据表进行映射,name是实体类的成员变量,table 表示映射的表名 -->
        <class name="User" table="user">
            <!-- 映射主键,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
            <id name="id" column="id">
                <!-- 设置主键策略 -->
                <generator class="native"/>
            </id>
            <!-- 映射字段,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
            <property name="name" column="name"/>
            <property name="password" column="password"/>
            <!-- 一对多关系映射 -->
            <!-- 这里 cascade="all" 的意思是:如果该用户执行增删改后,其对应的 Car 表中的数据也会同步更新,也就是说cascade 等于的属性代表级联风格-->
            <set name="cars" cascade="all">
                <key column="cid"/>
                <one-to-many class="Car"/>
            </set>
        </class>
    </hibernate-mapping>
    

    <set name="cars" cascade="all"> 标签是用来映射 set 集合的

    <one-to-many> 标签表示映射一对多关系

    级联风格 Session中的方法
    persisit persist()
    merge merge()
    save-update save()、update()、saveOrUpdate()
    delete delete()
    lock lock()
    refresh refresh()
    evict evict()
    replicate replicate()

    创建 Car 实体类的 mapping Car.hbm.xml

    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="com.xp.model.entity">
        <class name="Car" table="car">
            <!-- 主键映射 -->
            <id name="id" column="id">
                <!-- 设置主键映射规则 -->
                <generator class="native"/>
            </id>
            <!-- 其他字段映射 -->
            <property name="name" column="name"/>
            <property name="color" column="color"/>
            <!-- 多对一关系映射 -->
            <many-to-one name="user" class="User" column="cid"/>
        </class>
    </hibernate-mapping>
    

    <many-to-one> 标签表示多对一关系映射,

  3. 将刚刚创建的 mapping Car.hbm.xml 注册到 hibernate 核心配置文件中

    <mapping resource="mapper/Car.hbm.xml"/>
    
  4. 测试

    @Test
    public void test() {
        // 获取会话
        Session session = HibernateUtil.getSession();
        // 初始化用户和汽车
        User user = new User(null, "李老板", "888", null);
        Car car1 = new Car(null, "宝马", "红色", null);
        Car car2 = new Car(null, "奔驰", "白色", null);
        Car car3 = new Car(null, "保时捷", "蓝色", null);
    
        // 让用户拥有三辆车
        Set<Car> cars = new HashSet<>();
        cars.add(car1);
        cars.add(car2);
        cars.add(car3);
        user.setCars(cars);
        // 让车拥有主人
        car1.setUser(user);
        car2.setUser(user);
        car3.setUser(user);
    
        // 开启事务,并将对象中存储的数据写入数据库中
        Transaction transaction = session.beginTransaction();
        session.save(user);
        session.save(car1);
        session.save(car2);
        session.save(car3);
        transaction.commit();
        session.close();
    }
    

    创建后表结构和数据如下

    image-20200729200849340

:如果出现了栈溢出(StackOverflowError),则需修改实体类,将实体类中调用关联关系对象的方法修改,比如 toString() 方法和 hashCode() 方法。

5. 多对一

5.1 什么是多对一关系

多对多关系是关系数据库中两个表之间的一种关系,该关系中第一个表中的第一个行可以与第二个表中的一个或多个行相关。第二个表中的第一个行也可以与第一个表中的一个或多个行相关。

5.2 多对多实例

场景:一个用户可以有很多个角色,一个角色可以对应很多个用户

  1. 创建实体类

    这里的实体类都使用了 lombok 进行偷懒

    User

    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
    
        /**
         * 用户id
         */
        private Integer userId;
    
        /**
         * 用户名
         */
        private String name;
    
        /**
         * 密码
         */
        private String password;
    
        /**
         * 用户对应的角色集合
         */
        private Set<Role> roles;
    
    }
    

    Role

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public class Role {
        /**
         * 角色id
         */
        private Integer roleId;
    
        /**
         * 角色名
         */
        private String roleName;
    
        /**
         * 角色对应的用户set集合
         */
        private Set<User> users;
    
    }
    
  2. 编写 mapping 文件

    User.hbm.xml

    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="com.xp.model.entity">
        <!-- orm 映射 实体类和数据表进行映射 -->
        <class name="User" table="user">
            <!-- 映射主键,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
            <id name="userId" column="id">
                <!-- 设置主键策略 -->
                <generator class="native"/>
            </id>
            <!-- 映射字段,其中name表示实体类中的成员变量,column 表示对应表中的字段 -->
            <property name="name" column="name"/>
            <property name="password" column="password"/>
            <!-- 
    			多对多 
    			注意:这里的 table 表示存储 多对多 关系映射的表,除了创建多对多这两张表,
    			还会通过这个映射来创建通过外键存储他们之间关系的 user_role 表
    		-->
            <set name="roles" table="user_role" cascade="save-update" inverse="true">
                <!-- 这里的 column 表示在另一张关系映射表中外键的名称 -->
                <key column="uid"/>
                <many-to-many class="Role" column="rid"/>
            </set>
        </class>
    </hibernate-mapping>
    

    inverse :这个属性是用来优化级联关系的维护的,可以设置 true 和 false 属性,默认是 false。当这个值为 true 时,不维护配置关系,交给对方来维护。

    <set name="roles" table="user_role" cascade="save-update" inverse="true">:这里得注意得是 table 的值是通过外键存储多对多关系的表。

    <key column="uid"/>:这里的 colunmn="uid" 表示外键的字段名,也就是说 user_role 这张表和 user 这张表的主键绑定的外键名叫 uid

    Role.hbm.xml

    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="com.xp.model.entity">
        <class name="Role" table="role">
            <!-- 主键映射 -->
            <id name="roleId" column="roleId">
                <generator class="native"/>
            </id>
            <!-- 其他字段映射 -->
            <property name="roleName" column="roleName"/>
            <!-- 多对多映射 -->
            <set name="users" cascade="save-update" table="user_role">
                <key column="rid"/>
                <many-to-many column="uid" class="User"/>
            </set>
    
        </class>
    </hibernate-mapping>
    
  3. 编写 Hibernate 核心配置文件hibernate.cfg.xml

    <!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.jdbc.Driver</property>
            <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?useTimezone=true&amp;serverTimezone=GMT%2b8&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password">root</property>
    
            <!-- 可选项 -->
            <!-- 显示 SQL 语句 -->
            <property name="hibernate.show_sql">true</property>
            <!-- 显示的 SQL 语句是否格式化,也就是说打印出来的 SQL 语句是一行还是多行显示 -->
            <property name="hibernate.format_sql">true</property>
    
            <!-- 配置方言 -->
            <property name="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</property>
    
            <!-- 配置建表策略 -->
            <property name="hibernate.hbm2ddl.auto">update</property>
    
            <!-- 配置 mapping -->
            <mapping resource="mapper/User.hbm.xml"/>
            <mapping resource="mapper/Role.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>
    
  4. 测试

    @Test
    public void test(){
        // 获取会话,开启事务
        Session session = HibernateUtil.getSession();
        Transaction transaction = session.beginTransaction();
        // 初始化表单数据
        User user1 = new User(null, "张三", "666", null);
        User user2 = new User(null, "李四", "444", null);
        Role role1 = new Role(null, "CEO", null);
        Role role2 = new Role(null, "IT码农", null);
        Role role3 = new Role(null, "老司机", null);
    
        HashSet<Role> roles1 = new HashSet<>();
        HashSet<Role> roles2 = new HashSet<>();
        roles1.add(role1);
        roles1.add(role2);
        roles1.add(role3);
        roles2.add(role2);
    
        user1.setRoles(roles1);
        user2.setRoles(roles2);
    
        HashSet<User> users1 = new HashSet<>();
        HashSet<User> users2 = new HashSet<>();
        HashSet<User> users3 = new HashSet<>();
        users1.add(user1);
        users2.add(user1);
        users2.add(user2);
        users3.add(user1);
    
        role1.setUsers(users1);
        role2.setUsers(users2);
        role3.setUsers(users3);
    
        // 插入数据
        session.save(user1);
        session.save(user2);
        session.save(role1);
        session.save(role2);
        session.save(role3);
        // 提交事务,关闭会话
        transaction.commit();
        session.close();
    }
    

    测试后会生成 role、user、user_role 三张表,具体表关系如下:

    image-20200730105455088

推荐阅读