动态

详情 返回 返回

Hibernate 和 MyBatis 哪個更好用 - 动态 详情

由於編程思想與數據庫的設計模式不同,生出了一些 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培訓

user avatar nidexiaoxiongruantangna 头像
点赞 1 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.