barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

вторник, 31 июля 2012 г.

Oracle: аналог LIMIT / TOP

Некоторым аналогом функционала LIMIT / TOP по ограничению числа возвращаемых строк является псевдостолбец "rownum", представляющий собой номер строки в результирующей выборке. Таким образом, запрос:
SELECT * FROM abc WHERE rownum<=10
аналогичен запросу:
SELECT * FROM abc LIMIT 10
в MySQL / PostgreSQL, или:
SELECT TOP 10 * FROM abc
в MS SQL.
Правда, у такого подхода есть важная особенность: ограничение выборки производится раньше, чем сортировка с помощью ORDER BY, в отличие от конструкции LIMIT в MySQL / PostgreSQL. Т.е.:
SELECT * FROM abc WHERE rownum<=10 ORDER BY a
вовсе не аналогичен вызову
SELECT * FROM abc ORDER BY a LIMIT 10
в других СУБД, т.к. оракл проведет сначала выборку первых 10 значений, а потом уже отсортирует их по столбцу "a". Чтобы ограничение выборки выполнялось уже после сортировки, необходимо использовать вложенный запрос:
SELECT * FROM (SELECT * FROM abc ORDER BY a) WHERE rownum<=10
Альтернативой такому, мягко говоря, несимпатичному запросу может быть использование функции ROW_NUMBER.

среда, 25 июля 2012 г.

Плагин jquery.validation и элементы формы с одинаковыми именами

Плагин  jquery.validation не умеет работать с элементами с одинаковыми именами. Использование одинаковых имен ведет, во-первых, к тому, что валидируется только первый элемент, ну и, во-вторых, ко всяким глюкам с неверным позиционированием сообщения об ошибке и пр. Всякие костыли (например, http://www.codeboss.in/web-funda/2009/05/27/jquery-validation-for-array-of-input-elements/) работают лишь частично (например, элемент начинает вроде бы валидироваться, но сообщение об ошибке все равно у всех одноименных элементов одно на всех). Поправить эту ситуацию изменением пары строк в исходниках не получится - слишком многое в них завязано на имена элементов. Поэтому остается лишь ходить обходными путями, например, назначать элементам уникальные имена и преобразовывать их при отправке провалидированной формы в обработчике, назначенном параметром "submitHandler" в вызове validate().

Hibernate: обновление идентификатора persistent-объекта

Проблема:

При попытке выполнить сохранение (session.update()) persistent-объекта после изменения поля, являющегося идентификатором в hibernate-маппинге, валится исключение:
org.hibernate.HibernateException: identifier of an instance of ... was altered from ... to ...
Решение:

Обновлять идентификаторы у persistent-объекта нельзя (вероятно, это связано со спецификой генерации Hibernate`ом SQL-запросов на обновление). Если сделать для объекта session.evict(), а потом - session.save(), то добавится новая запись в БД, с новым идентификатором. Это тоже не то, что мы хотели. Остается другой вариант - выполнить session.delete(), а потом - session.save(). Тогда мы получим желаемое - идентификатор "обновится" в БД (точнее, сначала запись удалится, а потом - вставится с новыми идентификатором).

вторник, 24 июля 2012 г.

Hibernate: SQLQuery и двоеточия в тексте запроса

Задача следующая: выполнить с помощью Hibernate некоторый SQL-запрос к БД PostgreSQL (используя session.createSQLQuery()), использующий приведение типов с помощью "::", например:
SELECT SETVAL('mysequence'::regclass,11111)
Проблема заключается в том, что Hibernate использует двоеточие для реализации именованных параметров запроса, поэтому "::regclass" воспринимается им как параметр с именем ":regclass", что в итоге приводит к ошибке при выполнении запроса, т.к. значение для этого параметра не задано:
...
Caused by: org.hibernate.exception.DataException: Не указано значение для параметра 1.
...
Caused by: org.postgresql.util.PSQLException: Не указано значение для параметра 1.
Никаких способов экранирования двоеточия, насколько я понял, нет (соответствующий тикет висит аж с 2005 года и версии 3.1: https://hibernate.onjira.com/browse/HHH-1237, - тогда как я проверяю на версии 4.1). Хорошо хоть, что в PostgreSQL есть и другой способ приведения типов - с помощью конструкции CAST:
SELECT SETVAL(CAST('mysequence' AS regclass),11111)
Но для других БД, где в SQL применяется двоеточие для такой же или другой цели, таких замен может и не быть. 

понедельник, 23 июля 2012 г.

Получение thumbprint`а для X509Certificate в Java

Класс java.security.cert.X509Certificate не предоставляет соответствующего метода, поэтому приходится делать это вручную. Можно непосредственно, используя MessageDigest (http://stackoverflow.com/questions/1270703/how-to-retrieve-compute-an-x509-certificates-thumbprint-in-java), а можно воспользоваться классом DigestUtils из библиотеки commons-codec:
public static String getHexThumbprint(X509Certificate cert) throws CertificateEncodingException
{
return DigestUtils.shaHex(cert.getEncoded());  
}
public static byte[] getThumbprint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException
{
return DigestUtils.sha(cert.getEncoded());
}


пятница, 20 июля 2012 г.

Замена анонимным блокам PostgreSQL 9+ в более ранних версиях

Такая вещь, как анонимные блоки, появилась только в девятой версии PostgreSQL, в более ранних версиях нечто подобное можно делать, создавая и затем вызывая неанонимные функции / процедуры в схеме pg_temp, которые будут удалены по окончании сессии:
create function pg_temp.f(...) returns ... as $$
begin
   ...
end
$$ language plpgsql;
pg_temp.f(...);
pg_temp.f(...);

четверг, 19 июля 2012 г.

ScheduledThreadPoolExecutor и setMaximumPoolSize

java.util.concurrent.ScheduledThreadPoolExecutor использует фиксированный пул потоков, определяемый свойством corePoooSize. Вызов scheduler.setMaximumPoolSize() ни к чему не приводит, и если число задач больше чем corePoolSize, они стоят в очереди в ожидании свободного потока, даже если время их выполнения подошло.
НО никто не мешает вызовом setCorePoolSize() в любой момент жизни ScheduledThreadPoolExecutor`а увеличить или уменьшить этот размер пула. Увеличение размера приводит к моментальному созданию нового потока (если очередь scheduler.getQueue() непуста, т.е. не все задачи разобраны свободными потоками), а вот с уменьшением не всё так просто. 
Дело в том, что класс ThreadPoolExecutor.Worker устроен таким образом, что даже если число потоков в пуле превышает установленный corePoolSize, освободившийся поток все равно попытается взять из очереди задач очередную задачу, и завершится только если задачи там нет и ждать её ему пришлось больше, чем промежуток времени, устанавливаемый вызовом setKeepAliveTime():
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
Специфика периодических задач  ScheduledThreadPoolExecutor`а такова, что если число задач больше corePoolSize, в очереди всегда есть задачи, поэтому Worker всегда получит задачу вызовом workQueue.poll и завершаться не станет, даже если размер пула превышает заданный corePoolSize(). 
Таким образом, динамическое уменьшение corePoolSize возымеет эффект только когда число потоков в пуле станет меньше числа запланированных задач.
Ниже приведен небольшой график, полученный экспериментально (между изменениями размера corePoolSize и проверкой реального размера пула я ждал промежуток времени, достаточный, чтобы все задачи по разу отработали):



Виджет jQuery UI autocomplete и поиск по value, а не по label

Если элементы для автодополнения являются объектами и содержат и поле value, и поле label, то при фильтрации элементов для автодополнения виджет autocomplete использует label. Такой вариант недопустим если, например, label был специально обрезан, чтобы слишком не растягивать список подсказок. В данном случае фильтровать элементы нужно по value. 
Чтобы исправить ситуацию, нужно переопределить функцию jQuery.ui.autocomplete.filter:
jQuery.ui.autocomplete.filter = function(array, term) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
return $.grep( array, function(value) {
return matcher.test(value.value || value.label || value );
});
(в оригинале value.value и value.label идут в обратном порядке)

Повторный вызов HttpServletResponse.sendRedirect() в веб-приложении на Tomcat 6

Задача:

Попытка повторного вызова HttpServletReponse.sendRedirect() в веб-приложении, развернутом на Tomcat 6, приводит к исключению:
java.lang.IllegalStateException: org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
Необходимо реализовать возможность вызывать  sendRedirect() несколько раз, так, чтобы реальный редирект осуществлялся по URL`у, указанному в последнем вызове.

Решение:

Catalina не позволяет вызывать sendRedirect() повторно. Всё же, порой это бывает необходимо сделать. Например, пусть есть некоторый фильтр, который, после того, как запрос пройдет обработку во всех нижлежащих фильтрах, по какому-либо условию выполняет response.sendRedirect():
chain.doFilter(request, response);
if(...)
     ((HttpServletResponse)response).sendRedirect(...)
Однако, если в каком-либо из нижлежащих фильтров или в конечном сервлете, обрабатывающем запрос, также вызывается response.sendRedirect(), повторный его вызов в нашем фильтре вызовет вышеуказанное исключение, в то врем как редирект в фильтре всё же  делать надо, причем так, чтобы он переопределял URL, указанный в предыдущих вызовах sendRedirect().
Вариант, который пришел мне на ум и был реализован - перед передачей дальше по цепочке завернуть передаваемый фильтру response в некоторый объект-делегатор, который будет делегировать все вызовы оригинальному объекту ответа, кроме вызовов sendRedirect. Последний будет просто запоминать переданный ему URL, а реальный редирект будет выполняться только по вызову нового метода applyRedirect:
public class DeferredRedirectHttpServletResponse implements HttpServletResponse
{
  protected HttpServletResponse responseDelegate;

  protected String deferredRedirectURL = null;

  public DeferredRedirectHttpServletResponse(HttpServletResponse responseDelegate)
  {
    this.responseDelegate = responseDelegate;
  }
  @Override
  public void sendRedirect(String string) throws IOException {
    deferredRedirectURL = string;
  }
  public void applyRedirect() throws IOException
  {
    if(this.deferredRedirectURL != null)
      responseDelegate.sendRedirect(this.deferredRedirectURL);  
  }
...
/* здесь идут все методы из интерфейса HttpServletResponse, вызов которых делегируется объекту responseDelegate*/
...
}
Теперь код фильтра примет вид:
DeferredRedirectHttpServletResponse responseDelegator = new DeferredRedirectHttpServletResponse((HttpServletResponse)response);
chain.doFilter(request, responseDelegator);
if(...)
     responseDelegator.sendRedirect(...);
responseDelegator.applyRedirect();
Всё, теперь приложение работает корректно при всех условиях: и когда фильтр делает редирект (даже если он уже был сделан в сервлете / нижлежащем фильтре), и когда он его не делает.

UPDATE. Пожалуй, удобнее было бы наследовать HttpServletResponseWrapper, чем реализовывать HttpServletResponse, кода было бы меньше. Но на момент реализации моего DeferredRedirectHttpServletResponse я про HttpServletResponseWrapper не знал. Наткнулся на него, лазя по исходникам Spring Security, в частности, глядя на класс org.springframework.security.web.firewall.FirewalledResponse.
UPDATE 2. Кстати, если конечный обработчик запроса - jsp-страница, то тут есть одна особенность. Если без использования DeferredRedirectHttpServletResponse вызвать на jsp-хе response.sendRedirect() без следующего за ним return`а прокатывает (ответ просто игнорирует последующую запись в него jsp-страницей), то теперь валится тот же IllegalStateException, на этот раз уже из-за того, что отправка ответа пользователю на момент вызова applyRedirect() уже началась (с jsp-хи вовремя не return`ались, поэтому она уже успела записать в response свой вывод). В общем-то, вышесказанное относится не только к jsp-хам, а к любому коду, где в response идет запись после вызова sendRedirect() (просто на jsp эта самая запись не очевидна).

jQuery UI - виджет autocomplete и z-index

Виджет jQuery UI autocomplete для вычисления z-index`а выпадающего списка подсказок использует "реальный" z-index input`а, к которому он привязан, прибавляя к нему 1. Этот "реальный" z-index вычисляется следующим образом: сначала проверяется, установлено ли свойство z-index у самого input`а. Если да - берется оно, если нет - осуществляется обход всех родителей input`а и поиск z-index`а у них. Если z-index так и не будет найден, он принимается равным 0. Что важно: помимо проверки у элементов наличия свойства "z-index" проверяется также, что css-свойство "position" установлено и равно одному из значений: "absolute", "fixed", "relative". Если свойство "position" отсутствует или имеет другое значение, считается, что "z-index" у элемента не установлен (т.к. он, действительно, при этом не влияет на отображение элемента и игнорируется браузером). 
Пришлось со всем этим разбираться вот по какой причине: при использовании autocomplete-input`а внутри YUI2 Panel этот z-index вычисляется неверно, т.к. панель размещается внутри iframe`а, у которого z-index равен 3, однако при поиске "реального" z-index`а jQuery UI вначале находит div с классом "yui-panel", для которого z-index равен 1, и в итоге выбирает для виджета z-index равным 2, помещая его под iframe:


Побороть эту проблему можно, установив явно z-index и position для input`а, к которому прикреплен виджет.

вторник, 17 июля 2012 г.

PostgreSQL: запуск хранимки с правами владельца

Чтобы хранимая функция / процедура запускалась с правами владельца, а не вызывающего её пользователя, предназначена опция SECURITY DEFINER (http://www.postgresql.org/docs/9.1/static/sql-createfunction.html).

org.apache.commons.logging.LogConfigurationException: User-specified log class 'org.apache.commons.logging.impl.Log4JLogger' cannot be found or is not useable.

Часа полтора ушло на казалось бы простейшую проблему: использовать apache commons logging в связке с log4j. Проблема была в следующем: в веб-приложении, разворачиваемом на Tomcat 6, имеются файлы конфигурации commons-logging и log4j со следующим содержимым:

commons-logging.properties:
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
log4j.properties:
log4j.debug=false
log4j.rootLogger=DEBUG, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=%-4r [%t] %-5p %c %x - %m%n 
Первый конфигурационный файл должен подхватываться commons-logging`ом самостоятельно как первый с таким именем в CLASSPATH, путь ко второму указывается явно в web.xml. Сами jar-ники commons-logging-1.1.1.jar и log4j-1.2.15.jar в /WEB-INF/lib приложения лежат. Однако при попытке чтобы то ни было залогировать в веб-приложении валится исключение:
org.apache.commons.logging.LogConfigurationException: User-specified log class 'org.apache.commons.logging.impl.Log4JLogger' cannot be found or is not useable.
Как оказалось, проблема была в следующем: commons-logging-1.1.1.jar лежал также и в %JAVA_HOME%/jre/lib/ext (так уж получилось, что он залетел туда при установке КриптоПРО JCP). Поэтому и загружался этот jar-ник не тем класслоадером, что и остальные либы веб-приложения, и jar-ника log4j в /WEB-INF/lib ему было не видно. Выход - либо закинуть jar-ник log4j туда же в %JAVA_HOME%/jre/lib/ext, либо - удалить оттуда commons-logging-1.1.1.jar (в моем случае - не вариант, т.к. нарушило бы работу JCP).

Изменения в интерфейсе org.hibernate.usertype.UserType при переходе с Hibernate 3 на Hibernate 4

При переходе с третьего Hibernate`а на четвертый изменился интерфейс  org.hibernate.usertype.UserType: в методах nullSafeGet / nullSafeSet появился параметр SessionImplementor session. Соответственно, пользовательские типы, реализующие UserType (или его подынтерфейс EnhancedUserType), перестали компилироваться из-за ошибки:
<xxx> is not abstract and does not override abstract method nullSafeSet(java.sql.PreparedStatement,java.lang.Object,int,org.hibernate.engine.spi.SessionImplementor) in org.hibernate.usertype.UserType

java.net.SocketException: Connection reset при парсинге Hibernate-конфига

После некоторых манипуляций с проектом, использующим hibernate, стало вылиться исключение при чтении конфигурационного файла Hibernate:
org.hibernate.HibernateException: Could not parse configuration: /hibernate.cfg.xml
...
Caused by: org.dom4j.DocumentException: Connection reset Nested exception: Connection reset
...
java.net.SocketException: Connection reset
Какой коннект был сброшен, не совсем ясно О_о, ведь парсится локальный конфиг. Оказывается, в CLASSPATH оказались jar-ники 3ей версии Hibernate, тогда как приложение использует Hibernate 4 и в конфигурационных файлах указаны его DTD. Не найдя новых DTD в jar-никах старого Hibernate, XML-парсер полез за ними в интернет (куда его не пустили уже из-за прокси).

пятница, 13 июля 2012 г.

Сделать пользователя локальным администратором через TeamViewer

Задача:

Если доступ через Teamviewer к ПК из-под пользователя, не являющегося локальным админом. Известен также пароль локального админа. Нужно сделать локальным админом того ползователя, из-под которого работает Teamviewer.

Решение:

Если Teamviewer запущен не из-под непривилегированного пользователя, при попытке выполнить какую-либо административную задачу (например, сменить тип текущей учетной записи через панель управления) либо сменить пользователя через меню "Пуск" Teamviwer отключается, говоря, что не хватает прав (а на локальном компе вываливается запрос пароля пользователя-админа). Поэтому пришлось сменить пользователя на локального админа через командную строку (аналог su под *nix):
runas /user:localadmin cmd.exe
, где localadmin - учетка локального админа. Команда запросит пароль, после чего откроет cmd-окно уже от имени локального админа. У меня правда при вводе пароля возникла проблема со сменой раскладки - она почему-то ни в какую не хотела меняться на русскую в окне cmd, поэтому пришлось на своем компе ввести пароль в блокноте, копировать его, переключиться на Teamviewer и вставить его в окно cmd в ответ на запрос пароля.
Имея cmd с правами локального админа можно открыть оснастку локальных пользователей и групп lusrmgr.msc, и там в свойствах пользвователя добавить его в группу "Администраторы". 

среда, 11 июля 2012 г.

Различие в поведении метода shutdown() в java.util.concurrent.ScheduledThreadPoolExecutor и org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

Метод shutdown(), вызванный для ScheduledThreadPoolExecutor, дает всем ранее опубликованным задачам завершиться, в то время как ThreadPoolTaskScheduler.shutdown() прерывает все уже запущенные задачи. Чтобы ThreadPoolTaskScheduler вел себя также, как и JDK`ная реализация ScheduledExecutorService, нужно выполнить scheduler.setWaitForTasksToCompleteOnShutdown(true) перед вызовом scheduler.shutdown().

вторник, 10 июля 2012 г.

java.util.TreeSet и использование объектов с не согласующимися реализациями compareTo() и equals()

Если объект имеет не согласующиеся реализации compareTo() и equals(), т.е. если первая может вернуть 0 для объектов, для которых equals вернет false, использовать такие объекты в TreeSet нельзя (т.е. вобщем-то можно, но чревато неожиданным поведением). Дело в том, что TreeSet, на что обращено внимание в документации, для определения эквивалентности объектов при добавлении использует не equals(), а compareTo(), поэтому в набор нельзя будет добавить 2 объекта, которые неидентичны с точки зрения equals(), но возвращают 0 при сравнении с помощью compareTo().

пятница, 6 июля 2012 г.

Hibernate Criteria API: баг при одновременном использовании criteria.createAlias и criteria.setProjection(Projections.rowCount())

Мда, багов в Hibernate хватает. Наткнулся на ещё одну неприятность (это правда возможно и не совсем баг, просто немного неожиданное поведение) при попытке посчитать число строк с помощью Criteria, использующего псевдонимы (заданные с помощью criteria.createAlias). Например, пусть есть 3 таблицы: t1, t2 и t3. Соответствующие им POJO-классы пусть называются также. Связаны они связями много-к-одному через внешние ключи: t1.t2fk = t2.id, t2.t3fk = t3.id. Пусть есть некоторая Criteria, которая выбирает объекты t1, исходя из условия, накладываемого на связанные с ними объект t3. Чтобы в Criteria задать ограничение на свойство объекта t3, для соответствующей ассоциации с помощью criteria.createAlias создан псевдоним:
criteria.createAlias("t2fk.t3fk","t3alias")
Без проекции она генерирует SQL с двумя JOIN`ами:
select ... from t1 inner join t2 on ... inner join t3 t3alias_ on ... where t3alias_.id=?
При добавлении проекции Projections.rowCount() можно ожидать запрос вроде:
select count(*) from t1 inner join t2 on ... inner join t3 t3alias_ on ... where  t3alias_ .id=?
Однако, Hibernate поступает более кардинально, убирая все объединения из запроса:
select count(*) from t1 where t3alias_ .id=?
Естественно, запрос становится невиладным, причем Hibernate сам это понимает ещё до его выполнения:
ERROR: missing FROM-clause entry for table "t3alias_"
Решается проблема следующим образом: при использовании проекции необходимо задавать псевдоним не только для конечной таблицы t3, по которой выполняется отбор строк, но и для промежуточной таблицы t2 (а в общем случае - для всех таблиц, лежащих на пути между корневой таблицей и конечной таблицей, которая используется в where):
criteria.createAlias("t2fk","t2alias")
criteria.createAlias("t2alias.t3fk","t3alias");
PS Спасибо за подсказку http://facingtech.blogspot.com/2010/09/applying-projection-to-hibernate.html. Видимо, данное поведение разработчики Hibernate считают корректным и исправлять не собираются, т.к. соответствующий тикет висит ещё с версии 3.20: https://hibernate.onjira.com/browse/HHH-5719.

Hibernate: ошибка "Criteria objects cannot be created directly on components. Create a criteria on owning entity and use a dotted property to access component property"

Ошибка "Criteria objects cannot be created directly on components.  Create a criteria on owning entity and use a dotted property to access component property" возникает при попытке создания дочернего элемента Criteria для поля, представляющего не ассоциацию, а составной ключ:
Criteria childCriteria = criteria.createCriteria("id");
, где поле id класса, которому соответствует объект criteria, ссылается на объект составного ключа. 
В случае составных ключей, нет необходимости создания дочерних объектов Criteria, накладывать условия на поля составного ключа или выполнять по ним сортировку можно просто используя точку:
criteria.addOrder(Order.asc("id.timestamp"));
criteria.add(Restrictions.gt("id.timestamp",new Date()));
Кстати, создавать псевдонимы через criteria.createAlias для поля составного ключа тоже нельзя, т.к. createAlias предполагает выполнения JOIN`а в БД, тогда как составной ключ не требует никаких объединений т.к. является просто группировкой полей в отдельный объект.

четверг, 5 июля 2012 г.

Hibernate: использование нулевых идентификаторов

По-умолчанию Hibernate не позволяет использовать значения "0" в качестве числовых идентификаторов persistent-объектов, т.к. он трактует объект с идентификаторомв "0" как transistent. Таким образом, при попытке сохранить объект, ссылающийся на persistent-объект с нулевым идентификатором, валится неожиданное исключение:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing ...
Чтобы исправить данную ситуацию, нужно в маппинге класса задать ненулевое "unsaved-value" для идентификатора, например, "-1":
<id name="id" type="long" unsaved-value="-1">
  <column name="id"/>
  <generator class="increment"/>
</id>

вторник, 3 июля 2012 г.

Hibernate: fetch="subselect" и запросы с limit / order by

Столкнулся с багом Hibernate, который, похоже, живет уже давно и исправлять его не очень-то спешат: https://hibernate.onjira.com/browse/HHH-2666. Заключается он в том, что если для извлечения связанных объектов используется стратегия "subselect":

<set inverse="true" name="..." fetch="subselect">
  <key>
<column name="..." not-null="true"/>
  </key>
  <one-to-many class="..."/>
</set>
,то при извлечении родительских объектов с использованием limit / order by второй запрос, используемый для извлечения связанных объектов, в подзапросе уже не содержит limit / order by, что приводит к извлечению потенциально гораздо большего числа строк, большая часть которых может оказаться ненужной (здесь первый select выполняется hiberante`ом сразу же, второй - лениво, при обращении к коллекции связанных объектов):

Hibernate: select ... from ... where ... order by ... asc limit ?
Hibernate: select ... from ... where ... in (select ... from ... where ... )
Очень неприятно, т.к. стратегия, вобщем-то, достаточно удобная.