文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

JPA 之 Hibernate EntityManager 使用指南

2023-08-16 19:16

关注

Hibernate EntityManager 专题

参考:

基本概念及获得 EntityManager 对象

基本概念

在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在 JPA 中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。

EntityManager是 JPA 中用于增删改查的接口,连接内存中的 java 对象和数据库的数据存储。Hibernate EntityManager是围绕提供JPA编程接口实现的Hibernate Core的一个包装,支持JPA实体实例的生命周期,并允许用标准的Java Persistence查询语言编写查询。

EntityManager称为实体管理器,它由EntityManagerFactory所创建。EntityManagerFactory,作为EntityManager的工厂,包含有当前O-R映射的元数据信息,每个EntityManagerFactory,可称为一个持久化单元(PersistenceUnit),每个持久化单元可认为是一个数据源的映射(所谓数据源,可理解为一个数据库,可以在应用服务器中配置多个数据源,同时使用不同的PersistenceUnit来映射这些数据源,从而能够很方便的实现跨越多个数据库之间的事务操作!)

PersistenceContext,称为持久化上下文,它一般包含有当前事务范围内的,被管理的实体对象(Entity)的数据。每个EntityManager,都会跟一个PersistenceContext相关联。PersistenceContext中存储的是实体对象的数据,而关系数据库中存储的是记录,EntityManager正是维护这种OR映射的中间者,它可以把数据从数据库中加载到PersistenceContext中,也可以把数据从PersistenceContext中持久化到数据库,EntityManager通过Persist、merge、remove、refresh、flush等操作来操纵PersistenceContext与数据库数据之间的同步!

EntityManager是应用程序操纵持久化数据的接口。它的作用与hibernate session类似。为了能够在一个请求周期中使用同一个session对象,在hibernate的解决方案中,提出了currentSession的概念,hibernate中的current session,可以跟JTA事务绑定,也可以跟当前线程绑定。在hibernate中,session管理着所有的持久化对象的数据。而在EJB3中,EntityManager管理着PersistenceContext,PersistenceContext正是被管理的持久化对象的集合。

在 Java EE 环境下,一个 JTA 事务通常会横跨多个组件的调用(比如多个 EJB 组件的方法调用)。这些组件需要能够在单个事务范围内访问到同样的PersistenceContext。为了满足这种情况的需要,当EntityManager被注入或通过 JNDI 被查询的时候,它的 PersistenceContext 将会在当前事务范围内自动传播,引用到同一个 Persistence unit 的EntityManager将使用同样的 PersistenceContext。这可以避免在不同的组件之间传递EntityManager引用。

通过容器来传递PersistenceContext,而不是应用程序自己来传递EntityManager。这种方式(由容器管理着PersistenceContext,并负责传递到不同的EntityManager)称为容器管理的实体管理器(Container-Managed EntityManager),它的生命周期由容器负责管理,编程人员不需要考虑EntityManger的连接,释放以及复杂的事务问题等。

有一种不常见的情况是,应用程序自身需要独立访问PersistenceContext。即每次创建一个EntityManager都会迫使创建一个新的PersistenceContext。这些PersistenceContext即使在同一个事务范围内也不会跟其它EntityManager共享!这个创建过程可以由EntityManagerFactory的createEntityManager方法来创建。这被称为应用管理的实体管理器(application-managed entity manager)。

获得EntityManager对象

常用方式:SpringBoot容器托管对象方式:

依赖:

    org.springframework.boot    spring-boot-starter-data-jpa

对象注入:

@Autowiredprivate EntityManager entityManager;

实体状态和转换

在这里插入图片描述


实体状态详解:


常用的 API

SELECT、DELETE

SELECT

Ø find() :返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。

Ø getReference()

 T find(Class entityClass, Object primaryKey); T find(Class entityClass, Object primaryKey, Map var3); T find(Class entityClass, Object primaryKey, LockModeType var3); T find(Class entityClass, Object primaryKey, LockModeType var3, Map var4); T getReference(Class entityClass, Object primaryKey);// 参数说明:    entityClass// 被查询的实体类类型    primaryKey// 待查找实体的主键值

异同:

  1. 当在数据库中没有找到记录时,find()方法会返回null,

    而getReference() 方法会抛出javax.persistence.EntityNotFoundException异常,

  2. 调用getReference()方法,返回的其实并不是实例对象,而是一个代理。当你要使用实体时,才会真正的调用查询语句来查询实例对象

  3. 另外getReference()方法不保证 entity Bean已被初始化。

  4. 如果传递进getReference()或find()方法的参数不是实体Bean,都会引发 IllegalArgumentException


DELETE

Ø Remove()

void remove(Object entity);
  1. 如果级联关系cascade=CascadeType.ALL,在删除person 时候,也会把级联对象删除。把cascade属性设为cascade=CascadeType.REMOVE 有同样的效果。

    Person person = em.find(Person.class, 2);em.remove (person);
  2. 如果传递进remove ()方法的参数不是实体Bean,会引发一个IllegalArgumentException

    remove()方法不能移除游离对象,只能移除持久化对象

    Order order = new Order();order.setId(140);entityManager.remove(order);

    上面这段代码会抛出异常,因为order是自己创建的对象,也就是游离对象。必须这样写:

    Order order = new Order();order = entityManager.find(Order.class,140);entityManager.remove(order);

    这段代码中的order是从数据库中获取的,也就是持久化对象

    hibernate的delete()方法,只要对象有Id,就可以删除


INSERT、UPDATE

INSERT

Ø persist(): 将临时状态的实体持久化到数据库

void persist(Object entity);

persist方法:使对象由临时状态变为托管状态。进而变为持久化状态,就是执行INSERT操作。

  1. 如果传递进persist()方法的参数不是实体Bean,会引发IllegalArgumentException
  2. 和hibernate的save()方法有些不同:当Entity实体类中设置了主键自动生成时,如果传入对象有id值,则会抛出异常

特殊场景及处理方案:


UPDATE

Ø 当实体正在被容器管理,即托管状态,你可以调用实体的set方法对数据进行修改,在容器决定flush时(这个由Container自行判断),更新的数据才会同步到数据库,而不是在调用了set方法对数据进行修改后马上同步到数据库。如果希望修改后的数据马上同步到数据库,可以调用 EntityManager.flush() 方法。

// 使用示例@tranationalpublicvoid updatePerson() {    Person person = entityManager.find(Person.class, 1);    person.setName("lihuoming");//方法执行完后即可更新数据    entityManager.merge(person);}

Ø Merge

 T merge(T entity);
  1. 传入的对象没有id

    在这种情况下,调用merge方法,将返回一个新的对象(有id),并对这个新的对象执行insert操作。

  2. 传入的对象有id,entityManager的缓存中没有该对象,数据库中没有该记录:

    在这种情况下,调用merge方法,将返回一个新的对象,并对该对象执行insert操作。

    注意:如果Entity的主键设置的是自动生成,则新对象的id并不是原传入对象的id,而是自动生成的(比如自增长的id)。(其实和情况1的结果是一样的)

  3. 传入的对象有id,entityManager的缓存没有该对象,数据库中有该记录

    在这种情况下,调用merge方法,将会从数据库中查询对应的记录,生成新的对象,然后将传入的对象复制到新的对象,最后执行update操作。

    简单来说,就是更新操作。

  4. 传入的对象有id,entityManager的缓存有该对象

    在这种情况下,调用merge方法,JPA会把传入的对象赋值到entityManager的缓存中的对象,然后对entityManager缓存中的对象执行update操作。(和情况3的结果一样)

总结:执行merge时,如果实体ID为空,则进行insert操作。 如果有ID则进行update操作。


flush()、clear()

flush()

将实体的改变立刻刷新到数据库中

当EntityManager对象在一个session bean 中使用时,它是和服务器的事务上下文绑定的。EntityManager 在服务器的事务提交时提交并且同步它的内容。

在一个session bean 中,服务器的事务默认地会在调用堆栈的最后提交(如:方法的返回)。

// 例子1:在方法返回时才提交事务public void updatePerson(Person person) {    try {        Person person = em.find(Person.class, 2);        person.setName("lihuoming");        em.merge(person);        //后面还有众多修改操作    } catch (Exception e) {    e.printStackTrace();    }//更新将会在这个方法的末尾被提交和刷新到数据库中}

默认只在当事务提交时才将改变更新到数据库中,容器将所有数据库操作集中到一个批处理中,这样就减少了代价昂贵的与数据库的交互。

当调用 persist( ),merge( )或 remove( ) 这些方法时,更新并不会立刻同步到数据库中,直到容器决定刷新到数据库中时才会执行,默认情况下,容器决定刷新是在 “相关查询” 执行前或事务提交时发生。

当然 “相关查询” 除 find() 和 getreference() 之外,这两个方法是不会引起容器触发刷新动作的,默认的刷新模式是可以改变的。


如果你需要在事务提交之前将更新刷新到数据库中,你可以直接地调用EntityManager.flush()方法。

ORM框架执行的一些更新数据库的方法,其实质是在更新缓存,只有调用了 flush() 后才会将缓存同步到数据库,即真正执行SQL语句,但是这时并没有真正将数据保存进数据库,需要事务commit后才能全部保存。一般 flush 后立刻就会进行事务的提交。

public void updatePerson(Person person) {    try {        Person person = em.find(Person.class, 2);        person.setName("lihuoming");        em.merge(person);        em.flush();//手动将更新立刻刷新进数据库    //后面还有众多修改操作    } catch (Exception e) {    e.printStackTrace();    }}

clear()

分离所有当前正在被管理的实体

清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。

在处理大量实体的时候,如果你不把已经处理过的实体从EntityManager中分离出来,将会消耗你大量的内存。

调用EntityManager 的clear()方法后,所有正在被管理的实体将会从持久化内容中分离出来。

有一点需要说明下,在事务没有提交前(事务默认在调用堆栈的最后提交,如:方法的返回),如果调用clear()方法,之前对实体所作的任何改变将会丢失,所以建议在调用clear()方法之前先调用flush()方法保存更改。


JcreateQuery() – PQL

创建查询对象

除了使用 find() 或 getReference() 方法来获得Entity Bean之外,你还可以通过 JPQL 得到实体Bean。要执行 JPQL 语句,你必须通过 EntityManager 的 createQuery() 或 createNamedQuery() 方法创建一个Query 对象。

注:JPQL 没有插入语句。即不能执行insert语句。


Ø getResultList()

Query query = em.createQuery("select p from Person p where p.name=’黎明’");List result = query.getResultList();Iterator iterator = result.iterator();while( iterator.hasNext() ){//处理Person}

Ø getSingleResult()

返回查询的第一条数据,可以进行强转,如:

// 查询数目:Query query = em.createQuery("select count(1) from Person p");Long num = (Long)query. getSingleResult ();// 强转为实体:Query query = em.createQuery("select p from Person p");User user = (User)query. getSingleResult ();

Ø executeUpdate()

// 执行更新和删除操作,返回受影响的记录数。Query query = em.createQuery("delete from Person");int result =query.executeUpdate(); //影响的记录数

Ø 关于 **JPQL 和SQL **中参数的问题:

  1.  使用标识符
Query query =em.createQuery("delete from Person p where p.name := name");query.setParameter("name","张三");int result =query.executeUpdate(); //影响的记录数
  1.  使用索引下标
Query query =em.createQuery("delete from Person p where p.id = ?1 ");query.setParameter(1, "张三");int result =query.executeUpdate(); //影响的记录数  

createNaiveQuery() – SQL

用法基本同createQuery(),只不过这里使用的不是 JPQL 而是SQL

Ø 将查询到的数据映射成实体:

Query query = em.createNativeQuery("select * from person", Person.class);List result = query.getResultList();if (result != null){Iterator iterator = result.iterator();while( iterator.hasNext() ){Person person= (Person)iterator.next();…     }}

refresh()

如果怀疑当前被管理的实体已经不是数据库中最新的数据,则可以通过 refresh() 方法刷新实体,容器会把数据库中的新值重写进实体。这种情况一般发生在获取了实体之后,有人更新了数据库中的记录,这时需要得到最新的数据。当然再次调用 find() 或 getReference() 方法也可以得到最新数据,但这种做法并不优雅。

User user = em.find(User.class, 1);//第二次同样的查询不会访问数据库user = em.find(User.class, 1);// 运行以上代码,发现调用了两次find,但是只执行了一次select语句,这是缓存导致的。// 执行refresh()方法刷新缓存,容器会把数据库中的新值重写进实体。User user = em.find(User.class, 1);em.refresh(user);

其他方法

contains()

判断实体是否还在EntityManage的管理下,或者说是否属于当前持久上下文环境。

contains() 方法使用一个实体作为参数,如果这个实体对象当前正被持久化内容管理,返回值为true,否则为false。

如果传递的参数不是实体 Bean,将会引发一个IllegalArgumentException.

User user = em.find(User.class, 1);if (em.contains(user)){//正在被持久化内容管理}else{//已经不受持久化内容管理}

getFlushMode ():

获取持久上下文环境的Flush模式。返回FlushModeType类的枚举值。


setFlushMode()

改变实体管理器的Flush模式

setFlushMode()的Flush模式有2种类型:AUTO 和 COMMIT。AUTO为默认模式。

// 改变实体管理器的Flush模式em.setFlushMode(FlushModeType.COMMIT);

其实上面两种模式最终反映的结果是:JDBC 驱动跟数据库交互的次数。

JDBC 性能最大的增进是减少JDBC 驱动与数据库之间的网络通讯。

FlushModeType.COMMIT 模式使更新只在一次的网络交互中完成,而 FlushModeType.AUTO 模式可能需要多次交互(触发了多少次Flush 就产生了多少次网络交互)


isOpen()

判断当前的实体管理器是否是打开状态


close()

关闭实体管理器。

之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛出 IllegalstateException 异常,除了 getTransaction 和 isOpen方法(返回 false)。

不过,当与实体管理器关联的事务处于活动状态时,调用 close() 方法后持久上下文将仍处于被管理状态,直到事务完成。


getTransaction()

返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务


EntityTransaction 接口用来管理资源层实体管理器的事务操作

通过调用实体管理器的getTransaction方法 获得其实例。

① begin ()

用于启动一个事务,此后的多个数据库操作将作为整体被提交或撤消。若这时事务已启动则会抛出 IllegalStateException 异常。

② commit ()

用于提交当前事务。即将事务启动以后的所有数据库更新操作持久化至数据库中。

③ rollback ()

撤消(回滚)当前事务。即撤消事务启动后的所有数据库更新操作,从而不对数据库产生影响。

④ setRollbackOnly ()

使当前事务只能被撤消。

⑤ getRollbackOnly ()

查看当前事务是否设置了只能撤消标志。

⑥ isActive ()

查看当前事务是否是活动的。如果返回true则不能调用begin方法,否则将抛出 IllegalStateException 异常;如果返回 false 则不能调用 commit、rollback、setRollbackOnly 及 getRollbackOnly 方法,否则将抛出 IllegalStateException 异常。

需要注意的是:

// 在JPA里面,先需要 getTransaction,再 begin EntityTransaction transaction = entityManager.getTransaction();transaction.begin();// 在 hibernate 里面呢,直接 begin,然后进行 commit EntityTransaction transaction = session.beginTransaction();transaction.commit();

JPA 调用存储过程

参考:https://www.cnblogs.com/zhuang229/p/12227938.html

定义存储过程及调用方式

定义一个简单的存储过程

传入一个int参数,返回这个参数+1

CREATE DEFINER=`root`@`localhost` PROCEDURE `plus1inout`(IN ARG INT, OUT res INT)BEGIN     SET res = ARG + 1;END

注意:

  1. IN参数个数没有限制。

  2. 如果out参数类型为sys_refcursor,那么最好只定义这 一个out参数(JPA API限制);

    sys_refcursor 类型的 out 参数,在 JPA 中统一注册为 Void.class 类型,参数模式定义为 ParameterMode.REF_CURSOR;

    使用getResultList()方法获取游标fetch到的多行数据,返回结果为List,一个Object[]对应一行数据。

  3. 如果使用Oracle 存储包,只需在定义存储过程名字时加个对应的package名前缀即可(例如:包名.存储过程名)。


JPA调用存储过程的两种方式


实体类(使用注解声明存储过程)

@NamedStoredProcedureQuery注解(解析详见注解目录)

import lombok.Data;import javax.persistence.*;@Data@Entity// 存储过程使用了注解@NamedStoredProcedureQuery,并绑定到一个随意一个JPA表@NamedStoredProcedureQueries({        @NamedStoredProcedureQuery(            name = "User.plus1",             procedureName = "plus1inout",             parameters = {                @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),                @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class)             }),        @NamedStoredProcedureQuery(            name = "User.mytest",             procedureName = "mytest") })class user{    @Id    private Integer id;    private String name;    private String age;}

EntityManager直接调用存储过程

@Autowiredprivate EntityManager em;@Testpublic void test01(){    StoredProcedureQuery query = em        .createStoredProcedureQuery("plus1inout")// 创建StoredProcedureQuery对象,传入被调用的存储过程名称        .registerStoredProcedureParameter("ARG", Integer.class, ParameterMode.IN)// 注册参数        .registerStoredProcedureParameter("res", Integer.class, ParameterMode.OUT)        .setParameter("ARG", 20);// 传参    query.execute();// 执行存储过程调用    String result = query.getOutputParameterValue("res").toString();// 获取存储过程中的返回值    System.out.println(result);}

调用基于实体类注解的存储过程

使用EntityManager调用基于Entity实体类注解的存储过程。实体类详见 JPA调用存储过程

@Autowiredprivate EntityManager em;@Testpublic void test02(){    StoredProcedureQuery query = em        // 创建NamedStoredProcedureQuery对象,传入实体类上@NamedStoredProcedureQuery注解中name的值        .createNamedStoredProcedureQuery("proKQAttendanceRecord")                // IN模式的参数可以在实体类上注解,此处相应注释掉        // .registerStoredProcedureParameter("PRM_ID", Integer.class, ParameterMode.IN)                // 使用EntityManager调用基于Entity实体类注解的存储过程时,OUT或INOUT模式的参数,必须要在此处注册,并删掉原实体类上相应的参数注解。不然运行报错。        .registerStoredProcedureParameter("PRM_APPCODE", Integer.class, ParameterMode.OUT)        .registerStoredProcedureParameter("PRM_ERRMSGE", Integer.class, ParameterMode.OUT)        .setParameter("PRM_ID", attId);// 传参    query.execute();    List resultList = query.getResultList();    Object code = query.getOutputParameterValue("PRM_APPCODE");    Object msg = query.getOutputParameterValue("PRM_ERRMSGE");}

来源地址:https://blog.csdn.net/footless_bird/article/details/129294444

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯