必威体育Betway必威体育官网
当前位置:首页 > IT技术

spring data jpa-由mybatis切换

时间:2019-10-01 09:13:33来源:IT技术作者:seo实验室小编阅读:63次「手机版」
 

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的多事务配置(多个Transaction Manager)和使用

转自:http://blog.csdn.net/clementad/article/details/47275227 大多数项目只需要一个事务管理器。然而,有些项目为了提高效率、或

Spring mvc ContextLoaderListener 原理解析

对于熟悉Spring MVC功能,首先应从web.xml 开始,在web.xml 文件中我们需要配置一个监听器 ContextLoaderListener,如下。 <!-- 加载

Spring的ContextLoaderListener加载上下文的源码分析

前言: 1,如果使用自定义的监听器,需要经过下面的步骤 1到步骤10 2,如果使用Spring自己的监听器ContextLoaderListener,需要经过下面的

spring中packagesToScan的解释

spring中packagesToScan的解释 使用早前版本的spring,又恰好采用了Annotation注解方式(而非传统XML方式)配置hibernate对象关系映射,

spring-boot启动失败 Unregistering JMX-exposed bean

寄语:    学习的路途各种文章只是给你提供一个思路,适不适合自己还是要自己去总结,以下是我的项目遇到同样的问题但是尝试了多种方

分享到:

栏目导航

推荐阅读

热门阅读