wiki:Transactions

Трансакции

1. Трансакција за креирање на изнајмување

 @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    public Lease save(Long listingId,
                                        Long tenantId,
                                        Long landlordUserId,
                                        LocalDate startDate,
                                        LocalDate endDate,
                                        BigDecimal rentAmount,
                                        BigDecimal depositAmount) {
        try {
            System.out.println("=== ЗАПОЧНУВАЊЕ НА ТРАНСАКЦИЈА ЗА КРЕИРАЊЕ НА ИЗНАЈМУВАЊЕ ===");
            Listing listing = listingService.findById(listingId);

            if (!"available".equals(listing.getStatus())) {
                throw new IllegalStateException("Огласот не е достапен за изнајмување. Статус: " + listing.getStatus());
            }

            TenantProfile tenant = tenantProfileService.findByUserId(tenantId);
            if (tenant == null) {
                throw new IllegalArgumentException("Изнајмувачот не е пронајден со ID: " + tenantId);
            }

            LandlordProfile landlord = landlordProfileService.findByUserId(landlordUserId);
            if (landlord == null) {
                throw new IllegalArgumentException("Издавачот не е пронајден со ID: " + landlordUserId);
            }

            System.out.println("TENANT ID: " + tenant.getId());
            System.out.println("LANDLORD ID: " + landlord.getId());

            Lease lease = new Lease(
                    startDate,
                    endDate,
                    rentAmount,
                    depositAmount,
                    listing,
                    tenant,
                    landlord
            );

            Lease savedLease = leaseRepository.save(lease);
            listing.setStatus("rented");
            listingService.save(listing);
            return savedLease;

        } catch (Exception e) {
            System.err.println("ГРЕШКА ВО ТРАНСАКЦИЈАТА: " + e.getMessage());
            throw new RuntimeException("Грешка при креирање на изнајмување: " + e.getMessage(), e);
        }
    }

Оваа трансакција вклучува повеќе операции во базата на податоци кои мора да се извршат како една целина. Методот save() е анотиран со

@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)

што значи дека сите операции во рамките на методот се третираат како една атомска единица.

Прво се проверува дали огласот постои и дали е достапен за изнајмување со статус "available". Доколку огласот не е достапен, се фрла исклучок кој автоматски ја прекинува трансакцијата и се извршува ROLLBACK.

Потоа се бараат и валидираат профилите на издавач и изнајмувачот. Доколку било кој од нив не постои во базата, повторно се фрла исклучок и трансакцијата се поништува. Со ова се постигнува CONSISTENCY- се гарантира дека во базата нема да се внесат невалидни или неконзистентни податоци. Кога сите валидации поминуваат успешно, се извршуваат две клучни операции во базата:

  • Прва операција: Се креира и зачувува изнајмување во табелата Lease
  • Втора операција: Се ажурира статусот на огласот од "available" во "rented" и истиот се зачувува во базата.

Овие две операции се атомски поврзани - или и двете ќе успеат, или и двете ќе се поништат. Не може да се случи ситуација каде изнајмувањето е креирано, а статусот на огласот не е ажуриран, или обратно.

ATOMICITY се постигнува преку @Transactional анотацијата со параметар rollbackFor = Exception.class. Ова значи дека доколку се фрли било кој исклучок при извршување на методата, сите промени што се направени до тој момент автоматски се поништуваат со ROLLBACK операцијата.

ISOLATION се постигнува преку параметарот isolation = Isolation.READ_COMMITTED кој спречува други паралелни трансакции да читаат неодобрени промени. На тој начин се гарантира дека два корисника истовремено нема да можат да креираат договор за истиот листинг.

DURABILITY се постигнува автоматски кога методот завршува успешно без фрлање на исклучок. Во тој случај, Spring автоматски врши COMMIT операција и сите промени трајно се зачувуваат во базата на податоци.

2. Трансакција за оценување на корисник

@Transactional
    public void updateUserRating(Long userId, BigDecimal newRating) {
        UserD user = userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        if (user.getRating().compareTo(BigDecimal.ZERO) == 0) {
            user.setRating(newRating);
        } else {
            BigDecimal average = user.getRating()
                    .add(newRating)
                    .divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP);
            user.setRating(average);
        }
        userRepository.save(user);
    }

Методот updateUserRating() е анотиран со @Transactional бидејќи содржи две поврзани операции кои мора да се извршат како една атомска единица: прво се чита тековната оценка на корисникот од базата, потоа се пресметува новата просечна оценка. Доколку овој метод не претставуваше трансакција можеше да се случи следново: два корисника истовремено да му постават оценка на ист корисник што би резултирало со загуба на една од оценките.

3.Трансакција за креирање на имот со слики

@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public Property createPropertyWithImages(Long propertyTypeId, String name, String description,
                                         String street, String streetNumber, String municipality,
                                         String city, String country, MultipartFile[] images,
                                         UserD owner) throws IOException {
    try {
        System.out.println("=== ЗАПОЧНУВАЊЕ НА ТРАНСАКЦИЈА ЗА КРЕИРАЊЕ НА ИМОТ ===");
        
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Property name cannot be empty");
        }

        var propertyType = propertyTypeRepository.findById(propertyTypeId)
                .orElseThrow(() -> new RuntimeException("Property type not found with ID: " + propertyTypeId));

        Address address = new Address(street, streetNumber, municipality, city, country);
        address = addressRepository.save(address);

        Property property = new Property(name, description, LocalDateTime.now(), owner, propertyType, address);
        property = propertyRepository.save(property);

        if (images != null && images.length > 0) {
            processImageUploads(images, property);
        }
        return property;
        
    } catch (Exception e) {
        System.err.println("ГРЕШКА ВО ТРАНСАКЦИЈА ЗА КРЕИРАЊЕ НА ИМОТ: " + e.getMessage());
        throw new RuntimeException("Грешка при креирање на имот со слики: " + e.getMessage(), e);
    }
}

Методот createPropertyWithImages() е анотиран со @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED) бидејќи содржи три поврзани операции кои мора да се извршат како една атомска единица: прво се креира и зачувува адреса во базата, потоа се креира и зачувува имот кој покажува кон адресата преку address_id, и на крај се обработуваат и зачувуваат сликите во Property_Image табелата.

Доколку методот не беше означен со @Transactional би можело да се случи успешно креирање на адреса, имот, но на пример фрлање на исклучок при процесирање на сликите, покрај тоа корисникот би помислил дека неговиот имот е креиран со слики, но всушност има имот без слики, што не е она што го барал. Трансакцијата гарантира дека или се успева (имот со слики), или ништо не се прави.

Last modified 15 hours ago Last modified on 08/18/25 22:57:18
Note: See TracWiki for help on using the wiki.