Spring Data JPA Transaction

Entity



@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "messenger")
@IdClass(MessengerPrimaryKey.class)
public class Messenger implements Serializable {
    @Id
    @Column(name = "user_id", length = 64, nullable = false)
    private String userId;
    @Id
    @Column(name = "psid", length = 64, nullable = false)
    private String psId;

    @Column(name = "page_id", length = 64, nullable = false)
    private String pageId;

}

PrimaryKey


@Data
@NoArgsConstructor
@AllArgsConstructor
public class MessengerPrimaryKey implements Serializable {
    private String userId;
    private String psId;
}

Test1

transactionalTest1 是基本的 transactional, 在 transactionalTest1 結束後會做一個 commit

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import tw.com.urad.entities.Messenger;

import javax.inject.Inject;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MessengerRepositoryTest {

    @Inject
    private MessengerRepository repository;

    @Test
    @Transactional
    public void transactionalTest1() {
        final String userId = "userId";
        final String psId = "psId";
        final String pageId = "pageId";
        Messenger messenger = new Messenger(userId, psId, pageId);

        Messenger saved = repository.save(messenger);
        
        // Messenger(userId=userId, psId=psId, pageId=pageId)
        System.out.println(saved);

        // modified saved entity
        saved.setPageId("pageId2");

        // [Messenger(userId=userId, psId=psId, pageId=pageId)]
        System.out.println(repository.findAll());
    }
}

Test2

翻了一下 文件 裡面有一段這樣的敘述:

The readOnly flag instead is propagated as hint to the underlying JDBC driver for performance optimizations. Furthermore, Spring will perform some optimizations on the underlying JPA provider. E.g. when used with Hibernate the flush mode is set to NEVER when you configure a transaction as readOnly which causes Hibernate to skip dirty checks (a noticeable improvement on large object trees).

當 transactional 是 readOnly 的狀態, 整個 transactions 基本上就只有 read 的動作可以執行, 對有做讀寫分離的資料庫是一種不錯的策略。然後還會幫你優化底層的 JDBC Driver 的處理效能, 怎麼優化呢? 是因為他會跳過 Hibernate 的 dirty checks, 在資料庫資料量龐大的時候特別顯著。

dirty checks 是為了避免一些 "異常" 的資料, 指的應該是 lost update, dirty read, phantom read 這些意外, 我猜這個 "dirty checks" 指的是 persistence 資料的正確性檢查!?

參考資料:http://www.importnew.com/12314.html

至於為何 System.out.println(saved); 仍會有資料被 print, 是因為 save 回傳的是 entity, 這個 entity 是當初要寫入資料庫的那個, 並非在 transactional 階段的資料。

Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely.

參考資料:https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html


@RunWith(SpringRunner.class)
@SpringBootTest
public class MessengerRepositoryTest {

    @Inject
    private MessengerRepository repository;

    @Test
    @Transactional(readOnly = true)
    public void transactionalTest2() {
        final String userId = "userId";
        final String psId = "psId";
        final String pageId = "pageId";
        Messenger messenger = new Messenger(userId, psId, pageId);

        Messenger saved = repository.save(messenger);
        
        // Messenger(userId=userId, psId=psId, pageId=pageId)
        System.out.println(saved);

        // modified saved entity
        saved.setPageId("pageId2");

        // nothing
        System.out.println(repository.findAll());
    }
}


Test3

我在這個測試多增加了 @FixMethodOrder(MethodSorters.DEFAULT) 讓測試按照 alpha-number 的順序執行, 拿掉 readOnly, 理論上 void transactionalTest2() 應該會有資料, 但實際上並沒有。

If your test is @Transactional, it will rollback the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, HTTP client and server will run in separate threads, thus separate transactions. Any transaction initiated on the server won’t rollback in this case.

參考資料:https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications

就是說在 @SpringBootTest 的環境下, 為了避免弄髒真實的資料庫(拿真實的資料庫來做測試心臟也是很強大啊), 可以透過 @Transactional 在測試結束後 rollback。



@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.DEFAULT)
public class MessengerRepositoryTest {

    @Inject
    private MessengerRepository repository;

    @Test
    @Transactional
    public void transactionalTest1() {
        final String userId = "userId";
        final String psId = "psId";
        final String pageId = "pageId";
        Messenger messenger = new Messenger(userId, psId, pageId);

        Messenger saved = repository.save(messenger);
        System.out.println("===");
        System.out.println(saved);

        saved.setPageId("pageId2");
        System.out.println("===");
        System.out.println(repository.findAll());
    }

    @Test
    @Transactional
    public void transactionalTest2() {
        // print nothing
        System.out.println(repository.findAll());
    }
}

沒有留言:

張貼留言