В принципе, я пытаюсь понять, как написать правильный (или «правильно написать»?) Транзакционный код при разработке службы REST с Jax-RS и Spring , Кроме того, мы используем JOOQ для доступа к данным. Но это не должно быть очень уместным ...
Рассмотрим простую модель, в которой у нас есть некоторые организации, у которых есть эти поля: "id", "name", "code"
. Все это должно быть уникальным. Также есть поле status
.
Организация может быть удалена в какой-то момент. Но мы не хотим вообще удалять данные, потому что мы хотим сохранить их для целей аналитики/обслуживания. Поэтому мы просто установили поле статуса организации на 'REMOVED'
.
Поскольку мы не удаляем строку организации из таблицы, мы не можем просто поместить уникальное ограничение в столбец «name», потому что мы можем удалить организацию, а затем создать новую с тем же именем. Но предположим, что коды должны быть уникальными во всем мире, поэтому у нас есть уникальное ограничение на столбец code
.Как написать правильный/надежный транзакционный код с JAX-RS и Spring
Итак, давайте посмотрим на этот простой пример, который создает организацию, выполняющую некоторые проверки на этом пути.
Ресурс:
@Component
@Path("/api/organizations/{organizationId: [0-9]+}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaTypeEx.APPLICATION_JSON_UTF_8)
public class OrganizationResource {
@Autowired
private OrganizationService organizationService;
@Autowired
private DtoConverter dtoConverter;
@POST
public OrganizationResponse createOrganization(@Auth Person person, CreateOrganizationRequest request) {
if (organizationService.checkOrganizationWithNameExists(request.name())) {
// this throws special Exception which is intercepted and translated to response with 409 status code
throw Responses.abortConflict("organization.nameExist", ImmutableMap.of("name", request.name()));
}
if (organizationService.checkOrganizationWithCodeExists(request.code())) {
throw Responses.abortConflict("organization.codeExists", ImmutableMap.of("code", request.code()));
}
long organizationId = organizationService.create(person.user().id(), request.name(), request.code());
return dtoConverter.from(organization.findById(organizationId));
}
}
DAO служба выглядит следующим образом:
@Transactional(DBConstants.SOME_TRANSACTION_MANAGER)
public class OrganizationServiceImpl implements OrganizationService {
@Autowired
@Qualifier(DBConstants.SOME_DSL)
protected DSLContext context;
@Override
public long create(long userId, String name, String code) {
Organization organization = new Organization(null, userId, name, code, OrganizationStatus.ACTIVE);
OrganizationRecord organizationRecord = JooqUtil.insert(context, organization, ORGANIZATION);
return organizationRecord.getId();
}
@Override
public boolean checkOrganizationWithNameExists(String name) {
return checkOrganizationExists(Tables.ORGANIZATION.NAME, name);
}
@Override
public boolean checkOrganizationWithCodeExists(String code) {
return checkOrganizationExists(Tables.ORGANIZATION.CODE, code);
}
private boolean checkOrganizationExists(TableField<OrganizationRecord, String> checkField, String checkValue) {
return context.selectCount()
.from(Tables.ORGANIZATION)
.where(checkField.eq(checkValue))
.and(Tables.ORGANIZATION.ORGANIZATION_STATUS.ne(OrganizationStatus.REMOVED))
.fetchOne(DSL.count()) > 0;
}
}
Это приносит некоторые вопросы:
- Нужен ли
@Transactional
аннотацииcreateOrganization
метода ресурса? Или я должен создать еще одну службу, которая ведет переговоры с DAO и добавит аннотацию @Transactional к ее методу? Что-то другое? - Что произойдет, если два пользователя одновременно отправят запрос с тем же полем
"code"
. Перед первой транзакцией транзакции успешно пройдены, поэтому не будет отправлено 409 запросов. Затем первая транзакция будет выполнена правильно, но вторая будет нарушать ограничение DB. Это вызовет SQLException. Как изящно справиться с этим? Я имею в виду, что я все еще хочу показать хорошее сообщение об ошибке на стороне клиента, заявив, что это имя уже используется. Но я не могу разобрать SQLException или что-то еще. Могу ли я? - Как и предыдущий, но на этот раз «имя» не является уникальным. В этом случае вторая транзакция не будет нарушать каких-либо ограничений, что приводит к наличию двух организаций с тем же именем, что нарушает наши ограничения на доступность.
- Где я могу просмотреть/изучить учебники/код/и т. Д., Что вы считаете отличные примеры того, как писать правильный/надежный код REST + DB со сложной логикой ведения. Github/книги/блоги, что угодно. Я попытался найти что-то вроде этого myselft, но большинство примеров просто сосредоточены на сантехнике - добавьте эти библиотеки в maven, используйте эти аннотации, есть ваш простой CRUD, конец. Они вообще не содержат никаких транзакционных соображений. То есть
UPDATE: Я знаю об уровне изоляции и обычный error/isolation matrix (грязное чтение, и т.д ..). Проблема, которую я имею, заключается в нахождении какой-то «готовой к производству» выборки. Или хорошая книга по теме. Я до сих пор не понимаю, как правильно обрабатывать все ошибки. Думаю, мне нужно повторить пару раз, если транзакция завершилась неудачно .. и просто выбросить некоторую общую ошибку и реализовать клиент, который обрабатывает это. Но сделайте Мне действительно нужно использовать режим SERIALIZABLE, когда я использую запросы диапазона? Потому что это сильно повлияет на производительность. Но в остальном, как я могу гарантировать, что транзакция не удастся.
Во всяком случае, я решил, что сейчас мне нужно больше времени, чтобы узнать о сделках и управления БД в целом, чтобы решить эту проблему ...
Ну, что, если есть другие действительные статусы SUSPENDED, и т.д.? Дело в том, что я не могу сделать это с помощью простых ограничений ... Должен ли я писать сложные триггеры для каждого сложного ограничения, поэтому дублируя то, что я написал на Java? Также как изящно оправиться от нарушения этих ограничений? И где я могу найти хороший пример кода, который следует этим методам? –
Сфера действия транзакции является единицей работы, единица работ не определена на уровне DAO, вообще говоря, наличие @Transactionnal аннотаций на уровне DAO является конструктивным запахом – Gab
@Gab Очень не согласен - где, по вашему мнению, информация о транзакциях должна быть если не так близко к тому, где взаимодействуют базы данных? – Gandalf