0


Spring Data JPA 与 MyBatisPlus的比较

1 前言

JPA(Java Persistence API)和MyBatis Plus是两种不同的持久化框架,它们具有不同的特点和适用场景。

JPA是Java官方的持久化规范,它提供了一种基于对象的编程模型,可以通过注解或XML配置来实现对象与数据库的映射关系。JPA的优点是可以对数据库进行更高级的操作,如查询、更新、删除等,同时也支持事务管理和缓存机制,能够更好地支持复杂的业务逻辑。

MyBatis Plus (MPP) 是在MyBatis基础上进行封装的增强版本,它提供了更简单易用的API和更高效的性能。MyBatis Plus通过XML或注解的方式来配置数据库映射关系,并提供了丰富的查询、更新、删除操作的方法。相对于JPA,MyBatis Plus配置简单、易于上手,同时也灵活性较高,能够更好地满足项目的特定需求。

如果只是针对单表的增删改查,两者十分相似,本质上都算ORM框架,那么到底什么时候适合用JPA,什么时候用MyBatisPlus,下面做下这两者的详细对比。

2 POM依赖

  • JPA
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
  • MPP
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency>

3 Entity定义

  • JPA
importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.Id;importjavax.persistence.Table;importjavax.persistence.GeneratedValue;@Entity@Table(name ="dept")publicclassDept{@Id@Column(name ="id")@GeneratedValue(strategy =GenerationType.AUTO)privateLong id;@Column(name ="code")privateString code;@Column(name ="name")privateString name;}
  • MPP
importcom.baomidou.mybatisplus.annotation.TableField;importcom.baomidou.mybatisplus.annotation.TableId;@TableName(value ="dept")publicclassDept{@TableId(value ="id", type =IdType.AUTO)privateLong id;@TableField(value ="code")privateString code;@TableField(value ="name")privateString name;}

4 DAO基类

  • JPA
importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.stereotype.Repository;@RepositorypublicinterfaceDeptRepositoryextendsJpaRepository<Dept,Long>{}
  • MPP
importorg.apache.ibatis.annotations.Mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;@MapperpublicinterfaceDeptMapperextendsBaseMapper<Dept>{}

4.1 基类主要方法

方法JpaRepositoryMPP BaseMapper插入一条记录save(T entity)insert(T entity)插入多条记录saveAll(Iterable<T> entities)insertBatchSomeColumn(List<T> entityList)根据 ID 删除deleteById(ID id)deleteById(Serializable id)根据实体(ID)删除delete(T entity)deleteById(T entity)根据条件删除记录-delete(Wrapper<T> queryWrapper)删除(根据ID或实体 批量删除)deleteAllById(Iterable<? extends ID> ids)deleteBatchIds(Collection<?> idList)根据 ID 修改save(T entity)updateById(T entity)根据条件更新记录-update(Wrapper<T> updateWrapper)根据 ID 查询findById(ID id)selectById(Serializable id)查询(根据ID 批量查询)findAllById(Iterable<ID> ids)selectBatchIds(Collection<? extends Serializable> idList)根据条件查询一条记录-selectOne(Wrapper<T> queryWrapper)根据条件判断是否存在记录exists(Example<T> example)exists(Wrapper<T> queryWrapper)根据条件查询总记录数count(Example<T> example)selectCount(Wrapper<T> queryWrapper)根据条件查询全部记录findAll(Example<T> example, Sort sort)selectList(Wrapper<T> queryWrapper)根据条件查询分页记录findAll(Example<T> example, Pageable pageable)selectPage(P page, Wrapper<T> queryWrapper)

4.2 Example、Specification VS Wrapper

JPA使用Example和Specification 类来实现范本数据的查询,而MPP使用QueryWrapper来设置查询条件

4.2.1 JPA Example

Dept dept =newDept();
dept.setCode("100");
dept.setName("Dept1");// select * from dept where code = '100' and name = 'Dept1';List<Dept> deptList = deptRepository.findAll(Example.of(dept));

默认是生成的条件都是 “=”,如果要设置其他比较符,需要使用ExampleMatcher

Dept dept =newDept();
dept.setCode("100");
dept.setName("Dept1");// select * from dept where code like '100%' and name like '%Dept1%';List<Dept> deptList = deptRepository.findAll(Example.of(dept,ExampleMatcher.matching().withMatcher("code",ExampleMatcher.GenericPropertyMatchers.startsWith()).withMatcher("name",ExampleMatcher.GenericPropertyMatchers.contains())));

4.2.2 JPA Specification

Example仅能实现对字符串类型的匹配模式,如果要设置其他类型的字段,可以实现JpaSpecificationExecutor接口来完成:

importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.data.jpa.repository.JpaSpecificationExecutor;importorg.springframework.stereotype.Repository;@RepositorypublicinterfaceDeptRepositoryextendsJpaRepository<Dept,Long>,JpaSpecificationExecutor<Dept>{}

增加以上接口后,会增加以下查询方法:

  • findOne(Specification spec)
  • findAll(Specification spec)
  • findAll(Specification spec, Pageable pageable)
  • count(Specification spec)
  • exists(Specification spec)

使用示例:

Dept dept =newDept();
dept.setCode("100");
dept.setName("Dept1");// select * from dept where code like '100%' and name like '%Dept1%';Specification<Dept> spec =newSpecification<Dept>(){@OverridepublicPredicatetoPredicate(Root<Dept> root,CriteriaQuery<?> query,CriteriaBuilder cb){List<Predicate> predicates =newArrayList<>();
       predicates.add(cb.like(root.get("code"), dept.getCode()+"%"));
       predicates.add(cb.like(root.get("code"),'%'+ dept.getCode()+"%"));return query.where(predicates.toArray(newPredicate[predicates.size()])).getRestriction();}};List<Dept> deptList = deptRepository.findAll(Example.of(dept));

除了

equal

notEqual

, 针对日期、数字类型,还有

gt

ge

lt

le

等常用比较符。

4.2.3 MPP Wrpper

MPP Wrapper类似于JPA的CriteriaBuilder,不过用法上更加便捷:

Dept dept =newDept();
dept.setCode("100");
dept.setName("Dept1");// select * from dept where code = '100' and name = 'Dept';Wrapper<Dept> wrapper =Wrappers.lambdaQueryWrapper(detp);List<Dept> deptList = deptRepository.selectList(wrapper);

默认是生成的条件都是 “=”,如果要设置其他比较符,需要单独设置Wrapper:

Dept dept =newDept();
dept.setCode("100");
dept.setName("Dept1");// select * from dept where code like '100%' and name like '%Dept1%';Wrapper<Dept> wrapper =Wrappers.<Dept>lambdaQueryWrapper().likeRight(Dept::getCode, dept.getCode).like(Dept::getName, dept.getName);List<Dept> deptList = deptRepository.selectList(wrapper);

4.2.4 JPA Specification 与 MPP Wrpper的方法汇总

方法JPA SpecificationMPP Wrpper等于 =equaleq不等于 <>notEqualne大于 >greaterThan, gtgt大于等于 >=greaterThanOrEqualTo, gege小于 <lessThan, ltlt小于等于 <=lessThanOrEqualTo, leleBETWEEN 值1 AND 值2betweenbetweenNOT BETWEEN 值1 AND 值2-notBetweenLIKE ‘%值%’likelikeNOT LIKE ‘%值%’notLikenotLikeLIKE ‘%值’likelikeLeftLIKE ‘值%’likelikeRightNOT LIKE ‘%值’notLikenotLikeLeftNOT LIKE ‘值%’notLikenotLikeRight字段 IS NULLisNullisNull字段 IS NOT NULLisNotNullisNotNull字段 = trueisTrue-字段 = falseisFalse-字段 IN (v0, v1, …)inin字段 NOT IN (v0, v1, …)-notIn排序:ORDER BY 字段, … ASCascorderByAsc排序:ORDER BY 字段, … DESCdescorderByDesc排序:ORDER BY 字段, …orderBy(CriteriaQuery)orderBy拼接 ORororAND 嵌套andand正常嵌套 不带 AND 或者 OR-nested拼接 sql-apply无视优化规则直接拼接到 sql 的最后-last拼接 EXISTS ( sql语句 )existsexists拼接 NOT EXISTS ( sql语句 )-notExists去重distinct(CriteriaQuery)-设置查询字段select, multiselect(CriteriaQuery)select分组:GROUP BY 字段, …groupBy(CriteriaQuery)groupBySQL SET 字段-set设置 SET 部分 SQL-setSql字段自增变量 val 值-setIncrBy字段自减变量 val 值-setDecrBy条件判断selectCase-平均值avg-加和sum, sumAsLong, sumAsDouble-计数count, countDistinct-最大值max, greatest-最小值min, least-取反neg-绝对值abs-Productprod-差值diff-求商quot-取模mod-开根号sqrt-转换类型toLong, toInteger, toFloat, toDouble, toBigDecimal, toBigInteger, toString-集合是否为空isEmpty, isNotEmpty-集合大小size-是否包含isMember, isNotMember-键值对keys, values-字符串拼接concat-字符串分隔substring-去空白trim-大小写转换upper, lower-字符串长度length-空处理nullif, coalesce-

5 DAO子类

5.1 JPA Repository方法命名规范

JPA支持接口规范方法名查询,一般查询方法以 find、findBy、read、readBy、get、getBy为前缀,JPA在进行方法解析的时候会把前缀取掉,然后对剩下部分进行解析。例如:

@RepositorypublicinterfaceDeptRepositoryextendsJpaRepository<Dept,Long>{// 调用此方法时,会自动生成 where code = ? 的条件DeptgetByCode(String code);}

常用的方法命名有:
关键字方法命名sql条件DistinctfindDistinctByLastnameAndFirstnameselect distinct …​ where x.lastname = ?1 and x.firstname = ?2AndfindByNameAndPwdwhere name= ? and pwd =?OrfindByNameOrSexwhere name= ? or sex=?Is,EqualsfindById, findByIdIs, findByIdEqualswhere id= ?BetweenfindByIdBetweenwhere id between ? and ?LessThanfindByIdLessThanwhere id < ?LessThanEqualsfindByIdLessThanEqualswhere id <= ?GreaterThanfindByIdGreaterThanwhere id > ?GreaterThanEqualsfindByIdGreaterThanEqualswhere id > = ?AfterfindByIdAfterwhere id > ?BeforefindByIdBeforewhere id < ?IsNullfindByNameIsNullwhere name is nullisNotNull,NotNullfindByNameNotNullwhere name is not nullLikefindByNameLikewhere name like ?NotLikefindByNameNotLikewhere name not like ?StartingWithfindByNameStartingWithwhere name like ‘?%’EndingWithfindByNameEndingWithwhere name like ‘%?’ContainingfindByNameContainingwhere name like ‘%?%’OrderByfindByIdOrderByXDescwhere id=? order by x descNotfindByNameNotwhere name <> ?InfindByIdIn(Collection<?> c)where id in (?)NotInfindByIdNotIn(Collection<?> c)where id not in (?)TruefindByEnabledTuewhere enabled = trueFalsefindByEnabledFalsewhere enabled = falseIgnoreCasefindByNameIgnoreCasewhere UPPER(name)=UPPER(?)First,TopfindFirstByOrderByLastnameAscorder by lastname limit 1FirstN,TopNfindTop3ByOrderByLastnameAscorder by lastname limit 3

5.2 MPP自定义方法 + 接口默认实现

MyBatisPlus没有JPA那样可以根据接口的方法名自动组装查询条件,但是可以利用Java8的接口默认实现来达到同样的目的,只不过需要编写少量的代码:

importorg.apache.ibatis.annotations.Mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;@MapperpublicinterfaceDeptMapperextendsBaseMapper<Dept>{defaultDeptgetByCode(String code){returnselectOne(Wrappers.<Dept>lambdaWrapper().eq(Dept::getCode, code));}}

6 自定义SQL

JPA支持通过@Query注解和XML的形式实现自定义SQL,而MyBatis支持通过@Select、@Delete、@Update、@Script注解和XML的形式实现自定义SQL。

6.1 JPA

JPA的自定义SQL分为JPQL(Java Persistence Query Language Java 持久化查询语言)和原生SQL两种。
JPQL:

importorg.springframework.data.jpa.repository.Query;importorg.springframework.data.repository.query.Param;@RepositorypublicinterfaceDeptRepositoryextendsJpaRepository<Dept,Long>{@Query(value ="select d from Dept d where d.code = ?1")DeptgetByCode(String code);@Modifying@Query(value ="delete from Dept d where d.code = :code")intdeleteByCode(@Param("code")String code);}

原生SQL

importorg.springframework.data.jpa.repository.Query;importorg.springframework.data.repository.query.Param;@RepositorypublicinterfaceDeptRepositoryextendsJpaRepository<Dept,Long>{@Query(value ="SELECT * FROM dept WHERE name = ?1", countQuery ="SELECT count(*) FROM dept WHERE name = ?1", nativeQuery =true)Page<Dept>findByName(@Param("name")String name,Pageable pageable);}

XML形式:

/resource/META-INFO/orm.xml
<named-queryname="Dept.getByCode"><query> select d from Dept d where d.code = ?1</query></named-query><named-native-queryname="Dept.deleteByCode"><query> DELETE FROM dept WHERE code = ?1</query></named-native-query>

6.2 MyBatis

JPA的自定义SQL分为注解形式和XML形式
注解形式:

importorg.apache.ibatis.annotations.Mapper;importorg.apache.ibatis.annotations.Param;importorg.apache.ibatis.annotations.Select;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.baomidou.mybatisplus.core.metadata.IPage;@MapperpublicinterfaceDeptMapperextendsBaseMapper<Dept>{@Select(value ="SELECT * FROM dept WHERE code = #{code}")DeptgetByCode(@Param("code")String code);@Delete("DELETE FROM dept WHERE code = #{code}")intdeleteByCode(@Param("code")String code);@Select(value ="SELECT * FROM dept WHERE name = #{name}")IPage<Dept>findByName(@Param("name")String name,IPage<Dept> page);}

XML形式:

/resource/mapper/DeptMapper.xml
<mappernamespace="DeptMapper"><selectid="getByCode",resultType="Dept">
        SELECT * FROM dept WHERE code = #{code}
    </select><deleteid="deleteByCode">
        DELETE FROM dept WHERE code = #{code}
    </select><selectid="findByName">
        SELECT * FROM dept WHERE name = #{name}
    </select></mapper>

7 表关联

待补充

8 其他

对于简单的CRUD操作,JPA和MPP都提供了丰富的API简化开发人员的操作,但是有些差异化的地方需要总结下:
比较点JPAMPP成熟度JPA毕竟是javax标准,成熟度自然高MyBatis成熟度也很高,但是MPP毕竟是国内个人维护,质量和成熟度相对还是比较低的,但是使用起来更加适配国内开发者的习惯自动DDLJPA可以根据Entity的定义自动更新实际数据库的DDL, 使用起来比较便利利用MPP的脚本自动维护或Flyway进行SQL脚本的自动执行实体关系使用@OneToMany、@OneToOne、@ManyTo@Many注解描述表与表之间的关联,查询时自动进行表的关联,并且支持更新和删除时自动级联到关联的实体使用和标签以及@One、@Many注解来映射结果集和Java对象,只支持查询,不支持更新和删除, 另外还有一个MyBatis-Plus-Join项目, 可以实现Java中表Join的操作。复杂SQL查询不太方便使用xml结构化语言 + 动态SQL 标签 可以实现非常复杂的SQL场景数据库差异使用自带的API和JPQL的话,是不用关心具体用什么数据库,但是用原生SQL的话无法解决数据库的差异使用自带API的话,基本上不需要关注数据库的差异,如果切换了不同类型的数据库,通过配置databaseIdProvider 就可以根据当前使用数据库的不同选择不同的SQL脚本学习曲线较为难,主要是思路的转变,使用JPA更加关注的是实体间的关系,表的结构会根据实体关系自动维护对于传统的关系型数据库的操作,MyBatisPlus可以与JQuery操作DOM元素那么顺手

9 个人建议

目前对比下来整体的感觉是JPA侧重数据建模,关注数据一致性,屏蔽SQL操作,MyBatis侧重构建灵活的SQL,而MyBatisPlus在MyBatis的基础上较少了日常的CRUD操作,JPA更适合事务性系统,MyBatisPlus更适合做分析型系统。

个人是从SQL -> MyBatis -> MyBatisPlus的路线过来的,所以更习惯与用MPP解决数据的问题,在使用MPP的过程中,越来越发现自定义SQL用到越来越少,大部分场景下是可以用MPP的API组合来实现的,即便是MPP不支持多表关联,通过抽象视图的形式,也能达到单表查询的效果,只有在极限、特别复杂的情况下才会写SQL。

这么看来,其实JPA也是能满足日常的开发需求的。但是从传统SQL向JPA的转变是需要一个过程的,就跟面向过程开发到面向对象的开发,是需要一个大的开发思维一个转变,可能需要在项目的实践中不断体会和适应。


本文转载自: https://blog.csdn.net/qq_39609993/article/details/138270688
版权归原作者 118路司机 所有, 如有侵权,请联系我们删除。

“Spring Data JPA 与 MyBatisPlus的比较”的评论:

还没有评论