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

  1. void bean() 메서드
테스트 목표: bean에 등록되었는지 체크
@Test
public void bean() {
//not null인지 확인하고 logging
        http://log.info  ("portfolioMapper : {}", portfolioMapper);
        assertNotNull(portfolioMapper);
    }
  1. 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());
}
  1. 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());
}
  1. 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

  1. 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);

}
  1. 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가 생성되었는지 확인

}
  1. 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());
}
  1. 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로 지정
  1. 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);
    }
}
  1. 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);
    }
}
  1. 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

springMVC 패턴에서  view에서 넘긴 매개변수를 controller로 넘기고, controller는 service 를 호출한다.

service는 Http 통신 (request나 response 같은 객체) 를 받지 않고 순수한 자바로 이루어진 비즈니스 로직을 구성한다.

 

service는 매개변수만 있다면 어느 컨트롤러에서 호출하든 사용 가능한 클래스이고

객체 지향 중 OCP 원칙을 따르는 추상화를 이용한 구현 방식이 service와 serviceImpl..이라는데

1:1로 사용할 때는 추상화 없이 작성하는게 더 간단하지 않나? 싶어져서 왜 이런 설계 방식이 나오게 된 건지 궁금했다.

 

OCP

소프트웨어 요소는 새로운 기능을 추가하거나 변경할 수 있어야 한다. 

기존의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있어야 한다.

 

소프트웨어 요소는 한 번 구현된 기능이 정상적으로 작동하는 한 그 구현 코드는 수정되어서는 안 된다.

새로운 기능을 추가할 때 기존 코드를 변경하지 않고 확장할 수 있어야 한다.

 

OCP 구현 방법

추상화와 인터페이스 사용 추상화를 통해 구체적인 구현을 인터페이스를 구현하는 클래스에 뺀다.
그리고 새로운 기능을 추가할땐 그 클래스에 추가한다.
패턴 활용 디자인패턴(controller, service, dao...)를 사용하여 기능을 분담한다
의존성 주입 의존성 주입을 통해 객체가 직접적으로 다른 객체를 생성하거나 의존하는 것을 방지한다

 

 

https://okky.kr/questions/351520

 

OKKY - serviceImpl 사용 이유 질문 드립니다.

안녕하세요.초보 개발자 입니다.스프링 프레임워크 사용하면서 이론적으로 공부도하고 실무에 적용도 해봤지만큰 프로젝트를 하지 않아서 그런지 service, serviceImpl를 나누는 이유를 모르겠네요.

okky.kr

 

개발자들 중에서도 추상화를 통한 설계방식을 써야되나 의견이 분분한 것 같아서

프로젝트 규모에 따라서 설계하는 사람 마음대로인 것 같다.

 

1:1인 지금은 괜찮을지 몰라도 해당 클래스의 기능 추가 등으로 메서드를 추가 해야할 경우

해당 메서드를 사용하는 쪽에서 기능이 꼬일 경우를 방지하기 위해

유지보수 측면에서 serviceImpl로 분리하는 것으로 보인다.

 

'CS > SPRING' 카테고리의 다른 글

JPQL과 SQL의 차이점  (0) 2024.08.12
JPA의 영속성 컨텍스트  (0) 2024.08.12
JPA를 사용한 간단한 CRUD 구현  (0) 2024.08.12
웹서버 WAS, SERVLET  (0) 2024.07.08
JPA  (0) 2024.06.28

웹 애플리케이션 서버 : HTTP 기반 (WAS)

HTTP 기반으로 동작
웹 서버는 정적 리소스, WAS는 프로그램 코드를 실행해 애플리케이션 로직 실행

  • 동적 HTML, HTTP API(JSON)
  • 서블릿, JSP, 스프링 MVC

예) 톰캣, Jetty, Undetow

클라이언트가 Http 요청을 보내면
웹 서버가 요청한 정보의 http 응답을 하는 방식으로 실행된다.

 

웹 시스템 구성 - WAS, DB 

WAS는 정적 리소스, 애플리케이션 로직 모두 제공 가능하지만

WAS가 너무 많은 역할을 담당해 서버 과부하가 우려된다.

 

단점 및 보완방법

  • 애플리케이션 로직이 동작하는 WAS 서버는 잘 죽는다.
  • 가장 비싼 애플리케이션 로직이 정적 리소스 때문에 수행이 어려울 수 있음

정적 리소스만 제공하는 웹 서버는 잘 죽지 않는다.

WAS 장애시 오류 화면도 노출 불가능하기 때문에 정적 리소스는 웹서버가 처리한다.

WAS는 애플리케이션 로직 처리를 전담한다

 

장점

효율적인 리소스 관리가 가능하다

정적 리소스가 많이 사용되면 웹서버 증설

애플리케이션 리소스가 많이 사용되면 WAS 증설

  • 정적 리소스만 제공하는 웹 서버는 잘 죽지 않음
  • 애플리케이션 로직이 동작하는 WAS 서버는 잘 죽음
  • WAS, DB 장애시 WEB 서버가 오류 화면 제공 가능

웹 애플리케이션 서버를 직접 구현하기 위해

서블릿을 지원하는 WAS 사용

 

서블릿

1. 웹 브라우저에서 요청을 하면 url이 호출되면서 서블릿 코드가 실행

2. 생성된 요청 메세지 기반으로 request, response 객체 생성

3. http 응답 정보를 사용하는 HttpServletRequest

4. http 응답 정보를 제공하는 HttpServletResponse

 

서블릿 컨테이너

  • 톰캣처럼 서블릿을 지원하는 WAS
  • 서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기 관리
  • 서블릿 객체는 싱글톤으로 관리
    • 객체를 하나만 만든다.
    • 고객의 요청이 올 때 마다 계속 객체를 생성하는 것은 비효율적이다.
    • 최초 로딩 시점에 서블릿 객체를 미리 만들어두고 재활용한다.
    • 모든 고객의 요청사항은 동일한 서블릿 객체 인스턴스에 접근한다.
    • 공유 변수 사용 주의
    • 서블릿 컨테이너 종료시 함께 종료
  • JSP도 서블릿으로 변환 되어 사용
  • 동시 요청을 위한 멀티 쓰레드 처리 지원

쓰레드

 

애플리케이션 코드를 하나하나 순차적으로 실행하는 것은 쓰레드

자바 메인 메서드를 처음 실행하면 main이라는 이름의 쓰레드가 실행

쓰레드가 없다면 자바 애플리케이션 실행이 불가능

쓰레드는 한번에 하나의 코드 라인만 수행

동시 처리가 필요하면 쓰레드를 추가로 생성

 

클라이언트 요청 -> 쓰레드 요청 -> 서블릿 실행

요청마다 쓰레드 생성의 장단점

장점 단점
동시 요청을 처리할 수 있다 고객의 요청이 올때마다 쓰레드를 생성하면 응답 속도가 늦다
리소스(CPU, 메모리)가 허용할 때 까지 처리가능 쓰레드는 컨텍스트 스위칭 비용이 발생한다
하나의 쓰레드가 지연 되어도 나머지 쓰레드는 정상 동작 쓰레드 생성에 제한이 없다.
  고객 요청이 너무 많이 오면 CPU, 메모리 임계점을 넘어 서버가 죽는다

 

쓰레드 풀

요청마다 쓰레드 생성의 단점 보완

 

특징

필요한 쓰레드를 쓰레드 풀에 보관하고 관리한다

쓰레드 풀에 생성 가능한 쓰레드의 최대치를 관리한다. 톰캣은 최대 200개 기본 설정 (변경 가능)

 

사용

쓰레드가 필요하면 이미 생성되어 있는 쓰레드를 쓰레드 풀에서 꺼내 사용한다.

사용을 종료하면 쓰레드 풀에 해당 쓰레드를 반납한다

최대 쓰레드가 모두 사용중이라 쓰레드 풀에 쓰레드가 없으면

기다리는 요청은 거절당하거나 특정 숫자만큼 대기하도록 설정할 수 있다

 

장점

쓰레드가 미리 생성되어있으므로 쓰레드를 생성하고 종료하는 비용이 절약되고 응답 시간이 빠르다

생성 가능한 쓰레드의 최대치가 있으므로 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리된다

 

WAS의 멀티 쓰레드 지원

멀티 쓰레드에 대한 부분은 WAS가 처리

개발자가 멀티 쓰레드 관련 코드를 신경쓰지 않아도 됨

개발차는 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스 코드 개발

멀티 쓰레드 환경이므로(서블릿, 스프링빈) 주의해서 사용

 

HTML

동적으로 필요한 HTML 파일을 생성해서 전달

웹 브라우저 HTMl 해석

 

HTML이 아니라 데이터 전달

주로 JSON 형식 사용

다양한 시스템에서 호출

 

웹 브라우저 -> was (db) -> db

 

HTTP API

다양한 시스템에서 호출

데이터만 주고 받음. UI화면이 필요하면 클라이언트가 별도 처리

앱, 웹 클라이언트, 서버간 통신

 

주로 JSON 형태로 데이터 통신

  • UI 클라이언트 접점
  • 앱 클라이언트(아이폰, 안드로이드, PC)
  • 웹 브라우저에서 자바스크립트를 통한 HTTP API 호출
  • REACT, VUE.js같은 웹 클라이언트

서버 to 서버

  • 주문 서버 -> 결제 서버
  • 서버간 리소스 통신

서버사이드 렌더링 - SSR

서버에서 최종 HTML을 생성해 클라이언트에 전달

동적으로 실행

 

클라이언트 사이드 렌더 - CSR

HTML 결과를 자바스크립트를 사용해 웹브라우저에서 동적으로 생성하여 적용

주로 동적인 화면에 사용, 웹 환경을 마치 앱처럼 필요한 부분부분 변경 가능

예) 구글 지도, Gmail, 구글 캘린더

관련기술: React, Vue.js

'CS > SPRING' 카테고리의 다른 글

JPQL과 SQL의 차이점  (0) 2024.08.12
JPA의 영속성 컨텍스트  (0) 2024.08.12
JPA를 사용한 간단한 CRUD 구현  (0) 2024.08.12
serviceImpl 을 사용하는 이유  (1) 2024.07.23
JPA  (0) 2024.06.28

JPA는 ORM 기술을 구현하기 위한 인터페이스를 제공하는 프레임워크이다.

ORM은 객체 지향 프로그래밍의 객체와 절차형 데이터베이스의 테이블을 연결해준다.

ORM

스프링 프로그래밍을 할때 사용되는 class와 db의 테이블을 연결한다.

기존에는 xml로 sql문을 작성해 자바와 db 사이에 데이터 전송을 했다면

jpa 프레임워크는 xml 작성 필요 없이 class의 객체를 db의 테이블에 자동으로 연결해주는 것이다.

 

Entity

데이터베이스의 테이블에 매핑되는 자바 클래스이다.

예를 들어 사용자 정보를 담고 있는 데이터베이스 테이블이 있다면 이를 매핑한 클래스가 Entity이다.

 

각각의 엔티티는 @Entity 어노테이션을 클래스 위에 붙여

JPA에게 이 클래스가 데이터베이스의 테이블과 매핑된다는 것을 명시한다.

 

EntityManager

EntityManager는 엔티티를 관리하고 DB와의 상호작용을 처리한다.

EntityManager을 사용하여 엔티티를 데이터베이스에 저장하거나 조회하며, 트랜잭션을 관리할 수 있다.

매핑

JPA에서는 객체와 테이블 간의 매핑을 설정한다.

객체의 필드는 테이블의 컬럼과 매핑하고, 객체 간의 관계도 데이터베이스의 관계로 매핑된다.

 

매핑 설정은 어노테이션을 통해 이루어지며 예를 들어 @Column, @JoinColumn, @OneToOne, @OneToMany

등을 사용하여 설정된다.

JPQL

JPQL은 JPA에서 사용되는 객체 지향 쿼리 언어이다. SQL과 비슷하지만 엔티티와 필드를 기준으로

쿼리 작성이 가능하다.

 

JPQL을 사용하여 데이터베이스 조회를 객체 지향적으로 처리 가능하다.

 

트랜잭션

JPA는 트랜잭션을 지원하여 여러 데이터베이스 작업을 논리적 단위로 묶어서 실행한다.

이를 통해 데이터 일관성을 유지하고 예외 발생 시 롤백한다.

 

@Transactional 어노테이션을 사용해 메서드나 클래스에 트랜잭션을 설정한다.

 

장점

SQL문이 아닌 메서드를 통해 DB를 조작한다 객체 모델만을 이용하여 비즈니스 로직 구성이 가능하다.
sqlTemplate와 같이 선언문, 할당 등의 부수적인 코드를 작성 안해도 된다 각종 객체에 대한 코드를 별도로 작성하여 코드의 가독성을 높인다.
객체지향적인 코드 작성이 가능하다. 객체지향적 접근만 되기 때문에 생산성이 증가한다.
매핑하는 정보가 Class로 명시 되어있다. ERD의 의존도를 낮출 수 있고 리팩토링이 쉽다.
새로 쿼리를 짜야하는 경우가 생겼을때 대처가 쉽다 MySQL에서 PostgreSQL로 변환할 경우 쿼리를 수정할 필요가 없다.

 

단점

프로젝트의 규모가 크고 복잡하여 설계가 잘못된 경우 속도 저하 및 일관성을 무너뜨린다
복잡하고 무거운 쿼리는 별도의 튜닝이 필요하기 때문에 결국 SQL문을 써야된다.

 

JPA의 역할

jpa는 이 orm 기술을 구현하기 위한 인터페이스를 제공한다.

jpa를 사용하여 객체와 데이터베이스 테이블 간의 매핑 설정을 정의하고

이를 통해 crud작업을 수행할 수 있는 것이다.

 

jpa는 인터페이스이므로 db나 환경에 종속되지 않으며 다양한 orm에서 구현 가능하다.

 

JPA 구현체

jpa의 구현체에는 Hibernate, OpenJPA, EclipseLink 등이 있다.

이 구현체들은 JPA를 표준으로 정의한 인터페이스를 구현하여 제공한다.

따라서 JPA를 사용하여 개발자가 데이터베이스와 객체를 관리할 수 있도록 도와주는 역할을 하는 것이다.

 

'CS > SPRING' 카테고리의 다른 글

JPQL과 SQL의 차이점  (0) 2024.08.12
JPA의 영속성 컨텍스트  (0) 2024.08.12
JPA를 사용한 간단한 CRUD 구현  (0) 2024.08.12
serviceImpl 을 사용하는 이유  (1) 2024.07.23
웹서버 WAS, SERVLET  (0) 2024.07.08

+ Recent posts