JPA 和 Hibernate(Springboot中使用HQL)

前言:

在项目开发过程中,使用Springboot集成HQL,在此用于记录,下方有原生方法及HQL注意事项和示例,有兴趣的可往下阅读。


一、基本概念

  • JPA:全称是 Java Persistence API,即 Java 持久化 API,是 SUN 公司推出的一套基于 ORM 的规范,由一系列接口和抽象类构成。它是 EJB3 规范中负责对象持久化的应用程序编程接口(ORM 接口),定义了多种注释,这些注释可分为类级别、方法级别和字段级别注释。通过为实体类添加适当的注释,在程序运行时可以告诉实现框架(如 Hibernate)如何将一个实体类保存到数据库,以及如何将数据以对象的形式从数据库中读取出来。
  • Hibernate:是一个开源的全自动 ORM 对象关系映射框架,对 JDBC 进行了非常轻量级的对象封装。

二、JPA 和 Hibernate 的关系

  • 规范与实现:
    • JPA 是一套规范,而不是具体的 ORM 框架,像 Hibernate、EclipseLink 等 ORM 框架都是 JPA 的实现。
    • JPA 的标准是由 Hibernate 的作者参与定制的,因此可以认为 JPA 是对 ORM 功能和实现的一种抽象和规范。JPA 就像 JDBC 规范,而 Hibernate 就像 JDBC 驱动,Hibernate 除了作为 ORM 框架之外,也是一种 JPA 实现。
    • JPA 是接口,Hibernate 是实现。Hibernate 主要通过三个组件来实现与 JPA 的关系,包括 hibernate-annotation、hibernate-entitymanager 和 hibernate-core。
      • hibernate-annotation:支持 annotation 方式配置,包含标准的 JPA 注解以及 Hibernate 自身特殊功能的注解。它扩展了 JPA 的注解功能,允许开发者使用 Hibernate 特有的特性,为开发者提供了更灵活的配置方式,使得在使用注解进行对象 - 关系映射时,不仅可以使用标准的 JPA 注解,还能利用 Hibernate 自身的强大功能,例如一些特殊的数据库映射策略或优化选项。
      • hibernate-core:Hibernate 的核心实现,提供了 Hibernate 的所有核心功能。这是 Hibernate 的基础,包含了对象 - 关系映射的核心逻辑,如对象状态的管理(从新建、持久化、游离到删除状态的转换和维护)、数据库操作的封装(包括基本的 CRUD 操作)、缓存管理、事务管理以及各种优化策略的实现。它处理了将 Java 对象映射到数据库表、执行 SQL 语句和管理对象状态的主要逻辑。
      • hibernate-entitymanager:实现了标准的 JPA,可看作是 hibernate-core 和 JPA 之间的适配器,对 hibernate-core 进行封装,使 Hibernate 符合 JPA 的规范。它作为一个桥梁,确保 Hibernate 的核心功能可以通过 JPA 的接口提供给开发者,使得开发者可以使用 JPA 的标准 API 操作 Hibernate 的强大功能,让代码更具移植性和兼容性,符合 JPA 标准的开发模式。

三、Hibernate-Entitymanager 包的主要类及实现

  • HibernatePersistence.java:
    • 实现了 JPA 的 PersistenceProvider 接口,提供 createEntityManagerFactory 和 createContainerEntityManagerFactory 两个方法来创建 EntityManagerFactory 对象。这两个方法底层都是调用 EJB3Configuration 对象的 buildEntityManagerFactory 方法,来解析 JPA 配置文件 persistence.xml ,并创建 EntityManagerFactory 对象。它是整个 JPA 实现的入口点,当使用 JPA 的标准配置方式时,Hibernate 通过该类解析配置信息,根据配置信息创建出符合 JPA 规范的 EntityManagerFactory,这个工厂对象将用于后续创建 EntityManager,为后续的数据库操作提供基础。
// 以下是简化的示例代码,实际的实现会更复杂,涉及更多的配置解析和资源初始化
public class HibernatePersistence implements PersistenceProvider {
    @Override
    public EntityManagerFactory createEntityManagerFactory(String emName, Map properties) {
        EJB3Configuration configuration = new EJB3Configuration();
        // 解析 persistence.xml 并配置相关信息
        // 配置可能包括数据源、实体类、映射信息等
        configuration.configure(emName, properties);
        return configuration.buildEntityManagerFactory();
    }

    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
        EJB3Configuration configuration = new EJB3Configuration();
        // 根据容器提供的信息进行配置
        configuration.configure(info, properties);
        return configuration.buildEntityManagerFactory();
    }
}

  • EntityManagerFactory 对象:
    • 实现是 EntityManagerFactoryImpl 类,这个类有一个最重要的 private 属性就是 Hibernate 的核心对象之一 SessionFactory 。这个类最重要的方法是 createEntityManager ,来返回 EntityMnagaer 对象,而 sessionFactory 属性也传入了该方法。它作为一个工厂,负责创建和管理 EntityManager 对象,并且内部持有 SessionFactory,它利用 SessionFactory 的功能为创建的 EntityManager 提供支持,保证了 EntityManager 在操作数据库时能够利用 Hibernate 的核心功能。
public class EntityManagerFactoryImpl implements EntityManagerFactory {
    private SessionFactory sessionFactory;

    @Override
    public EntityManager createEntityManager() {
        return new EntityManagerImpl(sessionFactory);
    }
}

  • EntityManager 对象:
    • 实现是 EntityManagerImpl 类,这个类继承自 AbstractEntityManagerImpl 类,在 AbstractEntityManager 类中有一个抽象方法 getSession 来获得 Hibernate 的 Session 对象 ,正是在这个 Session 对象的实际支持下,EntityManagerImpl 类实现了 JPA 的 EntityManager 接口的所有方法,并完成实际的 ORM 操作。EntityManager 是实际操作数据库的接口,通过继承 AbstractEntityManagerImpl 并使用 Hibernate 的 Session 对象,它将 JPA 的操作转换为具体的 Hibernate 操作,实现了诸如 persist、merge、remove 等操作的具体逻辑。
public class EntityManagerImpl extends AbstractEntityManagerImpl implements EntityManager {
    @Override
    public Session getSession() {
        // 获取 Hibernate 的 Session 对象的具体实现逻辑
        return sessionFactory.getCurrentSession();
    }

    // 实现 JPA 的 EntityManager 接口的各种方法,如 persist 方法
    @Override
    public void persist(Object entity) {
        Session session = getSession();
        session.save(entity);
    }
}

  • QueryImpl 类:
    • 利用 EntityManagerImpl 的支持实现了 JPA 的 Query 接口。QueryImpl 负责将 JPA 的查询操作转换为 Hibernate 的查询操作,通过解析 JPQL 或 Criteria 查询,使用 Hibernate 的 Session 来执行具体的查询逻辑,将结果转换为符合 JPA 要求的对象或结果集。
public class QueryImpl implements Query {
    private EntityManagerImpl entityManagerImpl;

    public QueryImpl(EntityManagerImpl entityManagerImpl) {
        this.entityManagerImpl = entityManagerImpl;
    }

    @Override
    public List getResultList() {
        Session session = entityManagerImpl.getSession();
        // 执行 Hibernate 的查询操作,将结果转换为符合 JPA 的结果集
        return session.createQuery(...).list();
    }
}

  • TransactionImpl:
    • 利用 EntityManagerImpl 的支持实现了 JPA 的 EntityTransaction 接口。它管理事务操作,当调用 JPA 的事务相关方法时,它通过 EntityManagerImpl 获取 Hibernate 的 Session 并使用其事务管理功能,确保事务的开始、提交和回滚操作符合 JPA 规范。
public class TransactionImpl implements EntityTransaction {
    private EntityManagerImpl entityManagerImpl;

    public TransactionImpl(EntityManagerImpl entityManagerImpl) {
        this.entityManagerImpl = entityManagerImpl;
    }

    @Override
    public void commit() {
        Session session = entityManagerImpl.getSession();
        session.getTransaction().commit();
    }
}

四、注解对比与使用

  • 共同的常用注解:
    • @Entity:将一个类声明为 JPA 实体,会映射到数据库表。
    • @Table:用于指定实体在数据库中映射的表的名称,可设置表名、模式、索引等。
    • @Column:当实体类属性名和数据库列名不一致时使用,指定实体属性与数据库表列的映射关系,包括列名、长度、精度、是否可为空等。
    • @Id:指定实体的主键属性。
    • @GeneratedValue:用于指定主键的生成策略,例如自增长、序列、UUID 等。
    • @OneToMany:指定一对多关系,一个实体关联到多个其他实体,可设置目标实体类型、关联属性、级联操作等。
    • @ManyToOne:指定多对一关系,多个实体关联到一个实体,可设置目标实体类型、关联属性、是否可选等。
    • @ManyToMany:指定多对多关系,多个实体相互关联,可设置目标实体类型、关联表信息、级联操作等。
    • @JoinColumn:指定关联关系中的外键列的属性,包括在关联表中用于连接两个实体的外键列名称和其他选项。
    • @Transient:标记一个属性不需要持久化到数据库,不会映射到表的列,适用于临时计算属性或非持久化属性。
    • @NamedQuery:定义一个命名查询,使用实体类和属性名称,方便清晰地管理和调用查询。
    • @NamedQueries:用于定义一组命名查询。
  • 不常用注解:
    • @PersistenceContext:用于注入持久化上下文,方便在需要时自动获取对应的实体管理器。
    • @PersistenceUnit:用于注入持久化单元,访问其信息和配置。
    • @Version:用于乐观锁定版本控制,确保并发操作时数据的一致性。

五、高级特性与状态管理

  • JPA 优势:
    • 标准化:是 JCP 组织发布的 Java EE 标准之一,符合 JPA 标准的框架遵循相同架构,提供相同的访问 API,便于企业应用在不同 JPA 框架下少量修改即可运行。
    • 容器级特性的支持:支持大数据集、事务、并发等容器级事务,使 JPA 超越简单持久化框架,在企业应用中发挥更大作用(可进一步举例说明,如在分布式事务场景下的应用)。
    • 简单方便:
      • 创建实体和创建 Java 类一样简单,仅需使用 javax.persistence.Entity 注释。
      • JPA 的框架和接口简单,无太多特殊规则和设计模式要求,基于非侵入式原则设计,易与其他框架或容器集成。
    • 查询能力:
      • JPA 的查询语言是面向对象的,以面向对象的自然语法构造查询语句,可视为 Hibernate HQL 的等价物。
      • 定义了独特的 JPQL(Java Persistence Query Language),是 EJB QL 的扩展,操作对象是实体,支持批量更新和修改、JOIN、GROUP BY、HAVING 等高级查询特性,甚至支持子查询。
    • 高级特性:支持面向对象的高级特性,如类之间的继承、多态和复杂关系,让开发者使用面向对象的模型设计企业应用,无需自行处理这些特性在关系数据库中的持久化问题。
  • 实体状态管理:
    • EntityManager 中的实体状态:
      • 新建状态:新创建还未拥有持久性主键。
      • 持久化状态:已经拥有持久性主键并和持久化建立了上下文关系。
      • 游离状态:拥有持久性主键,但没有和持久化建立上下文关系。
      • 删除状态:拥有持久性主键,并且和持久化建立了上下文关系,但已从数据库中删除。

六、功能对比与使用方法

EntityManager 和 JpaRepository 的区别:
  1. EntityManager:

    • 是 JPA 的核心接口,用于管理持久化单元和实现与数据库的交互,提供对数据库的基本操作,如保存、更新、删除和查询实体对象。

    • 可通过 EntityManagerFactory 创建,每个持久化单元通常对应一个 EntityManager 实例。在 Spring 中,可通过注入 EntityManager 并配合 @PersistenceContext 注解使用。

    • 常用方法:

      • persist(entity):将实体对象保存到数据库,执行 INSERT 操作。
      • merge(entity):将实体对象更新到数据库,执行 UPDATE 操作。
      • remove(entity):从数据库中删除实体对象,执行 DELETE 操作。
      • find(entityClass, primaryKey):根据主键查询实体对象。

persist(entity):将实体对象保存到数据库,执行 INSERT 操作。

@PersistenceContext
private EntityManager entityManager;

public void saveEntity(Entity entity) {
    entityManager.persist(entity);
}

merge(entity):将实体对象更新到数据库,执行 UPDATE 操作。

@PersistenceContext
private EntityManager entityManager;

public void updateEntity(Entity entity) {
    entityManager.merge(entity);
}

remove(entity):从数据库中删除实体对象,执行 DELETE 操作。

@PersistenceContext
private EntityManager entityManager;

public void deleteEntity(Entity entity) {
    entityManager.remove(entity);
}

find(entityClass, primaryKey):根据主键查询实体对象。

@PersistenceContext
private EntityManager entityManager;

public Entity findEntityById(Long id) {
    return entityManager.find(Entity.class, id);
}

  1. JpaRepository:

    • 是 Spring Data JPA 提供的接口,是 PagingAndSortingRepository 和 QueryByExampleExecutor 的子接口,继承了两者的方法并提供更多 JPA 相关方法。

    • 封装了通用的数据访问操作,提供更高级别的抽象,开发者无需编写具体的数据访问代码。是一个泛型接口,将实体类和主键类型作为泛型参数传递给它可实现对实体对象的 CRUD 操作。在 Spring 中,可通过继承 JpaRepository 接口自定义数据访问接口,Spring Data JPA 会自动生成实现。

    • 常用方法:

      • 继承已有的方法:
// 定义一个继承自 JpaRepository 的数据访问接口
public interface EntityRepository extends JpaRepository<Entity, Long> {
    // 可直接使用 JpaRepository 提供的方法,无需编写具体实现
}

自定义方法:

// 定义一个继承自 JpaRepository 的数据访问接口
public interface EntityRepository extends JpaRepository<Entity, Long> {
    // 根据属性查询实体对象列表
    List<Entity> findByProperty(String property);
    
    // 根据属性查询实体对象并分页
    Page<Entity> findByProperty(String property, Pageable pageable);
    
    // 根据属性查询实体对象并排序
    List<Entity> findByProperty(String property, Sort sort);
    
    // 自定义查询语句
    @Query("SELECT e FROM Entity e WHERE e.property = :property")
    List<Entity> customQuery(@Param("property") String property);
}

七、使用 EntityManager 写 HQL

注入 EntityManager

在 Service 或 Repository 类中使用 @PersistenceContext 注解注入 EntityManager。

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;

@Repository
public class MyRepository {
    @PersistenceContext
    private EntityManager entityManager;
}

编写 HQL 查询语句

HQL 是面向对象的查询语言,使用实体类名而非表名,支持使用别名简化查询。

String hql = "SELECT e FROM Entity e WHERE e.property = :property";

通过调用 createQuery () 方法创建查询对象。

TypedQuery<Entity> query = entityManager.createQuery(hql, Entity.class);

使用 setParameter () 方法设置参数的值。

query.setParameter("property", "someValue");

使用 getResultList () 获取结果列表。

List<Entity> entities = query.getResultList();

使用 getSingleResult () 获取单个结果,注意处理 NoResultException。

try {
    Entity entity = query.getSingleResult();
    // 处理查询结果
} catch (NoResultException e) {
    // 处理查询结果为空的情况
}

七、项目中的使用步骤

添加依赖:在 pom.xml 文件中添加 Spring Boot Starter Data JPA 依赖,它会自动引入 Hibernate 作为 JPA 的实现。

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

配置数据源:在 application.ymlapplication.properties 中配置数据源信息,例如:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springbootdata
    username: root
    password: mysql

配置 JPA 和 Hibernate 属性:在 application.ymlapplication.properties 中配置 JPA 和 Hibernate 的相关属性,例如:

spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
        format_sql: true
        use_sql_comments: true

创建实体类:通过注解方式定义表结构,例如:

import lombok.Data;
import javax.persistence.*;

@Data
@Entity   
@Table(name = "users")    
public class Account {
    @GeneratedValue(strategy = GenerationType.IDENTITY)   
    @Column(name = "id")    
    @Id     
    int id;
    @Column(name = "username")   
    String username;
    @Column(name = "password")   
    String password;
}

创建 Repository 接口:使用 Spring Data JPA 的 Repository 接口来简化数据访问层的开发。

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
    // 可以在此添加自定义的查询方法
}

使用 Repository 接口:在 Service 层中注入并使用 Repository 接口。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;

    public List<Account> findAllAccounts() {
        return accountRepository.findAll();
    }
}

八、HQL 细节

1. 不加 select 的查询:

HQL查询一般需要使用 select 来明确查询的字段。但是,如果你不加 select,HQL会默认查询所有字段,类似于SQL中的 SELECT * FROM。不过,通常不推荐使用这种写法,因为这样返回的结果可能会导致内存开销过大,尤其在查询的字段较多时。

示例:

String hql = "from Employee";  // 不加 select,默认查询所有字段
Query query = session.createQuery(hql);
List<Employee> employees = query.list();

注意: 这种查询会返回整个 Employee 实体类的列表。


2. 单独返回指定字段:

如果你只想查询一个或者多个特定的字段,可以通过在 select 后面指定字段。这样HQL会返回一个包含指定字段的结果集。返回的结果通常是一个Object数组或者是指定类型的对象。

示例:

String hql = "select e.name, e.salary from Employee e";  // 返回指定字段
Query query = session.createQuery(hql);
List<Object[]> result = query.list();  // 每个元素是一个 Object[],包含name和salary
for (Object[] row : result) {
    String name = (String) row[0];
    Double salary = (Double) row[1];
    System.out.println(name + ": " + salary);
}

这里返回的是 Employee 实体的 namesalary 字段,结果是一个 Object[] 数组。


3. 返回单个类型字段:

如果你只需要查询一个字段,返回的是单个值,可以直接返回该字段。你可以直接指定单一字段,这样返回的结果就是一个单一的值,而不是一个实体对象。

示例:

String hql = "select e.salary from Employee e";  // 返回单个字段
Query query = session.createQuery(hql);
List<Double> salaries = query.list();  // 返回 salary 字段的列表
for (Double salary : salaries) {
    System.out.println(salary);
}

这里返回的是 salary 字段的列表,类型为 Double


4. 使用聚合函数:

聚合函数是HQL中常用的功能,可以通过 count(), sum(), avg(), min(), max() 等函数来进行统计查询。

示例:

// 计算平均工资
String hql = "select avg(e.salary) from Employee e";  
Query query = session.createQuery(hql);
Double averageSalary = (Double) query.uniqueResult();  // 返回一个单一的结果
System.out.println("Average Salary: " + averageSalary);

示例 2: 使用 count() 统计员工人数:

// 统计员工数量
String hql = "select count(e) from Employee e";  
Query query = session.createQuery(hql);
Long employeeCount = (Long) query.uniqueResult();
System.out.println("Employee Count: " + employeeCount);

示例 3: 使用 sum() 计算所有员工的总工资:

// 计算总工资
String hql = "select sum(e.salary) from Employee e";  
Query query = session.createQuery(hql);
Double totalSalary = (Double) query.uniqueResult();
System.out.println("Total Salary: " + totalSalary);

5. 使用 group byhaving

HQL还支持 group byhaving 子句,可以用于分组查询。

示例:

// 按部门分组,计算每个部门的员工人数
String hql = "select e.department, count(e) from Employee e group by e.department";
Query query = session.createQuery(hql);
List<Object[]> result = query.list();
for (Object[] row : result) {
    String department = (String) row[0];
    Long count = (Long) row[1];
    System.out.println(department + ": " + count);
}

6.使用New Map:

HQL直接返回Map类型

示例:

String hql = "select NEW MAP(s.id as sid, s.name as sname) from section s"
Query query = session.createQuery(hql);
List<Map<String, Object>> result = query.list();
  • 直接返回 Map 可能会遇到以下几种问题:

    1. 内存问题:如果查询返回的数据非常大,JPA 会将所有数据加载到内存中,可能导致内存溢出。
    2. 性能问题:查询的数据量越大,所消耗的时间和资源也越多。
  • 使用 NEW MAP 返回 Map 类型的查询结果时,最好的做法是:

    1. 使用 List<Map<String, Object>> 封装每一条记录。
    2. 采用分页查询来处理大数据量,避免一次性加载所有数据。

总结:
  • 不加 select:默认查询所有字段。
  • 单独返回指定字段:通过 select 指定要查询的字段,可以返回一个包含多个字段的 Object[] 数组。
  • 返回单个字段:只查询一个字段并返回一个简单类型的列表。
  • 聚合函数:HQL支持常见的SQL聚合函数,如 count(), sum(), avg() 等。
  • group byhaving:用于对查询结果进行分组。
  • New Map:用于指定返回 Map 类型,最好外面再用一层 List 包含,确保每个 Map 对应一行数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白的一叶扁舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值