第六节 通过JPA原生API访问数据

JPA的核心API有这么几个:

  • EntityManagerFactory

    系统中针对每个持久化单元创建一个对应的EntityManagerFactory,它是EntityManager的工厂。这个对象的创建是重量级的,同时也是线程安全的。一般在程序中是一次创建,重复使用,直至应用结束时才关闭。

  • EntityManager

    实体管理器EntityManagerJPA API核心中的核心,从EntityManagerFactory对象创建,代表对数据库的一次会话。它可以直接执行实体的增、删、改操作,并通过创建Query对象执行实体查询操作。它的创建是轻量的,但不是线程安全的,应该按需创建,及时关闭。

  • EntityTransaction

    对数据库的操作必然涉及事务问题。在Java EE环境中,事务一般是由应用服务器容器管理的,应用代码可以不用考虑事务。但在Java SE环境中,需要由应用代码来负责开始和提交/回滚事务。通过EntityManager对象的getTransaction()方法获取事务对象EntityTransaction,在持久化操作开始前调用EntityTransactionbegin()方法开始一个事务,操作成功完成后调用它的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访问数据的步骤:

  1. 在应用启动时,针对每个持久化单元一次性创建EntityManagerFactory对象并缓存起来;
  2. 对数据库的每一次访问:
    1. EntityManagerFactory对象中生成一个EntityManager新实例;
    2. 调用EntityManagergetTransaction()方法获得EntityTransaction事务对象;
    3. 调用EntityTransactionbegin()方法开始一个新的事务;
    4. try...catch...语句内部调用EntityManager的方法进行增删改查操作;
    5. 当数据访问方法成功完成,调用EntityTransactioncommit()方法提交事务;
    6. 如果数据访问方法抛出异常,调用EntityTransactionrollback()方法回滚事务;
    7. 调用EntityManager对象的close()方法关闭它;
  3. 当整个应用结束运行之前,调用EntityManagerFactory对象的close()方法关闭它。

results matching ""

    No results matching ""