Version 6 (modified by 8 days ago) ( diff ) | ,
---|
Трансакции
Употребата на трансакции беше од голема корист за операции кои требаа да се извршат или сите или да падне, за лесно да се најде грешката. Во мојата апликација истото го употребив во неколку наврати, кои ќе ги покажам подолу.
1. Трансакција при регистрација на нов корисник
@Transactional public void register_user(String name, String surname, String email, String password, String password_confirmation) { if (email == null || email.isEmpty() || password == null || password.isEmpty() || password_confirmation == null || password_confirmation.isEmpty()) { throw new NoExistingCredentialsException("Credentials cannot be empty."); } if (!password.equals(password_confirmation)) { throw new IllegalArgumentException("Passwords do not match. Check the mistake and try again."); } if (reportiumUserRepository.findByEmail(email).isPresent()) { throw new UserAlreadyExistsException(String.format("User with this email '%s' already exists.", email)); } //creating the user ReportiumUser new_application_user = new ReportiumUser(); new_application_user.setActive(true); new_application_user.setName(name); new_application_user.setSurname(surname); new_application_user.setEmail(email); new_application_user.setPasswordHash(passwordEncoder.encode(password)); new_application_user.setCreatedAt(LocalDateTime.now()); ReportiumUser savedUser = reportiumUserRepository.save(new_application_user); //I have a trigger that creates the user profile UserProfile user_profile = userProfileRepository.findByReportiumUser(savedUser).get(); UserProfileLog userProfileLog = new UserProfileLog(); userProfileLog.setUserProfile(user_profile); userProfileLog.setChangedAt(LocalDateTime.now()); String description = String.format("New user <%s %s> with mail '%s' has been registered.", savedUser.getName(), savedUser.getSurname(), savedUser.getEmail()); userProfileLog.setChangeDescription(description); profileLogRepository.save(userProfileLog); }
Оваа трансакција ја извршува регистрацијата на нов корисник атомарно и безбедно. На почеток се валидираат влезните податоци, се проверува совпаѓањето на лозинките и единственоста на e-маил адресата. Ако условите се исполнети, се креира активен корисник со хеширана лозинка, профилот се креира преку тригер (може да го најдете тука), а потоа се запишува лог за настанот. Сè се извршува во рамки на @Transactional, па при било каква грешка се прави целосен rollback и не се дозволуваат дупликати или делумно снимени податоци. На овој начин се обезбедуваат конзистентност, интегритет и следливост на процесот на регистрација.
2. Креирање на Log запис во табелата
@Override @Transactional public void createLog(Integer userId, LogType type) { if (type == null) { throw new IllegalArgumentException("Log type must not be null"); } UserProfile profile = profileRepository .findById(userId).orElseThrow(() -> new NoExistingCredentialsException("User does not exist")); String fullName = buildFullName(profile); String description = switch (type) { case LOGIN -> "User %s logged in successfully.".formatted(fullName); case REGISTRATION -> "User %s registered successfully.".formatted(fullName); case LOGOUT -> "User %s logged out successfully.".formatted(fullName); case CHANGE_ROLE -> "The role of user %s was changed successfully.".formatted(fullName); case CHANGE_PASSWORD -> "User %s changed the password successfully.".formatted(fullName); }; UserProfileLog log = new UserProfileLog(); log.setUserProfile(profile); log.setChangeDescription(description); log.setChangedAt(LocalDateTime.now()); userProfileLogRepository.save(log); }
Оваа трансакција атомски креира лог-запис за соодветна активност. Прво се валидира LogType и се вчитува соодветниот профил; ако недостиствува било што, процесот се прекинува со грешка. Потоа се гради описна порака според типот и се снима запис во UserProfileLog со тековен временски печат. Поради @Transactional, запишувањето е конзистентно—или целосно успева, или се прави rollback без делумни записи.
3. Трансакција при бришење на Person објект
Оваа трансакција обезбедува безбедно и конзистентно бришење на Person објект од базата. Пред бришењето, сите извештаи поврзани со тоа лице атомски се пренасочуваат кон специјалниот „stub“ (архивен) запис, за да не останат сираци-записи и да се зачува референтниот интегритет.
Целиот процес се извршува во една @Transactional операција, што гарантира дека или сите чекори ќе успеат, или ниеден (се прави rollback при грешка). Паралелно се запишува и аудит-лог со e-маилот на администраторот и бројот на префрлени извештаи, за целосна следливост. Архивниот „stub“ запис е заштитен од бришење, со што се спречуваат неконзистентни состојби.
@Transactional @Override public void deletePerson(String userEmail, Long personId) { Person stub = personRepository.findByStubTrue().orElseGet(() -> { Person newStub = new Person(); newStub.setEmbg("UNIQUE-EMBG"); newStub.setName("Stub"); newStub.setSurname("Collector"); newStub.setGender(Gender.MALE); newStub.setDateOfBirth(LocalDate.of(1900, 10, 10)); newStub.setAlive(false); newStub.setAddress("N/A"); newStub.setContactPhone("N/A"); newStub.setStub(true); return personRepository.save(newStub); }); Person targetToDelete = personRepository.findById(personId).orElseThrow(() -> new PersonNotFoundException("Person with id=" + personId + " not found")); if (targetToDelete.getPersonId() == stub.getPersonId()) { throw new IllegalStateException("Archive (stub) person cannot be deleted."); } int totalReportsMoved = reportRepository.reassignReportsToStub((long) targetToDelete.getPersonId(), (long) stub.getPersonId()); String logMessage = String.format("Admin User with email %s has deleted Person: %s %s. All his %d reports were moved to stub person.", userEmail, targetToDelete.getName(), targetToDelete.getSurname(),totalReportsMoved); reportiumUserRepository.findByEmail(userEmail).ifPresent(reportiumUser -> { UserProfileLog userProfileLog = new UserProfileLog(); userProfileLog.setChangedAt(LocalDateTime.now()); userProfileLog.setChangeDescription(logMessage); userProfileLog.setUserProfile(reportiumUser.getProfile()); userProfileLogRepository.save(userProfileLog); }); personRepository.delete(targetToDelete); } *** Методот reassignReportsToStub @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = """ UPDATE Report r SET r.person.personId = :stub_person_id WHERE r.person.personId = :target_to_delete_person_id """) int reassignReportsToStub(@Param("target_to_delete_person_id") Long targetId, @Param("stub_person_id") Long stubId);