【Java・SpringBoot】明示的トランザクション処理（SpringBootアプリケーション実践編21）

ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
前回は、トランザクションの基本的な処理である宣言的トランザクション実装したので、今回は**明示的トランザクション**を学びます^^
構成は前回/これまでの記事を参考にしてください

⭐️前回の記事
[Java・SpringBoot】宣言的トランザクション処理（SpringBootアプリケーション実践編20）](https://qiita.com/suema0331/items/2dee56f9de5b82a2ad40)

#明示的トランザクション
* 実践では**宣言的トランザクション**を使う
* Springが用意するトランザクションでは対応できない場合に、**明示的トランザクション**を使う

###明示的トランザクションの使い方
* **PlatformTransactionManagerクラス**
    * トランザクションを開始、コミットなどをするクラス

```java
PlatformTransactionManager txManager;
//トランザクション開始
TransactionStatus status = txManager.getTransaction(def);
//コミット
txManager.commit(status);
```
* **DefaultTransactionDefinitionクラス**
    * トランザクションの設定をするクラス

```java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();

//トランザクションの名前を設定
def.setName("UpdateUser");
//読取専用ではない設定
def.setReadOnly(false); 
//トランザクションの伝播レベルをREQUIREDに設定
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
```
###明示的トランザクション実践

```UserService.java
@Service
public class UserService {
//中略(全文は下記参考)
    @Autowired
    PlatformTransactionManager txManager;

//中略(全文は下記参考)
    /**
     * １件更新用メソッド.
     */
    public boolean updateOne(User user) throws DataAccessException {
        //インスタンス生成
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();

        //設定
        def.setName("UpdateUser");
        def.setReadOnly(false);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        //トランザクション開始
        TransactionStatus status = txManager.getTransaction(def);

        // 判定用変数
        boolean result = false;
        try {
            // １件更新
            int rowNumber = dao.updateOne(user);

            if (rowNumber > 0) {
                // update成功
                result = true;
            }
        }catch (Exception e){
            txManager.rollback(status);
            throw new DataAccessException("ERROR Update",e) {};
        }
        //コミット
        txManager.commit(status);

        return result;
    }
//中略(全文は下記参考)
}
```

#SpringBootを起動してホーム画面確認！
* http://localhost:8080/home
* ユーザ一覧からユーザ詳細画面に移り、ユーザー名更新
* "Update Oyakata Neko"に名前を更新しても、ユーザ名が変わっていない（更新失敗）、トランザクションがロールバックできていることがわかりました〜〜！^^
* 明示的トランザクションは、AOP内でサービスクラスのメソッドを実行する前後で、トランザクションの開始/コミットをすればOK！


![スクリーンショット 2020-12-05 12.12.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/629c70cd-95a4-cf2c-9bde-3cdde9471e61.png)

![スクリーンショット 2020-12-05 12.10.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/0fb5cadd-fb51-274d-7a74-d571a97adebf.png)


#(参考)コード全文

```UserService.java
package com.example.demo.login.domain.service;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.repository.UserDao;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class UserService {
    @Autowired
    @Qualifier("UserDaoJdbcImpl")

    UserDao dao;
    @Autowired
    PlatformTransactionManager txManager;

    /**
     * insert用メソッド.
     */
    public boolean insert(User user) {
        // insert実行
        int rowNumber = dao.insertOne(user);
        // 判定用変数
        boolean result = false;

        if (rowNumber > 0) {
            // insert成功
            result = true;
        }
        return result;
    }

    /**
     * カウント用メソッド.
     */
    public int count() {
        return dao.count();
    }

    /**
     * 全件取得用メソッド.
     */
    public List<User> selectMany() {
        // 全件取得
        return dao.selectMany();
    }

    /**
     * １件取得用メソッド.
     */
    public User selectOne(String userId) {
        // selectOne実行
        return dao.selectOne(userId);
    }

    /**
     * １件更新用メソッド.
     */
    public boolean updateOne(User user) throws DataAccessException {
        //インスタンス生成
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();

        //設定
        def.setName("UpdateUser");
        def.setReadOnly(false);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        //トランザクション開始
        TransactionStatus status = txManager.getTransaction(def);

        // 判定用変数
        boolean result = false;
        try {
            // １件更新
            int rowNumber = dao.updateOne(user);

            if (rowNumber > 0) {
                // update成功
                result = true;
            }
        }catch (Exception e){
            txManager.rollback(status);
            throw new DataAccessException("ERROR Update",e) {};
        }
        //コミット
        txManager.commit(status);

        return result;
    }

    /**
     * １件削除用メソッド.
     */
    public boolean deleteOne(String userId) {

        // １件削除
        int rowNumber = dao.deleteOne(userId);

        // 判定用変数
        boolean result = false;

        if (rowNumber > 0) {
            // delete成功
            result = true;
        }
        return result;
    }
    // ユーザー一覧をCSV出力する.
    /**
     * @throws DataAccessException
     */
    public void userCsvOut() throws DataAccessException {
        // CSV出力
        dao.userCsvOut();
    }

    /**
     * サーバーに保存されているファイルを取得して、byte配列に変換する.
     */
    public byte[] getFile(String fileName) throws IOException {

        // ファイルシステム（デフォルト）の取得
        FileSystem fs = FileSystems.getDefault();

        // ファイル取得
        Path p = fs.getPath(fileName);

        // ファイルをbyte配列に変換
        byte[] bytes = Files.readAllBytes(p);

        return bytes;
    }
}
```