spring framework 4.X
file I/O - upload & download
dependency 추가
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
File Resolver 등록
servlet-context.xml
property 필수사항은 maxUploadSize 하나뿐 value는 바이트 기준 직접 계산해서 넣어줘야함
<!-- File Resolver -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="5242880"/>
<beans:property name="defaultEncoding" value="utf-8"/>
</beans:bean>
Upload
form 태그 작성
<form action="upload" method="post" enctype="multipart/form-data">
<div>
<input type="text" name="msg"/>
</div>
<div>
<input type="file" name="file1"/>
</div>
<div>
<button>업로드</button>
</div>
</form>
Controller
// path는 원래 OS기준으로 특정한 장소를 결정하여 저장을 해야한다!
String path = "E:\\webspace\\sts08\\up\\";
@ResponseBody
@PostMapping("/upload")
public void upload(MultipartFile file1, HttpServletRequest req) {
// 파일명의 중복을 막기 위해 시간 추가
String msg = System.currentTimeMillis() +"_";
File f = new File(path + msg + file1.getOriginalFilename());
// try(
// InputStream is = file1.getInputStream();
// OutputStream os = new FileOutputStream(f);
// ) {
// int su = -1;
// while((su=is.read())!=-1) {
// os.write(su);
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// 위와 같은 IO 작업을 Spring에서 한줄로 처리 할 수 있다.
file1.transferTo(f);
}
Download
View 작성
<h1>download</h1>
<p> file1: <a href="load?fname=${fname }">download</a></p>
<p> file2: <a href="load/${fname }">download</a></p>
Controller
@GetMapping("/down")
public void down(Model model, String fname) {
model.addAttribute("fname", fname);
}
// RequestParam을 이용한 다운로드
@GetMapping("/load")
public void load(String fname, HttpServletResponse resp) throws FileNotFoundException, IOException {
File f = new File(path+fname);
// 파일을 무조건 다운로드 되도록
resp.setContentType("application/octet-stream");
// 다운로드 되는 파일명 지정 (앞에 '_' 기준으로 시간이 달려있기 때문에 분리)
resp.setHeader("Content-Disposition", "attachment; filename="+fname.split("_")[1]);
try(InputStream is = new FileInputStream(f);
OutputStream os = resp.getOutputStream();){
int su = -1;
while((su=is.read())!= -1) {
os.write(su);
}
}
}
// PathVariable을 이용한 다운로드
// PathVariable에서 확장자까지 가져올려고하면 아래처럼 작성해야한다.
@GetMapping("/load/{fname:.+}")
public void load2(@PathVariable String fname, HttpServletResponse resp) throws FileNotFoundException, IOException {
File f = new File(path+fname);
// 파일을 무조건 다운로드 되도록
resp.setContentType("application/octet-stream");
// 다운로드 되는 파일명 지정 (앞에 '_' 기준으로 시간이 달려있기 때문에 분리)
resp.setHeader("Content-Disposition", "attachment; filename="+fname.split("_")[1]);
try(InputStream is = new FileInputStream(f);
OutputStream os = resp.getOutputStream();){
int su = -1;
while((su=is.read())!= -1) {
os.write(su);
}
}
}
Interceptor
DispatcherServlet을 기준으로 뭔가 처리를 하고 싶을 땐 AOP 보단 Interceptor가 보다 성능도 효율도 좋다.
<!-- ineterceptor -->
<interceptors>
<interceptor>
<mapping path="/*"/>
<!-- <mapping path="/dept/*"/> -->
<beans:bean class="com.bit.sts08.utils.LoggerInterceptor"></beans:bean>
</interceptor>
</interceptors>
public class LoggerInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("Controller 호출 전");
// 리턴값이 있는데 이를 통해서 Controller 가 호출 될지 안될지 선택할 수 있다.
// request 객체도 받아오기 때문에 session을 얻어와서 로그인 유무를 파악한다던지 할 수 있다.
// if("".equals("")) {
// request.getRequestDispatcher("/WEB-INF/views/down.jsp").forward(request, response);
// return false;
// }
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("Controller 호출 후.. " + modelAndView.getViewName());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("View Resolving 후.. " + ex.toString());
// 오류가 발생할 경우 특정 페이지로 이동시킬 수 있다.
// if(ex!=null) {
// request.getRequestDispatcher("/WEB-INF/views/down.jsp").forward(request, response);
// }
}
}
Spring framework 5.X
java, Spring, maven-plugin, servlet, jsp 버전 수정
의존성 추가
spring-jdbc, spring-tx, commons-dbcp, mysql, mybatis, mybatis-spring
web.xml을 지우고 pom.xml에 플러그인 업데이트
설정 후 mvn package를 이용해서 패키징이 된다면 성공적으로 된 것
<build>
...
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>flase</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
xml을 최대한 없앤 자바의 코드만으로 프로젝트를 작성하도록 설정
WebConfig - web.xml의 대신
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
// <context-param>
// <param-name>contextConfigLocation</param-name>
// <param-value>classpath:/applicationContext.xml</param-value>
// </context-param>
return new Class[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
// DispatcherServlet를 서블릿으로 지정해주는 작업
// <servlet>
// <servlet-name>appServlet</servlet-name>
// <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
// </servlet>
return new Class[] {ServletConfig.class};
}
@Override
protected String[] getServletMappings() {
// 서블릿 매핑을 전체로, 즉 모든 요청을 하나로 모으는 작업
// <servlet-mapping>
// <servlet-name>appServlet</servlet-name>
// <url-pattern>/</url-pattern>
// </servlet-mapping>
return new String[] {"/"};
}
}
ServletConfig - DispatcherServlet 설정 servlet-context.xml 대신함
// <annotation-driven/>
@EnableWebMvc
// <context:component-scan base-packpage="com.bit.sts09"/>
@ComponentScan("com.bit.sts09")
public class ServletConfig implements WebMvcConfigurer{
// <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
// <beans:property name="prefix" value="/WEB-INF/views/" />
// <beans:property name="suffix" value=".jsp" />
// </beans:bean>
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// InternalResourceViewResolver resolver = new InternalResourceViewResolver();
// resolver.setPrefix("/WEB-INF/views");
// resolver.setSuffix(".jsp");
// registry.viewResolver(resolver);
registry.viewResolver(new InternalResourceViewResolver("/WEB-INF/views/", ".jsp"));
}
// <resources mapping="/resources/**" location="/resources/" />
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
RootConfig - applicationContext.xml을 대신함
import com.mysql.cj.jdbc.Driver;
@Configuration
@MapperScan("com.bit.sts09.model")
public class RootConfig {
// <!-- DBCP -->
// <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
// <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
// <property name="url" value="jdbc:mysql://192.168.99.100:3306/lecture"/>
// <property name="username" value="scott"/>
// <property name="password" value="tiger"/>
// </bean>
@Bean
public DataSource getDataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(Driver.class);
dataSource.setUrl("jdbc:mysql://192.168.99.100:3306/lecture");
dataSource.setUsername("scott");
dataSource.setPassword("tiger");
return dataSource;
}
// <!-- Mybatis -->
// <bean
// id="sqlSessionFactory"
// class="org.mybatis.spring.SqlSessionFactoryBean"
// p:configLocation="classpath:/mybatis-config.xml"
// p:dataSource-ref="dataSource"/>
@Bean
public SqlSessionFactory getSqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(getDataSource());
// Resource resource = new ClassPathResource("/mybatis-config.xml");
// sqlSessionFactory.setConfiguration(resource);
// Resource[] resources = {new ClassPathResource("/mapper/dept-mapper.xml")};
// sqlSessionFactory.setMapperLocations(resources);
// 위에 주석된 것들 대신에 현재클래스에 @MapperScan 어노테이션 장착 + Dao 클래스에 @Mapper 장착
return sqlSessionFactory.getObject();
}
// <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
// <constructor-arg ref="sqlSessionFactory"></constructor-arg>
// </bean>
@Bean
public SqlSession getSqlSession() throws Exception {
return new SqlSessionTemplate(getSqlSessionFactory());
}
}
Spring Boot 2
boot 1 버전은 현재 종료됨, 2, 3 버전만 있으며 각각 spring 4, 5 버전을 사용함
Spring 6 - Boot 3
=> JAVA 17 기반, 사용되는 엔진 변경, 성능 향상 등
사용자 입장에서는 크게 메리트가 있는점은 없다고한다...
Boot Project 생성
사용할 라이브러리 및 Spring 버전 프로젝트 생성시 지정 가능
devtools - 코드를 수정하면 자동으로 갱신해줌
터미널에서 실행하는 방법
배포 시 (jar만 옮겨서 jar만 실행시키는 구문을 쓰는게 제일 좋다)
mvn package
java -jar E:\webspace\boot01\target\boot01-0.0.1-SNAPSHOT.jar
개발 시
mvn spring-boot:run
maven을 설치하지 않았을 경우
mvn 대신 mvnw 를 이용해서 사용하면되기 때문에 배포시에 jdk만 깔아도되는 것
resources/
application.properties - port번호와 같은 환경설정을 바꿀 때 자동완성도 가능
server.port=8282
/static/
해당 경로에 정적파일들을 넣으면 추가 경로 없이 파일명 만으로 불러올 수 있다.
thymeleaf, lombok, mybatis, mysql을 이용한 웹프로젝트
application.properties - mysql
mysql 라이브러리를 추가했기 때문에 DataSource를 정의하지 않으면 서버가 시작도 되지않는다.
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.99.100/lecture
spring.datasource.username=scott
spring.datasource.password=tiger
template - thymeleaf
thymeleaf 설정은 Boot에 라이브러리를 추가해줌으로써 전부 다 해주었기 때문에 template에 html파일만 추가하면 된다.
백엔드 개발자와 프론트엔드 개발자 처리방식이 일치 하지않는 문제를 원천적으로 해결하기 위한 템플릿이다.
프론트엔드에서 작업한 것을 그대로 가져와서 백엔드에서 처리할 때는 태그에 속성을 추가해줌으로써 처리할 수 있기 때문에
서로간에 작업 간에 영향을 주는 것이 없어지게 되는 장점이 있게 된다.
<h1>template view index</h1>
<table>
<thead>
<tr>
<th>deptno</th>
<th>dname</th>
<th>loc</th>
</tr>
</thead>
<tbody>
<tr th:each="bean:${list}">
<td th:text="${bean.deptno}">1234</td>
<td th:text="${bean.dname}">test</td>
<td th:text="${bean.loc}">test</td>
</tr>
</tbody>
</table>
Mybatis 연결
@MapperScan, Mysql 연결
@SpringBootApplication
@MapperScan("com.bit.boot02.model")
public class Boot02Application {
@Autowired
DataSource dataSource;
@Bean
public SqlSessionFactory getSqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
public static void main(String[] args) {
SpringApplication.run(Boot02Application.class, args);
}
}
Vo - lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptVo {
private int deptno;
private String dname, loc;
}
Dao
@Mapper 달아주고 @Select 로 Query Mapping
@Mapper
public interface DeptDao {
@Select("select * from dept")
List<DeptVo> findAll();
@Select("select * from dept where deptno=#{pk}")
DeptVo findOne(int pk);
@Insert("insert into dept value(#{deptno}, #{dname}, #{loc})")
void insertOne(DeptVo bean);
@Update("update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}")
int updateOne(DeptVo bean);
@Delete("delete from dept where deptno=#{pk}")
int deleteOne(int pk);
}
Service
@Service
@AllArgsConstructor
public class DeptService {
private final SqlSession sqlSession;
public List<DeptVo> selectAll() {
return sqlSession.getMapper(DeptMapper.class).findAll();
}
public DeptVo selectOne(int deptno) {
return sqlSession.getMapper(DeptMapper.class).findOne(deptno);
}
@Transactional
public void insertOne(DeptVo bean) {
sqlSession.getMapper(DeptMapper.class).insertOne(bean);
}
@Transactional
public int updateOne(DeptVo bean) {
return sqlSession.getMapper(DeptMapper.class).updateOne(bean);
}
@Transactional
public int deleteOne(int deptno) {
return sqlSession.getMapper(DeptMapper.class).deleteOne(deptno);
}
}
Controller
@Controller
public class MainController {
private final DeptService deptService;
@GetMapping
@CrossOrigin
public List<?> list() {
return deptService.selectAll();
}
@CrossOrigin
@PostMapping
public ResponseEntity<?> add(@ModelAttribute("bean") DeptVo bean) {
try {
deptService.insertOne(bean);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok("success");
}
@CrossOrigin
@GetMapping("/{deptno}")
// public DeptVo detail(@PathVariable("deptno") int deptno) {
// return deptService.selectOne(deptno);
public ResponseEntity<?> detail(@PathVariable("deptno") int deptno) {
DeptVo bean = deptService.selectOne(deptno);
if(bean!=null) {
return ResponseEntity.ok(bean);
} else {
return ResponseEntity.notFound().build();
}
}
@CrossOrigin
@PutMapping("/{deptno}")
public ResponseEntity<?> edit(@RequestBody DeptVo bean, HttpServletRequest req) throws URISyntaxException {
if(deptService.updateOne(bean) > 0 ) {
RestTemplate template = new RestTemplate();
URI url = new URI(req.getRequestURL().toString());
RequestEntity param = new RequestEntity(HttpMethod.GET, url);
return template.exchange(url, HttpMethod.GET, param, DeptVo.class);
// return ResponseEntity.ok(bean);
}
return ResponseEntity.internalServerError().build();
}
@CrossOrigin
@DeleteMapping("/{deptno}")
public ResponseEntity<?> del(@PathVariable("deptno") int deptno) {
if(deptService.deleteOne(deptno) > 0) {
return ResponseEntity.ok("success");
}
return ResponseEntity.badRequest().build();
}
}