跳到主要內容

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());
    }
}

留言

這個網誌中的熱門文章

Google Compute Engine‎ - AccessDeniedExceptions 403

原因 打算從 instance 打包 logs 到 google cloud storage 發生了 AccessDeniedException: 403 Insufficient OAuth2 scope to perform this operation. , 看起來是 instance 沒有 storage 權限 解決 Reference: https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes 重新設定 service account 權限 instance 上內建有 gcloud , 就直接用現有的工具查詢一下 instance 的 account. $ gsutil info 或者在本機直接 gcloud compute instances describe INSTANCE_NAMES Account: [alpha-number-compute@developer.gserviceaccount.com] Project: [our-project-name] 會看到 instance 的一些狀態, 接下來就簡單多了, 按照下列的說明, 要先 stop instance, 更改 storage scope 再重新 start 。 To change an instance's service account and access scopes, the instance must be temporarily stopped. To stop your instance, read the documentation for Stopping an instance. After changing the service account or access scopes, remember to restart the instance. # Stop Instance gcloud compute instances stop INSTANCE_NAMES # 設定 storage scope 為 full (Read, Write) gcloud co...

Spring-boot Thymeleaf Html5 SAXParseException 解析錯誤

thymeleaf 解析 html5 出錯 <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>SB Admin - Start Bootstrap Template</title> <!-- Bootstrap core CSS--> <link href="../static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom fonts for this template--> <link href="../static/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <!-- Page level plugin CSS--> <link href="../static/vendor/datatables/dataTables.bootstrap4.css" rel="stylesheet"> <!-- Custom styles for this template--> <li...

Spring boot Https 設定

1. 產生 SSL certificate keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 Enter keystore password: Re-enter new password: What is your first and last name? [Unknown]: jerry What is the name of your organizational unit? [Unknown]: td What is the name of your organization? [Unknown]: com What is the name of your City or Locality? [Unknown]: taipei What is the name of your State or Province? [Unknown]: taiwan What is the two-letter country code for this unit? [Unknown]: tw Is CN=jarvis, OU=td, O=urad, L=taipei, ST=taiwan, C=tw correct? [no]: yes 這個 certificate 是 self-signed certificate 沒有經過第三方認證, 所以沒有公信力, 正式上線會在瀏覽器看到 連線不被信任 要有公信力的 certificate 最簡單的是 Lets Encrypt , 其他就是花一些錢找簽發 certificate 的組織 2. Enable HTTPS in Spring Boot Spring Boot 內建的 tomcat 預設 http 是 8080, Spring Boot 可以設定 http 跟 https, 但沒辦法同時存在這兩個設定, 如果要同時存在兩種 connection, 建議依照 文件 建議設定 https, 再透過 programmatically 去設定 http 會比較容易。 相關範例可以參考 : https://...