반응형

 

전공 수업에서 노드와 자바스크립트를 이용해 Express와 연동하여 웹 프로그램을 만든 적이 있다.

그땐 자바스크립트랑 노드의 개념을 잘 모르고 무작정 따라하기만 했다.

 

깃허브를 정리하던 중 수업 때 만든 프로그램을 보고, 갑자기 Node.js가 언어인지, Javascript가 언어인지 헷갈리기 시작했다.

 

자바스크립트를 프론트엔드 언어, 노드를 백엔드 언어라고 믿고 있었는데..결론은 아니다! 

 

 

💡 Node.js란?

 

Node.js는 Chrome V8 Javascript 엔진으로 빌드 된 JavaScript 런타임이다.

 

한마디로, Node.js를 통해 다양한 자바스크립트 애플리케이션을 실행할 수 있고, 특히 서버를 실행하는 데 가장 많이 사용된다!

  • Node.js는 JavaScript를 서버에서도 사용할 수 있도록 만든 프로그램이다.
  • Node.js는 V8이라는 JavaScript 엔진 위에서 동작하는 자바스크립트 런타임(환경)이다.
  • Node.js는 서버사이트 스크립트 언어가 아니다. 프로그램(환경)이다.
  • Node.js는 웹서버와 같이 확장성 있는 네트워크 프로그램을 제작하기 위해 만들어졌다.

단순히 정적인 홈페이지, 웹에서 실행가능한 게임을 만들려면 javascript만으로도 가능하다.

하지만 웹 브라우저가 아닌 환경에서는 javascript는 동작하지 않는다.

따라서 웹 외에 독립적인 공간에서도 javascript만으로 프로그램이 실행되도록 하기 위해 Node.js라는 환경이 개발된 것이다!!

 

Node.js를 통해 정적인 홈페이지 뿐만 아니라 데이터가 변해가는 사이트(쇼핑몰, 티켓예매사이트, 블로그 등)를 만들 수 있으며, 여러 개발자가 만든 프로그램과 게임을 웹상에서 구동시켜 안드로이드 폰, 아이폰, 윈도우 pC 등 플랫폼의 제약에서도 벗어날 수 있게되었다.

 

물론 단순히 웹에서 실행 가능한 게임을 만들려면 JavaScript 만으로도 가능하지만 좀 더 진화 된 프로그램으로 실시간 온라인 채팅, 실시간 온라인 게임 등 실시간 기능을 넣거나, 로그인 기능을 넣어 유저를 관리하고 점수를 관리하는 데이터베이스 기능을 Node.js를 통해 만들 수가 있다.

 

💡 Node.js를 사용하는 이유

  • Node.js를 사용하려면 먼저 JavaScript를 배워야한다.
  • Node.js는 JavaScript를 사용하기 위해  만들어진 것이기 때문이다.
  • JavaScript는 C/C++, Java 와 같은 프로그래밍 언어이다.
  • 하지만 이름에서 알 수 있듯 JavaScript는 독립적인 언어가 아닌 스크립트 언어이다.
  • 스크립트 언어는 특정한 프로그램 안에서 동작하는 프로그램이기 때문에 웹 브라우저 프로그램 안에서만 동작을 한다.
  • 즉, 웹 브라우저(크롬, 사파리, 익스플로러, 파이어폭스 등)가 없으면 사용할 수 없는 프로그램이다.
  • 여기서 Node.js가 나오는 이유가 된다.
  • 즉, JavaScript 를 웹 브라우저에서 독립시킨 것으로 Node.js를 설치하게 되면 터미널프로그램(윈도우의 cmd, 맥의 terminal 등)에서 Node.js를 입력하여 브라우저 없이 바로 실행할 수 있다.
  • 하지만 JavaScript에서 분리된 언어이기 때문에 문법은 같다.
  • 이렇게 Node.js를 이용하여 웹 브라우저와 무관한 프로그램을 만들 수 있게 되었다.
  • 중요한 것은 Node.js를 이용하여 서버를 만들 수 있다는 것이다.
  • 중요한 이유는 이전까지 Server-Client 웹사이트를 만들 때 웹에서 표시되는 부분은 JavaScript 를 사용하여 만들어야만 했으며, 서버는 Reby, Java 등 다른 언어를 써서 만들었어야 했는데 마침내 한 가지 언어로 전체 웹 페이지를 만들 수 있게 된 것이다.

 

✅ 자바스크립트 런타임

  • 런타임이란 특정 언어로 만든 프로그램을 실행할 수 있는 환경이다.
  • 따라서 노드는 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있게 하는 자바스크립트 실행기!!

 

⭐ 결론

Node.js는 Javascript 만으로도 웹 브라우저에서 독립된 환경에서 프로그램을 실행가능하게 해주는 환경(프로그램)이다.

즉, Node.js=환경, Javascript=언어

Javascript를 사용하기 때문에 JSON형식과 쉽게 호환된다.

Node.js의 프레임워크가 Express이다.

 

 

 

한 블로그 글을 통해 두 개념을 이해하는데 많은 도움이 되었다.

 

https://hanamon.kr/nodejs-%ea%b0%9c%eb%85%90-%ec%9d%b4%ed%95%b4%ed%95%98%ea%b8%b0/#respond

 

Node.js 노드 개념 이해하기 자바스크립트 JavaScript 런타임 이벤트

Node.js 노드 개념 이해하기 JavaScript 런타임 - 노드는 다양한 자바스크립트 애플리케이션을 실행할 수 있으며, 서버를 실행하는데 제일 많이 사용된다. 이벤트 기반 이벤트 루프 논블로킹 I/O 싱글

hanamon.kr

 

반응형

'Web Application' 카테고리의 다른 글

REST API  (0) 2023.12.29
HTTP status code (HTTP 상태 코드)  (0) 2023.12.28
HTTP Request Methods  (0) 2023.12.23
쿠키(cookie)와 세션(session)의 차이  (1) 2023.12.22
브라우저 동작 방법  (0) 2023.12.20
반응형

한 페이지에서 큰 단위로 조회 결과가 나오는 경우 한눈에 보기 어렵고 원하는 결과를 찾기도 어렵다.

이를 위해 일정한 단위만큼 나누어 페이지를 넘기도록 하는 기능을 구현하고자 한다.

 

회원 정보를 출력하는 메인 페이지에서 페이징 기능을 추가하도록 하였다.

 

스프링 프레임워크에서 제공하는 Pageable 인터페이스를 사용해보자.

 


Pageable 인터페이스?

 

Pageable 인터페이스는 페이지 번호, 페이지 크기, 정렬 조건 등을 설정할 수 있는 메서드를 제공한다. 이를 통해 검색 결과를 페이지 단위로 나누고 원하는 페이지를 가져오는 등의 작업을 수행할 수 있다.

 

  • 주요 메서드와 기능
  • getPageNumber(): 현재 페이지 번호를 반환합니다. 0부터 시작합니다.
  • getPageSize(): 페이지 크기(한 페이지에 포함되는 아이템 수)를 반환합니다.
  • getOffset(): 현재 페이지의 시작 인덱스(오프셋)를 반환합니다. 페이지 번호와 페이지 크기를 이용하여 계산됩니다.
  • getSort(): 정렬 조건을 반환합니다.
  • next(): 다음 페이지를 반환합니다.
  • previousOrFirst(): 이전 페이지를 반환합니다. 첫 번째 페이지인 경우 첫 번째 페이지를 반환합니다.
  • first(): 첫 번째 페이지를 반환합니다.
  • hasNext(): 다음 페이지가 있는지 여부를 확인합니다.
  • hasPrevious(): 이전 페이지가 있는지 여부를 확인합니다.

페이징 기능 구현을 위해 아래의 글을 참고하였다.

 

9. 스프링부트와 타임리프로 게시판 만들기 - 페이징

페이징 - 백엔드 개념 부분 페이징이란? 페이징이란 여러 게시물을 볼 때 일정 갯수 이상이 넘어가면 다음 페이지에 존재할 수 있게 하는 것을 의미합니다. 위와 같이 게시글이 일정 갯수가 넘어

velog.io

 

1. @PageableDefault으로 페이지 정보 view로 전달

환자 정보 리스트를 화면에 출력하는 main페이지가 요청될 때 페이지 정보를 view에 함께 전달해야 한다.

@GetMapping("/main") //환자 정보 리스트
public String main(Model model, HttpSession session, @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.ASC) Pageable pageable) {

    Page<PatientEntity> patientEntityList = memberService.findAll(session, pageable);
    int nowPage = patientEntityList.getPageable().getPageNumber()+1;
    int startPage = Math.max(nowPage-4, 1);
    int endPage = Math.min(startPage+9, patientEntityList.getTotalPages());

    model.addAttribute("nowPage", nowPage);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("patientList", patientEntityList);
    return "main2";
}
  • @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.ASC) Pageable pageable
    : Pageable 인터페이스에서 page는 0부터 시작한다. 한 페이지에 보일 레코드의 개수(size)를 10으로 설정하였다. sort는 정렬 기준으로, PatientEntity의 id 필드를 기준으로 정렬한다. direction은 정렬 방식으로 DESC(내림차순)와 ASC(오름차순)으로 설정할 수 있다. 이 초기값을 바탕으로 Pageable 인터페이스를 생성한다.
  • Page<PatientEntity> patientEntityList = memberService.findAll(session, pageable);
    : 페이지 기능으로 하기 전에는 List형태였으나, 이제 Pageable을 사용하기 때문에, Page로 가져올 것이다. 타입 역시 Entity타입으로 가져온다. 
    서비스의 findAll함수를 pageable을 추가한 것에 맞게 수정해야 한다!!!
  • int nowPage = patientEntityList.getPageable().getPageNumber()+1
    : 현재페이지에 대한 정보이다. pageable은 페이지가 0부터 시작하므로 +1을 해줌으로써 1페이지부터 시작하도록 한다.
  • int startPage = Math.max(nowPage-4, 1)
    : 표시될 시작페이지에 대한 정보이다. 현재 페이지 앞에 최대 4개까지 보이도록 하고, 4개보다 적다면 1페이지부터 표시되도록 한다.
  • int endPage = Math.min(startPage+9, patientEntityList.getTotalPages())
    :표시될 마지막페이지에 대한 정보이다. 시작페이지에 9를 더한 값의 페이지까지 보이도록 하여 총 10개만 보이도록 하였고, 최대 페이지를 넘지는 않도록 하였다.
  • 각 값들을 model의 attribute로 view에 전달한다.

 

2. 서비스의 findAll()함수에 Pageable 기능을 하도록 수정

public Page<PatientEntity> findAll(HttpSession session, Pageable pageable){
        Long Id = (Long) session.getAttribute("loginId");
//        List<PatientEntity> patientEntityList = patientRepository.findByMemberEntity_Id(Id);
//        List<PatientDTO> patientDTOList = new ArrayList<>();
//        for(PatientEntity patientEntity: patientEntityList){//여러개의 entity를 여러개의 dto로 하나씩 담기위해
//            patientDTOList.add(PatientDTO.toPatientDTO(patientEntity));
//        }
        return patientRepository.findByMemberEntity_Id(Id, pageable);
    }

서비스 함수의 반환 타입을 Page 형태로 PatientEntity들이 전달되도록 수정하였다. 그리고 Pageable 인터페이스를 인자로 받는다.

주석처리된 부분은 페이징 처리 전에 List형태로 받았던 코드이다.

레포지토리의 findByMemberEntity_Id함수를 수정하여 그 반환형태 자체를 반환하도록 하였다.

 

 

3. 레포지토리 findByMemberEntity_Id함수 수정하기

Page<PatientEntity> findByMemberEntity_Id(Long id, Pageable pageable);

pageable 정보를 넘겨주면 조회결과 레코드를 10개씩 나누어 페이지로 알아서 반환해준다.

 

 

4.view에서 페이징 처리하기!

<div class="card-footer py-4">
  <nav aria-label="...">
    <ul class="pagination justify-content-end mb-0">
      <li class="page-item disabled">
        <a class="page-link" href="#" tabindex="-1">
          <i class="fas fa-angle-left"></i>
          <span class="sr-only">Previous</span>
        </a>
      </li>
      <th:block th:each="page:${#numbers.sequence(startPage,endPage)}">
        <li class="page-item" th:class="${page == nowPage} ? 'page-item active' : 'page-item'">
          <a class="page-link" th:if="${page != nowPage}" th:href="@{/main(page=${page-1})}" th:text="${page}"></a>
          <a class="page-link" th:if="${page == nowPage}" th:text="${page}" style="color:white; font-weight:bold;"></a>
        </li>
      </th:block>
      <li class="page-item">
        <a class="page-link" href="#">
          <i class="fas fa-angle-right"></i>
          <span class="sr-only">Next</span>
        </a>
      </li>
    </ul>
  </nav>
</div>
  • 첫번째 li는 이전 페이지로 이동하는 버튼을 구현한것이다
  • 두번째 li가 현재 페이지를 포함한 10개의 페이지 버튼을 보이도록 구현한것이다.
    th:each를 통해 model로 전달된 startPage와 endPage의 정보를 가지고, 각 페이지를 표시하도록 한다.
    각 페이지를 순회하면서 현재 활성화된 페이지와 같으면, page-link 스타일을 가지면서, 페이지 번호는 white색이며, 볼드체로 표시되도록 하였고, 활성화된 페이지가 아니면, page-link스타일을 가지면서 페이지 번호에 해당 페이지 링크를 걸어주었다.
  • 세번째 li가 다음 페이지로 이동하는 버튼을 구현한 것이다.

 

5. 구현결과  

페이지 버튼 성공!!

5페이지 까지는 1이 첫번째고, 10이 최대며, 그다음부터는 1씩 늘어나며 shift되는 것을 확인하였다!

잘 동작한다!!! 디자인도 잘 적용되었다!

반응형

반응형

서비스 함수에서 jpa repository에 정의된 쿼리 함수를 호출하는데에서 위와 같은 "No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call" 오류가 발생하였다.

 

오류가 발생한 코드의 위치

remove 명령을 실행하는 과정에서 트랜잭션과 관련된 문제가 발생한 것을 확인할 수 있다.

 

알아보니 이 오류는 스프링 트랜잭션 관련 문제로, 현재 스레드에 실제 트랜잭션이 없기 때문에 'remove' 호출을 신뢰할 수 없다는 것을 의미한 것이었다.

 

위 오류는 다음 두 가지 상황에서 발생할 수 있는데,

 

첫번째, 스프링의 트랜잭션 관리 설정이 올바르게 구성되어 있는지?

예를 들어, @EnableTransactionManagement 어노테이션이 설정되어 있는지 확인하고, 트랜잭션 관리자가 적절히 설정되어 있는지 확인해야 한다!

 

두번째, 트랜잭션 어노테이션이 메서드나 클래스에 적용되지 않은 경우

@Transactional 어노테이션을 메서드나 클래스에 추가하여 트랜잭션을 활성화해서 해결할 수 있다!

 

나의 경우 두번째에 해당!!!!

 

나의 코드를 보면, 트랜잭션 경계를 벗어난 상황에서 레포지토리 함수를 호출하고 있다. 

스프링에서 트랜잭션은 일반적으로 서비스 레이어에서 관리되는데, 다른 레이어(컨트롤러)에서 트랜잭션 경계를 벗어나서 호출하면 이러한 오류가 발생할 수 있다.

따라서 트랜잭션 범위 내에서 해당 호출을 수행하도록 코드를 구성해야 한다!

 

아래와 같이 해결하였다.

 

서비스 레이어에서 트랜잭션을 관리하도록 어노테이션 추가

 

 

이전에 비슷한 오류가 발생하였을 때, 레포지토리 함수 자체에 @transactional 어노테이션을 추가하여 해결한 적이 있다.

 

그렇다면, 레포지 함수 자체에 어노테이션을 추가하는 것과, 이를 호출하는 서비스 함수에 어노테이션을 추가하는 것 중 어느것이 더 적절한 방법일까?

 

일반적으로 서비스 계층에서 @Transactional 어노테이션을 추가하는 것이 더 좋은 설계이다!!!

 

이유는 다음과 같다

 

  1. 레이어 간의 역할 분리 : 서비스 계층은 비즈니스 로직을 처리하는 데 책임이 있으며, 트랜잭션 관리 역시 서비스 계층에서 이루어져야 한다. 따라서 트랜잭션 관리는 서비스 계층의 역할이며, 레포지토리는 데이터 액세스에 집중해야 한다. 이를 위해 트랜잭션 관리는 서비스 계층에 위임하는 것이 좋다!
  2. 트랜잭션 경계의 명확성: 서비스 계층에서 @Transactional 어노테이션을 추가하면 트랜잭션의 범위가 서비스 메서드의 호출과 일치하게 된다. 이는 트랜잭션 경계를 명확하게 설정하여 예기치 않은 동작을 방지하는 데 도움이 된다.
  3. 트랜잭션 관리의 유연성: 서비스 계층에서 트랜잭션 관리를 담당하면 여러 레포지토리 메서드를 호출하는 복잡한 비즈니스 로직에서도 단일 트랜잭션으로 묶을 수 있다. 이는 일관된 데이터 처리와 롤백을 보장하여 데이터의 무결성을 유지하는 데 도움이 된다.

따라서, 가능하다면 레포지토리에서 직접 @Transactional 어노테이션을 추가하는 대신 해당 함수를 호출하는 서비스 계층에서 @Transactional 어노테이션을 추가하는 것이 좋다.

 

이렇게 하면 역할과 책임이 분리되며, 트랜잭션 관리가 명확하고 유연하게 이루어질 수 있다!!!

 

"트랜잭션 관리를 명확히 하기!!!"

반응형

+ Recent posts