实例对比 hibernate, spring data jpa, mybatis 选型参考

原文: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不怎么变化的状况。

咱们能够两者结合使用!!

相关文章
相关标签/搜索