JavaRush /Курси /Модуль 5. Spring /Практика: Реалізація транзакцій у Spring-застосунку

Практика: Реалізація транзакцій у Spring-застосунку

Модуль 5. Spring
Рівень 6 , Лекція 5
Відкрита

Транзакції — це ключовий механізм для безпечної роботи з базами даних. Давай розберемося на прикладі інтернет‑магазину, чому вони такі важливі.

Уяви: покупець оформлює замовлення. Здається проста операція, але насправді треба:

  1. Списати гроші з рахунку покупця
  2. Зарезервувати товар на складі
  3. Створити запис про замовлення в базі

Що може піти не так? Майже все! Наприклад, гроші списались, а сервер «впав» до створення замовлення. Без транзакцій це перетвориться на справжній кошмар для служби підтримки. На щастя, транзакції вирішують цю проблему: або всі операції виконуються успішно, або система автоматично все відкатує назад.


Налаштування TransactionManager

Перш ніж писати код, переконаємося, що наш застосунок готовий до керування транзакціями. TransactionManager — це серце транзакційного механізму в Spring. Він координує виконання операцій.

Якщо ви використовуєте JPA/Hibernate (що ми й зробимо), то налаштування TransactionManager виглядає просто.

Додаємо залежності

Перше, що зробимо — підключимо залежності для роботи з JPA і базою даних (наприклад, H2 Database для тестів). Переконайтесь, що в pom.xml вказаний такий код:


<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>

Конфігурація бази даних

У файлі application.properties пропишемо налаштування для підключення до H2:


spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Конфігурація TransactionManager

Spring Boot налаштує TransactionManager автоматично, якщо ви використовуєте spring-boot-starter-data-jpa. Це означає, що ми вже готові працювати з транзакціями. Ура!


Реалізація транзакції: створення прикладу застосунку

Тепер створимо невеликий застосунок, що моделює реєстрацію замовлення в інтернет‑магазині. Наші задачі:

  1. Зберегти інформацію про замовлення.
  2. Оновити кількість доступних товарів.
  3. Якщо щось піде не так — відкотити всі зміни.

Створюємо сутності

Почнемо з бази даних і сутностей. У нас будуть дві таблиці: Order і Product.

Сутність Product


import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Product {

    @Id
    private Long id;
    private String name;
    private int stock; // Кількість на складі

    // Геттери й сеттери (можна використовувати Lombok)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }
}

Сутність Order


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId;
    private int quantity;

    // Геттери й сеттери
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}

Створюємо репозиторії

Тепер створимо репозиторії для роботи з нашими сутностями. Ми будемо використовувати JpaRepository.

ProductRepository


import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
    // Жодних додаткових методів поки не потрібно
}

OrderRepository


import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
    // Жодних додаткових методів поки не потрібно
}

Керування транзакцією через @Transactional

Тепер найцікавіше! Створимо сервіс, який:

  1. Зареєструє замовлення.
  2. Відкотить зміни, якщо товару недостатньо на складі.

OrderService


import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final ProductRepository productRepository;
    private final OrderRepository orderRepository;

    public OrderService(ProductRepository productRepository, OrderRepository orderRepository) {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
    }

    @Transactional
    public void placeOrder(Long productId, int quantity) {
        // 1. Отримуємо товар
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("Product not found!"));

        // 2. Перевіряємо наявність товару на складі
        if (product.getStock() < quantity) {
            throw new RuntimeException("Not enough stock for product!");
        }

        // 3. Зменшуємо кількість на складі
        product.setStock(product.getStock() - quantity);
        productRepository.save(product);

        // 4. Створюємо й зберігаємо замовлення
        Order order = new Order();
        order.setProductId(productId);
        order.setQuantity(quantity);
        orderRepository.save(order);

        // Зберігаємо зміни тільки якщо все пройшло успішно
        System.out.println("Order placed successfully!");
    }
}

Що тут робить анотація @Transactional? Вона гарантує, що всі зміни або будуть зафіксовані, або відкотяться, якщо станеться помилка. Наприклад, якщо товарів на складі недостатньо, метод викине виключення, і зміни буде відкотено.


Тестування транзакцій

Давай протестуємо, як це працює! Напишемо простий тестовий клас.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;

@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private ProductRepository productRepository;

    @Test
    public void testPlaceOrder_Success() {
        // Створюємо тестовий продукт
        Product product = new Product();
        product.setId(1L);
        product.setName("Test Product");
        product.setStock(10);
        productRepository.save(product);

        // Створюємо замовлення
        orderService.placeOrder(1L, 2);

        // Перевіряємо кількість товару на складі
        Product updatedProduct = productRepository.findById(1L).get();
        assert updatedProduct.getStock() == 8;
    }

    @Test
    public void testPlaceOrder_NotEnoughStock() {
        // Створюємо тестовий продукт
        Product product = new Product();
        product.setId(2L);
        product.setName("Another Product");
        product.setStock(5);
        productRepository.save(product);

        try {
            // Намагаємося замовити більше, ніж є на складі
            orderService.placeOrder(2L, 10);
        } catch (Exception e) {
            System.out.println(e.getMessage()); // Expect "Not enough stock for product!"
        }

        // Перевіряємо, що товар не змінився
        Product updatedProduct = productRepository.findById(2L).get();
        assert updatedProduct.getStock() == 5;
    }
}

Підсумки

Тепер у нас є транзакційний сервіс, який:

  • Гарантує цілісність даних.
  • Автоматично відкатує зміни при помилках.
  • Легко тестується.

На практиці такі підходи роблять додатки надійними й передбачуваними. І так, не забудь поставити собі зірку за сьогоднішній прогрес!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ