https://engkimbs.tistory.com/746

 

[Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP

| 스프링 AOP ( Aspect Oriented Programming ) AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으..

engkimbs.tistory.com

 

OOP(자바의 기본방식 :  각체지향 ) 에서 Aspect 만 바뀌였다.

특정 한 타이밍에 어느 위치에 어떤 메소드를 실행 시킬 것인지에 대한 프로그래밍 기법

 

 Aspect: 기존 객체 지향 프로그램에서 모듈화 기법으로

추가된 중복 코드(공통 처리 부분)을 별도의 독립된 클래스(Aspect)로 만들어 놓은 것

 

AOP : 공통(공용), 공유 할 내용, 일종의 기법

 

핵심모듈 : 반드시 구현할 기능

예) 로그인 여부 확인(회원 수정, 삭제 등에 필요함)

 

위와 같은 기능을 할 코드들을 따로 클래스로 만들어준다.

이것을 공통 관심사 모듈(Aspect)이라고 한다.

 

공통 관심사 모듈

예) 로그인 처리부분, 보안, 공통으로 사용할 수 없는 코딩

-> 스프링에서는 Aspect는 Advice라고 한다. Angular에서는 서비스라고 한다.

 

AOP는 언제 불러다 사용할 것이고, 언제 실행 할 것인지가 중요하다.

이렇게 실행 위치를 지정해주는 것은 joinPoint, PointCut라고 한다.

 

JoinPoint :  실행 위치를 지정(실행 위치만 지정한다.)

예) before, after

 

PointCut : 어떤 메소드 앞, 뒤에서 실행을 지정

어느 메소드를 사용할 것인지도 같이 설정해준다.

ex) before에 startMethod 실행지정.

 

이러한 지정 법은 Weaving이라고 한다.

핵심클래스의 특정위치에 넣어주는 것

 

이러한 모든 것을 통틀어 Aspect라고 한다.

 

또한 Aspect와 Advice를 합쳐진 것을 보고 Advisior라고도 한다.


AOP를 실행 시키는 방법

 

1. 프록시 기반의 AOP지원(AOP객체를 생성해주는 클래스)

default(메소드 중심)->빈즈 클래스로 구성.

2. <aop:config>태그를 사용

3. AspectJ이용 (@Aspect Annotation을 이용하는 방법)

 

프로젝트 생성하기

맨 밑에 있는 mvc를 선택해도 되지만(Spring MVC Project),

자동으로 만들어주는 프로젝트이기 때문에 수정하기가 쉽지않다.

따라서, 수동으로 프로젝트를 설계하기 위해 위와 같은 프로젝트로 설정한다.

 

프로젝트 생성시 체크해볼 것

 


POM.xml 수정하기

 

AOP -> springFrame(스프링 프레임워크이 필요하고, 스프링-익스프레션, 스프링-빈즈 라는 라이브러리가 필요하다.)

AOP연습에서는 위와 같은 lib가 필요하다.

 

변경 전

<?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.dod</groupId>
	<artifactId>dko</artifactId>
	<name>fvdsafds</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>3.1.1.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>
	
		<!-- 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>

 

변경 후

<?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.hjs</groupId>
   <artifactId>springAop</artifactId>
   <name>SpringAop</name>
   <packaging>war</packaging>
   <version>1.0.0-BUILD-SNAPSHOT</version>
   <properties>
      <!-- Generic properties -->
      <java-version>1.6</java-version>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      
      <!-- Spring -->
      <org.springframework-version>4.2.5.RELEASE</org.springframework-version>
      
      
      
      <!-- Hibernate / JPA -->
      <hibernate.version>4.2.1.Final</hibernate.version>
      
      <!-- Logging -->
      <logback.version>1.0.13</logback.version>
      <slf4j.version>1.7.5</slf4j.version>
      
      <!-- Test -->
      <junit.version>4.11</junit.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>
      
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-expression</artifactId>
         <version>${org.springframework-version}</version>
      </dependency>
      
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-beans</artifactId>
         <version>${org.springframework-version}</version>
      </dependency>
   </dependencies>
</project>

샘플 스프링 메이븐으로 만들어보기

 

샘플 스프링 메이븐으로 만들게 되면 pom.xml의 내용이 필요한 내용만 들어가게 된다.

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>AOPSpring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>

		<!-- Generic properties -->
		<java.version>1.6</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

		<!-- Spring -->
		<spring-framework.version>3.2.3.RELEASE</spring-framework.version>

		<!-- Hibernate / JPA -->
		<hibernate.version>4.2.1.Final</hibernate.version>

		<!-- Logging -->
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>

		<!-- Test -->
		<junit.version>4.11</junit.version>

	</properties>
	
	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Logging with SLF4J & LogBack -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>

		<!-- Hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		
		<!-- Test Artifacts -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring-framework.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>	
</project>

https://blog.naver.com/hojysoo/221971341519

 

20200520 - 스프링 프로젝트 관점지향 프로그래밍 (AOP - Aspect Oriented Programming)

OOP에서 Aspect만 바뀌었다.​특정 한 타이밍에 어느 위치에 어떤 메서드를 실행시킬 것인지에 대한 프...

blog.naver.com

버전 정보를 수정해준다.

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>AOPSpring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>

		<!-- Generic properties -->
		<java.version>1.6</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

		<!-- Spring -->
		<spring-framework.version>4.2.5.RELEASE</spring-framework.version>

		<!-- Hibernate / JPA -->
		<hibernate.version>4.2.1.Final</hibernate.version>

		<!-- Logging -->
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>

		<!-- Test -->
		<junit.version>4.11</junit.version>

	</properties>
	
	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Logging with SLF4J & LogBack -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>

		<!-- Hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		
		<!-- Test Artifacts -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring-framework.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

	
	<!-- 1. 핵심 Class 빈즈 등록 -->
<bean id="testServiceImpl" class="sp.aop.TestServiceImpl" />

<!-- 2. Advice Class 빈즈 등록 -->
<bean id="beforeLog" class="sp.aop.BeforeLogAdvice" />

<!-- 3.PointCut 생성 -->
<bean id="" class="org.springframework.aop.support.JdkRegexpMethodPointcut" />

	</dependencies>	
</project>

 

package sp.aop;

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice; // 이후라고하면 MethodAfterAdvice 구현받으면 된다.

//추가
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//Advice : 모든 Class에 공통으로 사용할 기능( 소스코드 -> method 중심 )
//interface 대신(OOP 방식) 에 Advice Class 를 작성해서 구현(AOP 방식) -> Aspect

//실행 위치를 특정 Method 의 앞에서 실행 -> MethodBeforeAdvice 를 구현
public class BeforeLogAdvice implements MethodBeforeAdvice { //공통관심 모듈 클래스를 만든다.
	private Log log = LogFactory.getLog(getClass()); //Log 객체를 얻어오는 문장
	
	//1) Spring의 AOP(method 중심) -> 핵심 CLass의 Method
	//2) 생성된 객체를 배열로 받아온다.
	//3) target Class(핵심 Class의 객체를 얻어온다.)
	
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		//TODO auto-generated method stub
		log.info(method.toString()+" Method : "+target+"에서 호출 전!");
	}//before() END

}//BeforeLogAdvice CLASS END

 

package sp.aop;
//모든 핵심 class 에서 공통으로 사용할 목적으로의 Method 작성, 인터페이스는 메소드 옆에 {} 지정하면 안된다.
public interface TestService {
    // 해당 메소드들은 public abstract void 와 같이 추상메소드로 작성된다.
	public void save(String msg);// 입력
	public void write(); //출력
}//TestService INTERFACE END

 

 

app.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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">


<!-- 1. 핵심 Class 빈즈 등록 -->
<bean id="testServiceImpl" class="sp.aop.TestServiceImpl" />

<!-- 2. Advice Class 빈즈 등록 -->
<bean id="beforeLog" class="sp.aop.BeforeLogAdvice" />


<!-- 3.PointCut 생성 -> 어느 위치에서 AOP Method 를 지정해서 실행
	value="접근 지정 반환명 package 명... Class 명 하위  package 명(..) 특정 Method명 0개 이상"
	        (*) 매개변수 한 개 표시, (*,*) 매개변수 2개 표시 -->
	        
<bean id="writePointcut"
 class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="pattern" value=".*write.*" />
</bean>


<!-- 4. Advice + PointCut(Advisor) 설정 -->
<bean id="testAdvisor" 
class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="beforeLog" /> 
	<property name="pointcut" ref="writePointcut" />
</bean>


</beans>

package sp.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; // xml문서를 찾을 수 있게 하는 lib

public class ResultMain {

	public static void main(String[] args) {
		String path="sp/aop/app.xml";
		ApplicationContext context = new ClassPathXmlApplicationContext(path);
		
		//TestService service= (TestService)context.getBean("testServiceImpl");
		//원래는 위의 것이 일반적이지만, AOP 객체를 얻어오기 위해서는, 밑에처럼 AOP객체를 얻는다.
		
		//AOP객체를 생성 -> Advisor 작동 -> Advice + pointcut 실행
		TestService service = (TestService)context.getBean("testService");
		service.save("AOP 적용 연습");
		//before advice(before() 작동 실행) -> 실행상태에서 처리
		service.write();
		
	}//main()END

}//CLASS END

 

 

 

package sp.aop;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class AfterLogAdvice implements AfterReturningAdvice {

	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		// TODO Auto-generated method stub
		 System.out.println(method.toString()+"method:"+target+"에서 호출 후!");
	}

  /* @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
      
       * 1. 추가된 객체
       * 2.핵심클래스의 method명
       * 3.생성된 객체들
       * 4.targetClass의 객체
       
      System.out.println(method.toString()+"method:"+target+"에서 호출 후!");
   }*/

}

app.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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">


<!-- 1. 핵심 Class 빈즈 등록 -->
<bean id="testServiceImpl" class="sp.aop.TestServiceImpl" />

<!-- 2. Advice Class 빈즈 등록 -->
<bean id="beforeLog" class="sp.aop.BeforeLogAdvice" />
<bean id="AfterLog" class="sp.aop.AfterLogAdvice" />


<!-- 3.PointCut 생성 -> 어느 위치에서 AOP Method 를 지정해서 실행
	value="접근 지정 반환명 package 명... Class 명 하위  package 명(..) 특정 Method명 0개 이상"
	        (*) 매개변수 한 개 표시, (*,*) 매개변수 2개 표시 -->
	        
<bean id="writePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="pattern" value=".*write.*" />
</bean>

<bean id="savePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="pattern" value=".*save.*" />
</bean>


<!-- 4. Advice + PointCut(Advisor) 설정 -->
<bean id="testAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="beforeLog" />
	<property name="pointcut" ref="writePointcut" />
</bean>

<!-- 4-1. Advice + PointCut(Advisor) 설정 -->
<bean id="testAfterAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="AfterLog" />
	<property name="pointcut" ref="savePointcut" />
</bean>

<!-- 5. AOP를 적용(ProxyFactoryBean 객체를 생성) target(핵심 Class) -->
<bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="testServiceImpl" />
	<property name="interceptorNames">
		<list>
			<value>testAdvisor</value>
			<value>testAfterAdvisor</value>
		</list>
	</property>
</bean>
<!-- ///////END AOP 환경설정 부분 -->

</beans>
package com.ohj.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
	
	
	// MockMvc : 가짜  MVC
	@Setter(onMethod_= {@Autowired})
	private WebApplicationContext ctx;
	private MockMvc mockMvc;
	
	//무조건 실행하는 메소드
	//@Before : import 할 때  JUnit을 이용.
	//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
	
   @Before
   public void setup() {
      this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
   }

   //MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
   @Test
   public void testList() throws Exception{
	   
	   //BoardController.java쪽 
      log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
   }
   
	@Test
	public void testRegister() throws Exception{
		String resultPage= mockMvc
				.perform(MockMvcRequestBuilders.post("/board/register")
				.param("title", "테스트 새글 제목")
				.param("content", "테스트 새글 내용")
				.param("writer", "user00"))
				.andReturn().getModelAndView().getViewName();
		log.info(resultPage);
	}

}
​
package com.ohj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
	
	private BoardService service; 
	
	@GetMapping("/list")
	public void list(Model model) {
		log.info("list");
		model.addAttribute("list", service.getList());
	}// list() END
	// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
	
	//등록처리 와 테스트
	
	/*public BoardController()에 POST방식으로 처리 되는 register()추가
	리턴 타입 : String
	리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 
	추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
	
	리턴 할 떄에는 'redirect:'라는 접두어를 사용!
	스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
	*/
	@PostMapping("register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		log.info("register:"+board);
		service.register(board);
		rttr.addFlashAttribute("result",board.getBno());
		return "redirect:/board/list";
	}
	
}//BoardController CLASS END

//   BoardControllerTests :  
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());​
스프링 MVC(기본프레임워크) + mybatis(DB프레임워크)

주제 : 게시물 관리

작업순서
1. 요구분석(요구사항) - Naming/내부구조(비즈니스영역(로직관련/ Service)/영속영역(데이터관련/domain,mapper)/프레젠테이션영역(사용자UI관련))
2. 개발환경 구축
3. 플랫폼 구축 (스프링 MVC 프로젝트 생성)
4. 데이터베이스 테이블
5. 데이터베이스 연결 테스트
6. 영속 계층 테스트
7. 비즈니스 계층 테스트


1. 요구 분석 : CRUD 기능 구현

2.JDK 1.8 이상(JAVA(jdk(환경변수(user/system(path)) 잡기)/jre)
*톰캣과 오라클은 8080 포트를 공통으로 잡고 있기 때문에 포트번호를 각각 따로 설정해준다.
오라클 11g EX (c:\에 다운)
톰캣 9.0 이상 (c:\에 다운)
이클립스 + STS플러그인(마켓플레이스에서 다운 가능)
마이바티스 플러그인(마켓플레이스에서 다운 가능)/workspace(인코딩 UTF-8설정해주기)

3.pom.xml
Maven을 위한 통합 버전 관리
스프링 5.3 아성
자바 1.8 이상
Log4j(콘솔창 로그 찍어주는 것/log.info(""+__))/Juinit(단위별 테스트/@Test)

4. 오라클
tbl_board
bno(PK(Number))
titie(varchar2)
content(varchar2)
writer(varchar2)
regdate(Date(default sysdate))
updatedate(Date(default sysdate))

폴더 구조

오라클 - 오라클디벨로퍼
		- cmd
1. 계정/비밀번호 생성
2. 접속/ 리소스 원한 설정
3. 테이블 생성
bno :Pk,title,writer,contents,regdate(등록날짜),updatedate(수정날짜)
4. dummy data
5.데이터베이스 연결테스트 
- (root-context.xml 수집
1.하카리 환경설정(*참고 : JDBC에 비해 약 15~30% 빠른 처리 속도를 가짐) 
2.데이터소스 객체
3.sqlsessionfactory 객체
4.마이바티스 관련 파일 위치)

6.영속 계층 테스트
7.비즈니스 계층 테스트
lombok
-@setter
-@AllArgconstructher


*lombok.jar
java jar lombok.jar
Java Bean  형태의 객체 생성시
1. getter/setter(@setter)
2. 생성자(@AllArgconstructher)
3. toString() wowjddml
Test용 클래스
1.
@단위테스트
@Context 환경 파일 위치
@log 출력
public class __ {
	@setter(onMethod = Autowired)
    2. 멤버변수
    
	3.실제테스트 메서드(){
    @test
	}
}

 

5. 영속(persistance) 계층 테스트 CRUD
	1. com.~.mapper
    	1.BoardMapper.java
        2.BoardMapper.xml->mybatis <- xmlns:__/(@ ex)Select / <> ex)<Select>)
        <?xml~?>
        <mapper>
        
        </mapper>

 

https://codedragon.tistory.com/6467

 

데이터 저장계층 또는 영속계층(Persistence Layer)에 대한 이해 - DAO(Data Acess Object), DTO(Data Transfer Objec

데이터 저장계층 또는 영속계층(Persistence Layer)에 대한 이해 DAO/DTO/VO란, 영속계층(Persistence Layer)에서 사용되는 특정 패턴을 통해 구현되는 Java Bean 용어 설명 DAO (Data Acess Object) · 특정 타입..

codedragon.tistory.com

 

프레젠테이션(웹) 계층의 CRUD 구현

 

1.Controller 의 작성

 

스프링 MVC의 Controller는

하나의 클래스 내에서 여러 매서드를 작성하고,

@RequestMapping등을 이용하여

URLdf 분기하는 구조로 작성 할 수 있기 때문에

 

하나의 클래스에서 필요한 만큼

매서드의 분기를 이용하는 구조로 작성.

 

 

이전 방식 :  톰캣(WAS)을 실행하고 웹화면을 만들어서 결과를 확인하는 방식의 코드를 작성

이전 방식의 단점 : 오래 걸리고, 테스트를 자동화 하기가 어렵다.

WAS를 실행하지 않고 컨트롤러를 테스트 할 수 있는 방법으로 진행

 

1.BoardController의 분석

작성하기 전에 이루어지는 사전 작업

반드시 현재 원하는 기능을 호출하는 방식에 대하여

테이블(표)로 정리한 후 , 작성

테이블에서 FROM 항목은 해당 url 호출하기 위해 별도의 입력 화면이 필요하다는 것을 의미 

 

1.BoardController의 작성

 

1.BoardController는 com.이니셜.controller 패키지에 선언

URL 분석된 내용들을 반영하는 메서드 설계

 

@Controller 어노테이션을 추가 하여 스프링의 빈으로 인식할 수 있도록한ㄷ.

@RequestMapping 을 통하여 '/board' 로 시작하는 모든 처리를 가 담당하도록 설정www.servlet-context.xmlwww.servlet-context.xml 에 기본적으로 설정되어 있으므로 별도의 설정이 필요없다.

 

보드컨트롤러는 보드서비스에 대하여 의존적이므로

@AllArgsConstructor 를 이용하여 생성자를 만들고, 자동주입

클래스 선언부에 어노테이션 추가
BoardController 는 BoardService에 대하여 의존적이므로
@AllArgsConstructor 를 이용하여 생성자를 만들고, 자동 주입


만약, 생성자를 만들지 않을 경우에는 반드시
@Setter(onMethod_ = {@Autowired} 를 이용하여 처리

 

 

 

조회를 위한 list() 메소드 추가

 

list()는 나중에 게시물의 목록을 전달해야 하므로 Model객체를 파라메터를 통하여

BoardServiceImpl객체의 getList()결과를 담아서 전달.

 

package com.ohj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.ohj.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
	
	private BoardService service;
	
	@GetMapping("/list")
	public void list(Model model) {
		log.info("list");
		model.addAttribute("list",service.getList());
	}
}

 

 

테스트 코드는
웹을 개발하면서 매번 URL을 테스트하기 위해
Tomcat과 같은 WAS를 실행하는 불편한 단계를 생략할 수 있도록 코드를 작성.

이 때 필요한 클래스
- WebAppConfiguration
- WebApplicationContext

- MockMvc (가짜 예) 핸드폰 )
- MockMvcBuilders
- MockMvcRequestBuilders

 

 

package com.ohj.controller;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

public class BoardControllerTests {

}

 

클래스 선언 부에 어노테이션 추가

@WebAppConfiguration : 서블릿의 ServletContext를 이용하기 위함.

즉, 스프링에서는 WebAppConfiguration 라는 객체를 이용하기 위함이다.

 

의존성 주입을 위함 멤버(인스턴스) 변수 및 메서드 추가

 

MokMvc: 가짜 MVC

가상으로 URL과 파라미터등을 마치 브라우저에서 사용하는 것처럼 만들어서 컨트롤러를 실행 해 볼 수 있다.

 

 

 

등록처리 와 테스트

public BoardController()에 POST방식으로 처리 되는 register()추가
리턴 타입 : String
리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 
추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.

리턴 할 떄에는 'redirect:'라는 접두어를 사용!
스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.

 

테스트 코드

의 post() : POST방식으로 데이터를 전달 할 수 있다.

param(): 전달해야 하는 파라미터들을 지정 할 수 있다. 마치 <input> 처럼

 

이와 같은 방식으로 코드를 작성하면 

최초 작성시에는 일이 많게 느껴지지만,

오류 매번 입력할 필요가 없기 때문에 오류 발생 및 수정하는 경우,
반복적인 테스트가 수월함

 

package com.ohj.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)


//Test for Controller
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", 
	"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
	
	
	// MockMvc : 가짜  MVC
	@Setter(onMethod_= {@Autowired})
	private WebApplicationContext ctx;
	private MockMvc mockMvc;
	
	//무조건 실행하는 메소드
	//@Before : import 할 때  JUnit을 이용.
	//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
	
   @Before
   public void setup() {
      this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
   }

   //MockMvcRequestBuilders 를 이용하면 마치 GET방식의 호출을 할 수 있다.
   @Test
   public void testList() throws Exception{
      log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
   }


}

 

 

상세보기

조회 처리와 테스트

@GetMapping : 특별한 경우가 아니라면, 조회는 GET방식으로 처리

@RequestParam : bno 값을 좀 더 명시적으로 처리하기 위함

Model 파라미터 :  화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함

 

package com.ohj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
	
	private BoardService service; 
	
	@GetMapping("/list")
	public void list(Model model) {
		log.info("list");
		model.addAttribute("list", service.getList());
	}// list() END
	// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
	
	//등록처리 와 테스트
	
	/*public BoardController()에 POST방식으로 처리 되는 register()추가
	리턴 타입 : String
	리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 
	추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
	
	리턴 할 떄에는 'redirect:'라는 접두어를 사용!
	스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
	*/
	@PostMapping("register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		log.info("register:"+board);
		service.register(board);
		rttr.addFlashAttribute("result",board.getBno());
		return "redirect:/board/list";
	}
	
	// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
	@GetMapping("/get")
	public void get(@RequestParam("bno") Long bno, Model model) {
		log.info("/get");
	    model.addAttribute("board",service.get(bno));
		
	}
	
}//BoardController CLASS END


//   BoardControllerTests :  
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());

 

package com.ohj.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
	
	
	// MockMvc : 가짜  MVC
	@Setter(onMethod_= {@Autowired})
	private WebApplicationContext ctx;
	private MockMvc mockMvc;
	
	//무조건 실행하는 메소드
	//@Before : import 할 때  JUnit을 이용.
	//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
	
   @Before
   public void setup() {
      this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
   }

   //MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
   @Test
   public void testList() throws Exception{
	   
	   //BoardController.java쪽 
      log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
   }
   
	@Test
	public void testRegister() throws Exception{
		String resultPage= mockMvc
				.perform(MockMvcRequestBuilders.post("/board/register")
				.param("title", "테스트 새글 제목")
				.param("content", "테스트 새글 내용")
				.param("writer", "user00"))
				.andReturn().getModelAndView().getViewName();
		log.info(resultPage);
	}
	
	
	@Test
	public void testGet() throws Exception{
		log.info(mockMvc.perform(MockMvcRequestBuilders
				.get("/board/get")
				.param("bno","2"))
				.andReturn()
				.getModelAndView().getModelMap()
				);
	}

}

 

수정 처리와 테스트

변경된 내용을 수집해서 BoardVO파라미터로 처리한 후

BoardService를 호출

 

수정 작업을 시작하는 화면의 경우 GET방식으로 접금하지만

실제 작업은 PoST 방식으로 동작하므로  @PostMapping을 이용하여 처리!

 

service.modify()는 수정 여부를 boolean으로 처리하므로

성공한 경우에만 RedirectArrtibutes에 추가

 

 

 

package com.ohj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
	
	private BoardService service; 
	
	@GetMapping("/list")
	public void list(Model model) {
		log.info("list");
		model.addAttribute("list", service.getList());
	}// list() END
	// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
	
	//등록처리 와 테스트
	
	/*public BoardController()에 POST방식으로 처리 되는 register()추가
	리턴 타입 : String
	리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 
	추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
	
	리턴 할 떄에는 'redirect:'라는 접두어를 사용!
	스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
	*/
	@PostMapping("register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		log.info("register:"+board);
		service.register(board);
		rttr.addFlashAttribute("result",board.getBno());
		return "redirect:/board/list";
	}
	
	// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
	@GetMapping("/get")
	public void get(@RequestParam("bno") Long bno, Model model) {
		log.info("/get");
	    model.addAttribute("board",service.get(bno));
		}
	

	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr) {
		log.info("modify:"+board);
		if(service.modify(board)) {
			rttr.addFlashAttribute("result","success");
		}
		return "redirect:/board/list";
	}
	
	
}//BoardController CLASS END


//   BoardControllerTests :  
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
package com.ohj.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
	
	
	// MockMvc : 가짜  MVC
	@Setter(onMethod_= {@Autowired})
	private WebApplicationContext ctx;
	private MockMvc mockMvc;
	
	//무조건 실행하는 메소드
	//@Before : import 할 때  JUnit을 이용.
	//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
	
   @Before
   public void setup() {
      this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
   }

   //MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
   @Test
   public void testList() throws Exception{
	   
	   //BoardController.java쪽 
      log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
   }
   
	@Test
	public void testRegister() throws Exception{
		String resultPage= mockMvc
				.perform(MockMvcRequestBuilders.post("/board/register")
				.param("title", "테스트 새글 제목")
				.param("content", "테스트 새글 내용")
				.param("writer", "user00"))
				.andReturn().getModelAndView().getViewName();
		log.info(resultPage);
	}
	
	
	@Test
	public void testGet() throws Exception{
		log.info(mockMvc.perform(MockMvcRequestBuilders
				.get("/board/get")
				.param("bno","2"))
				.andReturn()
				.getModelAndView().getModelMap()
				);
	}
	
	@Test
	public void testModify() throws Exception{ 
		String resultPage= mockMvc.perform(MockMvcRequestBuilders
				.post("/board/modify")
				.param("bno", "1")
				.param("title", "테스트 수정된 제목")
				.param("content", "테스트 수정된 내용")
				.param("writer", "user00"))
				.andReturn()
				.getModelAndView()
				.getViewName();
	}

}

 

삭제 처리와 테스트

삭제는 반드시 POST방식으로만 처리

 

 

package com.ohj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
	
	private BoardService service; 
	
	@GetMapping("/list")
	public void list(Model model) {
		log.info("list");
		model.addAttribute("list", service.getList());
	}// list() END
	// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
	
	//등록처리 와 테스트
	
	/*public BoardController()에 POST방식으로 처리 되는 register()추가
	리턴 타입 : String
	리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 
	추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
	
	리턴 할 떄에는 'redirect:'라는 접두어를 사용!
	스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
	*/
	@PostMapping("register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		log.info("register:"+board);
		service.register(board);
		rttr.addFlashAttribute("result",board.getBno());
		return "redirect:/board/list";
	}
	
	// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
	@GetMapping("/get")
	public void get(@RequestParam("bno") Long bno, Model model) {
		log.info("/get");
	    model.addAttribute("board",service.get(bno));
		}
	

	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr) {
		log.info("modify:"+board);
		if(service.modify(board)) {
			rttr.addFlashAttribute("result","success");
		}
		return "redirect:/board/list";
	}
	
	
	@PostMapping("/remove")
	public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
		log.info("remove:"+bno);
		if(service.remove(bno)) {
			rttr.addFlashAttribute("result","success");
		}
		return "redirect:/board/list";
	}
	
	
}//BoardController CLASS END


//   BoardControllerTests :  
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());

 

 

 

https://bactoria.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-addAttribute-addFlashAttribute-%EC%B0%A8%EC%9D%B4%EC%A0%90-RedirectAttributes-rttr-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8

 

스프링 addAttribute addFlashAttribute 차이점 (RedirectAttributes rttr) / 리다이렉트

addAttribute와 addFlashAttribute의 차이점 addAttribute는 삭제를 했을 때 url 을 보자. redirect는 list까지다. 근데 page , perPageNum, searchType, keyword 가 뒤에 따라 붙었다. rttr.addAttribute 때문..

bactoria.tistory.com

 

영속성 비즈니스 계층

테이블의 컬럼 구조를 반영하는 VO(Value Object)

1. VO클래스 작성

테이블 설계를 기준으로 작성

영속성_비즈니스 계층

테이블의 컬럼 구조를 반영하는 VO(Value Object) 클래스 생성
MyBatis의 Mapper 인터페이스의 작성 처리 및 XML 처리
작성한 Mapper 인터페이스의 테스트

위의 과정 전에
먼저 JDBC 연결을 테스트하는 과정을 반드시 거친다.

1. 영속 계층의 구현 준비

거의 모든 웹 애플리케이션의 최종 목적은
데이터 베이스에 데이터를 기록하거나,
원하는 데이터를 가져오는 것이 목적이기 때문

2. Mapper 인터페이스의 Mapper XML 

MyBatis 는 SQL을 처리하는데, 어노테이션이나 XML을 이용할 수 있다. 

간단한 SQL일 경우 : 어노테이션을 이용하여 처리 

복잡하거나 검색 SQL : XML로 처리. 

XML의 경우 
단순 텍스트를 수정하는 과정만으로 처리가 끝나지만, 

어노테이션의 경우 
코드를 수정하고, 다시 빌드하는 등, 유지 보수성이 떨어진다. 

 

 

//BoardVO.java

package com.ohj.domain;

import java.util.Date;
import lombok.Data;

@Data
public class BoardVO {
   
   private Long bno;
   private String title;
   private String content;
   private String writer;
   private Date regdate;
   private Date updateDate;

}

 

 

Mapper 만들기

 

//인터페이스  BoardMapper

package com.ohj.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;


public interface BoardMapper {
   
   @Select("select * from tbl_board where bno > 0")
   public List<BoardVO> getList();
   
}

BoardMapper 인터페이스 작성시,

필요한 SQL을 어노테이션의 속성값으로 처리 할 수 있다.

주의사항 : SQL 작성할 때 ';' 이 없도록 작성 해야한다.BoardMapper 인터페이스 작성 시,

필요한 SQL을 어노테이션의 속성값으로 처리할 수 있다.

주의사항 : SQL 작성할 때 ';' 이 없도록 작성해야 한다.

select문 뒤에 bno 컬럼 조건을 주어서 Primary key(PK)를 이용하도록 한다.

SQL Developer 에서 먼저
SQL이 문제 없이 실행 가능한 지를 확인
데이터 베이스의 commit 여부를 확인.

 

 

실제 작업하기 전 test 코드를 만들어 본다.

BoardMapperTests 클래스는 

스프링을 이용하여 

 

실제 작업하기 전 test 코드를 만들어 본다.

package com.ohj.Mapper;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.mapper.BoardMapper;

//lombok : 선언해준 겟터셋터를 자동으로 만들어주고 tostring을 자동으로 만들어주고 생성자도 생성해준다.
import lombok.Setter;
import lombok.extern.log4j.Log4j;

//테스트 및 주입을 위한 어노테이션
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
	
	@Setter(onMethod_=@Autowired)
	BoardMapper mapper;
	
	@Test
	public void testGetList() {
		//forEach :리스트를 반환해줌
      mapper.getList().forEach(board -> log.info(board));
      
      
	}// testGetList END

}//BoardMapperTests END

실행해보기

콘솔창에 결과 값이 나온다.

 

Mybatis 설정 및 생성하기

 

MyBatis를 설치해준다.

 

가장 기본적인 xml 문서를 만들어주는 것이다(mybatis 관련)
generatorConfig -> BoardMapper 로 바꿔준다.
만들어진 결과물

 

결과 코드를 수정해준다.
불필요한 코드를 삭제 해준다 (주석을 건 내용은 예시문)

 

XML을 작성할 때 주의 사항

<mapper> 의 namespace 속성 값 : Mapper 인터페이스와 동일한 이름
<select> 태그의 id 속성 값 : 메서드 이름과 일치.
resultType 속성 값 : select 쿼링 결과를 특정 클래스의 객체로 자동 생성하기 위함

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper 
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<generatorConfiguration>
  <context id="context1">
    <jdbcConnection connectionURL="???" driverClass="???" password="???" userId="???" />
    <javaModelGenerator targetPackage="???" targetProject="???" />
    <javaClientGenerator targetPackage="???" targetProject="???" type="XMLMAPPER" />
    <table schema="???" tableName="???">
      <columnOverride column="???" property="???" />
    </table>
  </context>
</generatorConfiguration> -->

 // 내부에서 사용할 메서드는 여기있다고 알려주고
<mapper namespace = "com.ohj.mapper.BoardMapper">
	// 메서드명과 id가 동일, resultType은 리턴 타입과 동일(BoardVO)
	<select id = "getList" resultType="com.ohj.domain.BoardVO">
   		 // 시작태그와 닫는태그의 기호가 <> 이기 때문에 부호표시를 위해 CDATA로 묶어놓은 것
		<![CDATA[
			select * from tbl_board where bno > 0
		]]>
	</select>

</mapper>

 

generatorConfiguration.xml에 설정해 놨으므로 해당 내용들은 주석 처리 해준다. (이름을 BoardMapper 바꿔준다.)

히카리->JDBC 코드를 단축(커넥션 풀)
마이바티스 - > 쿼리문을 XML에 정리

 

tbl_board 테이블은 PK칼럼으로 bno를 이용하고,

시퀀스를 이용하여

데이터가 추가 될때 마다 자동으로 번호가 만들어지는 방식을 사용.

이처럼 자동으로 PK값이 정해지는 경우 처리 방법

insert만 처리 되고 생성된 px값을 알 팔요가 없는 경우

insert문이 실행되고 생성 된 PK값을 알아야 할 경우

BoardMapper 인터페이스 수정 : 위의 상황을 고려하여 메소드를 추가 선언

 

//BoardMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<generatorConfiguration>
  <context id="context1">
    <jdbcConnection connectionURL="???" driverClass="???" password="???" userId="???" />
    <javaModelGenerator targetPackage="???" targetProject="???" />
    <javaClientGenerator targetPackage="???" targetProject="???" type="XMLMAPPER" />
    <table schema="???" tableName="???">
      <columnOverride column="???" property="???" />
    </table>
  </context>
</generatorConfiguration> -->


<mapper namespace = "com.ohj.mapper.BoardMapper">
	<!-- BoardMapper.java-->
	<select id = "getList" resultType="com.ohj.domain.BoardVO">
		<![CDATA[
			select * from tbl_board where bno > 0
		]]>
	</select>
	
	<insert id="insert">
		insert into tbl_board(bno, title, content, writer)
		values (seq_board.nextval, #{title}, #{content}, #{writer})
	</insert>
	
	<insert id="insertSelectKey">
		<selectKey keyProperty="bno" order="BEFORE" resultType="long">
			select seq_board.nextval from dual
		</selectKey>
	
		insert into tbl_board(bno, title, content, writer)
		values (#{bno}, #{title}, #{content}, #{writer})
	</insert>

</mapper>

 

//BoardMapper.java

package com.ohj.mapper;

import java.util.List;

//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;


public interface BoardMapper {

//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
   public List<BoardVO> getList();
   
   //단순히 insert만 해주는 메소드
   public void insert(BoardVO board);
   //insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
   public void insertSelectKey(BoardVO board);
   
}

 

<insert id="insert"> : 단순히 시퀀스의 다음 값을 구해서 insert 할 때 사용

 

insert into SQL 문:

몇 건의 데이터가 변경 되었는지 만을 알려주기 떄문에

추가된 데이터의 PK값을 알 수는 없지만,

한번의 SQL처리만으로 작업이 완료 된다는 장점이 있다.

 

<insert id="insertSelectKey"> : @SelectKey 라는 어노테이션을 사용.

 

@SelestKey:

주로 PK 값을 미이(before)SQL문을 통하여 처리해두고,

특정한 이름으로 결과를 보관하는 방식

 

@Insert 할 때 SQL 문을 보면 #{bno}와 같이 이미 처리된 결과를 이용.

 

 

테스트 코드의

각 메서드에서 마지막에 log.info(board)를 작성하는 이유:

lombok이 만들어조는 toString()을 이용하여 

bno 멤버 변수(인스턴스 변수)의 값을 알아보기 위함.

 

//BoardMapperTests.java

package com.ohj.mapper;

import org.junit.Test;           // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
   @Setter(onMethod_ = @Autowired)
   BoardMapper mapper;
   
   @Test
   public void testGetList() {
      // 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
      mapper.getList().forEach(board -> log.info(board));  
   }// testGetList END
   
   @Test
   public void testInsert() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글");
	   board.setContent("새로 작성하는 내용");
	   board.setWriter("newbie");
	   
	   mapper.insert(board);
	   
	   log.info(board);
   }//testInsert() END
   
   @Test
   public void testInsertSelectKey() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 SelectKey");
	   board.setContent("새로 작성하는 내용 SelectKey");
	   board.setWriter("newbie");
	   
	   mapper.insertSelectKey(board);
	   
	   //이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
	   log.info(board);
   }//testInsertSelectKey() END
   
   
}// BoardMapperTests END

 

실행 결과

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@1a968a59, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@4667ae56, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@77cd7a0, org.springframework.test.context.support.DirtiesContextTestExecutionListener@204f30ec, org.springframework.test.context.transaction.TransactionalTestExecutionListener@e25b2fe, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@754ba872]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@589838eb, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@42dafa95, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@6500df86, org.springframework.test.context.support.DirtiesContextTestExecutionListener@402a079c, org.springframework.test.context.transaction.TransactionalTestExecutionListener@59ec2012, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@4cf777e8]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
INFO : org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7791a895: startup date [Wed May 13 11:36:14 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : jdbc.connection - 1. Connection opened
INFO : jdbc.audit - 1. Connection.new Connection returned 
INFO : com.zaxxer.hikari.pool.PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy.getNetworkTimeout()I)
INFO : jdbc.audit - 1. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 1. Connection.setAutoCommit(true) returned 
INFO : jdbc.audit - 1. Connection.isValid(1) returned true
INFO : jdbc.audit - 1. Connection.getTransactionIsolation() returned 2
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : jdbc.connection - 2. Connection opened
INFO : jdbc.audit - 2. Connection.new Connection returned 
INFO : jdbc.audit - 2. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 2. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 3. Connection opened
INFO : jdbc.audit - 3. Connection.new Connection returned 
INFO : jdbc.audit - 3. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 3. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 4. Connection opened
INFO : jdbc.audit - 4. Connection.new Connection returned 
INFO : jdbc.audit - 4. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 4. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 5. Connection opened
INFO : jdbc.audit - 5. Connection.new Connection returned 
INFO : jdbc.audit - 5. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 5. Connection.setAutoCommit(true) returned 
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : com.ohj.persistence.DataSourceTests - org.apache.ibatis.session.defaults.DefaultSqlSession@2d6764b2
INFO : com.ohj.persistence.DataSourceTests - HikariProxyConnection@1877062907 wrapping net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@79351f41
INFO : jdbc.connection - 6. Connection opened
INFO : jdbc.audit - 6. Connection.new Connection returned 
INFO : jdbc.audit - 6. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 6. Connection.setAutoCommit(true) returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.connection - 7. Connection opened
INFO : jdbc.audit - 7. Connection.new Connection returned 
INFO : jdbc.audit - 7. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 7. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 8. Connection opened
INFO : jdbc.audit - 8. Connection.new Connection returned 
INFO : jdbc.audit - 8. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 8. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 9. Connection opened
INFO : jdbc.audit - 9. Connection.new Connection returned 
INFO : jdbc.audit - 9. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 9. Connection.setAutoCommit(true) returned 
INFO : jdbc.connection - 10. Connection opened
INFO : jdbc.audit - 10. Connection.new Connection returned 
INFO : jdbc.audit - 10. Connection.setReadOnly(false) returned 
INFO : jdbc.audit - 10. Connection.setAutoCommit(true) returned 
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned 
INFO : jdbc.audit - 1. Connection.prepareStatement(select * from tbl_board where bno > 0) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@7096b474
INFO : jdbc.sqlonly - select * from tbl_board where bno > 0 

INFO : jdbc.sqltiming - select * from tbl_board where bno > 0 
 {executed in 56 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned true
INFO : jdbc.resultset - 1. ResultSet.new ResultSet returned 
INFO : jdbc.audit - 1. PreparedStatement.getResultSet() returned net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy@2f9a01c1
INFO : jdbc.resultset - 1. ResultSet.getMetaData() returned oracle.jdbc.driver.OracleResultSetMetaData@458342d3
INFO : jdbc.resultset - 1. ResultSet.getType() returned 1003
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 1
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user00
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:30.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:30.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 2
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용1
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user01
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 3
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용2
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user02
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 4
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용3
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user03
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 5
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용4
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user04
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultsettable - 
|----|------|--------|-------|----------------------|----------------------|
|bno |title |content |writer |regdate               |updatedate            |
|----|------|--------|-------|----------------------|----------------------|
|1   |테스트제목 |테스트 내용  |user00 |2020-05-12 10:24:30.0 |2020-05-12 10:24:30.0 |
|2   |테스트제목 |테스트 내용1 |user01 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|3   |테스트제목 |테스트 내용2 |user02 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|4   |테스트제목 |테스트 내용3 |user03 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|5   |테스트제목 |테스트 내용4 |user04 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|----|------|--------|-------|----------------------|----------------------|

INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
INFO : jdbc.audit - 1. Connection.getMetaData() returned oracle.jdbc.driver.OracleDatabaseMetaData@665e9289
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=1, title=테스트제목, content=테스트 내용, writer=user00, regdate=Tue May 12 10:24:30 KST 2020, updateDate=Tue May 12 10:24:30 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=2, title=테스트제목, content=테스트 내용1, writer=user01, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=3, title=테스트제목, content=테스트 내용2, writer=user02, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=4, title=테스트제목, content=테스트 내용3, writer=user03, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=5, title=테스트제목, content=테스트 내용4, writer=user04, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned 
INFO : jdbc.audit - 1. Connection.prepareStatement(select seq_board.nextval from dual) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@350ec41e
INFO : jdbc.sqlonly - select seq_board.nextval from dual 

INFO : jdbc.sqltiming - select seq_board.nextval from dual 
 {executed in 8 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned true
INFO : jdbc.resultset - 1. ResultSet.new ResultSet returned 
INFO : jdbc.audit - 1. PreparedStatement.getResultSet() returned net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy@69637b10
INFO : jdbc.resultset - 1. ResultSet.getMetaData() returned oracle.jdbc.driver.OracleResultSetMetaData@71984c3
INFO : jdbc.resultset - 1. ResultSet.getType() returned 1003
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(NEXTVAL) returned 6
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultsettable - 
|--------|
|nextval |
|--------|
|6       |
|--------|

INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
INFO : jdbc.audit - 1. Connection.getMetaData() returned oracle.jdbc.driver.OracleDatabaseMetaData@665e9289
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned 
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board(bno, title, content, writer)
		values (?, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@470a696f
INFO : jdbc.audit - 1. PreparedStatement.setLong(1, 6) returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "새로 작성하는 글 SelectKey") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "새로 작성하는 내용 SelectKey") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(4, "newbie") returned 
INFO : jdbc.sqlonly - insert into tbl_board(bno, title, content, writer) values (6, '새로 작성하는 글 SelectKey', '새로 작성하는 
내용 SelectKey', 'newbie') 

INFO : jdbc.sqltiming - insert into tbl_board(bno, title, content, writer) values (6, '새로 작성하는 글 SelectKey', '새로 작성하는 
내용 SelectKey', 'newbie') 
 {executed in 3 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=6, title=새로 작성하는 글 SelectKey, content=새로 작성하는 내용 SelectKey, writer=newbie, regdate=null, updateDate=null)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned 
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board(bno, title, content, writer)
		values (seq_board.nextval, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@28d6290
INFO : jdbc.audit - 1. PreparedStatement.setString(1, "새로 작성하는 글") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "새로 작성하는 내용") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "newbie") returned 
INFO : jdbc.sqlonly - insert into tbl_board(bno, title, content, writer) values (seq_board.nextval, '새로 작성하는 글', 
'새로 작성하는 내용', 'newbie') 

INFO : jdbc.sqltiming - insert into tbl_board(bno, title, content, writer) values (seq_board.nextval, '새로 작성하는 글', 
'새로 작성하는 내용', 'newbie') 
 {executed in 3 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=null, title=새로 작성하는 글, content=새로 작성하는 내용, writer=newbie, regdate=null, updateDate=null)
INFO : jdbc.connection - 11. Connection opened
INFO : jdbc.audit - 11. Connection.new Connection returned 
INFO : com.ohj.persistence.JDBCTests - net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@77602954
INFO : jdbc.connection - 11. Connection closed
INFO : jdbc.audit - 11. Connection.close() returned 
INFO : org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7791a895: startup date [Wed May 13 11:36:14 KST 2020]; root of context hierarchy
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : jdbc.connection - 1. Connection closed
INFO : jdbc.audit - 1. Connection.close() returned 
INFO : jdbc.connection - 2. Connection closed
INFO : jdbc.audit - 2. Connection.close() returned 
INFO : jdbc.connection - 3. Connection closed
INFO : jdbc.audit - 3. Connection.close() returned 
INFO : jdbc.connection - 4. Connection closed
INFO : jdbc.audit - 4. Connection.close() returned 
INFO : jdbc.connection - 5. Connection closed
INFO : jdbc.audit - 5. Connection.close() returned 
INFO : jdbc.connection - 6. Connection closed
INFO : jdbc.audit - 6. Connection.close() returned 
INFO : jdbc.connection - 7. Connection closed
INFO : jdbc.audit - 7. Connection.close() returned 
INFO : jdbc.connection - 8. Connection closed
INFO : jdbc.audit - 8. Connection.close() returned 
INFO : jdbc.connection - 9. Connection closed
INFO : jdbc.audit - 9. Connection.close() returned 
INFO : jdbc.connection - 10. Connection closed
INFO : jdbc.audit - 10. Connection.close() returned 
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

 

테스트 결과의 마지막을 살펴보면

BoardVO 클래스의 toString()을 이용해 bno멤버변수의값을 알아보기 위한 것이다

 

실행결과를 보면 

'select seq_board.nextval from dual'쿼리가 먼저 실행되고

여기서 생성된 결과를 이용하여

bno값을 처리 되는 것을 볼 수 있다.

 

테스트 코드의 마지막 부분을 보면

BoardVO 객체의 bno값이 지정된 것을 확인할 수 있다.

* 참고 : 시퀸스의 값이므로 테스트 할 때 마다 다른 값이 나온다.

시퀸스 값: 중복 없는 값을 위한 것일 뿐 다른 의미는 없다.

 

@SelectKey를 이용하는 방식은

SQL을 한번더 실행하는 부담이 있기는 하지만,

자동으로 추가되는 PK값을 확인해야하는 상황에서 유용하게 사용될 수 있다.

 

전체 조회 -@어노테이션
		-  XML
레코드 삽입 - xml
			1.insert
            2.미리 PK값을 꺼내서 insert
            
(상세보기(BoardVO 객체를 리턴 받는다), 삭제, 수정)

 

insert가 된 데이터를 조회하는 작업

PK를 이용하여 처리하므로

BoardMapper 의 파라미터 역시 BoardVO 클래스의 bno 타입 정보를 이용하여 작업

//BoardMapper.java

package com.ohj.mapper;

import java.util.List;

//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;


public interface BoardMapper {

//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
   public List<BoardVO> getList();
   
   //단순히 insert만 해주는 메소드
   public void insert(BoardVO board);
   //insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
   public void insertSelectKey(BoardVO board);
   //지정한 글 한 개만 보여주는 메소드
   public BoardVO read(long bno);
   
}

//BoardMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<generatorConfiguration>
  <context id="context1">
    <jdbcConnection connectionURL="???" driverClass="???" password="???" userId="???" />
    <javaModelGenerator targetPackage="???" targetProject="???" />
    <javaClientGenerator targetPackage="???" targetProject="???" type="XMLMAPPER" />
    <table schema="???" tableName="???">
      <columnOverride column="???" property="???" />
    </table>
  </context>
</generatorConfiguration> -->


<mapper namespace = "com.ohj.mapper.BoardMapper">
	<!-- BoardMapper.java-->
	<select id = "getList" resultType="com.ohj.domain.BoardVO">
		<![CDATA[
			select * from tbl_board where bno > 0
		]]>
	</select>
	
	<insert id="insert">
		insert into tbl_board(bno, title, content, writer)
		values (seq_board.nextval, #{title}, #{content}, #{writer})
	</insert>
	
	<insert id="insertSelectKey">
		<selectKey keyProperty="bno" order="BEFORE" resultType="long">
			select seq_board.nextval from dual
		</selectKey>
	
		insert into tbl_board(bno, title, content, writer)
		values (#{bno}, #{title}, #{content}, #{writer})
	</insert>
	
	<select id="read" resultType="com.ohj.domain.BoardVO">
	 select * from tbl_board where bno=#{bno}
	</select>

</mapper>

 

마이바티스는

Mapper인터페이스의 리턴타입에 맞게 select의 결과를 처리하기 때문에

tbl_board 테이블 내 모든 컬럼은 BoardVO의

bno, title, content, writer regdate, updateDate 속성 값으로 처리 된다.

 

마이바티스는 bno라는 컬럼이 존재하면

BoardVO인스튼서의 setBno()를 호출하여 해당 데이터를 세팅한다.

 

마이바티스의 모든 파라미터와 리턴 타입의 처리는

get 파라미터명(), set 컬럼명() 의 규칙으로 호출된다.

테스트 코드(BoardMapperTests.java)

현재 테이블에 존재하는 데이터의 bno 컬럼의 값을 이용해서 확인.

데이터 읽기에 의한 테스트 메서드 추가

//BoardMapperTests.java


package com.ohj.mapper;

import org.junit.Test;           // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
   @Setter(onMethod_ = @Autowired)
   BoardMapper mapper;
   
   @Test
   public void testGetList() {
      // 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
      mapper.getList().forEach(board -> log.info(board));  
   }// testGetList END
   
   @Test
   public void testInsert() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글");
	   board.setContent("새로 작성하는 내용");
	   board.setWriter("newbie");
	   
	   mapper.insert(board);
	   
	   log.info(board);
   }//testInsert() END
   
   @Test
   public void testInsertSelectKey() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 SelectKey");
	   board.setContent("새로 작성하는 내용 SelectKey");
	   board.setWriter("newbie");
	   
	   mapper.insertSelectKey(board);
	   
	   //이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
	   log.info(board);
   }//testInsertSelectKey() END
   
   @Test
   public void testRead() {
	   //존재하는 게시물 번호로 테스트
	   BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
	   log.info(board);
   }
   
   
}// BoardMapperTests END

 

 

삭제하기

 

package com.ohj.mapper;

import java.util.List;

//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;


public interface BoardMapper {

//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
   public List<BoardVO> getList();
   
   //단순히 insert만 해주는 메소드
   public void insert(BoardVO board);
   //insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
   public void insertSelectKey(BoardVO board);
   //지정한 글 한 개만 보여주는 메소드
   public BoardVO read(long bno);
   //삭제하기
   public int delete(long bno);
   
   
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<generatorConfiguration>
  <context id="context1">
    <jdbcConnection connectionURL="???" driverClass="???" password="???" userId="???" />
    <javaModelGenerator targetPackage="???" targetProject="???" />
    <javaClientGenerator targetPackage="???" targetProject="???" type="XMLMAPPER" />
    <table schema="???" tableName="???">
      <columnOverride column="???" property="???" />
    </table>
  </context>
</generatorConfiguration> -->


<mapper namespace = "com.ohj.mapper.BoardMapper">
	<!-- BoardMapper.java-->
	<select id = "getList" resultType="com.ohj.domain.BoardVO">
		<![CDATA[
			select * from tbl_board where bno > 0
		]]>
	</select>
	
	<insert id="insert">
		insert into tbl_board(bno, title, content, writer)
		values (seq_board.nextval, #{title}, #{content}, #{writer})
	</insert>
	
	<insert id="insertSelectKey">
		<selectKey keyProperty="bno" order="BEFORE" resultType="long">
			select seq_board.nextval from dual
		</selectKey>
	
		insert into tbl_board(bno, title, content, writer)
		values (#{bno}, #{title}, #{content}, #{writer})
	</insert>
	
	<select id="read" resultType="com.ohj.domain.BoardVO">
	 select * from tbl_board where bno=#{bno}
	</select>
	
	<delete id="delete">
		delete from tbl_board where bno = #{bno}
	</delete>
	

</mapper>

 

package com.ohj.mapper;

import org.junit.Test;           // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
   @Setter(onMethod_ = @Autowired)
   BoardMapper mapper;
   
   @Test
   public void testGetList() {
      // 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
      mapper.getList().forEach(board -> log.info(board));  
   }// testGetList END
   
   @Test
   public void testInsert() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글");
	   board.setContent("새로 작성하는 내용");
	   board.setWriter("newbie");
	   
	   mapper.insert(board);
	   
	   log.info(board);
   }//testInsert() END
   
   @Test
   public void testInsertSelectKey() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 SelectKey");
	   board.setContent("새로 작성하는 내용 SelectKey");
	   board.setWriter("newbie");
	   
	   mapper.insertSelectKey(board);
	   
	   //이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
	   log.info(board);
   }//testInsertSelectKey() END
   
   @Test
   public void testRead() {
	   //존재하는 게시물 번호로 테스트
	   BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
	   log.info(board);
   }
   
   @Test
   public void testDelete() {
	   log.info("DELETE COUNT:" + mapper.delete(3L));   
   }
   
   
}// BoardMapperTests END

 

수정하기

 

게시물의 업데이트는 제목, 내용, 작성자를 수정한다고 가정

업데이트할 때에는

최종 수정시간을 데이터베이스 내의 현재 시간으로 수정한다

 

업데이트와 딜리트와 마찬가지로 

'몇 개의 데이터가 수정되었는가'를 반환 받을 수 있도록 int 타입으로 메서드의 리턴 값을 설계

 

 

package com.ohj.mapper;

import java.util.List;

//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;


public interface BoardMapper {

//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
   public List<BoardVO> getList();
   
   //단순히 insert만 해주는 메소드
   public void insert(BoardVO board);
   //insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
   public void insertSelectKey(BoardVO board);
   //지정한 글 한 개만 보여주는 메소드
   public BoardVO read(long bno);
   //삭제하기
   public int delete(long bno);
   //수정하기
   public int update(BoardVO board);
   
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<generatorConfiguration>
  <context id="context1">
    <jdbcConnection connectionURL="???" driverClass="???" password="???" userId="???" />
    <javaModelGenerator targetPackage="???" targetProject="???" />
    <javaClientGenerator targetPackage="???" targetProject="???" type="XMLMAPPER" />
    <table schema="???" tableName="???">
      <columnOverride column="???" property="???" />
    </table>
  </context>
</generatorConfiguration> -->


<mapper namespace = "com.ohj.mapper.BoardMapper">
	<!-- BoardMapper.java-->
	<select id = "getList" resultType="com.ohj.domain.BoardVO">
		<![CDATA[
			select * from tbl_board where bno > 0
		]]>
	</select>
	
	<insert id="insert">
		insert into tbl_board(bno, title, content, writer)
		values (seq_board.nextval, #{title}, #{content}, #{writer})
	</insert>
	
	<insert id="insertSelectKey">
		<selectKey keyProperty="bno" order="BEFORE" resultType="long">
			select seq_board.nextval from dual
		</selectKey>
	
		insert into tbl_board(bno, title, content, writer)
		values (#{bno}, #{title}, #{content}, #{writer})
	</insert>
	
	<select id="read" resultType="com.ohj.domain.BoardVO">
	 select * from tbl_board where bno=#{bno}
	</select>
	
	<delete id="delete">
		delete from tbl_board where bno = #{bno}
	</delete>
	
	<update id="update">
		update tbl_board
		set title = #{title}, content = #{content}, writer = #{writer}, updateDate = sysdate
		where bno = #{bno}
	</update>
	

</mapper>

//BoardMapperTests.java

package com.ohj.mapper;

import org.junit.Test;           // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
   @Setter(onMethod_ = @Autowired)
   BoardMapper mapper;
   
   @Test
   public void testGetList() {
      // 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
      mapper.getList().forEach(board -> log.info(board));  
   }// testGetList END
   
   @Test
   public void testInsert() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글");
	   board.setContent("새로 작성하는 내용");
	   board.setWriter("newbie");
	   
	   mapper.insert(board);
	   
	   log.info(board);
   }//testInsert() END
   
   @Test
   public void testInsertSelectKey() {
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 SelectKey");
	   board.setContent("새로 작성하는 내용 SelectKey");
	   board.setWriter("newbie");
	   
	   mapper.insertSelectKey(board);
	   
	   //이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
	   log.info(board);
   }//testInsertSelectKey() END
   
   @Test
   public void testRead() {
	   //존재하는 게시물 번호로 테스트
	   BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
	   log.info(board);
   }
   
   @Test
   public void testDelete() {
	   log.info("DELETE COUNT:" + mapper.delete(3L));   
   }
   
   @Test
   public void testUpdate() {
	   
	   BoardVO board = new BoardVO();
	   board.setBno(5L);
	   board.setTitle("수정된 제목");
	   board.setContent("수정된 내용");
	   board.setWriter("user00");
	   
	   int count = mapper.update(board);
	   log.info("UPDATE COUNT:" + count);   
   }
   
   
}// BoardMapperTests END

 

비즈니스 계층: 로직을 기준으로 해서 처리

 

예) 쇼핑몰에서 상품을 구매한다고 가정

해당 쇼핑몰의 로직 예: 물건을 구매한 회원에게는 포인트를 올려준다

 

영속계층의 설계 : 상품과 회원을 나누어서 설계

비즈니스 계층 설계 : 상품영역과 회원영역을 동시에 사용하여 하나의 로직을 처리

 

 

구매서비스 - 상품 처리 객체

               - 회원 처리 객체

 

현재 단일 테이블을 사용하고 있기 때문에 위와 같은 구조는 아니지만,

설계를 할 때는 원칙적으로 영역을 구분하여 작성

일반적으로 비즈니스 영역에 있는 객체들을 서비스(Service)라는 용어로 사용

 

 

1. 비즈니스 계층의 설정

 

1-1. 비즈니스 계층을 위한 패키지 작성

패키지를 추가

2. 비즈니스 계층 설계

 

각 계층 간의 연결은 인터페이스를 이용하요 느슨한 연결(결합)을 한다.

게시물을

BoardService 인터페이스와 인터페이스를 구현 받는 BoardServicBoardServiceImple를 만들어준다.

BoardService 인터페이스
BoardServiceImpl 

 

 

2. BoardService 인터페이스에 메서드 선언 추가

 

BoardService 매서드 설계시,

메서드 이름은 현실적인 로직의 이름을 사용

명백하게 반환해야하는 데이터가 있는(select)메서드 :  리턴타입을 명시

 

get() : 게시물을 특정한 게시물을 가져오는 메서드

getList(): 전체 리스트를 구하는 메서드.

 

이 두가지 메서드는 처음부터 리턴 타입을 결정하여 진행

 

 

 

package com.ohj.service;

import java.util.List;

import com.ohj.domain.BoardVO;

public interface BoardService {
	
	//BoardMepper.java 와 연결 된다.
	
	public void register(BoardVO board); // 새 글을 등록 할 때 사용
	
	public BoardVO get(Long bno); //내가 지정한 글 번호의 레코드를 꺼내올 때 사용
	
	public boolean modify(BoardVO board); // 기존 글을 수정 할 때 사용
	
	public boolean remove(Long bno); //해당 글을 삭제 할 때
	
	public List<BoardVO> getList();// 전체 데이터를 조회할 때

}

 

BoardService를 저장하는 순간,

BoardServiceImpl에 오류가 발생하면 마우스를 대고 add를 해주면 아래와 같은 코드가 생성 된다.

package com.ohj.service;

import java.util.List;

import com.ohj.domain.BoardVO;

public class BoardServiceImpl implements BoardService {

	public BoardServiceImpl() {
		
	}

	@Override
	public void register(BoardVO board) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public BoardVO get(Long bno) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean modify(BoardVO board) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean remove(Long bno) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public List<BoardVO> getList() {
		// TODO Auto-generated method stub
		return null;
	}

}

 

추가

 

 

 

클래스 선언부에 어노테이션 추가

 

package com.ohj.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired; // 빈을 자동 주입
import org.springframework.stereotype.Service; //역할 알려주는애안에 있는 서비스(Service라고 어노테이션을 달면 얘는 서비스 역할을 하는애다 라고 알려주는 것)

import com.ohj.domain.BoardVO;
//수정 삭제 메소드를 가지고 있음
import com.ohj.mapper.BoardMapper;

//소스트리를 자동으로 만들어줌
//lombok으로 소스들이 매우 줄어들게 됨
import lombok.AllArgsConstructor;// 전체 값을 모두 받는 클래스(생성자)
import lombok.Setter; //toString으로 자동으로 변환해주기 위한 setter import
import lombok.extern.log4j.Log4j; //로그 값 찍어주는 것(콘솔창 찍어주는 것)


@Log4j
@Service
//스프링 4.3 부터는 단일 파라미터를 뱓는 생성자의 경우, 필요한 파라미터를 자동으로 주입 가능.
@AllArgsConstructor // 모든 파라미터를 이용하는 생성자를 생성
public class BoardServiceImpl implements BoardService {

	//비즈니스 계층에서 사용하기 때문에 서비스 어노테이션을 붙여 준다.
	//일을 수행하기 위해 mepper를 주입해야하는데 오토와이어드를 붙여 준다.
	// lombok이 자동 생성자를 생성해주기 때문에 기본 생성자는 없애준다.
	//public BoardServiceImpl() {	
	//}

	@Override
	public void register(BoardVO board) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public BoardVO get(Long bno) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean modify(BoardVO board) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean remove(Long bno) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public List<BoardVO> getList() {
		// TODO Auto-generated method stub
		return null;
	}

}

 

BoardMapper 객체를 위한 멤버(인스턴스) 변수 선언

 

package com.ohj.service;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.springframework.beans.factory.annotation.Autowired; // 빈을 자동 주입
import org.springframework.stereotype.Service; //역할 알려주는애안에 있는 서비스(Service라고 어노테이션을 달면 얘는 서비스 역할을 하는애다 라고 알려주는 것)

import com.ohj.domain.BoardVO;
//수정 삭제 메소드를 가지고 있음
import com.ohj.mapper.BoardMapper;

//소스트리를 자동으로 만들어줌
//lombok으로 소스들이 매우 줄어들게 됨
import lombok.AllArgsConstructor;// 전체 값을 모두 받는 클래스(생성자)
import lombok.Setter; //toString으로 자동으로 변환해주기 위한 setter import
import lombok.extern.log4j.Log4j; //로그 값 찍어주는 것(콘솔창 찍어주는 것)


@Log4j
@Service
//스프링 4.3 부터는 단일 파라미터를 뱓는 생성자의 경우, 필요한 파라미터를 자동으로 주입 가능.
@AllArgsConstructor // 모든 파라미터를 이용하는 생성자를 생성
public class BoardServiceImpl implements BoardService {

	//비즈니스 계층에서 사용하기 때문에 서비스 어노테이션을 붙여 준다.
	//일을 수행하기 위해 mepper를 주입해야하는데 오토와이어드를 붙여 준다.
	// lombok이 자동 생성자를 생성해주기 때문에 기본 생성자는 없애준다.
	//public BoardServiceImpl() {	
	//}
	
	@Setter(onMethod_ = @Autowired)
	private BoardMapper Mapper;

	@Override
	public void register(BoardVO board) {
		log.info("register" + board);
		Mapper.insertSelectKey(board);
		
	}

	@Override
	public BoardVO get(Long bno) {
		log.info("get...." + bno);
		return Mapper.read(bno);
	}

	@Override
	public boolean modify(BoardVO board) {
		log.info("modify...." + board);
		return Mapper.update(board)==1;// 값이 1일 떄 지워준다.
	}

	@Override
	public boolean remove(Long bno) {
		log.info("remove...." + bno);
		return Mapper.delete(bno)==1;
	
	}

	@Override
	public List<BoardVO> getList() {
		log.info("getList....");
		return Mapper.getList();
	}

}

 

spring의 서비스 어노테이션을 빨리 찾기 위해서 위 코드를 넣어준다.

 

 

package com.ohj.service;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
   @Setter(onMethod_ = {@Autowired})
   private BoardService service;
   
   @Test
   public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
      log.info(service);
      assertNotNull(service);
   }//testExist() END 
   
}//BoardServiceTests CLASS END

실행 후 성공 시 콘솔창 

package com.ohj.service;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
   @Setter(onMethod_ = {@Autowired})
   private BoardService service;
   
   @Test
   public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
      log.info(service);
      assertNotNull(service);
   }//testExist() END 
   
   @Test
   public void testRegister() {
	   
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 테스트 ");
	   board.setContent("새로 작성하는 내용 테스트");
	   board.setWriter("newbie test");
	   
	   service.register(board);
	   log.info("생성된 게시물의 번호:" + board.getBno());
   }
   
}//BoardServiceTests CLASS END

package com.ohj.service;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
   @Setter(onMethod_ = {@Autowired})
   private BoardService service;
   
   @Test
   public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
      log.info(service);
      assertNotNull(service);
   }//testExist() END 
   
   @Test
   public void testRegister() {
	   
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 테스트 ");
	   board.setContent("새로 작성하는 내용 테스트");
	   board.setWriter("newbie test");
	   
	   service.register(board);
	   log.info("생성된 게시물의 번호:" + board.getBno());
   }
   
   
   //하나의 글만 가져오는 메소드 테스트
   @Test
   public void testGet() {
	   log.info(service.get(1L));
   }
   
}//BoardServiceTests CLASS END

package com.ohj.service;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ohj.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
   @Setter(onMethod_ = {@Autowired})
   private BoardService service;
   
   @Test
   public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
      log.info(service);
      assertNotNull(service);
   }//testExist() END 
   
   @Test
   public void testRegister() {
	   
	   BoardVO board = new BoardVO();
	   board.setTitle("새로 작성하는 글 테스트 ");
	   board.setContent("새로 작성하는 내용 테스트");
	   board.setWriter("newbie test");
	   
	   service.register(board);
	   log.info("생성된 게시물의 번호:" + board.getBno());
   }
   
   
   //하나의 글만 가져오는 메소드 테스트
   @Test
   public void testGet() {
	   log.info(service.get(1L));
   }
   
   //삭제 테스트
   public void testDelete() {
	   //게시물 번호이 존재여부를 확인 하고 테스트 할 것
	   log.info("REMOVE RESULT:" + service.remove(7L));
   }
  
   //수정 테스트
   public void testUptate() {
	   BoardVO board = service.get(1L);
	   
	   if(board == null) {
		   return;
	   }
	   
	   board.setTitle("제목을 수정테스트 합니다.");

	   log.info("MODIFY RESULT:" + service.modify(board));
   }
   
   
}//BoardServiceTests CLASS END

join sql.txt
0.00MB
ojdbc8.jar
3.97MB

 

오라클 계정 생성 및 설정

 

 

테이블 생성과 Dummy 데이터 생성

 

SQL Developer 를 이용하여 본인의 계정으로 접속하여 테이블 생성.

게시물은 각 게시물 마다 고유의 번호가 필요 오라클의 경우 시퀸스를 이용하여 처리

 

시퀸스를 생성 할 때에는 다른 오브젝트들(테이블 등)과 구별하기 위하여 'seq_'로 시작하거나, 't_'와 같이 구분이 가능한 단어를 앞에 붙여준다.

 

tbl_board 테이블 구성요소

bno : 고유의 번호
title : 제목
content : 내용
writer : 작성자

 

 

테이블을 설계할 때에는

가능하면 레코드의 생성 시간과 최종 수정 시간을 같이 기록하는 것이 일반적

regdate : 생성 시간

updatedate : 최종 수정 시간

 

레코드가 생성 된 시간을 자동으로 기록될 수 있도록 기본 값을 sysdate로 설정

 

PK(Primary Key)를 지정 할 때에는 'pk_'로 시작하는 이름을 붙여주는 것이 일반적.

반드시 의미를 구분 할 수 있도록 생성해준다.

 

테이블 생성
create sequence seq_board;

create table tbl_board (
bno number(10,2),
title varchar2(200) not null,
content varchar2(2000) not null,
writer varchar2(50) not null,
regdate date default sysdate,
updatedate date default sysdate
);

테이블 수정
alter table tbl_board
add constraint pk_board
primary key(bno);

테이블 저장
commit;

 

테이블을 생성하고 나면, 테스트를 위한 여러개의 데이터를 추가하게 되는데, 이런 의미 없는 데이터를 'tpy data' 혹은 'dummy data' 라고 한다.

insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval,'테스트제목', '테스트 내용', 'user00');

insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval,'테스트제목', '테스트 내용1', 'user01');

insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval,'테스트제목', '테스트 내용2', 'user02');

insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval,'테스트제목', '테스트 내용3', 'user03');

insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval,'테스트제목', '테스트 내용4', 'user04');

commit;

 

일반 텍스트 파일을 생성 후, 아래의 코드 두 줄을 입력해주고 저장한다.

 

log4jdbc.log4j2.properties 파일 작성(test 로그 값 찍어주는 것/콘솔창에 찍어주는 것 처럼 나옴)

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL

 

root-context.xml을 수정해준다.
namespaces가 클릭 되면 위와 같이 자동으로 주소가 생성된다.(사용할 플러그인 주소를 가져옴)

 

아래 코드를 추가

 <!-- Root Context: defines shared resources visible to all other web components -->
	 <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
	  <!-- Old Driver -->
	  <!-- 
	   <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
	   -->
	   
	  <!-- log4jdbc --> 
		  <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		  <property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"></property>
		  <property name="username" value="ohj"></property>
		  <property name="password" value="920424"></property>
	 </bean>
	 
	 <!-- HikariCP configuration -->
	 <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
	  <constructor-arg ref="hikariConfig"></constructor-arg>
	 </bean>
	 
	 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	  <property name="dataSource" ref="dataSource"></property>
	 </bean>
	 
	 <mybatis-spring:scan base-package="com.ohj.mapper" />

 

 

package com.ohj.persistence;

import static org.junit.Assert.fail;

import java.sql.Connection;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; // SqlSession 객체를 얻어내야 하기 때문에 임포트 
import org.junit.Test; //단계별 테스트를 위한 임포트
import org.junit.runner.RunWith; // 실행 과정을 봐야 하기 위한 임포트
import org.springframework.beans.factory.annotation.Autowired; // 어노테이션을 이용한 주입방식(오토위어드 방식)
import org.springframework.test.context.ContextConfiguration; // 환경설정(테스트용도) 구조
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class DataSourceTests {
   
   @Setter(onMethod_= { @Autowired })
   private DataSource dataSource;
   
   @Setter(onMethod_= { @Autowired })
   private SqlSessionFactory sqlSessionFactory;
   
   @Test
   public void testMyBatis() {
      //세션팩토리에 접속하기 위한 코드
      try(SqlSession session = sqlSessionFactory.openSession();
         Connection con = session.getConnection();) {
         log.info(session);
         log.info(con);
         
      } catch(Exception e) {
         fail(e.getMessage());
      } // try catch END
   }// testMyBatis() END
   
   public void testConnection() {
      try(Connection con = dataSource.getConnection()){
         log.info(con);
      } catch(Exception e) {
         fail(e.getMessage());
      }// try catch END
   }// testConnection() END
}

 

package com.ohj.persistence;

import static org.junit.Assert.fail;

import java.sql.Connection;
import java.sql.DriverManager;

import org.junit.Test;

import lombok.extern.log4j.Log4j;

//순수 JDBC로 접속하기 위한 테스트 코드
@Log4j
public class JDBCTests {
   static {
      try {
         Class.forName("oracle.jdbc.driver.OracleDriver");
      } catch (Exception e) {
         e.printStackTrace();
      } // try catch END
   }// JDBCTests END

   @Test
   public void testConnection() {
      try (Connection con = DriverManager.getConnection("jdbc:log4jdbc:oracle:thin:@localhost:1521:XE", "inventory", "1234")) {
         log.info(con);
      } catch (Exception e) {
         fail(e.getMessage());
      } // try catch END
   }// testConnection END
   
}//CLASS END​

 

 

메이븐의 스프링 MVC 프로젝트를 위한 툴 설치

 

설치 루트 * 아래 설치 바 완료까지 기다려줌

 

 

인코딩 설정을 모두 UTF-8로 설정

 

http://download.eclipse.org/tools/ajdt/47_aj9/dev/update

 

 

설치가 성공 되지 않는 다면 위와 같은 주소로 설정하고 다시 roo 툴을 새로 인스톨해줌.
스프링 프로젝트로 재설정 해줌(오른쪽 상단의 표 클릭 후 -> Spring)
새로운 프로젝트 생성 루트

 

MVC 프로젝트를 클릭 후 next로 패키지명을 설정 해줌

 

자바 버전을 1.8로 맞춰준다.

 

스프링 4버전을 사용하기 위한 pom.xml 설정 변경

 

 

스프링 프레임워크 기초 설명

 

 

1 

사용자의 Request(요청d)는 Front - Controller인 DispatcherServlet을 통해서 처리한다. 
생성된 프로젝트의 web.xml을 보면 아래와 같이 
모든 Request를 DispatcherServlet이 받도록 처리하고 있다.
 <!-- Processes application requests --> <servlet>  <servlet-name>appServlet</servlet-name>  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  <init-param>   <param-name>contextConfigLocation</param-name>   <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>  </init-param>  <load-on-startup>1</load-on-startup> </servlet>   <servlet-mapping>  <servlet-name>appServlet</servlet-name>  <url-pattern>/</url-pattern> </servlet-mapping>

 

2, 3

HandlerMapping은 Request의 처리를 담당하는 컨트롤러를 찾기 위해서 존재한다. HandlerMapping 인터페이스를 구현한 여러 객체들 중 
RequestMappingHandlerMapping 같은 경우는 
개발자가 @RequestMapping 어노테이션이 적용된 것을 기준으로 판단하게 된다. 
적절한 컨트롤러가 찾아졌다면 HandlerAdapter를 이용해서 해당 컨트롤러를 동작시킨다.



4

Controller는 개발자가 작성하는 클래스로 실제 Request를 처리하는 로직을 작성하게 된다. 
이때 View에 전달해야 하는 데이터는 주로 Model이라는 객체에 담아서 전달한다. 
Controller는 다양한 타입의 결과를 반환하는데 
이에 대한 처리는 ViewResolver를 이용하게 된다.



5

ViewResolver는 Controller가 반환한 결과를 
어떤 View를 통해서 처리하는 것이 좋을지 해석하는 역할이다. 
가장 흔하게 사용하는 설정은 servlet-context.xml에 정의된 InternalResourceViewResolver다.
 <!-- 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>

 

6, 7

View는 실제로 응답 보내야 하는 데이터를 Jsp 등을 이용해서 생성하는 역할을 하게된다. 
만들어진 응답은 DispatcherServlet을 통해서 전송된다.​

=================================================================

위의 그림을 보면 모든 Request는 DispatcherServlet을 통하도록 설계되는데, 
이런 방식을 Front-Controller 패턴이라고 한다. 
Front-Controller 패턴을 이용하면 전체 흐름을 강제로 제한할 수 있다. 

​

예를 들어 HttpServlet을 상속해서 만든 클래스를 이용하는 경우 
특정 개발자는 이를 활용할 수 있지만 
다른 개발자는 자신이 원래 하던 방식대로 HttpServlet을 그대로 상속해서 개발할 수도 있다. 



Front-Controller 패턴을 이용하는 경우에는 
모든 Request의 처리에 대한 분배가 정해진 방식대로만 동작할기 때문에 
좀 더 엄격한 구조를 만들어 낼 수 있다.

 

 
spring-beans : 스프링 컨테이너를 이용해서 객체를 생성하는 기본 기능 제공

spring-context : 객체 생성, 라이프 사이클 관리, 스키마 확장 등의 기능 제공

spring-aop : 프락시 기반 AOP 기능 제공

spring-web : REST 클라이언트, 데이터 변환 처리, 서블릿 필터, 파일 업로드 지원 등 웹개발에 필요한 기능 제공

★spring-webmvc : 스프링 기반의 웹 MVC 프레임워크, 웹 애플리케이션 개발하는데 필요한 컨트롤러 구현 제공

spring-websocket : 스프링 MVC에서 웹소켓을 사용하기 위한 기능 지원

spring-oxm : XML과 자바 객체 간 매핑을 처리하기 위한 API 제공

spring-tx : 트랜잭션 처리 위한 추상 레이어 제공

spring-jdbc : JDBC 프로그래밍을 보다 쉽게 할 수 있는 템플릿 제공, 이를 이용하면 JDBC 프로그램에서 반복적으로 입력해야하는 코드 줄일 수 있음

srping-orm : 하이버네이트, JPA, MyBatis 등과 연동 지원

spring-jms : JMS 서버와 메시지를 쉽게 주고 받을 수 있도록 템플릿과 아노테이션 등 제공

sring-context-support : 스케줄링, 메일발송, 캐시연동, 벨로시티 등 부가기능 제공



POJO(Plain Old Java Object) 기반의 프레임워크

- 자바 객체의 라이프사이클을 스프링 컨테이너가 직접 관리하며, 스프링 컨테이너로부터 필요한 객체를 얻어올 수 잇음


★DI(Dependency Injection)을 지원

- 각 계층이나 서비스들 사이 또는 객체들 사이에 의존성이 존재할 경우 스프링 프레임워크가 서로를 연결시켜줌 이는 클래스들 사이에 약한 결합을 가능하게 함(의존성↓)

------ 주입 방법 : 생성자, 멤버, 어노테이션, xml


AOP(Aspect Oriented Programming)를 지원

- 트랜잭션, 로깅, 보안 등 여러 모듈에서 공통적으로 지원하는 기능을 분리하여 사용 가능, 확장성 높음

- 스프링 프레임워크의 소스는 모두 라이브러리로 분리시켜 놓음으로써 필요한 라이브러리만 가져다 쓸 수 있다 그리고 많은 외부 라이브러리들도 이미 스프링 프레임워크와 연동되고 있다

- Model2 방식의 MVC Framework를 지원


전체적인 설계가 만들어지고 거기에 대한 관점을 보는 것 : AOP



maven : 스프링

gradle : 안드로이드 프로젝트
 
기본적인 웹 게시물 관리

스프링 MVC와 Mybatis를 이용한
CRUD(등록, 수정, 삭제, 조회)와
페이징 처리
검색기능 의 게시물 관리를 제작.


중요하게 고려해야 할 부분

스프링 MVC를 이용하는 웹 프로젝트 전체 구조에 대한 이해
개발의 각 단계


Presentation(화면 계층 ) : 화면에 보여주는 기술을 사용하는 영역
Servlet/JSP나 스프링 MVC가 담당하는 영역.
프로젝트의 성격에 맞추어 앱으로 제작하거나,
CS(Client - Server)로 구성되는 경우도 있다.
스프링 MVC와 JSP를 이용한 화면 구성이 이에 속한다.


Business(비즈니스 계층) : 순수한 비느니스 로직을 담고 있는 영역
이 영역이 중요한 이유
고객기 원하는 요구사항을 반영하는 계층.
이 영역의 설계는 고객의 요구 사항과 정확히 일치해야 한ㄷ.
이 영역은 주로 xxxService와 같은 이름으로 구성하고,
메서드 이름 역시 고객들이 사용하는 용어를 그대로 사용하는 것이 일반적.


Persistence(영속 또는 데이터 계층) : 데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층.
일반적인 경우, 데이터베이스를 많이 이용하지만,
경우에 따라서 네트워크 호출이나 원격 호출 등의 기술이 접목될 수도 있다.
이 영역은 Mybatis와 mybatis-spring을 이용하여 구성.

Spring MVC : Presentation Tier를 구성,

root-context.xml, servleet-context.xml 등의 설정 파일이 해당 영역의 설정을 담당


Spring Core : POJO(Plain-Old-Java-Object)의 영역.
스프링의 의존성 주입을 이용해서 객체 간의 연관구조를 완성하여 사용


Mybatis : 현실적으로 mybatis-spring을 이용하여 구성하는 영역.
SQL에 대한 처리를 담당하는 구조
프로젝트를 3-tier로 구성하는 이유는 유지보수에 대한 필요성 때문
각 영역은 독립적으로 설계되어 추후 특정 기술이 변하더라도 필요한 부분을 전자제품의 부품처럼
쉽게 교환할 수 있게 하는 방식


각 영역은 설계 당시부터 영역을 구분하고,
해당 연결 부위는 인터페이스를 이용하여 설계하는 것이 일반적

 1. 네이밍 규칙

xxxController :
스프링 MVC에서 동작하는 Controller 클래스를 설계할 때 사용


xxxService, xxxServiceImpl :
비즈니스 영역을 담당하는 인터페이스는 xxxService 방식.
인터페이스를 구현한 클래스는 xxxServiceImpl 이름 사용

xxxDAO, xxxRepository :
DAO(Data-Access-Object)나 Repository(저장소) 이름으로 영역을 따로 구성하는 것이 보편적
별도의 DAO를 구성하는 대신, Mybatis의 Mapper 인터페이스를 활용

VO, DTO :
VO나 DTO는 일반적으로 유사한 의미로 사용하는 용어
데이터를 담고 있는 객체를 의미한다는 공통점이 있다


VO :
주로 Read Only 목적이 강하고, 데이터 자체도 immutable(불변)하게 설계하는 것이 정석

DTO :
주로 데이터 수집의 용도가 좀 더 강하다
예) 웹 화면에서 로그인하는 정보를 DTO로 처리하는 방식을 사용

테이블과 관련된 데이터는 VO라는 이름 사용

패키지의 구성은 프로젝트의 크기나 구성원들의 성향으로 결정

규모가 적은 프로젝트 :
Controlelr 영역을 별도의 패키지로 설계
Service 역역 등을 하나의 패키지로 설계

규모가 큰 프로젝트 :
많은 Service 클래스와 Controller들이 흔재할 경우,
비즈니스를 단위별로 구분하고(즉, 비즈니스 단위별로 패키지 작성)
다시 내부에서 Controller 패키지, Service패키지 등으로 다시 나누는 방식 이용
담당자가 명확해지고, 독립적인 설정을 가지는 형태로 개발
[작업 패키지 구성]

com.이니셜 : 메인 패키지

com.이니셜.config : 프로젝트와 관련된 설정 클래스들

com.이니셜.controller : 스프링 MVC의 Controller들

com.이니셜.service : 스프링의 Service 인터페이스와 구현 클래스들

com.이니셜.domain : VO, DTO 클래스들

com.이니셜.persistence : Mybatis Mapper 인터페이스

com.이니셜.exception : 웹 관련 예외처리

com.이니셜.aop : 스프링의 AOP 관련

com.이니셜.security : 스프링의 Security 관련

com.이니셜.util : 각종 유틸리티 클래스 관련
프로젝트를 진행하기 전에

고객의 요구사항을 인식하고, 이를 설계하는 과정이 필요

​

요구사항 분석 설계 :

고객이 원하는 내용이 무엇이고,

어느 정도까지 구현할 것인가에 대한 프로젝트의 범위를 정하는 것이 목적.

​

요구사항 : 

실제로 방대해 질 수 있으므로 프로젝트에서는 단계를 정확히 구분.

 경험이 많은 팀 구성 : 초기버전에 상당히 많은 기능을 포함 시켜서 개발

 반대의 경우 : 최대한 단순하고 눈에 보이는 결과를 만들어 내는 형태로 진행.

​

온전한 문장으로 정리.

주어는 "고객"이고, 목적어는 "대상(domain)"이 된다.

​

예) 게시판의 경우, 게시물이 대상이 된다.

  고객은 새로운 게시물을 등록할 수 있어야 한다.

  고객은 특정 게시물을 조회할 수 있어야 한다.

  고객은 작성한 게시물으 삭제할 수 있어야 한다.

등.

​

테이블 : tbl_board

VO클래스 : com.이니셜.domain.BoardVO

게시물과 관련된 로직 : com.이니셜.service.BoardService / com.이니셜.BoardController

​

​

요구사항에 따른 화면 설계

예) 고객은 새로운 게시물을 등록할 수 있어야 한다.

  세부적인 설계 : '어떤 내용들을 입력하게 될 것인가'

  이를 기준으로 테이블이나 클래스의 멤버 변수(인스턴스 변수)들을 설계.

​

이러한 화면을 설계할 때는 주로 Mock-up(목업) 들을 이용하는 경우가 많다.

대표적인 Mock-up툴 : PPT, Balsqmiq studio, Pencil Mockup 등.

​

각 화면을 설계하는 단계에서는

 사용자가 입력해야 하는 값과 함께

 전체 페이지의 흐름을 설계.(유즈케이스 다이어그램)

​

이 화면의 흐름을 URL로 구성하게 되는데,

이 경우 GET/POST 방식에 대하여 언급한다.



3. 게시물 관리 프로젝트 구성

​

스프링 프로젝트 이름 : springboard

스프링 프로젝트 생성 : Spring Legacy Project

​

프로젝트 생성 후, 진행 순서:

pom.xml의 수정

데이터 베이스 관련 처리

스프링 MVC 처리

 

 

스프링 폴더 설명

 

 

 

스프링 버전 5 사용하기

사용하기 전, pom.xml 버전 통일하기

 

pom.xml 내용 변경하기

스프링 관련 라이브러리 추가

spring-tx : 트랜잭션을 사용하기 위한 라이브러리

spring-jdbc : 데이터베이스와 연동하기 위한 라이브러리

spring-test : 테스트 코드를 단위 테스트하기 위한 라이브러리

 

프로젝트 시작 전 기본 세팅 추가

<!-- Spring Transaction -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
        
<!-- Spring JDBC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
        
<!-- Spring Test --> 
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		

 

 
Mybatis 관련 라이브러리 추가

HikariCP : 대용량 데이터 처리 속도를 빠르게 하기 위한 라이브러리

MyBatis : 데이터베이스 프레임워크

mybatis-spring : MyBatis 를 사용하기 위한 스프링 라이브러리

Log4jdbc : 데이터베이스 실행 시, 단위 테스트를 위한 라이브러리

 

<!-- HikariCP -->
	<dependency>
		<groupId>com.zaxxer</groupId>
		<artifactId>HikariCP</artifactId>
		<version>2.7.8</version>
	</dependency>

<!-- MyBatis -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.4.6</version>
	</dependency>

<!-- mybatis-spring  -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis-spring</artifactId>
		<version>1.3.2</version>
	</dependency>

<!-- Log4jdbc -->
	<dependency>
		<groupId>org.bgee.log4jdbc-log4j2</groupId>
		<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
		<version>1.16</version>
	</dependency>

인스톨/업데이트 클릭후 퀵인스톨러

설치 방법(cmd)
java -jar lombok.jar

 

 

 

만약, x표시가 뜬다면

업데이트를 해준다.

 

+ Recent posts