行业知识
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
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;@Repositorypublic interface DeptRepository extends JpaRepository {}- MPP
import org.apache.ibatis.annotations.Mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;@Mapperpublic 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 idList) 根据 ID 修改 save(T entity) updateById(T entity) 根据条件更新记录 - update(Wrapper updateWrapper) 根据 ID 查询 findById(ID id) selectById(Serializable id) 查询(根据ID 批量查询) findAllById(Iterable ids) selectBatchIds(Collection 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 小于 // 调用此方法时,会自动生成 where code = ? 的条件Dept getByCode(String code);}p常用的方法命名有:/p tabletheadtrth align="left"关键字/thth align="left"方法命名/thth align="left"sql条件/th/tr/theadtbodytrtd align="left"Distinct/tdtd align="left"findDistinctByLastnameAndFirstname/tdtd align="left"select distinct … where x.lastname = ?1 and x.firstname = ?2/td/trtrtd align="left"And/tdtd align="left"findByNameAndPwd/tdtd align="left"where name= ? and pwd =?/td/trtrtd align="left"Or/tdtd align="left"findByNameOrSex/tdtd align="left"where name= ? or sex=?/td/trtrtd align="left"Is,Equals/tdtd align="left"findById, findByIdIs, findByIdEquals/tdtd align="left"where id= ?/td/trtrtd align="left"Between/tdtd align="left"findByIdBetween/tdtd align="left"where id between ? and ?/td/trtrtd align="left"LessThan/tdtd align="left"findByIdLessThan/tdtd align="left"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;@Mapperpublic 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;@Repositorypublic 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;@Repositorypublic 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;@Mapperpublic 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
SELECT * FROM dept WHERE code = #{code}DELETE FROM dept WHERE code = #{code}SELECT * FROM dept WHERE name = #{name}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