当前位置: 首页 > 科技观察

说实话,Hibernate和MyBatis哪个更好用?

时间:2023-03-19 01:07:26 科技观察

前言由于编程思想和数据库设计模式的不同,诞生了一些ORM框架。核心是将关系数据库和数据转换为对象类型。目前流行的解决方案包括Hibernate和myBatis。两者各有利弊。竞争是激烈的,更重要的考虑因素之一是性能。因此,笔者通过各种实验,在同一场景下测得了两个性能相关的指标,供大家参考。测试目标下面的测试需要确定几个点:有性能差异的场景;不同场景下的性能差异;找出各个框架的优缺点,各种情况下的表现,以及适用场景。测试思路整体测试分为:单表插入、关联插入、单表查询、多表查询。测试分为两轮,一轮同场景默认参数,一轮调优强化,横向纵向对比分析。测试时保证输入输出的一致性。样本量应尽可能大,在100,000个级别以上,以减少统计误差。在测试大纲的具体场景中插入测试1:插入10万条记录。查询测试一:100万条数据,单表通过id查询10万次,没有关联字段。查询测试2:100万条数据,单表通过id查询10万次,输出关联的object字段。查询测试3:在100万*50万条关联数据中查询10万次,都输出相同的字段。准备数据库:mysql5.6表设计:twitter:twitterCREATETABLE`twitter`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`add_date`datetimeDEFAULTNULL,`modify_date`datetimeDEFAULTNULL,`ctx`varchar(255)NOTNULL,`add_user_id`bigint(20)DEFAULTNULL,`modify_user_id`bigint(20)DEFAULTNULL,PRIMARYKEY(`id`),KEY`UPDATE_USER_FORI`(`modify_user_id`),KEY`ADD_USER_FORI`(`add_user_id`),CONSTRAINT`ADD_USER_FORI`FOREIGNKEY(`add_user_id`)REFERENCES`user`(`id`)ONDELETESETNULL,CONSTRAINT`UPDATE_USER_FORI`FOREIGNKEY(`modify_user_id`)REFERENCES`user`(`id`)ONDELETESETNULL)ENGINE=InnoDBAUTO_INCREMENT=1048561DEFAULTCHARSET=utf8user:UserCREATETABLE`bigint`(`id`(20)NOTNULLAUTO_INCREMENT,`name`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=524281DEFAULTCHARSET=utf8测试数据准备:表1:twitter没有数据。表2:user500,000个随机用户名。随机内容推特表(material_twitter)没有id,只有随机字符串内容,共10万条。用于插入到twitter表中。生成数据代码,关联100个用户:insertintotwitter(ctx,add_user_id,modify_user_id,add_date,modify_date)SELECTname,ROUND(RAND()*100)+1,ROUND(RAND()*100)+1,'2016-12-31','2016-12-31'来自MATERIAL生成数据代码,关联500000个用户:insertintotwitter(ctx,add_user_id,modify_user_id,add_date,modify_date)SELECTname,ROUND(RAND()*500000)+1,ROUND(RAND()*500000)+1,'2016-12-31','2016-12-31'fromMATERIAL实体代码@Entity@Table(name="twitter")publicclassTwitterimplementsjava.io.Serializable{privateLongid;privateDateadd_date;privateDatemodify_date;privateStringctx;privateUseradd_user;privateUsermodify_user;privateStringcreateUserName;@Id@GeneratedValue(strategy=IDENTITY)@Column(name="id",unique=true,nullable=false)publicLonggetId(){returnid;}publicvoidsetId(Longid){this.id=id;}@Temporal(TemporalType.DATE)@Column(name="add_date")publicDategetAddDate(){returnadd_date;}publicvoidsetAddDate(Dateadd_date){this.add_date=add_date;}@Temporal(TemporalType.DATE)@Column(name="modify_date")publicDategetModifyDate(){returnmodify_date;}publicvoidsetModifyDate(Datemodify_date){this.modify_date=modify_date;}@Column(name="ctx")publicStringgetCtx(){returnctx;}publicvoidsetCtx(Stringctx){this.ctx=ctx;@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="add_user_id")publicUsergetAddUser(){returnadd_user;}publicvoidsetAddUser(Useradd_user){this.add_user=add_user;}@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="modify_user_id")publicUsergetModifyUser(){returnmodify_user;}publicvoidsetModifyUser(Usermodify_user){this.modify_user=modify_user;}@TransientpublicStringgetCreateUserName(){returncreateUserName;}publicvoidsetCreateUserName(StringcreateUserName){this.createUserName=createUserName;}}开始插入测试1代码操作:将随机内容推特表的数据加载到内存中,然后对推特表逐一添加,共100000条关键代码:hibernate:Sessionsession=factory.openSession();session.beginTransaction();Twitter=null;Datenow=newDate();for(StringmaterialTwitter:materialTwitters){//System.out.println("materialTwitter="+materialTwitter);t=newTwitter();t.setCtx(materialTwitter);t.setAddDate(现在);t.setModifyDate(现在);t.setAddUser(null);t.setModifyUser(null);session.save(t);}session.getTransaction().commit();mybatis:Twitter=null;Datenow=newDate();for(StringmaterialTwitter:materialTwitters){//System.out.println("materialTwitter="+materialTwitter);t=newTwitter();t.setCtx(materialTwitter);t.setAddDate(now);t.setModifyDate(now);t.setAddUser(null);t.setModifyUser(null);msession.insert("insertTwitter",t);}msession.commit();TwitterMapper.xml,插入代码片段:insertintotwitter(ctx,add_date,修改日期)values(#{ctx},#{add_date},#{modify_date})查询测试1通过id从1增加到100000查询推特内容,只输出微博内容的关键代码:hibernate:longcnt=100000;for(longi=1;i<=cnt;++i){Twitter=(Twitter)session.get(Twitter.class,i);//System.out.println("t.getCtx="+t.getCtx()+"t.getUser.getName="+t.getAddUser().getName());}mybatis:longcnt=100000;for(longi=1;i<=cnt;++i){Twittert=(Twitter)msession.selectOne("getTwitter",i);//System.out.println("t.getCtx="+t.getCtx()+"t.getUser.getName="+t.getAddUser().getName());}查询测试2和查询测试1大体相同,增加了微博创建者姓名字段,这里需要关联。其中微博对应10万用户。一些用户可能会重复。这里对应的用户数可能会对hibernatewithlazyloading的情况产生影响。这体现了hibernate的一个方便之处,可以直接通过getAddUser()获取用户相关的字段。但是myBatis需要写一个新的vo,所以在测试batis的时候,直接在推特实体中添加creatorname成员(createUserName)。这里hibernate会测试是否有懒加载或者没有懒加载。mybatis会同时测试默认和缓存。其中mybatis的缓存机制很难有效配置,不适合真正的业务(可能有脏数据)。这仅供参考。在测试过程中,针对与Twitter关联的用户数量做了两个案例。一种是推特一共关联了100个用户,即100个用户以内不同的推特,这里的关联是随机产生的。另一个是推特一共关联了50万用户,基本上会查询50个用户的信息。您可以在上面的“准备”中看到关联数据是如何生成的。关键代码:hibernate:longcnt=100000;for(longi=1;i<=cnt;++i){Twitter=(Twitter)session.get(Twitter.class,i);t.getAddUser().getName();//加载对应字段//System.out.println("t.getCtx="+t.getCtx()+"t.getUser.getName="+t.getAddUser().getName());}急加载配置变化,Twitter.java:@ManyToOne(fetch=FetchType.EAGER)//紧急加载//@ManyToOne(fetch=FetchType.LAZY)//延迟加载@JoinColumn(name="add_user_id")publicUsergetAddUser(){returnadd_user;}mybatis:for(longi=1;i<=cnt;++i){Twitter=(Twitter)msession.selectOne("getTwitterHasUser",i);//System.out.println("t.getCtx="+t.getCtx()+"t.getUser.getName="+t.getCreateUserName());}TwitterMapper.xml配置:selecttwitter.*,user.nameascreteUserNamefromtwitter,userwheretwitter.id=#{id}ANDtwitter.add_user_id=user.id测试结果测试分析测试分为插入、单表查询、关联查询。在关系查询中,hibernate分为三种情况进行配置。其中,在关联字段查询上,hibernate在这两种情况下的性能差异比较大。都是在懒加载的情况下。如果推特对应的用户很多,性能会比只映射100个用户差很多。也就是说,如果用户数量少(关联用户总数),即同一个用户会被重复查询,没必要对user表做太多的查询。其中,查询文档后证明,使用懒加载时,会以id为key缓存对象,即查询100个用户后,缓存后续的用户信息,从根本上提高了表现。甚至高于myBatis。如果关联50万个用户,hibernate需要查询50万个用户信息,并把这50万个用户组装起来。这时候性能比myBatis差了点,但是相差不大,不到1ms,可以接受。其中hibernate和myBatis在非懒加载情况下的性能对比其他测试也比较大,均值小于1ms。造成这种差异的主要原因是myBatis加载的字段非常干净,没有太多多余的字段,直接体现在关联上。相反,hibernate会将整个表的话加载到对象中,包括关联的用户字段。这种情况下,hibernate是好是坏,要看具体场景。对于管理平台,需要展示的信息较多,在并发要求不高的情况下,hibernate更有优势。但是在一些小活动、网站、高并发的情况下,hibernate方案并不适合,myBatis+VO是首选。测试总结总体而言,myBatis在所有情况下都略优于hibernate,尤其是插入和单表查询。不过区别并不明显,基本可以忽略不计。最大的区别在于,在关系查询中,为了保证POJO的数据完整性,hibernate需要加载关联数据,额外查询更多的数据。这里hibernate没有提供相应的灵活性。关联的一个很大区别是延迟加载功能。其中hibernate可以专门使用POJO完整性进行缓存,可以将对象保存在一级缓存和二级缓存中。如果对单个对象进行更多的查询,将会有明显的性能优势。以后在单对象关联的时候,可以通过懒加载和二级缓存来提升性能。最后,数据查询的性能与orm框架关系不大,因为orm主要帮助开发者将关系型数据转化为基于对象的数据模型。从代码深入分析,hibernate被设计成重量级的,这对于开发来说是非常重要的。可以说是重新开发了一个数据库,开发也不允许太在意数据库的特性,直接基于hibernate开发。执行分为sql生成、数据封装等过程,大量的时间花在了这里。而myBatis更直接,主要是做associations和outputfields之间的映射。其中sql基本已经写好了,直接替换即可。没必要像hibernate那样动态生成整条sql语句。好在hibernate现阶段已经优化过了。它在性能上和myBatis相差不大,但是在开发效率和扩展性上,却比myBatis好很多。最后,关于myBatis的缓存,hibernate的查询延迟等,后面再做测试。关于缓存配置,myBatis是对Hibernate等ORM实现进行了比较严密的封装,因为hibernate对数据对象操作进行了比较严格的封装,可以在其作用范围内保证缓存同步,而ibatis提供了半封闭的封装实现,所以很难实现缓存操作的完全自动同步。以上缓存配置测试只是为了性能分析,没有增加可用性,因为如果myBatis直接配置缓存,可能会出现脏数据。在关联查询数据的情况下,hiberntae的带有二级缓存的懒加载是一个比较好的解决方案(没有脏数据),与myBatis相比也有明显的优势。在这种情况下,性能与myBatis相当。实际情况下,myBatis可能不会在这个地方配置缓存,就会有脏数据,所以hibernate在这里的表现很可能更好。