Performance Comparison of Hibernate and MyBatis for Insert and Query Operations
This article presents a systematic performance test of Hibernate and MyBatis covering bulk inserts, single‑table queries, and various association queries on MySQL, analyzes the impact of lazy versus eager loading, caching, and data volume, and provides practical recommendations for choosing the appropriate ORM framework.
Introduction Due to differences between programming paradigms and relational database design, ORM frameworks such as Hibernate and MyBatis have emerged. Their core purpose is to map relational data to objects. This article evaluates their performance under identical scenarios to help developers choose the right tool.
Test Objectives The tests aim to identify performance differences across scenarios, quantify the gap when the same workload is used, and reveal the strengths and weaknesses of each framework in various use cases.
Test Approach Four test groups were defined: single‑table insert, association insert, single‑table query, and multi‑table query. Each group was executed twice – once with default parameters and once with tuned parameters – to enable horizontal and vertical comparisons. Sample sizes were kept large (≥100 000 records) to reduce statistical error.
Test Outline
Insert Test 1: Insert 100 000 rows into twitter table.
Query Test 1: Query 100 000 rows by primary key, output only the tweet content.
Query Test 2: Same as Test 1 but also fetch the tweet creator’s name (requires a join).
Query Test 3: Query 100 000 rows from a 50 000 × 500 000 association, output the same fields.
Preparation
Database: MySQL 5.6
Table definitions:
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; 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;Test Data Preparation
Two source tables were created: twitter: empty initially. user: 500 000 random usernames.
Material table material_twitter containing 100 000 random tweet strings (no id).
Data generation SQL (100 000 associations):
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_twitter;Data generation SQL (500 000 associations):
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_twitter;Entity Code (Hibernate)
@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 d) { this.add_date = d; }
@Temporal(TemporalType.DATE)
@Column(name = "modify_date")
public Date getModifyDate() { return modify_date; }
public void setModifyDate(Date d) { this.modify_date = d; }
@Column(name = "ctx")
public String getCtx() { return ctx; }
public void setCtx(String c) { this.ctx = c; }
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "add_user_id")
public User getAddUser() { return add_user; }
public void setAddUser(User u) { this.add_user = u; }
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modify_user_id")
public User getModifyUser() { return modify_user; }
public void setModifyUser(User u) { this.modify_user = u; }
@Transient
public String getCreateUserName() { return createUserName; }
public void setCreateUserName(String n) { this.createUserName = n; }
}Insert Test 1 – Hibernate
Session session = factory.openSession();
session.beginTransaction();
Twitter t = null;
Date now = new Date();
for (String materialTwitter : materialTwitters) {
t = new Twitter();
t.setCtx(materialTwitter);
t.setAddDate(now);
t.setModifyDate(now);
t.setAddUser(null);
t.setModifyUser(null);
session.save(t);
}
session.getTransaction().commit();Insert Test 1 – MyBatis
Twitter t = null;
Date now = new Date();
for (String materialTwitter : materialTwitters) {
t = new Twitter();
t.setCtx(materialTwitter);
t.setAddDate(now);
t.setModifyDate(now);
t.setAddUser(null);
t.setModifyUser(null);
msession.insert("insertTwitter", t);
}
msession.commit();MyBatis mapper fragment for insertion:
<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>Query Test 1 – 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());
}Query Test 1 – 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());
}Query Test 2 – Hibernate (lazy vs eager)
long cnt = 100000;
for (long i = 1; i <= cnt; ++i) {
Twitter t = (Twitter) session.get(Twitter.class, i);
t.getAddUser().getName(); // loads associated user
}To switch to eager loading, modify the entity:
@ManyToOne(fetch = FetchType.EAGER) // eager loading
@JoinColumn(name = "add_user_id")
public User getAddUser() { return add_user; }Query Test 2 – 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());
}MyBatis mapper for the join query:
<select id="getTwitterHasUser" parameterType="long" resultType="org.pushio.test.show1.entity.Twitter">
select twitter.*, user.name as createUserName
from twitter, user
where twitter.id = #{id}
AND twitter.add_user_id = user.id
</select>Test Results
An image (omitted) shows the measured latencies. Overall, insert and single‑table queries show only marginal differences (sub‑millisecond). Association queries reveal larger gaps: with a small user pool (100 users) Hibernate’s lazy loading caches user objects and outperforms MyBatis; with 500 000 distinct users, Hibernate incurs many extra selects and becomes slightly slower, though the gap remains under 1 ms.
Analysis
Hibernate benefits from first‑level (session) caching when the same user is queried repeatedly, giving it an edge in low‑cardinality associations.
MyBatis retrieves only the required columns, avoiding the overhead of loading full entity graphs.
When the association cardinality is high, both frameworks perform similarly; the choice then depends on development convenience and cache strategy.
Conclusion
MyBatis slightly leads in raw speed for inserts and simple queries, while Hibernate offers richer object‑oriented features and automatic caching that can outweigh the minor performance penalty in many management‑type applications. For high‑concurrency, lightweight services, MyBatis + explicit VO is preferable; for feature‑rich back‑office systems, Hibernate’s convenience and caching can be advantageous.
Cache Configuration Note
Hibernate’s second‑level cache combined with lazy loading provides a clean, low‑overhead solution for repeated entity access, whereas MyBatis cache configuration is more manual and prone to stale data if not carefully managed.
--- End of article ---
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
