Spring BootからJPA処理データベースへ(04)

12121 ワード

04.登録/変更/照会APIの作成


3つのクラスは作成にAPIが必要です
Dto
  • 受信要求データ
    コントローラ
  • API要求
  • トランザクション、ドメイン機能間の秩序化サービス
  • 4-1)登録機能の実現:PostsAPIコントローラ、PostsSaveRequestDto、PostsServiceクラス

  • PostsApiコントローラは、Webパッケージに使用することができ、
  • .
  • PostsSaveRequestDtoはWebです.
  • PostsServiceはサービスです.postsパッケージに
  • を作成
    PostsApiコントローラソースコード
    import com.cutehuman.springboot.service.posts.PostsService;
    import com.cutehuman.springboot.web.dto.PostsSaveRequestDto;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    @RequiredArgsConstructor //선언된 모든 final 필드가 포함된 생성자 생성
    @RestController //컨트롤러가 JSON을 반환하도록 
    public class PostsApiController {
        private final PostsService postsService;
    
        @PostMapping("/api/v1/posts") //POST 요청을 받을 수 있는 API 만들어 줌
        public Long save(@RequestBody PostsSaveRequestDto requestDto){
            return postsService.save(requestDto);
        }
    }
    PostsSaveRequestDtoソースコード
    import com.cutehuman.springboot.domain.posts.Posts;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @Getter //선언된 모든 필드의 get 메소드 생성
    @NoArgsConstructor //기본 생성자 자동 추가
    public class PostsSaveRequestDto {
        private String title;
        private String content;
        private String author;
        
        @Builder //해당 클래스의 빌더 패턴 클래스 생성
        //생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함
        public PostsSaveRequestDto(String title, String content, String author){
            this.title = title;
            this.content = content;
            this.author = author;
        }
    
        public Posts toEntity(){
            return Posts.builder()
                    .title(title)
                    .content(content)
                    .author(author)
                    .build();
        }
    }
    PostsServiceソース
    import com.cutehuman.springboot.domain.posts.PostsRepository;
    import com.cutehuman.springboot.web.dto.PostsSaveRequestDto;
    import lombok.RequiredArgsConstructor;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @RequiredArgsConstructor //선언된 모든 final 필드가 포함된 생성자 생성
    @Service 
    public class PostsService {
        private final PostsRepository postsRepository;
    
        @Transactional
        public Long save(PostsSaveRequestDto requestDto){
            return postsRepository.save(requestDto.toEntity()).getId();
        }
    }
    にスプリングからbeanオブジェクトを取得する方法
  • @Autowired
  • setter
  • ジェネレータ
  • その中で最も推奨される方法は構造関数として注入することである.
    に注意
  • は、要求/応答クラスx
  • としてEntityクラスを絶対的に使用する.
  • リクエスト/レスポンスにDtoクラスを個別に作成して使用
    =>
  • は、データベース・レイヤとビュー・レイヤの役割を完全に分離します.

    4-2)登録機能検証テストコード


    1.WebパッケージにPostsApiControllerTestを作成する
    import com.cutehuman.springboot.domain.posts.Posts;
    import com.cutehuman.springboot.domain.posts.PostsRepository;
    import com.cutehuman.springboot.web.dto.PostsSaveRequestDto;
    import org.junit.After;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.boot.web.server.LocalServerPort;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.List;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class PostsApiControllerTest {
    
        @LocalServerPort
        private int port;
    
        @Autowired
        private TestRestTemplate restTemplate;
    
        @Autowired
        private PostsRepository postsRepository;
    
        @After //메소드 실행 후 Advice 실행
        public void tearDown() throws Exception{
            postsRepository.deleteAll();
        }
    
        @Test
        public void Posts_등록된다() throws Exception{
            //given
            String title = "title";
            String content = "content";
            PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                    .title(title)
                    .content(content)
                    .author("author")
                    .build();
    
            String url = "http://localhost:" + port + "/api/v1/posts";
    
            //when
            ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);
    
            //then
            assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
            assertThat(responseEntity.getBody()).isGreaterThan(0L);
    
            List<Posts> all = postsRepository.findAll();
            assertThat(all.get(0).getTitle()).isEqualTo(title);
            assertThat(all.get(0).getContent()).isEqualTo(content);
        }
    
    }
    @WebMvcTestテスト
  • APIを使用しない理由
    :@WebMvcTestはJPA機能をサポートしていません.
    (コントローラ、コントローラ、機器等の外部可動部品のみ)
  • .
  • JPA機能を一度にテストする場合、@SpringBootTestとTestRestTemplate(
  • )を使用します.
    2.テストの実行
  • WebEnvironment.ランダムポートはRANDOM PORTにより実行される
  • insertクエリーの実行
  • 4-3)修正/照会機能の実現


    PostsApiController
    @RequiredArgsConstructor
    @RestController
    public class PostsApiController {
       
       ...
    
        @PutMapping("/api/v1/posts/{id}")
        public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto){
            return postsService.update(id, requestDto);
        }
    
        @GetMapping("/api/v1/posts/{id}")
        public PostsResponseDto findById(@PathVariable Long id){
            return postsService.findById(id);
        }
    }
    PostsResponseDto
    web.dtoパッケージで作成
    import com.cutehuman.springboot.domain.posts.Posts;
    import lombok.Getter;
    
    @Getter
    public class PostsResponseDto {
       private Long id;
       private String title;
       private String content;
       private String author;
    
       // Entity의 필드 중 일부만 사용하므로
       // 생성자로 Entity를 받아 필드에 값을 넣음
       public PostsResponseDto(Posts entity){
           this.id = entity.getId();
           this.title = entity.getTitle();
           this.content = entity.getContent();
           this.author = entity.getAuthor();
       }
    }
    PostsUpdateRequestDto
    web.dtoパッケージで作成
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @Getter
    @NoArgsConstructor
    public class PostsUpdateRequestDto {
       private String title;
       private String content;
    
       @Builder
       public PostsUpdateRequestDto(String title, String content){
           this.title = title;
           this.content = content;
       }
    }
    Posts
    public class Posts {
      ...
      
      public void update(String title, String content){
              this.title = title;
              this.content = content;
        }
    }
    PostsService
    @RequiredArgsConstructor
    @Service
    public class PostsService {
    
        ...
        
        @Transactional
        public Long update(Long id, PostsUpdateRequestDto requestDto){
            Posts posts = postsRepository.findById(id)
                    .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));
    
            posts.update(requestDto.getTitle(), requestDto.getContent());
    
            return id;
        }
    
        public PostsResponseDto findById (Long id){
            Posts entity = postsRepository.findById(id)
                    .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id="+id));
    
            return new PostsResponseDto(entity);
        }
    }
    ❗update機能ではデータベースへのクエリーは許可されていません
    :JPAの永続性コンテキスト
    永続性コンテキストは、永続的なストレージエンティティの環境であり、論理概念です.JPAでは、エンティティが永続性コンテキストに含まれているかどうかが重要です.
    JPAのエンティティーマネージャがDBからトランザクションにデータをインポートした場合、そのデータは永続性コンテキストのままになります.
    この状態でデータの値を変更すると、トランザクションの終了時にテーブルに変更が反映されます.
    ->エンティティオブジェクトの値を変更する場合は、更新クエリX=>ダーティ処理の概念を個別に発行する必要があります.

    4-4)修正機能テストコードを使用して検証する


    1.PostsApiControllerTestでのコード作成
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class PostsApiControllerTest {
    
      @Test
          public void Posts_수정된다() throws Exception{
              //given
              Posts savedPosts = postsRepository.save(Posts.builder()
                      .title("title")
                      .content("content")
                      .author("author")
                      .build());
    
              Long updateId = savedPosts.getId();
              String expectedTitle = "title2";
              String expectedContent = "content2";
    
              PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
                      .title(expectedTitle)
                      .content(expectedContent)
                      .build();
    
              String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;
              HttpEntity<PostsUpdateRequestDto> requestEntity = new HttpEntity<>(requestDto);
    
              //when
              ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);
    
              //then
              assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
              assertThat(responseEntity.getBody()).isGreaterThan(0L);
              List<Posts> all = postsRepository.findAll();
              assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
              assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
        }
    
    }
    2.テストの実行
    更新クエリーを実行するかどうかを確認できます

    4-5)Tomcatを使用したクエリーチェック機能


    ローカル環境では、DBとしてH 2を使用します.メモリに直接アクセスするには、Webコンソールを使用する必要があります.
    1.Webコンソールの有効化
    application.属性へのオプションの追加
    spring.h2.console.enabled=true
    2.アプリケーションクラスを実行する主な方法
  • が正常に動作している場合、Tomcatは8080ポートで動作します.

  • Webブラウザhttp://localhost:8080/h2-consoleに接続:写真のようにJDBC URLを記入する必要があります

  • connectボタン>をクリックして、現在のプロジェクトH 2を管理する管理ページに移動します(POTSテーブルは、次のように正常に表示されている必要があります).

  • 単純クエリー
  • を実行
    SELECT * FROM posts;
  • は現在登録データがないためinsertクエリ
  • を実行する.
    insert into posts (author, content, title) values ('author', 'content', 'title');
  • に登録されているデータを確認してAPIを要求する
    :ブラウザにhttp://localhost:8080/api/v1/posts/1と入力し、APIクエリー機能をテストする