本文的所有示例都将在 Junit 单元测试中完成,因此需要先添加 Junit 依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.14.0</version>
<scope>test</scope>
</dependency>
这里我的 Idea 可能是因为版本缘故,不支持最新的 Junit 6.0,所以使用的是 Junit 5。
如前文展示的,使用Persistence
可以获取EntityManagerFactory
的实例:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpa-demo");
createEntityManagerFactory
方法需要接收一个持久单元名(Persistence Unit Name),对应的是 JPA 配置文件src/main/resources/META-INF/persistence.xml
中的persistence-unit
标签的name
属性:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="jpa-demo" transaction-type="RESOURCE_LOCAL">
<!-- ... -->
</persistence-unit>
</persistence>
createEntityManagerFactory
方法有另一个重载版本:
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.show_sql", false);
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpa-demo", properties);
Assertions.assertNotNull(entityManagerFactory);
通过参数properties
可以设置 JPA 的配置信息,比如是否打印 SQL 日志(hibernate.show_sql
)。
EntityManagerFactory
EntityManagerFactory
用于创建EntityManager
:
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManager
getTransaction
EntityManger
可以用于开启事务:
transaction = entityManager.getTransaction();
transaction.begin();
find
使用主键查询数据:
Customer customer = entityManager.find(Customer.class, 1);
System.out.println("-----------------------");
System.out.println(customer);
需要注意的是,执行find
后,JPA 会立即执行 SQL 并获取到数据实体,这点和getReference
有所不同。
getReference
Customer customer = entityManager.getReference(Customer.class, 1);
System.out.println("-----------------------");
System.out.println(customer);
getReference
同样可以根据主键查询数据,但区别在于getReference
返回的实际上是一个代理对象,该方法执行时不会立即执行 SQL 语句,而是在真正读取代理对象的相关属性时才执行 SQL 并获取数据。这点有点像 Spring 的 @Lazy 注解标记的 Bean,是一种懒加载行为。
这种方式查询数据有一个弊端,如果在真正执行 SQL 获取数据前 EntityManager
被关闭,就会出现异常:
Customer customer = entityManager.getReference(Customer.class, 1);
transaction.commit();
entityManager.close();
System.out.println("-----------------------");
Assertions.assertThrowsExactly(LazyInitializationException.class, () -> {
System.out.println(customer);
});
persist
使用 persist
可以保存实例:
Customer customer = new Customer();
customer.setLastName("icexmoon");
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreateTime(new Date());
entityManager.persist(customer);
需要注意的是,如果实体已经存在主键,使用persist
保存实体会报错:
Customer customer = new Customer();
customer.setLastName("icexmoon");
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreateTime(new Date());
customer.setId(11L);
Assertions.assertThrowsExactly(EntityExistsException.class, () -> {
entityManager.persist(customer);
});
remove
remove
方法可以从数据库中移除实体对应的数据行,需要注意的是,与 MyBatis 不同,它不能移除游离状态(和 Session 不关联)的实体:
Customer customer = new Customer();
customer.setId(1L);
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
entityManager.remove(customer);
});
只能移除持久态(和 Session 关联)的实体:
Customer customer = entityManager.find(Customer.class, 1);
entityManager.remove(customer);
merge
merge
方法的用途类似于 Hibernate 的saveOrUpdate
,不过在部分细节有所不同。根据接收的对象状态的不同,merge
方法的行为有所不同,可以用下面的流程图表示:
示例1, merge
接收的是一个临时实体(没有 ID):
Customer customer = new Customer();
customer.setLastName("icexmoon");
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreateTime(new Date());
Customer merge = entityManager.merge(customer);
// 临时实体的 ID 依然是 null
Assertions.assertNull(customer.getId());
// merge 方法返回的是一个持久实体,ID 不为 null
Assertions.assertNotNull(merge.getId());
示例2,merge
接收的是一个游离实体,且数据库中存在该实体:
Customer customer = new Customer();
customer.setId(3L);
customer.setLastName("Tom");
Customer merge = entityManager.merge(customer);
Assertions.assertEquals("Tom", merge.getLastName());
Assertions.assertEquals(3L, merge.getId());
示例3,merge
接收的是一个游离实体,且EntityManager
中有 ID 相同实体的缓存:
Customer customer = new Customer();
customer.setId(3L);
customer.setLastName("Jim");
Customer customerExists = entityManager.find(Customer.class, 3L);
Customer merge = entityManager.merge(customer);
Assertions.assertEquals("Jim", merge.getLastName());
Assertions.assertEquals(3L, merge.getId());
Assertions.assertSame(customerExists, merge);
实例4,merge
接收的是一个游离实体,且该实体不存在于数据库中:
Customer customer = new Customer();
customer.setId(99L);
customer.setLastName("Bruce");
Assertions.assertThrowsExactly(OptimisticLockException.class, ()->{
entityManager.merge(customer);
});
教程中说此时会保存数据,但实际测试发现会报错,抛出一个OptimisticLockException
异常,可能是 JPA 版本不同导致的。
flush
flush
方法用于同步持久上下文环境,即将持久上下文环境中的所有未保存实体的状态信息保存到数据库:
Customer customer = entityManager.find(Customer.class, 3L);
customer.setLastName("Li Lei");
entityManager.flush();
示例中的flush
方法调用时,会执行 UPDATE 语句修改数据库中对应的行数据。
可以通过setFlushMode
方法修改 flush
的执行方式:
-
FlushModeType.AUTO:自动更新数据库实体
-
FlushModeType.COMMIT:直到下次事务提交才更新数据库记录
可以通过getFlushMode
方法获取当前持久上下文环境的 Flush 模式。
refresh
示例:
Customer customer = entityManager.find(Customer.class, 3L);
customer = entityManager.find(Customer.class, 3L);
这个示例执行了两次find
,但实际只会执行一次 SELECT 语句,因为 Hibernate 有缓存。
如果需要立即从数据库更新实体数据,可以使用refresh
:
Customer customer = entityManager.find(Customer.class, 3L);
customer = entityManager.find(Customer.class, 3L);
// 这里会强制执行 SELECT 更新实体数据
entityManager.refresh(customer);
clear
清除持久上下文环境,断开所有关联的实体。如果这时还未提交的更新会被撤销。
contains
判断一个实体是否属于当前持久上下文环境管理的实体。
Customer customer = entityManager.find(Customer.class, 3L);
Assertions.assertTrue(entityManager.contains(customer));
entityManager.clear();
Assertions.assertFalse(entityManager.contains(customer));
isOpen
判断当前实体管理器是否打开状态。
close
关闭实体管理器。如果实体管理器关联的事务仍然处于活动状态,close
调用时不会立即关闭,直到事务执行完毕。
EntityTransaction
EntityTransaction
代表 JPA 中的事务,可以执行事务相关的操作:
-
begin,开启事务
-
commit,提交事务
-
rollback,回滚事务
-
setRollbackOnly,将事务设置成只能回滚
-
getRollbackOlny,查看事务是否设置成了只能回滚
-
isActive,查看是否是否活动状态,只有非活动状态才能 begin,只有活动状态才能执行 commit、rollback 等操作。
本文的完整示例可以从获取。
文章评论