Mybatis与Spring Data Jpa怎么选?

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

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

原文始发于微信公众号(Java艺术):Mybatis与Spring Data Jpa怎么选?