Spiga

NHibernate之旅(6):探索NHibernate中的事务

2008-10-20 13:52 by 李永京, 16378 visits, 收藏, 编辑

本节内容

  • 事务概述
  • 1.新建对象
    • 【测试成功提交】
    • 【测试失败回滚】
  • 2.删除对象
  • 3.更新对象
  • 4.保存更新对象
  • 结语

上一篇我们介绍了NHibernate中的Insert, Update, Delete操作,这篇我们来看看NHibernate中的事务。你通过它可以提交或者回滚你的操作。

事务概述

1.NHibernate中的事务(Transactions)

简单描述:要求ISession使用事务;做一些操作;提交或者回滚事务。

写成代码就像这样:

ITransaction tx = _session.BeginTransaction();
//一些保存、更新、删除等操作
tx.Commit();

实际上在NHibernate使用事务要使用using强制资源清理和异常机制,一般像这样:

using (ITransaction tx = _session.BeginTransaction())
{
    try
    {
        //一些保存、更新、删除等操作
        tx.Commit();
    }
    catch (HibernateException)
    {
        tx.Rollback();
        throw;
    }
}

2.什么时候使用事务?

回答是:在任何时候都要使用事务,即使是在读取、查询数据的时候,为什么呢?因为你不清楚数据库什么时候操作失败,如何恢复原来数据。而NHibernate中的事务(可以通过 tx.Rollback()方法),帮助我们完成这些事情。

下面看看例子,我们修改上篇的Insert、Update、Delete操作:

1.新建对象

public int CreateCustomerTransaction(Customer customer)
{
    using (ITransaction tx = _session.BeginTransaction())
    {
        try
        {
            int newId = (int)_session.Save(customer);
            _session.Flush();
            tx.Commit();
            return newId;
        }
        catch (HibernateException)
        {
            tx.Rollback();
            throw;
        }
    }
}

这篇以新建对象为例,分别从成功提交和失败回滚两个角度来测试这个方法。

【测试成功提交】

首先写一个测试用例,假设这个测试可以运行成功:

[Test]
public void CreateCustomerTransactionTest()
{
    var customer = new Customer() { Firstname = "YJing", Lastname = "Lee" };
    int newIdentity = _transaction.CreateCustomerTransaction(customer);
    var testCustomer = _transaction.GetCustomerById(newIdentity);
    Assert.IsNotNull(testCustomer);
}

测试这个方法,使用TestDriven.NET集成的NCover(分析代码的覆盖率)查看代码运行覆盖率,在这个测试方法上右击选择“Test With”—“Coverage”,如下图所示:

使用Coverage测试

这时自动打开NCoverExplorer(查看代码覆盖率的分析结果),我们可以看到CreateCustomerTransaction方法运行覆盖情况,我们发现这个方法通过事务成功提交了操作并返回新的Id。分析结果效果图如下所示:

成功提交代码覆盖率分析结果

【测试失败回滚】

我们在写一个失败回滚的测试,由于我认为设置了一个“将截断字符串或二进制数据”错误,这时必须在测试方法上指定测试预期的异常。

[Test]
[ExpectedException(typeof(NHibernate.HibernateException))]
public void CreateCustomerThrowExceptionOnFailTest()
{
    var customer = new Customer()
    {
        Firstname = "012345678901234567890123456789012345678901234567890123456789",
        Lastname = "YJingLee"
    };
    _transaction.CreateCustomerTransaction(customer);
}

同理按上面的步骤测试这个方法看看CreateCustomerTransaction方法运行情况,由于出现错误(这里是“将截断字符串或二进制数据”错误),所以系统抛出了HibernateException异常,此时系统发生回滚。分析结果效果图如下所示:

失败回滚代码覆盖率分析结果

2.删除对象

我们修改上例中的删除对象的代码,如下所示:

public void DeleteCustomerTransaction(Customer customer)
{
    using (ITransaction tx = _session.BeginTransaction())
    {
        try
        {
            _session.Delete(customer);
            _session.Flush();
            tx.Commit();
        }
        catch (HibernateException)
        {
            tx.Rollback();
            throw;
        }
    }
}

3.更新对象

我们修改上例中的更新对象的代码,如下所示:

public void UpdateCustomerTransaction(Customer customer)
{
    using (ITransaction tx = _session.BeginTransaction())
    {
        try
        {
            _session.Update(customer);
            _session.Flush();
            tx.Commit();
        }
        catch (HibernateException)
        {
            tx.Rollback();
            throw;
        }
    }
}

4.保存更新对象

我们修改上例中的保存更新对象的代码,如下所示:

public void SaveOrUpdateCustomersTransaction(IList<Customer> customers)
{
    using (ITransaction tx = _session.BeginTransaction())
    {
        try
        {
            foreach (Customer c in customers)
                _session.SaveOrUpdate(c);
            _session.Flush();
            tx.Commit();
        }
        catch (HibernateException)
        {
            tx.Rollback();
            throw;
        }
    }
}

好了,由于篇幅有限,上面三个方法在这里我就不测试了,大家可以参考创建对象测试的步骤来测试一下其他几个方法吧!

结语

感觉这节内容很少的样子,在NHibernate官方文档中对事务讲解的并不多,自己挖空心思也就挤了这么多东西。不过在这一节带领大家学会了测试工具TestDriven.NET的另一个功能就是怎么查看代码运行覆盖率,还是有一点收获的哦。下一节想继续深入事务话题一起讨论NHibernate中的并发控制,到现在还没有想好怎么写呢,希望大家对这个系列给出意见和建议。谢谢支持!

本系列链接:NHibernate之旅系列文章导航

NHibernate Q&A

下次继续分享NHibernate!

标签: NHibernate
Add your comment

65 条回复

  1. #1楼 GOOD[未注册用户]2008-10-20 14:02
    支持下:)
     回复 引用   
  2. #2楼[楼主] 李永京      2008-10-20 14:04
    @GOOD
    谢谢支持哦~~
     回复 引用 查看   
  3. #3楼 Astar      2008-10-20 14:12
    学习中.....
     回复 引用 查看   
  4. #4楼[楼主] 李永京      2008-10-20 14:29
    @Astar
    ;-)
     回复 引用 查看   
  5. #5楼 二淡淡的[未注册用户]2008-10-20 14:31
    请问LZ,
    对于一个初学ORM的人来说,
    您觉得是选择LINQ呢,还是NHibernate好呢?
     回复 引用   
  6. #6楼 重典      2008-10-20 14:37
    跟踪李兄进度...
     回复 引用 查看   
  7. #7楼 重典      2008-10-20 14:38
    我又想到一句:尾随李兄回家

    呵呵
     回复 引用 查看   
  8. #8楼[楼主] 李永京      2008-10-20 15:01
    @二淡淡的
    LINQ微软新发布的一项可以说的是跨时代的技术。有这微软的支持!适用于小型项目开发,事实上,现在所有项目都可以使用linq了,简单!NHibernate比较经典的框架,在原来ado.net2.0时代的选择,不过现在至今有很多很多追随者。建议学学LINQ吧。这个下次吧
     回复 引用 查看   
  9. #9楼[楼主] 李永京      2008-10-20 15:02
    @重典
    尾随李兄回家 什么深入的含义呢??
     回复 引用 查看   
  10. #10楼 Gray Zhang      2008-10-20 15:17
    @二淡淡的
    一表一实体的对应关系之下用LINQ比较合适,映射复杂了还是交给NH来吧
     回复 引用 查看   
  11. #11楼[楼主] 李永京      2008-10-20 15:27
    @Gray Zhang
    Thanks,到现在还没有说到NHibernate中的关联。。在这之前先搞懂全部单表的内容。
     回复 引用 查看   
  12. #12楼 二淡淡的[未注册用户]2008-10-20 15:36
    @李永京
    谢谢您的回答,您说“适用于小型项目开发,事实上,现在所有项目都可以使用linq了,简单!”
    是不是指大型项目用NHibernate比LNIQ跟适合呢?
     回复 引用   
  13. #13楼 二淡淡的[未注册用户]2008-10-20 15:38
    @Gray Zhang
    谢谢您的回答,
    您的意思是说LINQ和NHibernate可以并存,不矛盾,
    并且NHibernate更适用在更复杂大型的项目中吗?
     回复 引用   
  14. #14楼 谢慧琦      2008-10-20 16:18
    楼主你好,能告诉我如何使用NHibernate分页以及执行存储过程吗?
     回复 引用 查看   
  15. #15楼[楼主] 李永京      2008-10-20 16:52
    @二淡淡的
    有这个可能,linq对关系关联还是没有NHibernate强,不过微软推出了实体框架,用在大型项目中更好了。LINQ和NHibernate可以并存,不矛盾,我更加期待NHibernate2.1版本的LINQ for NHibernate的发布。到时又是另外一个世界了。
     回复 引用 查看   
  16. #16楼[楼主] 李永京      2008-10-20 16:53
    @谢慧琦
    今后说到哦,关于存储过程只要写个过程,在映射文件中定义<sql-insert>exec CustomerInsert ?,?,?,?</sql-insert>就可以了。
     回复 引用 查看   
  17. #17楼 Gray Zhang      2008-10-20 17:11
    @谢慧琦
    ISession s=SessionFactory.OpenSession();

    IList lst=s.CreateQuery(query).SetFirstResult(Start).SetMaxResults(Max).List();
     回复 引用 查看   
  18. #18楼[楼主] 李永京      2008-10-20 17:20
    @Gray Zhang
    我还在看NHibernate呢....你都全会了,以后再问问你啊~~呵呵
     回复 引用 查看   
  19. #19楼 KKU[未注册用户]2008-10-21 10:14
    写个多Update Instance的例子吧,呵呵应该会有更多的收获。没有你写的那么简单的。
     回复 引用   
  20. #20楼[楼主] 李永京      2008-10-21 16:54
    @KKU
    呵呵,这放在下一节,并发控制,涉及了版本控制内容
     回复 引用 查看   
  21. #21楼 cobrayang[未注册用户]2008-10-25 09:29
    请教LZ,我在事务里执行了查询和更新语句,那事务中的查询语句还需要使用TABLOCKX排他表锁吗?
     回复 引用   
  22. #22楼[楼主] 李永京      2008-10-25 13:18
    @cobrayang
    直接使用SaveOrUpdate方法啊,NHibernate帮你做了一切
     回复 引用 查看   
  23. #23楼 cobrayang[未注册用户]2008-10-25 13:29
    喔,明白了,直接使用事务就完了,3Q
     回复 引用   
  24. #24楼 dikongpulu      2008-10-30 23:58
    "在任何时候都要使用事务,即使是在读取、查询数据的时候"
    对单表进行数据操作也需要用事务吗
     回复 引用 查看   
  25. #25楼[楼主] 李永京      2008-10-31 00:44
    @dikongpulu
    呵呵,如果你认为你的程序逻辑复杂,就用事务吧
     回复 引用 查看   
  26. #26楼 dikongpulu      2008-10-31 10:15
    @李永京
    这跟程序逻辑没有关系吧,是不是只有对多表操作才应该用事务
     回复 引用 查看   
  27. #27楼[楼主] 李永京      2008-10-31 12:23
    @dikongpulu
    恩,对于增删改必须加上事务处理,对于查询在多表的情况下必须使用,单表情况下如果你的程序逻辑比较复杂也加上事务吧
     回复 引用 查看   
  28. #28楼 Eric Lau[未注册用户]2008-11-25 14:40
    这节的内容没有体现什么是真正的事务,一般来说,对于批量处理的时候才会考虑使用事务,确保数据的操作的完整性,而楼主演示的仅仅是对于单个对像操作,这时候使用事务看起来有点多余了。就算数据有错,插上入也是不成功的,根本就不需要NHibernate的rollback.
     回复 引用   
  29. #29楼[楼主] 李永京      2008-11-25 15:08
    @Eric Lau
    恩,这节说的很基础~~
     回复 引用 查看   
  30. #30楼 icemanpro[未注册用户]2008-12-17 11:21
    事务嵌套是如何写??
     回复 引用   
  31. #31楼[楼主] 李永京      2008-12-17 12:46
    @icemanpro
    使用Using吗?
     回复 引用 查看   
  32. #32楼 icemanpro[未注册用户]2008-12-17 14:40
    @李永京
    是的。按上面例子写嵌套,会提示不能访问已被释放的资源。
     回复 引用   
  33. #33楼[楼主] 李永京      2008-12-17 16:44
    @icemanpro
    在业务逻辑层中写一些事务处理,一般而言事务是不嵌套的,事务的AICD就说明了这一点,不知道你是开发什么应用。。
     回复 引用 查看   
  34. #34楼 stg609      2009-03-06 16:02
    @李永京
    我想问个问题,事务如果没有回滚是不是表示其中的操作(更新、删除等)肯定成功了?我可不可以在transcation.commit方法之后写一个bool类型的数,来告诉调用这个操作的那个方法“所做的操作是否成功”。
    我想这么做的原因,是希望可以在业务逻辑层中知道dal所做的操作是否成功。插入操作可以有返回值,所以要知道成不成功很简单,而其他就无法知道了。所以想用这种办法,楼主你觉得可以吗?
     回复 引用 查看   
  35. #35楼[楼主] 李永京      2009-03-13 14:21
    @stg609
    可以,在逻辑层判断返回值问题
     回复 引用 查看   
  36. #36楼 marchcuckoo2009-06-20 11:48
    很不错的,这教程比我学hibernate看的教程讲的好多了,图文并茂,很详细。谢谢了。
     回复 引用   
  37. #37楼 Yan11[未注册用户]2009-07-17 16:16
    楼主 我在调用create方法时,nunit test总是显示 can not insert the value Null into column PatientID. 我是按照楼主的例子写的,
    Patient patient = new Patient() { PatientID = 111,。。。。} 在 test code 中。
    public virtual int PatientID
    {
    //get;
    //set;
    get { return _patientID;}
    set { _patientID = value; }
    } 在domain 中。
    我同时测试了delete,运行正常。
    请楼主明示。谢谢
     回复 引用   
  38. #38楼 Yan11[未注册用户]2009-07-17 16:25
    PatientID 我定义为一个primary key
     回复 引用   
  39. #39楼 Yan11[未注册用户]2009-07-17 16:37
    我又研究了一下楼主的例子,在customer表里面也有customerID, 为什么在创建一个新对象的时候,只写firstname,lastname.不给customerID值能创建吗。
     回复 引用   
  40. #40楼[楼主] 李永京      2009-07-18 10:16
    @Yan11
    是主键,NHibernate会按照内置的主键生成器策略在持久化的时候为对象生成一个ID
     回复 引用 查看   
  41. #41楼 Yan11[未注册用户]2009-07-19 22:31
    谢谢楼主,但是楼主能明示一下在如何利用主键生成器生成一个ID, 我做了一个主键生成器类。
    我查询了nHibernate之旅(5) level0咨询同样的问题, 我并没有找到相关的例子代码。 谢谢楼主啦!
     回复 引用   
  42. #42楼[楼主] 李永京      2009-07-20 07:44
    @Yan11
    在映射文件中不是定义了《id》标签,这就是定义id生成器。。看看NHibernate官方文档就清楚了。
     回复 引用 查看   
  43. #43楼 Yan11[未注册用户]2009-07-20 17:37
    谢谢楼主的帮助,作为初学者,楼主的1句点拨都很重要。
     回复 引用   
  44. #44楼 cohoo[未注册用户]2009-10-15 10:50
    using (ITransaction tran = _session.BeginTransaction())
    {
    try
    {
    _session.Update(customer, customer.CustomerId);
    _session.Save(customer, customer.CustomerId);
    _session.Flush();
    tran.Commit();
    return 1;
    }
    catch
    {
    tran.Rollback();
    throw;
    }
    }
    博主,我先进行更新操作,然后在重复插入一条记录到数据库,为什么这个事务控制却没有回滚呢,这条记录依然被更新了。
     回复 引用   
  45. #45楼[楼主] 李永京      2009-10-15 10:58
    @cohoo
    你为什么传id进去?id NHibernate有专门的生成策略~~
    NHibernate根据不同的策略有着不同的机制。例如文章中的id策略不需要传id的,只要对象为新的(新建的)NHibernate为认为是新的一条数据。
     回复 引用 查看   
  46. #46楼 cohoo[未注册用户]2009-10-15 11:16
    @李永京
    因为我的Customer表中CustomerId不是自增的,如果用Save(object obj)方法插入的话,就会出现错误could not insert: [DomainModel.Entities.Customer][SQL: INSERT INTO Customer (Version, Firstname, Lastname) VALUES (?, ?, ?); select SCOPE_IDENTITY()]这个错误;而采用Save(object obj,object id)方法,相当于自己手动生成一个id,这样就可以插入成功了。
     回复 引用   
  47. #47楼[楼主] 李永京      2009-10-15 12:12
    @cohoo
    哦,你没设置自动生成id策略
     回复 引用 查看   
  48. #48楼 会长      2009-11-11 17:34
    楼主及大家好:
    using (ITransaction tx = _session.BeginTransaction())
    为什么使用完毕后要立即请求清理呢?为什么Session使用完后不用马上Close,谢谢。
     回复 引用 查看   
  49. #49楼[楼主] 李永京      2009-11-11 18:17
    @会长
    Session要关闭,我例子都没有关闭,呵呵
     回复 引用 查看   
  50. #50楼       2010-04-26 11:49
    引用李永京:
    @会长
    Session要关闭,我例子都没有关闭,呵呵



    额...怪不得,我还以为我写错了...
    呵呵,支持楼主.
     回复 引用 查看   
  51. #51楼 DreamZero      2010-04-28 09:32
    跟踪进行时!
     回复 引用 查看   
  52. #52楼 ibrat      2010-06-24 10:10
    @二淡淡的
    为什么不 Linq.NHibernate呢!!
    这样两个你都用到了!!
     回复 引用 查看   
  53. #53楼 Silent Void      2010-08-14 22:25
    @李永京
    请问,NH的更新操作,是在Save/Update/Delete的时候就执行,还是在Flush的时候才执行?

    int newId = (int)_session.Save(customer);
    _session.Flush();
    如果是在Save的时候,就已经更新到DB中,那Flush还有啥用?如果是在Flush的时候才之心,那前面的Save操作能取到newID吗?
     回复 引用 查看   
  54. #54楼[楼主] 李永京      2010-08-14 22:32
    @Silent Void
    Save实际就是把对象变为持久态,Session可以控管。这时可以取到其Id了,Flush或者Commit的时候实际才提交到数据库的。
     回复 引用 查看   
  55. #55楼 Silent Void      2010-08-14 22:46
    引用李永京:
    @Silent Void
    Save实际就是把对象变为持久态,Session可以控管。这时可以取到其Id了,Flush或者Commit的时候实际才提交到数据库的。

    再请教,这个ID是怎么生成的?数据库自增,还是NH来控制?谢谢。
     回复 引用 查看   
  56. #56楼[楼主] 李永京      2010-08-14 22:49
    @Silent Void
    NH根据我们在映射文件中配置的Id生成策略生成Id的。
     回复 引用 查看   
  57. #57楼 Silent Void      2010-08-14 23:36
    引用李永京:
    @Silent Void
    NH根据我们在映射文件中配置的Id生成策略生成Id的。

    @李永京
    继续请教,如果是在影射文件中配置:
    1.多个Session同时请求了ID,但没有更新到DB,这样的话ID应该不会冲突吧?
    2.如果做了最简单的负载均衡,同一个系统部署到了多台服务器上,这样的话,每台服务器的进程是独立的,这样会不会导致最后生成的ID重复?
    3.如果这个表中的数据,可以通过NH来创建记录,也可以通过从其他的业务同步,这样的话,ID就无法生成连续的了,只能用GUID,或者分区段,某个区段的ID用于NH创建的记录,另一个区段的ID用于同步。但这样操作,貌似还是挺折腾。

    感觉ID这种表示标识性的东西,其生成规则放在一个统一的地方维护最好,我觉得最好的地方,就是放在DB了,由DB自动生成。
     回复 引用 查看   
  58. #58楼[楼主] 李永京      2010-08-15 10:42
    @Silent Void
    不管什么策略,都是由db控制,不重复的。你可以看看NHibernate参考文档里面的介绍。我现在比较常用的是hilo、Guid.comb这两种。
     回复 引用 查看   
  59. #59楼 路过12[未注册用户]2010-10-11 17:18
    @cohoo
    用了using,就隐式处理事务,自定义的事务就没有作用了。因此不要用using语句。
     回复 引用   
  60. #60楼 桀骜的灵魂      2010-10-28 14:32
    京个我又回来了,妄想Castle的AR可以成就一段时间,发觉还是和NH差距不是一点半点,所有又来拜读了。
     回复 引用 查看   
  61. #61楼 lonelybrother      2010-11-07 14:42
    请都李老师
    你customer.hbm.xml中id是这样写的
    <id name="Id" column="CustomerId">
    <generator class="assigned" />
    </id>
    id是自己指定??
    也就是保存时,要给个id,还是nh自动生成?
    个人认为应是传过去吧?
    可是参看李老师的代码,没传,怎么会有呢? 3Q!!
     回复 引用 查看   
  62. #62楼 virus      2010-12-21 10:30
    一切皆事务吗?
    获取单条记录也用事务吗?
     回复 引用 查看   
  63. #63楼[楼主] 李永京      2010-12-21 11:18
    @virus
     回复 引用 查看   
  64. #64楼 GavinYoung      2011-06-19 01:40
    写得不错。
    @lonelybrother
    数据库里相关表的相关列设了自动增长了吧。
     回复 引用 查看   
  65. #65楼 sqq_qqs      2011-11-21 13:58
    请问一下
    int newId = (int)_session.Save(customer);
    _session.Flush();
    我发现去掉_session.Flush();,数据还是会提交到数据库中,那请问_session.Flush();这句有什么用额?
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1315032 vUw2bZJty5Y=