目录
  • 1 前言
  • 2 POM依赖
  • 3 Entity定义
  • 4 DAO基类
    • 4.1 基类主要方法
    • 4.2 Example、Specification VS Wrapper
  • 5 DAO子类
    • 5.1 JPA Repository方法命名规范
    • 5.2 MPP自定义方法 + 接口默认实现
  • 6 自定义SQL
    • 6.1 JPA
    • 6.2 MyBatis
  • 7 表关联
    • 8 其他
      • 9 个人建议

        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
        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<Dept, Long> {
        }
        
        • MPP
        import org.apache.ibatis.annotations.Mapper;
        import com.baomidou.mybatisplus.core.mapper.BaseMapper;
        
        @Mapper
        public interface DeptMapper extends BaseMapper&lt;Dept&gt; {
        }
        

        4.1 基类主要方法

        方法 JpaRepository MPP 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 = new Dept();
        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 = new Dept();
        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接口来完成:

        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<Dept, Long>, JpaSpecificationExecutor<Dept>  {
        }
        

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

        • 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<Dept> spec = new Specification<Dept>() {
           @Override
           public Predicate toPredicate(Root<Dept> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
               List<Predicate> 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<Dept> deptList = deptRepository.findAll(Example.of(dept)); 
        

        除了equalnotEqual, 针对日期、数字类型,还有gtgeltle等常用比较符。

        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<Dept> wrapper = Wrappers.lambdaQueryWrapper(detp);
        List<Dept> 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<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 Specification MPP Wrpper
        等于 = equal eq
        不等于 <> notEqual ne
        大于 > greaterThan, gt gt
        大于等于 >= greaterThanOrEqualTo, ge ge
        小于 < lessThan, lt lt
        小于等于 <= lessThanOrEqualTo, le le
        BETWEEN 值1 AND 值2 between between
        NOT BETWEEN 值1 AND 值2 notBetween
        LIKE ‘%值%’ like like
        NOT LIKE ‘%值%’ notLike notLike
        LIKE ‘%值’ like likeLeft
        LIKE ‘值%’ like likeRight
        NOT LIKE ‘%值’ notLike notLikeLeft
        NOT LIKE ‘值%’ notLike notLikeRight
        字段 IS NULL isNull isNull
        字段 IS NOT NULL isNotNull isNotNull
        字段 = true isTrue
        字段 = false isFalse
        字段 IN (v0, v1, …) in in
        字段 NOT IN (v0, v1, …) notIn
        排序:ORDER BY 字段, … ASC asc orderByAsc
        排序:ORDER BY 字段, … DESC desc orderByDesc
        排序:ORDER BY 字段, … orderBy(CriteriaQuery) orderBy
        拼接 OR or or
        AND 嵌套 and and
        正常嵌套 不带 AND 或者 OR nested
        拼接 sql apply
        无视优化规则直接拼接到 sql 的最后 last
        拼接 EXISTS ( sql语句 ) exists exists
        拼接 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
        Product prod
        差值 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<Dept, Long> {
            
            // 调用此方法时,会自动生成 where code = ? 的条件
        	Dept getByCode(String code);
        }
        

        常用的方法命名有:

        关键字 方法命名 sql条件
        Distinct findDistinctByLastnameAndFirstname select distinct …​ where x.lastname = ?1 and x.firstname = ?2
        And findByNameAndPwd where name= ? and pwd =?
        Or findByNameOrSex where name= ? or sex=?
        Is,Equals findById, findByIdIs, findByIdEquals where id= ?
        Between findByIdBetween where id between ? and ?
        LessThan findByIdLessThan where id < ?
        LessThanEquals findByIdLessThanEquals where id <= ?
        GreaterThan findByIdGreaterThan where id > ?
        GreaterThanEquals findByIdGreaterThanEquals where id > = ?
        After findByIdAfter where id > ?
        Before findByIdBefore where id < ?
        IsNull findByNameIsNull where name is null
        isNotNull,NotNull findByNameNotNull where name is not null
        Like findByNameLike where name like ?
        NotLike findByNameNotLike where name not like ?
        StartingWith findByNameStartingWith where name like ‘?%’
        EndingWith findByNameEndingWith where name like ‘%?’
        Containing findByNameContaining where name like ‘%?%’
        OrderBy findByIdOrderByXDesc where id=? order by x desc
        Not findByNameNot where name <> ?
        In findByIdIn(Collection<?> c) where id in (?)
        NotIn findByIdNotIn(Collection<?> c) where id not in (?)
        True findByEnabledTue where enabled = true
        False findByEnabledFalse where enabled = false
        IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)
        First,Top findFirstByOrderByLastnameAsc order by lastname limit 1
        FirstN,TopN findTop3ByOrderByLastnameAsc order 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<Dept> {
            default Dept getByCode(String code) {
        		return selectOne(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:

        import org.springframework.data.jpa.repository.Query;
        import org.springframework.data.repository.query.Param;
        
        @Repository
        public interface DeptRepository extends JpaRepository<Dept, Long> {
            @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<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-query name="Dept.getByCode">
          <query> select d from Dept d where d.code = ?1</query>
        </named-query>
        <named-native-query name="Dept.deleteByCode">
          <query> DELETE FROM dept WHERE code = ?1</query>
        </named-native-query>
        

        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<Dept> {
            @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<Dept> findByName(@Param("name") String name, IPage<Dept> page);
        }
        

        XML形式:
        /resource/mapper/DeptMapper.xml

        <mapper namespace="DeptMapper">
        	<select id = "getByCode", resultType = "Dept">
        		SELECT * FROM dept WHERE code = #[code]
        	</select>
        	<delete id = "deleteByCode">
        		DELETE FROM dept WHERE code = #[code]
        	</select>
        	<select id = "findByName">
        		SELECT * FROM dept WHERE name = #{name}
        	</select>
        </mapper>
        

        7 表关联

        待补充

        8 其他

        对于简单的CRUD操作,JPA和MPP都提供了丰富的API简化开发人员的操作,但是有些差异化的地方需要总结下:

        比较点 JPA MPP
        成熟度 JPA毕竟是javax标准,成熟度自然高 MyBatis成熟度也很高,但是MPP毕竟是国内个人维护,质量和成熟度相对还是比较低的,但是使用起来更加适配国内开发者的习惯
        自动DDL JPA可以根据Entity的定义自动更新实际数据库的DDL, 使用起来比较便利 利用MPP的脚本自动维护或Flyway进行SQL脚本的自动执行
        实体关系 使用@OneToMany、@OneToOne、@ManyTo@Many注解描述表与表之间的关联,查询时自动进行表的关联,并且支持更新和删除时自动级联到关联的实体 使用<association>和<collection>标签以及@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的转变是需要一个过程的,就跟面向过程开发到面向对象的开发,是需要一个大的开发思维一个转变,可能需要在项目的实践中不断体会和适应。

        声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。