浅谈Spring Data JPA与MyBatisPlus的比较
投稿:zx
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<Dept> { }
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));
除了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<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的转变是需要一个过程的,就跟面向过程开发到面向对象的开发,是需要一个大的开发思维一个转变,可能需要在项目的实践中不断体会和适应。
到此这篇关于浅谈Spring Data JPA与MyBatisPlus的比较的文章就介绍到这了,更多相关Spring Data JPA与MyBatisPlus内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!