JPA中PersistenceUnit与PersistenceContext区别

PersistenceContext持久化上下文和PersistenceUnit持久化单元是 JPA 中的两个重要概念,我们用它们来管理应用程序中实体的生命周期。

在本教程中,我们将简要介绍实体管理器和实体管理器工厂。接下来,我们将了解持久性上下文为何如此重要及其用例。最后,我们将看到持久性单元的作用及其用例。

EntityManager和EntityManagerFactory
在深入了解细节之前,必须对EntityManager和EntityManagerFactory接口有基本的了解。正如我们将看到的,它们在管理持久性、实体和数据库交互方面发挥着重要作用。

EntityManager实体管理器
EntityManager是一个与持久化上下文交互的接口。它对实体执行 CRUD 操作、跟踪更改并确保在事务提交时与数据库同步。 EntityManager代表持久性上下文并在事务范围内运行。

EntityManagerFactory实体管理器工厂
EntityManagerFactory是一个创建EntityManager的接口,有效地充当工厂。创建后,EntityManagerFactory与特定的持久性单元相关联,从而可以创建EntityManager 的实例。

PersistenceContext持久化上下文
PersistenceContext 是一个短暂的、事务范围的上下文,用于管理实体的生命周期。它表示作为实体管理器的一级缓存存储在内存中的一组托管实体 。如果事务开始,则会创建持久性上下文,并最终在事务提交或回滚时关闭或清除。

持久性上下文自动检测对托管实体所做的更改,并确保所有实体更改与持久性存储同步。

我们可以使用@PersistenceContext注释来定义持久化上下文的类型:

@PersistenceContext
private EntityManager entityManager;

JPA 中有两种类型的持久化上下文:TRANSACTION 和EXTENDED。

让我们首先使用@Entity注释创建与PRODUCT表对应的实体:

@Entity
@Table(name = "PRODUCT")
public class Product {
    
    @Id
    private Long id;
    private String name;
    private double price;
   
// standard constructor, getters, setters
}

现在,让我们创建服务类 PersistenceContextProductService:

@Service
public class PersistenceContextProductService {
    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManagerTransactionType;
    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManagerExtendedType;
    @Transactional
    void insertProductWithTransactionTypePersistenceContext(Product product) {
        entityManagerTransactionType.persist(product);
    }
    Product findWithTransactionTypePersistenceContext(long id) {
        return entityManagerTransactionType.find(Product.class, id);
    }
    void insertProductWithExtendedTypePersistenceContext(Product product) {
        entityManagerExtendedType.persist(product);
    }
    Product findWithExtendedTypePersistenceContext(long id) {
        return entityManagerExtendedType.find(Product.class, id);
    }
}


事务范围的持久化上下文PersistenceContext:
TRANSACTION PersistenceContext类型是 JPA 中的默认持久化上下文。在此类型中,PersistenceContext绑定到事务。这意味着每个事务都会创建和销毁PersistenceContext。

让我们使用TRANSACTION类型持久化上下文来持久化产品。我们将保存Product实体,并且在提交事务时更改将自动保留:

@Test
void whenProductPersistWithTransactionPersistenceContext_thenShouldPersist() {
    Product p = new Product(1L, "Product 1", 100.0);
    persistenceContextProductService.insertProductWithTransactionTypePersistenceContext(p);
    Product productFromTransactionScoped = persistenceContextProductService.findWithTransactionTypePersistenceContext(1L);
    Assertions.assertNotNull(productFromTransactionScoped);
    Product productFromExtendedScoped = persistenceContextProductService.findWithExtendedTypePersistenceContext(1L);
    Assertions.assertNotNull(productFromExtendedScoped);
}

EXTENDED扩展的持久化上下文
EXTENDED PersistenceContext类型将PersistenceContext的范围扩展到事务边界之外。我们可以通过带有EXTENDED类型的@PersistenceContext注释来创建它。

现在,让我们使用EXTENDED类型持久化上下文来持久化产品,并且不使用事务。产品将仅保存在持久性上下文中:

@Test
void whenProductPersistWithExtendedPersistence_thenShouldPersist() {
    Product product = new Product(2L, "Product 1", 100.0);
    persistenceContextProductService.insertProductWithExtendedTypePersistenceContext(product);
    Product productFromExtendedScoped = persistenceContextProductService.findWithExtendedTypePersistenceContext(2L);
    Assertions.assertNotNull(productFromExtendedScoped);
    Product productFromTransactionScoped = persistenceContextProductService.findWithTransactionTypePersistenceContext(2L);
    Assertions.assertNull(productFromTransactionScoped);
}

应用程序在删除 bean 或有意关闭扩展持久性上下文时提交更改。

PersistenceUnit持久化单元
PersistenceUnit定义了实体类集及其配置,并且它表示实体管理器管理的这些实体的逻辑分组。我们可以通过创建persistence.xml文件或扩展PersistenceUnitInfo接口来创建持久性单元。

@PersistenceUnit JPA注释将实体管理器工厂注入到 bean 中:

@PersistenceUnit(name = "persistence-unit-name")
private EntityManagerFactory entityManagerFactory;

持久化单元支持两种类型:RESOURCE_LOCAL 和 JTA。

持久化单元的一大优点是我们可以在同一个应用程序中定义多个持久化单元,每个持久化单元适用于系统的不同部分甚至单独的数据库。

1、资源本地持久化单元
默认情况下,Spring应用程序使用资源本地持久化单元。在资源本地PersistenceUnit中,我们负责管理事务。它不依赖于外部事务管理器。

让我们声明一个persistence.xml文件,位于类路径的META-INF/persistence.xml中:

<persistence-unit name="com.baeldung.contextvsunit.h2_persistence_unit" transaction-type="RESOURCE_LOCAL">
    <description>EntityManager serializable persistence unit</description>
    <class>com.baeldung.contextvsunit.entity.Product</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
        <property name=
"hibernate.hbm2ddl.auto" value="update"/>
        <property name=
"hibernate.show_sql" value="true"/>
        <property name=
"hibernate.generate_statistics" value="false"/>
        <property name=
"hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        <property name=
"jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name=
"jakarta.persistence.jdbc.url" value="jdbc:h2:mem:db2;DB_CLOSE_DELAY=-1"/>
        <property name=
"jakarta.persistence.jdbc.user" value="sa"/>
        <property name=
"jakarta.persistence.jdbc.password" value=""/>
    </properties>
</persistence-unit>

正如我们所看到的,我们使用数据库连接属性定义持久性单元。此外,我们还配置 Hibernate 属性,包括方言、事务设置和持久性操作的其他属性。每次应用程序与数据库交互时,它都会在持久性单元的上下文中运行。我们在持久性单元内定义 Java 实体和数据库表之间的映射。

现在,让我们在PersistenceUnitProductService 类中使用这个持久性单元:

@Service
public class PersistenceUnitProductService {
    @PersistenceUnit(name = "com.baeldung.contextvsunit.h2_persistence_unit")
    private EntityManagerFactory emf;
    @Transactional
    void insertProduct(Product product) {
        EntityManager entityManager = emf.createEntityManager();
        entityManager.persist(product);
    }
    Product find(long id) {
        EntityManager entityManager = emf.createEntityManager();
        return entityManager.find(Product.class, id);
    }
}

让我们保留一个 Product 实体来验证一切是否按我们的预期工作:

@Test
void whenProductPersistWithEntityManagerFactory_thenShouldPersist() {
    Product p = new Product(1L, "Product 1", 100.0);
    persistenceUnitProductService.insertProduct(p);
    Product createdProduct = persistenceUnitProductService.find(1L);
    assertNotNull(createdProduct);
}

2、JTA持久单元
使用JTA意味着我们将工作委托给容器。因此,我们无法通过EntityManagerFactory获取EntityManager。相反,我们必须使用容器通过@PersistenceContext注释提供和注入的EntityManager。

企业应用程序在 TomEE 和 WildFly 等 Java EE 容器中部署时通常使用 JTA 持久性单元。