-
코드로 배우는 스프링부트 웹 프로젝트 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>
후에 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로 했으니까 당연히 안 되는거였다. 한참 해맸다🥲
[부트스트랩 템플릿 적용하기]
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에 맞게 수정
📍 프로젝트 구조 만들기
의존성 추가
• 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를 넘김
[자동으로 처리되는 날짜/시간 설정]
데이터의 등록 시간, 수정시간 등을 자동으로 추가 및 변경할 수 있도록 어노테이션 이용해서 설정
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