템플릿 엔진이란 지정된 템프릿과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어를 지칭한다.
서버 탬플릿 엔진과 클라이언트 탬플릿 엔진으로 구분된다.
서버 탬플릿 엔진은 서버에서 DB 혹은 API에서 가져온 데이터를 미리 정의된 Template에 넣어 html을 그려서 클라이언트에 전달해주는 역할을 한다.
클라이언트 탬프릿 엔진은 html 형태로 코드를 작성할 수 있으며, 동적으로 DOM을 그리게 해주는 역할을 한다.
내가 사용할 탬플릿 엔진은 머스테치로 심플한 서버 템플릿 엔진이다.
우선 기본적인 index 화면을 구성해 보았다.
기본적인 index 페이지의 경로는 src/main/resources/templates 를 기준으로 한다.
해당위치에 index.mustache를 생성해 준다.
이제 해당 페이지를 호출할 컨트롤러를 구현해보자.
이제 테스트코드로 정상적으로 호출이 되는지 확인해 보도록 한다.
결과는 성공.
이제 간단한 게시글 등록 화면을 만들어 보자.
기본적으로 화면에 관련된 HTML과 js는 이동욱님의 github에서 가져와서 사용할 예정이다.
우선 해당화면을 구현하기 위해서 CDN 방식으로 제이쿼리와 부트스트랩을 이용한다.
스크립스 관련 부분은 footer에 배치하고 CSS에 관련된 부분은 header에 배치했다.
이유는 페이지 로딩속도를 향상시키기 위해서 인데,
HTML은 head 부분이 먼저 실행이 끝나야 body가 실행되는 구조이므로
head쪽에서 불필요하게 js를 CDN하면 해당 js를 모두 읽기 전까지 화면 그리는 시간이 늘어진다.
그리고 footer에서 제이쿼리와 부스트스트랩의 CDN도 이와 같은 순차적인 처리 특성을 고려해야 한다.
부트스트랩은 제이쿼리에 의존성을 가지고 있기 때문에 제이쿼리의 CDN을 부트스트랩 앞쪽으로 선언했다.
index에서 사용할 js를 작성하자.
각 js별 중복된 함수 이름이 발생할 경우 오버라이트되는 불상사를 피하기 위해
index.js의 속성으로 함수를 생성하는 방식으로 접근하는 것을 책에서 권장하고 있다.
나도 실무에서 이러한 문제를 상당히 자주 겪어봤기 때문에 해당 추천방식으로 사용하고 있다.
header에서 css를 선언.
footer에서 js 선언.
index.js의 내용들
이제 index.mustace에 header와 footer를 포함시켜야 한다.
기존에 jsp를 사용했을때 include와 비슷한 역할을 머스태치에도 가지고 있다.
부분템플릿이라고 하는데, 외부 파일을 템플릿의 일부로 불러올 수 있는 기능으로서 {{> 파일이름}}과 같이 사용한다.
header와 footer는 templates/layout 아래에 위치하기 때문에
{{>layout/header}}, {{>layout/footer}} 로 선언해서 접근하도록 한다.
이제 게시글 등록화면을 호출하기 위한 url을 담당할 controller 메소드를 추가하자.
우선 index화면을 접근해보자.
수정한 Ver.2 로 타이틀이 정상적으로 수정되있는 것을 확인했다.
이제 등록화면으로 접근하기 위해 글 등록 버튼을 클릭하자.
정상적으로 화면이 호출 되었다.
테스트를 위해 필드에 적당한 값들을 입력한다.
이제 등록 버튼을 클릭하면
성공했다는 alert 메세지가 출력되었다.
확실한 확인을 위해 DB데이터 확인하자.
등록시간 수정시간이 일치하고 입력된 값들이 원하는 DB column에 잘 들어간 것을 확인 할 수 있다.
이제 정상적으로 입력된 데이터들을 목록으로 조회하는 화면을 만들어 보자.
데이터 목록은 index화면에서 출력을 하도록 할 예정이므로 index.mustach를 수정해야 한다.
목록영역을 추가하자.
머스태치 문법은 변수선언 방식이다.
{{, }} 사이에 변수 이름을 입력한다. 문자열은 자동으로 HTML 이스케이프가 되어 출력되는데 만약 이스케이프 되지 않은 문자열을 출력하고 싶다면 {{{값 }}}를 사용한다.
일단 이제 조회 쿼리를 작성하도록 하자.
단순하게 Posts 테이블의 전체 데이터를 PK값의 내림차순으로 검색해서 가져오도록 해보자.
여기서 나는 한가지 중대한 실수를 했다.
테이블 alias를 정의하는 p를 실수로 대문자로 작성했다.
이렇게 되면 Hibaernate에서 에러가 발생할 것이다. 수정하자.
화면에서 필요로 하는 필드값 id, title, author, modifedDate를 담을 Dto를 작성해야 한다.
이제 service에 해당 쿼리를 호출하도록 메소드를 만들자.
여기서 잠간 체크할 부분은 @Transactional 의 readOnly 속성이다.
이 값을 true 사용하면 해당하는 트랜잭션은 읽기전용이되는데,
왜 트랜잭션 선언이 필요한지를 알아야 한다.
우리가 쓰고 있는데 JPA는 Hibernate를 구현체로 사용한다.
Hibernate는 트랜잭션 단위로 영속성 컨텍스트를 관리하며 한 트랜잭션안에서의 값 변경에 대한 더티체크를 항시 수행한다.
등록/수정이 없는 조회성 메소드에서 불필요한 더티체크를 수행하지 않도록 하면 성능항 이점이 생기기 때문에 readonoly= true로 설정해주는 것으로 이해하고 있다.
다만 이 기능은 JDBC 제조사 별로 버전에 따라 기능이 지원이 안될 수 있다는 점을 감안해야 한다.
그리고 Dto로 반환하는 부분을 steam을 사용하여 깔끔하게 코드를 구현했다.
TDD 교육과정에서 배운 코드컨밴션 내용중에 하나인 '라인하나에 .을 하나만 찍는다'를 지키는 코드 작성을 하기 위해 '.' 단위로 개행을 했다.
이제 기존에 조회화면을 호출하는 메소드를 수정해야 한다.
index.mustace에서 조회되는 내용을 {{posts}}로 선언하여 바인딩하고 있기 때문에 이 기준에 맞도록 model에 addAttribute를 사용해서 조회값을 담아 보낼 것이다.
이제 새롭게 다시 어플리캐이션을 실행하자.
아무 목록이 없는 빈화면이 잘 출력되었다.
이제 데이터를 등록 해보자.
잘 등록되고 조회가 되는 것을 확인했다.
일전에 작성한 게시글 수정 서비스 메소드를 활용하여 게시글 수정 기능을 만들어 보자.
수졍화면을 만들어야 한다.
기존에 만든 posts-save.mustache와 다른점을 간단히 살펴보자.
우선 각 필드값 부분에 값 바인딩을 위한 변수 선언들이 보인다.
조회된 데이터가 각 변수별로 바인딩되서 출력이 될 것이다.
그리고 기능을 위한 버튼들이 하단에 추가되었다.
해당 버튼들의 클릭 이벤트관련 코드는 일전에 작성한 index.js에 작성되있다.
이제 등록된 글의 id값으로 데이터를 조회해서 이를 수정화면으로 보낼 컨트롤러 메소드를 추가한다.
재기동하면 기존의 데이터는 소거된 상태가 되기때문에
새롭게 데이터를 다시 등록 후 해당 게시글의 제목을 클릭해서 수정화면으로 진입하자.
정상적으로 id값에 대한 데이터가 조회되서 화면에 출력된 것을 확인 할 수 있다.
해당 글의 글 번호와 작성자는 수정되면 안되기 때문에 readonly 속성으로 인해 수정이 불가한 필드로 선언되 있다.
이제 제목과 내용을 수정해보자.
정상적으로 수정이 잘 되었다.
목록에서도 제목과 수정시간이 변경된 것이 확인된다.
이제 수정화면에 추가된 삭제버튼의 기능을 만들어 보자.
JPA에서 제공하는 삭제 인터페이스를 사용할 예정이므로 별도의 repository 구현체 작성없이 바로 서비스 메소드를 작성하도록 하자.
위의 코드는 조회를 한 뒤에 해당 entity를 대상으로 삭제하도록 하는 방식으로 구현되어있다.
단순하게는 postsRepository.deleteById(id); 를 통해서 해당 id값의 데이터 소거를 수행할 수 있지만, 안정적인 삭제 프로세스를 위해 조회를 해서 데이터가 있는 경우에 해당 데이터를 삭제하도록 하는 방식으로 코드가 구현되었다.
이제 해당 API를 호출하기 위해 PostsApiController에 삭제 메소드를 추가하자.
정상적으로 삭제되었다.
목록에서도 정상적으로 삭제된 것이 확인된다.
이번장을 통해서 머스태치의 간단한 사용방법과 api의 기능을 연결하여 CRUD(등록/조회/수정/삭제) 4가지 기능을 간단하게 구현해 보았다.
간단한 샘플 코드이지만 JPA의 특성을 간략히 맛볼 수 있었다.
특히 더티체크를 통한 update 쿼리를 자동으로 생성하는 부분은 JPA를 안써본 분들이라면 매우 색다른 경험이였을 것이다.
그리고 템플릿 엔진에 대한 간단한 개념정리를 할 수 있었고 화면의 로딩속도를 위한 최적화 방법들도 고민해볼 수 있었다.
또 브라우저의 전역변수 충돌에 대한 대비 방안도 배울 수 있었다.
JPA에 대한 상세한 포스트도 추후에 한번 작성해봐야 할 것 같다.