Which is better, Hibernate or MyBatis

Posted by mhewall on Fri, 18 Feb 2022 16:33:09 +0100

Because the programming idea is different from the design mode of database, some ORM frameworks have been born. The core is to turn relational database and data into object-oriented. Hibernate and myBatis are currently popular solutions. Both have their own advantages and disadvantages. The competition is fierce, and one of the more important considerations is performance. Therefore, through various experiments, the author measures two performance related indexes in the same scenario for your reference.

Test target
The following tests need to determine the following contents: scenarios of performance differences; Performance difference ratio under different scenarios; Find out the advantages and disadvantages of each frame, the performance in various situations and the applicable scenes.

Test ideas
The overall test is divided into: single table insertion, association insertion, single table query and multi table query. The test is divided into two rounds, one round for default parameters and one round for tuning and strengthening in the same scenario. The horizontal and vertical comparison and analysis are carried out. Ensure the consistency of input and output during the test. The sample size shall be as large as possible to reach more than 100000 level, so as to reduce the statistical error.

Test outline
Insertion test under specific scenario: 1:100000 records are inserted. Query test 1: a single table in 1 million data is queried 100000 times through id, and there is no associated field. Query test 2: single table in 1 million data is queried 100000 times through id, and the associated object field is output. Query test 3: query 100000 times in 1 million * 500000 related data, and both output the same fields.

prepare
Database: mysql 5.6 table design: twitter: 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: 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

Test data preparation:
Table 1: twitter

No data.

Table 2: user

500000 random user names.

The random content twitter table (material_twitter) has no id, only random string content, a total of 100000. Used to insert tweet control table.

Generate 100 user association Codes:

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

Generate data code and associate 500000 users:

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 code

@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;
  }
  
}

start
Insert test 1
Code operation: load the data of random content twitter table into memory, and then add them to twitter table one by one, a total of 100000.

Key code: 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 code snippet:

<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
Query twitter content by increasing id from 1 to 100000, and only output microblog content.

Key codes:

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());
    }

Query test 2
As in query test 1, the creator name field of microblog is added, and association is required here. The microblog has 100000 users. Some users may repeat. The corresponding number of users here may have an impact on the lazy loading of hibernate.

Here is a convenience of hibernate. You can get user related fields directly through getAddUser().

However, myBatis needs to write a new vo, so when testing batis, the creator name member (createUserName) is directly added to the Twitter entity.

Here, hibernate will test lazy loading and no lazy loading respectively. mybatis will detect both default and cache.

Among them, the caching mechanism of mybatis is difficult to configure effectively and is not suitable for real business (there may be dirty data). Here is for reference only.

During the test, there are two situations for the number of users associated with Twitter. One is that Twitter is associated with 100 users, that is, different Twitter is within 100 users, and the association relationship here is generated randomly. The other is that Twitter is associated with 500000 users, and basically the information of 50 users will be queried.

You can see the generation method of associated data in "preparation" above.

Key codes:

hibernate:

long cnt = 100000;
for(long i = 1; i <= cnt; ++i){
      Twitter t = (Twitter)session.get(Twitter.class, i);
      t.getAddUser().getName();//Load corresponding fields
      //System.out.println("t.getCtx="+ t.getCtx() + " t.getUser.getName=" + t.getAddUser().getName());
    }

Quick lazy loading configuration changes, twitter java:

@ManyToOne(fetch = FetchType.EAGER)//Emergency loading
      //@ManyToOne(fetch = FetchType.LAZY) / / lazy loading
  @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 configuration:

<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>

test result

Test analysis
The test is divided into insert, single table query and association query. hibernate is configured in three cases in association query. In the associated field query, the performance of hibernate is quite different in the two cases. In the case of lazy loading, if there are more users corresponding to twitter, the performance will be much worse than that of mapping only 100 users.

In other words, if the number of users is small (the total number of associated users), that is, the same user will be queried repeatedly, there is no need to query the user table too much. After querying the document, it is proved that when lazy loading is used, the object will be cached with id as the key, that is, after querying 100 users, the subsequent user information uses the cache, which fundamentally improves the performance. Even higher than myBatis.

If 500000 users are associated, hibernate needs to query 500000 user information and assemble these 500000 users. At this time, the performance is worse than that of myBatis, but the difference is not large, less than 1ms, which means it is acceptable. The performance difference between hibernate and myBatis under non lazy loading is also relatively large compared with other tests, and the average value is less than 1ms.

The main reason for this difference is that the fields loaded by myBatis are very clean, and there are not too many redundant fields, which are directly reflected in the association. In contrast, hibernate will load the words of the whole table into the object, including the associated user field.

In this case, hibernate is good or bad. It depends on the specific scenario. For the management platform, it needs to display more information. When the concurrency requirements are not high, hibernate has advantages. However, in some small activities, Internet websites and high concurrency, the scheme of Hibernate is not suitable, and myBatis+VO is the first choice.

Test summary
At first glance, myBatis is slightly better than hibernate in all cases, especially insertion and single table query. However, the difference is not obvious, and the difference can be basically ignored. The big difference is that in order to ensure the data integrity of POJO, hibernate needs to load the associated data and query more data additionally. Hibernate does not provide corresponding flexibility here.

A big difference in association is the lazy loading feature. Among them, hibernate can make special use of POJO integrity for caching, and can save objects in the primary and secondary caches. If there are more queries on a single object, it will have obvious performance benefits. In the future, for single object association, lazy loading and L2 cache can be used to improve the performance.

Finally, the performance of data query has little to do with the orm framework, because orm mainly helps developers transform relational data into object-oriented data model. From the perspective of in-depth analysis of the code, hibernate is designed to be relatively heavyweight. For development, it can be regarded as redeveloping a database, so that developers do not pay much attention to the characteristics of the database, It is developed directly on the basis of hibernate. The execution is divided into sql generation, data encapsulation and other processes. It takes a lot of time here.

However, myBatis is more direct, mainly a mapping between association and output fields. The sql is basically written and can be replaced directly. There is no need to dynamically generate the whole sql statement like hibernate.

Fortunately, hibernate has been optimized well at this stage, and there is no much difference in performance than myBatis, but in terms of development efficiency and scalability, it is much better than myBatis. Finally, about myBatis cache, hibernate query cache, etc., we will do a special test later.

About cache configuration
myBatis is more tightly encapsulated than hibernate and other ORM implementations, because hibernate implements a more tightly encapsulated operation of data objects, which can ensure the cache synchronization within its scope of action, while ibatis provides a semi closed encapsulation implementation, so it is difficult to achieve complete automatic synchronization of cache operation. The above cache configuration test is only for performance analysis and does not include availability, because dirty data may occur if myBatis directly configures the cache.

In the case of associated query data, hibernate's lazy loading with L2 cache is a better scheme (no dirty data), and it also has obvious advantages compared with myBatis. In this scenario, the performance is the same as myBatis.

In the real case, myBatis may not configure the cache in this place, and there will be dirty data. Therefore, it is likely that the hibernate performance will be better here.

key word: java training

Topics: Hibernate Mybatis