很開心在上周末下班前一起和客戶端的.NET架構師解決了開發人員的NHibernate交易使用問題,好久沒用Hibernate這個老牌ORM武器了,連開保險上膛都很生疏,來筆記Hibernate問題解決,順便回憶。
客戶端開發人員的問題是在同一個Transaction中,有三個資料庫的操作,但後面的操作無法讀取到同一個Transaction先前寫入的資料。
用SQL語言來說就是..
BEGIN TRAN
--STEP01
INSERT INTO T1 VALUES ('A')
--STEP02
SELECT * FROM T1
IF @@ROWCOUNT = 0
BEGIN
PRINT '開發人員報案的錯誤'
END
--STEP03
INSERT INTO T1 VALUES ('B')
COMMIT
我們先收集了客戶使用NHibernate的配置: 出問題的環境是SQL Server 2008資料庫,在資料庫連線部分,客戶使用NHibernate的stateless Session,NHibernate的版本是4.0.3.4000。
模擬問題環境(SQL資料庫)
打開管理工具,建立測試用的資料庫及資料表
CREATE Database HibernateDb
use HibernateDb
CREATE TABLE [dbo].[POKERS1](
[ID] [int] NOT NULL,
[NAME] [varchar](20) NULL,
[TITLE] [varchar](10) NULL,
[COLOR] [varchar](10) NULL)
CREATE TABLE [dbo].[POKERS2](
[ID] [int] identity NOT NULL,
[NAME] [varchar](20) NULL,
[TITLE] [varchar](10) NULL,
[COLOR] [varchar](10) NULL)
模擬問題環境(Visual Studio)
1.新增一個名稱為NHibernateConsole的主控台應用程式專案
2.Nuget安裝Hibernate 套件(專案按右鍵,選管理Nuget),打開NuGet package manager搜尋Hibernate,然後按一下安裝。
3.手動在專案根目錄新增Hibernate組態檔案hibernate.cfg.xml,然後在組態檔案內先加上1個mapping file的設定。
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">
NHibernate.Connection.DriverConnectionProvider
</property>
<property name="connection.driver_class">
NHibernate.Driver.SqlClientDriver
</property>
<property name="connection.connection_string">
Server=STANLEY14\SQL2014;database=HibernateDb;Integrated Security=SSPI;
</property>
<property name="dialect">
NHibernate.Dialect.MsSql2012Dialect
</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="use_sql_comments">true</property>
<!-- Mapping files -->
<mapping file="Mappings\Poker1.hbm.xml" />
</session-factory>
</hibernate-configuration>
4.新增Models資料夾,並建立名稱為Poker1.cs的Model
namespace NHibernateConsole.Models
{
public class Poker1
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Title { get; set; }
public virtual string Color { get; set; }
}
}
5.新增Mappings資料夾,並建立名稱為Poker1.hbm.xml的XML文件
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="NHibernateConsole" namespace="NHibernateConsole.Models">
<class name="Poker1" table="Pokers1" dynamic-update="true" >
<cache usage="read-write"/>
<id name="Id" column="Id" type="int">
</id>
<property name="Name" />
<property name="Title" />
<property name="Color" />
</class>
</hibernate-mapping>
6.檔案屬性設定為一律複製
7.新增NhibernateSession.cs類別處理db session
using NHibernate;
using NHibernate.Cfg;
namespace NHibernateConsole
{
public class NHibernateSession
{
public static ISession OpenSession()
{
var configuration = new Configuration();
configuration.Configure();
ISessionFactory sessionFactory = configuration.BuildSessionFactory();
return sessionFactory.OpenSession();
}
public static IStatelessSession OpenStateLessSession()
{
var configuration = new Configuration();
configuration.Configure();
ISessionFactory sessionFactory = configuration.BuildSessionFactory();
return sessionFactory.OpenStatelessSession();
}
}
}
8.打開program.cs
static void Main(string[] args)
{
using (IStatelessSession session = NHibernateSession.OpenStateLessSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Poker1 p1 = new Poker1 { Id = 1, Name = "Elsa", Title = "Princess", Color = "Disney" };
session.Insert(p1);
Poker1 p2 = new Poker1 { Id = 2, Name = "Belle", Title = "Princess", Color = "Disney" };
session.Insert(p2);
//開發人員報案事發地點
var result = session.QueryOver<Poker1>().Where(x => x.Title == "Princess").List();
Console.WriteLine($"查詢筆數:{result.Count}");
foreach (var item in result)
{
Console.WriteLine("公主:" + item.Name);
}
Poker1 p3 = new Poker1 { Id = 3, Name = "Anna", Title = "Princess", Color = "Disney" };
session.Insert(p3);
transaction.Commit();
}
}
Console.WriteLine("寫入成功");
Console.ReadKey();
}
經過了一些步驟之後,可以來測試了~~~
測試
偵錯中斷點先設定在program.cs 的foreach哪一行,顯示的資料集合筆數果然是0筆資料
診斷工具(intellitrace事件)只偵測到一次ADO.NET 的事件(Reader)
問題原因
後來發現,因為使用了stateless session,預設的batch size是20,她會等到載滿人之後才開車,也是一種調校系統整批寫入或更新效能的方法,但可能客戶這次的情境不適合。
解決辦法
在program.cs建立stateless sesssion之後,加上這一行
session.SetBatchSize(1);
或是hibernate.cfg.xml加入adonet.batch_size屬性
<property name="adonet.batch_size">1</property>
再重新偵錯後,程式查出剛剛寫進去但尚未commit的2筆資料! 結案~
診斷工具(intellitrace事件)偵測到3次ADO.NET 的事件(2次insert,1次Reader)
Hibernate是一個Java語言處理ORM的解決方案,2002年就出道了,而且很快有了.NET的兄弟版叫NHibernate,很多從Java轉過來.NET的工程師很自然的選擇她,一種她鄉遇故知的概念。
2008年之後,微軟也推出了Entity Framework (全名ADO.NET Entity Framework) ,她是微軟以 ADO.NET 為基礎所發展出來的物件關聯對應 (O/R Mapping) 解決方案,包含在 Visual Studio 2008 Service Pack 1 以及 .NET Framework 3.5 Service Pack 1 中發表。
參考
http://nhibernate.info/doc/nh/en/index#batch-inserts