TL;DR;

Мы хотели реализовать пагинацию, и для этого нам пришлось форкнуть диалект для Hibernate.

В тот день ничего не предвещало еды беды увлекательных приключений. Всё началось с сообщения в файлообменнике Skype.

“Мы будем делать новый проект!” - говорили они. “Там всё просто.” - говорили они.

Наши приключения начались в тот момент, когда мы узнали, что у заказчика есть определенный стек, в который требуется вписать новый проект. Ядро стека составляли известные слова на букву “ха”: Hadoop, HBase, Hive. Добавьте сюда немного Kafka и так любимых в мире enterprise “гибких и готовых к использованию” интеграционных решений.

Начало

Итак, нашей команде предстояло реализовать проект на технологиях, которые мы впервые видим, но, ничего, мы же эксперты. Нам нужно было изучить две NoSQL СУБД (Hive и HBase) и выбрать наиболее подходящее. В итоге остановились на HBase + Apache Phoenix. Почему:

  1. Hive оказался медленным для наших задач

  2. Hive поддерживал транзакционность очень слабо

  3. HBase быстр, но пользоваться им напрямую нам показалось неудобным

  4. Apache Phoenix добавлял SQL интерфейс к NoSQL (HBase)

  5. Apache Phoenix поддерживал транзакционность

Расплата

Самое основное:

  1. Apache Phoenix не поддерживает создание view на несколько таблиц ?_?

  2. Нет ограничений по уникальности (кроме первичного ключа)

  3. Внешние ключи тоже не поддерживает ?\_(?)_/?

При чем тут Hibernate

Мы решили пойти легким (как нам тогда казалось) путем и использовать стандартный подход для работы с базой данных: Spring Data / Hibernate диалект для СУБД. Найти диалект для Apache Phoenix оказалось подозрительно легко. Jorge Ruesga разработал диалект и опубликовал его на Github. Мы взяли диалект и пошли фигачить CRUD запросы.

Работа на полчаса

Сначала всё шло хорошо. А хорошо - потому что мы брали по одной сущности или сразу все сущности из таблицы. Сюрприз поджидал нас в один из пятничных вечеров. Ну знаете, в такие вечера, в которые ты уже более менее расслабился и думаешь, что скоро спокойно пойдешь домой, потому что тут работы на полчаса.

Всё началось с того, что мы захотели доставать не все данные из таблицы, а часть. Короче говоря, реализовать стандартную пагинацию.

“Ну что может быть сложного?” - спросите вы. Вот и мы так думали. Просто берешь и делаешь что-то типа такого:

public interface UserRepository extends CrudRepository<User, Integer> {
    Page<User> find(Pageable pageable);
}

И всё должно работать. Ну ведь да? Все мы знаем, что под капотом этого метода формируется запрос с OFFSET и LIMIT. Но что, если я вам скажу, что в итоговом запросе OFFSET и LIMIT перепутаны местами?

True изменения

Как и все другие интересные истории про задачи “на полчаса” наша задача казалась нам какой-то мелочью, которую можно поправить “на раз-два взяли”. Скажу лишь, что в итоге мы потратили на нее почти пять часов, прежде чем до нас дошло, что что-то идет не так.

Оказалось, что чудесный Hibernate использует специальный класс при формировании конструкции с OFFSET и LIMIT: LimitHandler. А наш не менее чудесный диалект реализует этот класс таким образом, что в итоге значения для OFFSET и LIMIT оказываются перепутаны.

В итоге, чтобы это поправить нам пришлось форкнуть этот диалект, дописав в него всего несколько строк:

@Override
public boolean bindLimitParametersInReverseOrder() {
	return true;
}

Итог

Мы хотели реализовать пагинацию и для этого нам пришлось форкнуть диалект для Hibernate.