Mapper 인터페이스
- DAO를 사용하지 않음, DAO 대신 사용
- 반드시 인터페이스로 선언해야한다
- 네임스페이스 명은 패키지 포함 인터페이스 이름으로 작성
- <mapper namespace="myspring.user.dao.UserMapper">
- 메서드 명은 SQL와 동일하게 작성 (xml의 mapper id값과 동일)
예시
**Mapper.java
package kr.or.ddit.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface **Mapper {
public int insert**(); // xml id명과 동일한 이름으로 작성
}
**_SQL.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.or.ddit.mapper.**Mapper"> <!-- Mapper로 작성 -->
<insert id="insert**"> <!-- interface 메서드와 동일하게 작성 -->
</insert>
</mapper>
SQL
--파일그룹
CREATE TABLE FILE_GROUP(
FILE_GROUP_NO NUMBER,
FILE_REGDATE DATE,
CONSTRAINT PK_FILE_GROUP PRIMARY KEY(FILE_GROUP_NO)
);
COMMENT ON TABLE FILE_GROUP IS '파일그룹';
COMMENT ON COLUMN FILE_GROUP.FILE_GROUP_NO IS '파일그룹번호';
COMMENT ON COLUMN FILE_GROUP.FILE_REGDATE IS '파일저장일';
--파일상세
CREATE TABLE FILE_DETAIL(
FILE_SN NUMBER,
FILE_GROUP_NO NUMBER,
FILE_ORIGINAL_NAME VARCHAR2(50),
FILE_SAVE_NAME VARCHAR2(100),
FILE_SAVE_LOCATE VARCHAR2(400),
FILE_SIZE NUMBER,
FILE_EXT VARCHAR2(10),
FILE_MIME VARCHAR2(30),
FILE_FANCYSIZE VARCHAR2(30),
FILE_SAVE_DATE DATE,
FILE_DOWNCOUNT NUMBER,
CONSTRAINT PK_FILE_DETAIL PRIMARY KEY(FILE_SN,FILE_GROUP_NO),
CONSTRAINT FK_FILE_DETAIL FOREIGN KEY(FILE_GROUP_NO)
REFERENCES FILE_GROUP(FILE_GROUP_NO)
);
COMMENT ON COLUMN FILE_DETAIL.FILE_SN IS '파일 순번';
COMMENT ON COLUMN FILE_DETAIL.FILE_GROUP_NO IS '파일 그룹 번호';
COMMENT ON COLUMN FILE_DETAIL.FILE_ORIGINAL_NAME IS '파일 원본 명';
COMMENT ON COLUMN FILE_DETAIL.FILE_SAVE_NAME IS '파일 저장 명';
COMMENT ON COLUMN FILE_DETAIL.FILE_SAVE_LOCATE IS '파일 저장 경로';
COMMENT ON COLUMN FILE_DETAIL.FILE_SIZE IS '파일 크기';
COMMENT ON COLUMN FILE_DETAIL.FILE_EXT IS '파일 확장자';
COMMENT ON COLUMN FILE_DETAIL.FILE_MIME IS '파일 마임타입';
COMMENT ON COLUMN FILE_DETAIL.FILE_FANCYSIZE IS '파일 팬시 크기';
COMMENT ON COLUMN FILE_DETAIL.FILE_SAVE_DATE IS '파일 저장 일시';
COMMENT ON COLUMN FILE_DETAIL.FILE_DOWNCOUNT IS '파일 다운로드 수';
JAVA
FileGroupVO.java
package kr.or.ddit.vo;
import java.util.Date;
import java.util.List;
import lombok.Data;
@Data
public class FileGroupVO {
private long fileGroupNo;
private Date fileRegdate;
private List<FileDetailVO> fileDetailVoList;
}
FileDetailVO.java
package kr.or.ddit.vo;
import java.util.Date;
import lombok.Data;
@Data
public class FileDetailVO {
private int fileSn;
private long fileGroupNo;
private String fileOriginalName;
private String fileSaveName;
private String fileSaveLocate;
private long fileSize;
private String fileExt;
private String fileMime;
private String fileFancysize;
private Date fileSaveDate;
private int fileDowncount;
}
type Alias설정
<typeAliases>
<typeAlias type="kr.or.ddit.vo.FileDetailVO" alias="fileDetailVo" />
<typeAlias type="kr.or.ddit.vo.FileGroupVO" alias="fileGroupVo" />
</typeAliases>
bean 등록 (mapper 패키지)
root-context.xml
<!-- Mapper 인터페이스 설정
개발자가 직접 DAO를 설정하지 않아도
자동으로 Mapper 인터페이스를 활용하는 객체를 생성하게 됨
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="kr.or.ddit.**.mapper" />
</bean>
** 예시 **
JSP
register2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<script type="text/javascript" src="/resources/js/jquery.min.js"></script>
<h2>파일업로드</h2>
<!-- spring form 태그 생성 -->
<!-- 필수! modelAttribute = "폼객체의 속성명(VO)" (mybatis) -->
<form:form action="/item/registerPost2" method="post" enctype="multipart/form-data"
modelAttribute="memberVO"> <!-- 폼 객체의 속성명을 addAttribute 속성에 지정 (같은 이름으로) -->
<!-- MemberVO의 객체의 memName과 연동 -->
<!-- path = memberVo의 프로퍼티명
폼 항목에 바인딩(메모리에 데이터가 올라감)되는 폼 객체의 프로퍼티를 지정함
disabled : 폼 항목을 비활성화 할 때 사용. 기본값 false
readonly : 폼 항목을 읽기 전용으로 만들 때 사용. 기본값 false -->
<p>아이디: <form:input path="memId" disabled="false"/>
<p>이름 : <form:input path="memName" readonly="true"/></p>
<!-- submit으로 정보를 전달 할 시 form태그를 사용하지 않아도 정보가 전달됨 -->
<!-- form 태그를 사용하지않고 el태그를 통해 값을 받음 -->
<p>주민번호1 : <input type="text" name="memRegno1" value="${memberVO.memRegno1}"> </p>
<input type="file" name="fileImage" />
<button type="submit">파일업로드</button>
</form:form>
JAVA
ItemController.java
package kr.or.ddit.controller;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import kr.or.ddit.service.ItemService;
import kr.or.ddit.vo.MemberVO;
import lombok.extern.slf4j.Slf4j;
@RequestMapping("/item")
@Slf4j
@Controller
public class ItemController {
//DI(의존성 주입), IoC(제어의 역전)
// root-context에 있는 value값 c:\\upload
@Autowired
String uploadPath;
@Autowired
String uploadFolder;
@Autowired
ItemService service;
// 컨트롤러는 기본적으로 자바빈즈 규칙에 맞는 객체 VO는
// 다시 화면으로 폼객체를 전달함
// 폼 객체의 속성명은 직접 지정하지 않으면 폼 객체의 클래스명의
// 맨처음 문자를 소문자로 변환하여 처리함 (memberVO)
// 속성명 지정 @ModelAttribute("이름")
@GetMapping("/register2")
public String register2(@ModelAttribute("memberVO") MemberVO memberVO) {
memberVO.setMemName("오리");
memberVO.setMemId("a001");
memberVO.setMemRegno1("123123");
return "item/register2";
}
// disabled한 파라미터의 값은 전달되지않음
@PostMapping("/registerPost2")
public String registerPost2(@ModelAttribute("memberVO") MemberVO memberVO) {
// 계획 ( 경로와 파일이름 )
int result = this.service.registerPost2(memberVO);
return "item/register2";
}
}
ItemService.java
package kr.or.ddit.service;
import kr.or.ddit.vo.MemberVO;
public interface ItemService {
public int registerPost2(MemberVO memberVO);
}
ItemServiceImpl.java
package kr.or.ddit.service.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import kr.or.ddit.mapper.FileGroupMapper;
import kr.or.ddit.service.ItemService;
import kr.or.ddit.vo.FileDetailVO;
import kr.or.ddit.vo.FileGroupVO;
import kr.or.ddit.vo.MemberVO;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnailator;
@Slf4j
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
String uploadPath;
// 매퍼 xml이 inject됨
@Autowired
FileGroupMapper mapper;
@Override
public int registerPost2(MemberVO memberVO) {
int result = 0;
// memberVO 객체로부터 파일객체를 꺼내보자
MultipartFile multipartFile = memberVO.getFileImage();
// 파일명
String fileName = multipartFile.getOriginalFilename();
// MIME(Multipurpose Internet Mail Extensions)타입
/*
* Multipurpose Internet Mail Extensions의 약자로 간단히 말하면 파일 변환을 의미한다.현재는 웹을 통해 여러
* 형태의 파일을 전달하는데 사용하고 있지만 이 용어가 생길 땐 이메일과 함께 동봉할 파일을 텍스트 문자로 전환해서 이메일 시스템을 통해
* 전달하기 위해 개발되어 Internet Mail Extensions라고 불리기 시작했다고 한다.
*/
String contentType = multipartFile.getContentType();
// 파일 크기. byte(1) short(2) int(4) long(8)
long size = multipartFile.getSize();
log.info("====================");
log.info("fileName : " + fileName);
log.info("contentType : " + contentType);
log.info("size : " + size);
log.info("====================");
// 서버의 어디로 파일을 복사할것인지?
// 연월일 폴더 생성
// C:\\upload
// , : + \\ +
// 2024\\08\\06
File uploadFolder = new File(this.uploadPath, getFolder());
if (!uploadFolder.exists()) {
uploadFolder.mkdirs();
}
// 만약 폴더가 없으면 자동 생성
if (uploadFolder.exists() == false) {
uploadFolder.mkdirs();
}
// 같은날 같은 이미지 업로드 시 파일 중복 방지 시작 /////
// java.util.UUID => 랜덤값 생성
UUID uuid = UUID.randomUUID();
// 원래의 파일 이름과 구분하기 위해 _를 붙임(asdfasdfsdfa_개똥이.jpg)
fileName = uuid.toString() + "_" + fileName;
// 같은날 같은 이미지 업로드 시 파일 중복 방지 끝 /////
// File객체 설계(복사할 대상 경로, 파일명)
// C:\\upload\\2024\\08\\06 + "\\" + asdfafds_개똥이.jpg
File saveFile = new File(uploadFolder, fileName);
try {
// 파일객체.transferTo(설계)
multipartFile.transferTo(saveFile);
// 썸네일 처리
// 이미지인지 체킹
// C:\\upload\\2024\\08\\06 + "\\" + s_asffasd_개똥이.jpg
if (checkImageType(saveFile)) {// 이미지라면
// 계획
FileOutputStream thumbnail = new FileOutputStream(new File(uploadFolder, "s_" + fileName));
// 썸네일 생성(파일객체,계획,가로크기,세로크기)
Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail, 100, 100);
thumbnail.close();
}
FileGroupVO fgvo = new FileGroupVO();
// File_group 테이블에 insert
// selectkey에서 받은 fileGroupNo가 fgvo값에 들어옴
result += this.mapper.insertFileGroup(fgvo);
FileDetailVO fdvo = new FileDetailVO();
fdvo.setFileSn(1);
fdvo.setFileGroupNo(fgvo.getFileGroupNo());
fdvo.setFileOriginalName(multipartFile.getOriginalFilename());
fdvo.setFileSaveName(fileName);
fdvo.setFileSaveLocate("/upload/"+getFolder().replace("\\", "/")+"/"+fileName); // 웹경로+uuid_파일명
fdvo.setFileSize(size);
fdvo.setFileExt(fileName.substring(fileName.lastIndexOf(".")+1));
fdvo.setFileMime(contentType);
// 팬시 크기
fdvo.setFileFancysize("0");
result+= this.mapper.insertFileDetail(fdvo);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
// 연 / 월 / 일 폴더 생성
public String getFolder() {
// 2024-08-06 형식(format)지정
// 간단한 날짜 형식
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date today = new Date();
String str = sdf.format(today);
// 2024-08-06 > 2024\\08\\06
return str.replace("-", File.separator);
}
public boolean checkImageType(File file) {
// MIME(Multipurpose Internet Mail Extensions) : 문서, 파일 또는 바이트 집합의 성격과 형식. 표준화
// MIME 타입 알아냄. .jpeg / .jpg의 MIME타입 : image/jpeg
String contentType;
try {
contentType = Files.probeContentType(file.toPath());
// image로 시작
return contentType.startsWith("image");
} catch (Exception e) {
log.error(e.getMessage());
}
return false;
}
}
FileGroupMapper.java
package kr.or.ddit.mapper;
import org.apache.ibatis.annotations.Mapper;
import kr.or.ddit.vo.FileDetailVO;
import kr.or.ddit.vo.FileGroupVO;
@Mapper
public interface FileGroupMapper {
public int insertFileGroup(FileGroupVO fgvo);
public int insertFileDetail(FileDetailVO fdvo);
}
Mybatis
FileGroup_SQL.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.or.ddit.mapper.FileGroupMapper">
<!-- file_group insert -->
<insert id="insertFileGroup" parameterType="fileGroupVo">
<!-- 숫자형 문자 + 숫자 => 숫자 -->
<selectKey order="BEFORE" keyProperty="fileGroupNo" resultType="long">
select nvl(max(file_group_no),to_char(sysdate,'YYYYMMDD')||'000')+1 file_group_no
from file_group
where to_char(sysdate,'YYYYMMDD') = to_char(file_regdate,'YYYYMMDD')
</selectKey>
insert into file_group(file_group_no, file_regdate)
values(#{fileGroupNo}, sysdate)
</insert>
<insert id="insertFileDetail" parameterType="fileDetailVo">
insert into file_detail(file_sn,file_group_no,file_original_name,
file_save_name,file_save_locate,file_size,file_ext,
file_mime,file_fancysize,file_save_date,file_downcount)
values(#{fileSn},#{fileGroupNo},#{fileOriginalName},#{fileSaveName},
#{fileSaveLocate},#{fileSize},#{fileExt},#{fileMime},#{fileFancysize},
sysdate, #{fileDowncount})
</insert>
</mapper>