LINUX.ORG.RU

Java и ORM. Окей, просто ORM.

 , , ,


0

4

Доброго времени. Боли и недоумевания тред.

На проекте на работе (5-й год разрабатываем говн интернет банк для очень большого и известного, но не зеленого банка). Для доступа к БД используем Apache Cayenne. Того же поля ягода, что и MyBatis. Сначала я не понимал концепта, но теперь почти счастлив. Оно умеет в native SQL через шаблонизатор перед исполнением. Няшка. Но черт побери, я хочу поддержку управления транзакциями на тех же человеческих аннотациях, JTA хочу, а такой возможности нет.
Понадобилось написать очередной эмулятор очередной системы, с которой интегрируемся - решил попробовать хибернейт, и обомлел. Оно генерит километровые left join'ы. Перед insert'ом связываю наполняемую сущность через reference с ещё несколькими сущностями, а перед тем, как заперсиститься, он делает N селектов, где N - количество связанных сущностей, чтобы проверить, что связи верны. Судя по логам, селектит таки все поля, что не лениво инициализируются. И слава б-гу у этих сущностей все тяжелые поля LAZY, а если бы нет? Почему просто id не заселектить? Почему не попытаться просто в БД положить, ну пусть уже она ругается. OpenJPA - вовсе обрубок по ощущуениям, после хибернейта. Подозреваю, что TopLink и EclipseLink трогать и вовсе смысла нет. JDBC? Слишком низкий уровень, динамически запросы не повыстраивать, а очень хотелось бы. Ну не строки же конкатенировать, да ещё билдеры под это писать. JdbcTemplate спринговый таки манит удобным управлением транзакциями, но таки jdbc остается jdbc, никакой динамики в селектах. Вот, разве что, ещё на JOOQ стоит посмотреть (оно ещё шевелится?).

Хочу поинтересоваться у местных яверов - какие ORM используете вы (на работе/в собственных проектах)? Не используете ORM? Почему?

// P.S.: Уютненький, вот, на спринге с JdbcTemplate писан. Выглядит очень лаконично, работает быстро. Но таки будь выборки позабористее, не факт, что был бы jdbc, думается.

Upd
Читал в мануале на сайте хибернейта про паттерны работы с тразнакциями, но мне интересно вот что. В рамках приложения, как далеко из DAO уходят Entity? На выходе из DAO слоя по вашему мнению должны возвращаться именно Entity? По-моему логично возвращать их, чтобы в неком «сервисном слое» можно было пробрасывать транзакции без лишних аттачей/деттачей. Один сеньор со своим чувством прекрасного склонен считать, что на выходе из DAO сразу стоит конвертить Entity в dto. Получается 100500 конвертеров, что по моему мнению сран ад какой-то. И библиотеки для ремаппинга тут не очень-то спасают, ибо всё-равно ад и тормоза.

Для большинства задач Hibernate подходит. Но его надо уметь готовить. Без опыта получается беда. А в целом нормально. В последнем проекте, например, около 90% запросов в базу не идут, т.к. они уже закешированы в кеше второго уровня. И это практически всё даром, буквально парой строчек. Руками все эти кеши менеджить было бы грустно.

Иногда JDBC. Или задача маленькая, или не ложится на хибернейт, или времени вагон и можно себе позволить писать на каждый запрос кастомный SQL.

mybatis юзал. Штука неплохая, но не понравилась. Была бы куча времени и соответствующий проект, написал бы свою видение псевдо-ORM.

Legioner ★★★★★ ()

какие ORM используете вы

ORM не используем, (пользуемся Spring JDBC).

Не используете ORM? Почему?

В нашем случае больше проблем, чем пользы (при сложных выборках ORM только мешает и отрицательно сказывается на производительности).

barti_ddu ()

Присоединяюсь к Legioner. Хибернейт довольно не тривиальная в настройке вещь, а в случае неправильно настройки может стать горлышком бутылки. Использовал eclipseLink. Kто-то даже говорил, что оно якобы быстрее работает. ИМХО никаких особых достоинств по сравнению с хибернейтом не видно, только с документацией значительно хуже.

myBatis несколько другой фреймворк. Отлично подходит, когда нужно исполнять хранимые процедуры/какой-то сложный SQL. Естественно работает быстрее jpa, но и кода надо писать больше.

dycore ()

Hibernate + HQL + JDBC

базу надо делать не как попало, а с расчетом на использование хибернейта. Если база уже кривая косая, то ой.

stevejobs ★★★★☆ ()

Чем именно плох JDBC (с spring jdbc template)?

А всякие JPA я тоже на самом деле не осилил. Использовать могу, в коде разобраться могу - но «преимуществ» так и не прочувствовал.

BattleCoder ★★★★★ ()

Почему просто id не заселектить? Почему не попытаться просто в БД положить, ну пусть уже она ругается.

я писал свой ORM, потому что мне не была нужна шизанутая JPA + надо было уметь умный маппинг, а не прибитый гвоздями, получилось:

- на любой результат запроса можно примапить любой объект (даже если имена колонок не равны именам свойств)

- результат запроса можно примапить в двумерный массив с отдельным map для метаинформации колонок - это нужно для табличной гуйни и отчетов

- можно селектить id потому что объект ссылается на другой так:

class One {
   Ref<Two> twoId;
}

interface Ref<T> {
   String getId();//на деле тип можно любой
   T getValue()
}

таким образом если мне нужно из ссылки вытащить id то лишнего не селектится, если всегда надо свойство то это можно указать хинтами

Deleted ()
Ответ на: комментарий от Legioner

А в целом нормально. В последнем проекте, например, около 90% запросов в базу не идут, т.к. они уже закешированы в кеше второго уровня.

если юзер строит отчет по мульену записей со ссылками, то покупаем сервер с 100GiB оперативки?

Deleted ()
Ответ на: комментарий от BattleCoder

ну у меня изнчально проект был динамический и запросы генерировались на лету в runtime

делал подобный проект генерируя запросы через criteriaapi и сдела вывод что это не для генерации, а для перфекционистов которые хотят linq но не могут в этом признаться.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)

Но черт побери, я хочу поддержку управления транзакциями на тех же человеческих аннотациях, JTA хочу, а такой возможности нет.

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

maloi ★★★★★ ()

Оно генерит километровые left join'ы. Перед insert'ом связываю наполняемую сущность через reference с ещё несколькими сущностями, а перед тем, как заперсиститься, он делает N селектов, где N - количество связанных сущностей, чтобы проверить, что связи верны. Судя по логам, селектит таки все поля, что не лениво инициализируются. И слава б-гу у этих сущностей все тяжелые поля LAZY, а если бы нет? Почему просто id не заселектить?

Насколько я слышал openjpa и eclipseLink отличаются от хибернейта в первую очередь тем что там такого бреда нет. Сам ни тем ни другим ни третьим серьезно не пользовался.

maloi ★★★★★ ()

Посмотри ORMLite. Есть аннотации, не генерит километровые джоины. Работа очень прозрачна - как ты думаешь, что он работает, так на самом деле и оказывается.

С транзакциями пока не очень, но проект несложный и открытый - код понятен, как на ладони. Можно дописать и оформить пуллреквестом, автор очень охотно их принимает.

Adonai ★★★ ()
Ответ на: комментарий от Deleted

таким образом если мне нужно из ссылки вытащить id то лишнего не селектится, если всегда надо свойство то это можно указать хинтами

Такое и в хибернейте можно.

Legioner ★★★★★ ()
Ответ на: комментарий от Deleted

если юзер строит отчет по мульену записей со ссылками, то покупаем сервер с 100GiB оперативки?

Все ссылки lazy, будет грузиться только то, я скажу. Запросы в HQL примерно похожи на SQL. Если прям совсем страшный отчёт, напишу на JDBC, не обломаюсь. Просто отчётов, грубо говоря, 10 на всё приложение, а остального круда тысяча.

Legioner ★★★★★ ()
Ответ на: комментарий от Deleted

Да. Хибернейт заполняет значение прокси-объектом, который знает про свой id. Если дёрнешь любой геттер кроме getId, уйдёт запрос в базу. А если getId, то всё нормально. Но для этого надо, чтобы маппинг был написано на геттере (или access(property) стояло).

Legioner ★★★★★ ()
Ответ на: комментарий от Deleted

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

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Хибернейт заполняет значение прокси-объектом

Ага т.е. оно еще и с байткодом костыляет

Deleted ()

Оно генерит километровые left join'ы

Они длинные ввиде текста или по сути своей сложные запросы? Если текст, то СУБД пофиг, она prepared statement один раз парсит.

Если нужно вытягивать дочерные обьекты сразу, то fetch join

vertexua ★★★★★ ()

JOOQ в прямых руках - сказка. Подходит как для новой разработки, так и для человеческого интерфейса с тоннами pl/sql enterprise говнокода с тоннами вложенных типов. Ну а спринговые транзакции поверх него накладываются элементарно.

LestorN ()
Ответ на: комментарий от Legioner

Интересное замечание, не знал. Но это же не справедливо для кейса с селектом перед персистом, если в сохраняемом объекте есть ссылка на сущность из БД? Или таки снова спасет только кэш?

Кстати, используете EhCache?

bytecode ★★ ()
Ответ на: комментарий от vertexua

Скорее текстом. Но вот какой момент: есть два связанных с бизнесовой точки зрения документа, у обоих есть ссылки на валюты и ещё некоторые справочные данные. При селекте документа, у которого связанный в EAGER, само собой, на справочные поля каждого документа сгенерится свой отдельный left join. А вводные данные таковы, что у обоих документов валюты всегда одинаковые.

Да, какой-то из пальца высосанный кейс, но есть такой. Вроде можно же держать валюту только в одном документе, но нет, второй тоже можно выбирать самостоятельно без ссылки на первый.

bytecode ★★ ()
Ответ на: комментарий от bytecode

Если я правильно понял, что нужно сделать, то делается так:

Мы делаем LOR. Обработчик сохранения комментария. У комментария есть автор (пользователь).

В нашем обработчике доступен accountId. Вопрос в том — как сохранить комментарий без запроса к таблице account.

1 способ.

Account = new Account();
account.setAccountId(accountId);
Message = new Message();
message.setAuthor(account);
message.setText(text);
entityManager.persist(message);

т.е. просто заполняем пустым объектом с заполненым ID. Не знаю, по стандарту ли это, но в хибернейте работает.

2 способ по стандарту.

Account account = entityManager.getReference(Account.class, accountId);
Message = new Message();
message.setAuthor(account);
message.setText(text);
entityManager.persist(message);

getReference возвращает управляемый объект, но запрос в БД не делает.

В обоих способах message сохранится без дополнительных селектов.

Да, ehcache.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от bytecode

Если у нас есть много документов, а валюта одна и не хочется делать джойны, тут как раз и подойдёт lazy loading. Просто дёргаем документы через lazy. Валюта в первый раз загрузится и ляжет в кеши. В следующие разы при запросе той же валюты она будет браться из кеша.

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Про первый вариант слышал, но ни разу не пробовал. Тоже не уверен, по стандарту ли это, но не суть.

А вот, в чем соль. Я как раз использую второй вариант, при этом селект выполняется. Вы уверены, что его не должно быть, или в вашем случае его не происходит, потому что Account уже висит в кэше?

Да, используется field access, аннотации над полями.

P.S.: Почему всё-таки EntityManager, а не SessionFactory?
P.P.S.: В теме появился апдейт. Раз уж на то пошло, интересно выслушать мнение и касательно его.

bytecode ★★ ()
Ответ на: комментарий от bytecode

А вот, в чем соль. Я как раз использую второй вариант, при этом селект выполняется. Вы уверены, что его не должно быть, или в вашем случае его не происходит, потому что Account уже висит в кэше?

В моих тестах запроса не было. Запрос будет при обращении к какому-нибудь полю, это да.

Да, используется field access, аннотации над полями.

Попробуйте конкретно для id поставить property access. Возможно в этом дело.

P.S.: Почему всё-таки EntityManager, а не SessionFactory?

Потому что стандартный интерфейс и даёт всё, что нужно. Пока по крайней мере необходимости в SessionFactory не возникало. Впрочем какой то большой разницы не вижу. Разве что в JPA есть Type-Safe Criteria Queries, это может склонить в его сторону.

Legioner ★★★★★ ()

Читал в мануале на сайте хибернейта про паттерны работы с тразнакциями, но мне интересно вот что. В рамках приложения, как далеко из DAO уходят Entity? На выходе из DAO слоя по вашему мнению должны возвращаться именно Entity? По-моему логично возвращать их, чтобы в неком «сервисном слое» можно было пробрасывать транзакции без лишних аттачей/деттачей. Один сеньор со своим чувством прекрасного склонен считать, что на выходе из DAO сразу стоит конвертить Entity в dto. Получается 100500 конвертеров, что по моему мнению сран ад какой-то. И библиотеки для ремаппинга тут не очень-то спасают, ибо всё-равно ад и тормоза.

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

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Type-Safe Criteria Queries

Точно, это на ум не пришло.

Но ещё смущает в нем не-checked исключение при использовании getSingleResult, в отличие от hibernate, у которого uniqueResult из сессии возвращает null, если запись не найдена.

bytecode ★★ ()
Ответ на: комментарий от bytecode

У меня вот такой утилитный метод есть для этих целей:


    public static <T> Optional<T> getSingleResultOpt(TypedQuery<T> query) {
        List<T> list = query.getResultList();
        Iterator<T> iterator = list.iterator();
        if (!iterator.hasNext()) {
            return Optional.empty();
        }
        T result = iterator.next();
        if (iterator.hasNext()) {
            throw new NonUniqueResultException();
        }
        return Optional.of(result);
    }

Если жава не 8-я, можно null возвращать.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 2)
Ответ на: комментарий от LestorN

JOOQ в прямых руках - сказка. Подходит как для новой разработки, так и для человеческого интерфейса с тоннами pl/sql enterprise говнокода с тоннами вложенных типов.

Я смотрю тут все джук хвалят. Вот пример, как вроде обычный sql превращается в нечитаемое месиво с джуком. Если у кого-то руки прямее, будьте добры, перепишите.

@SuppressWarnings("unchecked")
	@Override
	public List<MWithdrawal> loadLastOfEveryUsers(Integer count) {
		/*
		 * with numbered_by_user as ( select row_number() over (partition by
		 * user_id order by request_date desc) as number_by_user, w.* from
		 * withdrawal w ) select n.* from numbered_by_user n left join ( select
		 * user_id, max(number_by_user) last_unprocessed_number_by_user from
		 * numbered_by_user where processed = false group by user_id )
		 * last_unprocessed on last_unprocessed.user_id = n.user_id where
		 * n.number_by_user <= 4 or
		 * last_unprocessed.last_unprocessed_number_by_user is not null and
		 * n.number_by_user <= last_unprocessed.last_unprocessed_number_by_user;
		 */
		Table<Record2<Integer, Integer>> ids_numbered_by_user = dsl
				.select(rowNumber().over().partitionBy(WITHDRAWAL.USER_ID).orderBy(WITHDRAWAL.REQUEST_DATE.desc()).as("number_by_user"),
						WITHDRAWAL.ID.as("withdrawal_id")).from(WITHDRAWAL).asTable("ids_numbered_by_user");
		SelectOnConditionStep<Record> numbered_by_user = dsl.select().from(ids_numbered_by_user).join(WITHDRAWAL)
				.on(WITHDRAWAL.ID.eq((Field<Integer>) ids_numbered_by_user.field("withdrawal_id")));
		Field<Integer> numbered_by_user_user_id = (Field<Integer>) numbered_by_user.field(WITHDRAWAL.USER_ID.getName());
		Field<Integer> numbered_by_user_number = (Field<Integer>) numbered_by_user.field("number_by_user");
		Table<?> last_unprocessed = dsl.select(numbered_by_user_user_id, max(numbered_by_user_number).as("last_unprocessed_number_by_user"))
				.from(numbered_by_user).where(((Field<Boolean>) numbered_by_user.field(WITHDRAWAL.PROCESSED.getName())).eq(Boolean.FALSE))
				.groupBy(numbered_by_user_user_id).asTable("last_unprocessed");
		Field<Integer> last_unprocessed_user_id = (Field<Integer>) last_unprocessed.field(WITHDRAWAL.USER_ID.getName());
		Field<Integer> last_unprocessed_number_by_user = (Field<Integer>) last_unprocessed.field("last_unprocessed_number_by_user");
		Field<Integer> numbered_by_user_withdrawal_id = (Field<Integer>) numbered_by_user.field("withdrawal_id");
		Table<Record2<Integer, Integer>> withdrawal_ids = dsl
				.select(numbered_by_user_withdrawal_id, numbered_by_user_number)
				.from(numbered_by_user)
				.leftOuterJoin(last_unprocessed)
				.on(last_unprocessed_user_id.eq(numbered_by_user_user_id))
				.where(numbered_by_user_number.le(count).or(
						last_unprocessed_number_by_user.isNotNull().and(numbered_by_user_number.le(last_unprocessed_number_by_user))))
				.asTable("withdrawal_ids");
		Field<Integer> withdrawal_id = (Field<Integer>) withdrawal_ids.field("withdrawal_id");
		SelectLimitStep<Record> select = dsl.select().from(WITHDRAWAL).join(withdrawal_ids).on(withdrawal_id.eq(WITHDRAWAL.ID)).join(CURRENCY)
				.on(CURRENCY.ID.eq(WITHDRAWAL.CURRENCY_ID)).join(CURRENCY_CONFIG).on(CURRENCY_CONFIG.ID.eq(WITHDRAWAL.CURRENCY_ID))
				.leftOuterJoin(WITHDRAWALS_TXIDS).on(WITHDRAWALS_TXIDS.field(TRANSACTION_TXID.WITHDRAWAL_ID).eq(WITHDRAWAL.ID))
				.orderBy(WITHDRAWAL.USER_ID.asc(), WITHDRAWAL.REQUEST_DATE.desc());

		List<MWithdrawal> list = select.fetch().map(createGroupRecordMapper());
		return list;
	}
orm-i-auga ★★★★★ ()

spring-data-jpa (для сложных запросов - specification) + spring transactions

windofchange ()
Последнее исправление: windofchange (всего исправлений: 2)

Хочу поинтересоваться у местных яверов - какие ORM используете вы

Смотрю в сторону GreenDAO для андроида. Не уверен в нужности.

anonymous ()

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

by_zero ()

Используется на проекте сейчас Hibernate. Использовал и EclipseLink, второй показался побыстрее.

Где то в интернетах было видео выступления на конференции, где ребята рассказывали о своем опыте отказа от Hibernate и переходе на OpenJPA(а может еще на что то) Возможно это оно тыц!

TheKnight ★★★ ()
Ответ на: комментарий от by_zero

А я думал, что это пережиток двухзвенного прошлого.

bytecode ★★ ()

Возьми нормальный апп-сервер типа WildFly, вынеси всю работу с базой в отдельные сервисные классы и сделай их session bean-ами. Получишь транзакции через аннотации, JTA, CDI и прочие плюшки. И ходи в базу хоть через MyBatis, хоть через что, транзации вообще не должны иметь отношения к ORM, если у тебя JTA. Не говоря уже, что можно будет заинжектить себе TransactionManager JTA-шный через @Resource и рулить аннотациями руками по-нормальному. Достаточно взять полноценный апп-сервер.

DiKeert ★★ ()
Последнее исправление: DiKeert (всего исправлений: 2)

Хибернейт в целом ок, но штука капризная - нужно следить по логам что выходить. Но в целом с ним работать можно. Вообще мне нравится Scala и Slick :)

dizza ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.