Spring Data JPA 与 MyBatisPlus的比较

Spring Data JPA 与 MyBatisPlus的比较

码农世界 2024-05-21 后端 60 次浏览 0个评论

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
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
    • MPP
      
          com.baomidou
          mybatis-plus-boot-starter
      
      

      3 Entity定义

      • JPA
        import javax.persistence.Column;
        import javax.persistence.Entity;
        import javax.persistence.Id;
        import javax.persistence.Table;
        import javax.persistence.GeneratedValue;
        @Entity
        @Table(name = "dept")
        public class Dept {
        	@Id
        	@Column(name = "id")
        	@GeneratedValue(strategy = GenerationType.AUTO)
        	private Long id;
        	
        	@Column(name = "code")
        	private String code;
        	
        	@Column(name = "name")
        	private String name;
        }
        
        • MPP
          import com.baomidou.mybatisplus.annotation.TableField;
          import com.baomidou.mybatisplus.annotation.TableId;
          @TableName(value = "dept")
          public class Dept {
              @TableId(value = "id", type = IdType.AUTO)
              private Long id;
              @TableField(value = "code")
              private String code;
              @TableField(value = "name")
              private String name;
          }
          

          4 DAO基类

          • JPA
            import org.springframework.data.jpa.repository.JpaRepository;
            import org.springframework.stereotype.Repository;
            @Repository
            public interface DeptRepository extends JpaRepository {
            }
            
            • MPP
              import org.apache.ibatis.annotations.Mapper;
              import com.baomidou.mybatisplus.core.mapper.BaseMapper;
              @Mapper
              public interface DeptMapper extends BaseMapper {
              }
              

              4.1 基类主要方法

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

              4.2 Example、Specification VS Wrapper

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

              4.2.1 JPA Example

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

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

              Dept dept = new Dept();
              dept.setCode("100");
              dept.setName("Dept1");
              // select * from dept where code like '100%' and name like '%Dept1%';
              List 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接口来完成:

              import org.springframework.data.jpa.repository.JpaRepository;
              import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
              import org.springframework.stereotype.Repository;
              @Repository
              public interface DeptRepository extends JpaRepository, JpaSpecificationExecutor  {
              }
              

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

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

                使用示例:

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

                除了equal、notEqual, 针对日期、数字类型,还有gt、ge、lt、le等常用比较符。

                4.2.3 MPP Wrpper

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

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

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

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

                4.2.4 JPA Specification 与 MPP Wrpper的方法汇总

                方法JPA SpecificationMPP Wrpper
                等于 =equaleq
                不等于 <>notEqualne
                大于 >greaterThan, gtgt
                大于等于 >=greaterThanOrEqualTo, gege
                小于 <lessThan, ltlt
                小于等于 <=lessThanOrEqualTo, lele
                BETWEEN 值1 AND 值2betweenbetween
                NOT BETWEEN 值1 AND 值2-notBetween
                LIKE ‘%值%’likelike
                NOT LIKE ‘%值%’notLikenotLike
                LIKE ‘%值’likelikeLeft
                LIKE ‘值%’likelikeRight
                NOT LIKE ‘%值’notLikenotLikeLeft
                NOT 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
                拼接 ORoror
                AND 嵌套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)groupBy
                SQL 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在进行方法解析的时候会把前缀取掉,然后对剩下部分进行解析。例如:

                @Repository
                public interface DeptRepository extends JpaRepository {
                    
                    // 调用此方法时,会自动生成 where code = ? 的条件
                	Dept getByCode(String code);
                }
                

                常用的方法命名有:

                关键字方法命名sql条件
                DistinctfindDistinctByLastnameAndFirstnameselect distinct …​ where x.lastname = ?1 and x.firstname = ?2
                AndfindByNameAndPwdwhere 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 null
                isNotNull,NotNullfindByNameNotNullwhere name is not null
                LikefindByNameLikewhere name like ?
                NotLikefindByNameNotLikewhere name not like ?
                StartingWithfindByNameStartingWithwhere name like ‘?%’
                EndingWithfindByNameEndingWithwhere name like ‘%?’
                ContainingfindByNameContainingwhere name like ‘%?%’
                OrderByfindByIdOrderByXDescwhere id=? order by x desc
                NotfindByNameNotwhere name <> ?
                InfindByIdIn(Collection c)where id in (?)
                NotInfindByIdNotIn(Collection c)where id not in (?)
                TruefindByEnabledTuewhere enabled = true
                FalsefindByEnabledFalsewhere enabled = false
                IgnoreCasefindByNameIgnoreCasewhere UPPER(name)=UPPER(?)
                First,TopfindFirstByOrderByLastnameAscorder by lastname limit 1
                FirstN,TopNfindTop3ByOrderByLastnameAscorder by lastname limit 3

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

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

                import org.apache.ibatis.annotations.Mapper;
                import com.baomidou.mybatisplus.core.mapper.BaseMapper;
                @Mapper
                public interface DeptMapper extends BaseMapper {
                    default Dept getByCode(String code) {
                		return selectOne(Wrappers.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:

                import org.springframework.data.jpa.repository.Query;
                import org.springframework.data.repository.query.Param;
                @Repository
                public interface DeptRepository extends JpaRepository {
                    @Query(value = "select d from Dept d where d.code = ?1")
                	Dept getByCode(String code);
                    @Modifying
                    @Query(value = "delete from Dept d where d.code = :code")
                    int deleteByCode(@Param("code") String code);
                }
                

                原生SQL

                import org.springframework.data.jpa.repository.Query;
                import org.springframework.data.repository.query.Param;
                @Repository
                public interface DeptRepository extends JpaRepository {
                    @Query(value = "SELECT * FROM dept WHERE name = ?1", countQuery = "SELECT count(*) FROM dept WHERE name = ?1", nativeQuery = true)
                    Page findByName(@Param("name") String name, Pageable pageable);
                }
                

                XML形式:

                /resource/META-INFO/orm.xml

                
                   select d from Dept d where d.code = ?1
                
                
                   DELETE FROM dept WHERE code = ?1
                
                

                6.2 MyBatis

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

                注解形式:

                import org.apache.ibatis.annotations.Mapper;
                import org.apache.ibatis.annotations.Param;
                import org.apache.ibatis.annotations.Select;
                import com.baomidou.mybatisplus.core.mapper.BaseMapper;
                import com.baomidou.mybatisplus.core.metadata.IPage;
                @Mapper
                public interface DeptMapper extends BaseMapper {
                    @Select(value = "SELECT * FROM dept WHERE code = #{code}")
                	Dept getByCode(@Param("code") String code);
                    @Delete("DELETE FROM dept WHERE code = #{code}")
                    int deleteByCode(@Param("code") String code);
                    
                    @Select(value = "SELECT * FROM dept WHERE name = #{name}")
                    IPage findByName(@Param("name") String name, IPage page);
                }
                

                XML形式:

                /resource/mapper/DeptMapper.xml

                
                	
                	
                		DELETE FROM dept WHERE code = #{code}
                	
                	
                
                

                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的转变是需要一个过程的,就跟面向过程开发到面向对象的开发,是需要一个大的开发思维一个转变,可能需要在项目的实践中不断体会和适应。

转载请注明来自码农世界,本文标题:《Spring Data JPA 与 MyBatisPlus的比较》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,60人围观)参与讨论

还没有评论,来说两句吧...

Top