原文:java
最近重構之前寫的服務,最大的一個變更是將mybatis切換爲spring data jpa,切換的緣由很簡單,有兩點:第1、它是spring的子項目可以和spring boot很好的融合,沒有xml文件(關於這一點hibernate彷佛也很符合);第2、簡單優雅,好比不須要寫SQL、對分頁有自動化的支持等等,基於以上兩點開始了重構之路。在這以前瞭解了一下hibernate、mybatis和spring data jpa的區別,在這裏也簡單的記錄一下:Hibernate的O/R Mapping實現了POJO 和數據庫表之間的映射,以及SQL 的自動生成和執行;Mybatis則在於POJO 與SQL之間的映射關係,經過ResultMap對SQL的結果集進行映射;Spring Data jpa是一個用於簡化數據庫訪問,並支持雲服務的開源框架,容易上手,經過命名規範、註解查詢簡化查詢操做。這三者都是ORM框架,可是mybatis可能並無那麼典型,緣由就是mybatis映射的是SQL的結果集,另外hibernate和spring data jpa都是jpa(Java Persistence API,是從JDK5開始提供的,用來描述對象 <--> 關係表映射關係,並持久化的標準)的一種實現,從這一點上將這二者是一種並列的關係,spring data jpa用戶手冊上有這麼一句話Improved compatibility with Hibernate 5.2.,這說明,spring data jpa又是hibernate的一個提高,下邊先經過一個SQL:select * from User where name like '?' and age > ?的例子說明一下這三者在執行時候的區別:
首先看hibernate:spring
public interface UserDao{ List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age); } public class UserDaoImpl implements UserDao{ @Override public List<User> findByFirstNameAndAge(String firstName, Integer age) { //具體hql查找:"from User where name like '%'"+firstName + "and age > " + age; return hibernateTemplateMysql.execute(new HibernateCallback() { @Override public Object doInHibernate(Session session) throws HibernateException { String hql = "from User where name like '?' and age > ?"; Query query = session.createQuery(hql); query.setParameter(0, firstName+""); query.setParameter(1, age); return query.uniqueResult(); } }); } }
其次是mybatis:sql
@Mapper public interface UserMapper { Increment findByNameLikeAndAgeGreaterThan(String name,int age); } <select id="findByNameLikeAndAgeGreaterThan" parameterType="java.lang.Integer" resultMap="UserMap"> select u.* from user u <where> u.name like ?1 and u.age>?2 </where> </select> <resultMap id="UserMap" type="com.txxs.po.User"> <result column="id" property="id"/> <result column="name" property="name"/> <result column="age" property="age"/> </resultMap>
最後是spring data jpa:數據庫
public interface UserDao extends JpaRepository<User, Serializable>{ List<User> findByNameLikeAndAgeGreaterThan(String name,Integer age); } //爲了增長代碼的可讀性可使用註解的方式,這樣方法的命名就不須要嚴格按照規範 @Query("select * from User u where u.name like ?1 and u.age>?2")
經過上邊代碼的對比咱們能夠發現spring data jpa只要按照規範使用起來很是簡單,下邊是spring data jpa方法的命名規範:其餘的能夠參考用戶手冊
Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2express
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2數組
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1session
Between findByStartDateBetween … where x.startDate between ?1 and ?2mybatis
LessThan findByAgeLessThan … where x.age < ?1架構
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1app
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1(parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1(parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1(parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)
下邊記錄一下切換的服務的後臺架構,分爲四層:controller、service、repository以及mapper,這樣在修改的時候只修改repository便可,並添加新的層dao層,這樣只要經過repository的切換就能夠快速的實現spring data jpa和mybatis的快速切換,甚至能夠同時使用這兩個框架,從框架層面解決了切換的問題以後,因爲spring data jpa的更新和添加是類似的兩個方法,因此把全部的添加、批量添加、更新和批量更新抽象爲如下的兩個方法:
@Repository public class CommonRepository<T> { @PersistenceContext protected EntityManager entityManager; /** * 添加和批量添加數據 * @param lists */ @Transactional public void batchAddCommon(List<T> lists){ int size = lists.size(); for (int i = 0; i < size; i++) { entityManager.persist(lists.get(i)); if (i % 100 == 0 || i == (size - 1)) { entityManager.flush(); entityManager.clear(); } } } /** * 數據的批量更新 * @param lists */ @Transactional public void batchUpdate(List<T> lists) { int size = lists.size(); for (int i = 0; i < size; i++) { entityManager.merge(lists.get(i)); if (i % 100 == 0 || i == (size - 1)) { entityManager.flush(); entityManager.clear(); } } } }
從這一點上講spring data jpa會比mybatis要強不少,由於以上兩個方法能夠實現全部資源的更新和添加操做,而mybatis則須要爲每個資源實體去寫添加、批量添加、更新和批量更新等,這會很麻煩。如下是切換過程當中一些有記錄意義的SQL,羅列一下:
//修改方法和刪除方法都須要添加@Modifying,佔位符是從1開始而不是開始的 @Modifying @Query("update table n set n.column1 =?1 where n.column2 = ?2") Integer updateObject(String one,String two); @Modifying @Query("delete from table n where n.column1 = ?1") Integer getObject(String one); //查詢某一個字段的時候須要制定相應的類型,select全量數據的使用直接使用別名n便可,原生的SQL須要使用n.* @Query("select n.column1 as String from table n where n.column2 is null or n.column2 =''") List<String> getObject(); //原生SQL,進行了連表操做,而且查詢了知足數組條件 @Query(value="select s.*, i.* from table1 s, table2 i where i.column1 = s.column1 and i.column1 in (?1) order by s.id desc", nativeQuery = true) List<Server> getObject(List<Integer> arry);
在切換的使用遇到一個比較複雜的SQL,涉及聯表、查詢參數變量、in、case when、分頁、group by等,下邊給出mybatis和spring data jpa的寫法:
<select id="queryNotUsedObject" parameterType="com.txxs.po.Object" resultType="java.lang.Integer" > select DISTINCT (i.column1), SUM(CASE WHEN i.column7=#{column7} THEN 1 ELSE 0 END) used, sum(CASE WHEN i.column7 IS NULL THEN 1 ELSE 0 END) not_used from table1 i, table2 s <where> <if test="column2 != null and column2 != '' "> and s.column2 = #{column2} </if> <if test="column3 != null and column3 != '' "> and s.column3 = #{column3} </if> <if test="column4 != null and column4 != '' "> and i.column4 like '${column4}%' </if> <if test="column5 != null and column5 != '' "> and i.column5 like '${column5}%' </if> <if test="column6 != null and column6 != '' "> and i.column6 like '${column6}%' </if> and s.column1 = i.column1 </where> GROUP BY column1 having used =0 and not_used>0 ORDER BY s.id DESC <if test="page != null and page>=0" > limit #{page} , #{size} </if> </select>
spring data jpa方式:
public Page<Object> queryNotUsedObject(final Object query){ CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); //查詢的根 Root<Server> root = criteriaQuery.from(entityManager.getMetamodel().entity(Object.class)); //判斷參數 List<Predicate> predicates = new ArrayList<Predicate>(); if(null != query.getColumn1()){ predicates.add(criteriaBuilder.equal(root.get("Column1"), query.getColumn1())); } if(null != query.getColumn2()){ predicates.add(criteriaBuilder.equal(root.get("Column2"), query.getColumn2())); } //聯表操做 if(null != query.getColumn3()){ predicates.add(criteriaBuilder.equal(root.join("table1Column").get("Column3"), query.getColumn3())); } if(null != query.getColumn4()){ predicates.add(criteriaBuilder.equal(root.join("table1Column").get("Column4"), query.getColumn4())); } if(null != query.getColumn5()){ predicates.add(criteriaBuilder.equal(root.join("table1Column").get("Column5"), query.getColumn5())); } //拼接Sum Expression<Integer> sumExpOne = criteriaBuilder.sum(criteriaBuilder.<Integer>selectCase().when(criteriaBuilder.equal(root.join("table1Column").get("Column6"), query.getColumn6()), 1).otherwise(0)).as(Integer.class); Expression<Integer> sumExpTwo = criteriaBuilder.sum(criteriaBuilder.<Integer>selectCase().when(criteriaBuilder.isNull(root.join("table1Column").get("Column6")), 1).otherwise(0)).as(Integer.class); //查詢參數 List<Expression<?>> expressions = new ArrayList<Expression<?>>(); expressions.add(root.join("table1Column").get("Column1")); //having參數 List<Predicate> predicateArrayList = new ArrayList<Predicate>(); Predicate predicate = criteriaBuilder.equal(sumExpOne,0); predicate = criteriaBuilder.and(predicate,criteriaBuilder.greaterThan(sumExpTwo,0)); predicateArrayList.add(predicate); //拼接SQL criteriaQuery.multiselect(expressions.toArray(new Expression[expressions.size()])).distinct(true); criteriaQuery.where(predicates.toArray(new Predicate[predicates.size()])); criteriaQuery.groupBy(root.join("table1Column").get("Column1")); criteriaQuery.having(predicateArrayList.toArray(new Predicate[predicateArrayList.size()])); //獲取第一次執行的結果 final List<Integer> list = entityManager.createQuery(criteriaQuery).getResultList(); Sort sort = new Sort(Sort.Direction.DESC, "id"); Pageable objectDao.findAll(new Specification<Object>(){ @Override public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { //判斷參數 List<Predicate> predicates = new ArrayList<Predicate>(); predicates.add(root.get("id").in(list)); return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])); } },pageable); }
上邊代碼裏邊不少column不對應,是爲了隱去痕跡,方法已經測試經過,從上邊的代碼看spring data jpa對於複雜語句的支持不夠,須要經過代碼的方式實現,而這種方式代碼的可讀性會比較差,優化等都會有一些難度
最後總結一下就是若是業務簡單實用spring data jpa便可,若是業務複雜仍是實用mybatis吧
spring data jpa仍是隻使用簡單的操做.
感受最終仍是要用mybatis,以爲jpa這種東西只適合簡單的增刪改查比較多、SQL不怎麼變化的狀況。
咱們能夠兩者結合使用!!