-
코드로 배우는 스프링부트 웹 프로젝트 Day 4Spring/코드로 배우는 스프링부트 웹 프로젝트 2022. 8. 16. 11:43
/* Annotation들은 스프링부트 프로젝트의 Annotation 정리 페이지에 따로 정리해두었습니다. */
[레이아웃 템플릿 만들기]

파일 위치 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <style> * { margin: 0; padding: 0; } .header { width:100vw; height: 20vh; background-color: aqua; } .content { width: 100vw; height: 70vh; background-color: lightgray; } .footer { width: 100vw; height: 10vh; background-color:green; } </style> <div class="header"> <h1>HEADER</h1> </div> <div class="content" > <h1>CONTENT</h1> </div> <div class="footer"> <h1>FOOTER</h1> </div> </body> </html>
layout1.html 후에 CONTENT 부분을 다른 내용으로 채워야 하기 때문에 해당 부분을 변경 가능하도록 수정
layout1.html 상단 부분
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <th:block th:fragment="setContent(content)"> <head>content 이름의 파라미터를 가지는 setContent를 th:fragment로 지정
layout1.html 중간 부분
<div class="content" > <th:block th:replace = "${content}"> </th:block> </div>content부분은 상단부분에서 파라미터로 받은 content로 대체하여 출력하도록 함
layout1.html 하단 부분
</body> </th:block> </html>SampleController클래스의 exLayout1
@GetMapping({"/exLayout1", "/exLayout2", "/exTemplate"}) public void exLayout1() { log.info("exLayout..........."); }layout/layout1.html 을 사용하기 위하여 앞으로 만들 /exTemplate 추가
sample폴더에 exTemplate.html 생성
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <th:block th:replace="~{/layout/layout1 :: setContent(~{this:content} )}"> <th:block th:fragment="content"> <h1>exTemplate Page</h1> </th:block> </th:block>layout/layout1의 setContent에 exTemplate.html 본인의 content를 변수로 넣어 실행되도록 함
하지만, 실행을 해보았더니 에러가 떴다..
Caused by: org.thymeleaf.exceptions.TemplateInputException: Error resolving fragment: "${content}": template or fragment could not be resolved (template: "/layout/layout1" - line 39, col 19) template이나 fragment를 확인할 수 없다고 한다.
찾아보니 절대경로를 /abcd/efg 같이 한 경우 application.properties에서 지정해놓은 디폴트 위치 끝에 / 가 있어서 //abcd/efg 같이 되기 때문에 위같은 오류가 뜨는 경우가 대부분인데 나는 application.properties에 디폴트 위치를 설정해놓지 않았다. 그럼 뭐가 문제인거지?
오타였다..
<th:block th:replace="~{/layout/layout1 :: setContent(~{this::content} )}">exTemplate 파일에서 replace 부분을 위 코드처럼 바꾼다.이전 코드는 this:content로 했으니까 당연히 안 되는거였다. 한참 해맸다🥲

/sample/exTemplate 
소스코드도 알맞게 변경된 것 확인 [부트스트랩 템플릿 적용하기]
Simple Sidebar - Bootstrap Sidebar Template - Start Bootstrap 을 다운로드 받아서 압축해제 후, 안에 있는 파일들을 복사해서 프로젝트의 static 폴더 밑에 붙여넣기
layout/basic.html 파일을 만들고 붙여넣은 파일들 중, index.html을 복사해서 basic.html에 붙여넣기
<!-- Favicon--> <link rel="icon" type="image/x-icon" th:href="@{assets/favicon.ico}" /> <!-- Core theme CSS (includes Bootstrap)--> <link th:href="@{/css/styles.css}" rel="stylesheet" /> <!-- Bootstrap core JS--> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script> <!-- Core theme JS--> <script th:src="@{/js/scripts.js}"></script>파일의 아래쪽에 있던 JavaScript 링크를 위쪽으로 옮기고 경로를 Thymeleaf에 맞게 수정

/sample/exSidebar 📍 프로젝트 구조 만들기

의존성 추가
• MariaDB관련 JDBC 드라이버
• Thymeleaf에서 사용할 java8 날짜 관련 라이브러리 추가
implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client' implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-java8time'application.properties에 데이터 베이스 관련 설정 추가
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.datasource.url=jdbc:mariadb://localhost:12345/bootex spring.datasource.username=bootuser spring.datasource.password=bootuser spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true spring.thymeleaf.cache=false설정 후, 실행해서 정상적으로 동작하는지 확인
static 폴더에 ex3에서 사용한 bootstrap sidebar관련 파일들 추가
templates 폴더에 guestbook 폴더 생성
templates 폴더에 마찬가지로 ex3에서 사용한 layout폴더 자체 추가

controller 폴더 생성 후 GuestbookController 클래스 추가
package org.zerock.guestbook.controller; import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/guestbook") @Log4j2 public class GuestbookController { @GetMapping({"/", "/list"}) public String list(){ log.info("list............"); return "/guestbook/list"; } }/templates/guestbook/list.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}"> <th:block th:fragment="content"> <h1>GuestBook List Page</h1> </th:block> </th:block>파라미터에 GuestBook List를 넘김

/guestbook/list [자동으로 처리되는 날짜/시간 설정]
데이터의 등록 시간, 수정시간 등을 자동으로 추가 및 변경할 수 있도록 어노테이션 이용해서 설정
entity/BaseEntity.java 생성
package org.zerock.guestbook.entity; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import java.time.LocalDateTime; @MappedSuperclass @EntityListeners(value = {AuditingEntityListener.class}) @Getter public class BaseEntity { @CreatedDate @Column(name = "regdate", updatable = false) private LocalDateTime regDate; @LastModifiedDate @Column(name="moddate") private LocalDateTime modDate; }@MappedSuperClass 를 이용해서 등록, 수정 시간을 사용하는 엔티티를 모음
@EntityListeners에서 AuditingEntityListeners를 통해 regDate, modDate에 값이 지정되도록 함
@Column에서 updatable을 false로 설정해서 regdate 칼럼값은 수정되지 않도록 함
@CreatedDate로 엔티티 생성 시간을 받음
@LastModifiedDate로 최종 수정 시간을 자동으로 수정하도록 함
AuditingEntityListener를 활성화시키기 위해 GuestbookApplication에 @EnableJpaAuditing 추가
@SpringBootApplication @EnableJpaAuditing public class GuestbookApplication { public static void main(String[] args) { SpringApplication.run(GuestbookApplication.class, args); } }[엔티티 클래스와 Querydsl 설정]
entity/Guestbook.java
package org.zerock.guestbook.entity; import lombok.*; import javax.persistence.*; @Entity @Getter @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class Guestbook extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long gno; @Column(length = 100, nullable = false) private String title; @Column(length = 1500, nullable = false) private String content; @Column(length = 50, nullable = false) private String writer; }BaseEntity 클래스를 상속함

테이블 정상적으로 만들어짐 repository/GuestbookRepository.java 인터페이스 생성
package org.zerock.guestbook.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.zerock.guestbook.entity.Guestbook; public interface GuestbookRepository extends JpaRepository<Guestbook, Long> { }[동적 쿼리 처리를 위한 Querydsl 설정]
JPA의 쿼리 메서드 기능 + @Query는 선언할 때 고정된 형태의 값을 가진다는 단점이 있음
단순한 검색 조건은 기본 기능으로 충분하지만, 복잡한 조합의 검색 조건이 되면 경우의 수가 많아지고 동적으로 쿼리를 생성해서 처리할 수 있는 기능이 필요해짐
📌Querydsl 은 동적 쿼리를 생성해서 처리할 수 있는 기술
📎 Querydsl을 이용하면 작성된 엔티티 클래스를 그대로 이용하는 것이 아니라 Q도메인을 이용해야 함
이를 작성하기 위해 Querydsl 라이브러리를 이용해서 엔티티 클래스를 Q도메인 클래스로 변환하는 방식을 이용할 수 있도록 설정해주기
- plugins 항목에 querydsl 관련 추가
- dependencies 항목에 필요한 라이브러리 추가
- Gradle에서 사용할 추가적인 task 추가
build.gradle
plugins { id 'org.springframework.boot' version '2.7.2' id 'io.spring.dependency-management' version '1.0.12.RELEASE' id 'java' id 'war' id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' } ``` dependencies { ``` implementation 'com.querydsl:querydsl-jpa' } tasks.named('test') { useJUnitPlatform() } def querydslDir = "$buildDir/generated.querydsl" querydsl { jpa = true querydslSourcesDir = querydslDir } sourceSets { main.java.srcDir querydslDir } configurations { querydsl.extendsFrom compileClasspath } compileQuerydsl { options.annotaitionProcessorPath = configurations.querydsl }위 코드를 입력하고 build.gradle을 갱신하면 아래와 같이 compileQuerydsl이 실행 가능해짐

이를 실행했더니
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileQuerydsl'.
> com/mysema/codegen/model/Type이러한 오류 발생
검색해보니 이 책이 2020년에 써진지라 버전이 업데이트 되면서 뭔가를 추가 설정해주어야 하는 것 같다.

querydsl 버전의 문제였다. 현재 querydsl 버전은 5.0.0 이므로 이를 명시해주어야 한다.
아래 코드를 추가하면 정상 작동한다.
buildscript { ext { querydslVersion = '5.0.0' } } ``` dependencies { ``` implementation "com.querydsl:querydsl-jpa:${querydslVersion}" implementation "com.querydsl:querydsl-apt:${querydslVersion}" }정상 작동된 것을 확인한 후, build폴더에 다음과 같이 파일들이 생성된다.

이렇게 생성된 파일들은 모두 Q로 시작함
생성된 QGuestBook 클래스에는 내부적으로 선언된 필드들이 변수 처리되어있음
'Spring > 코드로 배우는 스프링부트 웹 프로젝트' 카테고리의 다른 글
코드로 배우는 스프링부트 웹 프로젝트 Day 6 (0) 2022.08.18 코드로 배우는 스프링부트 웹 프로젝트 Day 5 (0) 2022.08.17 코드로 배우는 스프링부트 웹 프로젝트 Day 3 (0) 2022.08.14 코드로 배우는 스프링부트 웹 프로젝트 Day 2 (0) 2022.08.12 코드로 배우는 스프링부트 웹 프로젝트 Day1 (0) 2022.08.10