Java/Spring Boot

[JPA] RESTful API를 만들어보자. (CRUD)

누리는 귀여워 2023. 11. 20. 01:26

1. 환경

1. Sping Boot

2. Spring Data JPA

3. Gradle

4. postgreSQL

5. PostMan

6. Swagger

7. 인텔리제이 무료버전


2. DB 구조


3. CRUD 구현

(1). Entity 생성

@Entity
@Getter
@Setter
@ToString
@Table(name = "room_cd")
public class EclassRoomEntity {

    @Id
    @Column(name = "room_cd_id", length = 36)
    private String roomCdId;

    @Column(nullable = false, length = 100)
    private String roomNm; // 강의실명

    @Column(nullable = false)
    private int hdcnt; // 정원, headCount

    @Column(length = 1, columnDefinition = "varchar (1) default 'N'")
    private String useYn; // 사용여부

    /* 기본 생성자 추가 */
    public EclassRoomEntity() {
    }

    /* 초기화용 생성자 */
    public EclassRoomEntity(String roomCdId) {
        this.roomCdId = roomCdId;
    }

    /* @Builder = 생성자 자동 생성 */
    @Builder
    public EclassRoomEntity(String roomCdId, String roomNm, int hdcnt, String useYn) {
        this.roomCdId = roomCdId;
        this.roomNm = roomNm;
        this.hdcnt = hdcnt;
        this.useYn = useYn;
    }

}

(2). DTO 생성

@Data // @Data = @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor
public class EclassRoomDTO {

    private String roomCdId; // 강의실 코드
    private String roomNm;   // 강의실명
    private int hdcnt;        // 정원
    private String useYn;     // 사용여부

    // 기본 생성자
    public EclassRoomDTO() {
    }

    public EclassRoomDTO(String roomCdId, String roomNm, int hdcnt, String useYn) {
    }

    /* ex) DB에서 조회할 때 강의실 정보를 Entity 객체에 담아오고, DTO로 변환하여 클라이언트에게 전달 */
    public static EclassRoomDTO toDTO(EclassRoomEntity entity) { // Entity -> DTO 변환
        EclassRoomDTO dto = new EclassRoomDTO(); // DTO 객체 생성
        dto.setRoomCdId(entity.getRoomCdId());
        dto.setRoomNm(entity.getRoomNm());
        dto.setHdcnt(entity.getHdcnt());
        dto.setUseYn(entity.getUseYn());
        return dto;
    }
}

(3). DTO -> Entity 변환 클래스 생성

public class EclassRoomEntityBuilder {

    public static EclassRoomEntity buildFromDTO(EclassRoomDTO eclassRoomDTO) { // DTO -> Entity 변환
        return EclassRoomEntity.builder() // builder을 이용하여 entity 객체 생성
                .roomCdId(eclassRoomDTO.getRoomCdId())
                .roomNm(eclassRoomDTO.getRoomNm())
                .hdcnt(eclassRoomDTO.getHdcnt())
                .useYn(eclassRoomDTO.getUseYn())
                .build();
    }

    /* EclassRoom Insert roomCdId 자동 증가용 */
    public static EclassRoomEntity buildNewId(String newRoomCdId, EclassRoomDTO eclassRoomDTO) { // DTO -> Entity 변환
        return EclassRoomEntity.builder() // builder을 이용하여 entity 객체 생성
                .roomCdId(newRoomCdId)
                .roomNm(eclassRoomDTO.getRoomNm())
                .hdcnt(eclassRoomDTO.getHdcnt())
                .useYn(eclassRoomDTO.getUseYn())
                .build();
    }

}

(4). Repository 생성

 
				                                // Entity명 , PK Type
public interface EclassRoomRepository extends JpaRepository<EclassRoomEntity, String> {
 			
}

(4). 전체 조회

1). Controller

@RestController
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
@Tag(name = "Eclass - Room")
public class EclassRoomController {

	private final EclassRoomService eclassRoomService; // 생성자 주입 방식

	/* @RestController이 적용되면 @ResponseBody를 사용하지 않아도 자동으로 됨 /*
	/* Test Class 생성 단축키 : Ctrl + Shift + T */

	/* 전체 조회 */
	@GetMapping("/selectEclassRoomList")
	@Operation(summary = "강의실 리스트", description = "강의실 리스트")
	public ResponseEntity<List<EclassRoomDTO>> getAllEclassRooms() {
		
		// Service에서 모든 강의실 정보를 조회하여 List<EclassRoomEntity>로 받아옴
		List<EclassRoomEntity> eclassRoomEntityList = eclassRoomService.getAllEclassRooms();

		List<EclassRoomDTO> eclassRoomDTOList = eclassRoomEntityList
				.stream() // List -> stream()으로 변환
				.map(EclassRoomDTO::toDTO) // 각각의 EclassRoomEntity를 EclassRoomDTO로 변환
				.collect(Collectors.toList()); // stream() -> List 변환

		// 강의실 정보를 ResponseEntity로 감싸서 반환
		// 성공 시 200 코드와 정보가 담긴 eclassRoomDTOList가 담긴 ResponseEntity를 반환
		return new ResponseEntity<>(eclassRoomDTOList, HttpStatus.OK);
	}

}

2). Service

@Service
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
public class EclassRoomService {

    private final EclassRoomRepository eclassRoomRepository;

    /* Test Class 생성 단축키 : Ctrl + Shift + T */
    /* 전체 조회, 단건 조회 = Entity -> DTO 변환
    *  등록, 수정 = DTO -> Entity 변환
    *  클라이언트 -> DB인지, DB -> 클라이언트인지 생각하면 됨 */

    /* 전체 조회 */
    public List<EclassRoomEntity> getAllEclassRooms() {

        /* Repository에 find.All()을 사용하여 모든 강의실 정보를 DB에서 가져온다.
        *  조회된 강의실 정보를 리스트로 반환한다.*/
        return eclassRoomRepository.findAll(); // findAll() = 전체 조회
    }
}

(5). 단건 조회

1). Controller


@RestController
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
@Tag(name = "Eclass - Room")
public class EclassRoomController {

	private final EclassRoomService eclassRoomService; // 생성자 주입 방식

	/* @RestController이 적용되면 @ResponseBody를 사용하지 않아도 자동으로 됨 /*
	/* Test Class 생성 단축키 : Ctrl + Shift + T */

	/* 단건 조회 */
	@GetMapping("/selectEclassRoomList/{roomCdCi}")
	@Operation(summary = "강의실 단건 조회", description = "강의실 단건 조회")
	public ResponseEntity<EclassRoomDTO> getEclassRoom(@PathVariable String roomCdCi) {
		
		// Service에 getEclassRoom(roomCdCi)를 호출하여 단일 정보를 가져오고 DTO에 저장.
		// Service에서 Controller로 넘겨줄 때 Entity -> DTO로 변환하여 넘겨줌
		EclassRoomDTO eclassRoomDTO = eclassRoomService.getEclassRoom(roomCdCi);

		if (eclassRoomDTO == null) { // eclassRoomDTO가 null이면 해당 404 에러 코드 반환
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		} else if (eclassRoomDTO.getRoomCdId() == null) { // roomCdId가 null이면 해당 404 에러 코드 반환
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		} else {
			// 성공 시 200 코드와 정보가 담긴 eclassRoomDTO가 담긴 ResponseEntity를 반환
			return new ResponseEntity<>(eclassRoomDTO, HttpStatus.OK); 
		}
	}
}

2). Service

@Service
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
public class EclassRoomService {

    private final EclassRoomRepository eclassRoomRepository;

    /* Test Class 생성 단축키 : Ctrl + Shift + T */
    /* 전체 조회, 단건 조회 = Entity -> DTO 변환
    *  등록, 수정 = DTO -> Entity 변환
    *  클라이언트 -> DB인지, DB -> 클라이언트인지 생각하면 됨 */
    
    /* 단건 조회 */
    public EclassRoomDTO getEclassRoom(String roomCdCi) {

        // roomCdCi에 맞는 강의실 정보를 가져온다. 해당되는 강의실이 없으면 예외 발생
        EclassRoomEntity entity = eclassRoomRepository.findById(roomCdCi) // findById(roomCdCi) = roomCdCi를 이용하여 단건 조회
                .orElseThrow(() -> new EntityNotFoundException("roomCdCi not found ! : " + roomCdCi)); // roomCdCi가 없으면 예외

        return EclassRoomDTO.toDTO(entity); // entity를 DTO로 변환하여 반환
    }
}

(6). 등록

1). Controller

@RestController
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
@Tag(name = "Eclass - Room")
public class EclassRoomController {

	private final EclassRoomService eclassRoomService; // 생성자 주입 방식

	/* @RestController이 적용되면 @ResponseBody를 사용하지 않아도 자동으로 됨 /*
	/* Test Class 생성 단축키 : Ctrl + Shift + T */

	/* 등록 */
	@PostMapping("/InsertEclassRoom")
	@Operation(summary = "강의실 등록", description = "강의실 등록")
	public ResponseEntity<EclassRoomDTO> createEclassRoom(@RequestBody EclassRoomDTO eclassRoomDTO) {
		
		/* eclassRoomService.createEclassRoom(eclassRoomDTO)을 호출하여
		   등록 정보가 담긴 DTO를 사용하여 강의실을 등록하고, 등록된 정보를 createdRoom에 저장 */
		EclassRoomDTO createdRoom = eclassRoomService.createEclassRoom(eclassRoomDTO);

		// 강의실 정보를 ResponseEntity로 감싸서 반환
		return new ResponseEntity<>(createdRoom, HttpStatus.CREATED);
	}
}

2). Service

@Service
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
public class EclassRoomService {

    private final EclassRoomRepository eclassRoomRepository;

    /* Test Class 생성 단축키 : Ctrl + Shift + T */
    /* 전체 조회, 단건 조회 = Entity -> DTO 변환
    *  등록, 수정 = DTO -> Entity 변환
    *  클라이언트 -> DB인지, DB -> 클라이언트인지 생각하면 됨 */

    /* 등록 */
    public EclassRoomDTO createEclassRoom(EclassRoomDTO eclassRoomDTO) {
        // Repository에서 JPQL 가져오기
        Integer maxNumericValue = eclassRoomRepository.findMaxNumericValue();

        // 현재 roomCdCI이 null이면 초기값으로 0 설정
        if (maxNumericValue == null) {
            maxNumericValue = 0;
        }

        // 현재 최대값에서 1을 더하여 새로운 roomCdId 생성 | MA-1 -> MA-2 ...
        int newNumericValue = maxNumericValue + 1;
        String newRoomCdId = "MA-" + newNumericValue;

        // newRoomCdId, DTO를 roomEntity에 담고 저장
        EclassRoomEntity roomEntity = EclassRoomEntityBuilder.buildNewId(newRoomCdId, eclassRoomDTO); // 객체 생성

        // roomEntity를 DB에 저장하고, 저장된 Entity를 savedEntity에 반환
        EclassRoomEntity savedEntity = eclassRoomRepository.save(roomEntity); // save = 엔티티가 있으면 수정, 없으면 등록

        return EclassRoomDTO.toDTO(savedEntity); // Entity -> DTO 변환하여 반환
    }
}

3). Repository

public interface EclassRoomRepository extends JpaRepository<EclassRoomEntity, String> {

    // 등록 시 현재 DB에서 가장 큰 roomCdId SELECT하는 쿼리문
    /*
        room_cd_id에서 MA-다음 숫자 부분을 추출하고 최대값을 찾는다.
        room_cd_id에서 4번째 문자부터 끝까지 문자열을 추출하고 문자열 -> 정수로 변환
        nativeQuery = true == JPA 쿼리(JPQL)가 아닌 SQL 쿼리를 사용할 때는 적어줘야됨
        service에서도 쿼리를 작성할 수 있지만 가독성을 위해 Repository에 작성함
    */
    @Query(value = "SELECT MAX(CAST(SUBSTRING(room_cd_id, 4) AS int)) FROM room_cd", nativeQuery = true)
    Integer findMaxNumericValue();

}

(7). 수정

1). Controller

@RestController
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
@Tag(name = "Eclass - Room")
public class EclassRoomController {

	private final EclassRoomService eclassRoomService; // 생성자 주입 방식

	/* @RestController이 적용되면 @ResponseBody를 사용하지 않아도 자동으로 됨 /*
	/* Test Class 생성 단축키 : Ctrl + Shift + T */

	/* 수정 */
	@PutMapping("/UpdateEclassRoom/{roomCdCi}")
	@Operation(summary = "강의실 수정", description = "강의실 정보 수정")
	public ResponseEntity<EclassRoomDTO> updateEclassRoom(@PathVariable String roomCdCi, @RequestBody EclassRoomDTO eclassRoomDTO) {
		
		/* eclassRoomService.updateEclassRoom(roomCdCi, eclassRoomDTO)을 호출하여
		   등록 정보가 담긴 DTO를 사용하여 강의실을 등록하고, 등록된 정보를 updatedRoom에 저장 */
		EclassRoomDTO updatedRoom = eclassRoomService.updateEclassRoom(roomCdCi, eclassRoomDTO);

		// 강의실 정보를 ResponseEntity로 감싸서 반환
		return new ResponseEntity<>(updatedRoom, HttpStatus.OK);
	}
}

2). Service

package tuna.tunaEclass.tunaEclass.eclassRoom.service;

import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import tuna.tunaEclass.tunaEclass.eclassRoom.dto.EclassRoomDTO;
import tuna.tunaEclass.tunaEclass.eclassRoom.dto.EclassRoomEntityBuilder;
import tuna.tunaEclass.tunaEclass.eclassRoom.entity.EclassRoomEntity;
import tuna.tunaEclass.tunaEclass.eclassRoom.repository.EclassRoomRepository;

import java.util.List;

@Service
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
public class EclassRoomService {

    private final EclassRoomRepository eclassRoomRepository;

    /* Test Class 생성 단축키 : Ctrl + Shift + T */
    /* 전체 조회, 단건 조회 = Entity -> DTO 변환
    *  등록, 수정 = DTO -> Entity 변환
    *  클라이언트 -> DB인지, DB -> 클라이언트인지 생각하면 됨 */

    /* 수정 */
    public EclassRoomDTO updateEclassRoom(String roomCdCi, EclassRoomDTO eclassRoomDTO) {
        
        /* 강의실 코드로 강의실을 조회한다, 해당 코드가 없으면 예외 발생 */
        EclassRoomEntity existingEntity = eclassRoomRepository.findById(roomCdCi)
                .orElseThrow(() -> new EntityNotFoundException("roomCdCi not found ! : " + roomCdCi));

        // DTO를 Entity로 변환
        EclassRoomEntity updatedEntity = EclassRoomEntityBuilder.buildFromDTO(eclassRoomDTO);

        // Entity 식별자를 설정
        updatedEntity.setRoomCdId(existingEntity.getRoomCdId());

        // 수정된 Entity를 DB에 저장하고, updatedEntity에 다시 할당
        updatedEntity = eclassRoomRepository.save(updatedEntity);

        return EclassRoomDTO.toDTO(updatedEntity); // Entity -> DTO 변환하여 반환
    }
    
}

(8). 삭제

1). Controller

@CrossOrigin("*")
@RestController
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
@Tag(name = "Eclass - Room")
public class EclassRoomController {

	private final EclassRoomService eclassRoomService; // 생성자 주입 방식

	/* @RestController이 적용되면 @ResponseBody를 사용하지 않아도 자동으로 됨 /*
	/* Test Class 생성 단축키 : Ctrl + Shift + T */

	/* 삭제 */
	@DeleteMapping("/DeleteEclassRoom/{roomCdCi}")
	@Operation(summary = "강의실 삭제", description = "강의실 삭제")
	// Void를 사용한 이유 = 해당 메서드가 반환하는데 반환할 데이터가 없음을 나타내기 위해
	public ResponseEntity<Void> deleteEclassRoom(@PathVariable String roomCdCi) {

		/* eclassRoomService.deleteEclassRoom(roomCdCi)를 호출하여
		해당되는 roomCdCi를 포함하는 강의실을 삭제합니다 */
		eclassRoomService.deleteEclassRoom(roomCdCi);
		return ResponseEntity.noContent().build();
	}
}

2). Service

@Service
@RequiredArgsConstructor // final을 가진 생성자 자동 생성
public class EclassRoomService {

    private final EclassRoomRepository eclassRoomRepository;

    /* Test Class 생성 단축키 : Ctrl + Shift + T */
    /* 전체 조회, 단건 조회 = Entity -> DTO 변환
    *  등록, 수정 = DTO -> Entity 변환
    *  클라이언트 -> DB인지, DB -> 클라이언트인지 생각하면 됨 */

    /* 삭제 */
    public void deleteEclassRoom(String roomCdCi) {
        /* 강의실 코드로 강의실을 조회한다, 해당 코드가 없으면 예외 발생 */
        EclassRoomEntity existingEntity = eclassRoomRepository.findById(roomCdCi) // findById(roomCdCi) = roomCdCi를 이용하여 조회
                .orElseThrow(() -> new EntityNotFoundException("roomCdCi not found ! : " + roomCdCi)); // roomCdCi가 없으면 예외

        // 엔터티 삭제
        eclassRoomRepository.delete(existingEntity); // .delete = 삭제
    }
}

4. Swagger 확인

5. PostMan을 이용하여 테스트 

클릭 !

 

[PostMan] API를 쉽게 테스트 해보자 !

1. PostMan이 뭐에요? 개발된 API를 테스트 할 수 있는 플랫폼 2. 개발 소스 클릭 ! [JPA] RESTful API를 만들어보자. (CRUD) 1. 환경 1. Sping Boot 2. Spring Data JPA 3. Gradle 4. postgreSQL 5. PostMan 6. Swagger 7. 인텔리제이

noorypapa.tistory.com