1. portfolio Entity/model 작성
수강생 - 포트폴리오
- ID
- 대표 이미지
- 과정명
- 과정 썸네일
model
dto
- PortfolioDTO
entity
- PortfolioEntity
클라이언트에서 서버 데이터 요청에 dto를 쓰고 db 데이터 요청할때 Entity 사용
elasticsearch를 쓰게되면 indexName을 지정해줘야되니 주석처리 해놨습니다
필드명
Long portfolio\_id //포토폴리오 id
String courseName //과정명
String thumbnailUrl //대표이미지입니다만 url 형식이라 url이라고 표기했습니다.
2. portfolio repository/service 작성
repository
- portfolioMapper
- portfolioRepository
service
- PortfolioService
PortfolioMapper
ObjectMapper의 JSON 변환기능을 사용해 DTO와 ENTITY간의 반환 수행
클라이언트에 반환할 데이터는 DTO형식으로 일관되게 사용합니다.
@Component public class PortfolioMapper {
//Jackson 라이브러리 클래스
//JSON 직렬화, 역직렬화
private final ObjectMapper objectMapper;
//생성자 (의존성 주입)
public PortfolioMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
1. fromEntity
Entity → DTO
public Portfolio fromEntity(PortfolioEntity entity) {
//Objectmapper의 converValue 메서드 사용하여 필드값 매핑
return objectMapper.convertValue(entity, Portfolio.class);
}
2.toEntity
DTO → Entity
public PortfolioEntity toEntity(Portfolio dto) {
//Objectmapper의 converValue 메서드 사용하여 필드값 매핑
return objectMapper.convertValue(dto, PortfolioEntity.class);
}
3.fromEntities
List<DTO> → List<Entity>
public List fromEntities(List entities) {
//Objectmapper의 converValue 메서드 사용하여 필드값 매핑
return entities.stream() .map(this::fromEntity) .collect(Collectors.toList()); }
4. toEntities
List<Entity> → List<DTO>
public List toEntities(List dtos) {
return dtos.stream() //(dtos = 객체 리스트)를 스트림으로 변환
.map(this::toEntity) //toEntity 메서드를 사용하여 DTO → ENTITY
.collect(Collectors.toList()); //리스트에 담기
}
PortfolioRepository
과정명에 맞는 포토폴리오 리스트를 가져오는 쿼리문
## **JpaRepository**
JPA에서 제공하는 기본 인터페이스로 DB의 CRUD 작업을 지원합니다.
JpaRepository를 상속받은 repository 인터페이스는 CRUD 작업을 할 수 있습니다.
- findById(Long id): 특정 ID로 엔티티를 조회
- findAll(): 모든 엔티티를 조회
- save(T entity): 엔티티를 저장하거나 업데이트
- deleteById(Long id): 특정 ID의 엔티티를 삭제
이러한 메서드는 기본적으로 JpaRepository에서 제공하므로
별도로 구현할 필요 없이 사용할 수 있습니다.
예를 들어, findBySeq(Long seq)와 같은 메서드는 JpaRepository의 기본 메서드가 아니지만,
findById(seq)와 같은 기본 메서드는 자동으로 제공됩니다.
### 네이티브 쿼리를 사용할 때 주의할 점
1. **데이터베이스 종속성**:
- 네이티브 쿼리는 데이터베이스의 SQL 문법을 직접 사용하므로
- 특정 데이터베이스 시스템에 종속적입니다.
- 예를 들어, MySQL에서 작동하는 SQL 쿼리가 PostgreSQL에서는 작동하지 않을 수 있습니다.
- 데이터베이스를 변경할 경우, 기존에 작성한 네이티브 쿼리를 수정해야 할 수 있습니다.
2. **엔티티 캐싱 우회**:
- JPA는 엔티티 캐싱 기능을 사용하여 성능을 최적화합니다.
- 네이티브 쿼리는 이 캐싱을 우회할 수 있으며, 이로 인해 성능에 영향을 줄 수 있습니다.
- 예를 들어, 네이티브 쿼리로 조회한 데이터는 JPA의 1차 캐시(트랜잭션 범위의 캐시)에
- 저장되지 않을 수 있습니다.
3. **엔티티 필드 이름 변경**:
- JPQL 쿼리는 엔티티의 필드 이름을 기준으로 쿼리를 작성합니다.
- 필드 이름이 변경되면 JPQL 쿼리가 자동으로 업데이트됩니다.
- 그러나 네이티브 쿼리는 SQL 문법을 직접 사용하므로,
엔티티의 필드 이름이 변경되면 네이티브 쿼리를 수동으로 수정해야 합니다.
@Repository
public interface PortfolioRepository extends JpaRepository<PortfolioEntity, Long> {
//couseName 필드가 메서드의 파라미터 courseName과 일치하는 레코드만 선택
@Query("SELECT p FROM PortfolioEntity p WHERE p.courseName = :courseName")
ArrayList findByCourseName(@Param("courseName") String courseName);
}
PortfolioService
JPA 자체에서 페이지네이션 기능을 지원합니다. JPARepository를 통해 사용할 수 있습니다.
특정 시퀀스 값을 가진 Portfolio 조회
public Portfolio find(Long seq) {
//데이터베이스에서 해당 ID를 가진 PortfolioEntity 조회
return portfolioRepository.findById(seq)
//PortfolioEntity를 DTO로 변환 (Mapper의 fromEntity메서드 호출)
.map(portfolioMapper::fromEntity)
//null 검증
.orElse(null);
}
페이지네이션된 Portfolio 리스트 조회
public Page<Portfolio> findList(Pageable pageable) {
//pageable(페이지 정보)를 기반으로 ProtfolioEntity 리스트를 페이지 단위로 조회
//Page<PortfolioEntity> 형태로 반환
Page<PortfolioEntity> entityPage = portfolioRepository.findAll(pageable);
//Mapper의 fromEntity 메서드를 사용해 Entity를 DTO로 변환하여 Page<Portfolio>로 반환
return entityPage.map(portfolioMapper::fromEntity);
}
Portfolio 생성 (create)
public Portfolio create(Portfolio dto) {
//dto를 entity로 변환
PortfolioEntity entity = portfolioMapper.toEntity(dto);
//변환된 entity를 db에 저장
PortfolioEntity savedEntity = portfolioRepository.save(entity);
//entity를 다시 dto로 변환하여 반환
return portfolioMapper.fromEntity(savedEntity);
}
Portfolio 수정 (update)
public Portfolio modify(Portfolio dto) {
//dto를 entity로 변환
PortfolioEntity entity = portfolioMapper.toEntity(dto);
//entity를 db에 저장
PortfolioEntity savedEntity = portfolioRepository.save(entity);
//entity를 dto로 변환하여 반환
return portfolioMapper.fromEntity(savedEntity);
}
Portfolio 삭제 (delete)
public void remove(Long seq) {
//해당 ID의 레코드를 DB에서 삭제
portfolioRepository.deleteById(seq);
}
TEST CODE 작성
service
- PortfolioServiceTests
repository
- PortfolioRepositoryTests
controller
- api 작성 후 test 코드 작성
PortfolioMapperTest
- void bean() 메서드
테스트 목표: bean에 등록되었는지 체크
@Test
public void bean() {
//not null인지 확인하고 logging
http://log.info ("portfolioMapper : {}", portfolioMapper);
assertNotNull(portfolioMapper);
}
- toEntity() 메서드
테스트 목표: DTO → ENTITY 검증
@Test
public void toEntity() {
Portfolio portfolio = Portfolio.builder()
.portfolio_id(1L)
.courseName("test")
.thumbnailUrl("/images/test.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test.jpg")))
.build();
//when
//mapper의 메서드를 호출하여 toEntity 기능 테스트
PortfolioEntity portfolioEntity = portfolioMapper.toEntity(portfolio);
// then
//필드 값 검증
assertNotNull(portfolioEntity);
assertEquals(portfolio.getCourseName(), portfolioEntity.getCourseName());
assertEquals(portfolio.getThumbnailUrl(), portfolioEntity.getThumbnailUrl());
assertEquals(portfolio.getCourseThumbnail(), portfolioEntity.getCourseThumbnail());
}
- testFromEntities() 메서드
테스트 목표: List<Entity> → LIST<DTO> 검증
@Test
public void testFromEntities() {
PortfolioEntity entity = PortfolioEntity.builder()
.portfolio_id(1L)
.courseName("test")
.thumbnailUrl("/images/test.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test.jpg")))
.build();
List<PortfolioEntity> portfolioEntity = List.of(entity);
// when
//mapper의 메서드를 호출하여 fromEntities 기능 테스트
List<Portfolio> dtos = portfolioMapper.fromEntities(portfolioEntity);
// then
//필드 값 검증
assertNotNull(dtos);
assertEquals(1, dtos.size());
Portfolio portfolio = dtos.get(0);
assertEquals(entity.getCourseName(), portfolio.getCourseName());
assertEquals(entity.getThumbnailUrl(), portfolio.getThumbnailUrl());
assertEquals(entity.getCourseThumbnail(), portfolio.getCourseThumbnail());
}
- testToEntities() 메서드
테스트 목표: List<DTO> → LIST<Entity> 검증
@Test
public void testToEntities() {
Portfolio dto = Portfolio.builder()
.portfolio_id(1L)
.courseName("test")
.thumbnailUrl("/images/test.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test.jpg")))
.build();
List<Portfolio> dtos = List.of(dto);
// when
//mapper의 메서드를 호출하여 toEntities 기능 테스트
List<PortfolioEntity> entities = portfolioMapper.toEntities(dtos);
// then
//필드 값 검증
assertNotNull(entities);
assertEquals(1, entities.size());
PortfolioEntity entity = entities.get(0);
assertEquals(dto.getCourseName(), entity.getCourseName());
assertEquals(dto.getThumbnailUrl(), entity.getThumbnailUrl());
assertEquals(dto.getCourseThumbnail(), entity.getCourseThumbnail());
}
PortfolioRepositoryTests
- setUp() 메서드
데이터베이스 초기화 후 테스트 데이터 삽입
@BeforeEach //테스트 우선실행행
public void setUp() {
//DB 초기화
PortfolioRepository.deleteAll();
// 테스트 데이터 삽입
PortfolioEntity entity1 = PortfolioEntity.builder()
.courseName("test1")
.thumbnailUrl("/images/test1.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test1.jpg")))
.build();
PortfolioEntity entity2 = PortfolioEntity.builder()
.courseName("test2")
.thumbnailUrl("/images/test2.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test2.jpg")))
.build();
PortfolioEntity entity3 = PortfolioEntity.builder()
.courseName("test2")
.thumbnailUrl("/images/test2.jpg")
.courseThumbnail(new ArrayList<>(List.of("/images/test2.jpg")))
.build();
PortfolioRepository.save(entity1);
PortfolioRepository.save(entity2);
PortfolioRepository.save(entity3);
}
- testFindByCourseName() 메서드
PortfolioRepository의 findByCourseName 메서드가가
특정 courseName을 가진 PortfolioEntity 객체를 올바르게 조회하는가
@Test
public void testFindByCourseName() {
//courseName이 test1인 엔티티 조회
List<PortfolioEntity> CourseName1 = PortfolioRepository.findByCourseName("test1");
//courseName이 test2인 엔티티 조회
List<PortfolioEntity> CourseName2 = PortfolioRepository.findByCourseName("test2");
//courseName이 test3인 엔티티 조회
List<PortfolioEntity> CourseName3 = PortfolioRepository.findByCourseName("test3");
//test1 (결과가 notnull인가 검증)
assertTrue(CourseName1.stream()
.anyMatch(portfolio -> "test1".equals(portfolio.getCourseName())),
"test1");
//test2 (결과가 notnull인가 검증)
assertTrue(CourseName2.stream()
.anyMatch(portfolio -> "test2".equals(portfolio.getCourseName())),
"test2");
//test3 (결과가 null인가 검증)
assertTrue(CourseName3.isEmpty(), "test3");
}
setUp() 메서드
테스트 목표: DB 초기화
@BeforeEach
public void setUp() {
//DB 초기화
portfolioRepository.deleteAll();
}
bean() 메서드
테스트 목표: PortfolioService 빈이 제대로 주입되었는가
@Test
public void bean() {
//null이 아님을 logging
http://log.info ("PortfolioService : {}", portfolioService);
assertNotNull(portfolioService);
}
testFind() 메서드
테스트 목표: PortfolioService의 find 메서드가
특정 portfolio_id로 Portfolio를 올바르게 조회하는가
@Test
public void testFind() {
Long seq = 1L;
PortfolioEntity entity = PortfolioEntity.builder()
.portfolio_id(seq)
.courseName("Test")
.build();
portfolioRepository.save(entity); // 데이터베이스에 저장
//id를 호출하여 Portfolio와 조회
Portfolio result = portfolioService.find(seq);
//null이 아니며 courseName과 동일한가
assertNotNull(result);
assertEquals(entity.getCourseName(), result.getCourseName());
}
testFindList() 메서드
테스트 목표: PortfolioService의 finList 메서드가 페이지네이션된 Portfolio 리스트를 올바르게 조회하는가
@Test
public void testFindList() {
Pageable pageable = Pageable.unpaged();
//entity를 DB에 저장하고 DTO 호출
PortfolioEntity entity = PortfolioEntity.builder()
.courseName("Test")
.build();
portfolioRepository.save(entity);
Portfolio dto = Portfolio.builder()
.courseName("Test")
.build();
Page<PortfolioEntity> entityPage = new PageImpl<>(List.of(entity));
Page<Portfolio> dtoPage = new PageImpl<>(List.of(dto));
//페이지네이션 호출
Page<Portfolio> result = portfolioService.findList(pageable);
//결과가 비어있지 않으며 페이지 총 요소와 내용 검증
assertNotNull(result);
assertEquals(1, result.getTotalElements());
assertEquals(dto.getCourseName(), result.getContent().get(0).getCourseName());
}
PortfolioServiceTests
testCreate() 메서드
테스트 목표: PortfolioService의 create 메서드가 DTO를 올바르게 생성하는가
@Test
public void testCreate() {
//dto 빌드
Portfolio dto = Portfolio.builder()
.courseName("New Course")
.build();
//service의 create 호출
Portfolio result = portfolioService.create(dto);
assertNotNull(result);
assertEquals(dto.getCourseName(), result.getCourseName());
assertTrue(result.getPortfolio_id() != null); // ID가 생성되었는지 확인
}
- testModify() 메서드
테스트 목표: PortfolioService의 modify 메서드가 기존 Portfolio를 수정하는가
@Test
public void testModify() {
PortfolioEntity existingEntity = PortfolioEntity.builder()
.courseName("Old")
.build();
PortfolioEntity savedEntity = portfolioRepository.save(existingEntity); // 데이터베이스에 저장
//dto 빌드
Portfolio dto = Portfolio.builder()
.portfolio_id(savedEntity.getPortfolio_id())
.courseName("Updated")
.build();
//서비스의 modify 호출
Portfolio result = portfolioService.modify(dto);
//null이 아니며 courseName이 업데이트되었는가
assertNotNull(result);
assertEquals("Updated", result.getCourseName());
}
- testRemove() 메서드
테스트 목표: PortfolioService의 remove 메서드가 특정 portfolio_id의 Portfolio를 삭제하는가
@Test
public void testRemove() {
PortfolioEntity entity = PortfolioEntity.builder()
.courseName("deleted")
.build();
PortfolioEntity savedEntity = portfolioRepository.save(entity); // 데이터베이스에 저장
Long seq = savedEntity.getPortfolio_id();
//id호출
portfolioService.remove(seq);
//삭제 후 db에 없는지 검증
Optional<PortfolioEntity> deletedEntity = portfolioRepository.findById(seq);
assertTrue(deletedEntity.isEmpty());
}
PortfolioController
@RestController: RESTful 웹 서비스의 컨트롤러임을 선언
JSON 또는 XML 형태로 HTTP 요청과 응답을 처리한다.
@RequiredArgsConstructor: Lombok 어노테이션으로, final로 선언된 필드에 대한 생성자를 자동으로 생성
@RequestMapping("/api/portfolio"): 이 클래스의 모든 메서드에 대해 기본 URL 경로를 /api/portfolio로 설정
@Tag(name = "portfolioCRUD"): OpenAPI (Swagger) 문서화에서 이 컨트롤러의 API를 portfolioCRUD로 지정
- getPortfolio(Pageable pageable)
HTTP 메서드: GET
경로: /api/portfolio
메서드 설명: 페이지네이션 넣어 Portfolio 객체의 리스트를 조회
@GetMapping
public ResponseEntity<Page<Portfolio>> getPortfolio(Pageable pageable) {
//serivce의 findList(pageable)을 호출하여 포토폴리오 리스트 조회
Page<Portfolio> portfolio = portfolioService.findList(pageable);
//조회된 리스트를 responseEntity로 감싸 http 응답 반환
return new ResponseEntity<>(portfolio, HttpStatus.OK);
}
getPortfolio(@PathVariable Long seq)
HTTP 메서드: GET
경로: /api/portfolio/{seq}
메서드 설명: 특정 id(seq)값에 해당하는 포토폴리오 조회
// @PathVariable을 통해 URL 경로에서 seq 값을 추출
(@PathVariable Long seq)
@GetMapping("/{seq}")
public ResponseEntity<Portfolio> getPortfolio(@PathVariable Long seq) {
//service의 find를 통해 Portfolio를 조회.
Portfolio portfolio = portfolioService.find(seq);
if (portfolio != null) {
//not null이면 http상태 200 ok 반환
return new ResponseEntity<>(portfolio, HttpStatus.OK);
} else {
//null일 경우 404 not found 반환
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
- createPortfolio(@RequestBody Portfolio portfolio)
HTTP 메서드: POST
경로: /api/portfolio
메서드 설명: 새로운 포토폴리오 생성
@PostMapping
//requestBody를 통해 Portfolio 객체 추출
public ResponseEntity<Portfolio> createPortfolio(@RequestBody Portfolio portfolio) {
//service의 portfolio를 호출하여 포토폴리오 생성
Portfolio createdPortfolio = portfolioService.create(portfolio);
//생성된 포토폴리오를 201 created와 함께 반환
return new ResponseEntity<>(createdPortfolio, HttpStatus.CREATED);
}
updatePortfolio(@PathVariable Long seq, @RequestBody Portfolio portfolio)
HTTP 메서드: PUT
경로: /api/portfolio/{seq}
설명: 특정 seq 값에 해당하는 Portfolio를 수정
@PutMapping("/{seq}")
//@PathVariable을 통해 URL 경로에서 seq 값을 추출
//@RequestBody를 통해 요청 본문에서 수정된 Portfolio 객체 추출
public ResponseEntity<Portfolio> updatePortfolio(@PathVariable Long seq, @RequestBody Portfolio portfolio) {
//portfolioService.find(seq)로 기존 Portfolio를 조회
Portfolio existingPortfolio = portfolioService.find(seq);
if (existingPortfolio != null) {
//not null일 경우 portfolioService.modify(portfolio)로 수정
Portfolio updatedPortfolio = portfolioService.modify(portfolio);
//수정된 Portfolio를 ResponseEntity로 감싸 HTTP 상태 200 OK를 반환
return new ResponseEntity<>(updatedPortfolio, HttpStatus.OK);
} else {
//null일 경우 404 not found 반환
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
- deletePortfolio(@PathVariable Long seq)
HTTP 메서드: DELETE
경로: /api/portfolio/{seq}
설명: 특정 seq 값에 해당하는 Portfolio를 삭제
@DeleteMapping("/{seq}")
//@PathVariable을 통해 URL 경로에서 seq 값 추출
public ResponseEntity<Void> deletePortfolio(@PathVariable Long seq) {
//portfolioService.find(seq)로 기존 Portfolio를 조회
Portfolio existingPortfolio = portfolioService.find(seq);
if (existingPortfolio != null) {
//not null일 경우 portfolioService.remove(seq)로 삭제
portfolioService.remove(seq);
//삭제 후 HTTP 상태 204 NO CONTENT를 반환
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
//not null일 경우 404 not found 반환
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
'CS > SPRING' 카테고리의 다른 글
JPQL과 SQL의 차이점 (0) | 2024.08.12 |
---|---|
JPA의 영속성 컨텍스트 (0) | 2024.08.12 |
serviceImpl 을 사용하는 이유 (1) | 2024.07.23 |
웹서버 WAS, SERVLET (0) | 2024.07.08 |
JPA (0) | 2024.06.28 |