淺談Spring Data JPA與MyBatisPlus的比較
1 前言
JPA(Java Persistence API)和MyBatis Plus是兩種不同的持久化框架,它們具有不同的特點(diǎn)和適用場(chǎng)景。
JPA是Java官方的持久化規(guī)范,它提供了一種基于對(duì)象的編程模型,可以通過注解或XML配置來實(shí)現(xiàn)對(duì)象與數(shù)據(jù)庫(kù)的映射關(guān)系。JPA的優(yōu)點(diǎn)是可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行更高級(jí)的操作,如查詢、更新、刪除等,同時(shí)也支持事務(wù)管理和緩存機(jī)制,能夠更好地支持復(fù)雜的業(yè)務(wù)邏輯。
MyBatis Plus (MPP) 是在MyBatis基礎(chǔ)上進(jìn)行封裝的增強(qiáng)版本,它提供了更簡(jiǎn)單易用的API和更高效的性能。MyBatis Plus通過XML或注解的方式來配置數(shù)據(jù)庫(kù)映射關(guān)系,并提供了豐富的查詢、更新、刪除操作的方法。相對(duì)于JPA,MyBatis Plus配置簡(jiǎn)單、易于上手,同時(shí)也靈活性較高,能夠更好地滿足項(xiàng)目的特定需求。
如果只是針對(duì)單表的增刪改查,兩者十分相似,本質(zhì)上都算ORM框架,那么到底什么時(shí)候適合用JPA,什么時(shí)候用MyBatisPlus,下面做下這兩者的詳細(xì)對(duì)比。
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) |
根據(jù) ID 刪除 | deleteById(ID id) | deleteById(Serializable id) |
根據(jù)實(shí)體(ID)刪除 | delete(T entity) | deleteById(T entity) |
根據(jù)條件刪除記錄 | - | delete(Wrapper<T> queryWrapper) |
刪除(根據(jù)ID或?qū)嶓w 批量刪除) | deleteAllById(Iterable<? extends ID> ids) | deleteBatchIds(Collection<?> idList) |
根據(jù) ID 修改 | save(T entity) | updateById(T entity) |
根據(jù)條件更新記錄 | - | update(Wrapper<T> updateWrapper) |
根據(jù) ID 查詢 | findById(ID id) | selectById(Serializable id) |
查詢(根據(jù)ID 批量查詢) | findAllById(Iterable<ID> ids) | selectBatchIds(Collection<? extends Serializable> idList) |
根據(jù)條件查詢一條記錄 | - | selectOne(Wrapper<T> queryWrapper) |
根據(jù)條件判斷是否存在記錄 | exists(Example<T> example) | exists(Wrapper<T> queryWrapper) |
根據(jù)條件查詢總記錄數(shù) | count(Example<T> example) | selectCount(Wrapper<T> queryWrapper) |
根據(jù)條件查詢?nèi)坑涗?/td> | findAll(Example<T> example, Sort sort) | selectList(Wrapper<T> queryWrapper) |
根據(jù)條件查詢分頁(yè)記錄 | findAll(Example<T> example, Pageable pageable) | selectPage(P page, Wrapper<T> queryWrapper) |
4.2 Example、Specification VS Wrapper
JPA使用Example和Specification 類來實(shí)現(xiàn)范本數(shù)據(jù)的查詢,而MPP使用QueryWrapper來設(shè)置查詢條件
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));
默認(rèn)是生成的條件都是 “=”,如果要設(shè)置其他比較符,需要使用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僅能實(shí)現(xiàn)對(duì)字符串類型的匹配模式,如果要設(shè)置其他類型的字段,可以實(shí)現(xiàn)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> { }
增加以上接口后,會(huì)增加以下查詢方法:
- 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
, 針對(duì)日期、數(shù)字類型,還有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);
默認(rèn)是生成的條件都是 “=”,如果要設(shè)置其他比較符,需要單獨(dú)設(shè)置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 |
無視優(yōu)化規(guī)則直接拼接到 sql 的最后 | - | last |
拼接 EXISTS ( sql語(yǔ)句 ) | exists | exists |
拼接 NOT EXISTS ( sql語(yǔ)句 ) | - | notExists |
去重 | distinct(CriteriaQuery) | - |
設(shè)置查詢字段 | select, multiselect(CriteriaQuery) | select |
分組:GROUP BY 字段, … | groupBy(CriteriaQuery) | groupBy |
SQL SET 字段 | - | set |
設(shè)置 SET 部分 SQL | - | setSql |
字段自增變量 val 值 | - | setIncrBy |
字段自減變量 val 值 | - | setDecrBy |
條件判斷 | selectCase | - |
平均值 | avg | - |
加和 | sum, sumAsLong, sumAsDouble | - |
計(jì)數(shù) | count, countDistinct | - |
最大值 | max, greatest | - |
最小值 | min, least | - |
取反 | neg | - |
絕對(duì)值 | abs | - |
Product | prod | - |
差值 | diff | - |
求商 | quot | - |
取模 | mod | - |
開根號(hào) | sqrt | - |
轉(zhuǎn)換類型 | toLong, toInteger, toFloat, toDouble, toBigDecimal, toBigInteger, toString | - |
集合是否為空 | isEmpty, isNotEmpty | - |
集合大小 | size | - |
是否包含 | isMember, isNotMember | - |
鍵值對(duì) | keys, values | - |
字符串拼接 | concat | - |
字符串分隔 | substring | - |
去空白 | trim | - |
大小寫轉(zhuǎn)換 | upper, lower | - |
字符串長(zhǎng)度 | length | - |
空處理 | nullif, coalesce | - |
5 DAO子類
5.1 JPA Repository方法命名規(guī)范
JPA支持接口規(guī)范方法名查詢,一般查詢方法以 find、findBy、read、readBy、get、getBy為前綴,JPA在進(jìn)行方法解析的時(shí)候會(huì)把前綴取掉,然后對(duì)剩下部分進(jìn)行解析。例如:
@Repository public interface DeptRepository extends JpaRepository<Dept, Long> { // 調(diào)用此方法時(shí),會(huì)自動(dòng)生成 where code = ? 的條件 Dept getByCode(String code); }
常用的方法命名有:
關(guān)鍵字 | 方法命名 | 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自定義方法 + 接口默認(rèn)實(shí)現(xiàn)
MyBatisPlus沒有JPA那樣可以根據(jù)接口的方法名自動(dòng)組裝查詢條件,但是可以利用Java8的接口默認(rèn)實(shí)現(xiàn)來達(dá)到同樣的目的,只不過需要編寫少量的代碼:
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的形式實(shí)現(xiàn)自定義SQL,而MyBatis支持通過@Select、@Delete、@Update、@Script注解和XML的形式實(shí)現(xiàn)自定義SQL。
6.1 JPA
JPA的自定義SQL分為JPQL(Java Persistence Query Language Java 持久化查詢語(yǔ)言)和原生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 表關(guān)聯(lián)
待補(bǔ)充
8 其他
對(duì)于簡(jiǎn)單的CRUD操作,JPA和MPP都提供了豐富的API簡(jiǎn)化開發(fā)人員的操作,但是有些差異化的地方需要總結(jié)下:
比較點(diǎn) | JPA | MPP |
---|---|---|
成熟度 | JPA畢竟是javax標(biāo)準(zhǔn),成熟度自然高 | MyBatis成熟度也很高,但是MPP畢竟是國(guó)內(nèi)個(gè)人維護(hù),質(zhì)量和成熟度相對(duì)還是比較低的,但是使用起來更加適配國(guó)內(nèi)開發(fā)者的習(xí)慣 |
自動(dòng)DDL | JPA可以根據(jù)Entity的定義自動(dòng)更新實(shí)際數(shù)據(jù)庫(kù)的DDL, 使用起來比較便利 | 利用MPP的腳本自動(dòng)維護(hù)或Flyway進(jìn)行SQL腳本的自動(dòng)執(zhí)行 |
實(shí)體關(guān)系 | 使用@OneToMany、@OneToOne、@ManyTo@Many注解描述表與表之間的關(guān)聯(lián),查詢時(shí)自動(dòng)進(jìn)行表的關(guān)聯(lián),并且支持更新和刪除時(shí)自動(dòng)級(jí)聯(lián)到關(guān)聯(lián)的實(shí)體 | 使用<association>和<collection>標(biāo)簽以及@One、@Many注解來映射結(jié)果集和Java對(duì)象,只支持查詢,不支持更新和刪除, 另外還有一個(gè)MyBatis-Plus-Join項(xiàng)目, 可以實(shí)現(xiàn)Java中表Join的操作。 |
復(fù)雜SQL查詢 | 不太方便 | 使用xml結(jié)構(gòu)化語(yǔ)言 + 動(dòng)態(tài)SQL 標(biāo)簽 可以實(shí)現(xiàn)非常復(fù)雜的SQL場(chǎng)景 |
數(shù)據(jù)庫(kù)差異 | 使用自帶的API和JPQL的話,是不用關(guān)心具體用什么數(shù)據(jù)庫(kù),但是用原生SQL的話無法解決數(shù)據(jù)庫(kù)的差異 | 使用自帶API的話,基本上不需要關(guān)注數(shù)據(jù)庫(kù)的差異,如果切換了不同類型的數(shù)據(jù)庫(kù),通過配置databaseIdProvider 就可以根據(jù)當(dāng)前使用數(shù)據(jù)庫(kù)的不同選擇不同的SQL腳本 |
學(xué)習(xí)曲線 | 較為難,主要是思路的轉(zhuǎn)變,使用JPA更加關(guān)注的是實(shí)體間的關(guān)系,表的結(jié)構(gòu)會(huì)根據(jù)實(shí)體關(guān)系自動(dòng)維護(hù) | 對(duì)于傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)的操作,MyBatisPlus可以與JQuery操作DOM元素那么順手 |
9 個(gè)人建議
目前對(duì)比下來整體的感覺是JPA側(cè)重?cái)?shù)據(jù)建模,關(guān)注數(shù)據(jù)一致性,屏蔽SQL操作,MyBatis側(cè)重構(gòu)建靈活的SQL,而MyBatisPlus在MyBatis的基礎(chǔ)上較少了日常的CRUD操作,JPA更適合事務(wù)性系統(tǒng),MyBatisPlus更適合做分析型系統(tǒng)。
個(gè)人是從SQL -> MyBatis -> MyBatisPlus的路線過來的,所以更習(xí)慣與用MPP解決數(shù)據(jù)的問題,在使用MPP的過程中,越來越發(fā)現(xiàn)自定義SQL用到越來越少,大部分場(chǎng)景下是可以用MPP的API組合來實(shí)現(xiàn)的,即便是MPP不支持多表關(guān)聯(lián),通過抽象視圖的形式,也能達(dá)到單表查詢的效果,只有在極限、特別復(fù)雜的情況下才會(huì)寫SQL。
這么看來,其實(shí)JPA也是能滿足日常的開發(fā)需求的。但是從傳統(tǒng)SQL向JPA的轉(zhuǎn)變是需要一個(gè)過程的,就跟面向過程開發(fā)到面向?qū)ο蟮拈_發(fā),是需要一個(gè)大的開發(fā)思維一個(gè)轉(zhuǎn)變,可能需要在項(xiàng)目的實(shí)踐中不斷體會(huì)和適應(yīng)。
到此這篇關(guān)于淺談Spring Data JPA與MyBatisPlus的比較的文章就介紹到這了,更多相關(guān)Spring Data JPA與MyBatisPlus內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java根據(jù)模板導(dǎo)出Excel報(bào)表并復(fù)制模板生成多個(gè)Sheet頁(yè)
本文主要介紹了Java根據(jù)模板導(dǎo)出Excel報(bào)表并復(fù)制模板生成多個(gè)Sheet頁(yè)的方法,具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03詳解Java去除json數(shù)據(jù)中的null空值問題
這篇文章主要介紹了詳解Java去除json數(shù)據(jù)中的null空值問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08springboot如何使用logback-spring配置日志格式,并分環(huán)境配置
這篇文章主要介紹了springboot如何使用logback-spring配置日志格式,并分環(huán)境配置的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07