68일차 내용 정리

채공부·2025년 8월 28일

AOP

  • Messenger 클래스 생성

@Component
public class Messenger {

	public void sendGreeting(String msg) {
		System.out.println("오늘의 인사 : "+msg);
	}
	
	public void sendMember(MemberDto dto) {
		System.out.println(dto);
	}
	
	public String getMessage() {
		System.out.println("getMessage() 메소드가 수행됩니다");
		return "열심히 공부 합시다";
	}
}
  • Spring08ExampleApplication 에 testAop 메소드에 작성
@Autowired
private Messenger m;

public void testAop() {
	m.sendGreeting("안녕하세요");
	m.sendMember(null);
	String msg = m.getMessage();
	System.out.println("리턴된 msg : "+msg);
}
오늘의 인사 : 안녕하세요
null
getMessage() 메소드가 수행됩니다
리턴된 msg : 열심히 공부 합시다

@Around

@Before + @After 의 개념

  • MessengerAspect 클래스 생성
  • @Around("execution(void send*(..))") : 매개변수의 모양을 상관하지 않겠다
    ➜ 갯수와 type 을 제한하지 않는다
  • spring 이 관리하는 bean 의 메소드 중에서 리턴 type 이 void 이고 send 로 시작되는 모든 메소드에 적용
  • getArgs() 사용
@Aspect
@Component
public class MessengerAspect{
	@Around("execution(void send*(..))") // @Before + @After 개념이라고 생각
	public void checkGreeting(ProceedingJoinPoint joinPoint) {
		// 메소드에 전달된 인자들 목록을 얻어내기
		Object[] args = joinPoint.getArgs();
		// 반복문 돌면서 매개변수에 담긴 값들을 하나하나 참조하면서
		for(Object tmp : args) {
			// 찾고 싶은 type 을 확인
			if(tmp instanceof String) { // 만일 String type 이라면
				// 찾았다면 원래 type 으로 casting 한다
				String msg = (String)tmp;
				System.out.println("매개변수에 전달된 값 : "+msg);		
			}
		}
	}
}
매개변수에 전달된 값 : 안녕하세요

➜ 메소드 호출 안 했다

  • proceed()
  • 이 메소드를 호출하는 시점에 실제로 aspect 가 적용된 메소드가 수행된다 (호출하지 않으면 수행 X)
joinPoint.proceed();

@Slf4j

로그 출력을 도와주는 어노테이션
해당 어노테이션이 붙어있는 클래스의 메소드에서 log 객체를 사용 가능

@Slf4j

// log 객체를 이용해서 메세지를 출력
log.info("매개변수에 전달된 값 : " +msg);
log.warn("매개변수에 전달된 값 : " +msg);
log.error("매개변수에 전달된 값 : " +msg);
log.debug("매개변수에 전달된 값 : " +msg);
2025-08-28T10:44:26.189+09:00  INFO 15984 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요

2025-08-28T10:46:12.407+09:00  WARN 15984 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요

2025-08-28T10:47:25.011+09:00 ERROR 15984 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요
  • application.properties 에 debug 관련 코드 작성

  • 이 프로젝트의 패키지 안에서 일어나는 debug 로그만 출력하겠다는 의미

logging.level.com.example.spring08=DEBUG
  • Messenger 출력 코드 log.debug 로 수정
@Slf4j
@Component
public class Messenger {

	public void sendGreeting(String msg) {
		log.debug("오늘의 인사 : "+msg);
	}
	
	public void sendMember(MemberDto dto) {
		if(dto != null) {
			log.debug(dto.toString());
		}
	}
	
	public String getMessage() {
		log.debug("getMessage() 메소드가 수행됩니다");
		return "열심히 공부 합시다";
	}
}
  • Spring08ExampleApplication 출력 코드 log.debug 로 수정
log.debug("리턴된 msg : "+msg);
  • 출력 결과
2025-08-28T11:02:04.831+09:00 DEBUG 15984 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요
2025-08-28T11:02:04.831+09:00 DEBUG 15984 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : 오늘의 인사 : 안녕하세요
2025-08-28T11:02:04.831+09:00 DEBUG 15984 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : getMessage() 메소드가 수행됩니다
2025-08-28T11:02:04.831+09:00 DEBUG 15984 --- [Spring08_Example] [  restartedMain] c.e.spring08.Spring08ExampleApplication  : 리턴된 msg : 열심히 공부 합시다

메세지 조작 (파라미터 변경)

  • MemberDto 에 어노테이션 추가

  • @Data : setter, getter 메소드를 만들어주고 toString() 메소드를 오버라이딩 해서 필드에 들어 있는 정보를 확인 가능

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Alias("memberDto")
@Data
public class MemberDto {
	private int num;
	private String name;
	private String addr;
}
  • Spring08ExampleApplication 에 코드 추가
MemberDto dto = MemberDto.builder().num(1).name("유재석").addr("압구정").build();
m.sendMember(dto);
  • MessengerAspect 에 코드 추가
if(tmp instanceof MemberDto) { // 만일 MemberDto type 이라면
	log.debug("매개변수에 전달된 값 : "+tmp);
    MemberDto dto = (MemberDto)tmp;
	dto.setName("유제니");
}
2025-08-28T11:18:29.645+09:00 DEBUG 11400 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : 오늘의 인사 : 안녕하세요
2025-08-28T11:18:29.649+09:00 DEBUG 11400 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : MemberDto(num=1, name=유재석, addr=압구정)
2025-08-28T11:18:29.649+09:00 DEBUG 11400 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : MemberDto(num=1, name=유제니, addr=압구정)

메세지 호출 제어 (특정 조건 차단)

  • MessengerAspect 에 코드 추가
log.debug("매개변수에 전달된 값 : " +msg);
	if(msg.contains("박명수")) {
		log.error("박명수는 금지된 사용자의 이름으로 차단합니다");
		return; // 여기서 리턴하면 아래의 joinPoint.proceed(); 가 호출이 안된다
}
  • Spring08ExampleApplication 에 호출
// 오늘의 인사가 출력 안된다
m.sendGreeting("안녕하세요, 박명수씨");
// 오늘의 인사가 출력된다
m.sendGreeting("안녕하세요");
매개변수에 전달된 값 : 안녕하세요, 박명수씨
2025-08-28T11:41:09.870+09:00 DEBUG 16864 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요, 박명수씨
2025-08-28T11:41:09.870+09:00 ERROR 16864 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 박명수는 금지된 사용자의 이름으로 차단합니다
매개변수에 전달된 값 : 안녕하세요
2025-08-28T11:41:09.870+09:00 DEBUG 16864 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : 안녕하세요
2025-08-28T11:41:09.870+09:00 DEBUG 16864 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : 오늘의 인사 : 안녕하세요
2025-08-28T11:41:09.871+09:00 DEBUG 16864 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 매개변수에 전달된 값 : MemberDto(num=1, name=유재석, addr=압구정)
2025-08-28T11:41:09.871+09:00 DEBUG 16864 --- [Spring08_Example] [  restartedMain] com.example.spring08.aop.Messenger       : MemberDto(num=1, name=유제니, addr=압구정)

리턴값 가로채기

  • MessengerAspect 에 checkReturn 메소드 생성
  1. 메소드의 return typ 은 String 이고
  2. com.example.spring08.aop 패키지에 속해있는 모든 클래스(*) 중에서
  3. get 으로 시작하는 메소드
  4. 메소드의 매개변수는 비어있는 메소드
    ➜ 위의 4가지 조건이 모두 만족하면 아래의 aspect 가 허용
@Around("execution(String com.example.spring08.aop.*.get*())")
public Object checkReturn(ProceedingJoinPoint joinPoint) throws Throwable {
	// aspect 가 적용된 메소드를 실행하고 해당 메소드가 리턴하는 값을 변수에 담기
	Object obj = joinPoint.proceed();
	// 원래 type 으로 casting
	String returnValue = (String)obj;
	log.debug("원래 리턴한 값 : "+returnValue);
	// 리턴값이 있는 메소드에 aspect 를 적용하면 반드시 해당 데이터를 리턴해야 한다
	return "공부하지 말고 놉시다"; // 다른 값을 리턴해줄 수도 있다
}
2025-08-28T11:59:51.025+09:00 DEBUG 9468 --- [Spring08_Example] [  restartedMain] c.example.spring08.aop.MessengerAspect   : 원래 리턴한 값 : 열심히 공부 합시다
2025-08-28T11:59:51.025+09:00 DEBUG 9468 --- [Spring08_Example] [  restartedMain] c.e.spring08.Spring08ExampleApplication  : 리턴된 msg : 공부하지 말고 놉시다

Spring 프레임 워크의 특징

  1. DI (Dependency Injection)

  2. IOC (Inversion Of Control)

  3. AOP (Aspect Oriented Programming)

실습 (Gallery)

갤러리 목록

1 : N 구조

  • GalleryDto 클래스 생성
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GalleryDto {
	private int num;
	private String title;
    private String writer;
	private String content;
	private String createdAt;
	private List<GalleryImageDto> imageList;
	private String profileImage;
}
  • GalleryImageDto 클래스 생성
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GalleryImageDto {
	private int num;
	private int galleryNum;
	private String saveFileName;
	private String createdAt;
}
  • GalleryMapper 인터페이스 생성
  1. 게시글 저장 메소드 insert 생성
  2. 게시글 이미지 저장 메소드 insertImage 생성
  3. 시퀀스 번호 생성 메소드 getSequence 생성
  • 메소드의 리턴 type 이 SELECT 문의 resultType 으로 설정되기 때문에 SELECT 된 ROW 의 num 이 리턴된다
  1. 게시글 하나씩 조회할 메소드 getData 생성
  • 메소드의 리턴 type 이 GalleryDto 이기 때문에
    SELECT 된 ROW 1개 정보가 자동으로 GalleryDto 객체에 담겨서 리턴된다
  • 단, SELECT 문의 칼럼명과 GalleryDto 의 필드명이 일치해야 자동으로 담긴다
  • 메소드의 매개변수의 type 이 해당 SELECT 문의 parameterType 으로 설정
  1. 이미지와 같이 조회하는 게시글 목록 메소드 getListWithImages 생성
  • mapper xml 애 작성한 내용을 사용해야 하기 때문에 어노테이션 없이 메소드를 만든다
  1. 이미지 목록 조회 메소드 getImageList 문 생성
  • parameterType 은 int
  • SELECT 된 ROW 가 여러개니까 return type 이 List 이고
  • List 의 generic type 이 GalleryImageDto 이니깐 resultType 은 GalleryImageDto 가 된다
@Mapper
public interface GalleryMapper {

	// 게시글 저장
    @Insert("""
    		INSERT INTO gallery (num, title, writer, content)
    		VALUES (#{num}, #{title}, #{writer}, #{content})
    """)
	public void insert(GalleryDto dto);
    
    // 게시글 이미지 저장
    @Insert("""
	    	INSERT INTO gallery_image (num, galleryNum, saveFileName)
	    	VALUES (gallery_image_seq.NEXTVAL, #{galleryNum}, #{saveFileName})
    """)
	public int insertImage(GalleryImageDto dto);
    
    @Select("SELECT board_seq.NEXTVAL AS num FROM DUAL")
    public int getSequence();
    
 	@Select("""
 			SELECT g.num, title, writer, content,
 			TO_CHAR(g.createdAt, 'YYYY-MM-DD HH24:MI:SS') AS createdAt,
 			profileImage
 			FROM gallery g
 			JOIN users u ON writer = userName
 			WHERE g.num = #{num}
 	""")
 	public GalleryDto getData(int num);
    
 	public List<GalleryDto> getListWithImages(); // <select id="getListWithImage">
    
    @Select("""
     		SELECT num, saveFileName, TO_CHAR(createdAt, 'YYYY-MM-DD HH24:MI:SS') AS createdAt
 			FROM gallery_image
 			WHERE galleryNum = #{num}
 			ORDER BY num ASC 		
 	""")
 	public List<GalleryImageDto> getImageList(int num);
}
  • GalleryMapper.xml 생성
  1. 게시글과 이미지를 같이 조회하는 getListWithImages 문 생성
  2. GalleryIamgeDto 맵핑하는 galleryImageMap 문 생성
  • property = "Dto 의 필드명" column = "SELECT 된 칼럼명"
  • 예시) property = "num" column="gi_num"
    GalleryImageDto 객체의 setNum() 메소드를 호출하면서 SELECT 된 gi_num 을 전달하라는 의미
  1. GalleryDto 맵핑 galleryMap 문 생성
  • GalleryDto 의 imageList 라는 필드는 List<GalleryImageDto> type 이기 때문에 collection 요소를 이용해서 galleryImageMap 의 결과를 넣도록 설정한다
<mapper namespace="com.example.spring08.repository.GalleryMapper">
	<!-- GalleryImageDto 맵핑 -->
	<resultMap id="galleryImageMap" type="com.example.spring08.dto.GalleryImageDto">
		<id property="num" column="gi_num"/>
		<result property="galleryNum" column="galleryNum"/>
		<result property="saveFileName" column="saveFileName"/>
		<result property="createdAt" column="gi_createdAt"/>
	</resultMap>
	
	<!-- GalleryDto 맵핑 -->
	<resultMap id="galleryMap" type="com.example.spring08.dto.GalleryDto">
		<id property="num" column="g_num"/>
		<id property="writer" column="writer"/>
		<id property="title" column="title"/>
		<id property="content" column="content"/>
		<id property="createdAt" column="g_createdAt"/>
		<collection property="imageList" resultMap="galleryImageMap"/>
	</resultMap>
  
	<select id="getListWithImages" resultMap="galleryMap">
		SELECT g.num AS g_num, g.writer, g.title, g.content,
		TO_CHAR(g.createdAt, 'YYYY-MM-DD HH24:MI:SS') AS g_createdAt,
		gi.galleryNum, gi.num AS gi_num, gi.saveFileName,
		TO_CHAR(gi.createdAt, 'YYYY-MM-DD HH24:MI:SS') AS gi_createdAt
		FROM gallery g
		JOIN gallery_image gi
		ON g.num = gi.galleryNum
		ORDER BY g.num DESC, gi.num ASC
	</select>
</mapper>
  • GalleryController 클래스 생성 후 galleryList 메소드 생성
@RequiredArgsConstructor
@Controller
public class GalleryController {
	
	@GetMapping("/gallery/list")
	public String galleryList(Model model) {
    	return "gallery/list";
    }   
}
  • GalleryService 인터페이스 생성
public interface GalleryService {
	public List<GalleryDto> getGalleryList();
}
  • GalleryServiceImpl 클래스 생성 후 의존 객체를 생성자를 주입 받고 오버라이드
@RequiredArgsConstructor
@Service
public class GalleryServiceImpl implements GalleryService{
	// 의존 객체를 생성자로 주입 받기
	private final GalleryMapper galleryMapper;

	@Override
	public List<GalleryDto> getGalleryList() {
		return galleryMapper.getListWithImages();
	}
}
  • GalleryController 에 galleryList 메소드 완성
@RequiredArgsConstructor
@Controller
public class GalleryController {
	
	private final GalleryService galleryService;
	
	@GetMapping("/gallery/list")
	public String galleryList(Model model) {
		// 응답에 필요한 객체를 "list" 라는 키값으로 담고
		model.addAttribute("list", galleryService.getGalleryList());
		// Thymeleaf 페이지에서 목록을 응답
		return "gallery/list";
	}
}
  • list.html 생성
<th:block th:insert="~{/include/navbar :: nav('board')}"></th:block>

<div class="container">
    <a class="btn btn-outline-primary btn-sm mt-2" th:href="@{/gallery/new-form}">
        <i class="bi bi-image"></i> 
        <i class="bi bi-plus"></i>
        <span class="visually-hidden">겔러리 새글 작성</span>
    </a>
    <h1>Gallery 목록</h1>
    <div class="row" id="masonry-grid">    
        <div th:each="tmp : ${list}" class="col-sm-6 col-md-4 col-lg-3 mb-4">
            <div class="card">
                <img class="card-img-top"
                     th:src="@{/upload/{name}(name=${tmp.imageList.get(0).saveFileName})}"/>
                <div class="card-body">
                    <h4 th:text="${tmp.title}"></h4>
                    <a class="btn btn-sm btn-success"
                       th:href="@{/gallery/view(num=${tmp.num})}">
                        <span th:text="${#lists.size(tmp.imageList)}"></span> 개의 이미지
                        <!-- tmp.imageList.size() 도 가능하지만 null 인 경우 오류 발생-->
                    </a>
                </div>
            </div>
        </div>
    </div>
</div>
<script>
    // masonry 레이아웃을 초기화 하는 javascript
    const grid = document.querySelector("#masonry-grid");
    imagesLoaded(grid, ()=>{
        new Masonry(grid, { percentPosition:true});
    });
</script>
  • SecurityConfig 에 화이트리스트 추가
String[] whiteList = {"/", "/user/loginform", "/user/login-fail",
			"/user/expired", "/user/signup-form", "/user/signup",
			 "/test/**", "/user/check-id", "/board/list", "/board/view",
			 "/upload/**", "/gallery/list", "/gallery/view"}

갤러리 작성

  • GalleryController dp newForm 메소드 작성
@GetMapping("/gallery/new-form")
public String newForm() {
	
	return "gallery/new-form";
}
  • new-form.html 생성
<style>
    .drop-zone {
        border: 2px dashed #0d6efd;
        cursor: pointer;
        border-radius: 10px;
        background-color: #f8f9fa;
        display:flex;
        align-items: center;
        justify-content: center;
        height: 200px;
        
    }
    /* drop zone 에 파일을 드레그해서 위에 올렸을때 적용할 css */
    .drop-zone.dragover {
        background-color: #e9ecef;
    }
    .preview img {
        max-width: 120px;
        margin: 10px;
        border: 1px solid #ccc;
        border-radius: 5px;
    }
    .preview-item {
        position: relative;
        display: inline-block;
    }
	.remove-btn {
	    position: absolute;
	    top: 11px;
	    right: 11px;
	    background-color: #ffffffcc; /* 반투명 흰색 */
	    color: #333;
	    border: none;
	    border-radius: 4px;
	    width: 20px;
	    height: 20px;
	    font-size: 16px;
	    cursor: pointer;
	    text-align: center;
	    transition: background-color 0.2s, color 0.2s;
	    box-shadow: 0 1px 4px rgba(0,0,0,0.2);
	    display: flex;
		align-items: center;
		justify-content: center;
	}
	
	.remove-btn:hover {
	    background-color: #dc3545;
	    color: white;
	}
</style>
<div class="container mt-4">
    <h2><i class="bi bi-image"></i> 새 갤러리 작성</h2>
    <form id="saveForm" th:action="@{/gallery/save}" method="post" enctype="multipart/form-data">
        <div class="mb-3">
            <label for="title" class="form-label">제목</label>
            <input type="text" class="form-control" id="title" name="title" required>
        </div>
        <div class="mb-3">
            <label for="content" class="form-label">내용</label>
            <textarea class="form-control" id="content" name="content" rows="6" required></textarea>
        </div>

        <div class="mb-3">
            <label class="form-label">이미지 업로드 (Drag & Drop 또는 클릭)</label>
            <div class="drop-zone" id="dropZone">
                이곳에 이미지를 끌어다 놓거나 클릭하여 선택하세요.
                <input type="file" name="images" id="fileInput" multiple hidden>
            </div>
            <div class="preview d-flex flex-wrap mt-3" id="preview"></div>
        </div>

        <button type="submit" class="btn btn-primary">등록</button>
    </form>
</div>

<script>
    const dropZone = document.querySelector("#dropZone");
    const fileInput = document.querySelector("#fileInput");
    const preview = document.querySelector("#preview");

    let selectedFiles = [];
	
    // drop zone 을 클릭했을때 input type="file" 을 강제 클릭해서 파일 선택창이 열리도록 한다 
    dropZone.addEventListener("click", () => fileInput.click());
	
    // drop zone 에 마우스가 드레그해서 들어갔을때
    dropZone.addEventListener("dragover", (e) => {
        e.preventDefault();
        //dragover 클래스를 추가해서 회색으로 변경되도록 한다.
        dropZone.classList.add("dragover");
    });
 	// drop zone 에 마우스가 드레그해서 나갔을때 
    dropZone.addEventListener("dragleave", () => {
    	//dragover 클래스를 제거해서 색상이 원래 색으로 돌아가도록 한다. 
        dropZone.classList.remove("dragover");
    });
	//파일을 끌어다가 drop 했을때
    dropZone.addEventListener("drop", (e) => {
        e.preventDefault();
        dropZone.classList.remove("dragover");
        //선택된 파일객체가 들어 있는 배열을 기능을 모두 쓸수 있는 배열로 변경한다음
        const files = Array.from(e.dataTransfer.files);
        //기존에 선택한 파일이 들어 있는 배열에 합친다 
        selectedFiles = [...selectedFiles, ...files];
        //preview 를 업데이트한다(미리보기 기능)
        updatePreview();
    });
	// drag drop 이 아니고 파일을 직접 선택했을때 input type="file" 에  change 이벤트가 발생한다. 
    fileInput.addEventListener("change", () => {
    	//위와 동일한 동작을 한다 
        const files = Array.from(fileInput.files);
        selectedFiles = selectedFiles.concat(files);
        updatePreview();
    });
	
	// preview 을 업데이트 하는 함수 (내부에서 await 이 있는 비동기 함수이기 때문에 async 키워드를 붙여서 만들었다 )
    async function updatePreview() {
		//이미 출력된 이미지를 모두 없애고 
        preview.innerHTML = "";
		
        // input type="file" 에 value 를 넣어줄 정보를 구성하기 위한 객체 
        const dataTransfer = new DataTransfer();
		
        //반복문 돌면서 선택된 파일 객체를 순서대로 참조하면서 
        for (let i = 0; i < selectedFiles.length; i++) {
        	// i 번째 파일 객체를 불러와서  
            const file = selectedFiles[i];
        	//만일 이미지 파일이 아니라면 continue (for 문을 계속 실행한다 continue 아래의 코드를 실행하지 않고 )
            if (!file.type.startsWith("image/")) continue;

            try {
            	// 선택한 이미지 파일을 읽어 들여서 결과를 얻어낸다 
                const imageUrl = await readFileAsDataURL(file);  

                const container = document.createElement("div");
                container.classList.add("preview-item");

                const img = document.createElement("img");
                img.setAttribute("src", imageUrl);

                const btn = document.createElement("button");
                
                btn.classList.add("remove-btn");
                btn.innerText = "×";
                
                btn.addEventListener("click", ()=>{
                	// selectedFiles 배열에서 i 번째방으로 부터 1개의 item 삭제
                	selectedFiles.splice(i, 1);
                	//삭제후 preview 가 다시 update 되도록 함수를 다시 호출해 준다.
                    updatePreview();
                });

                container.insertAdjacentElement("beforeend", img);
                container.insertAdjacentElement("beforeend", btn);
                preview.insertAdjacentElement("beforeend", container);
				//DataTransfer 객체에 파일객체를 추가 
                dataTransfer.items.add(file);
            } catch (err) {
                console.error("이미지 로딩 실패:", err);
            }
        }
		//선택한 파일(이미지 파일만 선별)객체가 들어있는 배열을 input type="file" 의 value 로 넣어준다. 
        fileInput.files = dataTransfer.files;
    }

    //전달된 파일을 읽어들이는 Promise 객체를 리턴해주는 함수 
    function readFileAsDataURL(file) {
    	
        return new Promise((resolve, reject) => {
        	//FileReader객체를 이용해서 파일을 읽어들이고 
            const reader = new FileReader();
        	//모두다 읽었을때 실행할 함수 등록 
            reader.onload = ()=>{
            	//읽은 data url 문자열을 resolve() 함수를 호출하면서 전달한다(Promise 가 해결된다)
            	resolve(reader.result);
            }
            reader.readAsDataURL(file);
        });
    }
    
    //폼에 "submit" 이벤트가 일어 났을때 실행할 함수 등록 
    document.querySelector("#saveForm").addEventListener("submit", (e)=>{
    	//여기서 폼 입력란에 대한 검증을 하고 폼 제출을 막고 싶으면 e.preventDefault() 를 실행하게 하면 된다.
    	
    	//만일 선택한 파일이 없다면 폼 전송 막기
    	if(selectedFiles.length < 1){
    		alert("업로드할 이미지를 1 개 이상 선택해 주세요");
    		e.preventDefault();
    	}
    });  
</script>
  • GalleryUploadRequest 클래스 생성
public class GalleryUploadRequest {
	private String title;
	private String content;
	// <input type="file" name="images" multiple>
	// images 라는 파라미터명으로 파일이 여러개 전송되니까 MultipartFile[] type 으로 선언한다
	private MultipartFile[] images;
}
  • GalleryController 에 gallerySave 메소드 생성

  • 업로드된 파일의 모든 정보를 GalleryUploadRequest 의 images 라는 MultipartFile[] 객체에 담아서 전달

@PostMapping("/gallery/save")
public String gallerySave(GalleryUploadRequest uploadRequest) {
		
	return "gallery/save";
}
profile
학원 공부 내용 정리

0개의 댓글