作者:禅与计算机程序设计艺术
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代码。
@Entity
: 此注解声明一个类为Hibernate实体类。@Id
: 此注解声明一个属性为主键。@GeneratedValue
: 此注解用于标识主键生成策略。@Column
: 此注解用于声明列的属性。@Transient
: 此注解用于标识属性不是表的一部分。@ManyToOne
: 此注解声明属性是一对多关系。@OneToMany
: 此注解声明属性是多对一关系。@OneToOne
: 此注解声明属性是一对一关系。@JoinColumn
: 此注解声明外键。@Version
: 此注解用于声明记录的版本。@NamedQueries
: 此注解声明命名的查询。@NamedNativeQueries
: 此注解声明命名的原生SQL查询。@ManyToMany
: 此注解声明属性是多对多关系。@JoinTable
: 此注解声明中间表。@OrderBy
: 此注解用于指定查询结果的排序顺序。@FetchMode
: 此注解用于指定关联对象加载策略。@Filter
: 此注解用于指定过滤条件。@NaturalId
: 此注解用于指定唯一标识符。
Hibernate性能调优
Hibernate的性能调优涉及到许多方面,下面以几个典型的性能调优场景为例,阐述如何进行Hibernate性能调优:
- 大批量加载:Hibernate可以通过批量加载方式提升查询效率,通过
fetchSize
设置批量加载的大小,默认为10。例如:
List<Customer> customers = session.createQuery("FROM Customer ORDER BY name DESC").setMaxResults(1000).list();
- 批处理INSERT:Hibernate可以通过批处理INSERT的方式提升插入效率。通过设置
batch_size
属性可以启用批处理INSERT。例如:
<property name="jdbc.batch_size">10</property>
- 延迟加载:Hibernate可以通过延迟加载的方式提升查询效率,通过
lazy=true
设置关联对象延迟加载。例如:
List<Order> orders = customer.getOrders();
- 缓存配置: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>
- 日志配置: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的这些特性可以通过相应的注解或配置项来开启或关闭。