Страницы

суббота, 19 ноября 2011 г.

NHibernate. Вникаем в hilo генератор.

  Для большинства пользователей NHibernate hilo генератор остаётся туманным, и они предпочитают старый недобрый identity. Давайте разберём их его по косточкам.
Сама аббревиатура hilo обозначает High Low, которые обозначают два числа. На примере его можно объяснить так: программа запрашивает в некоем хранилище уникальное High число. Допустим, это будет 5. При этом Low число равно 9. Тогда мы можем безопасно присваивать в качестве идентификатора числа 50,51,52,...59. После исчерпания чисел приложение запрашивает новое число High. Такой механизм позволяет безопасно присваивать идентификаторы на стороне клиента, даже для множества клиентов. Таков сам принцип генератора hilo, теперь посмотрим, как он реализован в nhibernate.
  Для начала образец нашего наш "домена":
public class Cat
{
  public virtual int Id { get; set; }

  public virtual string Name { get; set; }
}
и его маппинг:
<class name="Cat">
  <id name="Id">
    <generator class="hilo">
    <param name="table">HighNumbers</param>
    <param name="column">NextHigh</param>
    <param name="max_lo">4</param>
    </generator>
  </id>
  <property name="Name"/>
</class>
  • table- название таблице, содержащей информацию о текущем High числе.
  • column - название колонки, содержащей информацию о текущем High числе.
  • max_lo - максимально low число, которое захватывает nhibernate.
  Рассмотрим на живых примерах. Пустим в дело такой код:
[Test]
public void SaveManyCats()
{
  using(var session = _sessionFactory.OpenSession())
  using (var tx = session.BeginTransaction())
  {
    for (int i = 0; i < 15; i++)
    {
      session.Persist(new Cat{Name = "Cat number " + i});
    }
    tx.Commit();
  }
}
Для начала выполним его с identity-генератором. И вот что будет в логах:

2011-11-25 23:06:19,288 DEBUG [7] - (SQL) :
    INSERT 
    INTO 
        dbo.Cat 
        (Name) 
    VALUES 
        (@p0);     select SCOPE_IDENTITY();     @p0 = 'Cat number 0' [Type: String (4000)]

2011-11-25 23:06:19,384 DEBUG [7] - (SQL) :
    INSERT 
    INTO 
        dbo.Cat 
        (Name) 
    VALUES 
        (@p0);     select SCOPE_IDENTITY();     @p0 = 'Cat number 1' [Type: String (4000)]

... и еще шесть таких записей. 

 
  Nhibernate после сохранения объекта в бд, присваивает объекту в памяти идентификатор. И чтобы знать значение этого идентификатора, дописывает к запросу select SCOPE_IDENTITY(); Таким образом Nhibernate выполняет сохранение одного объекта за одно обращение к БД, расходуя машинные ресурсы, как олигархи деньги в Куршавеле! А чем же лучше hilo? Снова взглянем на логи. Во-первых, видны обращения для получения High числа, которые регистрируются в таблице HighNumbers .


2011-11-25 23:20:17,734 DEBUG [7] - (SQL) : Reading high value:
    select 
        NextHigh 
    from 
        [dbo].HighNumbers 
    with (updlock,rowlock)

2011-11-25 23:20:17,799 DEBUG [7] - (SQL) : Updating high value:
    update 
        [dbo].HighNumbers 
    set 
        NextHigh = @p0 
    where 
        NextHigh = @p1;     @p0 = 2 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]

2011-11-25 23:20:17,871 DEBUG [7] - (SQL) : Reading high value:
    select 
        NextHigh 
    from 
        [dbo].HighNumbers 
    with (updlock, rowlock)
 

2011-11-25 23:20:17,874 DEBUG [7] - (SQL) : Updating high value:     update 
        [dbo].HighNumbers 
    set 
        NextHigh = @p0 
    where 
        NextHigh = @p1;     @p0 = 3 [Type: Int32 (0)], @p1 = 2 [Type: Int32 (0)]

Обращений могло быть и меньше, но я специально выбрал max_lo = 5, чтобы была видна работа hilo генератора. Так происходит вставка:

2011-11-25 23:20:17,929 DEBUG [7] - (SQL) : Batch commands:
command 0:
    INSERT
    INTO
        dbo.Cat
        (Name, Id)
    VALUES
        (@p0, @p1);
    @p0 = 'Cat number 0' [Type: String (4000)], @p1 = 5 [Type: Int32 (0)]
command 1:
    INSERT
    INTO
        dbo.Cat
        (Name, Id)
    VALUES
        (@p0, @p1);
    @p0 = 'Cat number 1' [Type: String (4000)], @p1 = 6 [Type: Int32 (0)]
... и еще шесть команд.


  Всё внимание  выделенным словах: NHibernate вставил все 8 записей в одном пакете! Это удалось сделать, потому что hilo генератор присваивает значения на клиенте.
  Присвоенные идентификаторы - 5,6.. и т.д. Как они получились? Дело в том, что nhibernate  высчитывает диапазон по формуле high * (max_lo + 1)..high * (max_lo + 1) + max_lo. В нашем случае, для первого зарезервированного числа high = 1, диапазон был 5..9, и для втого high = 2  10..14.
  И еще несколько слов о hilo. Зарезервированный диапазон чисел хранится на уровне ISessionFactory,  и доступ к нему потокобезопасен. Если приложение закрылось, не выбрав весь диапазон чисел - он пропадает. Но не стоит об этом беспокоится (если конечно вы не установили слишком большое max_lo). А если всё-таки предел близок - берите Int64. Их хватит на все разумные размеры приложений. Еще можно использовать фильтр where. Только помните, что на одну иерархию классов должен быть один фильтр (об этом ниже).
  Identity генератор не позволяет замапить иерархию классов через union-subclass, т.к. в этом случае нет никакой ясности, как искать базовый класс по идентификатору, если он есть в нескольких таблицах наследников. В случае hilo такой проблемы нет - идентификаторы уникальны.

  Надеюсь, мне удалось приоткрыть завесу тайны над hilo. Если у вас есть nhibernate - забудьте identity как страшный сон! Hilo намного эффективней, и даёт больше возможностей.

Комментариев нет:

Отправить комментарий