1、开发对比开发速度  http://www.zhihu.com/question/21104468

Hibernate的真正掌握要比Mybatis来得难些。Mybatis框架相对简单很容易上手,但也相对简陋些。个人觉得要用好Mybatis还是首先要先理解好Hibernate。

开发社区

Hibernate 与Mybatis都是流行的持久层开发框架,但Hibernate开发社区相对多热闹些,支持的工具也多,更新也快,当前最高版本4.1.8。而Mybatis相对平静,工具较少,当前最高版本3.2。

开发工作量

Hibernate和MyBatis都有相应的代码生成工具。可以生成简单基本的DAO层方法。

针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。

2、系统调优对比Hibernate的调优方案

  1. 制定合理的缓存策略;
  2. 尽量使用延迟加载特性;
  3. 采用合理的Session管理机制;
  4. 使用批量抓取,设定合理的批处理参数(batch_size);
  5. 进行合理的O/R映射设计

Mybatis调优方案

MyBatis在Session方面和Hibernate的Session生命周期是一致的,同样需要合理的Session管理机制。MyBatis同样具有二级缓存机制。 MyBatis可以进行详细的SQL优化设计。

SQL优化方面

Hibernate的查询会将表中的所有字段查询出来,这一点会有性能消耗。Hibernate也可以自己写SQL来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性。而Mybatis的SQL是手动编写的,所以可以按需求指定查询的字段。

Hibernate HQL语句的调优需要将SQL打印出来,而Hibernate的SQL被很多人嫌弃因为太丑了。MyBatis的SQL是自己手动写的所以调整方便。但Hibernate具有自己的日志统计。Mybatis本身不带日志统计,使用Log4j进行日志记录。

扩展性方面

Hibernate与具体数据库的关联只需在XML文件中配置即可,所有的HQL语句与具体使用的数据库无关,移植性很好。MyBatis项目中所有的SQL语句都是依赖所用的数据库的,所以不同数据库类型的支持不好。

3、对象管理与抓取策略对象管理

Hibernate 是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。也就是说,相对于常见的 JDBC/SQL 持久层方案中需要管理 SQL 语句,Hibernate采用了更自然的面向对象的视角来持久化 Java 应用中的数据。

换句话说,使用 Hibernate 的开发者应该总是关注对象的状态(state),不必考虑 SQL 语句的执行。这部分细节已经由 Hibernate 掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。

而MyBatis在这一块没有文档说明,用户需要对对象自己进行详细的管理。

抓取策略

Hibernate对实体关联对象的抓取有着良好的机制。对于每一个关联关系都可以详细地设置是否延迟加载,并且提供关联抓取、查询抓取、子查询抓取、批量抓取四种模式。 它是详细配置和处理的。

而Mybatis的延迟加载是全局配置的。

4、缓存机制对比Hibernate缓存

Hibernate一级缓存是Session缓存,利用好一级缓存就需要对Session的生命周期进行管理好。建议在一个Action操作中使用一个Session。一级缓存需要对Session进行严格管理。

Hibernate二级缓存是SessionFactory级的缓存。 SessionFactory的缓存分为内置缓存和外置缓存。内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定SQL语句等),对于应用程序来说,它是只读的。外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。二级缓存称为进程级缓存或SessionFactory级缓存,它可以被所有session共享,它的生命周期伴随着SessionFactory的生命周期存在和消亡。

5、优势对比

Mybatis优势

  • MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
  • MyBatis容易掌握,而Hibernate门槛较高。

Hibernate优势

  • Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
  • Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
  • Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
  • Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。  

作者:郑沐兴
链接:http://www.zhihu.com/question/21104468/answer/92986556
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

通过各种实验,测出两个在相同情景下的性能相关的指数,供大家参考。
测试目标
以下测试需要确定几点内容:
性能差异的场景;
性能不在同场景下差异比;
找出各架框优劣,各种情况下的表现,适用场景。

测试思路
测试总体分成:单表插入,关联插入,单表查询,多表查询。
测试分两轮,同场景下默认参数做一轮,调优做强一轮,横纵对比分析了。
测试中尽保证输入输出的一致性。
样本量尽可能大,达到10万级别以上,减少统计误差。

测试提纲
具体的场景情况下
插入测试1:10万条记录插入。
查询测试1:100万数据中单表通过id查询100000次,无关联字段。
查询测试2:100万数据中单表通过id查询100000次,输出关联对象字段。
查询测试3:100万*50万关联数据中查询100000次,两者输出相同字段。

准备
数据库:mysql 5.6
表格设计:
twitter:推特
Sql代码

    1. CREATE TABLE `twitter` (  
    2.   `id` bigint(20) NOT NULL AUTO_INCREMENT,  
    3.   `add_date` datetime DEFAULT NULL,  
    4.   `modify_date` datetime DEFAULT NULL,  
    5.   `ctx` varchar(255) NOT NULL,  
    6.   `add_user_id` bigint(20) DEFAULT NULL,  
    7.   `modify_user_id` bigint(20) DEFAULT NULL,  
    8.   PRIMARY KEY (`id`),  
    9.   KEY `UPDATE_USER_FORI` (`modify_user_id`),  
    10.   KEY `ADD_USER_FORI` (`add_user_id`),  
    11.   CONSTRAINT `ADD_USER_FORI` FOREIGN KEY (`add_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL,  
    12.   CONSTRAINT `UPDATE_USER_FORI` FOREIGN KEY (`modify_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL  
    13. ) ENGINE=InnoDB AUTO_INCREMENT=1048561 DEFAULT CHARSET=utf8

    user: 用户

    Sql代码

      1. CREATE TABLE `user` (  
      2.   `id` bigint(20) NOT NULL AUTO_INCREMENT,  
      3.   `name` varchar(255) DEFAULT NULL,  
      4.   PRIMARY KEY (`id`)  
      5. ) ENGINE=InnoDB AUTO_INCREMENT=524281 DEFAULT CHARSET=utf8

      测试数据准备:

      twitter

      无数据。


      user

      50万个随机的用户名。


      随机内容推特表(material_twitter)

      无id,仅有随机字符串内容,共10万条。

      用于插入控推特表。


      生成数据代码,关联100个用户:

      Java代码

      1. insert into twitter(ctx,add_user_id,modify_user_id,add_date,modify_date)  
      2. SELECT name,ROUND(RAND()*100)+1,ROUND(RAND()*100)+1,'2016-12-31','2016-12-31'  
      3. from MATERIAL


      生成数据代码,关联500000个用户:

      Java代码

      1. insert into twitter(ctx,add_user_id,modify_user_id,add_date,modify_date)  
      2. SELECT name,ROUND(RAND()*500000)+1,ROUND(RAND()*500000)+1,'2016-12-31','2016-12-31'  
      3. from MATERIAL

      实体代码

      Java代码

        1. @Entity  
        2. @Table(name = "twitter")  
        3. public class Twitter implements java.io.Serializable{  
        4.     private Long id;  
        5.     private Date add_date;  
        6.     private Date modify_date;  
        7.     private String ctx;  
        8.     private User add_user;  
        9.     private User modify_user;  
        10.     private String createUserName;  
        11.     @Id  
        12.     @GeneratedValue(strategy = IDENTITY)  
        13.     @Column(name = "id", unique = true, nullable = false)  
        14.     public Long getId() {  
        15.         return id;  
        16.     }  
        17.     public void setId(Long id) {  
        18.         this.id = id;  
        19.     }  
        20.     @Temporal(TemporalType.DATE)  
        21.     @Column(name = "add_date")  
        22.     public Date getAddDate() {  
        23.         return add_date;  
        24.     }  
        25.     public void setAddDate(Date add_date) {  
        26.         this.add_date = add_date;  
        27.     }  
        28.     @Temporal(TemporalType.DATE)  
        29.     @Column(name = "modify_date")  
        30.     public Date getModifyDate() {  
        31.         return modify_date;  
        32.     }  
        33.     public void setModifyDate(Date modify_date) {  
        34.         this.modify_date = modify_date;  
        35.     }  
        36.     @Column(name = "ctx")  
        37.     public String getCtx() {  
        38.         return ctx;  
        39.     }  
        40.     public void setCtx(String ctx) {  
        41.         this.ctx = ctx;  
        42.     }  
        43.     @ManyToOne(fetch = FetchType.LAZY)  
        44.     @JoinColumn(name = "add_user_id")  
        45.     public User getAddUser() {  
        46.         return add_user;  
        47.     }  
        48.     public void setAddUser(User add_user) {  
        49.         this.add_user = add_user;  
        50.     }  
        51.     @ManyToOne(fetch = FetchType.LAZY)  
        52.     @JoinColumn(name = "modify_user_id")  
        53.     public User getModifyUser() {  
        54.         return modify_user;  
        55.     }  
        56.     public void setModifyUser(User modify_user) {  
        57.         this.modify_user = modify_user;  
        58.     }  
        59.     @Transient  
        60.     public String getCreateUserName() {  
        61.         return createUserName;  
        62.     }  
        63.     public void setCreateUserName(String createUserName) {  
        64.         this.createUserName = createUserName;  
        65.     }  
        66. }

        开始

        插入测试1

        代码操作:

        将随机内容推特表的数据加载到内存中,然后一条条加入到推特表中,共10万条。

        关键代码:

        hibernate:

        Java代码

        1. Session session = factory.openSession();  
        2.         session.beginTransaction();  
        3.         Twitter t = null;  
        4.         Date now = new Date();  
        5.         for(String materialTwitter : materialTwitters){  
        6. //          System.out.println("materialTwitter="+materialTwitter);  
        7.             t = new Twitter();  
        8.             t.setCtx(materialTwitter);  
        9.             t.setAddDate(now);  
        10.             t.setModifyDate(now);  
        11.             t.setAddUser(null);  
        12.             t.setModifyUser(null);  
        13.             session.save(t);  
        14.         }  
        15.         session.getTransaction().commit();

        mybatis:

        Java代码

          1. Twitter t = null;  
          2. Date now = new Date();  
          3. for(String materialTwitter : materialTwitters){  
          4.             System.out.println("materialTwitter="+materialTwitter);  
          5.         t = new Twitter();  
          6.         t.setCtx(materialTwitter);  
          7.         t.setAddDate(now);  
          8.         t.setModifyDate(now);  
          9.         t.setAddUser(null);  
          10.         t.setModifyUser(null);  
          11.         msession.insert("insertTwitter", t);  
          12.    }  
          13. msession.commit();  
           TwitterMapper.xml,插入代码

          片段:

          Xml代码

            1. <insert id="insertTwitter" keyProperty="id" parameterType="org.pushio.test.show1.entity.Twitter" useGeneratedKeys="true">    
            2.         insert into twitter(ctx, add_date,modify_date) values (#{ctx},#{add_date},#{modify_date})    
            3.     </insert>

            查询测试1

            通过id从1递增到10万依次进行查询推特内容,仅输出微博内容。

            关键代码:

            hibernate:

            Java代码

              1. long cnt = 100000;  
              2.         for(long i = 1; i <= cnt; ++i){  
              3.             Twitter t = (Twitter)session.get(Twitter.class, i);  
              4.             //System.out.println("t.getCtx="+ t.getCtx() + " t.getUser.getName=" + t.getAddUser().getName());  
              5.         }  
              mybatis:

              Java代码

                1. long cnt = 100000;  
                2.     for(long i = 1; i <= cnt; ++i){  
                3.         Twitter t = (Twitter)msession.selectOne("getTwitter", i);  
                4.         //System.out.println("t.getCtx="+ t.getCtx() + " t.getUser.getName=" + t.getAddUser().getName());  
                5.     }

                查询测试2

                与查询测试1总体一样,增加微博的创建人名称字段,此处需要关联。

                其中微博对应有10万个用户。可能一部份用户重复。这里对应的用户数可能与hibernate配懒加载的情况有影响。

                此处体现了hibernate的一个方便处,可以直接通过getAddUser()可以取得user相关的字段。


                然而myBatis则需要编写新的vo,因此在测试batis时则直接在Twitter实体中增加创建人员名字成员(createUserName)。


                此处hibernate则会分别测试有懒加载,无懒加载。

                mybatis会测有默认与有缓存两者情况。

                其中mybatis的缓存机制比较难有效配置,不适用于真实业务(可能会有脏数据),在此仅供参考。

                测试时,对推特关联的用户数做了两种情况,一种是推特共关联了100个用户,也就是不同的推特也就是在100个用户内,这里的关联关系随机生成。

                另外一种是推特共关联了50万个用户,基本上50个用户的信息都会被查询出来。


                在上文“准备”中可以看到关联数据生成方式。

                关键代码:


                hibernate:

                Java代码

                1. long cnt = 100000;  
                2. for(long i = 1; i <= cnt; ++i){  
                3.             Twitter t = (Twitter)session.get(Twitter.class, i);  
                4.             t.getAddUser().getName();//加载相应字段  
                5.             //System.out.println("t.getCtx="+ t.getCtx() + " t.getUser.getName=" + t.getAddUser().getName());  
                6.         }

                急懒加载配置更改处,Twitter.java:

                Java代码

                1. @ManyToOne(fetch = FetchType.EAGER)//急加载  
                2.      //@ManyToOne(fetch = FetchType.LAZY)//懒加载  
                3. @JoinColumn(name = "add_user_id")  
                4. public User getAddUser() {  
                5.     return add_user;  
                6. }

                mybatis:

                Java代码

                1. for(long i = 1; i <= cnt; ++i){  
                2.             Twitter t = (Twitter)msession.selectOne("getTwitterHasUser", i);  
                3.             //System.out.println("t.getCtx="+ t.getCtx() + " t.getUser.getName=" + t.getCreateUserName());  
                4.         }

                TwitterMapper.xml配置:

                Xml代码

                1. <select id="getTwitterHasUser" parameterType="long"   
                2.         resultType="org.pushio.test.show1.entity.Twitter">  
                3.         select twitter.*,user.name as creteUserName from twitter,user  
                4.         where twitter.id=#{id}  
                5.             AND twitter.add_user_id=user.id  
                6.     </select>

                测试结果




                测试分析

                测试分成了插入,单表查询,关联查询。关联查询中hibernate分成三种情况进行配置。

                其中在关联字段查询中,hibernate在两种情况下,性能差异比较大。 都是在懒加载的情况下,如果推特对应的用户比较多时,则性能会比仅映射100个用户的情况要差很多。

                换而言之,如果用户数量少(关联的总用户数)时,也就是会重复查询同一个用户的情况下,则不需要对用户表做太多的查询。

                其中通过查询文档后,证明使用懒加载时,对象会以id为key做缓存,也就是查询了100个用户后,后续的用户信息使用了缓存,使性能有根本性的提高。甚至要比myBatis更高。

                如果是关联50万用户的情况下,则hibernate需要去查询50万次用户信息,并组装这50万个用户,此时性能要比myBatis性能要差,不过差异不算大,小于1ms,表示可以接受。

                其中hibernate非懒加载情况下与myBatis性能差异也是相对其他测试较大,平均值小于1ms。

                这个差异的原因主要在于,myBatis加载的字段很干净,没有太多多余的字段,直接映身入关联中。反观hibernate则将整个表的字都会加载到对象中,其中还包括关联的user字段。

                hibernate这种情况下有好有坏,要看具体的场景,对于管理平台,需要展现的信息较多,并发要求不高时,hibernate比较有优势。

                然而在一些小活动,互联网网站,高并发情况下,hibernate的方案太不太适合,myBatis+VO则是首选。


                测试总结

                总体初观,myBatis在所有情况下,特别是插入与单表查询,都会微微优于hibernate。不过差异情况并不明显,可以基本忽略差异。

                差异比较大的是关联查询时,hibernate为了保证POJO的数据完整性,需要将关联的数据加载,需要额外地查询更多的数据。这里hibernate并没有提供相应的灵活性。

                关联时一个差异比较大的地方则是懒加载特性。其中hibernate可以特别地利用POJO完整性来进行缓存,可以在一级与二级缓存上保存对象,如果对单一个对象查询比较多的话,会有很明显的性能效益。

                以后关于单对象关联时,可以通过懒加载加二级缓存的方式来提升性能。

                最后,数据查询的性能与orm框架关无太大的关系,因为orm主要帮助开发人员将关系数据转化成对象型数据模型,对代码的深析上来

                看,hibernate设计得比较重量级,对开发来说可以算是重新开发了一个数据库,不让开发去过多关心数据库的特性,直接在hibernate基础上进 行开发,执行上分为了sql生成,数据封装等过程,这里花了大量的时间。然而myBatis则比直接,主要是做关联与输出字段之间的一个映射。其中sql 基本是已经写好,直接做替换则可,不需要像hibernate那样去动态生成整条sql语句。

                好在hibernate在这阶段已经优化得比较好,没有比myBatis在性能上差异太多,但是在开发效率上,可扩展性上相对myBatis来说好太多。

                最后的最后,关于myBatis缓存,hibernate查询缓等,后续会再专门做一篇测试。


                关于缓存配置


                myBatis相对Hibernate 等封装较为严密的ORM 实现而言,因为hibernate对数据对象的操作实现了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭的封装实现,因此对缓存的操作难以做到完全的自动化同步。

                以上的缓存配置测试仅为性能上的分析,没有加入可用性上的情况,因为myBatis直接配置缓存的话,可能会出现脏数据,。

                在关联查询数据的情况下,hiberntae的懒加载配二级缓存是个比较好的方案(无脏数据),也是与myBatis相比有比较明显的优势。此情景下,性能与myBatis持平。

                在真实情况下,myBatis可能不会在这个地方上配置缓存,会出现脏数据的情况,因而很有可能在此hibernate性能会更好。