воскресенье, 24 января 2010 г.

Кэширование в Hibernate (Ч4)

это перевод Hibernate in Action главы 5 
Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1) 
Очистка сессии, уровни изоляции транзакций, выбор и установка уровня изоляции транзакций в Hibernate (Ч2)
Пессимистическая и оптимистическая блокировки, транзакции приложения, версионирование, детализация сессии (Ч3) 
 
5.3 Теория и практика кэширования
Основным обоснованием нашего утверждения является то, что приложения, использующие объектно-реляционный слой, как ожидается, превосходят приложения, построенные с использованием прямого JDBC, благодаря возможности кэширования. Хотя мы будем утверждать, что самые странные заявления должны быть сконструированы таким образом, что возможно достигнуть приемлемой производительности без использования КЭШа, нет никаких сомнений, что для некоторых видов приложений – особенно приложения, осуществляющие в основном чтения или приложения, сохраняющие значительные метаданные в БД – кэширование может иметь огромное влияние на производительность.
    Мы начнем наше исследование кэширования с некоторой справочной информации. Она включает в себя объяснение различия кэширования и идентичности областей, и влияния кэширования на изоляцию транзакций. Эта информация, и эти правила могут применяться для кэширования в общем; они применимы не только для Hibernate приложений. Эта дискуссия дает основу для понимания, почему система кэширования в Hibernate такая, какая она есть. Затем, мы введем вас в систему кэширования Hibernate и покажем вам, как включить настройки и управлять КЭШем Hibernate первого и второго уровня. Мы рекомендуем вам внимательно изучить основы, изложенные в этом разделе, прежде, чем начать использование кэш-памяти. Без основы, вы можете быстро заиметь сложные в отладки проблемы одновременной доступности и риски целостности ваших данных.
    Кэш-память хранит представление текущего состояния БД для приложения, либо в памяти или на диске компьютера сервера приложения. Кэш – это локальная копия данных. Кэш находится между приложением и БД. Кэш может использоваться, чтобы избежать обращений к БД, когда:
•    Приложение выполняет поиск по идентификатору (первичному ключу)
•    Хранимый слой разрешает ленивую ассоциацию
Также возможно, кэшировать результаты запросов. Как вы увидите, в главе 7, прирост производительности кэширования запросов минимален, большинстве случаев это так, так что эта функция используется гораздо реже.
    Прежде, чем мы рассмотрим, как работает Hibernate кэш, давайте рассмотрим различные параметры кэширования и посмотрим, как они относятся к идентичности и параллелизму.

5.3.1 Стратеги кэширования и области
Кэширование является настолько фундаментальным понятием в объектно-реляционном отображении, что вы не сможете понять производительность, масштабируемость или семантику транзакций в реализации объектно-реляционного отображения, без первоначальных представления какую стратегию (или стратегии) кэширования он использует. Существуют три основных типа КЭШа:
•    Транзакционный – связанный с текущей единицей работы, которая может быть фактическая транзакция БД или транзакция приложения. Она корректна и используется во время работы единицы работы. Каждая единица работы имеет свой кэш.
•    Процессный – распределяется между многими (возможно одновременными) единицами работы или транзакции. Это означает, что данные в процессном КЭШе доступны одновременно выполняемым операциям, очевидно с последствиями для изоляции транзакций. Процессный кэш может хранить хранимые объекты целиком в КЭШе, или может хранить их состояние в разобранном формате.
•    Кластерный – распределяется между несколькими процессами на одной машине или между несколькими машинами в кластере. Он требует какой-то удаленный процесс взаимодействия для поддержания согласованности. Кэширование информации должно быть продублировано на всех узлах кластера. Для многих (не всех) приложений, кластерное кэширование имеет сомнительную ценность, так как чтение и обновление КЭШа может быть лишь незначительно быстрее, чем прямое обращение в БД.
Хранимые слои могут обеспечивать несколько уровней кэширования. Например, промах КЭШа(cache miss) (поиск элемента в КЭШе, который там не содержится) в транзакционном типе может последовать за поиском в процессном. Запрос к БД будет последней инстанцией.
    Тип КЭШа, используемый хранимым уровнем, влияет на сферу идентичности объектов (взаимосвязь между Java идентичностью и идентичностью на уровне БД)

Кэширование и идентичность объектов
Рассмотрим транзакционный кэш. Кажется естественным, что этот кэш используется также для осуществление идентичности хранимых объектов. Это означает, что транзакционный кэш реализует обработку идентичности: два поиска, использующих один и тот же идентификатор БД, возвращают тот же Java объект в одной единице работы. Поэтому транзакционный кэш идеально подходит, если механизм сохранения также предоставляет транзакционную идентичность.
    Механизм сохранения с процессным КЭШем можно выбрать для реализации процессной идентификации. В этом случае, объект идентификации эквивалентен для БД для всего процесса. Два поиска, использующие один и тот же идентификатор БД в двух единицах одновременно выполняемой работы, вернут в результате один и тот же экземпляр Java. Кроме того, объекты извлекаются из процессного КЭШа, могут быть возвращены по значению. Кэш содержит кортежи данных, а не хранимые экземпляры. В этом случае, каждая единица работы загружает свою копию состояния(кортеж) и строит свой собственный хранимый объект. Объем кэш-памяти и объем идентичности объектов уже не являются одинаковыми.
    Кластерный кэш всегда требует удаленной связи, а в случае POJO-решений, таких как Hibernate, объекты всегда передаются удаленно по значению. Кластерный кэш не может гарантировать идентичность через кластер. Вы должны выбирать между транзакционной или процессной идентичностью объектов.
    Для типичной архитектуры Интернет или корпоративных приложений, самым удобным является, когда сфера идентичности объектов ограничивается одной единицей работы. Иными словами, это ни необходимо, ни желательно, иметь идентичными объекты в двух параллельных потоках. Существуют и другие виды приложений (в том числе некоторые настольные или толстый-клиент архитектуры), где была бы уместно использовать процессную идентичность объектов. Это особенно справедливо, когда память сильно ограничена – потребление памяти транзакционного кэша пропорционально количеству одновременных единиц работы.
    Недостатком процессной идентичности является необходимость синхронизации доступа к хранимым объектам в КЭШе, что приводит к высокой вероятности возникновения тупиков.

Кэширование и параллелизм
Любая реализация объектно-реляционного отображения, которая позволяет нескольким единицам работы разделять одни хранимые объекты, должна предоставлять некоторые формы блокировок на уровне объектов, для обеспечения синхронизации параллельного доступа.  Обычно, это реализуется, используя блокировки на чтение и запись (устанавливаемые в памяти) вместе с определением тупиков. Реализации, вроде Hibernate, которые поддерживают различных набор экземпляров для каждой единицы работы (транзакционная идентичность), избегают этих проблем в значительной степени.
    Наше мнение, что блокировок в памяти следует избегать, по крайней мере для веб-сайтов и корпоративных приложений, где многопользовательская масштабируемость является главной заботой. В этих приложений, обычно нет необходимости сравнивать объекты на идентичность в параллельных единицах работы; каждый пользователь  должен быть полностью изолирован от других пользователей.
    Существуют довольно веские аргументы в пользу этой точки зрения, когда реляционной  СУБД реализует модель многоверсионного параллелизма  (например, Oracle или PostgreSQL). Это несколько нежелательно для объектно-реляционного хранимого КЭШа, чтобы переопределять семантику транзакций или модель параллелизма нижележащей БД.
    Рассмотрим эту опцию снова. Транзакционный кэш является предпочтительным, если вы также можете использовать транзакционную идентичность объектов и это лучшая стратегия для высоко-параллельных многопользовательских систем. Этот первый уровень КЭШа будет обязательным, посколько он также гарантирует идентичность объектов. Однако, это единственный кэш, который вы можете использовать. Для некоторых данных, может быть полезен кэш второго уровня, относящийся к процессу (или кластеру), который возвращает данные по значению. Поэтому этот сценарий имеет два уровня КЭШа; вы позже увидите, что Hibernate использует этот подход.
    Давайте обсудим, какие преимущества есть от второго уровня кэширования, или, другими словами, когда для включать процессный (или кластерный) КЭШ второго уровня, в дополнении к обязательному КЭШу первого уровня.

Кэширование и изоляция транзакций
Процессный или кластерный кэш делают извлечение данных из БД в одной единице работы, видимой для другой единицы работы. Это может иметь очень неприятные эффекты, при изоляции транзакций.
    Во-первых, если приложение не имеет эксклюзивного доступа к БД, то процессное кэширование не должно использоваться, за исключением данных, которые редко меняются и могут быть безопасно обновлены по истечению срока кэширования. Этот тип данных часто встречается в приложениях, управляющих содержанием, но редко в финансовых приложениях.
    Вам нужно взглянуть на два основных сценария, с участием неэксклюзивного доступа:
•    Кластерные приложения
•    С общими данными
Любое приложение, задуманное для расширяемости, должно поддерживать кластерные операции. Процессный кэш не поддерживает согласованность между различными КЭШами на разных машинах в кластере. В этом случае вы должны использовать кластерный (распределенный) кэш вместо процессного КЭШа.
    Многие Java приложения разделяют доступ к своей БД с другими приложениями. В этом случае, вы не должны использовать любой тип КЭШа, кроме транзакционного кэша. Не существует никакой возможности для кэширующей системы, что знать когда совместное приложение обновило общие данные. Собственно, это можно реализовать на уровне приложения для показа недействительности процессного (или кластерного) КЭШа, когда изменения сделаны в БД, но мы не знаем какого-либо стандартного или лучшего пути достижения этой цели. Конечно, это никогда не будет встроенной возможностью Hibernate. Если вы реализуете такое решения, то оно, скорее всего подойдет только для вас, потому что оно сильно зависит от среды и используемых продуктов.
    После рассмотрения неэкслюзивного доступа к данным, вы должны установить, как уровень необходим для данных приложения. Не всякий кэш реализует все уровни изоляции транзакций и это критически важно узнать. Давайте посмотрим на данные, которые выгодны для процессного (или кластерного) КЭШа.
    Полное объектно-реляционное решение позволит вам настроить кэш второго кровня отдельно для каждого класса. Хорошими кандидатами на кэширования являются классы, которые представляют:
•    Данные, которые редко меняются
•    Не критичные данные
•    Данные, которые локальны для приложения и не являются общими
Плохими кандидатами для КЭШа второго уровня являются:
•    Данные, которые часто меняются
•    Финансовые данные
•    Данные, которые являются общими с другими приложениями
Однако, это не все правила, которые мы обычно применяем. Многие приложения имеют несколько классов, со следующими свойствами:
•    Маленькое количество экземпляров
•    На каждый экземпляр ссылается много других экземпляров другого класса или классов
•    Экземпляры редко (или никогда) обновляются
Такие данные иногда называют справочными данными. Справочные данные являются отличным кандидатом для кэширования процессным или кластерным КЭШем, и любое приложение, которое интенсивно использует  эти данные сильно выигрывает от того, что данные закэшированы. Вы позволяете данным обновляться во время истечение таймаута КЭШа.
    Мы сформировали картину двойного слоя системы кэширования в предыдущих разделах, с транзакционным КЭШем первого уровня и опциональным процессным или кластерным КЭШем второго уровня. Это похоже на систему кэширования Hibernate.

5.3.2 Архитектура КЭШа в Hibernate
Как мы говорили ранее, Hibernate имеет двухуровневую архитектуру кэш-памяти. Различные элементы этой системы можно увидеть на рис. 5.5
(РИС. 5.5)
Кэш первого уровня является также сессией. Жизненный цикл сессии соответствует либо транзакции БД, либо транзакции приложения (как описано ранее в этой главе). Мы считаем кэш, связанный с сессий транзакционным. Кэш первого уровня является обязательным и не может быть выключен; это также гарантирует идентичность объектов внутри транзакции.
    Кэш второго уровня в Hibernate является подключаемым и может быть процессным или кластерным. Это кэш состояний (возвращаемых по значению), а не хранимых экземпляров. Стратегия параллельности  КЭШа определяет детали транзакционной изоляции для определенных элементов данных, в то время, как поставщик КЭШа представляет собой физическую, фактическую реализацию КЭШа. Использование КЭШа второго уровня является необязательным и может быть настроено на каждый класс и на каждую ассоциацию.
    Hibernate также реализует кэширование результатов запросов, тесно интегрированных с КЭШем второго уровня. Это является дополнительной функцией. Мы обсуждаем кэш запросов в главе 7, так как его использование тесно связано с фактическим выполнением запросов.
    Давайте начнем с использования КЭШа первого уровня, который также называется КЭШем сессии.

Использование КЭШа первого уровня
Кэш сессии гарантирует, что когда приложение запросит такой же хранимый объект дважды в той же сессии, то он получит тот же (идентичный) Java экземпляр. Это иногда позволяет избегать ненужного трафика БД. Более важно, что он обеспечивает следующее:
•    Слой хранимых объектов не подвержен опасности переполнения стека, в случае циклических ссылок в графе объектов
•    Никогда не возникнет конфликтов представлений об одной строке БД в конце транзакции БД. Существует не более одного объекта, представляющего любую строку БД. Все изменения, внесенные в этот объект, могут быть безопасно записаны в БД (сброшены).
•    Изменения, сделанные в конкретной единице работы всегда незамедлительно видны всему другому коду, исполняемому внутри этой единицы работы.
Вам не нужно делать ничего специального для того, чтобы разрешить кэш сессии. Он всегда включен, и по указанным причинам, не может быть отключен.
Всякий раз, когда вы вызываете save(), update() или saveOrUpdate() и всякие раз, когда вы получаете объект, используя load(), find(), list(), iterate() или filter(), он добавляется в кэш сессии. Когда впоследствии вызывается flush(), то состояние объекта синхронизируется с БД.
    Если вы не хотите, чтобы эта синхронизация происходила, или если вы обрабатываете огромное количество объектов и необходимы эффективные средства управления памятью, то вы можете использовать метод evict() сессии, для удаления объекта и его коллекции из первого уровня кэш-памяти. Есть несколько сценариев, где это может быть полезно.

Управление КЭШем первого уровня
Рассмотрим этот часто задаваемый вопрос: «Я получаю OutOfMemoryException при попытке загрузить 100,000 объектов и манипулируя всеми ими. Как я могу сделать массовые обновления с Hibernate?».
    Наше мнение, что объектно-реляционное отображение не подходит для операций массового обновления (или удаления). Если у вас есть прецедент, как этот, то иная стратегия почти всегда лучше: вызов хранимой процедуры в БД или использование прямых SQL UPDATE и DELETE выражений. Не передавайте все данные в основную память для простых операций, если она может быть выполнена более эффективно в БД.
Если ваше приложение в основном использует массовые операции, то объектно-реляционного отображение не является правильным инструментом для работы!
Если вы настаиваете на использовании Hibernate для массовых операций, вы должны немедленно вызывать evict() каждого объекта, после того, как он будет обработан (при переборе результатов запроса), и тем самым предотвратить исчерпание памяти.
Чтобы полностью убрать все объекты из КЭШа первого уровня, вызовите Session.clear(). Мы не пытаемся убедить вас, что убранные объекты первого уровня является плохой вещью в целом, однако, редко встречаются хорошие случаи его использования. Иногда использование проекций и отчетных запросов, как это обсуждается в главе 7, раздел 7.4.5 «Повышение производительности с отчетными запросами», может быть лучшим решением.
Обратите внимание, что убирание, как и операции сохранения и удаления, могут автоматически применяться к ассоциированным объектам. Hibernate будет убирать ассоциированные экземпляры из сессии, если в файле отображения атрибуту cascade задано значение ”all” или “all-delete-orphan” для конкретной ассоциации.
Когда случается промах первого уровня КЭШа, Hibernate пытается взять значение из КЭШа второго уровня, если он включен для определенного класса или ассоциации.

Кэш второго уровня Hibernate
Hibernate кэш второго уровня является процессным или кластерным; все сессии обладают одним и тем же КЭШем второго уровня. Кэш второго уровня, на самом деле, имеет область SessionFactory.
Хранимые экземпляры хранятся в КЭШе второго уровня в разобранном виде. Думайте о демонтаже, как о процессе похожем на сериализацию (однако, алгоритм, намного, намного быстрее, чем Java сериализация).
Внутреннее представление этого процессного/кластерного КЭШа не представляет большого интереса; более важным является правильное использование политики кэширования, то есть стратегии кэширования и выбор физических провайдеров КЭШа.
Различные виды данных требуют различной политики кэширования: соотношение чтение к записи изменяется, размер БД изменяется, и  некоторые таблицы используются совместно с другими приложениями. Так что, кэш второго уровня настраиваемся под детализацию каждого индивидуального класса или коллекцию ролей. Это позволяет, например, разрешить кэш второго уровня для справочных данных и запретить его для классов, представляющих финансовые записи. Политика КЭШа включает в себя настройку следующих параметров:
•    Включен ли кэш второго уровня
•    Стратегию параллелизма Hibernate
•    Политика истекания срока кэширования (такую, как тайм-аут, LRU, зависимую от ОП)
•    Физическое устройство КЭШа (в памяти, индексируемые файлы, кластерная репликация)
Не все классы имеют выгоду от кэширования, поэтому чрезвычайно важно иметь возможность отключить кэш второго уровня. Повторяем, кэш-память, как правило полезно только для классов, которые по большинству считываются. Если у вас есть данные, которые обновляются чаще, чем читаются, не разрешайте кэш второго уровня, даже если все остальные условия для кэширования верны! Кроме того, кэш второго уровня может быть опасен в системах, которые разделяют данные с другими приложениями, которые могут эти данные изменить. Как мы объяснили в предыдущих разделах, необходимо тщательно подумать над решением.
    Hibernate кэш второго уровня устанавливается в два этапа. Во-первых, вы должны решить, какую стратегию параллельности использовать. После этого вам нужно настроить истечение КЭШа и физические атрибуты, используя поставщика КЭШа.



 Встроенные стратегии параллелизма
Стратегия параллелизма является посредником; она несет ответственность за хранение элементов данных в кэш памяти и извлечение их из КЭШа. Это важная роль, поскольку она определяет семантику изоляции транзакций для этого конкретного пункта. Вам придется принять решение по каждому хранимому классу, какую стратегию параллелизма кэша использовать, если вы хотите разрешить кэш второго уровня.
    Есть четыре встроенных стратегии параллелизма, представляющие снижение уровня строгости, в терминах изолированности транзакции:
•    Транзакционная – доступна только в управляемой среде. Она гарантирует полную изоляцию транзакций до повторяемого чтения, если это требуется. Используйте эту стратегию для данных которых в большинстве считываются, в которых очень важно предотвратить появление устаревших данных в параллельных транзакциях, в редких случаях обновления.
•    Чтение-запись – поддерживает изоляцию чтения подтвержденного, используя механизм временных меток. Она доступна только в некластерных средах. Опять же, использовать эту стратегию нужно для чтения данных, в которых очень важно предотвратить появление устаревших данных в параллельных транзакциях, в том редком случае обновления.
•    Нестрогое-чтение-запись -  не дает никакой гарантии согласованности между КЭШем и БД. Если есть возможность одновременного доступа к одной сущности, то вам необходимо настроить достаточно короткий срок истечения тайм-аута. В противном случае, вы можете прочитать устаревшие данные в КЭШе. Используйте эту стратегию, если данные редко меняются (несколько часов, дней или даже недель), и небольшая вероятность появления устаревших данных не является критической проблемой. Hibernate считает недействительным кэшируемый элемент, если модифицируемый объект очищается(flush), но это асинхронные операции, без какой-либо блокировки КЭШа или гарантии, что полученные данные являются последней версией.
•    Только-для-чтения – данная стратегия подходит для данных, которые никогда не меняются. Используйте её только для справочных данных.
Обратите внимание, что с уменьшением строгости приходит увеличение производительности. Вы должны тщательно оценивать эффективность кластерного КЭШа с полной изоляцией транзакций, перед тем как использовать его в реальных условиях. Во многих случаях, вам было бы лучше, отключить кэш второго уровня для конкретного класса, если устаревшие данные не приемлемы. В начале, протестируйте ваше приложение с отключенным КЭШем второго уровня. Тогда включайте его только для хороших классов-кандидатов, по одному, постоянно тестируя производительность вашей системы и оценивая стратегию параллелизма.
    Можно определить свою собственную стратегию, реализуя net.sf.hibernate.cache.CacheConcurrencyStrategy, но это довольно трудная задача и применяется только в крайне редких случаях  оптимизации.
    Вашим следующим шагом после выбора стратегий параллелизма, которые вы будете использовать для ваших кандидатов на кэширования, является выбор поставщиков КЭШа. Поставщик является подключаемым, физической реализаций системы кэширования.


Выбор поставщика КЭШа
Сейчас, Hibernate заставляет вас выбирать одного поставщика КЭШа для всего приложения. Следующие поставщики встроены в Hibernate:
•    EHCache предназначен для простого процессного кэширования в одной JVM. Он может кэшировать в памяти или на диске и поддерживает опциональный Hibernate кэш результатов запроса.
•    OpenSymfony OSCache – это библиотека, которая поддерживает кэширование в памяти и на диске в одной JVM, с богатым набором политик истечения и поддержкой КЭШа запросов.
•    SwarmCache – это кластерный кэш, основанный на JGroups. Он использует кластерное аннулирование, но не поддерживает кэш Hibernate запросов.
•    JBossCache – это полностью транзакционно-репликационный кластеризованный кэш, также основанных на JGroups. Кэш запросов Hibernate поддерживается, предполагая, что часы в кластере синхронизированы.
Легко написать адаптер для других продуктов, реализуя net.sf.hibernate.
cache.CacheProvider.
    Не каждый поставщик КЭШа совместим с каждой стратегией параллелизма. Матрица совместимости представлена в таблице 5.1 поможет вам выбрать соответствующую комбинацию.

Поставщик кэша
только-для-чтения
нестрогое-чтение-запись
чтение-запись
транзакционная
EHCache
x
x
x
OSCache
x
x
x
SwarmCache
x
x
x
JBossCache
x
x


Настройка кэширования включает в себя два этапа:
1.    Взгляните на файлы отображения ваших хранимых классов и решите, какую стратегию параллельного КЭШа вы хотели бы использовать для каждого класса и для каждой ассоциации.
2.    Включите предпочтительного поставщика КЭШа в глобальной конфигурации Hibernate и установите конкретные настройки поставщика.
Например, если вы используете OSCache, нужно отредактировать oscache.properties, или для EHCache ehcache.xml в ваших путях к классам(classpath).
    Давайте, добавим кэширование к нашему CaveatEmptor  Category и Item классам.

Hibernate кэширование на практике и заключение (Ч5)

суббота, 23 января 2010 г.

Пессимистическая и оптимистическая блокировки, транзакции приложения, версионирование, детализация сессии (Ч3)

 5.1.7 Использование пессимистической блокировки
Блокировка – это механизм, который предотвращает одновременный доступ к конкретному объекту данных. Когда одна транзакция владеет блокировкой на элемент, то непараллельная транзакция может читать и/или изменять этот элемента. Блокировка может быть только моментальным замком, состоявшимся в том время, как элемент был прочитан, или он может состояться до тех пор пока транзакция не завершится. Пессимистическая блокировка – это блокировка, которая устанавливается при чтении элемента и удерживается до тех пор, пока транзакция не завершится.
    В режиме чтения подтвержденного (наш предпочтительный уровень изоляции транзакций), БД никогда не приобретает пессимистическую блокировку, если это явно не запрашивается приложением.  Как правило, пессимистические блокировки не являются самым масштабируемым подходом к параллелизму. Однако, при определенных обстоятельствах, они могут быть полезны для предотвращения тупиковых ситуаций на уровне БД, которые приводят к сбою транзакций. Некоторые СУБД (например, Oracle и PostgreSQL) обеспечивают SQL SELECT … FOR UPDATE синтаксис, чтобы разрешить использование явных пессимистических блокировок. Вы можете проверить Hibernate диалекты, чтобы узнать поддерживает ли ваша БД эту функцию. Если БД не поддерживает такой функции, то Hibernate будет всегда выполнять обычный SELECT без FOR UPDATE  пункта.
    Класс Hibernate LockMode позволяет запрашивать пессимистические блокировки на конкретный элемент. Кроме того, вы можете использовать LockMode, чтобы заставить Hibernate не использовать уровень КЭШа или выполнить простую проверку версии. Вы увидите выгоду этих операций, когда мы обсудим версионирование и кэширование.
Давайте взглянем на использование LockMode.  Если у вас есть транзакция, на подобии этой:
  Transaction tx = session.beginTransaction();
  Category cat = (Category) session.get(Category.class, catId);
  cat.setName("New Name");
  tx.commit();
тогда, вы можете установить пессимистичную блокировку следующим образом:
  Transaction tx = session.beginTransaction();
  Category cat = (Category) session.get(Category.class, catId, LockMode.UPGRADE);
  cat.setName("New Name");
  tx.commit();
В этом режиме, Hibernate будет загружать категории, используя SELECT … FOR UPDATE, таким образом, блокирую возвращаемые строки в БД, пока они не будут освобождены, когда транзакция завершится.
    Hibernate определяет несколько стратегий блокировок:
•    LockMode.NONE – не обращается к БД, за исключением случае, если объект не в КЭШе.
•    LockMode.READ – обходит оба уровня КЭШа, а также выполняет проверки версии, чтобы убедиться, что версия объекта в памяти совпадает с той, что существует в БД.
•    LockMode.UPGRADE – обходит оба уровня КЭШа, выполняет проверку версии (если применимо), и получает пессимистическую блокировку обновления уровня БД, если она поддерживается.
•    LockMode.UPGRADE_NOWAIT – тоже самое, что UPGRADE, но использует SELECT … FOR UPDATE NOWAIT в Oracle. Это отключает ожидание освобождения параллельных блокировок, тем самым выбрасывая исключение о блокировке немедленно, если блокировка не может быть получена.
•    LockMode.WRITE – устанавливается автоматически, когда Hibernate записывает строку в текущей транзакции (это внутренний режим; вы не можете указать это явно).

По умолчанию, load() и get() используют LockMode.NONE. LockMode.READ является наиболее полезным с Session.lock() и несвязанным объектом. Например:
  Item item = ... ;
  Bid bid = new Bid();
  item.addBid(bid);
  ...
  Transaction tx = session.beginTransaction();
  session.lock(item, LockMode.READ);
  tx.commit();
Этот код выполняет проверку версии на несвязанном Item для проверки того, что строка БД не обновлена другой транзакцией с момента её извлечения, перед сохранением нового Bid каскадно (предполагая, что каскадная ассоциация от Item к Bid разрешена).
    Задавая явный LockMode, отличный от LockMode.NONE, вы заставляете Hibernate обойти оба уровня КЭШа и пройти весь путь к БД. Мы считаем, что в большинстве случаев кэширование является более полезным, чем пессимистическая блокировка, поэтому мы не используем явный LockMode, если мы действительно в нем не нуждаемся. Наш совет в том, что если у вас есть профессиональный администратор БД на вашем проекте, то пусть администратор решит какие операции требуют пессимистических блокировок, как только приложение запущено и работает. Это решение должно зависеть от тонких деталей взаимодействия между различными операциями, о которых можно не догадаться сразу.
    Давайте рассмотрим ещё один аспект одновременного доступа к данным. Мы считаем, что большинство Java разработчиков знакомы с понятием транзакции БД, и это то, что они обычно имеют ввиду под транзакцией. В этой книге, мы полагаем, что это мелкозернистые(fine-grained) транзакции, но мы также полагаем более крупнозернистое понятие. Наши крупнозернистые транзакции будут соответствовать тому, что пользователь считает одной единицей работы. Почему они должны отличаться от мелкозернистых транзакций БД?
   БД изолирует воздействие параллельных транзакций БД. Она должна появиться в приложении, где каждая транзакция является единственной доступной транзакцией в настоящее время в БД (даже если это не так). Изоляция является дорогостоящей. БД должна выделять значительные ресурсы для каждой транзакции на время транзакции. В частности, мы уже обсуждали, что многие БД блокируют строки, которые были прочитаны или обновлены в транзакции, не допуская любые другие транзакции, до завершения первой транзакции. В системах с высокой степенью параллелизма, эти блокировки могут ухудшить масштабируемость, если они удерживаются дольше, чем это абсолютно необходимо. По этой причине, вы не должны удерживать транзакции БД (или даже JDBC соединений) открытых, в ожидании пользовательского ввода (все это разумеется также относится к Hibernate транзакции, поскольку это всего лишь адаптер к основном механизму транзакций БД).
    Если вы хотите работать с длительным ожиданием пользовательских действий, используя преимущества ACID транзакций, то простые транзакции БД не являются достаточными. Вам нужна новая концепция, длительные транзакции приложения.


5.2 Работа с транзакциями приложения
Бизнес-процессы, которые могут рассматриваться, как одна единица работы с точки зрения пользователя, обязательно охватывают несколько клиентских запросов. Это особенно справедливо, когда пользователь принимает решение об обновлении данных на основе текущего состояния этих данных.
    В крайнем примере, предположим, что вы собираете данные, введенные пользователем на нескольких экранах, возможно, с помощью пошагового мастера. Вы должны прочитать и записать соответствующие пункты данных в нескольких запросах (следовательно, несколько транзакций БД) пока пользователь не нажмет «Готово» на последней странице. На протяжении всего этого процессы, данные должны оставаться согласующимися и пользователь должен быть проинформирован о любых изменениях в данных, сделанных в любой параллельной транзакции. Мы называем это крупнозернистой концепцией транзакции приложения, более широкое понятие единицы работы.
    Мы сейчас переформулируем это определение точнее. Большинство веб-приложений включает в себя несколько примерно следующих типов функциональности.
1.    Данные извлекаются и отображаются на экране в первой транзакции БД.
2.    Пользователь имеет возможность просмотреть и изменить данные, вне всяких транзакций БД.
3.    Изменения сохраняются в БД во второй транзакции.
В более сложных приложения, это может быть несколько таких взаимодействий с пользователем до того, как конкретное бизнес-действие будет завершено. Это приводит к понятию транзакции приложения (иногда, называемое, длинные транзакции, пользовательская транзакция или бизнес транзакция). Мы предпочитаем понятие транзакция приложения или пользовательская транзакция, так как эти условия являются менее расплывчатыми и подчеркивают аспект транзакции с точки зрения пользователя.
    Поскольку вы не можете рассчитывать на БД для обеспечения изоляции (или даже атомарности) от параллельных транзакций приложения, изоляция становится заботой самого приложения, возможно даже озабоченностью пользователя.
    Давайте обсудим применение транзакций приложения на примере.
В нашем приложении CaveatEmptor, как пользователь может размещать комментарий, так и любой администратор может открыть экран редактирования комментариев, удалять или редактировать текст комментария. Предположим, что два разных администратора открыли экран редактирования, чтобы посмотреть один и тот же комментарий одновременно. Как изменить текст комментария и отобразить эти изменения. На данные момент, мы имеем три способа справиться с попыткой одновременной записи в БД:
•    Последний коммит выигрывает  - оба обновления являются успешными, а второе изменение перезаписывает изменения первого. Сообщение об ошибке не показывается.
•    Первый коммит выигрывает – первая модификация записывается, а пользователь, предоставивший второе изменение, получает сообщение об ошибке. Пользователь должен перезапустить бизнес-процесс, получая обновленные комментарии. Этот вариант часто называют оптимистической блокировкой.
•    Слияние конфликтующих обновлений – первая модификация записывается, а вторая может применяться пользователем по выбору.
Первый вариант, где последний выигрывает, проблематичен; второй пользователь перезаписывает изменения первого пользователя, не видя изменений, внесенных первым пользователем, даже зная, что они существуют.  В нашем примере, это вероятно, не имело бы значения, но это было бы неприемлемо для некоторых других видов данных. Второй и третий варианты, как правило приемлемы для большинства видов данных. С нашей точки зрения, третий вариант, это просто изменений второго – вместо того, чтобы показывать сообщение об ошибке, мы показываем сообщение, а затем позволяем пользователю вручную объединить изменения. Не существует единственного наилучшего решения. Вы должны исследовать собственные бизнес-требования, чтобы выбрать между этими тремя вариантами.
    Первый вариант бывает по умолчанию, если вы не делаете ничего особенного в вашем приложении, поэтому этот вариант не требует никаких усилий с вашей стороны (или на стороне Hibernate). Вы будете иметь две транзакции БД: данные комментария загружается в первой транзакции, а вторая транзакция сохраняет изменения в БД, без проверки наличия обновлений, что могло произойти.
    С другой стороны, Hibernate может помочь вам реализовать вторую и третью стратегию, с помощью управляемого версионирования для оптимистической блокировки.


5.2.1 Использование управляемого версионирования
Управляемой версионирование основывается либо на номере версии, который увеличивается или на метке, которая обновляется в настоящем времени, при любом изменении объекта. Для управляемого версионирования в Hibernate, мы должны добавить новое свойство для нашего класса Comment и отобразить его в качестве номера версии, используя тег . Во-первых, давайте посмотрим на изменения в классе Comment:

public class Comment {
  ...
  private int version;
  ...
  void setVersion(int version) {
    this.version = version;
  }
  int getVersion() {
    return version;
  }
}

Вы можете также использовать модификаторы public для сеттера и геттера. Свойство должно прийти сразу после отображения идентификатора в файле отображения для класса Comment:
<class name="Comment" table="COMMENTS">
<id ...
<version name="version" column="VERSION"/>
...
</class>


Номер версии – это просто счетчик, он не имеет какой-либо полезного семантического значения. Некоторые люди предпочитают использовать временные метки(timestamp), вместо счетчика:
public class Comment {
  ...
  private Date lastUpdatedDatetime;
  ...
  void setLastUpdatedDatetime(Date lastUpdatedDatetime) {
    this.lastUpdatedDatetime = lastUpdatedDatetime;
  }
  public Date getLastUpdatedDatetime() {
    return lastUpdatedDatetime;
  } 
}
<class name="Comment" table="COMMENTS">
<id ...../>
<timestamp name="lastUpdatedDatetime" column="LAST_UPDATED"/>
...
</class>
Теоретически, временные метки немного менее безопасны, так как две параллельные транзакции могут одновременно загружать и обновлять один элемент в одну и ту же миллисекунду; на практике это вряд ли произойдет. Тем не менее, мы рекомендуем, чтобы в новых проектах использовались числовая версия, а не временные метки.
    Вам не нужно устанавливать значение версии или временной метки самому; Hibernate будет инициализировать значение, когда вы впервые сохраните Comment и увеличит и сбросит его при каждом изменении объекта.

FAQ    Если версия родительского объекта изменится, то изменится ли наследник? Например, если одна ставка в коллекции ставок Item изменится, то изменится ли номер версии для Item или нет? Ответ на этот и подобные вопросы прост: Hibernate будет увеличивать номер, когда объект «загрязнен»(dirty). Это включает в себя все «грязные» свойства, будь то одиночные значения или коллекции. Подумайте о взаимоотношениях Item и Bid: если изменяется Bid, то версия связанного Item не увеличивается. Если мы добавим или удалим  Bid из коллекции ставок, то версия Item будет обновлена (конечно, мы хотели бы сделать Bid неизменяемым классом, поскольку не имеет смысла изменять ставки).
Всякий раз, когда Hibernate обновляет комментарий, он использует колонку с версией в SQL WHERE условии:
update COMMENTS set COMMENT_TEXT='New comment text', VERSION=3
where COMMENT_ID=123 and VERSION=2
Если другая транзакций приложения будет обновлять этот же элемент, то поскольку он был считан в текущей транзакции приложения, столбец с версией не будет содержать значение 2, и строка не будет обновлена. Hibernate будет проверять количество строк, возвращаемых JDBC драйвером –которые в данном случае будут обозначать количество обновляемых строк, если их количество будет равно нулю, то будет выброшено исключение StaleObjectStateException.
    Используя это исключение, мы можем показать пользователю второй транзакции приложения сообщение об ошибке («Вы работали с устаревшими данными, поскольку другой пользователь изменил их!»), и пусть первая транзакция выиграет. Кроме того, мы могли бы поймать исключение и показать второму пользователю новый экран, позволяющий пользователю вручную произвести слияние между двумя версиями.
    Как вы можете видеть, Hibernate позволяет с легкостью использовать версии для осуществления оптимистической блокировки. Можете ли вы использовать оптимистические и пессимистические блокировки вместе, или вы можете принять решения только в пользу одной блокировки? А почему она называется оптимистической?
    Оптимистический подход всегда предполагает, что все будет хорошо, и что конфликтующие изменения данных происходят редко. Вместо того, чтобы быть пессимистичным и блокировать одновременный доступ к данным сразу же (и форсируя сериализацию выполнение), оптимистический контроль параллелизма будет блокировать только в конце единицы работы и выбрасывать исключение.

    Конечно, обе стратегии имеют свои области применения. Многопользовательские приложения обычно по умолчанию используют оптимистический контроль параллелизма и используют пессимистические блокировки, когда это необходимо. Обратите внимание, что продолжительность пессимистической блокировки в Hibernate является продолжительность одной транзакции БД! Это означает, что вы не можете использовать эксклюзивную блокировку, чтобы заблокировать одновременный доступ дольше, чем на одну транзакцию БД. Мы считаем, что это хорошо, потому что единственным решением была бы чрезвычайно дорогая блокировка в памяти (или так называемая блокировка таблицы в БД) на срок, например, транзакции приложения. Это почти всегда узкое место в производительности; каждый доступ к данным требует дополнительной проверки блокировки с синхронизированным менеджером блокировок. Вы можете, если это сильно необходимо в вашем конкретном приложении, осуществлять простую длительную пессимистичную блокировку, используя Hibernate для управления блокированием таблицы. Шаблоны для этого могут быть найдены на сайте Hibernate, но мы определенно не рекомендуем такой подход. Необходимо внимательно изучить последствия этого исключительного случая.
    Давайте вернемся к транзакциям приложения. Теперь вы знаете основы управляемого версионирования и оптимистической блокировки. В предыдущих главах (а ранее в этой главе), мы говорили о Hibernate-сессии, как не о том, что является транзакцией. В самом деле, сессия имеет большую гибкость, и её можно использовать по-разному с БД и транзакциями приложения. Это означает, что степень детализации(гранулированности) является гибкой; это может быть любая единица работы, по вашему желанию.

5.2.2 Детализации сессии
Чтобы понять, как можно использовать Hibernate-сессию, давайте рассмотрим отношения между транзакциями. Ранее, мы обсуждали две взаимосвязанные концепции:
•    Уровень идентичности (см. раздел 4.1.4)
•    Детализацию БД и транзакций приложения
Hibernate сессия определяет сферу идентичности объекта. Транзакция Hibernate соответствует сфере транзакций БД.
(РИСУНОК 5.2)
Какова взаимосвязь между сессиями и транзакциями приложения? Давайте начнем обсуждение с наиболее распространенным использованием сессии.
Обычно, мы открываем новую сессию для каждого запроса клиента (например, запроса веб-браузера) и начинаем новую транзакцию. После выполнения бизнес-логики, мы фиксируем изменений транзакции БД и закрываем сессию перед отправкой ответа клиенту (см. рис. 5.2).
Сессии (S1) и транзакции БД (T1) имеют одну и ту же степень детализации. Если вы работаете с концепцией транзакций приложения, то это простой подход – все что вам нужно в вашем приложении. Нам нравится называть такой подход сессия-на-запрос.
Если вам необходима длительная транзакция приложения, то вам могут помочь несвязанные объекты (и Hibernate поддерживает оптимистическую блокировку, обсуждаемую в предыдущем разделе), осуществите его, используя тот же подход (см. рис. 5.3).
Предположим, что ваша транзакция приложения охватывает два клиентских запроса/ответа, например два HTTP-запроса в веб-приложении. Вы можете загрузить интересующие объекты в первой сессии, а затем связать их к новой сессии после того, как они были изменены пользователем.  Hibernate автоматически проверит версию. Время между (S1, T1) и (S2, T2) может быть «длинным», настолько длинным, насколько это нужно пользователю, чтобы сделать его изменения. Этот подход известен также, как сессия-на-запрос-с-несвязанными-объектами.
    Кроме того, вы можете предпочесть использовать одну сессию, которая охватывает несколько запросов вашей транзакции приложения. В этом случае, вам не нужно беспокоиться о присоединении несвязанных объектов, так как объекты остаются хранимыми в рамках одной длительной сессии (см. рис. 5.4). Конечно, Hibernate по-прежнему несет ответственность за выполнение оптимистической блокировки.
    Сессия сериализуемая и может быть безопасно сохранена, например, в сервлете HttpSession. Конечно, базовое JDBC соединение должно быть закрыто и новое соединение должно быть получено на следующий запрос. Вы используете методы disconnect() и reconnect() интерфейса сессии для того, чтобы освободить соединение и затем получать новое соединение. Такое подход известен, как сессия-на-транзакцию-приложения или длинная сессия.
    Как правило, ваш первый выбор должен сохранять Hibernate сессию открытой не более, чем на одну транзакцию БД (сессия-на-запрос). После завершения первоначальной транзакции БД, чем дольше сессия остается открытой, тем больше шансов, что она удерживает устаревшием данные в КЭШе хранимых объектов (кэш первого уровня является обязательным в сессии). Конечно, вы никогда не должны повторно использовать одну сессию дольше, чем это требуется для завершения одной транзакции приложения.
    Вопрос о реализации транзакций приложения и объем сессии важен при построении приложения. Мы обсудим реализацию стратегии, с примерами в главе 8 «Реализация транзакций приложения».
    В заключении, существует важный вопрос, которым вы можете быть обеспокоены. Если вы работаете с предоставленной схемой БД, то вы, вероятно, не можете добавить колонки версию или временных отметках для оптимистической блокировки в Hibernate.

5.2.3 Другие способы реализации оптимистической блокировки
Если у вас не колонок с версией или временной отметки, то Hibernate может выполнять оптимистическую блокировку, но только для объектов, которые извлечены и изменены в той же сессии. Если вам необходима оптимистическая блокировка для несвязанных объектов, то вы должны использовать номер версии или временные метки.
    Это альтернативная реализация оптимистической блокировки проверяет  текущее состояние БД, на неизменность значений хранимые свойств, в то время, как объект был извлечен (или во время последней очистки сессии). Вы можете включить эту функцию, установив optimistic-lock атрибут в файле отображения класса:
<class name="Comment" table="COMMENT" optimistic-lock="all">
<id ...../>
...
</class>

Сейчас, Hibernate будет включать все свойства в WHERE условие:
update COMMENTS set COMMENT_TEXT='New text'
where COMMENT_ID=123
and COMMENT_TEXT='Old Text'
and RATING=5
and ITEM_ID=3
and FROM_USER_ID=45
Кроме того, Hibernate будет включать только изменяемые свойства (только COMMENT_TEXT в данном примере) если вы выставите optimistic-lock=”dirty” (заметим, что этот параметр также требует, чтобы вы установили файл отображения класса в dynamic-update=”true”).
    Мы не рекомендуем этот подход; он медленный, более сложный и менее надежный, чем номер версии и не работает, если ваша транзакция приложения охватывает несколько сессий (как в случае, если вы используете несвязанные объекты).
    Мы теперь снова переключимся и рассмотрим новый аспект Hibernate. Мы уже упоминали о тесной взаимосвязи между транзакциями и кэшированием во введении этой главы. Основы транзакций и блокировки, а также детализация сессий, имеют важнейшее значение, если учесть кэширование данных на уровне приложения.

пятница, 22 января 2010 г.

Очистка сессии, уровни изоляции транзакций, выбор и установка уровня изоляции транзакций в Hibernate (Ч2)

это перевод книги Hibernate In Action Главы 5
Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1)

5.1.3 Очистка Session
Hibernate интерфейс Session реализует прозрачную запись. Изменения в модели предметной области, сделанные в области действия Session не распространяются немедленно на БД. Это позволяет Hibernate объединять множество изменений в минимальном количестве запросов к БД, помогая свести к минимуму воздействие задержек сети.


Например, если одно свойство объекта изменилось дважды в одной Transaction, Hibernate нужно выполнить только один SQL UPDATE. Другим примером полезности прозрачной записи, является то, что Hibernate может воспользоваться преимуществами пакетного(batch) выполнения запросов JDBC, когда выполняет несколько UPDATE, INSERT или DELETE выражений.
Очистка Hibernate сессии происходит только в следующих случаях:
•    когда Transaction фиксируется(коммит);
•    иногда перед выполнением запроса;
•    когда приложение вызывает Session.flush() явно;


Очистка состояния сессии в БД в конце транзакции БД необходимо для того, чтобы сделать изменения долговечными. Hibernate не очищает  сессию перед каждым запросом. Однако, если есть изменения хранимые в памяти, которые могут повлиять на результат запроса, то Hibernate, по умолчанию, синхронизирует их в первую очередь.


    Вы можете контролировать это поведение явно, устанавливая Hibernate FlushMode через вызов Session.setFlushMode(). Режимами очистки сессии являются:
•    FlushMode.AUTO – по умолчанию. Устанавливает поведение, описанное только что.
•    FlushMode.COMMIT – указывает, что сессия не будет очищена перед выполнением запроса (она будет очищена только в конце транзакции БД). Имейте в виду, что этот параметр может затронуть устаревшие данные: изменения, внесенные в объекты только в памяти, могут вступать в противоречие с результатами запроса.
•    FlushMode.NEVER – позволяет вам самим указать, что только явные вызовы flush()  приведут к синхронизации состояния сессии с БД.
 

Мы не рекомендуем вам изменять эту настройку по умолчанию. Это позволяет организовать оптимизацию производительности в редких случаях. Аналогичным образом, большинству приложений редко нужно указывать flush() явно. Эта функция полезна, когда вы работаете с триггерами, смешивая Hibernate с прямым JDBC, или работает с ошибками JDBC драйверов. Вы должны быть осведомлены о такой возможности, но необязательно использовать её.
Теперь вы понимаете, что основой использования транзакций с Hibernate является интерфейс Transaction, давайте обратим наше внимание на одновременный доступ к данным.

Может показаться, как будто вы не должны заботиться об изоляции транзакции – термин подразумевает, что что-то является и не является изолированным. Это вводит в заблуждение. Полная изоляция одновременных операций является исключительно дорогостоящей операцией, с точки зрения масштабируемости приложений, поэтому БД предоставляет несколько степеней изоляции. Для большинства приложений, частичная изоляция транзакций является приемлемым вариантом. Важно понимать, что степень изоляции следует выбирать для приложения, которое использует Hibernate, а также понимать, как Hibernate интегрируется с возможностями транзакций БД.
 

5.1.4 Понимание уровней изоляции
БД (и другие транзакционные системы) пытаются обеспечить изоляцию транзакций, а это означает, что, с точки зрения каждой одновременной транзакции, никакие другие транзакции не выполняются.
    Традиционно, это реализуется с использованием блокировок. Транзакция может установить блокировки на конкретный элемент данных, временно предотвращая доступ к этому элементу, другим транзакциям. Некоторые современные БД, такие как Oracle и PostgreSQL реализуют изоляцию транзакций, используя многоверсионный контрольно-измерительный параллелизм, который обычно считается более масштабируемым решением. Мы обсудим изоляцию, предполагая модель блокировок (большинство из наших наблюдений также применимы к многоверсионному параллелизму).
    Эта дискуссия о транзакциях БД и об уровне изоляции, предоставляемому БД.  Hibernate не добавляет дополнительную семантику; он использует  все, что доступно с данной БД. Если вы следили за многолетним опытом, которые поставщики БД имели, реализуя контроль параллельности, то вы будете четко видеть преимущества этого подхода. Ваша задача, как разработчика Hibernate приложений, состоит в том, чтобы понять возможности вашей СУБД и как можно изменить поведение  изоляцию БД, в случае необходимости, конкретно в вашем сценарии (и по вашим требованиям целостности данных).

Вопросы изоляции
Во-первых, давайте взглянем на некоторые явления, которые нарушают полную изоляцию транзакций. Стандарт ANSI SQL определяет стандартные уровни изоляции транзакций, с точки зрения того, какие из этих явлений являются допустимыми:
•    Потерянное обновление – две транзакции обновляют строку, а затем вторая прерывает транзакцию, в результате чего оба изменения будут потеряны. Это происходит в системах, которые не реализуют какую-либо блокировку. Одновременные транзакции не изолированы.
•    Грязное чтение - одна транзакция читает изменения, сделанные в другой транзакции, которая ещё не до конца совершена. Это очень опасно, потому что эти изменения не зафиксированы.
•    Неповторяемое чтение – транзакция читает строку дважды и считывает различные состояния каждый раз. Например, другая транзакция может быть записана в строку и зафиксирована между двумя считываниями.
•    Вторая проблема обновлений – частный случай неповторяемого чтения. Представьте себе, что есть две параллельные транзакции, обе читают строки, одна записывает туда и фиксируется, и далее вторая пишет туда же и фиксируется. Изменения, сделанные первой транзакцией, будут потеряны.
•    Фантомное чтение – транзакция выполняет запрос дважды, и второй результирующий набор включает в себя строки, которые были не видны в первом результирующем наборе (это не обязательно должен быть точно такой же запрос). Такая ситуация вызвана другой транзакцией, которая вставляла новые строки между выполнением этих двух запросов.

Теперь, когда вы понимаете все плохое, что может произойти, мы можем определить различные уровни изоляции транзакций и увидеть, какие проблемы они предотвращают.
Уровни изоляции
Стандартные уровни изоляции определяются стандартом ANSI SQL, но без привязки к SQL БД. JTA определяет те же уровни изоляции, и вы будете использовать эти уровни, чтобы позднее заявить желаемые уровень изоляции транзакции:
•    Неподтверждённое чтение – разрешает грязные чтения, но без потери обновлений. Одна транзакция может не писать в строку, если другая незафиксированная транзакция уже записывает туда. Однако, любая транзакция может читать любые строки. Этот уровень изоляции может быть реализован с использованием эксклюзивной блокировки записи.
•    Подтверждённое чтение  - разрешает неповторяемые чтения, но не грязные чтения. Это может быть достигнуто с помощью мгновенных общих блокировок чтения и эксклюзивной блокировки записи. Однако, незафиксированные пишущие транзакции блокируют  все другие транзакции на доступ к строке.
•    Повторяемое чтение – не допускает ни неповторяемого чтения, ни грязного чтения. Фантомное чтение может произойти. Это может быть достигнуто с использованием общих блокировок на чтение и эксклюзивной блокировки на запись. Блок чтения пишущих транзакций (но не другие операции чтения) и блок  пишущих транзакций блокируют все другие транзакции.
•    Упорядоченный (сериализуемый) – обеспечивает строгую изоляцию транзакций. Он эмулирует последовательное выполнение операций, как если бы операция была выполнена одна за другой последовательно, а не параллельно. Упорядоченность не может быть реализована с использованием только блокировки на уровне строк; это должен быть другой механизм, который предотвращает становление видимой, только что вставленной строки, из транзакции, которая уже выполняет запрос, возвращающий строку.
Приятно знать, как определены все эти технические термины, но как это поможет вам выбрать уровень изоляции, необходимый для вашего приложения?

5.1.5 Выбор уровня изоляции
Разработчики (включая нас) зачастую не уверены в уровне изоляции транзакций для использования в работающих приложениях. Слишком большая степень изоляции повредит производительность приложений с большим количеством одновременных операций. Недостаточная изоляция может привести к тонким ошибкам в нашем приложении, которые не могут быть воспроизведены, и мы никогда не узнаем об их наличии, пока система не поработает под большой нагрузкой.
    Обратите внимание на то, что мы называем кэшированием и оптимистической блокировкой (с помощью версионирования), эти две концепции объясняются позже в этой главе. Вы можете пропустить этот раздел и вернуться, когда придет время принимать решение по уровню изоляции в вашем приложении. Выбор правильного уровня изоляции, в конце концов, в значительной степени зависит от конкретного сценария. Это обсуждение содержит рекомендации; это не является прописными истинами.
    Hibernate старается быть как можно более прозрачным в отношении семантики транзакций БД. Тем не менее, кэширование и оптимистические блокировки влияют на эти семантики. Итак, что же представляет собой разумный уровень изоляции БД в Hibernate приложении?
    Во-первых, устраните уровень чтения неподтвержденного. Крайне опасно использовать изменения, незафиксированные одной транзакцией, в других.  Откат или сбой одной транзакции повлияет на другие параллельные транзакции. Откат первой транзакции может сбить другие транзакции или, возможно, даже оставить БД в противоречивом состоянии. Вполне возможно, что изменения, внесенные транзакцией, которая заканчивается во время отката, могут быть зафиксированы в любом случае, поскольку они могут быть считаны и затем распространятся  на другую транзакцию, которая была успешна!
    Во-вторых, большинству приложений не нужна упорядоченная (сериализованная)  изоляция (фантомное чтение обычно не проблема), и  этот уровень изоляции, как правило, плохо масштабируется. Немногие из существующих приложений используют упорядоченную изоляцию в работающем приложении; а скорее они используют пессимистическую блокировку (см. раздел 5.1.7, «Использование пессимистических блокировок»),  которая эффективно справляется с упорядоченным выполнением   операций в определенных ситуациях.
    Это оставляет вам выбор между чтением подтвержденного  и повторяемого чтения. Этот уровень изоляции исключает вероятности того, что одна транзакция может перезаписать изменения, сделанные другой параллельной транзакцией (вторая проблема  потери обновлений), если доступ к данным осуществляется в одной атомарной транзакции БД. Это важный вопрос, но использование повторяемого чтения не является единственным путем к его решению.
    Предположим, что вы используете версионирование данных, то, что Hibernate может сделать для вас автоматически. Сочетание (обязательного) сессионного Hibernate-кэша первого уровня и версионирования уже дают вам большинство функций для изоляции повторяемого чтения. В частности, версионирование предотвращает вторую проблему потери обновлений, и сессионный кэш первого уровня гарантирует, что состояние хранимых сущностей, загружаемых одной транзакцией, изолированы от изменений, делаемых в других транзакциях. Таким образом, изоляция чтения зафиксированного для всех транзакций БД было бы приемлемо, если вы используете версионирование данных.
    Повторяемое чтение обеспечивает немного большую воспроизводимость для результатов запроса (только на время транзакции БД), но поскольку фантомное считывание все ещё возможно, существует не так много смысла в этом (запрашивать одну таблицу дважды в одной транзакции  также не характерно для веб-приложений).
    Вы также можете рассмотреть (по желанию) Hibernate кэш второго уровня. Он может обеспечить такую же изоляцию транзакций, как и транзакция БД, но он может даже ослабить изоляцию. Если вы интенсивно используете стратегию параллельного кэширования для КЭШа второго уровня, который сохраняет семантику повторяемого чтения (например, для стратегии чтения-записи и особенно для стратегии нестрогого чтения-записи, обе они обсуждаются ниже в этой главе), выбор уровня изоляции по умолчанию прост: вы не можете достичь повторяемого чтения в любом случае, так что нет смысла в замедлении БД. С другой стороны, вы можете не использовать кэш второго уровня для критических классов, или вы можете полностью использовать транзакционный кэш, который обеспечивает изоляцию повторяемого чтения. Должны ли вы использовать повторяемое чтение в этом случае? Вы можете, если хотите, но это, наверное, не стоит накладных расходов.
    Установка уровня изоляции транзакций позволяет выбрать хорошую стратегию блокировки по умолчанию для всех ваших транзакций БД. Как установить уровень изоляции?

5.1.6 Установка уровня изоляции
Каждое JDBC соединение к БД использует уровень изоляции, установленный самой БД, как правило, это чтение подтвержденного или повторяемое чтение. Это значение по умолчанию может быть изменено в конфигурации БД. Вы также можете установить уровень изоляции транзакций для JDBC соединений, с помощью опции конфигурации Hibernate:

    hibernate.connection.isolation = 4

Hibernate затем установит этот уровень изоляции на каждое соединении JDBC, полученное из пула соединений, до начала транзакции. Разумные значения для этой опции выглядят следующим образом (вы также можете найти их, как константы в java.sql.Connection):
•    1 – изоляция уровня чтения неподтвержденного;
•    2 – изоляция уровня чтения подтвержденного;
•    4 – изоляция уровня повторяемого чтения;
•    8 – упорядоченная изоляция.

Обратите внимание, что Hibernate никогда не меняет уровень изоляции соединений, полученных из источника данных, предоставляемых сервером приложений в управляемой среде. Вы можете изменить стандартный уровень изоляции с помощью конфигурации сервера приложений.
    Как вы можете видеть,  установка уровня изоляции представляет собой глобальную опцию, которая затрагивает все соединения в транзакциях. Время от времени, полезно указать более строгую блокировку для конкретной транзакции. Hibernate позволяет явно указывать использовать пессимистической блокировки.

Пессимистическая и оптимистическая блокировки, транзакции приложения, версионирование, детализация сессии (Ч3) 
Кэширование в Hibernate (Ч4) 
Hibernate кэширование на практике и заключение (Ч5)

четверг, 21 января 2010 г.

Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1)

это перевод пятой главы книги Hibernate In Action 
Давайте поближе взглянем на один из главных вопросов в проектировании приложений: управление транзакциями. В этой главе, мы рассмотрим, как использовать Hibernate для управления транзакциями, как обрабатывается параллелизм, и как кэширование относится к обоим аспектам. Давайте взглянем на наше тестовое приложение.
Некоторая функциональность приложения требует, чтобы несколько различных вещей делались вместе. Например, когда аукцион заканчивается, наш приложение CaveatEmptor выполняет четыре различных задачи:
   1.    Отмечает лидирующую (наибольшее количество) ставку.
   2.    Назначает цену аукциона, установленную продавцом.
   3.    Назначает ставку успешного игрока, сделавшего лидирующую ставку.
   4.    Уведомляет продавца и успешных игроков.
Что произойдет, если мы не сможем установить ставку аукциона, из-за сбоя внешней системы кредитных карт?
Наши бизнес требования могут состоять в том, чтобы все составленные в список действия, должны  быть выполнены успешно или ни одно не должно быть выполнено. Если так, то мы назовем эти шаги транзакцией или единицей работы. Если хотя бы один шаг потерпит неудачу,  то вся единица работы должна потерпеть неудачу. Будем говорить, что транзакция атомарна. Несколько операций группируются вместе, как единые, неделимые единицы.
К тому же, транзакции разрешают многочисленным пользователям работать параллельно с одними данными без компромиссов целостности и корректности данных; различные транзакции не должны быть видимы и не должны оказывать влияние на другие параллеьно-запущенные транзакции. Несколько различных стратегий используются для реализации этого требования, которое называется изолированностью. Мы исследуем их в данной главе.
Транзакции также проявляют согласованность и долговечность. Согласованность означает, что любые транзакции работают с последовательным набором данных и оставляют данные в непротиворечивом состоянии, когда транзакция завершается.  Долговечность гарантирует, что когда одна транзакция завершается, все изменения, сделанные в течение этой транзакции, сохранятся и не будут потеряны, даже если система впоследствии даст сбой. Атомарность, последовательность, изолированность и долговечность вместе известны, как ACID критерии.
Мы начали эту главу с дискуссии о системном уровне  транзакций БД, где БД гарантирует ACID поведение. Мы взглянем на JDBC и JTA API и увидим, как Hibernate, работая как клиент с этим API, используется для контроля транзакций БД.
В онлайн-приложении, транзакции БД должны иметь чрезвычайно короткий срок жизни. Транзакции БД должны распространяться на одну серию операций с БД, чередующихся с бизнес-логикой.  Мы расширим ваше понимание транзакций, пониманием длинных транзакций приложения, где операции БД происходят в различных сериях операций, чередующихся с пользовательским взаимодействием. Есть несколько путей для реализации транзакций приложений в Hibernate приложениях, все они обсуждаются в данной главе. Заключительно, предмет кэширования ближе относится к транзакциям, чем может показаться на первый взгляд. Во второй половине этой главы, вооруженные пониманием транзакций, мы исследуем Hibernate’овскую сложную архитектуру кэширования. Вы узнаете, какие данные являются хорошим кандидатом на кэширование и как управлять параллелизмом КЭШа.
Давайте начнем с основ и увидим, как транзакции работают на нижнем уровне БД.

5.1 Понимание транзакций БД
БД реализует понятие единицы работы, как транзакцию БД (иногда называемую системная транзакция). Транзакции БД группируют операции доступа к данным. Транзакция гарантирует один из результатов работы: она или будет зафиксирована или откачена назад.  Следовательно,  транзакции БД действительно всегда атомарны.
Если несколько операций БД должны выполняться внутри транзакции, вы должны отметить границы единиц работы. Вы должны начать транзакцию и, в некоторой точке,  совершить изменения. Если ошибка случается (или во время выполнения операций или когда  применяются изменения), вы откатываете транзакцию, чтобы оставить данные в согласующемся состоянии. Это известно, как разграничение транзакции, и (в зависимости от используемого API) это включает большее или меньшее ручное вмешательство.
Вы возможно уже имеете опыт управления с двумя программными интерфейсами, с возможностью управления транзакциями: JDBC API и JTA.

5.1.1 JDBC и JTA транзакции

В неуправляемой среде, JDBC API используется для отметки границ транзакций. Вы начинаете транзакцию, вызывая setAutoCommit(false) на JDBC соединении и в конце, вызывая commit(). Вы можете, в любой момент выполнить немедленный откат, вызывая rollback().

FAQ
   
Что должен делать режим авто-коммита? Магическая установка, которая часто является источником недоразумений в JDBC соединениях - это режим автоматического коммита. Если соединение БД находится в режиме автоматического выполнения, то транзакция БД будет выполнена незамедлительно после каждого SQL выражения, и при начале новой транзакции. Это может быть полезно для расширенных запросов к БД и для расширенных обновлений.
Однако, режим авто-коммита чаще всего является не подходящим в приложениях. Приложение не выполняет продвинутые или любые незапланированные запросы; вместо этого, оно выполняет заранее запланированные последовательность связанных операций (которые, по определению, никогда не являются продвинутыми). Поэтому Hibernate автоматически запрещает режим авто-коммита сразу при установлении соединения (из провайдера соединений – это пул соединений). Если вы поддериживаете ваши собственные соединения, когда вы открываете Session, то на вашей ответственности лежит отключение авто-коммита!

Учтите, что некоторые СБД включают авто-коммит по умолчанию для каждого нового соединения, но другие не делают! Вы можете захотеть отключить авто-коммит в вашей глобальной системной конфигурации БД для того, чтобы быть уверенным, что вы никогда не столкнетесь с любыми проблемами. Вы можете разрешить авто-коммит только во время выполнения продвинутых запросов (например, в вашем инструменте для SQL запросов).
В системах, которые сохраняют данные в нескольких БД, обычная единица работы может включать доступ более чем к одному хранилищу данных. При этом, вы не можете достигать атомарности, используя один JDBC. Вам необходим менеджер транзакций с поддержкой для распределенных транзакций (двухфазный коммит). Вы связываетесь с менеджером транзакций, используя JTA.
В управляемой среде, JTA используется не только для распределенных транзакций, но также для декларативного контейнера управляемых транзакций(CMT). CMT позволяет вам избегать явного разграничения вызова транзакций в исходном коде вашего приложения; напротив, разграничение транзакции контролируется дескриптором, зависимым от среды развертывания. Этот дескриптор определяет порядок распространения контекста транзакции, когда один поток проходит через несколько различных EJB.
Мы не заинтересованы в деталях прямого JDBC или JTA разграничения транзакций. Вы будете использовать эти интерфейсы лишь косвенно.
Hibernate взаимодействует с БД через JDBC соединение;  следовательно, он должен поддерживать оба интерфейса. В автономных (или веб) приложениях доступны только JDBC транзакции; в сервере приложений Hibernate может использовать JTA. Поскольку мы хотели, чтобы код Hibernate приложения выглядел одинаково в управляемых и неуправляемых средах, то Hibernate предоставляет свой уровень абстракции, скрывая нижележащий интерфейс транзакций. Hibernate разрешает пользовательские расширения, так чтобы вы могли даже подключить адаптер для CORBA службы транзакций.
Управление транзакциями предоставляется разработчикам приложений через интерфейс Transaction. Вы не обязаны использовать этот интерфейс – Hibernate позволяет контролироваться JTA или JDBC транзакции напрямую, однако такое использование не приветствуется, и мы не будем обсуждать этот вариант.
Интерфейс Transaction предоставляет методы для объявление границ транзакций БД. Смотри листинг 5.1 для примера использования основных операций Transaction.
Listing 5.1 Использование Hibernate Transaction интерфейса
Session session = sessions.openSession();
Transaction tx = null;
try {
  tx = session.beginTransaction();
  concludeAuction();
  tx.commit();
} catch (Exception e) {
  if (tx != null) {
    try {
      tx.rollback();
    } catch (HibernateException he) {
       //log he and rethrow e
    }
  }
  throw e;
} finally {
  try {
    session.close();
  } catch (HibernateException he) {
  throw he;
  }
}

Вызов session.beginTransaction() отмечает начало транзакции БД.  В случае неуправляемой среды, этот вызов стартует JDBC транзакцию на JDBC соединении.  В случае управляемой среды, он начинает новую JTA транзакцию, если нет текущей транзакции JTA или присоединяется к текущей JTA транзакции. Это все обрабатывается Hibernate – вам не нужно заботиться о реализации этого.
Вызов tx.commit() синхронизирует состояние Session с БД. Hibernate затем коммитит соверщаемую транзакцию тогда и только тогда если beginTransaction() начал новую транзакцию (в обоих случаях). Если beginTransaction() не начал транзакцию БД, то commit() только синхронизирует состояние Session c БД; это оставлено на усмотрение ответственного участника (код, который начал транзакцию в первую очередь) к моменту завершения транзакции. Это согласуется с поведением, определенным JTA.
Если concludeActions() выбрасывает исключение, то мы должны принудительно  откатить транзакцию, вызывая tx.rollback(). Этот метод либо осуществляет немедленный откат или помечает транзакцию, как «только для отката» (если вы используете CMT).
FAQ
  
Быстрее ли откатываются немодифицирующие(read-only) транзакции? Если код транзакции считывает данные, но никогда их не модифицирует, должны ли вы откатывать транзакцию, вместо того, чтобы коммитить её? Будет ли это быстрее?
Видимо, некоторые разработчики решили, что такой подход быстрее в некоторых особых обстоятельствах, и эта вера в настоящее время распространилась на все сообщество. Мы проверили это предположение на наиболее популярных СУБД и не нашли никаких различий. Нам также не удалось обнаружить источник реальных цифр, указывающих на разницу в производительности. Также, не существует никаких причин, почему СУБД должны реализовывать такое поведение неоптимально – то есть, почему она не должна использовать быстрый внутренний алгоритм очистки транзакций. Всегда коммитьте вашу транзакцию и откатываете, если её не удалось совершить.
Критически важно закрывать Session в финальном блоке, с тем, чтобы обеспечить освобождение JDBC соединения и возвращения его в пул соединений (этот шаг является обязанностью самого приложения, даже в управляемой среде).
ПРИМЕЧАНИЕ
  
Пример в листинге 5.1 является стандартным для единицы работы Hibernate; поэтому, он включает в себя код проверки обработки исключений, для проверки HibernateException. Как вы можете видеть, даже откат Transaction и закрытие Session могут вызвать исключение. Вы не захотите, использовать этот пример в качестве шаблона в собственном приложении, так как лучше спрятать обработку ошибок с общим инфраструктурным кодом. Можно, например, использовать утилитарный класс для конвертации HibernateException в непроверяемое исключение времени выполнения, и скрывать детали отката транзакции и закрытия сессии. Мы обсудим этот вопрос о применении дизайна более подробно в главе 8, разделе 8.1, «Проектирование многослойных приложений».
Однако, есть один важный аспект, который вы должны знать: Session должна быть немедленно закрыта и отбрасываться (не использована повторно), когда происходит исключение.  Hibernate не может повторить неудавшуюся транзакцию. На практике, это не является проблемой, так как исключения БД, как правильно фатальны (например, нарушение ограничений) и нет четко определенного поведения для продолжения после неудавшейся транзакции. Приложение в режиме реальной работы(production) не должно бросать любых исключений БД.
Мы отметили, что вызов commit() синхронизирует состояние Session c БД. Это называется очистка (промывка, flushing)  процесса, которое вы автоматически инициируете, когда используете интерфейс Hibernate Transaction.

вторник, 19 января 2010 г.

Простой стеммер русских слов на Java

Возможно, кому-то будет интересно/полезно посмотреть на рабочий(в учебных целях) стеммер русских слов, написанный на Java. Данный стеммер писался для одной лабы по Java в университете(реализовывал примитивный полнотекстовый поиск), но это не значит, что этот стеммер является нерабочим или корявым. Просто его нужно допиливать(как и все созданное в учебных целях и на скорую руку).
Стеммер реализовывался по Snowball'овскому алгоритму http://snowball.tartarus.org/algorithms/russian/stemmer.html. Здесь есть код самого стеммера http://code.google.com/p/khomutetskiy-project/source/browse/#svn/trunk/UNIVER/src/ru/susu/ssearch/stemmer/russian и всяких служебных классов. И если присмотреться к алгоритмическому описанию алгоритма на Lisp-подобном синтаксисе(он у них похоже называется Snowball), то видно, что Lisp'оподобный синтаксис не в пример лаконичнее, но ИМХО не проще в понимании. Пусть каждый сделает выводы сам. Вообще из этого синтаксиса потом генерится Си код, который вообще никак не читаем, но зато быстро работает.
p.s.: http://code.google.com/p/khomutetskiy-project/source/browse/trunk/UNIVER/src/ru/susu/ssearch/test/RussianStemmerTest.java
p.p.s: вообще основное время заняло написание класса-обертки для суффиксов(SuffixContainer) и утилитарного(StemmerUtils). Без них код был бы совсем ужасен.

понедельник, 18 января 2010 г.

Бесплатная электронная библиотека книг

Сайт, на котором можно без регистрации скачать книги - http://vseknigi.org/. Это электронная библиотека книг. Она будет активно пополняться и в ближайшее время может стать даже самой большой в рунете. В основном она, наверное будет посвящена технической литературе, но будет также и художественная литература. Книги добавляются самими пользователями, либо их добавляет поисковый бот, отыскивающий информацию с простор интернета. На данный момент в ней всего лишь 1659 книг, но скоро их количество вырастет примерно до 40000. А в дальнейшем, скорее всего, вырастет в разы. Ну и в дальнейшем проект может вырасти во что-то типа вики, а также с периодической проверкой ссылок на корректность.
На данный момент проект только зародился и ему паре дней отроду.

пятница, 18 декабря 2009 г.

«Почему российские компании катастрофически отстают от западных?»

Зацепила меня статья про менеджмент http://habrahabr.ru/blogs/my_business/78576/. Зацепила тем, что вопросы в ней задаются правильные и неудобные. Надо задавать себе неудобные вопросы. И делать это постоянно.
Все больше убеждаюсь, что самая симпатичная мне компания - это Google. Хоть я и прекрасно понимаю какая это сейчас машина. И этой крупной машине будут свойственны те же изъяны, что и другим крупным машинам. Наверное, самое главное, что пропадает "дух стартапа", появляется задержка в принятии решений, боязнь ошибки. И это правильно, Google уже далеко не стартап. Мне нравится, что Брин пытается объяснить любое решение цифрами, найти какие-то алгоритмы и закономерности. Это правильный подход. Ну и надо быть немного "crazy":). Нравятся мне дерзкие компании.

среда, 16 декабря 2009 г.

Кэширование ресурсов в Eclipse

Проблема: изменил DTD и пытался перепроверить XML по новому DTD, но валидатор отрабатывал по старому DTD.
Решение: зайти в Window -> Preferences -> General -> Network Connections -> Cache и там удалить ресурсы, которые должны быть обновлены. После этого обновить XML документ, нажать кнопку "Reload Dependencies".

Комментарий:
Потратил уйму времени на выяснение того, почему новая версия DTD никак не может загрузиться. Казалось бы, логичная кнопка есть "Reload Dependencies", которая судя по описанию http://help.eclipse.org/help33/index.jsp?topic=/org.eclipse.wst.dtdeditor.doc.user/topics/tdtemplt.html должна перегружать DTD. Но по сути этого не происходило. Я знал, что у Eclipse есть кэш, куда он сохраняет DTD и XSD. Именно ковыряние по этой теме вывело на то, что нужно идти в настройки и там ковырять кэш. Вообще, интересно куда он кэш сохраняет в файловой системе, но сейчас некогда выяснять. "Тупо сделано"... что тут ещё скажешь.

вторник, 15 декабря 2009 г.

Многословная Java

Успеваю забывать то, что придумал пока кодирую. Java очень многословная. Тут дело именно в синтаксисе и всякие библиотечки не помогут. Java дает делать многие фишки, но слишком большой ценой. Муторно бывает по вложенным циклам ходить. Хочется это записывать в стиле современных языков. Надеюсь, что в Java 7 все-таки появятся замыкания. Хоть и синтаксис с ними станет ещё уродливее, но зато возможности вырастут. А вообще пора Scala изучать и не париться на эту тему. Как будет время свободное, то обязательно заюзаю и расскажу.

пятница, 4 декабря 2009 г.

Поставьте уже вашим сотрудникам большие мониторы

http://biz.cnews.ru/news/top/index.shtml?2009/02/20/338544
http://www.happy-pm.com/blog/?p=323

Управление ITшниками

По ссылке ниже можно прочитать перевод интересной статьи про управление ITшниками.
http://www.diggreader.ru/2009/09/13/neglasnyie-istinyi-upravleniya-gikami/

Самое главное в IT команде - это профессиональное уважение к тим-лиду и остальным участникам. Тут, как с врачами: главврачом всегда является врач высшей категории, который обладает реальным опытом и знаниями, а также практикует. Причем данное уважение никак не связано с общим обаянием человека или умением говорить красивые слова. Уважение связано с реальной работой. Айтишник готов работать до потери пульса не ради денег, а ради профессионального интереса, ради более изящного решения.


Ум айтишников нацелен на решение разнообразных задач. Но зачастую отсутствуют знания по элементарным основам бизнеса и управления. Если же айтишник устраняет недостаток этих знаний, то из него может получиться хороший менеджер.