파일 업로드의 경우 파일이 여러개 이고 보여지는화면이 하나이기에 1:N 으로 구성되어있음
따라서 , 테이블 생성 시 foreign key 지정이 필요함
* 사전에 html 메뉴 생성해놓기
1. 데이터 생성하기
: 두개의 데이터 테이블 생성
create table goods(
gno bigint primary key auto_increment,
name varchar(255) not null,
content text not null,
price int not null,
created_date timestamp default current_timestamp(),
updated_date timestamp default current_timestamp() on update current_timestamp()
);
create table goods_file(
fno bigint primary key auto_increment,
url varchar(255) not null,
name varchar(255) not null,
size bigint not null,
created_date timestamp default current_timestamp(),
updated_date timestamp default current_timestamp() on update current_timestamp(),
gno bigint not null,
constraint fk_goods_file_goods foreign key(gno) references goods(gno)
);
2. 맵핑할 클래스 생성하기
- main에 NoArgsConstructor 와 AllArgsConstructor 를 두개 쓰는 이유 : https://cantcoding.tistory.com/61
<롬복 코드 참고: https://dingue.tistory.com/14>
@Builder |
성자에 인자가 많을 때, 불필요한 생성자를 제거하고, 가독성+객체불변성+일관성 등 유연함을 부여함
- 빌더패턴의 표현, 파라미터 있는 생성자에만 쓸 수 있다. 즉 NoArgsConstructor 은 단독으로 사용하지 못함 |
@Getter | 단순히 필드를 리턴하는 것 |
@Setter | 단순히 필드에 값을 설정해 주는 것 |
@NoArgsConstructor | 파라미터가 없는 생성자를 생성함 - 필드가 final 로 생성되어 있는 경우 오류가 발생함 |
@AllArgsConstructor | 클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성해줌 - NonNull 마크가 되어있다면 생성자 내에서 null-check 로직으로 자동 생성 |
@RequiredConstructor | 초기화되지 않은 모든 final 필드, @NonNull 로 마크되어있는 모든 필드들의 생성자를 자동 생성 |
package com.green.nowon.domain.entry;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
//@RequiredArgsConstructor //final 이나 @nonnull인 필드값만 파라미터로 받는 생성자
@Builder //생성자에 인자가 많을 때, 불필요한 생성자를 제거하고, 가독성+객체불변성+일관성 등 유연함을 부여함
@AllArgsConstructor //모든 필드값을 파라미터로 받는 생성자로 만듬(필드 6개 모두를 초기화하는 역할을하는 파라미터가 정의된 생성자)
@NoArgsConstructor //파라미터가 없는 기본생성자를 생성(디폴트 초기화, 인자없이 생성할 수 있는 생성자)
@Getter
public class GoodsEntity {
private long gno; // 상품관리번호
private String name; //상품명
private String content; //내용
private int price; //가격
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
}
package com.green.nowon.domain.entry;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class GoodsFileEntity {
private long fno;
private String url; //파일 경로
private String name; // 파일이름
private long size; //파일사이즈 byte
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
//FK
private long gno; //FK Goods 에 있는 pk
}
3. 컨트롤러 생성
package com.green.nowon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class GoodsController {
@GetMapping("/goods")
public String list() {
return "goods/list" ;
}
}
4. HTML 생성
- Thymeleaf 에서 this 는 생략 가능
<!DOCTYPE html>
<html xmlns:th="//www.thymeleaf.org"
th:replace="layout/layout1 :: layout(~{this::head},~{this::main})" >
<!-- 호출 th:replace 또는 th:insert |여기 테플릿 head와 main 에 넣을게요-->
<head>
</head>
<main>
<h1>메인영역</h1>
<div class="wrap view-size">
<section id="goods-list">
<h1>상품목록 페이지</h1>
<div class="wrap">
<p class="tit"> 상품목록 페이지 </p>
<div class= "flex end">
<a href="/goods/new"> 상품등록 </a>
</div>
</div>
</section>
</div>
</main>
</html>
5. write page생성
- controller 수정
package com.green.nowon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class GoodsController {
@GetMapping("/goods")
public String list() {
return "goods/list" ;
}
@GetMapping("/goods/new")
public String write() {
return "goods/write" ;
}
}
- writer page 생성
*파일은 기존 text 불러오는것과는 별도로 구성을 해주어야 함
-> 서버주소 정하기 : post = 저장, put = 수정, delete =삭제 등등
-> 파일업로드 할때: enctype="multipart/form-data" : default 파일 업로드 크기 제한은 1mb
-> name 을 넣어 데이터랑 맵핑 해주기
- 중요 태그
<form action="/goods" method="post" enctype="multipart/form-data">
<input type="file" name="img" accept="image/*" multiple="multiple"> // multiple 은 사진 여러개 올리는 것
<!DOCTYPE html>
<html xmlns:th="//www.thymeleaf.org"
th:replace="layout/layout1 :: layout(~{this::head},~{this::main})" >
<!-- 호출 th:replace 또는 th:insert |여기 테플릿 head와 main 에 넣을게요-->
<head>
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#summernote').summernote();
});
</script>
</head>
<main>
<h1>메인영역</h1>
<div class="wrap view-size">
<section id="goods-list">
<h1>상품목록 페이지</h1>
<div class="wrap">
<p class="tit"> 상품목록 페이지 </p>
<!-- 파일 업로드 처리시 enctype="multipart/form-data -->
<form action="/goods" method="post" enctype="multipart/form-data"> <!-- 서버주소 정하기 -->
<p><input type="text" name="name" placeholder="상품명"></p>
<p><input type="text" name="price" placeholder="가격"> </p>
<!-- 이미지는 file 로 type, accept 는 형식 -->
<p>
<label>파일은 2MB 이내의 이미지 파일만 허용합니다. </label>
<input type="file" name="img" accept="image/*" ><!-- multiple="multiple" -->
</p>
<p><textarea id="summernote" name="content"></textarea></p>
<p><button type="submit">등록</button>
</form>
</div>
</section>
</div>
</main>
</html>
6. 데이터 맵핑
- controller save 맵핑
파일 - 맵핑 MultipartFile 이름
- multiple="multiple" 할 시 맵핑은 배열로 표기해야함
- name MultipartFile 객체 변수 이름과 일치하도록 설정 img
- private final 로 서비스 인터페이스 객체 생성 후 final 이니까 @RequiredArgsConstructor
- 저장할 메서드 만들기
package com.green.nowon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
import com.green.nowon.service.GoodsService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class GoodsController {
//Autowired
private final GoodsService service;
@GetMapping("/goods")
public String list() {
return "goods/list" ;
}
@GetMapping("/goods/new")
public String write() {
return "goods/write" ;
}
@PostMapping("/goods")
public String save(MultipartFile img, GoodsSaveDTO dto) { //MultipartFile 이름 //dto 에 나머지 맵핑하기
/* 여러개 사진 올릴 시 배열로 처리해야함: (MultipartFile img[], GoodsSaveDTO dto)*/
service.saveProcess(img,dto);
return "goods/write" ;
}
}
-> 나머지 정보 - 맵핑할 클래스 생성하기
package com.green.nowon.domain.dto;
import com.green.nowon.domain.entry.GoodsEntity;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
public class GoodsSaveDTO {
private String name;
private int price;
private String content;
//편의 메서드를 만들게요
public GoodsEntity toEntity() {
//builder.빌더클래스(필드값(위에랑 똑같이)).빌더클래스(필더값)...
return GoodsEntity.builder() //innerClass인 GoodsEntityBuilder 객체 생성
.name(name).price(price).content(content)// set name, price, content 한 GoodsEntityBuilder 객체
.build(); //
//= return new GoodsEntity()l;
}
}
- 용량제한 풀어주기
: application.yml
webflux:
multipart:
max-file-size: 2MB #default 1MB
7. 서비스 만들기 - interface 로 !
- controller 에서 적은대로 GoodsService.Interface 생성하기
package com.green.nowon.service;
public interface GoodsService {
}
- Interface 상속받을 클래스 만들기
- > 상속받은 클래스에 @Service
package com.green.nowon.service.impl;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import com.green.nowon.service.GoodsService;
@Service
public class GoodsServiceProcess implements GoodsService {
}
- 상품 정보저장할 method 생성 : service.saveProcess(img,dto);
package com.green.nowon.service;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
public interface GoodsService {
void saveProcess(MultipartFile img, GoodsSaveDTO dto);
}
- 클래스에서 Override 하기
- mapper.save(dto.toEntity()); 저장 메서드 만들기
package com.green.nowon.service.impl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
import com.green.nowon.mybatis.mapper.GoodsMapper;
import com.green.nowon.service.GoodsService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class GoodsServiceProcess implements GoodsService {
//DB 에 접근할 수 있는 DAO : mapper, repository -> interface
private final GoodsMapper mapper; //DI 종속객체를 주입한다(외부에서 주입): 내부 코드에서 결정 x (IoC)
@Override
public void saveProcess(MultipartFile img, GoodsSaveDTO dto) {
//상품정보를 DB - goods 테이블에 저장
mapper.save(dto.toEntity());
//파일정보 저장 DB- goods-file 테이블에 저장
//서버에 업로드
}
}
- mapper Interface 도 생성
package com.green.nowon.mybatis.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.green.nowon.domain.entry.GoodsEntity;
@Mapper
public interface GoodsMapper {
void save(GoodsEntity entity);
}
8. xml 만들기
- goods 테이블의 변수로 값 셋팅 하기
- goodsSaveDTO 와 동일하게 필드값 넣기
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.green.nowon.mybatis.mapper.GoodsMapper">
<!-- 추상메서드에 있는 이름과id 가 일치해야함 -->
<insert id="save">
insert into goods(name, price, content)
values(#{name}, #{price}, #{content})
</insert>
</mapper>
9. Goods-file 도 mapper 하기
- private final GoodsFileMapper fileMapper 와 같이 인터페이스 생성
- 저장 메서드 작성하기 : 상품정보 저장( goods) -> 서버에 업로드 -> 파일 정보 저장 (goods-file)
package com.green.nowon.service.impl;
import java.io.File;
import java.io.IOException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
import com.green.nowon.domain.entry.GoodsFileEntity;
import com.green.nowon.mybatis.mapper.GoodsFileMapper;
import com.green.nowon.mybatis.mapper.GoodsMapper;
import com.green.nowon.service.GoodsService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class GoodsServiceProcess implements GoodsService {
//DB 에 접근할 수 있는 DAO : mapper, repository -> interface
private final GoodsMapper mapper; //DI 종속객체를 주입한다(외부에서 주입): 내부 코드에서 결정 x (IoC)
private final GoodsFileMapper fileMapper;
@Override
public void saveProcess(MultipartFile img, GoodsSaveDTO dto) throws Exception, IOException {
////1. 상품정보 저장 : 상품정보를 DB - goods 테이블에 저장
//mapper.save(dto.toEntity());
// 2. 서버에 업로드
//파일정보 저장 DB- goods-file 테이블에 저장
long size=img.getSize();
String url="/images/upload/goods/"; //http 경로
String name=img.getOriginalFilename();
String fileFolder="E:/ncs2023/spring/spring-config-client/src/main/resources/static/images/upload/goods/"; //업로드되는 서버의 주소
File dest= new File(fileFolder+name);
img.transferTo(dest);
System.out.println(">>>> : 파일 업로드 완료!");
// 3. 파일 정보 저장 DB goods_file 테이블에 저장
fileMapper.save(GoodsFileEntity.builder()
.size(size).url(url).name(name)
.build());
}
}
- 인터페이스
package com.green.nowon.mybatis.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.green.nowon.domain.entry.GoodsFileEntity;
@Mapper
public interface GoodsFileMapper {
void save(GoodsFileEntity build);
}
여기까지 하면, forengn key로 인한 오류가 남
10. foreign key 설정하기
따라서 설정한 forengn key 는 bno 로, 조회 후 값을 넣도록 설정해야함
- goods-mapper 로 가서 아래와 같이 수정
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.green.nowon.mybatis.mapper.GoodsMapper">
<!-- 추상메서드에 있는 이름과id 가 일치해야함 -->
<insert id="save">
<!-- 아래에 있는 insert 쿼리르 실행 후 auto_increment 로 처리되는 pk 값을 조회하여 gno에 결과로 맵핑 -->
<selectKey keyProperty="gno" resultType="long" order="AFTER"> <!-- 아래 insert 후에 실행 -->
select LAST_INSERT_ID()
</selectKey>
insert into goods(name, price, content)
values(#{name}, #{price}, #{content})
</insert>
</mapper>
-serviceprocess 에 파일 정보 쿼리에 gno(goods.getGno()) 매핑
package com.green.nowon.service.impl;
import java.io.File;
import java.io.IOException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
import com.green.nowon.domain.entry.GoodsEntity;
import com.green.nowon.domain.entry.GoodsFileEntity;
import com.green.nowon.mybatis.mapper.GoodsFileMapper;
import com.green.nowon.mybatis.mapper.GoodsMapper;
import com.green.nowon.service.GoodsService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class GoodsServiceProcess implements GoodsService {
//DB 에 접근할 수 있는 DAO : mapper, repository -> interface
private final GoodsMapper mapper; //DI 종속객체를 주입한다(외부에서 주입): 내부 코드에서 결정 x (IoC)
private final GoodsFileMapper fileMapper;
@Override
public void saveProcess(MultipartFile img, GoodsSaveDTO dto) throws Exception, IOException {
////1. 상품정보 저장 : 상품정보를 DB - goods 테이블에 저장
GoodsEntity goods=dto.toEntity();
System.out.println(" insert 실행 전 "+goods);
mapper.save(goods);
System.out.println(" insert 실행 후 "+goods); // pk 컬럼값이 매핑된 결과를 볼 수 있다.
// 2. 서버에 업로드
//파일정보 저장 DB- goods-file 테이블에 저장
long size=img.getSize();
String url="/images/upload/goods/"; //http 경로
String name=img.getOriginalFilename();
String fileFolder="E:/ncs2023/spring/spring-config-client/src/main/resources/static/images/upload/goods/"; //업로드되는 서버의 주소
File dest= new File(fileFolder+name);
img.transferTo(dest);
System.out.println(">>>> : 파일 업로드 완료!");
// 3. 파일 정보 저장 DB goods_file 테이블에 저장
fileMapper.save(GoodsFileEntity.builder()
.size(size).url(url).name(name).gno(goods.getGno())
.build());
}
}
- goods_files 에서 gno 잘 들어갔는지 확인
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.green.nowon.mybatis.mapper.GoodsFileMapper">
<insert id="save">
insert into goods_file(size, url, name, gno)
values(#{size}, #{url}, #{name}, #{gno})
</insert>
</mapper>
>>>>>>>>>> 완료 !
파일 업로드해서 데이터 잘 들어가는지 확인하기 !
----> 사진은 업로드 후 프로젝트 refresh 후 http://localhost:8080/images/upload/goods/파일명.파일형식으로 확인
파일 저장 다른방법 1
서버에 올리기
: 단. 3가지 서비스(DB올리기, 서버올리기, 저장하기) 가 있어서 효율이 좋지 않음
package com.green.nowon.service.impl;
import java.io.File;
import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.green.nowon.domain.dto.GoodsSaveDTO;
import com.green.nowon.domain.entry.GoodsEntity;
import com.green.nowon.domain.entry.GoodsFileEntity;
import com.green.nowon.mybatis.mapper.GoodsFileMapper;
import com.green.nowon.mybatis.mapper.GoodsMapper;
import com.green.nowon.service.GoodsService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class GoodsServiceProcess implements GoodsService {
//DB 에 접근할 수 있는 DAO : mapper, repository -> interface
private final GoodsMapper mapper; //DI 종속객체를 주입한다(외부에서 주입): 내부 코드에서 결정 x (IoC)
private final GoodsFileMapper fileMapper;
@Override
public void saveProcess(MultipartFile img, GoodsSaveDTO dto) throws Exception, IOException {
////1. 상품정보 저장 : 상품정보를 DB - goods 테이블에 저장
GoodsEntity goods=dto.toEntity();
System.out.println(" insert 실행 전 "+goods);
mapper.save(goods);
System.out.println(" insert 실행 후 "+goods); // pk 컬럼값이 매핑된 결과를 볼 수 있다.
// 2. 서버에 업로드
//파일정보 저장 DB- goods-file 테이블에 저장
long size=img.getSize();
String url="/images/upload/goods/"; //http 경로
String name=img.getOriginalFilename();
//String fileFolder="E:/ncs2023/spring/spring-config-client/src/main/resources/static/images/upload/goods/"; //업로드되는 서버의 주소
//File dest= new File(fileFolder+name);
ClassPathResource cpr = new ClassPathResource("/static"+url);
System.out.println("업로드 폴더정보: "+ cpr.getPath().toString());
File dest=new File(cpr.getFile(), name);
img.transferTo(dest);
System.out.println(">>>> : 파일 업로드 완료!");
// 3. 파일 정보 저장 DB goods_file 테이블에 저장
fileMapper.save(GoodsFileEntity.builder()
.size(size).url(url).name(name).gno(goods.getGno())
.build());
}
}
'Spring' 카테고리의 다른 글
[springBoot] security 적용하기 1 - 회원가입 (0) | 2023.05.22 |
---|---|
[SpringBoot] JSP 구조(기초 구조 생성하기) (0) | 2023.05.16 |
[Spring] 데이터 소스를 통한 프로젝트 만들기 (0) | 2023.05.04 |
[Spring] MyBatis Query 연동, 구축, 맵핑, 쿼리날리기 (0) | 2023.04.25 |
[Spring] DB -스프링 웹 MVC 개념 (0) | 2023.04.24 |