728x90
300x250
[Spring Framework] 41. Spring 방식의 파일 업로드, 다운로드(Commons-io, FileUpload) - (XML, Java)


업로드에 관한 내용이다.

방식이 두 가지가 있다. 셋팅에 있어서 다소 차이가 있을 수 있다.


수 차례 실험을 해본 결과로는 Java 방식을 사용하고자 했을 때는 CGLib를 추가로 넣어줘야 한다.


[참고]

해당 코드는 Spring Framework 내에서만 동작하는 방법이다.

JSP/Servlet 업로드, 다운로드 관련해서는 JSP 카테고리에서 살펴보면 되겠다.


[개발 환경]

* IDE: Eclipse 2020-06

* Framework:

  - Spring Framework 4.2.4 RELEASES

  - commons-fileupload (2.8)

  - commons-io (1.4)
  - CGLIB (3.3.0) - Java 환경 설정 방식만 해당 [의외로 사용 빈도가 있는 jar 파일이다.]

    (RootConfig.java 파일과 Beans 등 사용하면 필요할 때가 있음.)


  - Java Version 1.8 (OpenJDK 15)






1. 프로젝트 구성도


공통적으로 작성한 파일은 한번만 소개하고 변경된 사항들만 분리 요약해서 작성하도록 하겠다.

"FileController.java" / 공통

"HomeController.java" / 공통

"insert.jsp" / 공통


 


 

 그림 1. XML 파일 방식

 그림 2. Java 방식




2. Java Compiler, Build Path, Project Desc (공통)


* Java Compiler - compiler compliance level 1.8
* Build Path
               - Add Jar로 ojdbc.jar 파일 등록해주기

* Project Factes - Java : 1.8



3. pom.xml (XML, Java 방식)


 

 (중략)


    <properties>
         <java-version>1.8</java-version>
         <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
         <org.aspectj-version>1.6.10</org.aspectj-version>
         <org.slf4j-version>1.6.6</org.slf4j-version>
    </properties>

 (중략)
 공통 - pom.xml

 
  <!-- https://search.maven.org/artifact/commons-fileupload/commons-fileupload/1.4/jar -->

  <dependency>
     <groupId>commons-fileupload</groupId>
     <artifactId>commons-fileupload</artifactId>
     <version>1.4</version>
  </dependency>

  <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.8.0</version>
  </dependency>

 
  <!-- https://search.maven.org/artifact/commons-fileupload/commons-fileupload/1.4/jar -->

  <dependency>
     <groupId>commons-fileupload</groupId>
     <artifactId>commons-fileupload</artifactId>
     <version>1.4</version>
  </dependency>

  <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.8.0</version>
  </dependency>
   

  <!-- 자바 방식 - FileUpload에서는 CGLIB 넣어줘야 함 -->
  <!-- https://mvnrepository.com/artifact/cglib/cglib -->
  <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
  </dependency>
  

 XML 방식 - pom.xml

 Java 방식 - pom.xml


하나 차이 밖에 없는데, cglib 라이브러리가 있느냐 없느냐 이런 차이인데, 그냥 다 써도 무방하다.

분리해서 소개한 이유는 최소의 라이브러리로 돌아가는지 태스트 해보려고 그랬다.


디버그 창에 종종 스프링 작업을 하면, 오류로 라이브러리가 필요하다고 올라오는데 그런 예기치 못한 작업들도 생각할 수 있으면 좋겠다.




4. insert.jsp (공통)


스프링을 공부하면서 연습 템플릿이 여의치 않아서 삽질을 다소 많이 하였다.


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page session="false" %>
<html>
<head>
 <meta charset="UTF-8">
 <title>File Upload - Insert 페이지</title>
</head>
<body>
<h1>
 Hello world! 
</h1>
 
<h1>파일 업로드</h1>
<form action="fileupload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadfile" placeholder="파일 선택" /><br/>
    <input type="submit" value="업로드">
</form>

<h2>다중 파일 업로드</h2>
<form action="fileupload2" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadfiles" placeholder="파일 선택" multiple/><br/>
    <input type="submit" value="업로드">
</form>

</body>
</html>


파일명: insert.jsp


[첨부(Attachments)]

insert.zip



[비고]

5, 6번(XML, Java 설정) 방식을 하는데 있어서 두 가지를 동시에 설정하면 충돌날 가능성이 있다.

주의해서 사용할 것을 권장한다.




5. 흔히 많이 알려진 XML 설정 방식


두 가지 셋팅을 해놓으면 간단하게 끝난다.

2가지 설정을 통해서 xml 셋팅이 간단하고 좋아보일 수도 있으나 라이브러리마다 셋팅의 양이 다양하고 복잡해지는 문제도 있다.

(지금 생각할 필요는 없음.)



1. root-context.xml (/src/main/webapp/WEB-INF/spring/root-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <!-- Root Context: defines shared resources visible to all other web components -->
 <!-- MultipartResolver -->
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <property name="maxUploadSize" value="100000000"/>  <!-- 10MB-->
     <property name="maxInMemorySize" value="100000000"/> <!-- 10MB-->
  <!-- <property name="uploadTempDir" value=“0"/> -->
 </bean>
 
 
 <!-- 외부 경로를 찾는 방식은 동작하지 않음. -->
 <!--  외부 경로 찾는 방식으로 다운로드를 구현하려면, 다운로더를 하나 구현하는 것이 빠름. -->
 <!-- 다운로드 경로는 servlet-context.xml에 넣어야 함. 이중 선언이 되어서 동작 안함. -->
 <!--
 <mvc:annotation-driven />
   
    <mvc:resources mapping="/static2/**" location="file:///c:/temp/" />
 <mvc:default-servlet-handler />
 -->
 
</beans>


파일명: root-context.xml


[첨부(Attachments)]

root-context.zip




2. servlet-context.xml (/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
  http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

 <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
 
 <!-- Enables the Spring MVC @Controller programming model -->
 <annotation-driven />

 <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
 <resources mapping="/resources/**" location="/resources/" />
 
 <!-- 다운로드 외부 경로 -->
 <resources mapping="/static2/**" location="file:///c:/temp/" />


 <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
 <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>
 
 <context:component-scan base-package="com.website.example" />
 
 
</beans:beans>


파일명: servlet-context.xml


[첨부(Attachments)]

servlet-context.zip


[Resources Mapping 관련]

밑줄 친 이 코드는 다운로드 위치(자원)를 맵핑해주는 명령어이다.

servlet-context.xml에 넣어야만 동작한다.


<mvn:resource......> 이렇게 해버리면, 돌아가지 않는다.


[유의할 점 - servlet-context.xml 이외의 파일에서 사용하고자 했을 때]

코드에 오류는 안 나는데, 동작이 안되는 당황스러운 일도 있다. 

관리차원에서 파일을 하나 별도로 xml파일을 하나 생성해서, 구현을 해버리면 되어야 하는데 파일 자체에는 오류도 안 나고,

서버에 컴파일해서 올린 후 호출하면 오류가 뜨는 경우가 있다.


servlet-context.xml 파일이 수동 셋팅에서는 중요하다.




6. Java 설정 방식


RootWebConfig.java 파일 하나만 만들어놓으면, 추가 셋팅 할 필요없이 자동으로 인식해준다.

(인식해주는 이유는 "extends WebMvcConfigurerAdapter" 이 한 줄이 추가되서 그렇다.)

별도로 셋팅해줄 필요는 없다.


package com.website.example.common;

import java.io.File;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


@Configuration
@Import({FileConfig.class})
@ComponentScan(basePackages = {"com.website.example"})
@EnableWebMvc
//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})


public class RootWebConfig extends WebMvcConfigurerAdapter {

          

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
              registry.addResourceHandler("/static/**").addResourceLocations("file:c:" + File.separator + "temp/");
        }

}


파일명: RootWebConfig.java


[첨부(Attachments)]

RootWebConfig.zip



package com.website.example.common;

import java.io.File;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;


@Configuration
public class FileConfig {
 
     @Bean
     public MultipartResolver multipartResolver() {

           org.springframework.web.multipart.commons.CommonsMultipartResolver multipartResolver = new                

                  org.springframework.web.multipart.commons.CommonsMultipartResolver();
    
                  multipartResolver.setMaxUploadSize(100000000);
                  multipartResolver.setMaxInMemorySize(100000000); // 10MB
                  return multipartResolver;
      }
 
}


파일명: FileConfig.java


[첨부(Attachments)]

FileConfig.zip



7. FileController.java (/com/website/example)


package com.website.example;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class FileController {
 
         private static final Logger logger = LoggerFactory.getLogger(FileController.class);
 
         private final String UPLOAD_PATH = "c:" + File.separator + "temp" + File.separator;

         
         @RequestMapping(value = "/insert", method = RequestMethod.GET)
         public String insert(Locale locale, Model model) {
                   logger.info("Welcome home! The client locale is {}.", locale);
  
                   return "insert";
         }
 
         @RequestMapping(value = "/fileupload",method = RequestMethod.POST)
         public String upload(MultipartFile uploadfile, Model model) throws IOException{
  
                   logger.info("upload() POST 호출");
                   logger.info("파일 이름: {}", uploadfile.getOriginalFilename());
                   logger.info("파일 크기: {}", uploadfile.getSize());

              
                   String result = saveFile(uploadfile);
    
                   if(result !=null){ // 파일 저장 성공
      
                        model.addAttribute("result", result);
        
                   } else { // 파일 저장 실패
      
                        model.addAttribute("result","fail");
        
                   }
    
                   return "home";

         }
 
         @RequestMapping(value = "/fileupload2", method = RequestMethod.POST)
         public String multiupload(@RequestParam("uploadfiles") MultipartFile[] file, Model model) throws IOException {
  
                  logger.info("Welcome multi file! The client locale is {}.", "Hello");
   
                  String result = "";
    
                  for(MultipartFile f : file){
                       result += saveFile(f);
                  }
    
                  return "home";
         }
 

         private String saveFile(MultipartFile file) throws IOException{
 
                // 파일 이름 변경
                UUID uuid = UUID.randomUUID();
                String saveName = uuid + "_" + file.getOriginalFilename();
 
                logger.info("saveName: {}",saveName);

                String fileName = file.getOriginalFilename();
                String contentType = file.getContentType();
                long filesize = file.getSize();
                // byte[] bytes = file.getBytes();
       
               //System.out.println("파일명:" + fileName);
               //System.out.println("컨텐츠 타입:" + contentType);
               //System.out.println("파일크기:" + filesize);
    
               // 저장할 File 객체를 생성(껍데기 파일)ㄴ
               File saveFile = new File(UPLOAD_PATH, saveName); // 저장할 폴더 이름, 저장할 파일 이름
 
               try {
                     file.transferTo(saveFile); // 업로드 파일에 saveFile이라는 껍데기 입힘
               } catch (IOException e) {
                     e.printStackTrace();
                     return null;
               }
 
               return saveName;
       }
 
}


파일명: FileController.java


[첨부(Attachments)]

FileController.zip



8. 출력 결과


동일한 결과를 볼 수 있다. 동일한 작업이기 때문이다.

차이점은 내부적인 관점에서 셋팅을 어떻게 했느냐 이런 문제라고 보겠다.



그림 3. (결과 1) 단일 업로드 모습



그림 4. (결과 1) 단일 업로드 모습 - 실제 파일 업로드 모습



그림 5. (결과 2) 다중 업로드 모습 - 실제 파일 업로드 모습



그림 6. (결과 2) 다중 업로드 모습 - 실제 파일 업로드 모습 - 폴더 모습


FileController.zip



* 맺음글(Conclusion)


Spring Framework 방식으로 Commons-io, Commons-FileUpload, CGLIB를 활용하여 업로드 프로그램을 구현하였다.

참고로 이 방식은 Spring Framework에서만 동작하는 방법이다.



반응형
728x90
300x250
[Spring-Framework] 38. Beans와 Context-XML 방식으로 DB 설정 - (Junit5)


이번에는 Spring Legacy Project로 Spring MVC Project를 생성했을 때, Resources 폴더에 등록하여 Junit5에서 태스트했을 때 사용하는 방법에 대해서 소개하려고 한다.


간단하지만, 의외로 처음 셋팅을 시도했을 때, 오류가 많이 생긴다.


글 제목에는 Beans가 적혀져 있지만, 따로 Beans 용어를 소개하진 않겠다.

이 코드를 해독하거나 태스트를 해 보면, Bean의 감을 잠시 맛볼 수는 있다.


* IDE: Eclipse 2020-06

* Framework: Spring Framework 4.2.4

* Spring-core

* Spring-tx

* Spring-jdbc

* Spring-test

* AspectJ

* AspectJWeaver



* 출력 결과


JUnit5와 Spring Framework 4.2.4 Releases의 조합으로 태스트를 하였다.



그림 1. 출력 결과 - 콘솔



그림 2. 태스트 결과





1. 프로젝트 구성도


아래의 그림은 프로젝트 구성도이다.

주로 작업할 코드는 "TestUnit.java", "applicationContext.xml", "db.properties", "pom.xml"이다.



그림 3. 프로젝트 구성도



2. Java Compiler, Build Paths, Project Factes



/src/main/webapp/WEB-INF/lib에 oracle JDBC.jar(ojdbc.jar) 파일 넣어주기


* Java Compiler - compiler compliance level 1.8

* Build Path - JUnit 5, JRE System Library (JavaSE 1.8) 등

   - Add Jar로 ojdbc.jar 파일 등록해주기

* Project Factes - Java : 1.8




3. pom.xml


(중략)


 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>


(중략)

  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${org.springframework-version}</version>

  </dependency>


  <!-- 밑줄 친 이유는 mvnrepository에서 찾아보면, scope라는 항목이 추가적으로 되어 있음. 지워야 동작함. -->
   
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>
    



  <!-- AOP 구현할 경우에는 필요함. (AspectJ) -->


  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  
  <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
  <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>${org.aspectj-version}</version>
      <scope>runtime</scope>
  </dependency>
  




4. db.properties(/src/main/resources)


jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:tcp://localhost/~/test
jdbc.username=sa
jdbc.password=
MYSQL_DB_URL=jdbc:mysql://localhost:3306/dbanme?useUnicode=true&characterEncoding=utf8
MYSQL_DB_USERNAME=
MYSQL_DB_PASSWORD=
ORACLE_DB_URL=jdbc:oracle:thin:@127.0.0.1:1521:xe
ORACLE_DB_USERNAME=HR
ORACLE_DB_PASSWORD=1234


파일명: db.properties


[첨부(Attachments)]

db.zip




5. applicationContext.xml(/src/main/resources)


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

 <context:component-scan base-package="com.website.example" />

 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 <!-- DataSource 설정 -->
 <context:property-placeholder location="classpath:db.properties" />

 <!-- DataSource 셋팅 -->
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="URL" value="${ORACLE_DB_URL}" />
        <property name="user" value="${ORACLE_DB_USERNAME}"/>
        <property name="password" value="${ORACLE_DB_PASSWORD}"/>
        <property name="connectionCachingEnabled" value="true"/>
    </bean>
   
 <!-- Spring JDBC 설정 -->
 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="dataSource" />
 </bean>
 

 <!-- 미구현하였으나 트랜젝션 고려중 -->

 <!-- Transaction 설정 -->
 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"></property>
 </bean>

 

 <!-- 미구현하였으나 AOP 고려중 -->

 <tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
   <tx:method name="get*" read-only="true"/>
   <tx:method name="*"/>
  </tx:attributes>
 </tx:advice>

 
 <!-- 미구현하였으나 AOP 고려중 -->
 <aop:config>
  <aop:pointcut id="txPointcut"  expression="execution(* com.website.example.service.DBService..*(..))"/>
  
  <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
 </aop:config>
 
</beans>



파일명: applicationContext.xml


[첨부(Attachments)]

applicationContext.zip


[추가]


     <!-- MySQL - DataSource 셋팅 -->
    <bean id="dataSource2" class="com.mysql.cj.jdbc.MysqlDataSource">
        <property name="URL" value="${ORACLE_DB_URL}" />
        <property name="user" value="${ORACLE_DB_USERNAME}"/>
        <property name="password" value="${ORACLE_DB_PASSWORD}"/>
    </bean>


    <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> -->
 
     <!-- 2. Oracle - DataSource 셋팅 -->
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="URL" value="${ORACLE_DB_URL}" />
        <property name="user" value="${ORACLE_DB_USERNAME}"/>
        <property name="password" value="${ORACLE_DB_PASSWORD}"/>
        <property name="connectionCachingEnabled" value="true"/>
    </bean>

    <!-- 3. Apache DBCP -->

   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
         <property name="driverClassName" value="${jdbc.driverClassName}" />
         <property name="url" value="${jdbc.url}" />
         <property name="username" value="${jdbc.username}" />
         <property name="password" value="${jdbc.password}" />
   </bean>


    <!-- 4. HikariCP -->
    <!--
    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
         <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
         <property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:xe" />
         <property name="username" value="username" />
         <property name="password" value="password" />
    </bean>
 
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
          <constructor-arg ref="hikariConfig"/>
    </bean>
     -->



    <!-- 5. MariaDB -->
    <!--
    <bean id="dataSource4" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="org.mariadb.jdbc.Driver"/>
        <property name="url" value="jdbc:mariadb://localhost:3306/test"></property>
        <property name="username" value="root"/>
        <property name="password" value="test"/>
    </bean>
     -->
    
    <!-- 6. MyBatis -->
    <!--
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource4"/>
        <property name="configLocation" value="classpath:mybatis/config.xml"/>
        <property name="mapperLocations" value="classpath:mybatis/sql/*.xml"></property>
    </bean>
   
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
   
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource4"/>
    </bean>
   
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
     -->





6. TestUnit.java (com/website/example/unit)


package com.website.example.unit;

import static org.junit.jupiter.api.Assertions.*;

import java.sql.Connection;
import java.sql.SQLException;

import javax.inject.Inject;
import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;


class TestUnit {
 
        private DataSource dataSource;
        private ApplicationContext ctx;
 
        // 태스트 환경에서 Autowired 버리기(JUnit 5 to Spring Framework 4.24 일 떄)
 

        @Test
        void test() {
  
               // 방법 1. (ApplicationContext로 Bean 불러오기)
               ctx = new GenericXmlApplicationContext("applicationContext.xml");
               this.dataSource = (DataSource) ctx.getBean("dataSource");
  
               if ( this.ctx != null ) {
                     System.out.println("야1");
               }
  
               if ( this.dataSource != null ) {
                     System.out.println("야2");
               }
  
               try(Connection con = dataSource.getConnection()){
                     System.out.println("참");
    
              }catch(Exception e)
              {
                    //fail(e.getMessage());
                    System.out.println("거짓");
    
              }
  
      }

}


파일명: TestUnit.java


[첨부(Attachments)]

TestUnit.zip


applicationContext.zip



* 맺음글(Conclusion)


연속해서 /src/main/webapp/WEB-INF/spring/root-context.xml 등의 작업을 소개하면 이해가 되지 않을 수 있어서 핵심 위주로 간단하지만, 

중요한 키 포인트를 위주로 정리하였다.


환경 셋팅 관련해서 허공에 삽질을 적게 하였으면 하는 바람이다.


[Tomcat 배포, 웹 개발]

1. [Spring Framework] 39. Beans와 Context-XML 방식으로 DB 설정 그리고 Autowired - (Web.xml), 2020-10-10

- https://yyman.tistory.com/1464


반응형
728x90
300x250
[Spring-Framework] 37. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java - AOP)


이번에 소개할 내용은 지난 36번 글에 이어서 AOP를 추가로 적용한 방법에 대해 소개하려고 한다.

어노테이션 방식의 트랜젝션 구축이 이해가 되지 않으면, 이전 글을 참고하면 도움이 된다.


1. [Spring-Framework] 36. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java 설정), 2020-10-10

- https://yyman.tistory.com/1461


IDE: Eclipse 2020-06

- Spring Framework 4.2.4 Releases

- Spring-JDBC

- Spring-TX

- Spring-Core

- AspeetJ Weaver

- AspectJ


DB: Oracle Databases 11g (Express Edition)



1. 프로젝트 구성도(비교)


AOP 작업을 추가한 프로젝트와 기존의 프로젝트의 구성을 비교해놓은 것이다.

이 글에서는 이전의 작성된 객체 파일을 그대로 복사 붙여넣기하여 AOP패키지에 적용하였다.


LogAdvisor.java 파일 말고는 추가 코드를 작성하진 않았다.

MainTestAOP.java도 복사, 붙여넣기해서 만든 것이다. 가리키고 있는 명칭만 변경하였다.



 

 



그림 1, 그림 2. 
(Spring 트랜젝션 + Spring JDBC) + AOP

그림 3, 그림 4. 이전 프로젝트
(Spring 트랜젝션 + Spring JDBC)



2. pom.xml 설정하기


((중략) - 추가된 사항)
    
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>

  
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  

  <!-- AspectJWeaver 추가 -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>
  





3. LogAdvisor.java(com.website.example.aop)


package com.website.example.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;


//관점, 서비스
@Aspect
@Service
public class LogAdvisor {


   // 2단계 - 전 단계 시야)
 
  @Before("execution(* com.website.example.aop.service.AccountServiceAOP.*(..))")
  // @Before("execution(public void sum())")
  // 반환값 없어도 무방
  
   public void logBefore() {
           System.out.println("전 단계");


   }

 
}


파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip



4. AccountServiceAOP.java(com.website.example.aop.service) - 예(복사, 붙여넣기)


코드 자체를 복사 붙여넣기 한 것이다.

AOP를 왜 하는지 철학을 이해하라고 하나 예로 보여주는 것이다.



package com.website.example.aop.service;

import java.sql.SQLException;


import com.website.example.vo.AccountVO;


public interface AccountServiceAOP {

 void accountCreate(AccountVO vo) throws SQLException;
 void accountTransfer(String sender, String receiver, int money) throws SQLException;
 
}




5. AccountServiceImplAOP.java(com.website.example.aop.service) - 예(복사, 붙여넣기)


하나 어노테이션 @Repository를 달아주었다. 그거 말고는 동일하다.



package com.website.example.aop.service;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.vo.AccountVO;


@Repository
@Transactional
public class AccountServiceImplAOP implements AccountServiceAOP{

 private AccountDAO accountDAO;
 private DataSource ds = null;
 
 public AccountServiceImplAOP(DataSource ds) {
  this.accountDAO = new AccountDAOImpl(ds);
  this.ds = ds;
 }

 @Override
 @Transactional(propagation=Propagation.NEVER)
 public void accountCreate(AccountVO vo) throws SQLException {
  accountDAO.createAccount(vo); 
  System.out.println("create CurrentTransactionName: " + TransactionSynchronizationManager.getCurrentTransactionName());
 }
 
 @Override
 @Transactional
 public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
     int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
     
        if(balance >= money){ // 보내는 돈이 잔액보다 많으면
     
         System.out.println("transfer CurrentTransactionName: " + TransactionSynchronizationManager.getCurrentTransactionName() );
         
   accountDAO.minus(sender, money);
   accountDAO.plus(receiver, money);
   
        } else{

         System.out.println("돈 없음");
         //throw new NoMoneyException();
        }
  
 }


}




6. RootConfigAOP.java(com.website.example.common) - 예(복사, 붙여넣기)



package com.website.example.common;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@Import({DBConfigAOP.class})
@ComponentScan(basePackages = {"com.website.example"})
@EnableAspectJAutoProxy
//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})
public class RootConfigAOP {


}




7. DBConfigAOP.java(com.website.example.common) - 예(복사, 붙여넣기)


package com.website.example.common;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.aop.service.AccountServiceAOP;
import com.website.example.aop.service.AccountServiceImplAOP;


@Configuration
@EnableTransactionManagement
public class DBConfigAOP {

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
   
    @Bean
    public DataSource dataSource() {
     
     DataSource dataSource = new MyDataSourceFactory().getOracleDataSource();
     
     /* Apache DBCP
     
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=utf8");
        dataSource.setUsername("test");
        dataSource.setPassword("test123!@#");
       
        */
        return dataSource;
    }
   
    @Bean
    public AccountDAO accountDAOImpl() {
     
     AccountDAO dao = new AccountDAOImpl(dataSource());
     return dao;
    }
   
    @Bean
    public AccountServiceAOP accountServiceImplAOP() {
     
     AccountServiceAOP service = new AccountServiceImplAOP(dataSource());

     return service;
    }
   
}




8. MainTestAOP.java(com.website.example.unit) - 예(복사, 붙여넣기)



package com.website.example.unit;


import java.sql.SQLException;

import java.sql.Timestamp;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.common.RootConfigAOP;

import com.website.example.dao.AccountDAOImpl;
import com.website.example.aop.service.AccountServiceAOP;
import com.website.example.aop.service.AccountServiceImplAOP;

import com.website.example.vo.AccountVO;


class MainTestAOP {

 @Test
 void test() throws SQLException {
  
       @SuppressWarnings("resource")
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfigAOP.class);

  
        AccountServiceAOP service = (AccountServiceAOP) applicationContext.getBean("accountServiceImplAOP");
  
  AccountVO vo = new AccountVO();
  
  // 1. 계정 생성
  vo.setName("홍길동2");
  vo.setBalance(10000);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 11:05:20"));
  service.accountCreate(vo);
  
  // 2. 계정 생성
  vo.setName("홍길자2");
  vo.setBalance(0);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 23:05:20"));
  service.accountCreate(vo);
  
  // 3. 거래 처리
  service.accountTransfer("홍길동", "홍길자", 500);
  
  
 }


}




* 맺음글(Conclusion)


AOP를 쉽고 간단하게 트랜젝션과 연결해서 사용하는 방법에 대해서 소개하였다.



반응형

+ Recent posts