TIL

day37 TIL

dalooong 2023. 7. 9. 13:28

Spring Boot DB 연동

프로젝트 순서

// READ ALL
    public List<StudentEntity> readStudentAll() {
        System.out.println(
                this.studentRepository.findAll());
        List<StudentEntity> studentEntityList= this.studentRepository.findAll();
        return studentEntityList;

    }

 

  • appcontroller - read all 마찬가지로 수정
@GetMapping("read-all")
    public @ResponseBody List<StudentEntity> readAll() {
        this.service.readStudentAll();
//        return "done-read-all";
        return this.service.readStudentAll();
    }

 

3.실행 → http://localhost:8080/read-all

json formatter로 인해 db 회원 목록이 이렇게 나열되어 보여진다.

dto 패키지 생성 → StudentDto클래스 생성

  • @Data 어노테이션 생성
package com.example.jpa.dto;

import lombok.Data;

@Data
public class StudentDto {
    private Long id;  //Entity.id
    private String name; //Entity.name
    private String email; //Entity.email
}

 

@ResponseBody로 return

  • AppService의 readStudentAlll() 작성
  • List<StudentEntity>를 반환하게 해보자.
  • Controller도 @ResponseBody List<StudentEntity>로 바꾸자
// READ ALL
    //public List<StudentEntity> readStudentAll() {
        public List<StudentDto> readStudentAll(){
        System.out.println(
                this.studentRepository.findAll());
        List<StudentEntity> studentEntityList =
                this.studentRepository.findAll();
        List<StudentDto> studentDtoList = new ArrayList<>();
        for (StudentEntity studentEntity :
                this.studentRepository.findAll()) {
            StudentDto studentDto = new StudentDto();
            studentDto.setId(studentEntity.getId());
            studentDto.setName(studentEntity.getName());
            studentDto.setEmail(studentEntity.getEmail());
            studentDtoList.add(studentDto);
        }
//        return studentEntityList;
        return studentDtoList;
    }
  • app controller 코드 수정
@GetMapping("read-all")
    public @ResponseBody List<StudentDto> readAll() {
        this.service.readStudentAll();
//        return "done-read-all";
        return this.service.readStudentAll();
    }

실행(json fommater를 다운받았기 때문에 데이터가 구조화되어서 실행된다)


Entity 가리기 - DTO

Entity를 그대로 사용자에게 돌려주게 되면 사용자 정보들이 그대로 노출 되기 때문에 Entity를 가리려고한다.

Data Transder Object

  • 일종의 디자인패턴
  • View에서 사용하는 데이터와 Model이 관리하는 데이터의 형태를 분리
  • Entity가 변화했을 때 DTO를 사용하는 View에 영향이 줄어든다.
  • Entity에서 사용자에게 노출되는 정보를 조절할 수 있다.(정보은닉)

-StudentDto 생성(dto 패키지 생성 후 StudentDto 클래스 생성)

  • 객체의 생성을 담당하는 클래스 메소드
  • 객체 내부의 메소드를 통해 새로운 객체를 만드는 방법이다.
import com.example.jpa.entities.StudentEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor

public class StudentDto {
    private Long id;  //Entity.id
    private String name; //Entity.name
    private String email; //Entity.email

    //studentDto가 entity정보가 있을 때 새로운 dto가 만들어지게끔 할 수 있음
    // 정적 팩토리 메소드
    // 유지보수, 재사용성 측면에서 좋음
    public static StudentDto
    fromEntity(StudentEntity studentEntity){
        //내부에서 새로운 dto를 만들어줌 
        StudentDto dto = new StudentDto();
        dto.setId(studentEntity.getId());
        dto.setName(studentEntity.getName());
        dto.setEmail(studentEntity.getEmail());
        return dto;
    }
}
  • DTO는 윗부분만 적어도 되지만 StudentDto fromEntity()를 추가
  • 이런식으로 static을 이용하여 static factory method 구조를 만들 수 있음
    • static factory method
      • 객체의 생성을 담당하는 클래스 메소드
      • 객체 내부의 메소드를 통해 새로운 객체를 만드는 방법

AppService // READ ALL 부분 작성

// READ ALL
    public List<StudentDto> readStudentAll() {
        List<StudentEntity> studentEntityList = this.studentRepository.findAll();
        List<StudentDto> studentDtoList = new ArrayList<>();

        for (StudentEntity studentEntity : studentEntityList) {
            studentDtoList.add(StudentDto.fromEntity(studentEntity));
        }
        return studentDtoList;
    }
    • static factory method 구조를 활용하면
      • studentEntity에서 studentDto로 바꾸는 일은 빈번하게 일어날텐데, 매번 new 해서 set하는건 번거로우일
      • studentDto가 entity정보가 있을때 새로운 dto가 만들어지게끔 할 수 있음
    • Skeleton 프로젝트
      1. 연동, Entity, Interface
      • 연동하기

클론을 생성하기 위해 강사님 깃 주소 https://github.com/edujeeho/likelion-backend-5.git 를 이용하여 깃 배쉬에서 다운 받을 폴더로 이동하여 다음과 같이 한다

    • $ git clone <https://github.com/edujeeho/likelion-backend-5.git>
      
      • 그 폴더에 클론이 생기게 되면 원하는 프로젝트를 인텔리제이로 열어주면 된다.
      • Entity

data를 넣어주기 위해 StudentEntity 를 생성해줌

클론한 강사님 repo 중 ‘Skeleton’ 프로젝트 빌드하기 → 만약 빌드 중 빌드 오류가 난다면 settings→ Gradle jdk, sdk, jvm 버전 본인꺼로 바꿔주면 빌드된다

빌드 성공 화면

  • Entity : data를 넣어주기 위해 StudentEntity 를 생성해줌
package com.example.student.entity;
/* CREATE TABLE students (
*   id INTEGER PRIMARY KEY AUTOINCREMENT,
*   name TEXT,
*   age INTEGER,
*   phone TEXT,
*   email TEXT
* */

import jakarta.persistence.*;
import lombok.Data;

//테이블생성 
@Data
@Entity
@Table(name = "students")
public class StudentEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer age;
    private String phone;
    private String email;
}
  • database 연결 (sqlite-’db.sqlite’생성)

위에 StudentEntity 에서 생성한 ‘students’ 테이블을 database 에서 db를 불러오면 생성 된 걸 확인할 수 있다.

만약 바로 reload가 되지 않는다면 db → drop한 뒤 새로 만들고 load하면 생성이 된걸 확인할 수 있다.

Interface

  • JpaRepository<StudentEntity, Long> 인터페이스를 상속받는 StudentRepository를 구현해줌
  • StudentRepository 인터페이스 생성
package com.example.student.repository;

import com.example.student.entity.StudentEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudentRepository extends JpaRepository<StudentEntity, Long> {
}

Service

  • JpaRepository에 대한 의존성 주입을 하여 해당 Service에서 JpaRepository를 사용할 수 있도록 한다.
@Service
public class StudentService {
    private final StudentRepository repository;

    public StudentService(StudentRepository repository) {
        this.repository = repository;
    }
  • 생성자 주입 시 final을 사용하는 이유는?
    • final 키워드를 사용하여 생성자에 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아줄 수 있다.
    • 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없고 오직 생성자 주입 방식에만 final 키워드를 사용할 수 있다.
    • 또한 final을 사용하게 되면 lombok에 @RequiredArgsConstructor를 사용하여 자동으로 생성자 주입을 사용할 수 있다.

Controller

@Controller
@RequestMapping("/students")
public class StudentController {
    
    private final StudentService service;
    
    public StudentController(StudentService service){
        this.service = service;
    }
  • Class에 @RequestMapping을 사용하는 이유는?
    • URL은 계층 구조로 이루어져 있는데 클래스에 RequestMapping을 사용하면 반복되는 코드와 오타의 위험성이 줄어들고 해당 클래스에서 관리하는 URL을 한정시켜 정의한 URL의 하위 계층만 관리하게 할 수 있다.

 

HTTP 상태코드란?

  • TTP Status Code(HTTP 상태 코드)는 클라이언트가 보낸 HTTP 요청에 대한 서버의 응답을 코드로 표현한 것으로 요청에 대한 성공 실패 실패요인 등을 알 수 있다.
  • 종류
    • 1xx(Informational) : 요청이 수신되어 처리중
    • 2xx(Successful) : 요청 정상 처리
    • 3xx(Redirection) : 요청을 완료하려면 추가 행동이 필요
    • 4xx(Client Error): 클라이언트 오류, 잘못된 문법등으로 서버가 요청을 수행할 수 없음
      • 404(Not Found)
        • 요청 리소스를 찾을 수 없음
        • 요청 리소스가 서버에 없거나 클라이언트가 권한이 부족한 리소스에 접근할 때 해당 리소스를 숨기고 싶을 때
    • 5xx(Server Error) : 서버오류, 서버가 정상 요청을 처리하지못함

CREATE

  • form에서 넘겨받은 데이터의 값을 DTO로 넘겨받아 save()메소드를 통해 DB에 저장한다.
  • 이때 저장한 후 redirect로 상세보기 페이지로 리턴해주어야 한다!
  • PRG(POST-Redirect-GET)
    • POST로 데이터를 전송 후 브라우저를 새로고침 하게되면 서버에 다시 요청되어 중복 요청이 된다.
    • 이렇게 되면 중복 된 데이터가 계속 들어가게 된다.
    • 이러한 것을 막기 위해 PRG를 사용해서 POST로 데이터 전송 후 GET 메소드로 리다이렉트 하여 데이터가 이중으로 전송되는 것을 막는다.
    • URL이 이미 POST→ GET으로 변경, 실수로 새로고침 해도 GET으로 결과 화면만 조회!
    // CREATE
        public StudentDto createStudent(StudentDto dto) {
            StudentEntity entity = new StudentEntity();
            entity.setName(dto.getName());
            entity.setAge(dto.getAge());
            entity.setPhone(dto.getPhone());
            entity.setEmail(dto.getEmail());
            return StudentDto.fromEntity(this.repository.save(entity));
            // 새로 등록된 DTO를 리턴
        }
    
    // Controller
    // 새로운 StudentEntity 생성 후 상세보기 페이지로
    @PostMapping("/create")
    public String create(StudentDto dto) {
        System.out.println(dto.toString());
        StudentDto newDto = service.createStudent(dto);
        return "redirect:/students/" + newDto.getId();
    }
    

 UPDATE

  • update도 save() 메소드를 사용하여 업데이트 한다.
  • 이때 update를 하게 될 때 해당 id로 받아온 객체를 회수하여 해당 데이터가 있을 때만 업데이트가 진행 되는 로직을 작성하는것이 좋다.
  • 업데이트가 완료되면 업데이트 완료된 객체를 넘겨 해당 업데이트된 객체를 화면에 보여준다.
  • PRG를 통해 조회 페이지로 Redirect 해준다.
// UPDATE
    public StudentDto updateStudent(Long id, StudentDto dto) {
        // id로 해당 객체를 회수하고 save를 하는것이 좋음
        Optional<StudentEntity> optionalEntity
                = this.repository.findById(id);
        if(optionalEntity.isPresent()){
            StudentEntity targetEntity = optionalEntity.get();
            targetEntity.setName(dto.getName());
            targetEntity.setAge(dto.getAge());
            targetEntity.setPhone(dto.getPhone());
            targetEntity.setEmail(dto.getEmail());
            repository.save(targetEntity);
            return StudentDto.fromEntity(targetEntity);
        } else throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
// Controller
// id에 해당하는 StudentEntity 수정 후 상세보기 페이지로
  @PostMapping("/{id}/update")
  public String update(@PathVariable Long id, StudentDto dto) {
      StudentDto updateDto = service.updateStudent(id, dto);
      return "redirect:/students/" + updateDto.getId();
  }

 

 DELETE

  • deleteById() 메소드를 사용하여 인자로 넘겨받은 ID의 값을 삭제한다.
  • 이때 existsById() 메소드를 사용하여 인자로 받은 ID가 실제 DB에 존재하는지 확인 한 후 delete를 진행한다.
  • delete를 하게되면 해당 데이터가 제거되기 때문에 Home으로 돌아가게 한다.
public void deleteStudent(Long id) {
      // existsById
      if(this.repository.existsById(id)) {
          this.repository.deleteById(id);
      } else throw new ResponseStatusException(HttpStatus.NOT_FOUND);
  }
// id에 해당하는 StudentEntity 삭제 후 홈페이지로
@PostMapping("/{id}/delete")
public String delete(@PathVariable Long id) {
    service.deleteStudent(id);
    return "redirect:/students";
}