File Upload
- 파일명과 경로를 문자열 형태로 넘겨주어 저장 및 출력할 경우, 해당 파일을 갖고 있지 않은 단말기에서 코드를 실행 시 파일 추적 불가
- 가상 경로를 생성하여 외장 서버 저장소에 저장해야 서버 사용자 모두가 파일 사용 가능
- 이를 위해 별도의 작업 필요
Upload Single File
- <form>을 통해 값을 전달 시, 단순한 입력 값과 달리 파일을 업로드하기 위해서는 선행 조건이 필요
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
- mvnrepository.com에서 commons-io와 commons-fileupload를 위한 <dependency>를 복사
- pom.xml 파일의 <dependencies> 내에 추가
<body>
<form action="upload1" method="post" enctype="multipart/form-data">
<table>
<caption align="top"><b>스프링 업로드(파일1개)</b></caption>
<tr>
<th>파일업로드</th>
<td><input type="file" name="photo" class="form-control"></td>
</tr>
<tr>
<td colspan="2" align="center">
<button type="submit">업로드</button>
</td>
</tr>
</table>
</form>
</body>
- 단순 입력 값만 전달할 때와 달리 반드시 ‘enctype=”multipart/form-data”’ 추가
@Controller
@RequestMapping("/test")
public class Controller {
@PostMapping("/upload1")
public ModelAndView read1(
@RequestParam MultipartFile photo,
HttpSession session) {
ModelAndView model=new ModelAndView();
model.setViewName("upload/uploadResult1");
String path=session.getServletContext().getRealPath("/WEB-INF/image");
String fileName=photo.getOriginalFilename();
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
if(!fileName.equals("")) {
fileName=sdf.format(new Date())+"_"+fileName;
try {
photo.transferTo(new File(path+"\\"+fileName));
}
catch (IllegalStateException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
} else {fileName="no";}
model.addObject("fileName", fileName);
model.addObject("title", title);
model.addObject("path", path);
return model;
}
}
- 매핑 메서드의 인자로 반드시 필요한 것은 다음과 같다
- multipart/form-data로 업로드한 파일의 정보를 포함하는 MultipartFile 객체 (변수 photo)
- 실제 저장되는 경로(realPath)의 정보를 포함하는 HttpServletRequest 혹은 HttpSession 객체 (HttpServletRequest 객체의 getSession() 메서드를 통해 HttpSession 객체를 호출할 수 있으므로 임의로 사용 가능)
- 실제로 업로드하여 저장되는 경로는 다음의 방법으로 구할 수 있다
- session.getServletContext().getRealPath("/WEB-INF/image");
- getRealPath() 메서드의 인자 값으로는 업로드한 파일 저장 용도의 가상 경로를 만들기 위한 실제 파일의 절대 경로를 입력
- 실제로 업로드한 파일이 가지는 파일명은 다음의 방법으로 구할 수 있다
- MultipartFile 객체(photo)의 getOriginalFilename() 메서드는 실제 저장되는 파일명을 반환
- 그러나 JSP에서 ‘DefaultFileRenamePolicy()’와 같이 하나의 파일이 중복 저장될 경우 이름을 달리 하여 다중 저장되는 기능은 없음
- 중복 저장이 불가한 문제를 해결하기 위해 모든 저장되는 파일명에 업로드 시간을 표기하여 중복 방지
- SimpleDateFormat 객체를 이용하여 시, 분, 초까지 기록
- ‘fileName=sdf.format(new Date())+"_"+fileName’와 같이 업로드 시간을 파일명에 삽입
- 실제 저장될 경로에 업로드하는 방법은 다음과 같다
- MultipartFile 객체(photo)의 transferTo(new File()) 메서드가 파일을 실질적으로 업로드하는 기능
- transferTo() 객체의 인자인 new File() 메서드의 인자 값으로는 업로드한 파일까지의 저장 경로(저장 경로 + 파일명) 입력
- 업로드한 파일이 없을 경우의 예외 처리 (try ~ catch)
<body>
<h2>업로드한 실제경로: ${path }</h2>
<h2>업로드한 이미지명: ${fileName }</h2>
<c:if test="${fileName=='no' }">
<img src="../photo/noimage.png" style="max-width: 100px">
</c:if>
<c:if test="${fileName!='no' }">
<img src="../photo/${fileName }" style="max-width: 100px">
</c:if>
</body>
- 저장된 파일을 찾기 위해서는 실제 저장 경로인 ${path}의 출력 값을 통해야 함
- 업로드한 파일이 없을 경우를 위한 조건문 작성
Upload Multiple Files
<body>
<form action="upload2" method="post" enctype="multipart/form-data">
<table>
<caption align="top"><b>스프링 업로드(파일여러개)</b></caption>
<tr>
<th>업로드</th>
<td><input type="file" name="photo" class="form-control" multiple="multiple"></td>
</tr>
<tr>
<td colspan="2" align="center">
<button type="submit">업로드</button>
</td>
</tr>
</table>
</form>
</body>
- 단일 파일 업로드 시와 달라진 점은 <input type=”file”>에 ‘multiple=”multiple”’ 속성이 추가
@Controller
@RequestMapping("/test")
public class Controller {
@PostMapping("/upload2")
public ModelAndView read2(@RequestParam String title,
@RequestParam ArrayList<MultipartFile> photo,
HttpSession session) {
ModelAndView model=new ModelAndView();
model.setViewName("upload/uploadResult2");
String path=session.getServletContext().getRealPath("/WEB-INF/image");
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
ArrayList<String> fileNames=new ArrayList<String>();
for(MultipartFile f:photo) {
String fileName=sdf.format(new Date())+"_"+f.getOriginalFilename();
fileNames.add(fileName);
try {
f.transferTo(new File(path+"\\"+fileName));
}
catch (IllegalStateException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
}
model.addObject("fileNames", fileNames);
model.addObject("title", title);
model.addObject("path", path);
return model;
}
}
- 단일 파일 업로드 시와 Controller도 유사
- 하지만 여러 파일이 업로드 되었으므로 다음의 작업을 반복문을 통해 반복해야 함
- transferTo(new File()) 객체를 통한 실제 파일 업로드
- 필요에 따라 List<String> 객체에, 출력을 위한 실제 저장 경로 및 파일명 저장
<body>
<h2>업로드한 실제경로: ${path }</h2>
<h2>업로드한 이미지들</h2>
<c:forEach var="imgs" items="${fileNames }">
<img src="../photo/${imgs }">
</c:forEach>
</body>
- 업로드한 이미지가 여러 개이므로 반복문 <c:forEach>를 이용하여 모두 출력 가능
JDBC_Connect to DataBases
Configurations
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
</dependencies>
- mvnrepository.com에서 mysql-connector-java, mybatis, mybatis-spring, spring-jdbc를 위한 <dependency>를 복사
- pom.xml 파일의 <dependencies> 내에 추가
JDBC
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="사용자 드라이버 클래스명"></property>
<property name="url" value="사용자 db url"></property>
<property name="username" value="사용자 아이디"></property>
<property name="password" value="사용자 비밀번호"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- class가 DriverManagerDataSource인 <bean> 생성 후 4개의 각 <property>에 해당하는 value 지정
- #sqlSessionFactory <bean>에 입력
- <property name="configLocation" value="classpath:mybatis-config.xml"/> (src/main/resources>mybatis-config.xml : 멤버 변수의 자료형 지정을 위한 mybatis 설정 파일)
- <property name="mapperLocations" value="classpath:mapper/Mapper.xml"/> (src/main/resources>mapper>파일들 : DAO에서 사용할 SQL문 지정을 위한 mapper 설정 파일, 와일드카드()를 통해 여러 파일을 동시에 지정 가능)
- #sqlSession <bean>은 DAO(@Repository 등록)와 SQL Mapper를 연결하는 기능
MyBatis
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="mdto" type="spring.mysql.mycar.MyCarDto"/>
</typeAliases>
</configuration>
- doctype을 위와 같이 작성
- <configuration>의 <typeAliases>태그를 통해 자료형이 될 객체(멤버 변수 포함한 객체)의 별칭(alias)를 지정 가능 → 이는 Mapper에서 활용
Mapper
<?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="spring.mysql.mycar.MyCarDao">
<select id="getAllListMyCar" resultType="mdto">
select * from mycar order by num desc
</select>
<select id="getTotalCount" resultType="int">
select count(*) from mycar
</select>
<insert id="insertOfMyCar" parameterType="mdto">
insert into mycar (carname,carprice,carcolor,carguip) values(#{carname},#{carprice},#{carcolor},#{carguip})
</insert>
<delete id="deleteOfMyCar" parameterType="String">
delete from mycar where num=#{num}
</delete>
<update id="updateOfMyCar" parameterType="mdto">
update mycar set carname=#{carname},carprice=#{carprice},carcolor=#{carcolor},carguip=#{carguip} where num=#{num}
</update>
</mapper>
- doctype을 위와 같이 작성
- <mapper>의 namespace는 DAO에서 사용할 메서드의 범위 탐색 시 활용할 변수명 입력
- 기능에 따라 <select>, <insert> 등의 태그 사용
- id 속성은 DAO에서 사용할 작업을 호출하기 위한 이름 (java의 메서드 기능)
- resultType 속성은 SQL문의 결과로 발생한 값들의 자료형(혹은 멤버 변수를 포함한 객체)
- parameterType 속성은 SQL문에 대입할 인자 값의 자료형(혹은 멤버 변수를 포함한 객체)
- 입력된 인자 값은 ‘#{ 변수명 }’의 방식으로 활용
DTO & DAO
public class MyCarDto {
@Repository
public class MyCarDao {
@Autowired
private SqlSession session;
String namespace="spring.mysql.mycar.MyCarDao";
public int getTotalCount() {
return session.selectOne(namespace+".getTotalCount");
}
public void insertCar(MyCarDto dto) {
session.insert("insertOfMyCar", dto);
}
public List<MyCarDto> getAllCars() {
return session.selectList("getAllListMyCar");
}
public void deleteCar(String num) {
session.delete("deleteOfMyCar", num);
}
}
- DAO 클래스는 @Repository 어노테이션으로 Beans에 등록
- DAO를 SQL문을 정의한 Mapper와 연결하기 위해 SqlSession 객체 생성 후 이를 @Autowired
- SqlSession을 통해 SQL문에 접근하기 위한 방법은 다음과 같다
- 기능에 따라 SqlSession 객체(session)의 select*(), insert() 등의 메서드 호출
- select*() 메서드는 단일 객체 반환 시 selectOne(), 복수 객체 반환 시 selectList() 사용
- 메서드에 입력된 인자 값은 SqlSession 객체의 인자 값으로 그대로 입력하여 SQL에 전달
- Mapper의 해당 태그를 찾기 위해 정확한 id 값 입력 (중복 id가 있다면 namespace 속성을 지정하여 범위 지정 가능 : “mycar.getData” 혹은 namespace+”.getData”)
Controller
@Controller
public class CarController {
@Autowired
MyCarDao dao;
@GetMapping("/kakao/list")
public String list(Model model) {
int totalCount=dao.getTotalCount();
List<MyCarDto> list=dao.getAllCars();
model.addAttribute("totalCount", totalCount);
model.addAttribute("list", list);
return "car/carList";
}
@PostMapping("/kakao/read")
public String carinsert(@ModelAttribute("dto") MyCarDto dto) {
dao.insertCar(dto);
return "redirect:list";
}
}
- DAO(@Repository)와 @Autowired로 연결하여 멤버 객체 호출
- 매핑을 통해 가상 경로 지정 및 값 전달
- 호출한 DAO 객체를 통해 필요한 메서드 사용 가능
- select의 경우 반환 값을 활용
- void 타입 메서드의 경우 DAO 객체 호출하여 사용, 반환 값은 없음
- 하나의 절대 경로에는 하나의 매핑 주소만 지정 가능 (중복 시 redirect 이용)
- redirect는 일반적으로 매핑 주소의 중간 경로를 제외한 마지막 경로만 지정
View