JavaRush /Kurslar /All lectures for UZ purposes /Transaktsiyani boshqarish

Transaktsiyani boshqarish

All lectures for UZ purposes
Daraja, Dars
Mavjud

TestContext freymvorkida transaktsiyalar TransactionalTestExecutionListener orqali boshqariladi, bu esa sukut bo'yicha o'rnatilgan, agar test sinfingizda aniq @TestExecutionListeners e'lon qilinmagan bo'lsa ham. Ammo, transaktsiyalarni qo'llab-quvvatlash vositalarini faollashtirish uchun siz o'zingizning ApplicationContext ichida PlatformTransactionManager bini sozlashingiz kerak, bu esa @ContextConfiguration annotatsiyasi semantikasi bilan yuklanadi (ko'proq ma'lumot keyinroq beriladi). Shu bilan birga, testlaringiz uchun @Transactional annotatsiyasini Spring'dan, sinf darajasida yoki metod darajasida e'lon qilishingiz kerak.

Testlar orqali boshqariladigan transaktsiyalar

Testlar orqali boshqariladigan transaktsiyalar – bu deklarativ ravishda TransactionalTestExecutionListener yoki dasturiy ravishda TestTransaction orqali boshqariladigan transaktsiyalar (keyinroq tushuntiriladi). Bunday transaktsiyalarni Spring tomonidan boshqariladigan (testlar uchun yuklanadigan ApplicationContext ichida to'g'ridan-to'g'ri freymvork orqali boshqariladigan) yoki dastur tomonidan boshqariladigan transaktsiyalar bilan aralashtirmaslik kerak (testlar orqali chaqiriladigan dastur kodida dasturiy ravishda boshqariladigan). Spring va dastur boshqaruvidagi transaktsiyalar odatda testlar orqali boshqariladigan transaktsiyalarda ishtirok etadi. Ammo, Spring yoki dastur boshqaruvidagi transaktsiyalar REQUIRED yoki SUPPORTSdan boshqa tarqalish turidan foydalanib sozlangan bo'lsa, ehtiyot bo'lish kerak (ko'proq ma'lumot uchun transaktsiyalar tarqalishi bo'limiga qarang).

Uprejdaeshlanmoq va testlar orqali boshqariladigan transaktsiyalar

Spring orqali boshqariladigan testlar transaktsiyalari bilan birgalikda test freymvorkidan har qanday uprejdaeshlanmoq formasini qo'llashda ehtiyot bo'lish kerak.

Ayniqsa, Spring orqali testlarni qo'llab-quvvatlash current transaktsiya maqomini hozirgi oqimga ( java.lang.ThreadLocal orqali) bog'laydi test metodini chaqirishdan oldin. Agar test freymvorki uprejdaeshlanmoqni qo'llab-quvvatlash uchun hozirgi test metodini yangi oqimda chaqirsa, unda hozirgi test metodida amalga oshirilgan har qanday harakatlar test transaktsiyasi orqali chaqirilmaydi. Natijada, bunday harakatlar test o'tkazish orqali qaytarilmaydi. Buning o'rniga, bunday harakatlar doimiy saqlash vositasiga - masalan, munosabatlar bazasiga yoziladi - hatto test orqali boshqariladigan transaktsiya Spring tomonidan to'g'ri qaytarilsa ham.

Buning sodir bo'lishi mumkin bo'lgan holatlar quyidagilarni o'z ichiga olishi mumkin:

  • JUnit 4'dan @Test(timeout = …) annotatsiyasini qo'llab-quvvatlash va TimeOut qoidasi

  • JUnit Jupiter'dan org.junit.jupiter.api.Assertions sinfidagi assertTimeoutPreemptively(…) metodlar

  • TestNG'dan @Test(timeOut = …) annotatsiyasini qo'llab-quvvatlash

Transaktsiyalarni faollashtirish va o'chirish

Test metodini @Transactional yordamida annotatsiya qilish testning transaktsiyada boshlanishiga olib keladi, bu esa sukut bo'yicha test tugagandan keyin avtomatik ravishda qaytariladi. Agar test sinfi @Transactional annotatsiyasi bilan belgilangan bo'lsa, unda ushbu sinf iyerarxiyasidagi har bir test metodi transaktsiya doirasida bajariladi. @Transactional bilan annotatsiya qilinmagan test metodlari (sinf yoki metod darajasida) transaktsiya doirasida bajarilmaydi. Eslatma bering, @Transactiona.l annotatsiyasi testlar hayotiy tsikli metodlari uchun qo'llab-quvvatlanmaydi – misol uchun, JUnit Jupiter'dan @BeforeAll, @BeforeEach yordamida belgilangan metodlar va h.k. Bundan tashqari, propagation atributi NOT_SUPPORTED yoki NEVERga o'rnatilgan @Transactional bilan belgilangan testlar transaktsiya doirasida bajarilmaydi.

Jadval 1. @Transactional bilan annotatsiyalangan atributlarni qo'llab-quvvatlash
Atribut Testlar orqali boshqariladigan transaktsiyalar uchun qo'llab-quvvatlanadi

value va transactionManager

ha

propagation

faqat Propagation.NOT_SUPPORTED va Propagation.NEVER qo'llab-quvvatlanadi

isolation

yo'q

timeout

yo'q

readOnly

yo'q

rollbackFor va rollbackForClassName

yo'q: buning o'rniga TestTransaction.flagForRollback()dan foydalaning

noRollbackFor va noRollbackForClassName

yo'q: buning o'rniga TestTransaction.flagForCommit()dan foydalaning

Metod darajasidagi hayotiy tsiklga tegishli metodlar – masalan, JUnit Jupiter'dan @BeforeEach yoki @AfterEach yordamida belgilangan metodlar – testlar orqali boshqariladigan transaktsiya doirasida bajariladi. Boshqa tomondan, to'plam va sinf darajasidagi hayotiy tsikl metodlari – masalan, JUnit Jupiter'dan @BeforeAll yoki @AfterAll yordamida belgilangan metodlar va TestNG'dan @BeforeSuite, @AfterSuite, @BeforeClass yoki @AfterClass yordamida belgilangan metodlar – ne testlar orqali boshqariladigan transaktsiya doirasida bajariladi.

Agar siz to'plam yoki sinf darajasidagi hayotiy tsikl metodida kodni transaktsiya doirasida boshqarishni istasangiz, mos PlatformTransactionManagerni test sinfiga o'rnatishingiz va uni TransactionTemplate bilan transaktsiyalarni dasturiy ravishda boshqarish uchun ishlatishingiz mumkin.

Eslatma bering, AbstractTransactionalJUnit4SpringContextTests va AbstractTransactionalTestNGSpringContextTests sinf darajasida transaktsiyalarni qo'llab-quvvatlash uchun oldindan o'rnatilgan.

Quyidagi misolda Hibernate asosidagi UserRepository uchun integratsion test yozish uchun umumiy stsenariy ko'rsatilgan:

Java
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
    @Autowired
    HibernateUserRepository repository;
    @Autowired
    SessionFactory sessionFactory;
    JdbcTemplate jdbcTemplate;
    @Autowired
    void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    @Test
    void createUser() {
        // Test bazasidagi boshlang'ich holatni kuzatamiz:
        final int count = countRowsInTable("user");
        User user = new User(...);
        repository.save(user);
        // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }
    private int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }
    private void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
    @Autowired
    lateinit var repository: HibernateUserRepository
    @Autowired
    lateinit var sessionFactory: SessionFactory
    lateinit var jdbcTemplate: JdbcTemplate
    @Autowired
    fun setDataSource(dataSource: DataSource) {
        this.jdbcTemplate = JdbcTemplate(dataSource)
    }
    @Test
    fun createUser() {
        // Test bazasidagi boshlang'ich holatni kuzatamiz:
        val count = countRowsInTable("user")
        val user = User()
        repository.save(user)
        // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
        sessionFactory.getCurrentSession().flush()
        assertNumUsers(count + 1)
    }
    private fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }
    private fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}

"Transaktsiyalarni qaytarish va tasdiqlash mantiqi" bobida tushuntirilgandek, createUser() metodidan keyin ma'lumotlar bazasini tozalash talab qilinmaydi, chunki TransactionalTestExecutionListener tomonidan kiritilgan o'zgarishlar avtomatik ravishda qaytariladi.

Transaktsiyani qaytarish va tasdiqlash mantiqi

Sukut bo'yicha test transaktsiyalari avtomatik ravishda test tugagandan keyin qaytariladi; ammo transaktsiyalarni tasdiqlash va qaytarish mantiqini @Commit va @Rollback annotatsiyalari yordamida deklarativ tarzda sozlash mumkin. Qo'shimcha ma'lumot uchun annotatsiyalarni qo'llab-quvvatlashga bag'ishlangan bobga qarang.

Dasturiy transaktsiyalarni boshqarish

Siz TestTransactiondagi statik metodlarni qo'llab-quvvatlash orqali testlar orqali boshqariladigan transaktsiyalar bilan dasturiy tarzda o'zaro ta'sir qilishingiz mumkin. Masalan, siz TestTransactionni test metodlari ichida, "oldin" va "keyin" metodlarida joriy test orqali boshqariladigan transaktsiyani ishga tushirish yoki tugatish yoki joriy test orqali boshqariladigan transaktsiyani qaytarish yoki tasdiqlashga sozlash uchun ishlatishingiz mumkin. TestTransactionni qo'llab-quvvatlash har doim faollashgan TransactionalTestExecutionListener eshituvchisi bo'lsa avtomatik ravishda mavjud.

Quyidagi misolda TestTransactionning ba'zi xususiyatlari ko'rsatilgan. Ko'proq ma'lumot uchun TestTransactionning javadociga qarang.

Java
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {
    @Test
    public void transactionalTest() {
        // Test bazasidagi boshlang'ich holatni tasdiqlaymiz:
        assertNumUsers(2);
        deleteFromTables("user");
        // Ma'lumotlar bazasidagi o'zgarishlar tasdiqlanadi!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);
        TestTransaction.start();
        // Ma'lumotlar bazasida boshqa harakatlar amalga oshiramiz, bu 
        // test tugagandan so'ng avtomatik ravishda qaytariladi...
    }
    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
    @Test
    fun transactionalTest() {
        // Test bazasidagi boshlang'ich holatni tasdiqlaymiz:
        assertNumUsers(2)
        deleteFromTables("user")
        // Ma'lumotlar bazasidagi o'zgarishlar tasdiqlanadi!
        TestTransaction.flagForCommit()
        TestTransaction.end()
        assertFalse(TestTransaction.isActive())
        assertNumUsers(0)
        TestTransaction.start()
        // Ma'lumotlar bazasida boshqa harakatlar amalga oshiramiz, bu 
        // test tugagandan so'ng avtomatik ravishda qaytariladi...
    }
    protected fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}

Transaktsiya tashqarisida kodni bajarish

Ba'zan tranzaktsion test metodidan oldin yoki undan keyin muayyan kodni bajarish lozim, lekin tranzaktsion kontekst tashqarisida – masalan, testni ishga tushirishdan oldin ma'lumotlar bazasining boshlang'ich holatini tekshirish yoki test tugagandan so'ng tranzaktsion tasdiqlashning kutilgan xatti-harakatini tekshirish (agar test tranzaktsiyani tasdiqlashga sozlangan bo'lsa). TransactionalTestExecutionListener bunday stsenariylar uchun @BeforeTransaction va @AfterTransactio.n annotatsiyalarini qo'llab-quvvatlaydi. Siz ushbu annotatsiyalardan birini test sinfidagi har qanday void metodiga yoki test interfeysidagi har qanday sukut bo'yicha void metodiga belgilashingiz mumkin, va eshituvchi TransactionalTestExecutionListener sizning metodingiz "tranzaktsiyadan oldin" yoki "tranzaktsiyadan keyin" kerakli vaqtda ishga tushishini ta'minlaydi.

Har qanday "oldin" metodlar (masalan, JUnit Jupiter'dan @BeforeEach bilan belgilangan metodlar) va har qanday "keyin" metodlar (masalan, JUnit Jupiter'dan @AfterEach bilan belgilangan metodlar) testlar orqali boshqariladigan transaktsiya doirasida bajariladi. Shuningdek, @BeforeTransaction yoki @AfterTransaction bilan belgilangan metodlar transaktsiya doirasida bajarilishi uchun sozlanmagan test metodlari uchun bajarilmaydi.

Transaktsiyalar menejerini sozlash

TransactionalTestExecutionListener test uchun Spring ichidagi ApplicationContextda PlatformTransactionManager binini aniqlashini kutadi. Agar ApplicationContext testida bir nechta PlatformTransactionManager mavjud bo'lsa, siz "@Transactional("myTxMgr")" yoki "@Transactional(transactionManager = "myTxMgr")" annotatsiyasini ko'rsatish orqali kvalifikator e'lon qilishingiz yoki TransactionManagementConfigurerni @Configuration annotatsiyasi bilan belgilangan sinf orqali amalga oshirishi mumkin. Ko'proq ma'lumot uchun test ApplicationContextda transaktsiyalar menejerini topish uchun ishlatiladigan algoritmga ko'proq ma'lumot olish uchun javadoc of TestContextTransactionUtils.retrieveTransactionManager()ga qarang.

Barcha transaktsiyaga aloqador annotatsiyalarni ko'rsatish

Quyidagi misolda JUnit Jupiter asosida tasavvuriy integratsion test stsenariysi ko'rsatilgan, bunda transaktsiyaga aloqador barcha annotatsiyalar ajratilgan. Bu misol eng yaxshi amaliyotni emas, balki ushbu annotatsiyalardan qanday foydalanishni ko'rsatish vazifasini bajaradi. Qo'shimcha ma'lumot va sozlamalarni ko'rsatish uchun annotatsiyalarni qo'llab-quvvatlashga bog'ishlangan bo'limga qarang. Transaktsiyalarni boshqarish uchun @Sql annotatsiyasi yorlig'i bo'limi @Sql annotatsiyasini ishlatish orqali sukut bo'yicha transaktsiyani qaytarishni amalga oshirish uchun deklarativ SQL ssenariyini ishlatish bo'yicha qo'shimcha misolni o'z ichiga oladi. Quyidagi misolda muhim annotatsiyalar ko'rsatilgan:

Java
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // Tranzaktsiya boshlanishidan oldin boshlang'ich holatni tekshirish mantiqi
    }
    @BeforeEach
    void setUpTestDataWithinTransaction() {
        // Tranzaktsiya doirasida test ma'lumotlarini o'rnatamiz
    }
    @Test
    // Sinf darajasida @Commit annotatsiyasini sozlashni bekor qiladi
    @Rollback
    void modifyDatabaseWithinTransaction() {
        // Test ma'lumotlaridan foydalanadigan va ma'lumotlar bazasi holatini o'zgartiradigan mantiq
    }
    @AfterEach
    void tearDownWithinTransaction() {
        // Tranzaktsiya ichida "buzg'unchilik (tear down)" mantiqini ishga tushiramiz
    }
    @AfterTransaction
    void verifyFinalDatabaseState() {
        // Tranzaktsiya qaytarilishidan keyin yakuniy holatni tekshirish mantiqi
    }
}
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
    @BeforeTransaction
    fun verifyInitialDatabaseState() {
        // Tranzaktsiya boshlanishidan oldin boshlang'ich holatni tekshirish mantiqi
    }
    @BeforeEach
    fun setUpTestDataWithinTransaction() {
        // Tranzaktsiya doirasida test ma'lumotlarini o'rnatamiz
    }
    @Test
    // Sinf darajasida @Commit annotatsiyasini sozlashni bekor qiladi
    @Rollback
    fun modifyDatabaseWithinTransaction() {
        // Test ma'lumotlaridan foydalanadigan va ma'lumotlar bazasi holatini o'zgartiradigan mantiq
    }
    @AfterEach
    fun tearDownWithinTransaction() {
        // Tranzaktsiya ichida "buzg'unchilik (tear down)" mantiqini ishga tushiramiz
    }
    @AfterTransaction
    fun verifyFinalDatabaseState() {
        // Tranzaktsiya qaytarilishidan keyin yakuniy holatni tekshirish mantiqi
    }
}
ORM-kodini testlashda noto'g'ri natijalardan qochish

Ilovangiz Hibernate sessiyasining yoki JPA doimiylik kontekstining holatini boshqaradigan kodini test qilganingizda, ushbu kodni bajarayotgan test metodlarida asosiy ish birligini qaytaring. Asosiy ish birligini qaytarmaslik noto'g'ri natijalarga olib kelishi mumkin: Testingiz o'tadi, ammo real ishlab chiqarish muhitida xuddi shu kod istisnolarni keltirib chiqaradi. Bu xotirada ish birligini saqlaydigan har qanday ORM tizimiga tegishli ekanligini yodda tuting. Quyidagi Hibernate asosidagi test misolida, bir metod noto'g'ri natijani namoyon qiladi, boshqa metod esa sessiya flush natijasi bilan to'g'ri ochiladi:

Java
// ...
@Autowired
SessionFactory sessionFactory;
@Transactional
@Test // Kutgan istisno yo'q!
public void falsePositive() {
    updateEntityInHibernateSession();
    // Noto'g'ri natija: istisno Hibernate sessiyasi
    // tugatgandan keyin (ya'ni ishlab chiqarish kodida) vujudga keladi
}
@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
    sessionFactory.getCurrentSession().flush();
}
// ...
Kotlin
// ...
@Autowired
lateinit var sessionFactory: SessionFactory
@Transactional
@Test // Kutgan istisno yo'q!
fun falsePositive() {
    updateEntityInHibernateSession()
    // Noto'g'ri natija: istisno Hibernate sessiyasi
    // tugatgandan keyin (ya'ni ishlab chiqarish kodida) vujudga keladi
}
@Transactional
@Test(expected = ...)
fun updateWithSessionFlush() {
    updateEntityInHibernateSession()
    // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
    sessionFactory.getCurrentSession().flush()
}
// ...

Quyidagi misolda JPA uchun mos metodlar ko'rsatilgan:

Java
// ...
@PersistenceContext
EntityManager entityManager;
@Transactional
@Test // Kutgan istisno yo'q!
public void falsePositive() {
    updateEntityInJpaPersistenceContext();
    // Noto'g'ri natija: istisno
    // JPA'dan EntityManager tugatgandan keyin
    // (ya'ni ishlab chiqarish kodida) vujudga keladi
}
@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
    entityManager.flush();
}
// ...
Kotlin
// ...
@PersistenceContext
lateinit var entityManager:EntityManager
@Transactional
@Test // Kutgan istisno yo'q!
fun falsePositive() {
    updateEntityInJpaPersistenceContext()
    // Noto'g'ri natija: istisno
    // JPA'dan EntityManager tugatgandan keyin
    // (ya'ni ishlab chiqarish kodida) vujudga keladi
}
@Transactional
@Test(expected = ...)
void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext()
    // Test davomida noto'g'ri ishlashlardan qochish uchun qo'lda qaytarish kerak
    entityManager.flush()
}
// ...
ORM-entity life-sikl callbacklarini testlash

ORM-kodini testlashda noto'g'ri natijalardan qochish haqida eslatmadagi kabi, agar ilovangiz entity life-sikl callbacklari (entity eshituvchilari sifatida ham tanilgan) ni ishlatsa, ushbu kodni bajarayotgan test metodlarida asosiy ish birligini qaytaring. Asosiy ish birligini qaytarmaslik yoki tozalamaslik muayyan life-sikl callbacklari chaqirilmasligiga olib kelishi mumkin.

Masalan, JPAni ishlatganda, @PostPersist, @PreUpdate va @PostUpdate annotatsiyalari bilan callbacklar saqlash yoki yangilashdan keyin entityManager.flush() chaqirilmasa o'tkazilmaydi. Xuddi shunday, agar entitet allaqachon joriy ish birligiga bog'langan bo'lsa (joriy doimiylik konteksti bilan bog'langan), entitetni qayta yuklashga urinish @PostLoad annotatsiyasini chaqirmaydi, agar entitetni qayta yuklashga urinishdan oldin entityManager.clear() chaqirilmasa.

Quyidagi misolda EntityManagerni qaytarish ko'rsatiladi, @PostPersist annotatsiyasi callbackiga ega bo'lishi uchun. Misolda ishlatilgan Person entiteti uchun @PostPersist annotatsiyasi bilan callback metodi bilan entity eshituvchisi ro'yxatdan o'tgan bo'lgan.

Java
// ...
@Autowired
JpaPersonRepository repo;
@PersistenceContext
EntityManager entityManager;
@Transactional
@Test
void savePerson() {
    // EntityManager#persist(...) @PrePersist'ga olib keladi, lekin @PostPersist emas
    repo.save(new Person("Jane"));
    // @PostPersist annotatsiyasi bilan callbackni amalga oshirish uchun qo'lda qaytarish kerak
    entityManager.flush();
    // @PostPersist annotatsiyasi bilan callbackni ishlatadigan test kodi
    // chaqirildi...
}
// ...
Kotlin
// ...
@Autowired
lateinit var repo: JpaPersonRepository
@PersistenceContext
lateinit var entityManager: EntityManager
@Transactional
@Test
fun savePerson() {
    // EntityManager#persist(...) @PrePersist'ga olib keladi, lekin @PostPersist emas
    repo.save(Person("Jane"))
    // @PostPersist annotatsiyasi bilan callbackni amalga oshirish uchun qo'lda qaytarish kerak
    entityManager.flush()
    // @PostPersist annotatsiyasi bilan callbackni ishlatadigan test kodi
    // chaqirildi...
}
// ...

Spring Framework test to'plamida barcha JPA life-sikl callbacklarini ishlatib operatsion misollar uchun JpaEntityListenerTests ga qarang.

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION