Zasadniczo staram się zrozumieć, jak napisać prawidłowy (lub "poprawnie napisać"?) Kod transakcyjny, przy tworzeniu usługi REST z Jax-RS i Spring . Ponadto używamy JOOQ do dostępu do danych. Ale to nie powinno być zbyt istotne ...
Rozważmy prosty model, w którym mamy pewne organizacje, które mają następujące pola: "id", "name", "code"
. Wszystkie muszą być unikatowe. Jest też pole status
.
Organizacja może zostać w pewnym momencie usunięta. Ale nie chcemy całkowicie usuwać danych, ponieważ chcemy je zapisać do celów analitycznych/konserwacyjnych. Dlatego właśnie ustawiliśmy pole statusu organizacji na 'REMOVED'
.
Ponieważ nie usuwamy wiersza organizacji z tabeli, nie możemy po prostu wstawić wyjątkowego ograniczenia do kolumny "nazwa", ponieważ możemy usunąć organizację, a następnie utworzyć nową o tej samej nazwie. Ale załóżmy, że kody muszą być unikalne globalnie, więc mamy unikalne ograniczenie w kolumnie code
.Jak napisać poprawny/niezawodny kod transakcyjny za pomocą JAX-RS i Spring
Zobaczmy więc ten prosty przykład, który tworzy organizację, przeprowadzając pewne kontrole po drodze.
zasobów:
@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));
}
}
serwis DAO wygląda tak:
@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;
}
}
To przynosi jakieś pytania:
- powinienem umieścić
@Transactional
adnotacji nacreateOrganization
metody zasobu? A może powinienem stworzyć jeszcze jedną usługę, która rozmawia z DAO i wstawi adnotację @Transactional do swojej metody? Coś innego? - Co by się stało, gdyby dwóch użytkowników jednocześnie wysłało żądanie z tym samym polem
"code"
. Przed dokonaniem pierwszej transakcji kontrole zostaną pomyślnie zakończone, więc nie zostanie wysłanych 409 odpowiedzi. Niż pierwsza transakcja zostanie zatwierdzona poprawnie, ale druga będzie naruszać ograniczenie DB. Spowoduje to wygenerowanie wyjątku SQLException. Jak z wdziękiem sobie z tym poradzić? Chodzi mi o to, że nadal chcę wyświetlać miły komunikat o błędzie po stronie klienta, mówiąc, że nazwa jest już używana. Ale nie mogę naprawdę parsować SQLException lub czegoś ... czy mogę? - Podobny do poprzedniego, ale tym razem "nazwa" nie jest unikalna. W takim przypadku druga transakcja nie naruszy żadnych ograniczeń, co prowadzi do posiadania dwóch organizacji o tej samej nazwie, które naruszają nasze ograniczenia biznesowe.
- Gdzie mogę zobaczyć/dowiedzieć się tutoriale/kod/etc., Które uważają za świetne przykłady, jak napisać poprawny/niezawodny kod REST + DB ze skomplikowaną logiką biznesową. Github/książki/blogi, cokolwiek. Próbowałem znaleźć coś takiego, ale większość przykładów skupia się na instalacjach - dodaj te biblioteki do maven, użyj tych adnotacji, jest twój prosty CRUD, koniec. W ogóle nie zawierają żadnych względów transakcyjnych. To znaczy.
UPDATE: wiem o poziomie izolacji i zwykle error/isolation matrix (brudny czyta, etc ..). Problemem jest znalezienie "gotowej do produkcji" próbki do nauki. Albo dobra książka na ten temat. Nadal nie wiem, jak poprawnie obsłużyć wszystkie błędy. Przypuszczam, że muszę powtórzyć kilka razy, jeśli transakcja się nie powiodła ... a następnie wystarczy podać ogólny błąd i zaimplementować klienta, który to obsługuje .. Ale nie Czy muszę używać trybu SERIALIZABLE, kiedy używam zapytań o zakres? Ponieważ znacznie wpłynie to na wydajność. Ale w inny sposób, w jaki sposób mogę zagwarantować, że ta transakcja się nie powiedzie.
Zresztą Zdecydowałem, że teraz muszę więcej czasu, aby dowiedzieć się o transakcji i zarządzania db w ogóle do rozwiązania tego problemu ...
A co, jeśli istnieją inne ważne statusy zawieszony, etc? Chodzi o to, że nie mogę tego zrobić z prostymi ograniczeniami ... Czy powinienem zawsze pisać skomplikowane wyzwalacze dla każdego złożonego ograniczenia, a więc powielać to, co napisałem w Javie? Również, jak z wdziękiem odzyskać od naruszania tych ograniczeń? I gdzie mogę znaleźć dobry przykład kodu, który następuje po tych praktykach? –
Zakres transakcji to jednostka pracy, jednostka pracy nie jest zdefiniowana na poziomie DAO, ogólnie rzecz biorąc adnotacje @Transactionnal na poziomie DAO to zapach projektu – Gab
@Gab Bardzo nie zgadzam się - jak sądzisz, gdzie powinna być zawarta informacja o transakcji zdefiniowane, jeśli nie tak blisko miejsca interakcji z bazą danych? – Gandalf