Java Persistence API)即Java持久化API,简称JPA,是一种ORM规范,JPA仅定义接口规范,实现这一规范的框架有Hibernate等。

Spring Data Jpa是对基于JPA的数据访问层的增强支持,底层使用Hibernate框架,支持使用原生SQLJPQL查询语言。

使用Spring Data Jpa仅需要定义接口,并继承JpaRepository接口,不需要编写实现类,也不需要编写XML映射文件。Spring Data Jpa默认提供简单的CRUD方法,并支持自动根据方法名生成SQL,提供注解方式动态生成SQL,也支持分页、排序。

JPQL查询语言是一种通过面向对象而非面向数据库的查询语言,它能让我们忘记表名、忘记列名,例如:

@Repository
public interface ProductDao extends JpaRepository<ProductPO, Long> {
   @Query(value = "select p from ProductPO p where p.id in :ids")
   List<ProductPO> findAllProductById(@Param("ids") Set<Long> productIds);
}

虽然Mybatis也提供注解方式实现sql拼接,但对注解的支持并没有Jpa的支持好。例如遇到简单的in查询时,使用Mybatis实现要比Jpa麻烦得多。

使用注解完全替换XML的写法如下。

public interface ProductMapper {
   @Select({
           "<script>",
               "select * from product ",
               "where ID in ",
                   "<foreach collection='ids' item='id' open='(' separator=',' close=')'>",
                      "#{id}",
                   "</foreach>",
           "</script>"
   })

   List<ProductPO> findAllProductById(@Param("ids") Set<Long> productIds);
}

对比两种实现,Mybatis还是显得繁琐。

Mybatis 3.5.x版本提供了另一种替代XML的实现,代码如下。

public interface ProductMapper {
   
   @SelectProvider(SelectProductSqlProvider.class)
   @ResultType(ProductPO.class)
   List<ProductPO> findAllProductById(@Param("ids") Set<Long> productIds);

   class SelectProductSqlProvider implements ProviderMethodResolver {
       public static String findAllProductById(Set<Long> productIds) {
           return new SQL() {{
               SELECT("*");
               FROM("product");
               WHERE("ID in (" + productIds.stream().map(String::valueOf).collect(Collectors.joining(",")) + ")");
               LIMIT(1);
           }}.toString();
       }
   }
}

其中@SelectProvider注解用于指定生成SELECT语句的生成器类型,要求实现ProviderMethodResolver接口,并在生成器中实现一个与Mapper方法名称参数相同的且返回值类型为String的静态方法。

@SelectProvider外,还有对应Insert操作的@InsertProvider、对应Update操作的@UpdateProvider以及对应Delete操作的@DeleteProvider

从以上Mybatis的两种实现来看,Mybatis完败,当然了,这也只是某方面而已。

我们再来看Spring Data Jpa在条件判断语句上的支持,Spring Data Jpa支持if条件语句,使用如下。

@Repository
public interface ResourceDao extends JpaRepository<ResourcePO, Long> {

   @Query(nativeQuery = true,
           value = "select r.* from resource r " +
                   "where r.parent_id=?1 and if(?2!=null,r.level=?2,1=1)")

   List<ResourcePO> findAllByParentIdAndLevel(Long parentId, Integer level);
}

Spring Data Jpa默认使用JPQL,给@Query注解配置nativeQuery=true即可使用原生SQL。在本例中,?1代表第一次参数,?2代表第二个参数,if语句实现当level不为空时,拼接r.level=?2,否则拼接1=1

Spring Data Jpa不支持嵌套,这也是Jpa弱势的地方,对比Mybatis就是小儿科,而且Mybatis支持choose-when-otherwise,也就是if-else

<choose>
 <when test="xx != null">
 </when>
 <otherwise>
 </otherwise>
</choose>

另一方面,虽然MybatisMybatis-Plus的助力,但在简单SQL的支持上远没有Jpa更方便。例如多个字段组合查询Jpa可以省略SQL,而只需要声明方法,代码如下。

@Repository
public interface ResourceDao extends JpaRepository<ResourcePO, Long> {
ResourcePO findByXxxAndYyy(Long xxx, Integer yyy);
}

findByXxxAndYyy自动生成的sql等于select * from resource where xxx=#{xxx} and yyy=#{yyy}

当然,你还可以继续拼接条件,如findByXxxAndYyyAndZzz,那么生成的sql就等于select * from resource where xxx=#{xxx} and yyy=#{yyy} and zzz=#{zzz}

And外,也还有OrEquals(=)、Between(<)、InNotIn(not in)等等。

综上,Spring Data JpaMybatis各有各的优势,在Mybatis插上Mybatis-Plus的翅膀后,选择Mybatis还是Spring Data Jpa整体开发效率与性能上并没有显著的差距。至于如何选择这两款ORM框架,个人认为可凭喜好选择,只要满足需求场景。

我个人更喜欢在分布式微服务项目中使用Spring Data Jpa,特别是使用领域驱动设计架构设计的项目,而在管理后台项目使用Mybatis

因为管理后台需要更灵活的查询支持,经常写些复杂的SQL,在这方面Jpa显得较弱势,而分布式微服务项目实现业务的核心逻辑,只需要用到简单的数据查询、删增改,因此较适合使用Jpa

https://mp.weixin.qq.com/s/XxSCN91SJYu5jQNZNe1BGw