认识 ORM 框架 Hibernate,为什么 2022 年了还在谈论它?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 SLS,月写入数据量 50GB 1个月
简介: 前言Hibernate 作为一种全自动 ORM 框架,在几年前常与 Spring、Struts2 一起使用,并称 SSH,作为主流的企业级应用框架。伴随着 MyBatis 的诞生,以及 Hibernate 本身的一些缺陷,如今 Hibernate 已经慢慢淡出了大家的视野。

前言


Hibernate 作为一种全自动 ORM 框架,在几年前常与 Spring、Struts2 一起使用,并称 SSH,作为主流的企业级应用框架。伴随着 MyBatis 的诞生,以及 Hibernate 本身的一些缺陷,如今 Hibernate 已经慢慢淡出了大家的视野。


然而作为 Java 持久化规范 JPA 的一种实现,日常开发中,我们偶尔还能听到它的声音。有时我们还会将其与 MyBatis 做一些对比,不过由于很多同学没有使用过它,可能查阅一些资料后还是云里雾里的,因此有必要对它有一个入门级的认识。


本文的目的并非谈论其缺陷,你能收获的是对 Hibernate 的整体认识。


快速上手


让我们先通过一个实例的方式对 Hibernate 有一个初步的认识。


首先引入相关依赖,这里我们使用的是 MySQL 数据库。


<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.9.Final</version>
</dependency>


假定我们在数据库中有一个 user 表定义如下。


create table user
(
    id       bigint unsigned auto_increment comment '主键'
        primary key,
    username varchar(20) not null comment '用户名',
    password varchar(20) not null comment '密码'
);


对应到 Java 中,应该有一个实体类与之对应。


@Data
public class User  {
    private Long id;
    private String username;
    private String password;
}


作为 ORM 框架,需要将数据库表和实体类之间建立映射关系。最初 Hibernate 使用 xml 文件表示映射关系,sun 公司参考 Hibernate 制定 JPA 规范后,Hibernate 又反向实现了 JPA 规范,而后 Hibernate 支持了使用 JPA 规范中的注解表示映射关系。这里我们使用 Hibernate 的 xml 文件表示映射关系。

表示 user 表与 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.zzuhkp.hibernate.entity.User" table="user">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="username" column="username"/>
        <property name="password" column="password"/>
    </class>
</hibernate-mapping>


最后,我们还需要对 Hibernate 进行一些全局的配置,例如 JDBC 的连接信息、日志打印等。

配置文件 hibernate.cfg.xml 内容如下。


<?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/test</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">12345678</property>
        <!--SQL 打印-->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <!--映射文件-->
        <mapping resource="/hibernate/mapping/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>


有了必要的配置后,我们就可以对数据库进行操作了。


    @Test
    public void testSession() {
        Configuration configuration = new Configuration();
        configuration.configure("/hibernate/cfg/hibernate.cfg.xml");
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            User user = new User();
            user.setUsername("hkp");
            user.setPassword("123");
            session.save(user);
            transaction.commit();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            transaction.rollback();
        } finally {
            session.close();
        }
        sessionFactory.close();
    }


这里只是简单的向 user 表中添加了一条数据。


核心组件


通过示例,我们可以看出 Hibernate 有如下的一些核心组件。


1. Configuration


Configuration 是 Hibernate xml 配置文件的 Java 类表示,如果有一些配置不方便在 xml 配置文件中表示,还可以直接使用 Configuration 类的 API 直接配置。


通常配置中会包含一些影响 Hibernate 运行的属性配置、映射文件配置。


Hibernate 可配置属性实在太多了,影响内部组件运行的属性全部一股脑放在了一起,因此个人感觉设计不是太友好,也许和 MyBatis 一样,把属性配置下沉到表示内部组件的标签中更为合适。


2. SessionFactory


SessionFactory 是 Session 的工厂类,对于项目中的一个数据库来说,根据配置创建一个 SessionFactory 对象即可。


3. Session


Session 表示 Hibernate 与数据库之间的一次会话,类似于 Connection,提供了多种方式操作数据库,非线程安全,通常来说,会为每个请求单独创建一个 Session 对象。这是 Hibernate 最核心的一个类。


4. Transaction


Transaction 是 Hibernate 中进行事务操作的接口,在 Hibernate 中可以使用基于 JDBC 的事务和基于 JTA 的事务,上层对事务的实现是无感的。


5. Hibernate 5 新组件


由于 Configuration 设计的复杂性及局限性,Hibernate 设计了一套新的组件替换 Configuration 创建 SessionFactory。最简单的构建示例代码如下。


// 1. 构建 ServiceRegistry
StandardServiceRegistryBuilder standardServiceRegistryBuilder = new StandardServiceRegistryBuilder();
standardServiceRegistryBuilder.configure("/hibernate/cfg/hibernate.cfg.xml");
ServiceRegistry serviceRegistry = standardServiceRegistryBuilder.build();
// 2. 构建 Metadata
MetadataSources sources = new MetadataSources(serviceRegistry);
Metadata metadata = sources.buildMetadata();
// 3. 构建 SessionFactory
SessionFactory sessionFactory = metadata.buildSessionFactory();


可以看到,即便用最简单的方式构建 SessionFactory,也涉及到了多个类。在 API 设计上也许更合理,但是对于使用方来说却造成了诸多不便。整个 Hibernate 框架提出了太多的概念,相对于 MyBatis 来说确实重了不少,上手难度极高。


对上述构建 SessionFactory 过程做一个简单的总结。


第一步是构建 ServiceRegistry,这个接口用于对 Hibernate 内部使用的服务进行注册,通常使用 StandardServiceRegistryBuilder 构建,构建时可以设置配置文件在类路径下的地址。

第二步是构建 Metadata,这个接口内部包含了实体和数据库表的映射关系,使用 MetadataSources 进行必要的配置和构建。

最后使用 Metadata 构建 SessionFactory。

每个步骤都可以进行细化,添加更多的配置,以 MetadataSources 为例,我们可以使用如下的方式添加映射文件。


MetadataSources sources = new MetadataSources(serviceRegistry);
sources.addResource("/hibernate/mapping/User.hbm.xml");
Metadata metadata = sources.buildMetadata();


常用配置


数据库访问


JDBC 数据源


ORM 框架必须要配置的是数据库连接,而连接一般会配置一个支持连接池的数据源。


Hibernate 内部默认实现了一个连接池,同时也支持多种开源的 DataSource 实现,包括 c3p0、Proxool、HikariCP、Vibur DBCP、Argoal,如果这些不满足要求还可以定义自己的实现。


以目前比较常用的 HikariCP 为例,使用方式如下。


首先引入依赖。


<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-hikaricp</artifactId>
    <version>5.6.9.Final</version>
</dependency>


然后在配置文件中添加必要的配置即可。


<!--通用配置,针对多种数据源有效-->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">12345678</property>
<!--HikariCP 特有配置-->
<property name="hibernate.hikari.idleTimeout">600000</property>
<property name="hibernate.hikari.minimumIdle">5</property>
<property name="hibernate.hikari.maximumPoolSize">10</property>


JTA 事务管理


除了对 JDBC 的支持,Hibernate 还添加了对 JTA 事务管理的支持。以 JTA 的实现 atomikos 为例,首先需要引入依赖。

<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jdbc</artifactId>
    <version>4.0.6</version>
</dependency>


然后在配置文件中添加如下 JTA 的配置。


<!--设置 JTA 控制事务-->
<property name="hibernate.transaction.coordinator_class">jta</property>
<!--JTA 事务管理器配置-->
<property name="hibernate.transaction.jta.platform">Atomikos</property>
<!--需要时获取 Connection,Session 关闭时释放 Connection-->
<property name="hibernate.connection.handling_mode">DELAYED_ACQUISITION_AND_HOLD</property>
<!--Session 与线程绑定-->
<property name="current_session_context_class">jta</property>


📢注意:由于 atomikos 有自己独有的数据源配置,因此上面示例配置文件中的数据源配置需要删去,然后使用 API 单独配置数据源实例。

private static DataSource getDataSource() {
    Properties properties = new Properties();
    properties.put("url", "jdbc:mysql://127.0.0.1:3306/test");
    properties.put("user", "root");
    properties.put("password", "12345678");
    AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
    ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
    ds.setXaProperties(properties);
    ds.setUniqueResourceName("resourceName");
    ds.setPoolSize(10);
    ds.setBorrowConnectionTimeout(60);
    return ds;
}
Configuration configuration = new Configuration();
configuration.configure("/hibernate/cfg/hibernate.cfg.xml");
// JTA 数据源
configuration.getProperties().put(AvailableSettings.DATASOURCE, getDataSource());


由于 Hibernate 抽象出了事务接口 Tranaction,因此事务使用方式和 JDBC 保持一致。


日志


关于日志,常用场景是开发阶段查看打印的 SQL,配置文件中常使用如下的两个属性配置

<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>

。其中 hibernate.show_sql 用于配置 SQL 打印,hibernate.format_sql 则用于对打印的 SQL 格式化。

不过有时我们可能还想要看设置的参数值,此时需要借助具体的日志框架来实现。比较常用日志框架是 logback,引入依赖。

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>


然后在 logback 配置文件 logback.xml 中做如下配置就可以查看 SQL 参数了。


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{yyyy-MM-dd_HH:mm:ss.SSS} [%-5level] [%logger{36}] - %msg%n</Pattern>
        </encoder>
    </appender>
    <logger name="org.hibernate.type" level="trace"/>
    <root level="info">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

主要是把 org.hibernate.type 日志级别设置为 trace。


模式生成


模式生成用于根据某些规则创建或修改数据库表。


1. 映射关系生成表结构


配置文件中有一个属性 hibernate.hbm2ddl.auto 可用于自动根据配置的映射关系生成数据库表结构,可取值及含义如下。


image.png


可以看到,除了 none 和 validate,其他的取值都会导致数据库表的变动,而我们一般不会单节点部署服务,因此其他的取值更适合单机应用或在测试环境使用。


2. 脚本文件执行


Hibernate 允许在应用启动时执行给定的脚本文件。假定类路径下有一个脚本文件 schema-generation.sql 内容如下。


insert into user(username,password) values ('hkp','123');


如果想要执行这个脚本文件,可以在配置文件中指定。


<property name="hibernate.hbm2ddl.import_files">schema-generation.sql</property>

这样,当应用启动时就会执行我们的脚本文件了,不过可以看出,这仅适用于单机应用单次执行,为避免多节点重复执行,建议仅在测试环境使用。


3. 数据库对象


如果不想创建脚本文件,还可以在映射文件指定 SQL。


<hibernate-mapping>
    <database-object>
        <create>...</create>
        <drop>...</drop>
    </database-object>
</hibernate-mapping>


4. 其他映射定义


模式生成时除了映射关系,还可指定一些约束,例如是否唯一、默认值等。示例如下:

<class name="com.zzuhkp.hibernate.entity.User" table="user">
    <property name="username" column="username" unique="true"/>
    <property name="deleted">
        <column name="deleted" default="0"/>
    </property>
</class>

体管理


作为一个 ORM 框架,Hibernate 除了处理映射关系,还会把从数据库查询到的数据在内存中统一的管理,管理的对象被称为领域模型,个人比较倾向于使用实体表达。


缓存


在 Hibernate 中,缓存被分为基于 SessionFactory 的一级缓存和基于 Session 的二级缓存。


一级缓存主要用于 Hibernate 跟踪管理的实体的状态变化,在某个时间 Hibernate 会把实体的变化提交到数据库。一级缓存不可关闭,也可用于避免重复从数据库加载数据。


二级缓存是常规意义上的缓存,Hibernate 提供的实现主要基于内存,不适用于多节点,由于缓存可能与数据库不一致,因此个人不推荐使用,限于篇幅这里也不加介绍。可通过如下的配置属性关闭二级缓存。


<property name="hibernate.cache.use_second_level_cache">false</property>


实体状态


Hibernate 管理的实体有自己的生命周期,不同的操作会操作其状态发生不同的变化,我这里画了张图,如下所示。

image.png

实体的状态有4种,包括临时、持久、游离、删除。


刚创建的实体处于临时状态。


创建的实体被保存到数据库后,这个实体就变成持久状态被 Hibernate 管理起来。

持久状态的实体被删除后就处于删除状态,待 Hibernate 把状态变化同步到数据库后就会真正删除数据。

Session 关闭后持久状态的实体就会变成游离状态,当然了也可以手动清理 Session。通过 Session 的某些方法还可以将游离状态的实体转换为持久状态。

实体修改后,Hibernate 不一定马上把变动同步到数据库,何时同步可以通过 Session.setFlushMode 或 Session.setHibernateFlushMode 方法设置,默认情况使用 FlushMode.AUTO 自动刷新模式, 事务提交、 HQL 或 SQL 执行时都可能触发同步。


用户可以手动调用 Session.flush 方法将变动刷新到数据库,也可以手动调用 Session.refresh 将最新的数据刷新到管理的实体中。


数据库操作


Hibernate 提供了 4 种操作数据库的方式,分别是 Session API 、Criteria API、HQL 和 SQL。


Session API


Sesion API 主要用于对单条记录进行增删改查。还是以快速上手章节部分的数据库表为例,这里不再赘述相关配置。


1. 新增


新增记录直接调用 Sesion.save 方法即可,如果主键由数据库生成,Hibernate 会自动设置主键值到对应的实体字段中。示例如下:


session.save(user);


2. 删除


删除记录直接调用 Session.remove 方法即可,需要注意的是方法参数值必须为 Hibernate 管理的持久状态的实体,这也意味着必须先查询,然后再删除,因此不太灵活。示例如下:

session.remove(user);


3. 修改


修改操作调用 Session.update 方法即可,不限制实体是否处于持久状态,如果失败将抛出异常。


session.update(user);


4. 查询


关于查询,Session 支持根据单个 ID 查询或根据多个 ID 查询,示例如下。

User user = session.get(User.class, 24L);
User user1 = session.load(User.class, 25L);
User user2 = session.byId(User.class).load(26L);
List<User> users = session.byMultipleIds(User.class).multiLoad(Arrays.asList(27L, 28L));

根据 Hibernate 官网的介绍,Session.load 方法可以通过创建代理的方式延迟加载数据,实际测试未发现有此效果。


通过上面的示例可以发现,Session API 只能对单条记录进行简单的操作,因此不适用于复杂的场景。


Criteria API


Hibernate 提供了重载的 Session.createCriteria 方法用于生成 Criteria 接口实例,用户可以使用 Criteria 提供的方法执行数据库查询,这种方式支持单表也支持多表联查。


为了兼容 JPA,到了 Hibernate 5.2 版本,createCriteria 方法被废弃,转而使用 JPA 的方法替代,到了 6.0 版本,这个方法已经被删除。不过由于 Oracle 将 Java EE 移交给 Eclipse 基金会后要求对方不能再使用 javax 包名,Hibernate 6.0 实现的最新的 JPA 包名已经发生变化,为了减少影响很多项目仍然停留在 Hibernate 5.x 版本。


这里我们先看下 Hibernate Criteria 的使用方式,下篇介绍 JPA 时再着重介绍 JPA 最新的方法。一个相对复杂的分页查询示例如下。


List<User> list = session.createCriteria(User.class)
        .add(Restrictions.eq("username", "hkp"))
        .addOrder(Order.desc("id"))
        .setFirstResult(10)
        .setMaxResults(10)
        .list();


等价于如下的 SQL:

select id,username,password 
from user 
where username = 'hkp' 
order by id desc 
limit 10,10

HQL


HQL 全称 Hibernate Query Language,是一个以对象为中心的查询语言,语法类似于 SQL,与上述 Criteria 示例等同的代码如下。


List<User> list = session.createQuery(
                "select u from User u where u.username = :username order by u.id desc")
        .setParameter("username", "hkp")
        .setFirstResult(10).setMaxResults(10)
        .list();


SQL


如果 CriteriaHQL 不满足要求,还可以使用 SQL,上述 HQL 等同的 SQL 代码如下。

List<User> list = session.createNativeQuery(
                "select * from user as u where u.username = :username order by u.id desc")
        .setParameter("username", "hkp")
        .setFirstResult(0)
        .setMaxResults(10).list();


拦截器与事件


拦截器在 Session 执行某些方法前被回调,一个比较常用的场景设置创建人、创建时间、修改人、修改时间,另外还可以使用拦截器进行加解密。拦截器示例如下。


public class CustomInterceptor extends EmptyInterceptor {
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        // insert,加密、设置创建人、创建时间
        this.setCreateInfo(entity, state, propertyNames);
        return true;
    }
    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        // update,加密、设置修改人、修改时间
        this.setUpdateInfo(entity, currentState, propertyNames);
        return true;
    }
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        // select,解密
        return super.onLoad(entity, id, state, propertyNames, types);
    }
    ...省略 setCreateInfo、setUpdateInfo 方法代码
}

定义之后进行简单配置就可以了。

configuration.setInterceptor(new CustomInterceptor());

除了拦截器,Hibernate 还提供了事件机制,作为拦截器的补充,不同的 Session 接口方法执行时会发出不同的事件,使用事件机制替代上述拦截器的功能可以使用如下的代码。

EventListenerRegistry eventListenerRegistry = this.sessionFactory.unwrap(SessionFactoryImplementor.class)
        .getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.POST_LOAD, new CustomPostLoadEventListener());
eventListenerRegistry.prependListeners(EventType.SAVE, new CustomSaveEventListener());
eventListenerRegistry.prependListeners(EventType.UPDATE, new CustomUpdateEventListener());


映射关系


映射关系可以说是 Hibernate 最复杂的部分了,Hibernate 不仅支持简单类型到数据库列的映射,还支持复杂的实体关系以及面向对象中的继承关系到数据库的映射。


映射类型


Hibernate 中的映射关系主要从面向对象的角度进行划分,Hibernate 中的类型分为值类型和实体类型。


image.png


向前面示例中的 User 类就是一个实体类型,有自己的标识符,可以独立于其他对象存在。


值类型则必须依托于实体类型,其生命周期和实体类型保持一致。值类型又可以进一步划分为基本类型、嵌入类型、集合类型,下面分别介绍。


命名策略


在介绍映射关系之前首先要提到的是命名策略,在使用 MyBatis 的时候,为了便于将数据库表字段转换为实体属性,我们常常会进行下划线转驼峰的配置,Hibernate 也有类似的策略。


Hibernate 的命名策略主要用于在映射关系中未显式指定实体类型、字段对应的数据库表、列名时自动进行的转换策略。


在 Hibernate 5 之前有一个 NamingStrategy 接口用于定义命名策略,不过在 Hibernate 5 版本的时候这个接口被废弃,转而替换为了隐式命名策略 ImplicitNamingStrategy 和物理命名策略 PhysicalNamingStrategy。Hibernate 首先使用隐式命名策略获取逻辑名称,然后再使用物理命名策略将逻辑名称转换为数据库中真正存在的物理名称。


Spring Boot 就提供了隐式命名策略和物理命名策略的实现,显式配置表名、列名的情况下用户无需关心命名策略。如果需要修改有两种方式。


指定配置属性 hibernate.implicit_naming_strategy 或 hibernate.physical_naming_strategy。

调用方法 Configuration.setImplicitNamingStrategy 或 Configuration.setPhysicalNamingStrategy。


基本类型映射


基本类型用于单个数据库列与实体属性之间的映射。Hibernate 内置了多种基本类型,支持了包括 Java 基本类型、日期类型在内的类型。


对于内置支持的类型,在 xml 映射文件中通过 property 标签配置实体属性名称与数据库列名之间的映射关系即可,Hibernate 知道如何进行类型处理。


<property name="username" column="username"/>


不过如果想要将自定义的 Java 类型与数据库列之前进行转换,就需要手动向 Hibernate 注册类型了。假定 user 表有一个 varchar 类型的 interests 列存储用户的兴趣爱好,我们想把它转换为 Java 中的 List 类型,可以如下操作。


首先自定义一个 Hibernate 类型,此时需要继承 BasicType 接口。


public class StringListType extends AbstractSingleColumnStandardBasicType<List> implements DiscriminatorType<List> {
    public static final StringListType INSTANCE = new StringListType();
    public StringListType() {
        super(VarcharTypeDescriptor.INSTANCE, StringListTypeDescriptor.INSTANCE);
    }
    @Override
    public List stringToObject(String xml) throws Exception {
        return fromString(xml);
    }
    @Override
    public String objectToSQLString(List value, Dialect dialect) throws Exception {
        return toString(value);
    }
    @Override
    public String getName() {
        return "StringList";
    }
    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }
}


然后向 Hibernate 注册。


configuration.registerTypeContributor((typeContributions, serviceRegistry) -> typeContributions.contributeType(StringListType.INSTANCE));


最后在映射文件中指定即可。

<property name="interests" column="interests" type="com.zzuhkp.hibernate.type.StringListType"/>


嵌入类型映射

嵌入类型可以为普通的 Java 类型,也可以是 Java 集合类型、Map。普通的 Java 类型也被称为组件,集合类型则用于表示一对多、多对一、多对多的关系。


由于嵌入集合类型较为复杂,这里看下组件类型,组件常用于对实体的属性进行分组,假如我们想使用如下的形式表示用户。


@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private UserExt ext;
}
@Data
public class UserExt {
    private String name;
    private Integer age;
}


这里的 User 类型中的 UserExt 就是一个组件,我们可以使用如下的映射文件表示。


集合类型映射


这里的集合类型与嵌入类型中的集合类型很相似,从面向对象的角度来看,只是这里的集合类型中的元素是 Hibernate 管理的实体,而嵌入类型中的集合类型只是作为实体中的属性出现,具体到数据库表,其实没什么两样。


集合类型映射可用于表示一对多、多对一、多对多的关系,在 Hibernate 中,还可以用多对一表示一对一的关系。


集合类型也是 Hibernate 中比较复杂的部分,限于篇幅这里仅给出一对多的示例。


假定用户可以有多个收货地址,收货地址数据库表如下。


create table address
(
    id            bigint unsigned auto_increment comment '主键' primary key,
    user_id       bigint unsigned null comment '用户ID',
    province_name varchar(20)     null comment '省份名称',
    city_name     varchar(20)     null comment '城市名称',
    area          varchar(20)     null comment '区域',
    detail        varchar(100)    null comment '详细地址'
);

对应实体如下。

@Data
public class Address{
  private Long id;
    private String provinceName;
    private String cityName;
    private String area;
    private String detail;
}

User 类型中可以引入地址列表。

@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private Set<Address> addresses;
}


映射关系可以如下表示。

<class name="com.zzuhkp.hibernate.entity.User" table="user">
    <id name="id" column="id">
        <generator class="native"/>
    </id>
    <property name="username" column="username"/>
    <property name="password" column="password"/>
    <set name="addresses" inverse="false" lazy="true">
        <key column="user_id"/>
        <one-to-many class="com.zzuhkp.hibernate.entity.Address"/>
    </set>
</class>

像操作基本类型一样操作集合类型即可。


总结

从上面的内容可以看出,仅仅是最基础的 Hibernate 内容也涉及到了大量的概念,个人看来 Hibernate 的缺点包括但不限于上手难度极高、设计不够灵活、不适用微服务盛行的今天,而 MyBatis 就相对轻量化,使用也比较简单,这也是 MyBatis 能取代 Hibernate 的原因。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
21天前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
12 1
|
3月前
|
SQL Java 数据库连接
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。通过映射机制,它可以自动处理对象与数据库表之间的转换,支持主流数据库,提高了代码的可移植性和可维护性。其核心接口包括 SessionFactory、Session 和 Transaction 等,通过它们可以执行数据库的 CRUD 操作。配置方面,需在项目中引入 Hibernate 及数据库驱动依赖,并创建 `hibernate.cfg.xml` 配置文件来设置数据库连接和 Hibernate 行为参数。
47 1
|
3月前
|
Java 数据库连接 数据库
告别繁琐 SQL!Hibernate 入门指南带你轻松玩转 ORM,解锁高效数据库操作新姿势
【8月更文挑战第31天】Hibernate 是一款流行的 Java 持久层框架,简化了对象关系映射(ORM)过程,使开发者能以面向对象的方式进行数据持久化操作而无需直接编写 SQL 语句。本文提供 Hibernate 入门指南,介绍核心概念及示例代码,涵盖依赖引入、配置文件设置、实体类定义、工具类构建及基本 CRUD 操作。通过学习,你将掌握使用 Hibernate 简化数据持久化的技巧,为实际项目应用打下基础。
198 0
|
3月前
|
数据库 Java 数据库连接
Struts 2 与 Hibernate 的完美邂逅:如何无缝集成两大框架,轻松玩转高效 CRUD 操作?
【8月更文挑战第31天】本文通过具体示例介绍了如何在 Struts 2 中整合 Hibernate,实现基本的 CRUD 操作。首先创建 Maven 项目并添加相关依赖,接着配置 Hibernate 并定义实体类及其映射文件。然后创建 DAO 接口及实现类处理数据库操作,再通过 Struts 2 的 Action 类处理用户请求。最后配置 `struts.xml` 文件并创建 JSP 页面展示用户列表及编辑表单。此示例展示了如何配置和使用这两个框架,使代码更加模块化和可维护。
88 0
|
4月前
|
SQL Java 数据库连接
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
77 0
|
5月前
|
Java 数据库连接 数据库
探索JPA生态:Hibernate与其他ORM框架的对比分析
【6月更文挑战第25天】**JPA标准下的Hibernate是流行的ORM实现,提供丰富功能如二级缓存和延迟加载,但其学习曲线较陡,性能优化复杂。相比如MyBatis,Hibernate的JPQL更面向对象,MyBatis则接近SQL。选择ORM需考虑项目需求和个人偏好。**
86 0
|
6月前
|
SQL 缓存 Java
框架分析(9)-Hibernate
框架分析(9)-Hibernate
|
5月前
|
Java 数据库连接
杨老师课堂之JavaEE三大框架Hibernate入门第一课
杨老师课堂之JavaEE三大框架Hibernate入门第一课
30 0
|
6月前
|
SQL Java 数据库连接
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
|
6月前
|
SQL Java 关系型数据库
数据库访问:什么是Hibernate框架?
【4月更文挑战第15天】Hibernate是开源ORM框架,将Java对象与数据库表映射,简化对象-关系映射,提升开发效率和性能。它自动化处理数据操作,支持多种数据库,自动生成SQL,提供配置选项和高级特性,减少手动SQL编写,便于切换数据库。
72 2