جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


در Spring با استفاده از Tranctional@ میتوانستیم براحتی تراکنش ها را کنترل کنیم و درگیر جزییات پیاده سازی و Transaction Management نشویم . ولی همیشه استفاده از Transctional@ بهترین روش نیست . در این بخش خواهیم دید که چه راهکارهایی برای کنترل بیشتر روی Transaction مانند TransactionTemplate داریم و مزایای آن چیست 




ابتدا ببینیم استفاده از Transactional@ چه مشکلاتی را میتواند ایجاد کند :


فرض کنید کدی داریم که دو I/O مختلف را استفاده کرده است :


@Transactional
public void initialPayment(PaymentRequest request) {
    savePaymentRequest(request); // DB
    callThePaymentProviderApi(request); // API
    updatePaymentState(request); // DB
    saveHistoryForAuditing(request); // DB
}


در کد باالا چند Database Call در کنار یک REST API Call سنگین داریم که در نگاه اول ممکن است این کد منطقی بنظر آید و از یک EntityManager برای کل متد Transactional@ بصورت اتمیک استفاده شود ولی اگر REST Call زیاد طول بکشد ممکن است ما DB Connection را از دست بدهیم و در این لحظه مشکل نمایان خواهد شد


وقتی متد initialPayment فراخوانی شود :


- یک EntityManager توسط کنترل کننده Transaction ایجاد میشود و یکی از اتصالات Connection Pool را استفاده میکند 

- بعد از اولین ارتباط با دیتابیس در حالی که Connection را حفظ کرده است، API خارجی فراخوانی میشود 

- و در نهایت از آن Connection اولیه میخواهد برای مابقی Database Call ها استفاده کند 

- اگر کار REST API Call زیاد طول بکشد ممکن است Connection را از دست بدهیم 



استفاده کردن I/O دیتابیس با دیگر I/O ها یک Bad Smash است و باید از یکدیگر تفکیک شوند و اگر به هر دلیلی نتوانیم آنها را از هم جدا کنیم میتوانیم با استفاده از Spring API برای مدیریت دستی Transaction ها استفاده کنیم 






استفاده از TransactionTemplate :



با استفاده از TransactionTemplate مجموعه ای از Call-Back API هایی خواهیم داشت که برای مدیریت دستی Transaction میتوانیم از آن استفاده کنیم

برای استفاد از TransacionTemplate میبایست آنرا با PlatformTransactionManager فراهم کنیم :


// test annotations
class ManualTransactionIntegrationTest {
 
    @Autowired
    private PlatformTransactionManager transactionManager;
 
    private TransactionTemplate transactionTemplate;
 
    @BeforeEach
    void setUp() {
        transactionTemplate = new TransactionTemplate(transactionManager);
    }
 
    // omitted
}


PlatformTransactionManager کمک میکند تا Template ما Transaction ها را commit, rollback و create کند



وقتی از Spring Boot استفاده میکنیم Bean ای از نوع PlatformTransactionManager بصورت اتوماتیک در اختیار ما میگذارد در غیر این صورت باید بصورت دستی نحوه ایجاد آنرا تعیین کنیم 





ایجاد Domain Model :


از این Entity برای مدل کردن جزییات یک پرداخت ساده استفاده میکنیم :


@Entity
public class Payment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private Long amount;
 
    @Column(unique = true)
    private String referenceNumber;
 
    @Enumerated(EnumType.STRING)
    private State state;
 
    // getters and setters
 
    public enum State {
        STARTED, FAILED, SUCCESSFUL
    }
}



همچنین برای تست کردن با استفاده از کتابخانه Testcontainers یک نمونه از PostgreSQL ایجاد میکنم 


تست :

@DataJpaTest
@Testcontainers
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = NONE)
@Transactional(propagation = NOT_SUPPORTED) // we're going to handle transactions manually
public class ManualTransactionIntegrationTest {
 
    @Autowired
    private PlatformTransactionManager transactionManager;
 
    @Autowired
    private EntityManager entityManager;
 
    @Container
    private static PostgreSQLContainer<?> pg = initPostgres();
 
    private TransactionTemplate transactionTemplate;
 
    @BeforeEach
    public void setUp() {
        transactionTemplate = new TransactionTemplate(transactionManager);
    }
 
    // tests
 
    private static PostgreSQLContainer<?> initPostgres() {
        PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:11.1")
                .withDatabaseName("baeldung")
                .withUsername("test")
                .withPassword("test");
        pg.setPortBindings(singletonList("54320:5432"));
 
        return pg;
    }
} 






Transaction هایی که نتیجه ای در بر خواهند داشت :



TransactionTemplate یک متدی بنام execute دارد که کد های داخلیش را در یک Transaction اجرا و در آخر نتیجه را بر میگرداند 


@Test
void givenAPayment_WhenNotDuplicate_ThenShouldCommit() {
    Long id = transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setAmount(1000L);
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);
 
        entityManager.persist(payment);
 
        return payment.getId();
    });
 
    Payment payment = entityManager.find(Payment.class, id);
    assertThat(payment).isNotNull();
}


در کد بالا یک Payment Entity را ذخیره کردیم و در آخر Id تولید شده را دریافت کردیم 


این رویه از اجرا میتواند Atomicity را تضمین کند و اگر یکی از عملیات داخل تراکنش موفقیت آمیز نبود تمامی عملیات rollback خواهد شد 


@Test
void givenTwoPayments_WhenRefIsDuplicate_ThenShouldRollback() {
    try {
        transactionTemplate.execute(status -> {
            Payment first = new Payment();
            first.setAmount(1000L);
            first.setReferenceNumber("Ref-1");
            first.setState(Payment.State.SUCCESSFUL);
 
            Payment second = new Payment();
            second.setAmount(2000L);
            second.setReferenceNumber("Ref-1"); // same reference number
            second.setState(Payment.State.SUCCESSFUL);
 
            entityManager.persist(first); // ok
            entityManager.persist(second); // fails
 
            return "Ref-1";
        });
    } catch (Exception ignored) {}
 
    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).isEmpty();
}


از آنجا که referenceNumber دوم تکراری است دیتابیس آنرا قبول نمیکند و عملیات با شکست روبرو میشود و کل عملیات ها rollback میشوند پس در دیتابیس Payment ای ذخیره نمیگردد 

بدون ترفند بالا برای ایجاد rollback میتوانیم با استفاده از متد ()setRollbackOnly آنرا ایجاد کنیم :


@Test
void givenAPayment_WhenMarkAsRollback_ThenShouldRollback() {
    transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setAmount(1000L);
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);
 
        entityManager.persist(payment);
        status.setRollbackOnly();
 
        return payment.getId();
    });
 
    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).isEmpty();
}







Transaction های بدون نتیجه :


اگر Transaction ما هیچ نتیجه ای برنمیگرداند میتوانیم از TransactionCallbackWithoutResult استفاده کنیم :


@Test
void givenAPayment_WhenNotExpectingAnyResult_ThenShouldCommit() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            Payment payment = new Payment();
            payment.setReferenceNumber("Ref-1");
            payment.setState(Payment.State.SUCCESSFUL);
 
            entityManager.persist(payment);
        }
    });
 
    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).hasSize(1);
}





تنظیمات دلخواه Transaction :


تا اینجا از تنظیمات پیش فرض TransactionTemplate استفاده کردیم و این تنظیمات پیش فرض در بیشتر مواقع به اندازه کافی کاربردی هستند ولی اگر بخواهیم میتوانیم تنظیماتی را تغییر دهیم :


مثلا سطح Isolation را تغییر دهیم :


transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);


تغییر propagation :


transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);


ست کردن Timeout :


transactionTemplate.setTimeout(1000);


Optimize کردن Transaction برای حالت Read-Only :


transactionTemplate.setReadOnly(true);


* توجه داشته باشید که ممکن است چندین نوع تراکنش های مختلف با تنظیمات مختلف نیاز داشته باشیم 






استفاده از PlatformTransactionManager :



علاوه بر TransactionTemplate میتوانیم از low-level API مانند PlatformTransactionManager استفاده کنیم. جالب است بدانیم که TransactionTemplate و Transactional@ هر دو از این API استفاده میکنند 



تنظیمات :


قبل از استفاده از این API باید نحوه ارائه Transaction را مشخص کنیم مثلا میتوانیم timeOut با زمان 3 ثانیه با سطح ایزوله ISOLATION_REPEATABLE_READ ست میکنیم 


DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
definition.setTimeout(3);


همانند TransactionTemplate تنظیمات انرا میتوانی در اینجا داشته باشیم ولی تنها با یک PlatformTransactionManager میتوانیم چندین تنظیمات مختلف داشته باشیم 



بعد از ست کردن تنظمیات Transaction ما میتوانیم Transaction ها را با کدنویسی مدیریت کنیم 



@Test
void givenAPayment_WhenUsingTxManager_ThenShouldCommit() {
  
    // transaction definition
 
    TransactionStatus status = transactionManager.getTransaction(definition);
    try {
        Payment payment = new Payment();
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);
 
        entityManager.persist(payment);
        transactionManager.commit(status);
    } catch (Exception ex) {
        transactionManager.rollback(status);
    }
 
    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).hasSize(1);
}










نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی