因为编程思维与数据库的设计模式不同,生出了一些 ORM 框架。外围都是将关系型数据库和数据转成对象型。以后风行的计划有 Hibernate 与 myBatis。两者各有优劣。竞争强烈,其中一个比拟重要的思考的中央就是性能。因而笔者通过各种试验,测出两个在雷同情景下的性能相干的指数,供大家参考。
测试指标
以下测试须要确定几点内容:性能差别的场景;性能不在同场景下差别比;找出各架框优劣,各种状况下的体现,实用场景。
测试思路
测试总体分成:单表插入,关联插入,单表查问,多表查问。测试分两轮,同场景下默认参数做一轮,调优做强一轮,横纵比照剖析了。测试中尽保障输入输出的一致性。样本量尽可能大, 达到 10 万级别以上,缩小统计误差。
测试提纲
具体的场景状况下插入测试 1:10 万条记录插入。查问测试 1:100 万数据中单表通过 id 查问 100000 次, 无关联字段。查问测试 2:100 万数据中单表通过 id 查问 100000 次, 输入关联对象字段。查问测试 3:100 万 *50 万关联数据中查问 100000 次, 两者输入雷同字段。
筹备
数据库:mysql 5.6 表格设计:twitter: 推特
CREATE TABLE `twitter` (`id` bigint(20) NOT NULL AUTO_INCREMENT,
`add_date` datetime DEFAULT NULL,
`modify_date` datetime DEFAULT NULL,
`ctx` varchar(255) NOT NULL,
`add_user_id` bigint(20) DEFAULT NULL,
`modify_user_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `UPDATE_USER_FORI` (`modify_user_id`),
KEY `ADD_USER_FORI` (`add_user_id`),
CONSTRAINT `ADD_USER_FORI` FOREIGN KEY (`add_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL,
CONSTRAINT `UPDATE_USER_FORI` FOREIGN KEY (`modify_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=1048561 DEFAULT CHARSET=utf8
user: 用户
CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=524281 DEFAULT CHARSET=utf8
测试数据筹备:
表一:twitter
无数据。
表二:user
50 万个随机的用户名。
随机内容推特表 (material_twitter) 无 id,仅有随机字符串内容,共 10 万条。用于插入控推特表。
生成数据代码, 关联 100 个用户:
insert into twitter(ctx,add_user_id,modify_user_id,add_date,modify_date)SELECT name,ROUND(RAND()100)+1,ROUND(RAND()100)+1,’2016-12-31′,’2016-12-31’from MATERIAL
生成数据代码, 关联 500000 个用户:
insert into twitter(ctx,add_user_id,modify_user_id,add_date,modify_date)SELECT name,ROUND(RAND()500000)+1,ROUND(RAND()500000)+1,’2016-12-31′,’2016-12-31’from MATERIAL
实体代码
@Entity
@Table(name = "twitter")
public class Twitter implements java.io.Serializable{
private Long id;
private Date add_date;
private Date modify_date;
private String ctx;
private User add_user;
private User modify_user;
private String createUserName;
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
@Temporal(TemporalType.DATE)
@Column(name = "add_date")
public Date getAddDate() {return add_date;}
public void setAddDate(Date add_date) {this.add_date = add_date;}
@Temporal(TemporalType.DATE)
@Column(name = "modify_date")
public Date getModifyDate() {return modify_date;}
public void setModifyDate(Date modify_date) {this.modify_date = modify_date;}
@Column(name = "ctx")
public String getCtx() {return ctx;}
public void setCtx(String ctx) {this.ctx = ctx;}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "add_user_id")
public User getAddUser() {return add_user;}
public void setAddUser(User add_user) {this.add_user = add_user;}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modify_user_id")
public User getModifyUser() {return modify_user;}
public void setModifyUser(User modify_user) {this.modify_user = modify_user;}
@Transient
public String getCreateUserName() {return createUserName;}
public void setCreateUserName(String createUserName) {this.createUserName = createUserName;}
}
开始
插入测试 1
代码操作:将随机内容推特表的数据加载到内存中, 而后一条条退出到推特表中, 共 10 万条。
要害代码:hibernate:
Session session = factory.openSession();
session.beginTransaction();
Twitter t = null;
Date now = new Date();
for(String materialTwitter : materialTwitters){// System.out.println("materialTwitter="+materialTwitter);
t = new Twitter();
t.setCtx(materialTwitter);
t.setAddDate(now);
t.setModifyDate(now);
t.setAddUser(null);
t.setModifyUser(null);
session.save(t);
}
session.getTransaction().commit();
mybatis:
Twitter t = null;
Date now = new Date();
for(String materialTwitter : materialTwitters){// System.out.println("materialTwitter="+materialTwitter);
t = new Twitter();
t.setCtx(materialTwitter);
t.setAddDate(now);
t.setModifyDate(now);
t.setAddUser(null);
t.setModifyUser(null);
msession.insert("insertTwitter", t);
}
msession.commit();
TwitterMapper.xml, 插入代码片段:
<insert id="insertTwitter" keyProperty="id" parameterType="org.pushio.test.show1.entity.Twitter" useGeneratedKeys="true">
insert into twitter(ctx, add_date,modify_date) values (#{ctx},#{add_date},#{modify_date})
</insert>
查问测试 1
通过 id 从 1 递增到 10 万顺次进行查问推特内容,仅输入微博内容。
要害代码:
hibernate:
long cnt = 100000;
for(long i = 1; i <= cnt; ++i){Twitter t = (Twitter)session.get(Twitter.class, i);
//System.out.println("t.getCtx="+ t.getCtx() + "t.getUser.getName=" + t.getAddUser().getName());
}
mybatis:
long cnt = 100000;
for(long i = 1; i <= cnt; ++i){Twitter t = (Twitter)msession.selectOne("getTwitter", i);
//System.out.println("t.getCtx="+ t.getCtx() + "t.getUser.getName=" + t.getAddUser().getName());
}
查问测试 2
与查问测试 1 总体一样,减少微博的创建人名称字段, 此处须要关联。其中微博对应有 10 万个用户。可能一部份用户反复。这里对应的用户数可能与 hibernate 配懒加载的状况有影响。
此处体现了 hibernate 的一个不便处,能够间接通过 getAddUser()能够获得 user 相干的字段。
然而 myBatis 则须要编写新的 vo,因而在测试 batis 时则间接在 Twitter 实体中减少创立人员名字成员(createUserName)。
此处 hibernate 则会别离测试有懒加载,无懒加载。mybatis 会测有默认与有缓存两者状况。
其中 mybatis 的缓存机制比拟难无效配置,不适用于实在业务(可能会有脏数据),在此仅供参考。
测试时,对推特关联的用户数做了两种状况,一种是推特共关联了 100 个用户,也就是不同的推特也就是在 100 个用户内,这里的关联关系随机生成。另外一种是推特共关联了 50 万个用户,基本上 50 个用户的信息都会被查问进去。
在上文“筹备”中能够看到关联数据生成形式。
要害代码:
hibernate:
long cnt = 100000;
for(long i = 1; i <= cnt; ++i){Twitter t = (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")
public User getAddUser() {return add_user;}
mybatis:
for(long i = 1; i <= cnt; ++i){Twitter t = (Twitter)msession.selectOne("getTwitterHasUser", i);
//System.out.println("t.getCtx="+ t.getCtx() + "t.getUser.getName=" + t.getCreateUserName());
}
TwitterMapper.xml 配置:
<select id="getTwitterHasUser" parameterType="long"
resultType="org.pushio.test.show1.entity.Twitter">
select twitter.*,user.name as creteUserName from twitter,user
where twitter.id=#{id}
AND twitter.add_user_id=user.id
</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 性能会更好。
关键词:java 培训