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 基类主要方法
方法 JpaRepository MPP 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 extends ID> 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 extends Serializable> 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 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
{ // 调用此方法时,会自动生成 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
{ 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简化开发人员的操作,但是有些差异化的地方需要总结下:
比较点 JPA MPP 成熟度 JPA毕竟是javax标准,成熟度自然高 MyBatis成熟度也很高,但是MPP毕竟是国内个人维护,质量和成熟度相对还是比较低的,但是使用起来更加适配国内开发者的习惯 自动DDL JPA可以根据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的转变是需要一个过程的,就跟面向过程开发到面向对象的开发,是需要一个大的开发思维一个转变,可能需要在项目的实践中不断体会和适应。
- MPP
- JPA
- MPP
- JPA
- MPP
还没有评论,来说两句吧...