클라우드에 저장하면, DB에 찾을 수 있게 내용을 저장해줘야 한다.
일반파일과 달리 이미지파일은 db에 저장하고 화면을 출력해 주는 내용까지 있어야 함.
style.js에 파일업로드 함수가 만들어져 있다.
jquery는 다중 태그이벤트도 한번에 처리 가능. $('선택자1 선택자2')
productReg.html에
이미지파일 업로드가 가능한 태그가 3개 있다.
이 태그들은
<label class="upload-display" for="main_file">
<span class="upload-thumb-wrap">
<img class="upload-thumb" src="../img/plus_btn.png" >
</span>
</label>
<input class="upload-name" value="파일선택" disabled="disabled">
<input type="file" name="file" id="main_file" class="upload-hidden"
로 되어 있고, form으로 감싸져 있다.
이미지 미리보기
label태그의 for="정한이름" 속성과 input의 id="정한이름"가 같으면 서로 연결된다.
파일은 input에 hidden으로 넣어서 form으로 넘긴다.
이 input은 모두 name이 file이다.
한번에 연결!
ProductController.java
//등록
@PostMapping("/registForm")
public String registForm(@Valid ProductVO vo, Errors error, Model model, RedirectAttributes ra, @RequestParam("file")List<MultipartFile> lists) {
if(error.hasErrors()) {
List<FieldError> list=error.getFieldErrors();
ArrayList<String> msglist=new ArrayList<>();
for(FieldError err : list) {
if(err.isBindingFailure()) {
model.addAttribute("msg", "형식이 잘못되었습니다.");
}else {
msglist.add(err.getDefaultMessage());
}
}
model.addAttribute("msglist", msglist);
model.addAttribute("vo", vo);
return "product/productReg";
}
//파일업로드 작업 ->
//리스트에서 빈값은 제거
lists=lists.stream().filter((x)->x.isEmpty()==false).collect(Collectors.toList());
//확장자가 image가 아니라면 경고문
for(MultipartFile file:lists) {
if(file.getContentType().contains("image") ==false ) {
ra.addFlashAttribute("msg","이미지는 png, jpg, jpeg형식만 등록가능합니다.");
return "redirect:/product/productReg";
}
}
//파일업로드 작업을 ->service영역으로 위임
//글 등록 작업
int result=productService.regist(vo,lists);
String msg=result==1?"정상 등록되었습니다":"등록에 실패했습니다";
ra.addFlashAttribute("msg", msg);
return "redirect:/product/productList"; //목록으로
}
@RequestParam("file")List<MultipartFile> list는 VO로도 받을 수도 있다.
확장자가 image가 아니라면 경고
이미지가 올라오면 file.getContentType이 image로 나온다.
-text/image
-image/png
-image/jpg
-image/jpeg
등등.
contains를 써서 이미지파일이 아닐 경우 경고메시지와 함께 리다이렉트.
ProductService.java
//글등록 (파일업로드)
public int regist(ProductVO vo,List<MultipartFile> list);
ProductServiceImpl.java
@Value("${project.uploadpath}")
private String uploadpath;
//날짜별로 폴더생성
public String makeDir() {
Date date=new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
String now=sdf.format(date);
String path=uploadpath + "\\" +now; //경로
File file = new File(path);
if(file.exists()==false) {//파일이 존재하면 true
file.mkdir(); //폴더생성
}
return now; //년월일 폴더위치
}
//글등록
//한 프로세스 안에서 예외가 발생하면, 기존에 진행했던 CRUD작업을 Rollback시킨다.
//조건-catch를 통해서 예외처리가 진행되면 트랜잭션 처리가 되지 않는다.
@Transactional(rollbackFor = Exception.class)
@Override
public int regist(ProductVO vo, List<MultipartFile> list) {
//1. 글등록 처리->
int result=productMapper.regist(vo);
//2. 파일인서트
for(MultipartFile file:list) {
//파일명
String origin = file.getOriginalFilename();
//브라우저별로 경로가 포함되서 올라오는 경우가 있기에 간단한 처리.
String filename=origin.substring(origin.lastIndexOf("\\")+1);
//폴더생성
String filepath=makeDir();
//중복파일의 처리
String uuid=UUID.randomUUID().toString();
//최종저장경로
String savename=uploadpath+"\\"+filepath+"\\"+uuid+"_"+filename;
try {
File save = new File(savename); //세이브경로
file.transferTo(save);//업로드 진행
} catch (Exception e) {
e.printStackTrace();
return 0; //실패의 의미로 0
}
//인서트 - insert이전에 prod_id가 필요한데, selectKey방식으로 처리
ProductUploadVO prodVO=ProductUploadVO.builder().filename(filename).filepath(filepath).uuid(uuid).prod_writer(vo.getProd_writer()).build();
productMapper.registFile(prodVO);
}//end for
return result; //성공시 1, 실패시 0
}
※이건 미완성된 구문임. mapper와 연결하는게 두개가 있다. 둘 다 검사해야한다.
makeDir의 return과 savename부분이 이전과 다른데, db에 저장할 때 간단하게 저장해서 나중에 편하게 사용하기 위함.
service는 매개변수로 list를 하나 받는데, 이 list는 업로드한 이미지들이 들어있다.
ProductUploadVO를 생성하고 build를 사용해 값을 넣는다. 그리고 mapper로 연결.
@Transactional(rollbackFor = Exception.class)
한 프로세스 안에서 예외가 발생하면, 기존에 진행했던 CRUD작업을 Rollback시킨다.
조건-catch를 통해서 예외처리가 진행되면 트랜잭션 처리가 되지 않는다.
-throws Exception을 하면 Transactional은 동작하지 않는다.
-try-catch를 해도 Transactional은 동작하지 않는다.
spring에서 사용하려면 최소한의 설정을 해줘야 하는데, 스프링부트는 바로 사용 가능
메서드에서 어떠한 에러가 발생하면 sql을 전부 롤백해줌.
위에서 insert가 2번, select가 1번 발생하는데 그 중 하나가 에러를 발생해도 다른 건 실행될 수 있다.
이걸 막기 위해 롤백이 실행됨.
try-catch 바깥에서 insert를 실행한다. Transactional은 이때 에러를 찾으면 동작.
날짜별로 폴더생성하는 makeDir메서드의 return을 now로 바꾼다(년월일 폴더위치)
나중에 찾기 편하게 하기 위해.
makeDir의 return이 바뀌었으니 최종저장경로를 지정하는 구문도 수정.
테이블생성
############################파일업로드 테이블############################
CREATE TABLE PRODUCT_UPLOAD (
UPLOAD_NO INT PRIMARY KEY auto_increment,
FILENAME varchar(100) not null, ##실제파일명
FILEPATH varchar(100) not null, ##폴더명
UUID varchar(50) not null, ##UUID명
REGDATE TIMESTAMP default now(),
PROD_ID INT, ##FK
PROD_WRITER VARCHAR(20) ##FK
);
1:N관계
PRODUCT가 1, PRODUCT_UPLOAD가 N
N쪽에 fk가 들어간다.
prod_id와 prod_writer를 fk로.
upload_no는 pk.
DB에 파일의 경로를 넣어 놓고, 나중에 찾아 쓴다.
filepath는 개발환경이나 클라우드저장 등에 따라 바뀔 수 있다.
이 테이블은 ProductUploadVO와 연결됨. 동일한 변수를 가지고 있다.
select max(prod_id) as prod_id from product where prod_writer = 'admin';
max는 위험할 수도 있다.
db는 Acid를 제공. but 약간의 확률이 있을 수도.
지금은 where로 특정조건을 주므로 문제되지 않을 것.
ProductMapper.java
public int regist(ProductVO vo);
public int registFile(ProductUploadVO vo);
mapper에서
파일업로드 테이블과 연결하는 메서드의 매개변수는 vo나 맵으로.
ProductMapper.xml
<!--
1. insert전에 product테이블의 키값을 selectKey태그를 이용해서 얻습니다.
2. resultType은 조회된 결과 타입,
keyProperty는 sql에 전달되는 vo에 저장할 key값,
order는 BEFORE, AFTER - insert 이전에 실행 or insert 이후에 실행
-->
<insert id="registFile" parameterType="ProductUploadVO">
<selectKey resultType="int" keyProperty="prod_id" order="BEFORE">
select max(prod_id) as prod_id from PRODUCT where prod_writer=#{prod_writer}
</selectKey>
insert into PRODUCT_UPLOAD(filename,
filepath,
uuid,
prod_id,
prod_writer)
values(#{filename},
#{filepath},
#{uuid},
#{prod_id},
#{prod_writer})
</insert>
insert이전에 prod_id가 필요한데, 값을 구할 방법이 없다!
서비스의 regist메서드에서 prod_writer는 화면에서 넘어오는데 prod_id는 넘어오지 않는다.
따라서, 시퀀스를 사용하거나 해야 하는데, mysql은 시퀀스가 없다.
=>
mybatis의
<selectKey>태그
-order는 순서. insert 이전인가 이후인가.
-resultType은 조회된 결과 타입.
-keyProperty는 sql에 전달되는 vo에 저장할 key값. vo의 멤버변수에 저장하겠다는 선언.
이 태그는 insert태그 안에 있다. 따라서 select ~ as 이름으로 얻은 값을 insert의 sql구문에서 #{}로 사용할 수 있다.
동작이 끝나면 db에도 저장되고, 업로드되는 폴더에도 저장된다.
db에 저장되었으니 list화면에서도 찾아볼 수 있다.
select *from product where prod_writer = 'admin' order by prod_id desc;
select * from product_upload;
'Spring Boot' 카테고리의 다른 글
230222 Spring Boot 파일다운로드 (0) | 2023.02.22 |
---|---|
230222 Spring Boot 이미지파일 불러오기 (0) | 2023.02.22 |
230221 Spring Boot 파일 업로드-썸네일 이미지 (0) | 2023.02.21 |
230221 Spring Boot 파일 업로드-비동기 업로드 방식 (0) | 2023.02.21 |
230221 Spring Boot 파일 업로드 방식 (0) | 2023.02.21 |