第六节 通过JPA原生API访问数据
JPA
的核心API
有这么几个:
EntityManagerFactory
系统中针对每个持久化单元创建一个对应的
EntityManagerFactory
,它是EntityManager
的工厂。这个对象的创建是重量级的,同时也是线程安全的。一般在程序中是一次创建,重复使用,直至应用结束时才关闭。EntityManager
实体管理器
EntityManager
是JPA API
核心中的核心,从EntityManagerFactory
对象创建,代表对数据库的一次会话。它可以直接执行实体的增、删、改操作,并通过创建Query
对象执行实体查询操作。它的创建是轻量的,但不是线程安全的,应该按需创建,及时关闭。EntityTransaction
对数据库的操作必然涉及事务问题。在
Java EE
环境中,事务一般是由应用服务器容器管理的,应用代码可以不用考虑事务。但在Java SE
环境中,需要由应用代码来负责开始和提交/回滚事务。通过EntityManager
对象的getTransaction()
方法获取事务对象EntityTransaction
,在持久化操作开始前调用EntityTransaction
的begin()
方法开始一个事务,操作成功完成后调用它的commit()
方法提交事务,操作失败时调用它的rollback()
方法进行事务回滚。Query
要对数据库执行查询,就需要通过
EntityManager
对象的createQuery()
方法创建Query
对象,接受一个JPA
查询语言写成的查询字符串作为参数。查询可以返回单个结果(调用Query
对象的getSingleResult()
方法),一个列表(调用Query
对象的getResultList()
方法),一个流(调用Query
对象的getResultStream()
方法)等。Query
还有一个变体TypedQuery
,支持泛型。当给EntityManager
对象的createQuery()
方法传入一个类对象作为第二个参数时将返回一个TypedQuery
对象。它能够返回适当类型的对象或泛型列表,而不需要强制类型转换。
下面是tmall-persistence-jpa
模块中的范例代码,OrderRepository
类用原生JPA API
实现了订单仓储接口Orders
:
public class BuyerRepositoryJpql implements Buyers {
private final EntityManager entityManager;
public BuyerRepositoryJpql(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public <T extends Buyer> T save(T buyer) {
return entityManager.merge(buyer);
}
@Override
public void delete(Buyer buyer) {
entityManager.remove(buyer);
}
@Override
public List<Buyer> findAll() {
return entityManager.createQuery("select o from Buyer o").getResultList();
}
@Override
public Optional<Buyer> getById(int id) {
return Optional.ofNullable(entityManager.find(Buyer.class, id));
}
@Override
public Optional<Buyer> getByName(String name) {
return entityManager
.createQuery("select o from Buyer o where o.name = :name", Buyer.class)
.setParameter("name", name)
.getResultStream()
.findAny();
}
@Override
public Stream<Buyer> findByNameStartsWith(String nameFragment) {
return entityManager
.createQuery("select o from Buyer o where o.name Like :name", Buyer.class)
.setParameter("name", nameFragment + "%")
.getResultStream();
}
@Override
public Stream<Buyer> findByNameContains(String nameFragment) {
return entityManager
.createQuery("select o from Buyer o where o.name Like :name", Buyer.class)
.setParameter("name", "%" + nameFragment + "%")
.getResultStream();
}
@Override
public Optional<PersonalBuyer> findPersonalBuyerByQQ(String qq) {
String jpql = "select o from PersonalBuyer o join o.imInfos i where KEY(i) = :key and VALUE(i) = :value";
return entityManager.createQuery(jpql, PersonalBuyer.class)
.setParameter("key", ImType.QQ)
.setParameter("value", qq)
.getResultStream()
.findAny();
}
}
我们编写集成测试类作为仓储的客户来调用仓储的持久化代码:
public abstract class BaseIntegrationTest implements WithAssertions {
private static EntityManagerFactory emf;
protected EntityManager entityManager;
private EntityTransaction transaction;
@BeforeAll
static void beforeAllTest() {
emf = Persistence.createEntityManagerFactory("default");
}
@BeforeEach
void BeforeEachTest() {
entityManager = emf.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
}
@AfterEach
void afterEachTest() {
transaction.rollback();
entityManager.clear();
}
@AfterAll
static void afterAllTest() {
emf.close();
}
}
class BuyerRepositoryJpqlTest extends BaseIntegrationTest {
private static final String buyer1Name = "张三";
private static final String buyer2Name = "华为公司";
private Buyers buyers;
private PersonalBuyer buyer1;
private OrgBuyer buyer2;
@BeforeEach
void beforeEach() {
buyers = new BuyerRepositoryJpql(entityManager);
buyer1 = buyers.save(new PersonalBuyer(buyer1Name));
buyer2 = buyers.save(new OrgBuyer(buyer2Name));
}
@AfterEach
void afterEach() {
buyers.findAll().forEach(buyers::delete);
}
@Test
void findById() {
assertThat(buyers.getById(buyer1.getId())).containsSame(buyer1);
assertThat(buyers.getById(buyer2.getId())).containsSame(buyer2);
}
@Test
void findByName() {
assertThat(buyers.getByName(buyer1Name)).containsSame(buyer1);
assertThat(buyers.getByName(buyer2Name)).containsSame(buyer2);
}
@Test
void findByNameStartsWith() {
assertThat(buyers.findByNameStartsWith("华"))
.contains(buyer2)
.doesNotContain(buyer1);
assertThat(buyers.findByNameStartsWith("三"))
.isEmpty();
}
@Test
void findByNameContains() {
assertThat(buyers.findByNameContains("三"))
.contains(buyer1)
.doesNotContain(buyer2);
}
@Test
void findAll() {
assertThat(buyers.findAll()).contains(buyer1, buyer2);
}
@Test
void delete() {
buyers.delete(buyer1);
assertThat(buyers.findAll()).contains(buyer2).doesNotContain(buyer1);
}
@Test
void update() {
buyer1.setName("李四");
buyers.save(buyer1);
assertThat(buyers.getById(buyer1.getId()).map(Buyer::getName)).containsSame("李四");
assertThat(buyers.getById(buyer2.getId()).map(Buyer::getName)).containsSame(buyer2Name);
}
@Test
void findPersonalBuyerByQQ() {
buyer1.setImInfo(ImType.QQ, "34567");
buyers.save(buyer1);
assertThat(buyers.findPersonalBuyerByQQ("34567")).containsSame(buyer1);
}
}
总结:在Java SE
环境中通过JPA
原生API
访问数据的步骤:
- 在应用启动时,针对每个持久化单元一次性创建
EntityManagerFactory
对象并缓存起来; - 对数据库的每一次访问:
- 从
EntityManagerFactory
对象中生成一个EntityManager
新实例; - 调用
EntityManager
的getTransaction()
方法获得EntityTransaction
事务对象; - 调用
EntityTransaction
的begin()
方法开始一个新的事务; - 在
try...catch...
语句内部调用EntityManager
的方法进行增删改查操作; - 当数据访问方法成功完成,调用
EntityTransaction
的commit()
方法提交事务; - 如果数据访问方法抛出异常,调用
EntityTransaction
的rollback()
方法回滚事务; - 调用
EntityManager
对象的close()
方法关闭它;
- 从
- 当整个应用结束运行之前,调用
EntityManagerFactory
对象的close()
方法关闭它。