java数据库持久层框架基础:为什么不是JPA?

mybatis和JPA

    关于java持久层框架的故事的起源,还是要归结到java语言JDBC(Java Database Connectivity)设计上的不够成熟。jdbc底层使用sql操作关系型数据库,这种方式本身和java“一切皆对象”的设计理念格格不入。如果书写的sql语句和某特定类型的数据库强相关,也会导致应用的移植性下降。
    这个时候JPA和Mybatis应运而生。
    严格来讲,JPA(这里并不是spring data jpa)和Mybatis并不能作为ORM框架放在一起讨论。JPA全称是Java Persistence API,是sun公司定义的一套 java ORM框架标准,但是流氓的是sun公司自己并没有实现这套标准。目前大家比较熟知的JBoss公司维护的Hibernate和spring团队维护的基于Hibernate的spring data JPA,就是基于JPA标准的实现。
    Herbernate和spirng data JPA是全自动的ORM框架,可以自动生成sql语句,从而实现应用层和数据库持久层的隔离。而Mybatis是一种半自动的ORM框架,虽然目前可以使用Mybatis plus,极大地简化了开发,但有时候还是需要开发者自己手写sql。
    关于JPA和Mybatis之争,可以参考
2018年JVM生态报告中关于ORM框架使用占比的数据。
java数据库持久层框架基础:为什么不是JPA?-orm框架.png
可以看到,Hibernate使用占比高达54%,而Mybatis的占比仅有6%。这似乎和我们的直观感觉有比较大的差异,因为我们周围使用Mybatis是多数,而Hibernate有比较高的学习成本,使用的并不多。至于基于Hibernate的spring data JPA,虽然有spring官方为其背书,近些年发展迅猛,但仍然不是主流。所以为什么会有这种反直觉的结果呢?网上也有基于google搜索指数的分析。
java数据库持久层框架基础:为什么不是JPA?-D-Chat_20200524202848.png
从这张图可以直观发现,使用mybatis的主力主要集中在中国大陆、韩国和日本,而除此之外的绝大多数地区,都是Hibernate的天下。
    Mybatis在中国、韩国和日本流行的确切原因无法得知,知乎上很多人对这个现象有自己的猜测,当然也顺带隐晦的diss了Hibernat e/JPA。这种中间有多少“屁股决定脑袋”的原因,我无法知晓,但我猜测如果在国外的论坛有类似的帖子,Mybatis也逃不掉被diss的宿命。实际上,一种技术方案的产生必然是为了解决某种场景下的问题,所以都有一定存在的价值。如果脱离场景去讨论技术的优劣是无聊的争执,根本没了解一种技术的设计方案,就去判断一个技术的好坏多少显得有些愚蠢。
    由于Mybatis国内广泛的使用,且Mybatis的使用门槛也不高,这里过多讨论Mybatis的设计并没有太大的意义。而JPA标准是Hibernate、spring data jpa等ORM框架的基础,对其有深入的理解就能明白全自动化的ORM框架的设计理念,或许能够对大家以后的技术框架选型有帮助。

JPA主要思想

实体

    JPA的主要思想使用java普通类对象(这里被称为实体,Entity)映射数据库中表中的一条记录,实体类的属性定义对应表相应的字段结构,框架能够根据这种映射实现实体对象持久化到数据库表中。已经持久化到数据库表中或者已经和数据库表的记录建立关联的实体被称为托管(Manage)对象(Herbernate中被称为持久状态,Persistent),处于托管状态的实体对象的所有改动都会影响数据库中对应的记录。为了清晰的区分实体是否生效(这里是指是否能够影响数据库记录),加上托管状态,JPA标准一共定义了四种状态:

  1. 瞬时(New):刚new出来的实体对象,无Id,还未和数据库表记录建立关联
  2. 托管(Manage):已经和数据库记录建立关联,所有改动都会影响数据库记录,但还未提交事务生效。
  3. 游离(Datached):有Id值,但没有和数据表记录建立关联,这里主要是和事务相关。
  4. 删除(Removed):有Id值,但是尚未和数据表记录建立关联。这里主要也是和事务相关。
    java数据库持久层框架基础:为什么不是JPA?-entity-state.png

实体管理

    JPA定义了实体,同时也确定了实体类的状态,剩下的是需要对实体进行管理,这就是JPA EntityManager的工作。EntityManager除了管理实体之外,还用来管理事务操作。综合EntityManager的实体操作和事务操作,能够对实体的状态变化有更好的理解。
    一般的JPA数据库CRUD操作包括三步:

  1. 使用EntityManager开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
  1. 获取实体并进行持久化操作
//这里示例是创建的Movie Entity
Movie movie = new Movie();
movie.setLength(120);
movie.setName("gone with wind");
//持久化persist操作
entityManager.persist(movie);
  1. 提交事务
transaction.commit();

这里new一个实体,比如上例中的new Movie(),这时这个实体的状态就是瞬时状态。当进行persist操作之后,此时实体状态属于托管状态。当事务提交之后,此时实体已经被更新到数据库表中,实体状态变为游离状态。从另一个方面,也就是说任何实体到数据库的持久化操作,都需要提交事务,这也是为什么在使用JPA时,数据库写入或更新的方法需要@Transactional的原因。
    其他类似的实体状态变化,可以参考上面的实体状态变化图。
    EntityManager底层屏蔽了数据库的CRUD sql操作,这样在使用JPA时,应用层和持久层是隔离的,即使更换了不同类型的数据库,只需要JPA底层兼容sql即可。除了persist、merge等EntityManager操作数据库的方法,spring data jpa还实现了使用Repository接口的方式。Repository接口的方法底层还是使用EntityManager,特色在于用方法名来识别各种查询参数,听起来就非常笨拙。不过为了灵活性,EntityManager支持JPQL,JPQL是有着类似sql的语法,但是使用实体类代替数据库表的结构来操作数据库。EntityManager甚至支持原生sql操作,但是并不建议这样使用。
    EntityMananger内部封装了数据库的连接操作,每个EntityManager都对应一个特定数据库的连接池。这意味着如果需要实现分库分表的数据库访问,需要配置不同的EntityManager。
    不同的EntityManager的管理由EntityManagerFactory实现,entityManagerFactory.createEntityManager()可以配置和创建一个EntityManager。
java数据库持久层框架基础:为什么不是JPA?-jpa structure.png

JPA设计思想的问题

    JPA 实体映射的思想很难合理地解决关系型数据库联表查询的问题。如果只是单个表的操作,只需要对应一个实体类,JPA的操作数据库可以说是非常简单。但是多个表的联合查询,意味着各个实体类之间需要实现属性的对应映射关系,这不是简单的继承或者组合能够实现的。这个难点在JPA设计范畴里仍然没有比较好的解决方案,不过querydsl联合JPA标准可以很简便的解决这个问题。

//querydsl解决联表问题的模板用法
 jpaQueryFactory.select().join().where();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章