spring boot

48일차 개판 세션사용, 조회수 중복 세션으로 처리, item 일괄등록(이미지), footer

비루블 2022. 8. 31. 17:48

요약정리

그냥 html에는 httpSession기능없음
thymleaf를 써야 사용 가능

 

로그인 세션사용

메인화면 if 사용하여 바꿈

 

controller 암호 사용시 주의점

암호가 포함되서 get으로 보내면 안되고 post로 보내야함.

 

로그아웃은 폼태그로해야함(get으로 하는 경우가 없음. 페이지 이동없이 바로 post)

 

로그아웃할때 세션처리

로그아웃 컨트롤러 만듬
httpsession을 새걸로 넣어줘야함. 값 넣어줬던걸 null로 바꾸면 안됨
httpSession.invalidate();
세션 초기화 

 

============================================

몽고db기반 세션 처리

라이브러리 설치

app에 코드 작성

 

2가지 방식 장단점

jdk는 obj보낼때 용이 근데 속성값이 byte배열이라 확인불가(엔티티 att byte[])
jackson방식은 json으로 저장해서 데이터를 보기 편함. 근데 object보낼때 문제가 있음.

현재 우리는 mongodb기반 jdk 형식으로 httpsession을 사용 중

============================================

 

조회수 새로고침 중복 증가 세션으로 고침(boardcontroller)

 

th:value에 스트링도 넣어보기

each문으로 여러칸 나타내기

image가 있으면 action method 쓸때 enctype도 꼭 넣어야함.

image를 받을때 entity에 있는 형태로 받으면 안됨. image로 받아야함.

 

 

 

오전일과
로그인, 세션
로그인.html
암호가 포함되서 get으로 보내면 안되고 post로 보내야함.

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"></html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로그인</title>
    <!-- <link th:href="@{../../static/css/joincss.css}" rel="stylesheet" /> -->
</head>
<body>
    <div class="container">
        <h3>로그인</h3>
        <hr />
        <form th:action="@{/member/login.do}" method="post">
            아이디 : <input type="text" name="id" /><br />
            암호 : <input type="password" name="password" /><br />
            <input type="submit" value="로그인" />
        </form>
    </div>
</body>
</html>


로그인을 repository 이용
아이디 비번 일치 두개 넣으면 and가 됨,
아이디랑 이름만 들고와라(fields)

// 로그인
@Query(value = "{_id:?0, password:?1}", fields = "{_id:1,name:1}")
public Member selectMemberLogin(String id, String pw);


controller(서비스에서 끌어옴)

@PostMapping(value = "/login.do")
public String loginPOST(@ModelAttribute Member member){

    // service호출해서 추가
    // System.out.println(member.toString());
    // 1. 전송값 확인
    // log.info(member.toString());
    // 2. 서비스로 전송
    Member ret = mService.loginMember(member);
    // 3. 결과값 확인
    if(ret != null){
        System.out.println(ret.toString());
        // 스토리지에서 토큰이나 시큐리티 시스템 넣어야함.
        // 세션에 적절한 값을 추가함.(기본값이 30분 동안 유지)
        httpSession.setAttribute("SESSION_ID", ret.getId());
        httpSession.setAttribute("SESSION_NAME", ret.getName());
        return "redirect:/home.do";
    }
    else{
        return "redirect:/member/login.do";
    }
}

 

service(repository 이용)

// 로그인
public Member loginMember(  Member member  ){
    try {
        return mRepository.selectMemberLogin(member.getId(), member.getPassword());
    }
    catch(Exception e){
        e.printStackTrace();
        return null;
    }
}



공통적으로 쓸거
과거에 했던 내용을 가지고 있기(세션)

// 세션 메모리 기반, (파일)db기반(몽고,오라클 등등), 세션db메모리(redis, memcached)
// 세션은 주로 메모리를 많이 씀.
@Autowired
HttpSession httpSession;

 

로그인 성공시 세션에 적절한 값을 추가함.(controller)

httpSession.setAttribute("SESSION_ID", ret.getId());
httpSession.setAttribute("SESSION_NAME", ret.getName());



homecontroller에 세션 추가(@)

package com.example.controller;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    
    @Autowired
    HttpSession httpSession;

    // 127.0.0.1:8080/ROOT
    // 127.0.0.1:8080/ROOT/home
    // 127.0.0.1:8080/ROOT/home.do
    @GetMapping(value ={ "/", "/home", "home.do"})
    public String homeGet(){
        // 세션에 값을 꺼냄
        String userid = (String) httpSession.getAttribute("SESSION_ID");
        if(userid == null){
            System.out.println("로그인 실패");
        }
        else{
            String username = (String) httpSession.getAttribute("SESSION_NAME");
            System.out.println(username);
            System.out.println("로그인 성공");
        }
        return "home"; // resources/templates/home.html
    }
}


홈화면 html if문 처리해줌
session_id 유무로 해줌(th:block으로 메뉴를 묶어줌)

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"></html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>홈화면</title>
</head>
<body>
    <h3>홈화면</h3>
    <th:block th:if="${session.SESSION_ID == null}">
        <a th:href="@{/member/login.do}">로그인</a>
        <a th:href="@{/member/join.do}">회원가입</a>
    </th:block>

    <th:block th:if="${session.SESSION_ID != null}">
        <form th:action="@{/member/logout.do}" method="post">
            <input type="submit" value="로그아웃">
        </form>
    <!-- <a th:href="@{/member/logout.do}">로그아웃</a> -->
    <a th:href="@{/member/mypage.do}">마이페이지</a>
    </th:block>

    <!-- <a href="/ROOT/board/boardlist.do">게시판</a>
    <a href="/ROOT/seller/seller.do">판매자</a> -->
    <a th:href="@{/board/boardlist.do}">게시판</a>
    <a th:href="@{/seller/seller.do}">판매자</a>
</body>
</html>


로그아웃은 폼태그로해야함(get으로 하는 경우가 없음. 페이지 이동없이 바로 post)

로그아웃 컨트롤러 만듬
httpsession을 새걸로 넣어줘야함. 값 넣어줬던걸 null로 바꾸면 안됨
httpSession.invalidate();
세션 초기화 

그냥 html에는 httpSession기능없음
thymleaf를 써야 사용 가능

지금까지 메모리 기반이였음.

 

================================================================================
몽고db기반으로 바꿔 볼 것임.
라이브러리 추가

<dependency>
    <groupId>org.springframework.session</groupId>
     <artifactId>spring-session-data-mongodb</artifactId>
</dependency>



boot20220827Application에 몽고db세션 추가


몽고db 세션 저장 컬렉션명, 시간(30분)
@EnableMongoHttpSession(collectionName = "boot_sessions", maxInactiveIntervalInSeconds = 1800)
30분

// @autowired로 하는 것은 bean에 의해서 생성이 되어지고 쓰는 것임.
// @bean = > 서버를 구동할 때 자동으로 객체를 생성(서버가 구동할때 자동으로 만들어 주세요)
// 클래스명 obj = new 생성자(시간);
// JdkMongoSessionConverter jdkMongoSessionConverter
// = new new JdkMongoSessionConverter(Duration.ofMinutes(30));
@BeanProperty
public JdkMongoSessionConverter jdkMongoSessionConverter(){
return new JdkMongoSessionConverter(Duration.ofMinutes(30));
}

@bean이 붙어 있으면 객체가 생성 되겠구나 라고 생각


몽고디비 session콜렉션 확인
로그인시 id 부여 로그아웃시 아이디가 바뀜
로그인 시간, 만료 시간이 있음.



jdk방식, jackson 방식이 있음
각자 장단점이 있음.
jdk는 obj보낼때 용이 근데 속성값이 byte배열이라 확인불가(엔티티 att byte[])
jackson방식은 json으로 저장해서 데이터를 보기 편함. 근데 object보낼때 문제가 있음.


boardcontroller 조회수 증가처리(위에서 코드 작성후 로그인해서 객체 생성되어있음=로그인 되어있는 상태)

// bean으로 객체가 생성되어있음.
// => 메모리였는데 => @bean으로 몽고db jdk로 넣어놓음
@Autowired
HttpSession httpSession;

 


boardlist (get)

// 목록에서 세션에 BOARD_HIT값을 true로 받음
httpSession.setAttribute("BOARD_HIT", true);


참일때만 증가시키키(list에서 미리 true로 바꿔주는 이유는 boardone으로 이동시 조회수는 증가하여야하기 때문)
boardone(get)에서는 false로 설정하면 증가가 안됨.

새로고침으로 조회수 증가 안되게 하기
현재 페이지의 넘버를 저장해서 이전글이나 다음글로 넘어갈때는 다시
updateHits가 작동하도록 설정해줌

// 0. 조회수 증가전 세션에서 꺼내오기
boolean isHit = (boolean) httpSession.getAttribute("BOARD_HIT");
if(isHit == true){
    // 0 조회수증가 (세션으로 처리)
    bService.boardUpdateHit(no);
    // 이자리에서는 한번만 됨
    httpSession.setAttribute("BOARD_HIT", false);
    httpSession.setAttribute("BOARD_NO", no);
}
long boardNo = (long) httpSession.getAttribute("BOARD_NO");
if (no != boardNo){
    bService.boardUpdateHit(no);
    httpSession.setAttribute("BOARD_NO", no);
}

위에는 새로고침 방지

아래는 이전글, 다음글 다시 조회수 증가후 다시 no로 바꿔놓음(새로고침 조회수 증가 방지)




마이페이지 해줌
membercontroller

// 마이페이지
@GetMapping(value = "/mypage.do")
public String mypageGET(Model model,
    @RequestParam(name = "menu", defaultValue = "0", required = false) int menu){
    // 세션에서 정보 가져오기(나중에 시큐리티 하면 이런거 다 필요없음.)
    String userid = (String) httpSession.getAttribute("SESSION_ID");
    if(userid == null){
        return "redirect:/member/login.do";
    }
    // 해당 아이디의 정보 가져오기
    Member ret = mService.selectoneMember(userid);
    model.addAttribute("memberinfo", ret);

    if(menu == 0){
        return "redirect:/member/mypage.do?menu=1";
    }
    return "member/mypage"; // join.html file
}
// 마이페이지 정보변경(POST)
@PostMapping(value = "/mypage.do")
public String mypagePOST(
    @RequestParam(name = "menu") int menu,
    @ModelAttribute Member member){
    // 1. 정보변경
    String userid = (String) httpSession.getAttribute("SESSION_ID");
    if(userid == null){
        return "redirect:/member/login.do";
    }
    member.setId(userid);
    if(menu == 1){
        short ret = mService.updateMember(member);
        if(ret > 0){
            return "redirect:/member/mypage.do?menu=1";
        }
        return "redirect:/member/mypage.do?menu=1";
    }

    // 2. 암호변경
    else if(menu == 2){
        System.out.println("컨트롤러"+member);
        mService.updatePwMember(member);
        return "redirect:/member/mypage.do?menu=2";
    }

    // 3. 회원탈퇴
    else if(menu == 3){
        short ret = mService.deleteMember(member);
        System.out.println(member);
        if (ret > 0) {
            httpSession.invalidate();
            return "redirect:/home.do";                
        }
        else{
            return "redirect:/member/mypage.do?menu=3";
        }
    }

    return null;
}


세션에서 정보 가져오기(나중에 시큐리티 하면 이런거 다 필요없음.)
정보변경, 암호변경, 회원탈퇴에 각각 메뉴인덱스 줬음.

mypage menu번호 처리 menu가 param에 없을시 자동으로 1로 이동

menu의 default를 0으로 줬고 menu0이 오면 menu1로 보내게끔 함.


회원정보수정
암호변경
회원탈퇴x

html구현 + controller 작성 같이함

mypage.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"></html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>마이페이지</title>
</head>
<body>
    <h3>마이페이지</h3>
    <a th:href="@{/member/mypage.do(menu=1)}"><button>정보변경</button></a>
    <a th:href="@{/member/mypage.do(menu=2)}"><button>암호변경</button></a>
    <a th:href="@{/member/mypage.do(menu=3)}"><button>회원탈퇴</button></a>
    <hr/>
    
    <div th:text="${memberinfo}"></div>
    <!-- <div th:text="${session.SESSION_ID}"></div> -->
    <div th:text="| 아이디 : ${session.SESSION_ID} |"></div>
    <!-- <div th:text="${session.SESSION_NAME}"></div> -->
    <div th:text="| 이름 : ${session.SESSION_NAME} 님 로그인중 |"></div>
    <div th:text="${param.menu}"></div><!-- 메뉴번호 잘 받아오나 확인 -->
    <div th:if="${#strings.toString(param.menu) == '1'}">
        정보변경
        <form th:action="@{/member/mypage.do?menu=1}" method="post">
            <!-- <input type="hidden" name="id" th:value="${memberinfo.id}"/> -->
            이름 : <input type="text" name="name" th:value="${memberinfo.name}"/><br />
            연락처 : <input type="text" name="phone" th:value="${memberinfo.phone}" /><br />
            <input type="submit" value="정보변경" />
        </form>
    </div>
    <div th:if="${#strings.toString(param.menu) == '2'}">
        암호변경
        <form th:action="@{/member/mypage.do(menu=2)}" method="post">
            <!-- <input type="hidden" name="id" th:value="${memberinfo.id}"/> -->
            현재 암호 : <input type="password" name="password" /><br />
            변결할 암호 : <input type="password" name="password1" /><br />
            변결할 암호 확인 : <input type="password"/><br />
            <input type="submit" value="암호변경" />
        </form>
    </div>
    <div th:if="${#strings.toString(param.menu) == '3'}">
        회원탈퇴
        <form th:action="@{/member/mypage.do(menu=3)}" method="post">
            <input type="hidden" name="id" th:value="${memberinfo.id}"/>
            암호 : <input type="password" name="password" /><br />
            <input type="submit" value="회원탈퇴" />
        </form>
    </div>

    <div th:replace="~{ member/footer :: footerFragment }"></div>
</body>
</html>

membercontroller

package com.example.controller;


import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.entity.Member;
import com.example.service.MemberService;

import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping(value = "/member")
@Slf4j
public class MemberController {
    // 서비스 이용 객체 생성
    @Autowired
    MemberService mService;

    // 세션 메모리 기반, (파일)db기반(몽고,오라클 등등), 세션db메모리(redis, memcached)
    // 세션은 주로 메모리를 많이 씀.
    @Autowired
    HttpSession httpSession;

    // 마이페이지 정보변경(POST)
    @PostMapping(value = "/mypage.do")
    public String mypagePOST(
        @RequestParam(name = "menu") int menu,
        @ModelAttribute Member member){
        // 1. 정보변경
        String userid = (String) httpSession.getAttribute("SESSION_ID");
        if(userid == null){
            return "redirect:/member/login.do";
        }
        member.setId(userid);
        if(menu == 1){
            short ret = mService.updateMember(member);
            if(ret > 0){
                return "redirect:/member/mypage.do?menu=1";
            }
            return "redirect:/member/mypage.do?menu=1";
        }

        // 2. 암호변경
        else if(menu == 2){
            System.out.println("컨트롤러"+member);
            mService.updatePwMember(member);
            return "redirect:/member/mypage.do?menu=2";
        }

        // 3. 회원탈퇴
        else if(menu == 3){
            short ret = mService.deleteMember(member);
            System.out.println(member);
            if (ret > 0) {
                httpSession.invalidate();
                return "redirect:/home.do";                
            }
            else{
                return "redirect:/member/mypage.do?menu=3";
            }
        }

        return null;
    }

    // 마이페이지
    @GetMapping(value = "/mypage.do")
    public String mypageGET(Model model,
        @RequestParam(name = "menu", defaultValue = "0", required = false) int menu){
        // 세션에서 정보 가져오기(나중에 시큐리티 하면 이런거 다 필요없음.)
        String userid = (String) httpSession.getAttribute("SESSION_ID");
        if(userid == null){
            return "redirect:/member/login.do";
        }
        // 해당 아이디의 정보 가져오기
        Member ret = mService.selectoneMember(userid);
        model.addAttribute("memberinfo", ret);

        if(menu == 0){
            return "redirect:/member/mypage.do?menu=1";
        }
        return "member/mypage"; // join.html file
    }



    @PostMapping(value = "/logout.do")
    public String logoutPOST(){
        httpSession.invalidate();
        return "redirect:/home.do";
    }


    
    @PostMapping(value = "/login.do")
    public String loginPOST(@ModelAttribute Member member){
        
        // service호출해서 추가
        // System.out.println(member.toString());
        // 1. 전송값 확인
        // log.info(member.toString());
        // 2. 서비스로 전송
        Member ret = mService.loginMember(member);
        // 3. 결과값 확인
        if(ret != null){
            System.out.println(ret.toString());
            // 스토리지에서 토큰이나 시큐리티 시스템 넣어야함.
            // 세션에 적절한 값을 추가함.(기본값이 30분 동안 유지)
            httpSession.setAttribute("SESSION_ID", ret.getId());
            httpSession.setAttribute("SESSION_NAME", ret.getName());
            return "redirect:/home.do";
        }
        else{
            return "redirect:/member/login.do";
        }
    }



    //로그인
    @GetMapping(value = "/login.do")
    public String loginGET(){
        return "member/login"; // join.html file
    }




    // 127.0.0.1:8080/ROOT/member/idcheck.do?id=aaa
    @GetMapping(value = "/idcheck.do")
    public String idCheckGET(
        Model model,
        @RequestParam(name="id", defaultValue = "") String id) {
        System.out.println(id);
        
        int ret = mService.selectIdCheck(id);
        model.addAttribute("ret", "사용가능");
        if(ret == 1 ){
            model.addAttribute("ret", "사용불가");
        }
        return "member/idcheck";
    }


    // 127.0.0.1:8080/ROOT/member/join.do
    @GetMapping(value = "/join.do")
    public String joinGET(){
        return "member/join";
    }

    @PostMapping(value = "/join.do")
    public String joinPOST( @ModelAttribute Member member ){
        System.out.println( member.toString());
        // service호출해서 추가
        int ret = mService.insertMember(member);

        if(ret < 1) {
            // a 태그 동작 시킴 => GET으로
            return "redirect:/member/join.do";    
        }
        // a 태그 동작 시킴 => GET으로
        return "redirect:/home.do";
    }

}

 


암호변경 임시함수 entity

 

 

 

 

판매자 할 것임.

1. 판매자 메뉴

seller.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"></html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>판매자</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>
    <script th:src="@{/js/bootstrap.js}"></script>
    <!-- <a href="../../static/css/bootstrap.css">
    <a href="../../static/js/bootstrap.js"> -->
    
</head>
<body>
    <h3>판매자</h3>
    <div class="container" style="border: 1px solid #cccccc;">
        <a th:href="@{/item/itemlist.do}">물품조회</a>
        <a th:href="@{/item/insertbatch.do}">일괄추가</a>

        
    </div>
</body>
</html>

 

2. 일괄등록 
insertbatch.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"></html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>물품 일괄 추가</title>
    <script th:src="@{/js/bootstrap.js}"></script>
</head>
<body>
    <div>
        <form th:action="@{/item/insertbatch.do}" method="post" enctype="multipart/form-data">
            <th:block th:each="i : ${#numbers.sequence(1,3)}">
                <input type="text" name = "name" placeholder="물품명" th:value="|물품명 ${i}|"/>
                <input type="text" name = "content" placeholder="물품내용" th:value="|내용 ${i}|"/>
                <input type="text" name = "price" placeholder="물품가격" th:value="${i}+100"/>
                <input type="text" name = "quantity" placeholder="물품수량" th:value="${i}+50"/>
                <input type="file" name = "image"/>
            </th:block>
            <input type="submit" value="물품일괄추가" />
        </form>
    </div>
</body>
</html>

th:value에 스트링도 넣어보기

each문으로 여러칸 나타내기

image가 있으면 action method 쓸때 enctype도 꼭 넣어야함.

image를 받을때 entity에 있는 형태로 받으면 안됨. image로 받아야함.
for로 일괄등록이라
모델 형태로 한번에 못받음.


아이템컨트롤러

package com.example.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import com.example.entity.Item;
import com.example.service.ItemService;


@Controller
@RequestMapping(value = "/item")
public class ItemController {

    @Autowired
    ItemService itemService;
    
    @GetMapping(value = "/seller.do")
    public String sellerGET(){
        return "seller/seller";
    }

    @GetMapping(value = "/insertbatch.do")
    public String insertbatchGET(){
        return "seller/insertbatch";
    }
    @PostMapping(value = "/insertbatch.do")
    public String insertbatchPOST(
        @RequestParam(name = "name") String[] name,
        @RequestParam(name = "content") String[] content,
        @RequestParam(name = "price") Long[] price,
        @RequestParam(name = "quantity") Long[] quantity,
        @RequestParam(name = "image") MultipartFile[] file) throws IOException{

        // System.out.println("이름"+Arrays.toString(name));
        // System.out.println("내용"+Arrays.toString(content));
        // System.out.println("가격"+Arrays.toString(price));
        // System.out.println("수량"+Arrays.toString(quantity));
        // System.out.println("이미지"+Arrays.toString(file));


        List<Item> list = new ArrayList<>();
        for(int i = 0; i<name.length; i++){
            Item item = new Item();
            item.setName(name[i]);
            item.setContent(content[i]);
            item.setPrice(price[i]);
            item.setQuantity(quantity[i]);

            // 이미지
            item.setFilename(file[i].getOriginalFilename());
            item.setFilesize(file[i].getSize());
            item.setFiletype(file[i].getContentType());
            item.setFiledata(file[i].getBytes()); // exception 해줌
            list.add(item);
        }
        itemService.insertbatch(list);
        return "redirect:/item/insertbatch.do"; 
    }

    @GetMapping(value = "/itemlist.do")
    public String selectlistGET(Model model){
        List<Item> ilist = itemService.selectItemList();
        model.addAttribute("list", ilist);
        return "seller/itemlist";
    }
}



css와 js를 bootstrap에서 들고옴

https://getbootstrap.com/docs/5.2/getting-started/download/

insertbatch 일괄등록
임시로 th:value="|물품명 ${i}|"사용

 

컴포넌트 개념인 footer.html 생성
mypage에서 footer.html 불러오기

파일위치 파일내의 원하는fragment

<div th:replace="~{ member/footer :: footerFragment }"></div>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

    <footer th:fragment="footerFragment">
        <p>copyright 2022</p>
    </footer>

</html>