Java Persistence API)即Java持久化API,简称JPA,是一种ORM规范,JPA仅定义接口规范,实现这一规范的框架有Hibernate等。
Spring Data Jpa是对基于JPA的数据访问层的增强支持,底层使用Hibernate框架,支持使用原生SQL或JPQL查询语言。
使用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>
另一方面,虽然Mybatis有Mybatis-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外,也还有Or、Equals(=)、Between(<)、In、NotIn(not in)等等。
综上,Spring Data Jpa与Mybatis各有各的优势,在Mybatis插上Mybatis-Plus的翅膀后,选择Mybatis还是Spring Data Jpa整体开发效率与性能上并没有显著的差距。至于如何选择这两款ORM框架,个人认为可凭喜好选择,只要满足需求场景。
我个人更喜欢在分布式微服务项目中使用Spring Data Jpa,特别是使用领域驱动设计架构设计的项目,而在管理后台项目使用Mybatis。
因为管理后台需要更灵活的查询支持,经常写些复杂的SQL,在这方面Jpa显得较弱势,而分布式微服务项目实现业务的核心逻辑,只需要用到简单的数据查询、删增改,因此较适合使用Jpa。