1. 계기

뱅크샐러드를 모티브로 시작한 프로젝트라 뱅크샐러드처럼 달력 형태로 daily 지출내역을 관리할 수 있으면 좋을 것 같다고 생각했다. 그래서 일단 달마다 시작일과 요일, 전체 날짜가 다르니 동적으로 만들어야할 것 같은데 잘 몰라서 역시 구글링을 했다. javascript로 동적인 날짜 정보를 가져오고, 그 정보를 우리가 아는 달력의 형태처럼 만들기 위해 css를 사용했다. 

 

* '예산 알림 받기' 버튼은 해당 포스트와는 관련 없는 내용이므로 설명을 하지 않았다.*

 

2. 기본 구조 - HTML

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
<th:block layout:fragment="content">

<div id="top" class="container text-center">
    <div class="row">
        <div class="col-2"><a href="/index">AccountBook</a></div>
        <div class="col-8">My AccountBook</div>
        <div class="col-2">마이 페이지</div>
    </div>
</div>

<div id="middle" class="container text-center" style="padding-top:50px;">
    <div class="row">
        <div class="col align-self-start">
            <div class="row">
                <div class="col-auto"><button class="btn btn-light nav-btn-prev" onclick="goPrev();"><</button></div>
                <div id="year-month" class="col-auto"></div>
                <div class="col-auto"><button class="btn btn-light nav-btn-next" onclick="goNext();">></button></div>
            </div>
        </div>
            <div class="col align-self-end"><button type="button" class="btn btn-success" id="budgetToastBtn" onclick="getAlarm();">예산 알림 받기</button></div>
    </div>
</div>

<div class="calendar">
    <div class="days">
        <div class="day">MON</div>
        <div class="day">TUE</div>
        <div class="day">WED</div>
        <div class="day">THU</div>
        <div class="day">FRI</div>
        <div class="day">SAT</div>
        <div class="day">SUN</div>
    </div>
    <div class="dates"></div>
</div>

</th:block>
</html>

 

2-1. 코드 톺아보기

- 레이아웃 코드

thymeleaf로 레이아웃을 만들어 적용했기에 윗부분에 다음과 같은 코드가 있다. 이는 레이아웃을 적용하지 않았다면 필요없다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
<th:block layout:fragment="content">

 

- 달력 넘기는 코드

<div id="middle" class="container text-center" style="padding-top:50px;">
    <div class="row">
        <div class="col align-self-start">
            <div class="row">
                <div class="col-auto"><button class="btn btn-light nav-btn-prev" onclick="goPrev();"><</button></div>
                <div id="year-month" class="col-auto"></div>
                <div class="col-auto"><button class="btn btn-light nav-btn-next" onclick="goNext();">></button></div>
            </div>
        </div>
            <div class="col align-self-end"><button type="button" class="btn btn-success" id="budgetToastBtn" onclick="getAlarm();">예산 알림 받기</button></div>
    </div>
</div>

왼쪽 상단에 현재 날짜를 보여주고, 이전달과 다음달로 이동할 수 있는 코드이고, goPrev(), goNext()로 동작한다.

 

- 캘린더 코드

<div class="calendar">
    <div class="days">
        <div class="day">MON</div>
        <div class="day">TUE</div>
        <div class="day">WED</div>
        <div class="day">THU</div>
        <div class="day">FRI</div>
        <div class="day">SAT</div>
        <div class="day">SUN</div>
    </div>
    <div class="dates"></div>
</div>

기본 캘린더 틀이고, 요일을 div로 만들고, 각 날짜를 아래 date 클래스의 div 태그에 동적으로 할당할 예정이다.

 

 

3. Javascript로 날짜 정보 동적으로 가져오기

 

3-1. Javascript 전체 코드

<script>
    var currentYear = ''; // 현재 페이지에 있는 연도
    var currentMonth = ''; // 현재 페이지에 있는 월
    var currentDate = ''; // 현재 페이지에 있는 일
    $(document).ready(function() {
        // 한국 표준시간 가져오기
        var date = new Date();
        var utc = date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
        var kstGap = 9 * 60 * 60 * 1000;
        var today = new Date(utc + kstGap);
        var koreaDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());

        // 연, 월, 일 가져오기
        currentYear = koreaDate.getFullYear();
        currentMonth = koreaDate.getMonth() + 1;
        currentDate = koreaDate.getDate();

        // 페이지 로딩 초기에 달력 가져오기
        renderCalendar(currentYear, currentMonth, currentDate);
    });

    function renderCalendar(currentYear, currentMonth, currentDate) {
    	// zeroFormat()은 따로 정의한 함수, 2024-5를 2024-05로 만들어주는 역할
        $('#year-month').text(currentYear + '.' + zeroFormat(currentMonth));

        // 지난달 마지막 날짜, 요일 구하기
        var startDay = new Date(currentYear, currentMonth - 1, 0);
        var prevDate = startDay.getDate();
        var prevDay = startDay.getDay();

        // 이번 달의 마지막 날짜, 요일 구하기
        var endDay = new Date(currentYear, currentMonth, 0);
        var nextDate = endDay.getDate();
        var nextDay = endDay.getDay();

        calendar = document.querySelector('.dates')
        calendar.innerHTML = '';

		// 이번달의 1일 위치를 맞추기 위해 이전달의 날짜들 위치시키기
        for (var i = prevDate - prevDay + 1; i <= prevDate; i++) {
            calendar.innerHTML = calendar.innerHTML + '<div class="day prev disable"></div>'
        }

		// 이번달의 날짜 위치시키기
        for (var i = 1; i <= nextDate; i++) {
            calendar.innerHTML = calendar.innerHTML + '<button type="button" id=' + i + ' class="day current btn-light" data-bs-toggle="modal" data-bs-target="#dateModal" onclick="modal(this.id);">' + i + '</button> ';
        }
    }
    
    // 이전달로 이동
    function goPrev() {
        currentMonth -= 1
        if (currentMonth == 0) {
            currentYear -= 1;
            currentMonth = 12;
        }
        renderCalendar(currentYear, currentMonth, 1);
    }

    // 다음달로 이동
    function goNext() {
        currentMonth += 1
        if (currentMonth == 13) {
            currentYear += 1;
            currentMonth = 1;
        }
        renderCalendar(currentYear, currentMonth, 1);
    }
    
    function zeroFormat(value) {
        if (value / 10 < 1) {
            return '0' + value;
        } else {
            return value;
        }
    }
</script>

 

3-2. 코드 톺아보기 - 한국 표준시간 데이터 가져오기 

    var currentYear = ''; // 현재 페이지에 있는 연도
    var currentMonth = ''; // 현재 페이지에 있는 월
    var currentDate = ''; // 현재 페이지에 있는 일
    $(document).ready(function() {
        // 한국 표준시간 가져오기
        var date = new Date();
        var utc = date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
        var kstGap = 9 * 60 * 60 * 1000;
        var today = new Date(utc + kstGap);
        var koreaDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());

        // 연, 월, 일 가져오기
        currentYear = koreaDate.getFullYear();
        currentMonth = koreaDate.getMonth() + 1;
        currentDate = koreaDate.getDate();

        // 페이지 로딩 초기에 달력 가져오기
        renderCalendar(currentYear, currentMonth, currentDate);
    });
  • currentYear, currentMonth, currentDate는 현재, 이전달, 다음달에도 사용할 것이기 때문에 전역변수로 설정했다.
  • 한국 표준시간의 달력이 필요하므로 한국 시간으로 계산해 Date 객체를 생성한다. 
  • 주의할 점은 month가 0부터 시작하기 때문에 숫자를 맞추기 위해서 month 값에 +1을 꼭 해주어야 한다.

 

필요한 year, month, date를 renderCalendar()에 넘겨 달력을 만든다.

 

3-3. 코드 톺아보기 - 캘린더 로드하기

    function renderCalendar(currentYear, currentMonth, currentDate) {
    	// zeroFormat()은 따로 정의한 함수, 2024-5를 2024-05로 만들어주는 역할
        $('#year-month').text(currentYear + '.' + zeroFormat(currentMonth));

        // 지난달 마지막 날짜, 요일 구하기
        var startDay = new Date(currentYear, currentMonth - 1, 0);
        var prevDate = startDay.getDate();
        var prevDay = startDay.getDay();

        // 이번 달의 마지막 날짜, 요일 구하기
        var endDay = new Date(currentYear, currentMonth, 0);
        var nextDate = endDay.getDate();
        var nextDay = endDay.getDay();

        calendar = document.querySelector('.dates')
        calendar.innerHTML = '';

		// 이번달의 1일 위치를 맞추기 위해 이전달의 날짜들 위치시키기
        for (var i = prevDate - prevDay + 1; i <= prevDate; i++) {
            calendar.innerHTML = calendar.innerHTML + '<div class="day prev disable"></div>'
        }

		// 이번달의 날짜 위치시키기
        for (var i = 1; i <= nextDate; i++) {
            calendar.innerHTML = calendar.innerHTML + '<button type="button" id=' + i + ' class="day current btn-light" data-bs-toggle="modal" data-bs-target="#dateModal" onclick="modal(this.id);">' + i + '</button> ';
        }
    }

이번달의 전체 날짜를 알게 되었으니 <div class="dates">를 가져와서 날짜를 입력하면 된다. 이때 고려해야할 점은 1일의 요일 위치인데, 이전달을 고려하지 않고 이번달 데이터만 html에 추가하면 1일의 요일이 맞지 않게 된다. 

 

따라서 이전달 정보를 가지고 와서 이번달 1일 전에 태그를 추가함으로써 이번달 1일의 요일을 맞출 수 있다.

// 지난달 마지막 날짜, 요일 구하기
var startDay = new Date(currentYear, currentMonth - 1, 0);
var prevDate = startDay.getDate();
var prevDay = startDay.getDay();
        
// 이번달의 1일 위치를 맞추기 위해 이전달의 날짜들 위치시키고 보이지 않게 disable하기
for (var i = prevDate - prevDay + 1; i <= prevDate; i++) {
    calendar.innerHTML = calendar.innerHTML + '<div class="day prev disable"></div>'
}

 

3-4. 이전달, 다음달로 이동하기

    // 이전달로 이동
    function goPrev() {
        currentMonth -= 1
        if (currentMonth == 0) {
            currentYear -= 1;
            currentMonth = 12;
        }
        renderCalendar(currentYear, currentMonth, 1);
    }

    // 다음달로 이동
    function goNext() {
        currentMonth += 1
        if (currentMonth == 13) {
            currentYear += 1;
            currentMonth = 1;
        }
        renderCalendar(currentYear, currentMonth, 1);
    }

이전달과 다음달로 캘린더 이동은 기본적으로 위에서 구현한 renderCalendar()에 필요한 날짜를 넘겨주면 쉽게 구현할 수 있다. 여기서 주의해야할 점은 날짜 계산을 어떻게 할 지인데, 1년은 1월부터 12월까지이고, 1월 이전에는 전년도의 12월이, 12월의 다음달은 다음년도의 1월이기 때문이다. 이는 먼저 월을 계산한 다음에 이전 년도나 다음년도로 넘어간다면 따로 currentYear, currentMonth를 재설정하는 방식으로 해결했다.

 

 

구현 화면

위 코드를 적용하면 날짜 정보는 동적으로 가져오지만, 아래처럼 우리가 아는 달력 형태가 아닌 데이터가 나열된 형태로만 확인할 수 있다.

 

4.  css로 달력 모양 만들기

/resources/static/css/index.css

.calendar {
    padding-top: 60px;
    position: relative;
    margin: 0 auto;
}

.calendar .days {
    display: flex;
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #ddd;
}

.calendar .day {
    display:flex;
    align-items: center;
    justify-content: center;
    width: calc(100% / 7);
    text-align: left;
    color: #999;
    font-size: 12px;
    text-align: center;
    border-radius:5px
}

.calendar .dates {
    display: flex;
    flex-flow: wrap;
    height: 290px;
}

 

구현 화면

 

위 css 코드를 적용한 다음에 화면이다. 우리가 알고 있는 일반 달력화면이 되었다! 참고한 블로그에서는 월요일부터 시작하는 달력으로 만들었지만 일요일부터 시작하는 달력을 만들고 싶으면 커스터마이징하면 될 것 같다.

 

 

참고

자바스크립트로 달력 만들기

 

1. 고민

가계부 프로젝트에서 핵심은 지출내역 저장/수정/삭제라고 생각하는데, 수정 화면을 만들 때 고민이 많이 되었다. 가장 고민이 되었던 부분은 수정 페이지로 넘어갔을 때 해당 리소스의 데이터를 어떻게 가져올 것인지였다. 등록 화면은 기존 데이터가 있을 필요없이 그냥 새로운 페이지를 만들면 되는데, 수정 화면은 기존의 리소스 데이터를 가져와서 보여준 뒤 사용자가 변경한 것을 저장하는 방식으로 진행을 해야했다. 

 

진행하면서 가장 고민이 되었던 부분은 기존의 리소스 데이터를 어떤 로직으로 가져와서 보여줄 것인지였다. 처음에는 화면 로딩 시 ajax로 데이터를 가져와서 반영하려고 했으나 아직 미숙해서 그런지 잘되지 않았다. 그래서 이전에 배웠던 Thymeleaf의 th:value 문법Controller에서 Model 객체로 수정하려는 리소스의 데이터를 담아서 화면에 넘겨주는 방식을 선택했다.

 

2. 순서

진행 순서는 다음과 같다.

 

1. '수정' 버튼 클릭

2. 수정화면으로 이동 + 클라이언트는 url로 리소스(지출내역) id를 서버로 전달

3. 서버는 받은 id로 리소스 정보를 db에서 가져와 Controller 단에서 Model에 담아서 전달

4. model에 있는 정보를 화면에서 보여주기

 

2-1. '수정' 버튼 클릭

// 수정 버튼 클릭시 modifyDailyPayment(this.id) 호출
// 여기서 this.id는 리소스의 id가 되게 설정했다
`<button class="btn btn-success btn-payment" id="`+ paymentId + `" onclick="modifyDailyPayments(this.id);">수정</button></li> `;

 

2-2. 수정화면으로 이동 + 클라이언트는 url로 리소스(지출내역) id를 서버로 전달

// 해당 리소스의 수정 화면으로 이동
function modifyDailyPayments(paymentId) {
  location.replace("http://localhost:8080/expenditure/" + paymentId);
}

 

2-3. 서버는 받은 id로 리소스 정보를 db에서 가져와 Controller 단에서 Model에 담아서 전달

@Controller
@RequiredArgsConstructor
public class AccountBookController {    
    
    @GetMapping("/expenditure/{paymentId}")
    public String modifyExpenditure(@PathVariable long paymentId, Model model) {
        GetDailyPaymentsResponseDto payment = dailyPaymentsService
                .getDailyPayment(paymentId);
                
        model.addAttribute("payment", payment);
        return "edit-expenditure";
    }
}

dailyPaymentsService에서 id로 리소스를 식별해 정보를 GetDailyPaymentsReponseDto 객체에 저장한 뒤, Model을 통해 "payment"라는 이름으로 해당 정보를 view로 넘겨준다.

 

- GetDailyPaymentsResponseDto.class

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GetDailyPaymentsResponseDto {

    private long dailyPaymentId;
    private Integer paidAmount;
    private String payLocation;
    private String methodOfPayment;
    private Long categoryId;
    private String categoryName;
    private String memo;
    private String date;
    
    public static GetDailyPaymentsResponseDto of(DailyPayments dailyPayments, String categoryName) {
        return GetDailyPaymentsResponseDto
                .builder()
                .dailyPaymentId(dailyPayments.getId())
                .paidAmount(dailyPayments.getPaidAmount())
                .payLocation(dailyPayments.getPayLocation())
                .methodOfPayment(dailyPayments.getMethodOfPayment())
                .categoryId(dailyPayments.getCategoryId())
                .categoryName(categoryName)
                .memo(dailyPayments.getMemo())
                .date(dailyPayments.getDate())
                .build();
    }
}

 

- model.addAttribute()

model.addAttribute("payment", payment);

 

 

필요한 정보를 view에 모두 보냈으면, 수정화면으로 이동하기 위해 "edit-expenditure" 문자열을 반환하면 된다.

 

 

2-4. model에 있는 정보를 화면에서 보여주기

위의 과정을 거치면 기존에 저장되어있던 지출 정보를 수정 화면에서 확인할 수 있다.

아직 드롭박스에 반영하는 법은 잘 모르겠다. 추후 적용 예정!

 

코드

위에서 서버에서 가져온 지출내역을 payment라는 이름으로 view에 넘겼다. 그렇기에 thymeleaf 문법을 이용해 payment의 정보를 적재적소에 위치시켜보자.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
<th:block layout:fragment="content">

    <div id="top" class="container text-center">
        <div class="row">
            <h3>지출 수정하기</h3>
        </div>
    </div>

    <div id="middle1" class="container text-center" style="padding-top:50px;">
        <div class="row">
            <div class="col-6">금액<input id="paid-amount" th:value="${payment.paidAmount}"></div>
            <div class="col-6">
                카테고리
                <button class="btn btn-light" type="button" th:text="${payment.categoryName}" id="category-radio" data-bs-toggle="modal" data-bs-target="#categoryModal" onclick="getCategoryList();" ></button>
            </div>
        </div>
    </div>

    <div id="middle2" class="container text-center" style="padding-top:50px;">
        <div class="row">
            <div class="col-6">거래처<input id="pay-location" th:value="${payment.payLocation}"></div>
            <div class="col-6">결제 수단<input id="method-payment" th:value="${payment.methodOfPayment}"></div>
        </div>
    </div>

    <div id="middle3" class="container text-center" style="padding-top:50px;">
        <div class="row">
            <div class="col-6">메모<input id="memo" th:value="${payment.memo}"></div>
            <div class="col-6">날짜
                <select id="date-select" class="form-select" onclick="getDate();" th:text="${payment.date}"></select>
            </div>
        </div>
    </div>
    
    ...
    
<th:block>
</html>

 

 

기본적인 thymeleaf 문법은 아래 th:value=..  형식을 따르면 된다.

<input id="paid-amount" th:value="${payment.paidAmount}">

 

 

2-5. 수정 완료

이제 원하는 만큼 정보를 변경한 뒤 아래 "수정하기" 버튼을 클릭하면 수정 api를 전송하면서 수정이 완료된다.

1. 계기

ajax를 사용하게된 계기는 클라이언트와 서버를 모두 개발하면서 클라이언트-서버 간 http 메시지 통신을 하고싶었기 때문이다. Thymeleaf를 사용했던 터라 form 태그로 할 수도 있었지만, 클라이언트 동작방식을 더 이해하고 싶어서 ajax를 선택했다.

 

1-1. ajax란?

ajax는 JavaScript를 사용한 비동기 통신, 클라이언트-서버 간 xml, json 데이터를 주고 받는 기술이다. ajax는 비동기로 동작하기 때문에 전체 페이지를 새로 고치지 않고 뷰를 갱신할 수 있다.

 

1-2. ajax 사용 이유

페이지 전체를 새로고침하지 않고 web 화면에서 데이터를 조회하고 싶은 경우에 사용할 수 있다. json 이나 xml 형태로 필요한 데이터만 받아 갱신하기 때문에 그만큼의 자원과 시간을 아낄 수 있다.

 

1-3. ajax 진행과정

1) XMLHttpRequest Object를 만든다

브라우저에게 request를 보낼 준비 시키는 과정이다.

이를 위해 필요한 method를 갖춘 object가 필요하다.

 

2) callback 함수를 만든다

callback 함수는 어떤 이벤트에 의해 호출되는 함수를 의미한다.

서버에서 response가 왔을 때 callback 함수를 실행한다.

HTML 페이지를 업데이트 한다.

 

1-4. ajax 사용법

ajax를 사용하기 위해서는 jquery를 import 해야한다. 

 

build.gradle

implementation 'org.webjars:jquery:3.1.1-1'

 

헤더

<head>
	<script src="/webjars/jquery/jquery.min.js"></script>
</head>

 

ajax 코드

<script>
function completeAuthEmail() {
    const parmas = {}; // body에 넣을 json 데이터

    $.ajax({
        url : url, // 메시지 보낼 url
        type : 'post',
        contentType : 'application/json; charset=utf-8;',
        dataType : 'json',
        data : JSON.stringify(params), // json 데이터를 JSON.stringify()를 해야 에러가 발생하지 않는다.
        success : function(response) {
            alert('인증이 완료 되었습니다.');
        },
        error : function(response, status, error) {
            alert('에러 발생');
        }
    })
}
</script>

기본적으로 HTTP 메시지를 주고 받는 것이기 때문에 HTTP 메시지 구조(start line, header, body)로 코드를 작성해야 한다. 위 코드에서 보이는 필드 중 url, type은 start line에, contentType, dataType은 header에, data는 body에 해당한다. 그렇기에 각각에 맞는 내용들을 입력하면 된다. 메시지를 보낸 후 성공 응답인 경우는 succes, 실패 응답이라면 error에 선언한 콜백함수가 실행된다.

 

여기서 주의해야할 점은 body에 해당하는 data에 해쉬 그대로 넣으면 에러가 발생한다. 그래서 JSON.stringify()로 형변환을 한 뒤 전송해야 한다.

 

  • XMLHttpRequest 객체를 얻은 뒤, url을 통해 요청하고 응답을 받으면 응답 결과에 맞는 함수를 실행하는 구조이다
  • 효율적인 Ajax 사용을 위해 Jquery에서 구현한 ajax 기능을 사용했다

 

구체적인 예시 - 인증 이메일 전송하기

function completeAuthEmail() {
    var email = $('#email').val();
    var authKey = $('#authKey').val();
    const params = {
        'email' : email,
        'authKey' : authKey
    }

    $.ajax({
        url : `/v1/auth/email/complete`,
        type : 'post',
        contentType : 'application/json; charset=utf-8;',
        dataType : 'json',
        data : JSON.stringify(params),
        success : function(response) {
            alert('인증이 완료 되었습니다.');
        },
        error : function(response, status, error) {
            alert(JSON.parse(response.responseText).message);
        }
    })
}

기본 ajax 구조를 가지고 실제로 작성했던 코드이다. 구조는 동일하지만, body에 들어가는 구체적인 값을 jQuery로 가져오고, 실제 사용하는 api url을 추가했다. 각 상황에 맞게 성공일 때 동작방식과 실패일 때 동작방식을 설정해주면 된다. 

 

여기서 주의해야할 점은 응답 메시지에서 JSON key를 가져오는 방법이다. 응답 body의 메시지를 가져오고 싶은데 처음에는 response['message'] 와 같은 형식으로 데이터를 가져오려고 했는데 에러가 발생하고 원하는 값을 가져오지 못했다. 찾아보니 response가 Object이므로 '.'으로 바디를 꺼낸 뒤 JSON으로 파싱을 해야 응답 바디에서 원하는 key의 값을 가져올 수 있다.

// response body
{ status : NOT_FOUND,
  message : "존재하지 않는 이메일입니다."
}

// response body에 있는 message 키의 값 가져오는 코드
JSON.parse(response.responseText).message

 

 

참고

[JQUERY] SpringBoot - ajax 사용법 및 예제(+thymeleaf)

 

1. 계기

화면을 개발하면서 가장 불편했던 것이 사소한 태그 하나를 고치더라도 서버를 재시작해야 한다는 것이었다. 게다가 화면은 수시로 계속 변하기 때문에 정말 번거로운 작업이었다. 그래서 (인텔리제이 + 서버 재시작 없이 + 화면 업로드) 조합으로 구글링을 한 결과 인텔리제이에서 서버를 재시작할 필요없이 화면 변경사항을 반영하는 방법이 있어서 적용해봤다. 

 

2. 적용

2-1. 의존성 추가

compileOnly 'org.springframework.boot:spring-boot-devtools'

 

2-2. yml에 설정 추가

현재 thymeleaf를 사용하고 있기에 관련해서 설정을 추가로 해줘야 한다.

spring:
  devtools:
    livereload:
      enabled: true
    restart:
      enabled: true
  thymeleaf:
    cache: false

 

3. Settings 설정 

3-1. Compiler

맥북 기준으로 [cmd + ,] 를 누른뒤 Setting으로 이동해 Build > Compiler로 이동해 Build Project Automatically에 체크를 해준다.

 

3-2. Advanced Settings

구글링 해보면 registry에 가서 compiler.automake.allow.when.app.running를 누르라고 하는데, 2021.2 이후 버전의 인텔리제이라면 이렇게 하지말고 아래처럼 해야한다.

 

Settings > Advanced Settings > 'Allow auto-make to start ...' 에 체크

 

4. Gradle에서 IntelliJ IDEA로 변경

다른 블로그 설명을 따르면, 아래 그림처럼 Edit Configurations 에 들어가서 Running Application Update Policies을 변경하라고 하는데 On 'Update' action도 없고 다른 블로그에서 보이는 Settings 화면과 달랐다. 참고한 블로그에서도 똑같이 겪었고, 다음과 같은 방식으로 해결했다. 

 

Settings > Build, Execution, Deployment > Build Tools > Gradle -> IntelliJ IDEA

 

 

5. 실행

설정이 끝나면 서버를 재시작해야 적용된 것을 확인할 수 있다. 이제 정적 리소스(html, css)를 변경한 뒤 서버 리로드 할 필요 없이 브라우저에서 cmd + shift + r로 캐시 없애고 새로고침하면 변경된 것이 적용되는 것을 확인할 수 있다. 

 

참고

https://zoetechlog.tistory.com/92

+ Recent posts