JPA&Hibernate---SessionFactory和EntityManagerFactory之间的区别

阅读 247
收藏 10
2017-11-17
原文链接:zhuanlan.zhihu.com
在谈Spring Data之前,要先讲讲JPA,讲JPA,又不免会说到Hibernate,那就从Hibernate开始说起吧。
因为很多人都喜欢把JPA和Hibernate混为一谈,这个系列文章会把这个问题讲明白。
有两种方法来处理Hibernate中的持久性:会话(session)和实体管理器。通过这篇文章,我们将分析这两种机制的区别。

JPA是一个标准

实体管理器是JPA规范的一部分,而Hibernate是基于Session对象来实现自己的解决方案,也就是处理持久性。我们已经看到他们中的一个(JPA)是一个标准。我们需要记住的是,JPA是“独立出来的”API,它是一个标准,它描述了如何以标准化的方式处理对象持久化。它可以有多个实现。因此,如果你的应用程序基于JPA的实现,则可以随时在不同的实现之间切换。但对于Hibernate来说却不是这样,它可以但不一定与其他持久性解决方案兼容。

Hibernate可以在JPA中使用

下一个区别是用于管理持久性的类。在JPA中,我们查找EntityManagerFactory,EntityManager,可以发现它们都位于javax.persistence包中。Hibernate使用它自己的类来表示持久性上下文:SessionFactorySession。由于JPA所在包(hibernate-jpa-2.1-api中的javax.persistence包)定义的基本都是接口,所以他们的实现可以是不同的(也就说所也可以是基于Hibernate来进行实现的)。

在使用Hibernate作为JPA实现的情况下,我们可以使用一些Hibernate所特有的功能。实际上,Hibernate的EntityManager实现调用了Session对象。我们可以从一些异常日志中观测到它,例如在关于Hibernate/JPA中的锁(下一篇文章,到时再补链接)的这篇文章中,我们可以看到:

javax.persistence.RollbackException: Error while committing the transaction
  at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:92)
  at com.sandboxWebapp.hibernate.locking.LockingSample.pessimisticReadWithWrite(LockingSample.java:117)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
  at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
  at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264)
  at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
  at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208)
  at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:159)
  at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:87)
  at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153)
  at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95)
Caused by: javax.persistence.LockTimeoutException: could not execute statement
  at org.hibernate.ejb.AbstractEntityManagerImpl.wrapLockException(AbstractEntityManagerImpl.java:1440)
  at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1339)
  at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
  at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:80)
  ... 29 more
Caused by: org.hibernate.exception.LockTimeoutException: could not execute statement
  at org.hibernate.dialect.MySQLDialect$1.convert(MySQLDialect.java:408)
  at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
  at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:136)
  at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:58)
  at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3238)
  at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3140)
  at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3470)
  at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:140)
  at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:393)
  at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:385)
  at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:302)
  at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:339)
  at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
  //请看此处
  at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1240)
  at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)
  at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
  at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
  at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:75)
  ... 29 more
Caused by: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
  at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)
  at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
  at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
  at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
  at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)
  at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)
  at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)
  at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)
  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2019)
  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1937)
  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1922)
  at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:133)
  ... 44 more

这个异常代表一个锁超时,并使用Hibernate的会话(Session)来管理持久性。我们可以在下面的输出片段中观察它:

at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1240)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)

想要从Hibernate的EntityManager实现中获取Session,通过下面简单的调用就能拿到了:

Session session = entityManager.unwrap(Session.class);

通过log日志记录这个session对象应该返回以下输出:

Hibernate's session is :SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] orphanRemovals=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] collectionQueuedOps=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])

Hibernate和JPA方法比较

这两者不仅只有以上的差异。有一些方法双方都有,但是名称不同。我们首先通过标识符获取一个实体。Hibernate的Session使用一个称为get的方法,而JPA所使用的方法称为find。(源码 就不截取了,请自行建立环境去验证的),本文所使用Hibernate版本为:

<dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-core</artifactId>
          <version>5.2.10.Final</version>
      </dependency>

为了从持久化上下文中分离出一个实体(也就是我们通常说的游离态),Hibernate使用evict方法。JPA与更通用的函数命名:detach(分离)。两个方案都有通过persist方法将对象附加到持久化上下文

两者都可以用refresh方法刷新 实体(entity)的状态。Hibernate和JPA有另一个相似之处。他们可以通过调用 clear()方法 来清除持久化上下文。

调用这个clear()方法会导致其中所有的实体分离(也就是游离化)。关于Session和EntityManager方法的区别,我们应该注意到Session有更多的方法来分析它的内部状态。

关于内部状态,它们有一个通用方法称为isOpen,并允许检查SessionEntityManager是否处于打开状态。此外,Hibernate通过Session,我们可以检查它是否连接(isConnected),是否包含脏(损坏的)数据(isDirty),或者判断所处理对象(实体或代理)是否处于只读模式(isReadOnly)。

关于对象状态的例子就不再次累述了,请参考http://blog.csdn.net/u014087286/article/details/47039349博文所述。

NOTE:Hibernate save()与persist()区别

Hibernate 之所以提供与save()功能几乎完全类似的persist()方法,一方面是为了照顾JPA的用法习惯。另一方面,save()和 persist()方法还有一个区别:使用 save() 方法保存持久化对象时,该方法返回该持久化对象的标识属性值(即对应记录的主键值);

但使用 persist() 方法来保存持久化对象时,该方法没有任何返回值。因为 save() 方法需要立即返回持久化对象的标识属性,所以程序执行 save() 会立即将持久化对象对应的数据插入数据库;而 persist() 则保证当它在一个事物外部被调用时,并不立即转换成 insert 语句, 这个功能是很有用的,尤其当我们封装一个长会话流程的时候,persist() 方法就显得尤为重要了。

主要内容区别:
1,persist把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时候。

2,save, 把一个瞬态的实例持久化标识符,及时的产生,它要返回标识符,所以它会立即执行Sql insert

同样,关于查询对象,两者也是有名字不同但作用相同的方法。JPA 通过调用getSingleResult获取单行和通过getResultList得到一个结果列表

Hibernate分别使uniqueResultlist方法来达到相同效果。在Hibernate5.2之前还有一些额外的方法来指定查询参数。通过它提供的setters,我们可以设置一个BigInteger,BigDecimal,二进制,布尔,字节,字符串或日期。

在Hibernate5.2之后,和JPA规范进行统一化,统一调用setParameter这个方法来达到相应目的。

这篇简短的文章描述了JPA和Hibernate的Session持久化机制之间的差异和相似之处。两者都被用来做同样的事情,将Java对象持久化到数据库中去。

他们分别通过EntityManager(JPA)和Session(Hibernate)对象管理持久化上下文(persistence context)来实现它。但他们在工作过程中也有一些相似之处。

两者都可以通过persist来持久化实体和通过clear方法从持久化上下文分离实体(使之 游离化)。一般来说,更抽象和标准化的解决方案对于应用程序的可移植性来说更好。

而使用Hibernate,我们不能轻易地将其转移到另一个持久性框架中。通过在Hibernate中使用JPA的实现(不使用Hibernate特有的功能),可以更容易实现代码的可移植性。

原文:JPA&Hibernate---SessionFactory和EntityManagerFactory之间的区别
评论