實例對比 hibernate, spring data jpa, mybatis 選型參考


原文:

 

 

最近重構以前寫的服務,最大的一個變動是將mybatis切換為spring data jpa,切換的原因很簡單,有兩點:第一、它是spring的子項目能夠和spring boot很好的融合,沒有xml文件(關於這一點hibernate似乎也很符合);第二、簡單優雅,比如不需要寫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:

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:

@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 = ?2

Or          findByLastnameOrFirstname                        … where x.lastname = ?1 or x.firstname = ?2

Is,Equals     findByFirstname,findByFirstnameIs,findByFirstnameEquals           … where x.firstname = ?1

Between      findByStartDateBetween                        … where x.startDate between ?1 and ?2

LessThan      findByAgeLessThan                          … where x.age < ?1

LessThanEqual    findByAgeLessThanEqual                        … where x.age <= ?1

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不怎么變化的情況。

我們可以二者結合使用!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM