スプリングブート例外処理の例


このチュートリアルでは、使用するスプリングブート例外処理の例を見ていきます@ControllerAdvice and @ExceptionHandler .
RESTful API例外を処理することもできます@RestControllerAdvice , 親愛なる訪問
@RestControllerAdvice example in Spring Boot
この記事は本来はBezkoder .
API APIの残りの処理
我々は、CRUD操作とFinderメソッドのためのRESTコントローラを作成しました.
コードを見てみましょう.
以下の手順を実行します.
  • Spring Boot Data JPA + H2 CRUD example
  • Spring Boot Data JPA + MySQL CRUD example
  • Spring Boot Data JPA + PostgreSQL CRUD example
  • Spring Boot Data JPA + SQL Server
  • Spring Boot Data JPA + Oracle example
  • Spring Boot MongoDB CRUD example

  • Spring Boot Cassandra CRUD example )
  • @RestController
    public class TutorialController {
    
      @Autowired
      TutorialRepository tutorialRepository;
    
      @GetMapping("/tutorials")
      public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
        try {
          ...
          return new ResponseEntity<>(tutorials, HttpStatus.OK);
        } catch (Exception e) {
          return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
      }
    
      @GetMapping("/tutorials/{id}")
      public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
        Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
    
        if (tutorialData.isPresent()) {
          return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
        } else {
          return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
      }
    
      @PutMapping("/tutorials/{id}")
      public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
        Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
    
        if (tutorialData.isPresent()) {
          ...
          return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
        } else {
          return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
      }
    
      ...
    
      @DeleteMapping("/tutorials/{id}")
      public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
        try {
          tutorialRepository.deleteById(id);
          return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (Exception e) {
          return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
      }
    
      @DeleteMapping("/tutorials")
      public ResponseEntity<HttpStatus> deleteAllTutorials() {
        // try and catch
      }
    
      @GetMapping("/tutorials/published")
      public ResponseEntity<List<Tutorial>> findByPublished() {
        // try and catch
      }
    }
    
    同じような例外に対してtry/catchを何度も使っていることがわかります.
    それらを単純に保つ方法はありますか、エラー応答メッセージをスマートに、そして、柔軟性を付けるどんな方法でもありますか?
    今問題を解決しましょう.
    春のコントローラアドバイスによる例外処理
    Springグローバルな例外ハンドラーによる例外処理をサポートします.@ExceptionHandler ) コントローラのアドバイス@ControllerAdvice ). これは、作るメカニズムを可能にするResponseEntity タイプの安全性と柔軟性@ExceptionHandler :
    @ControllerAdvice
    public class ControllerExceptionHandler {
    
      @ExceptionHandler(value = {ResourceNotFoundException.class, CertainException.class})
      public ResponseEntity<ErrorMessage> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorMessage message = new ErrorMessage(
            status,
            date,
            ex.getMessage(),
            description);
    
        return new ResponseEntity<ErrorMessage>(message, HttpStatus.NOT_FOUND);
      }
    }
    
    The @ControllerAdvice 注釈は@Component 注釈は、それがCLASSPATHのスキャンを介して自動検出されるように.コントローラアドバイスは、我々のコントローラのロジックを囲む一種のインターセプターで、私たちが彼らにいくつかの共通のロジックを適用するのを許します.
    そのメソッド@ExceptionHandler ) を共有する@Controller コンポーネントは、例外をキャプチャし、HTTP応答にそれらを翻訳します.The @ExceptionHandler 注釈は、どのタイプの例外を処理するかを示します.The exception インスタンスとrequest メソッド引数を通して注入されます.
    二つの注釈を一緒に使うことで、
  • ステータスコードと共にレスポンスの本体を制御する
  • 同じメソッドでいくつかの例外を扱います
  • 応答ステータス
    上の例では@ControllerAdvice REST Webサービスとリターンの場合ResponseEntity オブジェクトを追加します.
    春も提供@ResponseBody 注釈を返すと、オブジェクトが自動的にシリアル化され、JSONに渡されますHttpResponse オブジェクト.この方法は必要ありませんResponseEntity しかし、使用する必要があります@ResponseStatus HTTPステータスコードを設定します.
    
    @ControllerAdvice
    @ResponseBody
    public class ControllerExceptionHandler {
    
      @ExceptionHandler(ResourceNotFoundException.class)
      @ResponseStatus(value = HttpStatus.NOT_FOUND)
      public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorMessage message = new ErrorMessage(...);
        return message;
      }
    }
    
    セットアップスプリングブートプロジェクト
    次のいずれかの手順でソースコードを取得するか、または次のいずれかの手順で取得できます.
  • Spring Boot Data JPA + H2 CRUD example
  • Spring Boot Data JPA + MySQL CRUD example
  • Spring Boot Data JPA + PostgreSQL CRUD example
  • Spring Boot Data JPA + SQL Server
  • Spring Boot Data JPA + Oracle example
  • Spring Boot MongoDB CRUD example
  • Spring Boot Cassandra CRUD example
  • Springプロジェクトには、いくつかの変更を追加する必要があります.

    または、このチュートリアルの最後に新しいGithubソースコードを取得できます.
    最終的なプロジェクト構造は次のようになります.

    エラー応答メッセージ
    私たちは、春のブートによって提供されるデフォルトのエラー応答を使用する代わりに、私たち自身のメッセージ応答構造を作成したいです.
    特定のエラー応答構造を定義しましょう.
    例外/エラーメッセージ.ジャバ
    package com.bezkoder.spring.exhandling.exception;
    
    import java.util.Date;
    
    public class ErrorMessage {
      private int statusCode;
      private Date timestamp;
      private String message;
      private String description;
    
      public ErrorMessage(int statusCode, Date timestamp, String message, String description) {
        this.statusCode = statusCode;
        this.timestamp = timestamp;
        this.message = message;
        this.description = description;
      }
    
      public int getStatusCode() {
        return statusCode;
      }
    
      public Date getTimestamp() {
        return timestamp;
      }
    
      public String getMessage() {
        return message;
      }
    
      public String getDescription() {
        return description;
      }
    }
    
    カスタム例外の作成
    私たちは、スプリングブートコントローラで見つけられないリソースの例外をスローします.
    レットクリエイトResourceNotFoundException 拡張クラスRuntimeException .
    例外/ResourceObjecdException.ジャバ
    package com.bezkoder.spring.exhandling.exception;
    
    public class ResourceNotFoundException extends RuntimeException {
    
      private static final long serialVersionUID = 1L;
    
      public ResourceNotFoundException(String msg) {
        super(msg);
      }
    }
    
    コントローラのアドバイスを
    今私たちは特別なクラスを作成するつもりです@ControllerAdvice 注釈.このクラスは、特定の例外を処理しますResoureNotFoundException ) つの場所だけでグローバルな例外.
    例外/ControllerExceptionハンドラ.ジャバ
    package com.bezkoder.spring.exhandling.exception;
    
    import java.util.Date;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.context.request.WebRequest;
    
    import com.bezkoder.spring.exhandling.exception.ErrorMessage;
    import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;
    
    @ControllerAdvice
    public class ControllerExceptionHandler {
    
      @ExceptionHandler(ResourceNotFoundException.class)
      public ResponseEntity<ErrorMessage> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorMessage message = new ErrorMessage(
            HttpStatus.NOT_FOUND.value(),
            new Date(),
            ex.getMessage(),
            request.getDescription(false));
    
        return new ResponseEntity<ErrorMessage>(message, HttpStatus.NOT_FOUND);
      }
    
      @ExceptionHandler(Exception.class)
      public ResponseEntity<ErrorMessage> globalExceptionHandler(Exception ex, WebRequest request) {
        ErrorMessage message = new ErrorMessage(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            new Date(),
            ex.getMessage(),
            request.getDescription(false));
    
        return new ResponseEntity<ErrorMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);
      }
    }
    
    を使用してコントローラを変更
    私たちの残りのコントローラは、今/tryブロックをキャッチしていないとスローされますResourceNotFoundException ここで応答メッセージにNoCount発見通知を送りたい.
    コントローラ/チュートリアルコントローラ.ジャバ
    package com.bezkoder.spring.exhandling.controller;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;
    import com.bezkoder.spring.exhandling.model.Tutorial;
    import com.bezkoder.spring.exhandling.repository.TutorialRepository;
    
    @CrossOrigin(origins = "http://localhost:8081")
    @RestController
    @RequestMapping("/api")
    public class TutorialController {
    
      @Autowired
      TutorialRepository tutorialRepository;
    
      @GetMapping("/tutorials")
      public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
        List<Tutorial> tutorials = new ArrayList<Tutorial>();
    
        if (title == null)
          tutorialRepository.findAll().forEach(tutorials::add);
        else
          tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
    
        if (tutorials.isEmpty()) {
          return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
    
        return new ResponseEntity<>(tutorials, HttpStatus.OK);
      }
    
      @GetMapping("/tutorials/{id}")
      public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
        Tutorial tutorial = tutorialRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));
    
        return new ResponseEntity<>(tutorial, HttpStatus.OK);
      }
    
      @PostMapping("/tutorials")
      public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
        Tutorial _tutorial = tutorialRepository.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
        return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
      }
    
      @PutMapping("/tutorials/{id}")
      public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
        Tutorial _tutorial = tutorialRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));
    
        _tutorial.setTitle(tutorial.getTitle());
        _tutorial.setDescription(tutorial.getDescription());
        _tutorial.setPublished(tutorial.isPublished());
    
        return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
      }
    
      @DeleteMapping("/tutorials/{id}")
      public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
        tutorialRepository.deleteById(id);
    
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }
    
      @DeleteMapping("/tutorials")
      public ResponseEntity<HttpStatus> deleteAllTutorials() {
        tutorialRepository.deleteAll();
    
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }
    
      @GetMapping("/tutorials/published")
      public ResponseEntity<List<Tutorial>> findByPublished() {
        List<Tutorial> tutorials = tutorialRepository.findByPublished(true);
    
        if (tutorials.isEmpty()) {
          return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
    
        return new ResponseEntity<>(tutorials, HttpStatus.OK);
      }
    
    }
    
    実行とテスト
    CRUD REST APIと例外処理の実行を終了します.
    コマンドを実行して起動するmvn spring-boot:run .
  • 存在しないチュートリアルを取得する
  • 存在しないチュートリアルを更新します
  • 間違ったフィールドでチュートリアルを作成します
  • 存在しないチュートリアルを削除します

  • 結論
    今日、我々はスプリングブートレストAPIのための例外処理クラスを構築しました@ControllerAdvice and @ExceptionHandler . これで、独自のカスタム例外ハンドラークラスを作成したり、簡単に1つの場所でグローバル例外を処理できます.
    このスプリングプロジェクトにページネーションを追加する場合は、次の手順を実行できます.
    Spring Boot Pagination & Filter example | Spring JPA, Pageable
    複数のフィールドを並べ替えたり並べたりするには、次の手順に従います.
    Spring Data JPA Sort/Order by multiple Columns | Spring Boot
    またはJPAリポジトリの単位テストを書く方法
    Spring Boot Unit Test for JPA Repositiory with @DataJpaTest
    また、AWS(無料で)でこの春のブートアプリを展開する方法を知ることができますthis tutorial .
    またはDocker Compose: Spring Boot and MySQL example
    ハッピーラーニング!また会いましょう.
    更なる読書
    関連記事:
  • Spring Boot, Spring Data JPA – Rest CRUD API example
  • Spring Boot Pagination & Filter example
  • Spring Boot Sort/Order by multiple Columns
  • その他の実践
  • Spring Boot Multipart File upload example
  • Spring Boot Token based Authentication with Spring Security & JWT
  • ソースコード
    このチュートリアルの完全なソースコードを見つけることができますGithub .