Hibernate

Entitymanagerfactory 和 Sessionfactory

1、简介 本文将带你了解 SessionFactory 和 EntityManagerFactory 之间的异同。 顾名思义,这两个类都是用于创建数据库通信对象的工厂类。除了创建对象,它们还提供了其他功能,帮助我们与数据库进行交互。 2、EntityManagerFactory 是什么? Java 持久化 API(JPA)是 Java 应用管理持久化数据的规范。它提供了一种与关系数据库交互的标准方式。EntityManager(实体管理器)作为 JPA 的核心接口,用于与持久化上下文交互并管理实体的生命周期。它为基本的 CRUD 操作提供了具有方法的轻量级实例。 EntityManagerFactory 是一个 JPA 接口,用于创建 EntityManager 实例,以线程安全的方式与持久化上下文(Persistence Context)交互。 2.1、示例 首先,先定义实体: @Entity(name = "persons") public class Person { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Integer id; private String name; private String email; // Getter / Setter 省略 } 设置配置有多种方法,这里使用 persistence.xml 配置文件的方法。首先,需要在 resource/META-INF 文件夹中创建该配置文件,并定义连接配置: <persistence-unit name="com.baeldung.sfvsemf.persistence_unit" transaction-type="RESOURCE_LOCAL"> <description>Persistence Unit for SessionFactory vs EntityManagerFactory code example</description> <class>com.baeldung.sfvsemf.entity.Person</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="hibernate.

在 Hibernate 中更新和插入前更改字段值

1、概览 在使用 Hibernate 时,经常会遇到这样的情况:在将实体持久化到数据库之前,需要更改字段的值。这种情况可能是因为需要执行必要的字段转换。 本文将通过一个示例:即在执行更新和插入操作前将字段值转换为大写字母,来了解实现这一目的的不同方法。 2、实体生命周期回调 首先,定义一个简单的实体类 Student: @Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column private String name; // Getter / Setter 方法省略 } 第一种方法是 JPA 实体生命周期回调。JPA 提供了一组注解,允许我们在不同的 JPA 生命周期事件中执行一个方法,例如: @PrePresist:在插入事件之前执行。 @PreUpdate:在更新事件之前执行。 我们在 Student 实体类中添加 changeNameToUpperCase() 方法。该方法将 name 字段改为大写。该方法通过 @PrePersist 和 @PreUpdate 进行注解,以便 JPA 在持久化和更新之前调用该方法: @Entity @Table(name = "student") public class Student { @PrePersist @PreUpdate private void changeNameToUpperCase() { name = StringUtils.

Hibernate 中 INSERT 查询的 ON CONFLICT 子句

1、概览 本文将带你了解 Hibernate 6.5 中引入的用于 INSERT 查询的 ON CONFLICT 子句。 我们使用 ON CONFLICT 子句来处理使用 HQL 或 Criteria 查询插入数据时违反表约束的情况。ON CONFLICT 子句也可以用于处理 upsert 查询。 2、ON CONFLICT 子句 使用 ON CONFLICT 子句进行 insert 的语法如下: "INSERT" "INTO"? targetEntity targetFields (queryExpression | valuesList) conflictClause? conflictClause 的写法为: "on conflict" conflictTarget? conflictAction conflictAction(冲突操作)可以是 DO NOTHING 或者 DO UPDATE。 现在,来看一个例子。假设实体类 Student 的属性有 studentId 和 name: @Entity public class Student { @Id private long studentId; private String name; } studentId 属性是 Student 实体的唯一键。我们可以在 INSERT VALUES 查询中手动插入 @Id 值,或者使用 @GeneratedValue 注解来指定 ID 的生成策略。

Hibernate 6 中序列的命名策略

1、简介 本文将带你了解如何为数据库序列(Sequences)配置 Hibernate 6 的隐式 命名策略。Hibernate 6 引入了几种新的命名策略,这些策略会影响序列的命名和使用方式。 2、标准命名策略 默认情况下,Hibernate 6 使用标准命名策略。它根据实体名称和列名称生成序列名称。假如,我们有一个带有 id 列的实体 Person,那么序列名称就是 person_seq。 要修改命名策略,需要在 application.properties 中为不同的命名策略添加必要的配置。 # 使用标准命名策略 spring.jpa.properties.hibernate.id.db_structure_naming_strategy=standard 下面介绍如何为每种命名策略设置配置。 来看一个基本的 Person 实体类: @Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private String name; // Getter / Setter 省略 } 在本例中,由于我们使用的是标准策略,因此在建表的同时,Hibernate 会自动生成一个名为 person_seq 的序列: Hibernate: create table person ( id bigint not null, name varchar(255), primary key (id) ) Hibernate: create sequence person_seq start with 1 increment by 50 标准策略的一个关键点是其默认增量(increment)值。Hibernate 会分配一个较大的值,如 50,以优化批处理操作,减少序列检索所需的数据库调用次数。

Hibernate 异常 UnknownEntityException:Could not resolve root entity

1、概览 本文将带你了解 Hibernate 出现异常 “UnknownEntityException:Could not resolve root entity” 的原因,以及解决办法。 2、理解异常 通常,在 HQL 或 JPQL 查询中解析已知映射实体名称失败时,Hibernate 就会抛出 “UnknownEntityException: Could not resolve root entity” 异常。 简而言之,Hibernate 依赖 JPA 实体来完成对象关系映射的所有工作。因此,它希望查询中指定的实体名称与 @Entity 注解所注解的类名称相匹配。 因此,导致异常的最常见原因之一就是 使用了与有效实体类名称不匹配的名称。 3、示例 知道了导致 Hibernate 出现 UnknownEntityException 的原因后,来看看如何在实践中重现这种情况。 首先,创建一个 Person 类,如下: @Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准 Getter / Setter 方法省略 } 如上,使用 id、firstName 和 lastName 来定义一个 Person。 @Entity 注解表示 Person 类是一个 JPA 实体,@Id 表示代表主键的字段。

Hibernate 中的 load() 与 get()

1、简介 在 Hibernate 中,load() 和 get() 是用于从数据库检索数据的两种方法。本文将带你了解这两种方法之间的区别。 2、加载策略 Hibernate 中的 load() 方法采用了一种懒加载策略。调用该方法时,它会返回一个实体的代理对象,延迟数据库查询,直到访问对象的属性或方法时才执行查询。 如下: Person person = new Person("John Doe", 30); Session session = sessionFactory.getCurrentSession(); session.saveOrUpdate(person); Person entity = session.load(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 首先,创建一个新的 Person 对象并将其保存到数据库中。然后,使用 load() 根据 id 检索 Person 实体。虽然实体看起来是一个 Person 对象,但它只是 Hibernate 提供的一个代理对象。 当访问代理对象的属性(如 name 和 age)时,Hibernate 会拦截调用,并在必要时从数据库动态加载实际数据。相反,get()方法采用了急切加载策略,会立即查询数据库并返回实际实体对象: Person entity = session.get(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 3、数据存在时 当调用 load() 方法时,Hibernate 会用提供的主键 id 创建一个实体的代理对象。这个代理对象是实体数据的占位符,只填充了 id。实体的其余属性未初始化,将在首次访问时从数据库加载。如果试图在未初始化代理对象的情况下访问它的任何属性,就会抛出 LazyInitializationException 异常:

清除 JPA/Hibernate 中托管的实体

1、概览 本文将带你了解 JPA 是如何托管实体的,以及 Persistence Context(持久化上下文)由于外部变化而无法返回最新数据的情况。 2、Persistence Context 每个 EntityManager 都与一个 Persistence Context 相关联,该上下文在内存中存储所管理的实体。每当通过 EntityManager 对实体执行任何数据操作时,该实体就会变成由 Persistence Context 管理的实体。 当再次检索实体时,JPA 会从 Persistence Context 返回托管实体,而不是从数据库中获取。这种缓存机制有助于提高性能,而无需从数据库中重复获取相同的数据。 Persistence Context 在 JPA 中也被称为一级(first-level,L1)缓存。 3、Demo 设置 首先,创建一个简单的实体类: @Entity @Table(name = "person") public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; // 构造函数、Getter、Setter 方法省略 } 接下来,创建一个 Person 实体并将其持久化到数据库中: EntityTransaction transaction = entityManager.getTransaction(); transaction.begin(); Person person = new Person(); person.setName("David Jones"); entityManager.persist(person); transaction.

Hibernate 和 Spring Data JPA 中的 N+1 问题

1、概览 Spring JPA 和 Hibernate 为无缝数据库通信提供了强大的工具。不过,由于客户端将更多控制权委托给了框架,因此生成的查询可能远非最佳。 本文将带你了解使用 Spring JPA 和 Hibernate 时常见的 N+1 问题,以及可能导致该问题的不同情况。 2、社交媒体平台 为了更好地将问题形象化,我们需要概述实体之间的关系。以一个简单的社交网络平台为例。这里只有用户(User)和帖子(Post): 我们在图表中使用了 Iterable,并且我们将为每个示例提供具体的实现:List 或 Set。 为了测试请求的数量,我们将使用一个专用库,而不是检查日志。不过,我们会参考日志,以便更好地了解请求的结构。 如果在每个示例中没有明确指定关系的获取类型(Fetch Type),则默认情况下假定为默认值。所有的一对一关系都使用急切加载(Eager Fetch),而一对多关系则使用延迟加载(Lazy)。此外,代码示例中使用了 Lombok 来减少代码中的冗余。 3、N+1 问题 N+1 问题指的是,对于单个请求(例如检索用户),会对每个用户发出额外请求,以获取其信息。虽然这个问题通常与懒加载有关,但并非总是如此。 任何类型的关系都可能出现这种问题。不过,它通常出现在多对多或一对多关系中。 3.1、延迟加载 首先,来看看懒加载是如何导致 N+1 问题的,示例如下: @Entity public class User { @Id private Long id; private String username; private String email; @OneToMany(cascade = CascadeType.ALL, mappedBy = "author") protected List<Post> posts; // 构造函数/getter/setter } User 与 Post 之间是一对多的关系。这意味着每个 User 都有多个 Post。我们没有明确确定字段的 Fetch 策略。策略是从注解中推断出来的。如前所述,@OneToMany 默认采用 Lazy Fetch 策略:

Hibernate 异常 QueryException: “named parameter not bound”.

1、概览 本文将带你了解如何解决 Hibernate 异常 QueryException: “named parameter not bound”。 2、理解异常 简而言之,在将 Hibernate 查询转换为 SQL 时,由于语法无效,Hibernate 会抛出 QueryException 来提示错误。“named parameter not bound” 表明 Hibernate 无法绑定特定查询中指定的命名参数。 通常情况下,命名参数的前缀是冒号(:),后面是一个占位符,表示在执行查询之前需要设置的实际值: SELECT p FROM Person p WHERE p.firstName = :firstName; 造成异常的最常见原因之一是忘记为 Hibernate 查询中的命名参数赋值。 3、重现异常 理解了导致异常的原因后,通过一个实际的例子来重现这个异常。 创建如下 Person 实体类: @Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准的 Get、Set } 如上,@Entity 注解表示类是一个实体,它映射了数据库中的一个表。此外,@Id 表示 id 属性代表主键。 现在,创建一个带有命名参数的 Hibernate 查询,并假装忘记为参数设置值: @Test void whenNotSettingValueToNamedParameter_thenThrowQueryException() { Exception exception = assertThrows(QueryException.

将 Hibernate 代理对象转换为实际的实体对象

1、概览 本文将带你了解 Hibernate 是在什么时候创建代理对象的,代理对象有啥用?以及如何把 Hibernate 的代理对象转换为真正的实体对象。 2、Hibernate 何时创建代理对象? Hibernate 使用代理对象来实现懒加载。 有如下 PaymentReceipt 和 Payment 实体: @Entity public class PaymentReceipt { ... @OneToOne(fetch = FetchType.LAZY) private Payment payment; ... } @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Payment { ... @ManyToOne(fetch = FetchType.LAZY) protected WebUser webUser; ... } 加载这两个实体中的任何一个,都会导致 Hibernate 为关联字段使用 FetchType.LAZY 创建一个代理对象。 创建并运行集成测试: @Test public void givenPaymentReceipt_whenAccessingPayment_thenVerifyType() { PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L); Assert.assertTrue(paymentReceipt.getPayment() instanceof HibernateProxy); } 测试代码如上,加载了一个 PaymentReceipt,并验证了 payment 对象不是 CreditCardPayment 的实例,而是一个 HibernateProxy 对象。