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


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:

  1. public interface UserDao{
  2. List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age);
  3. }
  4. public class UserDaoImpl implements UserDao{
  5. @Override
  6. public List<User> findByFirstNameAndAge(String firstName, Integer age) {
  7. //具体hql查找:"from User where name like '%'"+firstName + "and age > " + age;
  8. return hibernateTemplateMysql.execute( new HibernateCallback() {
  9. @Override
  10. public Object doInHibernate(Session session) throws HibernateException {
  11. String hql = "from User where name like '?' and age > ?";
  12. Query query = session.createQuery(hql);
  13. query.setParameter( 0, firstName+ "");
  14. query.setParameter( 1, age);
  15. return query.uniqueResult();
  16. }
  17. });
  18. }
  19. }
其次是mybatis:
  1. @Mapper
  2. public interface UserMapper {
  3. Increment findByNameLikeAndAgeGreaterThan(String name,int age);
  4. }
  5. <select id= "findByNameLikeAndAgeGreaterThan" parameterType= "java.lang.Integer" resultMap= "UserMap">
  6. select
  7. u.*
  8. from
  9. user u
  10. <where>
  11. u.name like ? 1 and u.age>? 2
  12. </where>
  13. </select>
  14. <resultMap id= "UserMap" type= "com.txxs.po.User">
  15. <result column= "id" property= "id"/>
  16. <result column= "name" property= "name"/>
  17. <result column= "age" property= "age"/>
  18. </resultMap>
最后是spring data jpa:
  1. public interface UserDao extends JpaRepository<User, Serializable>{
  2. List<User> findByNameLikeAndAgeGreaterThan(String name,Integer age);
  3. }
  4. //为了增加代码的可读性可以使用注解的方式,这样方法的命名就不需要严格按照规范
  5. @Query( "select * from User u where u.name like ?1 and u.age>?2")
通过上边代码的对比我们可以发现spring data jpa只要按照规范使用起来非常简单,下边是spring data jpa方法的命名规范:其他的可以参考用户手册
KeywordSampleJPQL 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的更新和添加是相似的两个方法,所以把所有的添加、批量添加、更新和批量更新抽象为以下的两个方法:
  1. @Repository
  2. public class CommonRepository<T> {
  3. @PersistenceContext
  4. protected EntityManager entityManager;
  5. /**
  6. * 添加和批量添加数据
  7. * @param lists
  8. */
  9. @Transactional
  10. public void batchAddCommon(List<T> lists){
  11. int size = lists.size();
  12. for ( int i = 0; i < size; i++) {
  13. entityManager.persist(lists.get(i));
  14. if (i % 100 == 0 || i == (size - 1)) {
  15. entityManager.flush();
  16. entityManager.clear();
  17. }
  18. }
  19. }
  20. /**
  21. * 数据的批量更新
  22. * @param lists
  23. */
  24. @Transactional
  25. public void batchUpdate(List<T> lists) {
  26. int size = lists.size();
  27. for ( int i = 0; i < size; i++) {
  28. entityManager.merge(lists.get(i));
  29. if (i % 100 == 0 || i == (size - 1)) {
  30. entityManager.flush();
  31. entityManager.clear();
  32. }
  33. }
  34. }
  35. }
从这一点上讲spring data jpa会比mybatis要强很多,因为以上两个方法可以实现所有资源的更新和添加操作,而mybatis则需要为每一个资源实体去写添加、批量添加、更新和批量更新等,这会很麻烦。以下是切换过程中一些有记录意义的SQL,罗列一下:

  1. //修改方法和删除方法都需要添加@Modifying,占位符是从1开始而不是开始的
  2. @Modifying
  3. @Query( "update table n set n.column1 =?1 where n.column2 = ?2")
  4. Integer updateObject(String one,String two);
  5. @Modifying
  6. @Query( "delete from table n where n.column1 = ?1")
  7. Integer getObject(String one);
  8. //查询某一个字段的时候需要制定相应的类型,select全量数据的使用直接使用别名n即可,原生的SQL需要使用n.*
  9. @Query( "select n.column1 as String from table n where n.column2 is null or n.column2 =''")
  10. List<String> getObject();
  11. //原生SQL,进行了连表操作,并且查询了满足数组条件
  12. @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)
  13. List<Server> getObject(List<Integer> arry);
在切换的使用遇到一个比较复杂的SQL,涉及联表、查询参数变量、in、case when、分页、group by等,下边给出mybatis和spring data jpa的写法:
  1. <select id="queryNotUsedObject" parameterType="com.txxs.po.Object" resultType="java.lang.Integer" >
  2. select
  3. DISTINCT (i.column1),
  4. SUM(CASE WHEN i.column7=#{column7} THEN 1 ELSE 0 END) used,
  5. sum(CASE WHEN i.column7 IS NULL THEN 1 ELSE 0 END) not_used
  6. from
  7. table1 i,
  8. table2 s
  9. <where>
  10. <if test="column2 != null and column2 != '' ">
  11. and s.column2 = #{column2}
  12. </if>
  13. <if test="column3 != null and column3 != '' ">
  14. and s.column3 = #{column3}
  15. </if>
  16. <if test="column4 != null and column4 != '' ">
  17. and i.column4 like '${column4}%'
  18. </if>
  19. <if test="column5 != null and column5 != '' ">
  20. and i.column5 like '${column5}%'
  21. </if>
  22. <if test="column6 != null and column6 != '' ">
  23. and i.column6 like '${column6}%'
  24. </if>
  25. and s.column1 = i.column1
  26. </where>
  27. GROUP BY column1
  28. having used =0 and not_used>0
  29. ORDER BY s.id DESC
  30. <if test="page != null and page>=0" >
  31. limit #{page} , #{size}
  32. </if>
  33. </select>
spring data jpa方式:
  1. public Page<Object> queryNotUsedObject(final Object query){
  2. CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
  3. CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
  4. //查询的根
  5. Root<Server> root = criteriaQuery.from(entityManager.getMetamodel().entity(Object.class));
  6. //判断参数
  7. List<Predicate> predicates = new ArrayList<Predicate>();
  8. if( null != query.getColumn1()){
  9. predicates.add(criteriaBuilder.equal(root.get( "Column1"), query.getColumn1()));
  10. }
  11. if( null != query.getColumn2()){
  12. predicates.add(criteriaBuilder.equal(root.get( "Column2"), query.getColumn2()));
  13. }
  14. //联表操作
  15. if( null != query.getColumn3()){
  16. predicates.add(criteriaBuilder.equal(root.join( "table1Column").get( "Column3"), query.getColumn3()));
  17. }
  18. if( null != query.getColumn4()){
  19. predicates.add(criteriaBuilder.equal(root.join( "table1Column").get( "Column4"), query.getColumn4()));
  20. }
  21. if( null != query.getColumn5()){
  22. predicates.add(criteriaBuilder.equal(root.join( "table1Column").get( "Column5"), query.getColumn5()));
  23. }
  24. //拼接Sum
  25. Expression<Integer> sumExpOne = criteriaBuilder.sum(criteriaBuilder.<Integer>selectCase().when(criteriaBuilder.equal(root.join( "table1Column").get( "Column6"), query.getColumn6()), 1).otherwise( 0)).as(Integer.class);
  26. Expression<Integer> sumExpTwo = criteriaBuilder.sum(criteriaBuilder.<Integer>selectCase().when(criteriaBuilder.isNull(root.join( "table1Column").get( "Column6")), 1).otherwise( 0)).as(Integer.class);
  27. //查询参数
  28. List<Expression<?>> expressions = new ArrayList<Expression<?>>();
  29. expressions.add(root.join( "table1Column").get( "Column1"));
  30. //having参数
  31. List<Predicate> predicateArrayList = new ArrayList<Predicate>();
  32. Predicate predicate = criteriaBuilder.equal(sumExpOne, 0);
  33. predicate = criteriaBuilder.and(predicate,criteriaBuilder.greaterThan(sumExpTwo, 0));
  34. predicateArrayList.add(predicate);
  35. //拼接SQL
  36. criteriaQuery.multiselect(expressions.toArray( new Expression[expressions.size()])).distinct( true);
  37. criteriaQuery.where(predicates.toArray( new Predicate[predicates.size()]));
  38. criteriaQuery.groupBy(root.join( "table1Column").get( "Column1"));
  39. criteriaQuery.having(predicateArrayList.toArray( new Predicate[predicateArrayList.size()]));
  40. //获取第一次执行的结果
  41. final List<Integer> list = entityManager.createQuery(criteriaQuery).getResultList();
  42. Sort sort = new Sort(Sort.Direction.DESC, "id");
  43. Pageable objectDao.findAll( new Specification<Object>(){
  44. @Override
  45. public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
  46. //判断参数
  47. List<Predicate> predicates = new ArrayList<Predicate>();
  48. predicates.add(root.get( "id").in(list));
  49. return criteriaBuilder.and(predicates.toArray( new Predicate[predicates.size()]));
  50. }
  51. },pageable);
  52. }

上边代码里边很多column不对应,是为了隐去痕迹,方法已经测试通过,从上边的代码看spring data jpa对于复杂语句的支持不够,需要通过代码的方式实现,而这种方式代码的可读性会比较差,优化等都会有一些难度

最后总结一下就是如果业务简单实用spring data jpa即可,如果业务复杂还是实用mybatis吧



  • 贾文静 2018-07-08 16:10:39 #2楼
    spring data jpa还是只使用简单的操作
  • qq_15003505
    Xanthuim 2018-04-04 11:41:30 #1楼
    感觉最终还是要用mybatis,觉得jpa这种东西只适合简单的增删改查比较多、SQL不怎么变化的情况。
    • maoyeqiu
      txxs回复  Xanthuim 2018-04-05 10:18:49
      你说的很对,多数据源的时候还有很多蛋疼的问题

原文:https://blog.csdn.net/maoyeqiu/article/details/78752071

文章标签:  spring data jpa springboot
个人分类:  springboot
上一篇springcloud-eureka搭建高可用服务注册集群
下一篇spring data jpa 多数据源





  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值