728x90
300x250

[Spring-Framework] - Spring Framework 오프라인 개발방법 - xsd 스키마 서버 구축하기


이번에 소개할 내용은 Spring Framework에서 오프라인 상태로 개발하는 방법에 대해서 소개하려고 한다.

XML 문서는 ".xsd"라는 문서 규격에 의해서 양식이 정의된다고 볼 수 있다.


Spring Framework는 .xsd 문서 규격에 의해서 환경설정이 정의된다고 볼 수 있다.


이번에 소개할 내용은 기존에 classpath를 정의해서 오프라인 개발 방법에 대한 가이드는 인터넷에서도 많이 찾아볼 수 있는 주제이다.

하지만, 동작하지 않는 문제도 있어서 이 방법을 보완할 방법이 있을 거 같아서 고안해본 것이다.


해당 문제는 스키마 서버를 하나 구축하는 방법으로 해결해볼 수 있다고 본다.

스키마 서버를 구축해도, 오프라인 개발 방법으로 구현을 하고자 했을 때, w3c.org 규격에 대한 접속 경로를 찾지 못하는 오류는 발생할 수는 있다.

Top-Level 구조이기 때문에 동작은 가능하다고 본다.


The content to be introduced this time is to introduce how to develop offline in the Spring Framework.

It can be seen that the XML document is defined by the document standard ".xsd".


Spring Framework can be seen that the environment configuration is defined by the .xsd document standard.


The content to be introduced this time is a topic that can be found a lot on the Internet as a guide on how to develop offline by defining an existing classpath.


However, there is a problem that does not work, so I thought there was a way to compensate for this method, so I devised it.

I think this problem can be solved by building a schema server.


Even if a schema server is built, an error that cannot find a connection path to the w3c.org standard may occur when attempting to implement it in an offline development method.

Because it is a top-level structure, it is considered possible to operate.



1. 동작 모습(Operation state)


아래의 그림은 톰캣 스키마 서버를 추가로 생성한 모습이다.



그림 1. 스프링 프레임워크 - 스키마 서버 구축 모습의 예



그림 2. 스프링 프레임워크 - 스키마 경로 지정해주기



그림 3. 스프링 프레임워크 - 오프라인 동작 모습




2. 첨부(Attachment)


201231_SpringFramework_오프라인_개발방법_xsd운영하기_pdf.zip

201231_SpringFramework_오프라인_개발방법_xsd운영하기_pptx.zip


[GNU/GPL v3 License를 적용 받는다.]




3. 맺음글(Conclusion)


Spring Framework를 오프라인에서 개발하는 방법 중 스키마 서버구축 방법에 대해서 소개하였다.

반응형
728x90
300x250
[Spring-Framework] 42. Spring Framework에서 (Jaxb-runtime, activation, Jaxb Api, JSTL)을 활용한 XML 생성하기


이번에 소개할 방법은 Spring Framework에서 Jaxb-runtime, activation, Jaxb Api, JSTL을 활용하여 XML을 생성하는 방법에 대해서 소개하겠다.


JSP/Servlet 방식하고는 차이가 있는 점이 있다면, @(어노테이션)을 사용할 것이다.


* IDE: Eclipse 2020-06
* Library: Maven Project / Spring Framework 4.2.4 Releases.

1. https://mvnrepository.com/artifact/javax.servlet/servlet-api

   javax.servlet 2.5

2. https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api

   jaxb-api 2.3.0-b170201.1204

3. https://mvnrepository.com/artifact/javax.activation/activation

   activation 1.1

4. https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime

   jaxb-runtime 2.3.0-b170127.1453

5. https://mvnrepository.com/artifact/javax.servlet/jstl

   jstl 1.2



1. 프로젝트 구성도


실제로는 코드가 몇 줄 되지는 않지만, 문제는 라이브러리 셋팅 등에서 오류를 많이 경험할 수 있다.



그림 1. 프로젝트 구성도




2. Java Compiler, Build Path, Project Factes


* Java Compiler - compiler compliance level 1.8
* Build Path -> JRE System Library 1.8
* Project Factes - Java : 1.8



3. pom.xml

이 셋팅이 매우 무척 많이 중요하다.


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.website</groupId>
 <artifactId>example</artifactId>
 <name>Spring-XML-Jaxb</name>
 <packaging>war</packaging>
 <version>1.0.0-BUILD-SNAPSHOT</version>
 <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>
 <dependencies>
  <!-- Spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${org.springframework-version}</version>
   <exclusions>
    <!-- Exclude Commons Logging in favor of SLF4j -->
    <exclusion>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     </exclusion>
   </exclusions>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${org.springframework-version}</version>
  </dependency>
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  
  <!-- Logging -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>${org.slf4j-version}</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
   <version>${org.slf4j-version}</version>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>${org.slf4j-version}</version>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.15</version>
   <exclusions>
    <exclusion>
     <groupId>javax.mail</groupId>
     <artifactId>mail</artifactId>
    </exclusion>
    <exclusion>
     <groupId>javax.jms</groupId>
     <artifactId>jms</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jdmk</groupId>
     <artifactId>jmxtools</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jmx</groupId>
     <artifactId>jmxri</artifactId>
    </exclusion>
   </exclusions>
   <scope>runtime</scope>
  </dependency>

  <!-- @Inject -->
  <dependency>
   <groupId>javax.inject</groupId>
   <artifactId>javax.inject</artifactId>
   <version>1</version>
  </dependency>
    
  <!-- Servlet -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>2.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
  </dependency>
 
  <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
  <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.3.0-b170201.1204</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
  <dependency>
      <groupId>javax.activation</groupId>
      <artifactId>activation</artifactId>
      <version>1.1</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
  <dependency>
      <groupId>org.glassfish.jaxb</groupId>
      <artifactId>jaxb-runtime</artifactId>
      <version>2.3.0-b170127.1453</version>
  </dependency>
 

  <!-- Test -->
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <scope>test</scope>
  </dependency>       
 </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


파일명: pom.xml


[첨부(Attachments)]

pom.zip




4. HomeController.java (com.website.example)


package com.website.example;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ResponseBody;

import com.website.example.vo.BoardListVO;
import com.website.example.vo.BoardVO;

/**
 * Handles requests for the application home page.
 */

@Controller
public class HomeController {
 
 private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
 
 /**
  * Simply selects the home view to render by returning its name.
  */
 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String home(Locale locale, Model model) {
  logger.info("Welcome home! The client locale is {}.", locale);
  
  Date date = new Date();
  DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
  
  String formattedDate = dateFormat.format(date);
  
  model.addAttribute("serverTime", formattedDate );
  
  return "home";
 }
 
         @RequestMapping(value = "/dataTransform", produces="application/xml")
         @ResponseBody
         public BoardListVO dataTransform(Locale locale, Model model) {

  
              List<BoardVO> boardList = new ArrayList<BoardVO>();
              BoardVO vo = new BoardVO();
             vo.setId(1);
             vo.setTitle("야야야1");
             vo.setSearchCondition("하후");
             vo.setWriter("홍길동");
             vo.setRegDate(java.sql.Date.valueOf("2010-02-01"));
  
             boardList.add(vo);
  
            vo = new BoardVO();
            vo.setId(2);
            vo.setTitle("야야야2");
            vo.setSearchCondition("하후");
            vo.setWriter("홍길동");
            vo.setRegDate(java.sql.Date.valueOf("2010-03-01"));
            boardList.add(vo);
  
            BoardListVO boardListVO = new BoardListVO();
            boardListVO.setBoardList(boardList);
  
            System.out.println("가동중");
  
             return boardListVO;
        }
  
}


파일명: HomeController.java


[첨부(Attachments)]

HomeController.zip



5. BoardVO.java (com.website.example.vo)


package com.website.example.vo;

import java.sql.Date;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;


@XmlAccessorType(XmlAccessType.FIELD)
public class BoardVO {


         @XmlAttribute        // 사용 할 속성
         private int id;
         private String title;
         private String writer;
         private String content;
         private Date regDate;
 
        @XmlTransient        // 사용 안함
         private String searchCondition;
 
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getTitle() {
  return title;
 }
 public void setTitle(String title) {
  this.title = title;
 }
 public String getWriter() {
  return writer;
 }
 public void setWriter(String writer) {
  this.writer = writer;
 }
 public String getContent() {
  return content;
 }
 public void setContent(String content) {
  this.content = content;
 }
 public Date getRegDate() {
  return regDate;
 }
 public void setRegDate(Date regDate) {
  this.regDate = regDate;
 }
 public String getSearchCondition() {
  return searchCondition;
 }
 public void setSearchCondition(String searchCondition) {
  this.searchCondition = searchCondition;
 }
 
 
 
}


파일명: BoardVO.java


[첨부(Attachments)]

BoardVO.zip

HomeController.zip



6. BoardListVO.java (com.website.example.vo)


package com.website.example.vo;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "boardList")
@XmlAccessorType(XmlAccessType.FIELD)
public class BoardListVO {


       @XmlElement(name = "board")
        private List<BoardVO> boardList;
 
        public List<BoardVO> getBoardList(){
                 return this.boardList;
        }
 
        public void setBoardList(List<BoardVO> boardList) {
                 this.boardList = boardList;
        }
 
}


파일명: BoardListVO.java


[첨부(Attachments)]

BoardListVO.zip



7. 출력 결과


스프링에서 출력한 XML이다.



그림 2. 출력 결과


BoardVO.zip

HomeController.zip



*음글(Conclsuion)


스프링 프레임워크로 Jaxb2 외 다수 라이브러리를 활용하여 XML을 생성하였다.



* 참고자료(References)


1. A Guide to JAXB Annotations - HowToDoInJava, https://howtodoinjava.com/jaxb/jaxb-annotations/, Accessed by 2020-10-11, Last Modified 2019-02.


2. [Java] JAXB 활용한 Java 객체의 XML 변환 방법, https://haenny.tistory.com/8, Accessed by 2020-10-11, Last Modified 2019-07-09.


반응형
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] 36. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java 설정)


이번 글에서 소개할 내용은 스프링 프레임워크의 트랜젝션을 조금 더 다뤄보려고 한다.


어노테이션을 사용하는 데 있어서 방법이 두 가지가 있다.


- 1. Java Config 파일 구성 방식

- 2. (context.xml) XML 파일 구성 방식


* 참고로 스프링 프레임워크는 셋팅이 시작의 반이라고 보면 될 것 같다.


[작업 환경]

IDE: Eclipse 2020-06

Framework: Spring Framework 4.2.4 RELEASE

spring-jdbc

spring-core

spring-tx

mysql-connector-java (8.0.21)

oracle jdbc (WEB-INF/lib에 넣어줘야 함.)


OpenJDK 15 (프로젝트에 적용한 자바 버전은 1.8로 셋팅하였음.)


딱딱한 글도 중요하지만, 영상으로 이게 무엇인지 소개해주겠다. (작동 원리 시연)



영상 1. Spring Framework 4.2.4 RELEASES - Transactions (시연)





1. 프로젝트 구성


프로젝트 구성에 관한 것이다.



그림 1. 프로젝트 구성도



2. 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-jdbc -->
  <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</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/mysql/mysql-connector-java -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.21</version>
  </dependency>




3. Build Path, Project Factes, Java compiler 설정하기


* Build Path -> JRE System Library 1.8, JUnit 5

* Java Compiler -> Compiler compliance level: 1.8

* Project Factes -> Java : 1.8


* 오라클 JDBC: WEB-INF/lib폴더에 ojdbc.jar 파일 넣어주기 




4. AccountTbl.sql


-- Transaction 실습 DB (은행 - Account)
-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (FOODMENU_TBL)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE account_tbl
(
    idx NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    balance NUMBER,
    regidate TIMESTAMP
);

-- Sequence 정의
CREATE SEQUENCE account_sequence
START WITH 1
INCREMENT BY 1;


-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER account_trigger
BEFORE INSERT
    ON account_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT account_sequence.nextval INTO :NEW.IDX FROM dual;
END;


파일명: AccountTBL.sql


[첨부(Attachments)]

AccountTbl.zip




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


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. AccountVO.java (com.website.example.vo)


package com.website.example.vo;

import java.sql.Timestamp;;

public class AccountVO {
 
      private int idx;
      private String name;
      private int balance;
      private Timestamp regidate;
 
      public int getIdx() {
          return idx;
      }
 
      public void setIdx(int idx) {
          this.idx = idx;
      }
 
      public String getName() {
          return name;
      }
 
      public void setName(String name) {
          this.name = name;
      }
 
      public int getBalance() {
          return balance;
     }

     public void setBalance(int balance) {
          this.balance = balance;
     }


     public Timestamp getRegidate() {
         return regidate;
     }
 
     public void setRegidate(Timestamp regidate) {
         this.regidate = regidate;
     }
 
}


파일명: AccountVO.java


[첨부(Attachments)]

AccountVO.zip




6. DBConfig.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.service.AccountService;
import com.website.example.service.AccountServiceImpl;

@Configuration
@EnableTransactionManagement

public class DBConfig {

    @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 AccountService accountServiceImpl() {
     
     AccountService service = new AccountServiceImpl(dataSource());
     return service;
    }
   
}


파일명: DBConfig.java


[첨부(Attachments)]

DBConfig.zip




7. RootConfig.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.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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


}


파일명: RootConfig.java


[첨부(Attachments)]

RootConfig.zip




8. MyDataSourceFactory.java (com.website.example.common)



package com.website.example.common;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.cj.jdbc.MysqlDataSource;

import oracle.jdbc.pool.OracleDataSource;

public class MyDataSourceFactory {
 
 private InputStream is = null;
    private Properties props = null;
 
 public MyDataSourceFactory()  {
  
        String resource = "db.properties";
        this.is = getClass().getClassLoader().getResourceAsStream(resource);
        this.props = new Properties();
       
 }
 
 public DataSource getMySQLDataSource() {
  
        MysqlDataSource mysqlDS = null;
       
        try {
         
            props.load(is);
            mysqlDS = new MysqlDataSource();
            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));
            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));
            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        return mysqlDS;
       
    }
    
    public DataSource getOracleDataSource(){
     
        OracleDataSource oracleDS = null;
       
        try {
         
            props.load(is);
            oracleDS = new OracleDataSource();
            oracleDS.setURL(props.getProperty("ORACLE_DB_URL"));
            oracleDS.setUser(props.getProperty("ORACLE_DB_USERNAME"));
            oracleDS.setPassword(props.getProperty("ORACLE_DB_PASSWORD"));
           
           
        } catch (IOException e) {
         
            e.printStackTrace();
           
        } catch (SQLException e) {
         
            e.printStackTrace();
           
        }
       
        return oracleDS;
       
    }

}


파일명: MyDataSourceFactory.java


[첨부(Attachments)]

MyDataSourceFactory.zip




9. AccountDAO.java (com.website.example.dao)


package com.website.example.dao;

import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountDAO {
 
       void createAccount(AccountVO vo) throws SQLException;
       int getBalance(String name);
       void minus(String name, int money) throws SQLException;
       void plus(String name, int money) throws SQLException;
 
}


파일명: AccountDAO.java


[첨부(Attachments)]

AccountDAO.zip

MyDataSourceFactory.zip




10. AccountRowMapper.java (com.website.example.dao)



package com.website.example.dao;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import com.website.example.vo.AccountVO;

@Service
public class AccountRowMapper implements RowMapper<AccountVO>  {


      @Override
      public AccountVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
             AccountVO vo = new AccountVO();

             vo.setIdx(rs.getInt(1));
             vo.setName(rs.getString(2));
             vo.setBalance(rs.getInt(3));
             vo.setRegidate(rs.getTimestamp(4));
  
             return vo;
      }

}


파일명: AccountRowMapper.java


[첨부(Attachments)]

AccountRowMapper.zip





11. AccountDAOImpl.java (com.website.example.dao)


package com.website.example.dao;

import java.sql.SQLException;
import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import com.website.example.vo.AccountVO;


public class AccountDAOImpl implements AccountDAO {

       // Spring Framework - JDBC
       private JdbcTemplate jdbcTemplate = null;
       private DataSource ds = null;
 
       private final String INSERT = "insert into account_tbl(name, balance, regidate) values(?, ?, ?)";
       private final String SELECT_BALANCE = "select * from account_tbl where name = ?";
       private final String UPDATE_MINUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) - ? " +
          " where name = ?";
       private final String UPDATE_PLUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) + ? " +
          " where name = ?";
 
       public AccountDAOImpl(DataSource ds) {
             this.jdbcTemplate = new JdbcTemplate(ds);
             this.ds = ds;
       }
 
       public void createAccount(AccountVO vo) throws SQLException {
   
             this.jdbcTemplate.update(INSERT, vo.getName(), vo.getBalance(), vo.getRegidate());  
       }
 
       public int getBalance(String name){
     
             Object[] args = {name};
     
             AccountVO vo = this.jdbcTemplate.queryForObject(SELECT_BALANCE, args, new AccountRowMapper());
       
             int result = vo.getBalance();
     
             return result;
       }

    
       public void minus(String name, int money) throws SQLException{
     
              this.jdbcTemplate.update(UPDATE_MINUS, name, money, name);
       
       }
   
       public void plus(String name, int money) throws SQLException{

            /*
            // 예외 발생시키기
            if(true){
                throw new RuntimeException("런타임 에러");
            }
             */
     
            this.jdbcTemplate.update(UPDATE_PLUS, name, money, name);
       }
 
}


파일명: AccountDAOImpl.java


[첨부(Attachments)]

AccountDAOImpl.zip





12. AccountService.java (com.website.example.service)



package com.website.example.service;

import java.sql.SQLException;


import com.website.example.vo.AccountVO;


public interface AccountService {

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


파일명: AccountService.java


[첨부(Attachments)]

AccountService.zip




13. AccountServiceImpl.java (com.website.example.service)



package com.website.example.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 com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.vo.AccountVO;

@Repository
@Transactional
public class AccountServiceImpl implements AccountService{


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

      @Override
       public void accountCreate(AccountVO vo) throws SQLException {
             this.accountDAO.createAccount(vo);  
       }
 
      @Override
      public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
            int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
      
            if(balance >= money){ // 보내는 돈이 잔액보다 많으면
     
                 accountDAO.minus(sender, money);
                 accountDAO.plus(receiver, money);
              
        } else{

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


}


파일명: AccountServiceImpl.java


[첨부(Attachments)]

AccountServiceImpl.zip





14. MainTest.java (com.website.example.unit)


package com.website.example.unit;


import java.sql.SQLException;

import java.sql.Timestamp;

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

import com.website.example.common.RootConfig;
import com.website.example.service.AccountService;
import com.website.example.vo.AccountVO;


class MainTest {

      
      @Test
      void test() throws SQLException {
  
           @SuppressWarnings("resource")
           ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
  
           AccountService service = (AccountService) applicationContext.getBean("accountServiceImpl");
  
           AccountVO vo = new AccountVO();
  
  /*
  // 1. 계정 생성
  vo.setName("홍길동");
  vo.setBalance(10000);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 10:05:20"));
  service.accountCreate(vo);
  
  // 2. 계정 생성
  vo.setName("홍길자");
  vo.setBalance(0);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 22:05:20"));
  service.accountCreate(vo);
  */
  
          // 3. 거래 처리
          service.accountTransfer("홍길동", "홍길자", 500);
  
      }

}


파일명: MainTest.java


[첨부(Attachments)]

MainTest.zip




* 맺음글(Conclusion)


트랜젝션을 어노테이션 방식으로 Java Config 환경설정으로 구성하여 사용하는 방법에 대해서 소개하였다.


[Spring-Framework] 37. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java - AOP)

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



* 참고 자료(References)


1. Spring JDBC, https://velog.io/@koseungbin/Spring-JDBC, Accessed by 2020-10-10, Last Modified 2020-07-08.

2. Spring JDBC using Annotation based configuration, https://www.topjavatutorial.com/frameworks/spring/spring-jdbc/spring-jdbc-using-annotation-based-configuration/, Accessed by 2020-10-10, Last Modified 2016-02-14.

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


@Transactional이라는 어노테이션 기능 방식이 아닌 직접 구현으로 트랜젝션을 Spring JDBC에서 다루는 방법을 소개하려고 한다.


아마 이전의 Spring Framework의 34번 글을 실습해보았다면, 희안하게 insert 명령은 수행되어서 SQL Developer로 SELECT문으로 출력했을 때는 화면에 보이는데, Java에서 DB 질의로 SELECT문으로 조회한다면 동작이 되는 경우도 있고, 안 된 경우도 있었을 거 같다.


이 문제는 commit()을 했느냐 안 했느냐의 차이에서 발생한다.


1. [Spring-Framework] 34. Spring JDBCTemplate - 사용하기, 2020. 10. 9. 21:01

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


이 글의 개선 버전이라고 보면 되겠다.


- IDE: Eclipse 2020-06

- Spring Framework 4.2.4 RELEASES



1. 프로젝트 구성


다음 그림은 프로젝트를 구성하고 있는 구성도이다.



그림 1. 프로젝트 구성도


이전 글에서는 프로젝트 구성도까진 필요할까 고민했는데, 심플하게 적어주는 것이 더 나아보여서 핵심만 적었다.

지금 글에서는 Spring JDBC에서 트랜젝션을 제대로 사용하는 방법에 대해서 소개하려고 한다.



2. Project Factes, Build Paths, Java Compiler 버전 변경해주기


* Build Paths -> JRE Library 1.8로 변경, Junit 5 추가할 것

* Java Compiler -> Compiler complicance Level: 1.8 환경으로 바꿔줄 것

* Project Factes -> Java 버전 1.8로 변경할 것


- 오라클 JDBC는 WEB-INF/lib에 넣어서 해결해주면 된다.

  (Build Paths에 등록도 해줘야 함.)




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>
 <dependencies>


(중략)


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

(중략)



  <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.21</version>
  </dependency>





4. AccountTbl.sql


-- Transaction 실습 DB (은행 - Account)
-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (FOODMENU_TBL)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE account_tbl
(
    idx NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    balance NUMBER,
    regidate TIMESTAMP
);

-- Sequence 정의
CREATE SEQUENCE account_sequence
START WITH 1
INCREMENT BY 1;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER account_trigger
BEFORE INSERT
    ON account_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT account_sequence.nextval INTO :NEW.IDX FROM dual;
END;


파일명: AccountTbl.sql


[첨부(Attachments)]

AccountTbl.zip



5. MyDataSourceFactory.java (com.website.example.common)


package com.website.example.common;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.cj.jdbc.MysqlDataSource;

import oracle.jdbc.pool.OracleDataSource;

public class MyDataSourceFactory {
 
 private InputStream is = null;
    private Properties props = null;
 
 public MyDataSourceFactory()  {
  
        String resource = "db.properties";
        this.is = getClass().getClassLoader().getResourceAsStream(resource);
        this.props = new Properties();
       
 }
 
 public DataSource getMySQLDataSource() {
  
        MysqlDataSource mysqlDS = null;
       
        try {
         
            props.load(is);
            mysqlDS = new MysqlDataSource();
            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));
            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));
            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        return mysqlDS;
       
    }
    
    public DataSource getOracleDataSource(){
     
        OracleDataSource oracleDS = null;
       
        try {
         
            props.load(is);
            oracleDS = new OracleDataSource();
            oracleDS.setURL(props.getProperty("ORACLE_DB_URL"));
            oracleDS.setUser(props.getProperty("ORACLE_DB_USERNAME"));
            oracleDS.setPassword(props.getProperty("ORACLE_DB_PASSWORD"));
           
           
        } catch (IOException e) {
         
            e.printStackTrace();
           
        } catch (SQLException e) {
         
            e.printStackTrace();
           
        }
       
        return oracleDS;
       
    }

}


파일명: MyDataSourceFactory.java


[첨부(Attachments)]

MyDataSourceFactory.zip


AccountTbl.zip



6. AccountVO.java (com.website.example.vo)


package com.website.example.vo;

import java.sql.Timestamp;;


public class AccountVO {
 
     private int idx;
     private String name;
     private int balance;
     private Timestamp regidate;
 
     public int getIdx() {
          return idx;
     }
 
     public void setIdx(int idx) {
          this.idx = idx;
     }
 
     public String getName() {
          return name;
     }
 
     public void setName(String name) {
          this.name = name;
     }
 
     public int getBalance() {
          return balance;
     }

 

     public void setBalance(int balance) {
          this.balance = balance;
     }


     public Timestamp getRegidate() {
          return regidate;
     }
 
     public void setRegidate(Timestamp regidate) {
          this.regidate = regidate;
      }
   
}


파일명: AccountVO.java


[첨부(Attachments)]

AccountVO.zip

AccountTbl.zip



7. AccountDAO.java (com.website.example.dao)


package com.website.example.dao;


import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountDAO {
 
       void createAccount(AccountVO vo) throws SQLException;
       int getBalance(String name);
       void minus(String name, int money) throws SQLException;
       void plus(String name, int money) throws SQLException;
 
}


파일명: AccountDAO.java


[첨부(Attachments)]

AccountDAO.zip



8. AccountDAOImpl.java (com.website.example.dao)


package com.website.example.dao;

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

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.vo.AccountVO;

public class AccountDAOImpl implements AccountDAO {


       // Spring Framework - JDBC
       private JdbcTemplate jdbcTemplate = null;
       private DataSource ds = null;
 
       private final String INSERT = "insert into account_tbl(name, balance, regidate) values(?, ?, ?)";
       private final String SELECT_BALANCE = "select * from account_tbl where name = ?";
       private final String UPDATE_MINUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) - ? " +
          " where name = ?";
       private final String UPDATE_PLUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) + ? " +
          " where name = ?";
 
 
        public AccountDAOImpl(DataSource ds) {
               this.jdbcTemplate = new JdbcTemplate(ds);
               this.ds = ds;
        }
 
         public void createAccount(AccountVO vo) throws SQLException {
  
                TransactionSynchronizationManager.initSynchronization(); // 동기화
                Connection c = DataSourceUtils.getConnection(ds); //커넥션을 생성

  
                // 데이터 저장소 바인딩(트랜젝션)
                c.setAutoCommit(false); //트랜잭션을 시작
  
                try{
   
                    this.jdbcTemplate.update(INSERT, vo.getName(), vo.getBalance(), vo.getRegidate());
                    c.commit(); // 커밋
       
               }
               catch(Exception e) {
                    c.rollback(); // 예외가 발생하면 롤백
       
               }finally {
      
                    DataSourceUtils.releaseConnection(c, ds); // 스프링 유틸리티를 이용해 DB커넥션을 닫는다.
                    TransactionSynchronizationManager.unbindResource(ds); // 동기화 작업을 종료하고
                    TransactionSynchronizationManager.clearSynchronization();     // 정리한다.
      
               }

  
 }
 
    public int getBalance(String name){
     
     Object[] args = {name};
     
     AccountVO vo = this.jdbcTemplate.queryForObject(SELECT_BALANCE, args, new AccountRowMapper());
       
        int result = vo.getBalance();
     
        return result;
    }
    
    public void minus(String name, int money) throws SQLException{

     // 예외 발생시키기
     if(true){
      throw new SQLException(); // 의도적 예외 발생
        }
     
        this.jdbcTemplate.update(UPDATE_MINUS, name, money, name);
       
    }
    
    public void plus(String name, int money) throws SQLException{
          
        this.jdbcTemplate.update(UPDATE_PLUS, name, money, name);
    }
 
}


파일명: AccountDAOImpl.java


[첨부(Attachments)]

AccountDAOImpl.zip


AccountDAO.zip



9. AccountRowMapper.java (com.website.example.dao)


package com.website.example.dao;


import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.vo.AccountVO;


public class AccountRowMapper implements RowMapper<AccountVO>  {


       @Override
        public AccountVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
                 AccountVO vo = new AccountVO();

                 vo.setIdx(rs.getInt(1));
                 vo.setName(rs.getString(2));
                 vo.setBalance(rs.getInt(3));
                 vo.setRegidate(rs.getTimestamp(4));
  
                 return vo;
        }

}


파일명: AccountRowMapper.java


[첨부(Attachments)]

AccountRowMapper.zip




10. AccountService.java (com.website.example.service)


package com.website.example.service;

import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountService {

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


파일명: AccountService.java


[첨부(Attachments)]

AccountService.zip




11. AccountServiceImpl.java (com.website.example.service)


package com.website.example.service;

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

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;

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



public class AccountServiceImpl implements AccountService{

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


      @Override
      public void accountCreate(AccountVO vo) throws SQLException {
            this.accountDAO.createAccount(vo);  
      }
 

      @Override
      public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
               int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
               Connection conn = null;
     
               if(balance >= money){ // 보내는 돈이 잔액보다 많으면

                    TransactionSynchronizationManager.initSynchronization(); // 트랜잭션 동기화 작업 초기화

                    conn = ds.getConnection();
                    conn.setAutoCommit(false);
         
                    try {
                       accountDAO.minus(sender, money);
                       accountDAO.plus(receiver, money);
          
                       conn.commit(); // 성공
    
                   }catch(SQLException e) {
                        conn.rollback(); // 실패
                   }
                   finally {
                        // 커넥션 종료(Spring)
                       DataSourceUtils.releaseConnection(conn, this.ds);
          
                       // 동기화 작업을 종료하고 저장소를 비운다
                       TransactionSynchronizationManager.clearSynchronization();
                   }
           
             } else{

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


}


파일명: AccountServiceImpl.java


[첨부(Attachments)]

AccountServiceImpl.zip



12. MainTest.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 com.website.example.common.MyDataSourceFactory;
import com.website.example.service.AccountService;
import com.website.example.service.AccountServiceImpl;
import com.website.example.vo.AccountVO;


class MainTest {


     @Test
     void test() throws SQLException {

            MyDataSourceFactory sourceFactory = new MyDataSourceFactory();
            DataSource ds = sourceFactory.getOracleDataSource();
  
            AccountService service = new AccountServiceImpl(ds);
            AccountVO vo = new AccountVO();
   
            /*
            // 1. 계정 생성
           vo.setName("홍길동");
           vo.setBalance(10000);
           vo.setRegidate(Timestamp.valueOf("2020-01-20 10:05:20"));
           service.accountCreate(vo);
  
           // 2. 계정 생성
           vo.setName("홍길자");
           vo.setBalance(0);
           vo.setRegidate(Timestamp.valueOf("2020-01-20 22:05:20"));
           service.accountCreate(vo);
            */
  
           // 3. 거래 처리
           service.accountTransfer("홍길동", "홍길자", 500);
  
  
      }

}


파일명: MainTest.java


[첨부(Attachments)]

MainTest.zip

AccountServiceImpl.zip




* 맺음글(Conclusion)


스프링 JDBC에서 트랜젝션 사용하는 방법에 대해서 어노테이션을 적용하지 않는 방법에 대해서 살펴보았다.


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

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

(이해를 돕기 위하여 실제 개발환경도구에서 트랜젝션 시연 영상도 포함해서 소개하였다.)


반응형
728x90
300x250
[Spring-Framework] 34. Spring JDBCTemplate - 사용하기


사용 방법은 무척 간단하다.

pom.xml 등의 학습 지식이 없다면, 이전 글을 참고하면 좋겠다.


[작성 환경]

- IDE: Eclipse 2020-06
- Spring Framework 4.2.4 RELEASES




1. POM.xml 설정


https://mvnrepository.com/artifact/org.springframework/spring-jdbc

사이트에 접속해서 Spring-JDBC의 정보를 찾아서 pom.xml에 입력시켜 준다.

(중략)
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>


(중략)




2. 구현 부분

 

핵심만 밑줄로 표기하였다.



package com.website.example.board;

import java.sql.Date;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.model.FoodMenuVO;
import com.website.example.model.FoodMenuViewVO;


public class BoardDAOSpring {
 
 // Spring Framework - JDBC
 private JdbcTemplate jdbcTemplate = null;
 
 private final String FOODMENU_INSERT = "insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values(?, ?, ?, ?, ?)";
 private final String BOARD_UPDATE = "update board set subject=?, memo=? where id=?";
 private final String FOODMENU_DELETE = "delete foodmenu_tbl where id=?";
 private final String BOARD_GET = "select * from board where id=?";

 
 private final String BOARD_LIST = "SELECT * FROM ( " +
                "SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM ( " +
                   "SELECT f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 " +
                "where f1.store_id = f2.id order by f1.id desc " +
                ") Z WHERE ROWNUM <= ? " +
                ") WHERE RNUM >= ?";
 
      private final String BOARD_FULL_COUNT = "select count(*) from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id";

      // 변형을 할 필요가 있음. (태스트용)

      public BoardDAOSpring() {

              MyDataSourceFactory sourceFactory = new MyDataSourceFactory();
              DataSource ds = sourceFactory.getOracleDataSource();

              this.jdbcTemplate = new JdbcTemplate(ds);
      }
 
      public List<FoodMenuViewVO> getList(){
 
          // 코드 간결하게 작성가능해짐.
          System.out.println("Spring JDBC - GetBoardList()");
         //return jdbcTemplate.query(BOARD_LIST, new BoardRowMapper());
  
          Object args[] = {10, 1};
  
          return jdbcTemplate.query(BOARD_LIST, args, new FoodMenuViewRowMapper());

       // return null;

     }
 
     public int getCount() {
  
          int result = 0;
          FoodMenuVO vo =  jdbcTemplate.queryForObject(BOARD_FULL_COUNT, new FoodMenuViewCntRowMapper());
          result = vo.getId();
  
          System.out.println("갯수:" + result);
  
         return result;
  
     }
 
      public void insertTest() {
  
          FoodMenuVO vo = new FoodMenuVO();

  
          // 약 10만 개
          // insert 후에 commit 할 것
          /*

          for(int j = 0; j < 2000 ; j++) {
                for ( int i = 0; i < 50; i++) {
   
                       vo.setName("하하하하1234" + i);
                       vo.setPrice(1000);
                       vo.setStore_id(1);
                       vo.setCnt(0);
                       vo.setRegidate(Date.valueOf("2020-01-03"));
                       vo.setStore_id(1);
    
                       jdbcTemplate.update(FOODMENU_INSERT, vo.getName(),
                              vo.getPrice(), vo.getStore_id(),
                              vo.getCnt(), vo.getRegidate());
    
                }
        }// end of if
  */
  
        vo.setName("야해해");
        vo.setPrice(1000);
        vo.setStore_id(1);
        vo.setCnt(0);
        vo.setRegidate(Date.valueOf("2020-01-05"));
        vo.setStore_id(1);
  
        Object[] args = {vo.getName(),
                 vo.getPrice(),
                 vo.getStore_id(),
                 vo.getCnt(),
                 vo.getRegidate()};

  
        jdbcTemplate.update(FOODMENU_INSERT, args);
  
 }
 

      // 글 삭제
      public void deleteFoodMenu(FoodMenuVO vo) {
  
             System.out.println("===> Spring JDBC로 deleteBoard() 기능 처리");
             jdbcTemplate.update( FOODMENU_DELETE, vo.getId() );
  
      }
 
 
}


파일명: BoardDAOSpring.java



3. Mapper 구현


Spring JDBC의 특징은 Mapper 영역을 구현할 수 있다는 점이다.


package com.website.example.board;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.model.FoodMenuVO;
import com.website.example.model.FoodMenuViewVO;


public class FoodMenuRowMapper implements RowMapper<FoodMenuVO>  {


        @Override
        public FoodMenuVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
              FoodMenuVO vo = new FoodMenuVO();

              vo.setId(rs.getInt(1));
              vo.setName(rs.getString(2));
              vo.setPrice(rs.getInt(3));
              vo.setStore_id(rs.getInt(4));
              vo.setCnt(rs.getInt(5));
              vo.setRegidate(rs.getDate(6));
  
              return vo;
        }

}


파일명: FoodMenuRowMapper.java


package com.website.example.board;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.model.FoodMenuVO;


public class FoodMenuViewCntRowMapper implements RowMapper<FoodMenuVO>  {


        @Override
        public FoodMenuVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
               FoodMenuVO vo = new FoodMenuVO();
               vo.setId(rs.getInt(1));
               // System.out.println(rs.getInt(1));
  
               return vo;
       }

}


파일명: FoodMenuViewCntRowMapper.java


Mapper를 두 가지 형태로 두었는데, 상황에 따라서 변형이 가능하다.



* 맺음글(Conclusion)


Spring JDBC에 대해서 살펴보았다.

짧게 적은 이유는 사용 방법이 간단해서 그렇다. Spring JDBC의 트랜젝션을 언급하기 위해서 그렇다.


1. [Spring-Framework] 35. Spring-JDBCTemplate - 트랜젝션 (어노테이션 X), 2020-10-09

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


반응형
728x90
300x250
[Spring Framework] 33. AOP(Aspect-Oriented-Programming) - XML 방식


XML 기반으로 작성된 AOP 구현 방법에 대해서 소개하려고 한다.

AOP의 관점에 대한 5가지 방식을 모두 적용해보았다.


[태스트 환경]

* IDE: Eclipse 2020-06

* Spring Framework 4.2.4.RELEASE
* Aspectjweaver 1.6.10
* Spring-aop 4.2.4.RELEASE
* JUnit 5




1. 프로젝트 구성도


프로젝트 구성도이다.



그림 1, 그림 2. 프로젝트 구성도




2. 프로젝트 생성


Spring Legacy Project로 프로젝트를 생성한다.

참고로 Spring MVC Project로 선택하고 생성해야 한다.



3. Build Path, Java Compiler, Project Factes 버전 맞춰주기


* Build Path: JRE 버전을 1.8로 변경해준다.
               - Add Library로 JUnit 5를 등록해준다.

* Java Compiler: Compiler compliance level - 1.8로 변경해준다.

* Project Factes: Java 버전을 1.8로 변경해준다.




4. 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>

~~~

(중략)
    <!-- 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>
   
    <!-- Spring AOP 추가(Java) --> 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework-version}</version>
    </dependency> 
(중략)






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


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: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">
 
 <!-- 방법1 - JAVA -->
 <!-- JAVA 방식(어노테이션)의 AOP - AspectJ Weaver -->
 <!--  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  어노테이션 작업시 필수 선언해야 함. -->
 
 <!--  XML 방식의 AOP - AsepectJ Weaver -->
 <bean id="resultAOP" class="com.website.example.test.ResultAOP"></bean>
 <bean id="txAdviceXML" class="com.website.example.aop.LogAdvisorXML"></bean>

 <aop:config>
  <!-- txAdviceXML 하나 영역임 -->
  <aop:aspect ref='txAdviceXML'>

   <!-- 진입영역 -->
   <!-- 1개만 가능함 -->
   <!-- <aop:pointcut id="point1" expression="execution(* com.website.example.test..*())"/> -->
   <aop:pointcut id="point1" expression="execution(* com.website.example.test.ResultAOP..*())"/>           
          
          <!-- before -->
          <aop:before method="beforeAdvice" pointcut-ref="point1"/>
          <!-- after -->
          <aop:after method="afterAdvice" pointcut-ref="point1"/>
          
          <!-- around(메서드 자체를 가로채기) -->
          <aop:around method="aroundAdvice" pointcut-ref="point1"/>
          
          <!-- afterThrowing -->
          <aop:after-throwing method="afterThrowing" pointcut-ref="point1"/>
          
          <!-- afterReturning -->
          <aop:after-returning method="afterReturning" pointcut-ref="point1"/>
          
        </aop:aspect>
       
        <!-- 2번째 선언자 -->
 </aop:config>
 
</beans>


파일명: applicationContext.xml


[첨부(Attachments)]

applicationContext.zip



6. ResultAOP.java - com.website.example.text


핵심 로직에 해당되는 부분이다. 이 부분을 비즈니스 로직이라고 표현하기도 한다.


package com.website.example.test;


public class ResultAOP {


     public void method1() {
           System.out.println("[중간]:");
  
           // afterThrowing 유발 코드
           // int d = 2/0;
  
           System.out.println("결과: 메서드");
     }
 
}


파일명: ResultAOP.java


[첨부(Attachments)]

ResultAOP.zip


applicationContext.zip



7. LogAdvisor.java - com.website.example.aop


인터페이스 정의이다.

package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public interface LogAdvisor {

        public void beforeAdvice();
        public void afterAdvice();
        public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable;
        public void afterThrowing();
        public void afterReturning();
 
}


파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip




8. LogAdvisorXML.java - com.website.example.aop


package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;


public class LogAdvisorXML implements LogAdvisor{
  
       // around advice
       @Override
       public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
  
               System.out.println("2단계A?:");
               System.out.println("aroundMethod 호출 1");
  
               // 원래의 메소드를 호출한다.
               Object obj = pjp.proceed();

               System.out.println("2단계B?:");
               System.out.println("aroundMethod 호출 2");
  
               return obj;
       }


       // before advice
       @Override
       public void beforeAdvice() {
              System.out.println("1단계:");
              System.out.println("beforeMethod 호출");
  
       }


       // after
       @Override
       public void afterAdvice() {
              System.out.println("5단계:");
              System.out.println("afterMethod 호출");
  
       }


       // afterThrowing
      @Override
      public void afterThrowing() {
              System.out.println("4단계:");
              System.out.println("afterThrowing 호출");
      }

      // afterReturning
      @Override
      public void afterReturning() {
              System.out.println("3단계:");
              System.out.println("afterReturning 호출");
  
      }
 
}


파일명: LogAdvisorXML.java


[첨부(Attachments)]

LogAdvisorXML.zip


LogAdvisor.zip




9. TestMain.java - com.website.example.unit


package com.website.example.unit;


import org.junit.jupiter.api.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.website.example.test.ResultAOP;


public class TestMain {

      
       // 사용방법1
       @Test
       public void sample() {

                AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
   
                ResultAOP rAOP = (ResultAOP) factory.getBean("resultAOP");

                rAOP.method1();
  
                factory.close();
       }
 

       // 사용방법2
       @Test
       public void sample2() {

               ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
               ResultAOP rAOP = ctx.getBean("resultAOP", ResultAOP.class);
               //rAOP.method1();
  
               ctx.close();
       }
 
}


파일명: TestMain.java


[첨부(Attachments)]

TestMain.zip




10. 동작 결과


구현한 코드의 동작 결과이다.



그림 3. 출력 결과 1



그림 4. 출력 결과 2




11. 맺음글(Conclusion)


직관적으로 AOP를 아주 쉽게 이해할 수 있도록 작성하였다.

반응형

+ Recent posts