Как стать автором
Обновить

Комментарии 16

Свои мысли по поводу развития:

  1. Вынести функциональность LimitValidationConstraints, отвечающую за "интеграцию" с UniqueValidationConstraints в отдельную анноташку и валидатор. Учитывая, что эта функциональность привязана к LimitValidationConstraints, но при этом задумывалась для проверки уникальности с игнорированием существующей по констрейнту записи в рамках обновления - мы не сможем использовать ее "саму по себе", а только с группой для обновления. Ведь навесить больше одной аннотации над классом мы не можем? Поэтому имеет смысл создать отдельную аннотацию, например, CheckUniqueAnnotationIgnoringOneMatch. И отвязать эту функциональность от LimitValidationConstraints. Хотя я не уверен, что кому-то вообще эта самая LimitValidationConstraints будет нужна "сама по себе")

  2. Внедрять entityManager, наверное, было бы правильно через entityManagerFactory. Т.е. внедряем в валидаторы его, внутри валидаторов создаем через фактори entityManager. Для валидаторов можно сделать интерфейс с сеттером для entityManagerFactory. И создать "свой" validatorFactoryBean, расширяющий базовую LocalValidatorFactoryBean. В него в конфиге будем передавать нужную нам entityManagerFactory. И он, соответственно, будет прокидывать в наши валидаторы этот entityManagerFactory. Ведь в рамках одной прилаги могут работать разные датасурсы со своим "сопровождением". Но тогда еще о transactionManager стоило бы задуматься. Пока тут все заточено на "дефолт".

  3. Само собой оформление кода + рефакторинг. И нейминги местами так себе.

И еще функционал для проверки изменений в объектах/коллекциях можно вынести в отдельную "вкусность", которая будет полезна и за пределами валидации.

Получается проверка на уникальность в java, а в бд его нет ?, если так, тогда странное:

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

Если не обеспечить эту атомарность, то будет такое

| # | client 1      | client 2       |
|---|---------------|----------------|
| 1 | insert begin  |                |
| 2 |               |  insert begin  |
| 3 |               |  insert finish |
| 4 | insert finish |                |

client 1 и client 2 - хотят вставить одну и ту же запись - по идее только один должен суметь
но будут вставлены обе записи, по скольку каждый клиент работает в своей сессии/транзакции и включен режим MVCC или SnapShot isolation... read commited

по скольку на начало операции insert никто из них не видит изменений соседа

предварительная проверка в духе (table.field1Constraint1 = value1Constraint1 AND table.field2Constraint1 = value2Constraint1...) OR (...другой констрейнт)

не сработает в этой ситуации, по скольку опять же read commited еще не произошел, да же если insert был, но не было еще commit - то, опять же сосед ничего не узнает

В любом случае как не крути, необходима блокировка, по скольку id - должно быть уникальным значением

блокировка может быть и не явная, например по просить новый id у базы, например sequence (oracle)

ну а поскольку выполняется на стороне java все это, тогда запуск второго экземпляра java - все сломает - конечно, если не пользоваться менеджерами блокировок, но вообще-то менеджеры блокировок есть во многих бд, что бы не изобретать велосипед.

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

Могу согласиться... Аномалии в рамках параллельной работы множества транзакций - вообще проблема широкого круга, вы (на мой взгляд) по сути описали что-то подобное на "чтение фантомов", скажем. Но в данном случае, слава Богу, есть уровень изоляции read_commited.

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

Вернее, serializable*

То есть при многопоточной записи новых данных в таблице предлагается только вариант serializable? Как выше верно заметили, при старте потоки видят что в таблице нет записи. Вроде как по уникальной колонке только один поток сохранит данные, но остальные упадут с ошибкой. А если нужно чтобы сама запись была в отдельной транзакции..

Ну, есть еще и другой вариант, который предложили ниже. Это как минимум.

По поводу "... а если нужно чтобы сама запись была в отдельной транзакции" - я пока, наверное, не до конца уловил)

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

https://stackoverflow.com/questions/3495368/unique-constraint-with-jpa-and-bean-validation

Собственно не обязательно лочить всю таблицу, достаточно лочить только один объект, например какую ни будь спец запись в таблице, которая будет содержать maxId

А вообще можно положиться на UUID или hash значения - и выполнять upsert/merge - только, тогда возникает в случае hash - ситуация как race condition, а uuid - по сути не связан с полезными данными

оба варианта, так себе... без lock не обойтись

вопрос, а вот действительно ли нужно выносить такую важную штуку как id/Fk/Pk в java ? если только для читабельности ошибок, так вроде кол-во субд у нас в мире не бесконечно, основных/mainstream 4, если считать еще всяких недавно появишся, так не более 20-30 штук, маппинг ошибки конкретной бд в свою ошибку приложения сделать вроде не сложно, да и в модели можно комментарий на русском/англиском/ оставить - что вот это поле(я) являются unique

Думаю, тоже вариант, но делать это нужно будет во всей транзакции разом, под капот моих валидаторов это не спрятать, например... И в начале каждого транзакционного метода вручную реализовывать эту логику, т.к Spring какого-то готового решения а-ля "поле в анноташке" не предоставляет.

Ну, вынести можно в любом случае. Можно и для читабельности ошибок, и для "прозрачности" кода. Но полагаться исключительно на Java, видимо, не стоит. Оно в любом случае должно быть на обоих уровнях в данном случае.

Тут вопрос мб даже не в количестве СУБД, а в самой реализации обработки... Помню попытку на одном из проектов, выглядело не очень, и даже там по итогу были проверки на уникальность и на уровне Java.

Так-то у меня сейчас даже родилась идея... Почему бы не дополнить эту либу каким-то решением для декодинга сообщений от самых основных БД?

Можно дополнить либу, я бы так делал:
либа - отдельный maven - jar
плагин для бд - другой maven jar
связка lib - plugin через ServiceLoader (https://habr.com/ru/articles/118488/)

выбор какой плагин активировать через либо jdbc url (https://docs.oracle.com/javase/8/docs/api/java/sql/DatabaseMetaData.html#getURL--) , либо jdbc driver class (instance of)

Подскажите плиз, а чем вам условные проверки из коробки не нравятся?
Если мы хотим проверить что-то на уникальность, добавляем @Column(not_null = true) и т.д.

Далее пишем кастомный хендлер ошибок и делаем нужные нам операции. Это все работает со всеми видами relations(OneToOne, OneToMany, ManyToMany...).

Не очень понял про проверку на уникальность с not_null = true. Это же проверки на NN уже.

Если вы имели в виду unique в анноташке Column - это, насколько я понимаю, скорее для генерации DDL.

А что вы имеете в виду под кастомным хендлером ошибок? Условно, класс, который будет отлавливать и декодировать исключения из БД при сохранении, например? Это вполне себе вариант. Основная проблема, наверное, в том, чтобы написать красиво)

Решал аналогичную задачу в проекте на технологии asp.net.
Валидаторы входят в функционал presentation layer и логично, что валидаторы должны быть вписаны в общую архитектуру приложения. Внедряя в валидаторы EntityManager мы напрямую подключаем их к ORM - то есть выносим валидаторы за скобки используемой в приложении архитектуры.
Поэтому логично из валидатора обращаться к слою persistence layer приложения, который работает с базами данных. А уже этот слой может использовать ORM или прямые запросы к бд. В этом случае от валидатора будут скрыты механизмы взаимодействия с бд.

Да, я тоже над чем-то таким подумывал.

Пока можно остановиться на двух "основных" видах persistence layer: JPA и нативка (можно использовать JdbcTemplate - библиотека пишется со spring-boot-starter-data-jpa в уме, там оно тянется). Имеет смысл написать абстракцию, которая будет осуществлять работу с persistence-layer, и валидаторы будут анализировать плоды ее работы. Под нее как минимум две дефолтные реализации - JPA + entityManager и нативка. И оставить возможность интегрировать для persistence layer валидаторов свое решение - вдруг, скажем, свою ORM-ку пишем?) Ну или какой-нибудь NoSQL - там уже всякие свои OGM бывают.

По сути даже эдакий мини-фреймворк вырисовывается в этом случае.

Это пока идея "в ящик")

А как в целом ваше мнение о либе? Полезное решение хотя бы в рамках JPA? Стали бы использовать? Стоит развивать и в перспективе залить Maven Central, если доведется до ума?

В целом вопрос подобных валидаций на уровне Java дискуссионный. Это и здесь комментарии показывают.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории