Java必知必会系列:ORM框架与Hibernate

发布于:2023-10-25 ⋅ 阅读:(81) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.背景介绍

在互联网开发过程中,我们经常需要处理大量的数据,比如用户信息、订单数据等。如果需要对这些数据进行各种查询、修改、排序、统计等操作,那么数据库系统就成为性能瓶颈。为了解决这个问题,出现了各种关系型数据库管理系统(RDBMS),如MySQL、Oracle等,它们支持SQL语言,可以方便地存储和管理海量的数据;同时还提供了丰富的功能,如备份、恢复、复制等,用来保障数据的安全性和可用性。然而,当应用的需求越来越复杂,数据库的增长速度也越来越快时,管理大量数据变得越来越困难。 随着互联网快速发展,网站日益复杂化,用户对于应用数据的访问也越来越频繁。为了满足用户的需求,需要将数据从中心化的数据库中分离出来,让不同应用之间的数据交流更加灵活,各自独立的应用服务器可以自主选择合适的数据库系统,做到数据访问的高效率。于是,基于分布式的关系型数据库系统、NoSQL数据库、搜索引擎等出现了,其中的一种就是ORM框架。ORM框架使得数据库与应用之间的耦合度降低,让开发者只关注应用本身的业务逻辑,不需要再关心数据库的细节。常见的ORM框架有Hibernate、MyBatis等。

Hibernate是一个开源ORM框架,提供简洁易用的API,隐藏底层数据库操作细节,并通过映射元数据的方式实现对象与关系数据库的自动同步。它可以用于Java企业级应用,包括EJB3、JPA、Struts等。Hibernate有很多优点,如面向对象的编程风格,ORM设计模式,提供缓存机制,使得查询更快捷,缺省值管理,多线程环境下数据库连接管理等。

本文将以Hibernate作为ORM框架进行介绍。主要内容如下:

1)Hibernate概述

2)Hibernate的特点

3)Hibernate配置

4)Hibernate实体类定义

5)Hibernate查询

6)Hibernate事务管理

7)Hibernate缓存

8)Hibernate常用注解

9)Hibernate性能调优

10)Hibernate集成Spring

11)Hibernate其他

Hibernate概述

Hibernate(和英语“嗅觉”一样,字面意思为预知、察觉或领悟)是一个开放源代码的对象关系映射框架,它对JDBC(Java Database Connectivity)接口进行了非常薄的一层封装,简化了JDBC编程,并加入了许多便利的特性,使程序员不必直接操作JDBC API,从而大大减少了代码量,缩短了开发时间。

Hibernate的目标是简化JPA(Java Persistence API)的使用,使用Hibernate可以获得以下好处:

1.简单性:Hibernate通过提供一个高度整洁的对象关系模型,把一些复杂而繁琐的过程都封装起来了,使得开发人员能够专注于业务逻辑的实现,而不是纠结于繁琐的数据库操作上。

2.规范性:Hibernate遵循标准ORM规范,它的对象/关系映射结果始终与标准ORM框架一致。这样,Hibernate可以很好的集成到各种主流ORM框架之中,并保证兼容性。

3.可移植性:Hibernate可以在各种主流平台上运行,比如J2EE应用服务器、OSGi容器或者web容器中。它也可以运行于Android平台,甚至运行于Java SE平台上。

4.有效性:Hibernate使用“惰性加载”,它不会把所有数据从数据库加载到内存中,而是在真正需要的时候才把所需的数据从数据库加载进内存。这对于提高应用程序的响应能力非常重要。

5.无重复代码:Hibernate提供了一个强大的查询优化器,它可以根据创建查询时的规则和场景来生成最优的SQL语句,从而避免了冗余的代码,实现了“代码重用”。

6.灵活性:Hibernate支持动态关系映射,并且允许多个实体共用同一个表。这种灵活性使得Hibernate非常适合开发复杂的多表关联查询。

Hibernate的组成结构如下图所示:

Hibernate分为三个部分:

1.Hibernate Core:Hibernate Core包含了Hibernate运行的基本功能。

2.Hibernate ORM:Hibernate ORM包括了对各种持久化标准的支持,如JPA、JAXB、Hibernate Search等。

3.Hibernate Annotations:Hibernate Annotations是Hibernate的注释处理工具,它扩展了标准Java annotations,使得Hibernate能够更加简洁地进行配置。

Hibernate除了提供ORM外,还提供了对某些缓存策略、事务管理等功能的支持。由于Hibernate采用了“惰性加载”的策略,因此,当需要获取某个实体对象时,Hibernate并不会立即从数据库加载该对象所有属性的值,而是按需加载。而且,Hibernate内置了多种缓存策略,以提高应用程序的性能。

Hibernate的特点

1.查询优化器:Hibernate提供了查询优化器,它可以根据创建查询时的规则和场景来生成最优的SQL语句,从而避免了冗余的代码,实现了“代码重用”。

2.对象/关系映射:Hibernate通过建立实体-关系模型把数据库的表和实体对象相互对应,实体对象又通过Hibernate提供的功能,与数据库表相互交互。

3.配置:Hibernate通过XML和Annotation两种形式的配置文件进行配置,并通过读取配置信息来初始化Hibernate框架,简化了程序员的编码工作。

4.自动生成主键:Hibernate可以自动生成主键,用户无需指定主键字段。

5.事务管理:Hibernate提供了完整的事务管理机制,允许用户自由地定义事务边界,简化了事务处理。

6.缓存:Hibernate提供了完整的缓存机制,允许用户设置不同的缓存策略,减少数据库I/O。

7.查询语言:Hibernate提供了丰富的查询语言,支持完整的SQL语法,并可以调用存储过程。

8.动态查询:Hibernate提供了动态查询功能,允许用户编写任意条件查询,并且支持参数绑定。

9.统一的异常体系:Hibernate提供了一致的异常体系,可以方便地进行错误处理。

10.SQL函数支持:Hibernate提供了丰富的SQL函数支持,包括字符串函数、日期函数、聚集函数等。

11.数据类型转换:Hibernate提供了完善的数据类型转换机制,包括内建类型转换和外部类型转换。

12.集成Spring:Hibernate已经可以集成到Spring IOC和AOP框架之中,使得Hibernate具备了服务定位和依赖注入的能力。

13.关系映射和JPA兼容:Hibernate与JPA是完全兼容的。Hibernate可以使用JPA的一些注解,如@Entity、@Id、@GeneratedValue等。

14.连接池支持:Hibernate可以通过连接池来管理数据库连接,减少资源消耗。

15.结构化查询:Hibernate还提供了结构化查询功能,可以执行诸如分页查询等高级功能。

16.分布式事务:Hibernate提供了分布式事务管理功能,可以在不同的Hibernate数据源之间传播事务,实现跨库事务。

Hibernate配置

Hibernate的配置可以分为三步:

1.准备数据库:首先,准备好数据库,创建对应的表,并插入一些测试数据。

2.配置Hibernate工程:然后,创建一个Hibernate工程,引入Hibernate相关的jar文件。这里需要注意的是,Hibernate版本号应该与数据库的驱动版本匹配,否则可能导致连接失败。

3.编写配置文件:最后,编写hibernate.cfg.xml配置文件,配置数据库链接,设置SessionFactory的属性等。下面是一个简单的示例:

<?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>

        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/yourdatabase</property>
        <property name="connection.username">root</property>
        <property name="connection.password"></property>

        <!-- JDBC batch size (optional, default is unlimited) -->
        <property name="jdbc.batch_size">10</property>

        <!-- Disable lazy loading of entities (optional, default is false) -->
        <property name="hibernate.lazy">false</property>

        <!-- Show SQL output (optional, default is false) -->
        <property name="show_sql">true</property>

        <!-- Use second level cache (optional, default is true) -->
        <property name="cache.use_second_level_cache">true</property>
        <mapping resource="yourpackage.hbm.User"/>
    </session-factory>

</hibernate-configuration>

Hibernate实体类定义

Hibernate利用反射机制,根据配置文件中相应的映射信息,生成实体类的实例。这些实体类可以直接对数据库进行读写操作。下面是一个简单的实体类定义例子:

import javax.persistence.*;

@Entity // 此注解声明此类为一个实体类
public class User {

    @Id // 此注解声明标识为主键
    private int id;

    private String username;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}

这里的@Entity@Id@GeneratedValue@Column都是Hibernate提供的注解,可以用来定义实体类的属性及其约束条件。

实体类定义完成后,需要注册到SessionFactory中。例如:

Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();

Transaction tx = session.beginTransaction();

try {
    User user = new User();
    user.setUsername("Alice");
    session.save(user);

    // other operations...

    tx.commit();
} catch (Exception e) {
    if (tx!= null) tx.rollback();
    throw e;
} finally {
    session.close();
}

factory.close();

这里展示了如何创建、保存一个User对象,并提交事务。另外,Hibernate的其他操作,比如查询,更新,删除等,也可以通过SessionFactory或Session对象来实现。

Hibernate查询

Hibernate提供了丰富的查询语言,支持完整的SQL语法,并可以调用存储过程。

HQL查询

HQL(Hibernate Query Language)是Hibernate的查询语言,可以用类似SQL的语句来进行查询。

List<User> users = session.createQuery("from User").list();
for (User u : users) {
    System.out.println(u.getUsername());
}

这里展示了如何执行一条HQL查询,输出所有用户名。

除了使用单表查询之外,还可以使用联表查询、子查询等方式组合查询。例如:

List<Object[]> resultList = session.createQuery("SELECT u FROM User u WHERE u.age >?")
                                   .setParameter(0, 18).list();

这里展示了如何执行一个复杂的HQL查询,输出符合条件的年龄大于18的所有用户及其信息。

Criteria查询

Criteria查询提供了一种面向对象的方式,可以用Java代码构建复杂的查询。

Session session = factory.getCurrentSession();
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("gender", 'M'));
List<User> maleUsers = criteria.list();

这里展示了如何执行一个Criteria查询,输出所有男性用户的信息。

Criteria查询也支持复杂的查询构造,包括组合条件、分组、排序、限制结果数量等。

Hibernate事务管理

Hibernate通过Transaction接口管理事务,通过Transaction接口可以回滚,提交事务,关闭当前事务等。

Session session = factory.getCurrentSession();
Transaction tx = session.beginTransaction();
try {
    //... do something here
    tx.commit();
} catch (Exception e) {
    if (tx!= null) tx.rollback();
    throw e;
}

上面展示了如何开启一个事务,对事务进行提交或者回滚,另外,Hibernate还提供了较为方便的注解方式来管理事务,例如:

@Transactional
void saveData(List<UserData> dataList) {
    for (UserData ud : dataList) {
        //... save data to database
        // or raise an exception
        try {
            Thread.sleep(new Random().nextInt(10));
        } catch (InterruptedException ex) {}
    }
}

这里展示了如何在方法上使用注解来声明事务,并自动回滚异常。

Hibernate缓存

Hibernate提供了缓存机制,可以减少数据库I/O。

Hibernate提供了三种类型的缓存:一级缓存,二级缓存,查询缓存。其中,一级缓存是Hibernate默认使用的缓存,一般情况下,只要实体对象不被修改,Hibernate都会从一级缓存中取出对象;二级缓存需要自己手动配置;查询缓存可以减少数据库查询次数,但不能代替数据库缓存。

一级缓存

Hibernate一级缓存是默认使用的缓存,它的生命周期跟实体对象相同。Hibernate从缓存中读取对象速度远远快于从数据库中读取对象。如果实体对象被修改,Hibernate会自动更新缓存。

下面是一个例子:

// 获取Session
Session session = factory.getCurrentSession();

// 查询数据库,得到用户对象
User user1 = session.get(User.class, 1);

// 使用相同的id查询一次
User user2 = session.get(User.class, 1);

// 对象比较
System.out.println(user1 == user2);    // true

// 更新对象
user1.setUsername("Bob");

// 使用相同的id查询一次
User user3 = session.get(User.class, 1);

// 对象比较
System.out.println(user2 == user3);    // false

这里展示了Hibernate的一级缓存的使用方法,第一次查询返回的是实体对象,第二次查询则返回的是缓存中的对象,即使对象被修改,仍然会从缓存中取出对象。

二级缓存

Hibernate二级缓存是Hibernate扩展的缓存,需要自己手动配置。

配置方法如下:

<!-- Enable use of second level cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>

<!-- Configure the cache provider implementation -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.ehcache.EhCacheProvider</property>

<!-- Declare the various caches -->
<mapping>
  <class name="example.model.Employee" table="employee">
    <cache usage="read-write" />
  </class>
  <collection name="example.model.Department.employees" table="department_employees"
             cascade="all" inverse="false" fetch="join">
    <cache usage="nonstrict-read-write" />
  </collection>
</mapping>

这里展示了如何启用Hibernate二级缓存,并声明一些缓存。缓存声明需要放在实体类和集合类上,并通过cache标签来设置缓存策略。Hibernate提供了两种缓存策略,read-write表示对缓存对象的写入和读取,nonstrict-read-write表示对缓存对象的读取和写入,但对相同对象其他属性的写入不会更新缓存。

下面是一个例子:

Session session = factory.getCurrentSession();

// Fetch employee by primary key from first level cache
Employee emp1 = session.get(Employee.class, 1L);

// Second level cache should be enabled and hit on subsequent access
Employee emp2 = session.get(Employee.class, 1L);

// Verify both objects are same instance
System.out.println(emp1 == emp2);    // true

这里展示了Hibernate的二级缓存的使用方法,第一次查询返回的是实体对象,第二次查询则返回的是缓存中的对象,即使对象被修改,Hibernate依旧能从缓存中读取对象。

查询缓存

Hibernate查询缓存是Hibernate扩展的缓存,只能减少数据库查询次数,并不能代替数据库缓存。

配置方法如下:

<!-- Enable query caching -->
<property name="hibernate.cache.use_query_cache">true</property>

<!-- Expiration time of query cache in seconds-->
<property name="hibernate.cache.default_expiration">300</property>

<!-- Maximum number of queries to be cached per region (defaults to 100) -->
<property name="hibernate.cache.queries.enabled">true</property>
<property name="hibernate.cache.queries.max_entries">1000</property>

<!-- Whether to update timestamps when a cached query is used (defaults to false) -->
<property name="hibernate.cache.update_timestamp">true</property>

这里展示了如何启用Hibernate查询缓存,并设定过期时间和最大缓存条目数量。

Hibernate查询缓存需要在查询前声明,且不会影响其它缓存策略,所以通常建议在所有查询前添加缓存。

下面是一个例子:

Session session = factory.getCurrentSession();

Query query1 = session.createQuery("FROM Employee WHERE department=:deptName");
query1.setString("deptName", "Sales");

List<Employee> salesEmployees1 = query1.list();

Query query2 = session.createQuery("FROM Employee WHERE department=:deptName");
query2.setString("deptName", "Sales");

List<Employee> salesEmployees2 = query2.list();

// Objects returned should be the same instances since they were retrieved from cache
System.out.println(salesEmployees1 == salesEmployees2);   // true

// Wait for expiry before executing next query
Thread.sleep(4000);

Query query3 = session.createQuery("FROM Employee WHERE department=:deptName");
query3.setString("deptName", "Sales");

List<Employee> salesEmployees3 = query3.list();

// This will return new list as previous one was expired
System.out.println(salesEmployees1 == salesEmployees3);   // false

这里展示了Hibernate查询缓存的使用方法,第一次查询返回的是部门为“Sales”的员工列表,第二次查询返回的是之前缓存的列表,因为缓存有效期只有3秒钟,所以等待了一段时间之后再次查询。第三次查询则重新执行查询,返回的是新的列表。

Hibernate常用注解

Hibernate提供了许多注解,可以简化ORM代码。

  1. @Entity: 此注解声明一个类为Hibernate实体类。
  2. @Id: 此注解声明一个属性为主键。
  3. @GeneratedValue: 此注解用于标识主键生成策略。
  4. @Column: 此注解用于声明列的属性。
  5. @Transient: 此注解用于标识属性不是表的一部分。
  6. @ManyToOne: 此注解声明属性是一对多关系。
  7. @OneToMany: 此注解声明属性是多对一关系。
  8. @OneToOne: 此注解声明属性是一对一关系。
  9. @JoinColumn: 此注解声明外键。
  10. @Version: 此注解用于声明记录的版本。
  11. @NamedQueries: 此注解声明命名的查询。
  12. @NamedNativeQueries: 此注解声明命名的原生SQL查询。
  13. @ManyToMany: 此注解声明属性是多对多关系。
  14. @JoinTable: 此注解声明中间表。
  15. @OrderBy: 此注解用于指定查询结果的排序顺序。
  16. @FetchMode: 此注解用于指定关联对象加载策略。
  17. @Filter: 此注解用于指定过滤条件。
  18. @NaturalId: 此注解用于指定唯一标识符。

Hibernate性能调优

Hibernate的性能调优涉及到许多方面,下面以几个典型的性能调优场景为例,阐述如何进行Hibernate性能调优:

  1. 大批量加载:Hibernate可以通过批量加载方式提升查询效率,通过fetchSize设置批量加载的大小,默认为10。例如:
List<Customer> customers = session.createQuery("FROM Customer ORDER BY name DESC").setMaxResults(1000).list();
  1. 批处理INSERT:Hibernate可以通过批处理INSERT的方式提升插入效率。通过设置batch_size属性可以启用批处理INSERT。例如:
<property name="jdbc.batch_size">10</property>
  1. 延迟加载:Hibernate可以通过延迟加载的方式提升查询效率,通过lazy=true设置关联对象延迟加载。例如:
List<Order> orders = customer.getOrders();
  1. 缓存配置:Hibernate可以配置一级缓存和二级缓存,以减少数据库I/O。可以配置刷新频率、缓存空间大小等。例如:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.ehcache.EhCacheProvider</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
<property name="hibernate.cache.use_minimal_puts">true</property>
<property name="hibernate.cache.regions.strategy">ehcache-custom</property>
  1. 日志配置:Hibernate提供了日志配置选项,可以方便地监控Hibernate运行情况。例如:
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.type" level="TRACE" />
<logger name="org.hibernate.orm" level="INFO" />

Hibernate集成Spring

Hibernate可以方便地集成Spring框架,Spring提供了IoC、AOP等机制,可以更容易地管理Hibernate的生命周期。

Spring Boot可以轻松集成Hibernate,只需要在pom.xml文件中添加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

然后,在application.properties文件中添加如下配置:

spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
spring.datasource.username=root
spring.datasource.password=<PASSWORD>
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.database=MYSQL
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming-strategy=org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy

这里,我们在application.properties文件中,配置了数据源的URL、用户名、密码、驱动类名,并设置Hibernate的数据库类型、DDL脚本生成策略、SQL显示策略等。

Spring Boot自动配置好Hibernate后,就可以按照正常的Hibernate开发流程,编写Repository接口和ServiceImpl类,并注入EntityManager或JpaTemplate即可。

Hibernate其他

Hibernate还有一些其他的特性,如事件监听器、统计信息、自动更新、回调函数等。

Hibernate的这些特性可以通过相应的注解或配置项来开启或关闭。