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
。