BE - Spring Framework

수현·2023년 10월 5일
0

Ucamp

목록 보기
13/19

📒 Spring Framework (Mybatis)

📕 Spring DI / IoC

1. 프레임워크

1) 개념

  • 비기능적 요구사항(성능, 보안, 확장성, 안정성)을 만족하는 구조와 구현된 기능을 안정적으로 실행하도록 제어하는 구조의 라이브러리
  • 애플리케이션들의 최소한의 공통점을 찾아 하부 구조를 제공

2) 특징

  • 사용 이유
    • 비기능적인 요소들을 초기 개발 단계마다 구현해야 하는 불합리함 극복
    • 기능적인 요구사항에 집중할 수 있도록 함
    • 디자인 패턴처럼 반복적으로 발견되는 문제 해결하기 위한 Solution 제공

3) IoC (Inversion of Control)

  • 제어의 역전
    • 인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해줌
    • 컨테이너 역할을 해주는 프레임워크에게 제어하는 권한을 넘겨서 개발자의 코드가 신경쓸 것을 줄임
    • 프레임워크 동작 원리를 제어흐름이 일반적인 프로그램 흐름과 반대로 동작
  • Spring 컨테이너는 IoC 지원
    • 메타데이터(XML 설정)를 통해 Beans를 관리하고, 어플리케이션 중요 부분 형성
    • Spring 컨테이너는 관리되는 Bean들을 의존성 주입을 통해 IoC 지원

4) 디자인 패턴

  • 디자인패턴 + 라이브러리 = 프레임워크
    • 어플리케이션을 설계할 때 필요한 구조적인 가이드라인이 되지만, 구체적으로 구현된 기반 코드 제공X
    • 프레임워크를 사용하는 애플리케이션에 그 패턴이 적용됨

5) 클래스 라이브러리

  • 프레임워크
    • 디자인 패턴과 함께 적용된 기반 클래스 라이브러리를 제공
    • 프레임워크를 사용하는 구조적인 틀과 구현 코드 함께 제공
    • 특정 부분의 기술적인 구현을 라이브러리 형태로 제공
    • 프레임워크에서 개발자가 만든 클래스를 호출하여 실행의 흐름에 대한 제어를 담당
  • 라이브러리
    • 개발자가 만든 클래스에서 직접 호출하여 사용
    • 실행의 흐름에 대한 제어를 개발자의 코드가 관장
  • 프레임워크/라이브러리 차이
    • 특정 부분의 기술적인 구현을 라이브러리 형태로 제공
    • 실행흐름의 주도권을 프레임워크/사용자가 제어

2. Spring Framework

1) Spring Framework 개념

  • Java 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
  • 애플리케이션 프레임워크 : 특정 계층/기술/업무 분야에 국한되지 않고 애플리케이션의 전 영역을 포함
  • 경량급 프레임워크 : 단순한 웹컨테이너에서도 엔터프라이즈 개발의 고급기술 대부분 사용

2) Spring 삼각형

  • 엔터프라이즈 개발의 복잡함을 상대하는 Spring 전략
    • Portable Service Abstraction (서비스 추상화)
      • 객체 지향 원리에 충실하고, 특정 환경이나 규약에 종속되지 않고 필요에 따라 재활용 될 수 있는 방식으로 설계된 객체
      • 트래잭션 추상화, OXM 추상화
      • 데이터 액세스의 Exception 변환기능
      • 기술적인 복잡함은 추상화를 통해 Low Level의 기술 구현 부분과 기술을 사용하는 인터페이스로 분리함
    • DI (Dependency Injection)
      • Spring은 객체지향에 충실한 설계가 가능하도록 단순한 객체 형태로 개발
      • DI는 유연하게 확장 가능한 객체를 만들고 그 관계는 외부에서 dynamic 설정
    • AOP (Aspect Oriented Programming)
      • 애플리케이션 로직을 담당하는 코드에 남아 있는 기술
      • 관련 코드를 분리해서 별도의 모듈로 관리하게 해주는 기술
    • POJO (Plain Old Java Object)
      • 객체지향 원리에 충실
      • 특정 환경이나 규약에 종속되지 않고 필요에 따라 재활용 될 수 있는 방식으로 설계된 객체 (VM만 있어도 작동 가능한 정도)
      • Servlet은 POJO에 해당X

3) Spring Framework 특징

  • 컨테이너 역할
    • Spring Container는 Java 객체의 LifeCycle을 관리
    • Spring Container로부터 필요한 객체를 가져와 사용할 수 있음
  • DI 지원
    • Spring은 설정 파일이나 어노테이션을 통해서 객체 간의 의존관계를 설정
  • AOP 지원
    • Spring은 트랜잭션이나 로깅, 보안 등 공통적으로 필요로 하는 모듈을 실제 핵심 모듈에서 분리해서 적용
  • POJO 지원
    • Spring Container에 저장되는 Java 객체는 특정한 인터페이스를 구현하거나, 특정 클래스를 상속받지 않아도 됨
  • 트랜잭션 처리를 위한 일관된 방법 지원
    • JDBC, JTA 등 어떤 트랜잭션을 사용하던 설정을 통해 정보를 관리
    • 트랜잭션 구현에 상관없이 동일한 코드 사용 가능
  • 영속성과 관련된 다양한 API 지원
    • Spring은 MyBatis, Hibernate 등 데이터베이스 처리를 위한 ORM(Object Relational Mapping) 프레임워크들과의 연동 지원

4) Spring Framework의 기능 요소

  • Core 컨테이너
    • Spring 프레임워크의 기본 기능 제공
    • 이 모듈에 있는 BeanFactory는 Spring의 기본 컨테이너면서 스프링 DI의 기반
  • Context
    • BeanFactory 개념 확장
    • 국제화 (i18n) 메시지, 애플리케이션 생명주기 이벤트, 유효성 검증 지원
  • DAO
    • JDBC에 대한 추상화 계층으로 JDBC 코딩이나 예외처리 부분 간편화
    • AOP 모듈을 이용해 트랜잭션 관리 서비스 제공
  • ORM
    • MyBatis, Hibernate, JPA 등 사용되는 ORM 프레임워크와 연결고리 제공
  • AOP
    • AOP 모듈을 통해 Aspect 지향 프로그래밍을 지원
    • AOP 모듈은 스프링 애플리케이션에서 Aspect를 개발할 수 있는 기반을 지원함
  • Web
    • 일반적으로 웹 어플리케이션 개발에 필요한 기본 기능을 제공
    • Webwork나 Struts와 같은 다른 웹 어플리케이션 프레임워크와의 통합을 지원함
  • WebMVC
    • MVC(Model/View/Controller) 패러다임은 사용자 인터페이스가 애플리케이션 로직과 분리되는 웹 애플리케이션 만드는 경우 사용되는 패러다임
    • 웹 계층에서 결합도를 낮추는 Spring MVC 프레임워크

3. IoC와 Spring DI

1) IoC (제어권의 역전)

  • 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었음
  • 컴포넌트 의존관계 결정, 설정 및 생명주기를 해결하기 위한 디자인 패턴

2) IoC 컨테이너

  • 객체의 생성을 책임지고, 의존성을 관리
  • POJO의 생성, 초기화, 서비스, 소멸에 대한 권한을 가짐
  • 개발자들이 직접 POJO를 생성할 수 있지만 컨테이너에게 맡김

3) IoC의 분류

4) DL과 DI

  • DL (Dependency Lookyup, 의존성 검색)
    • 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lookup하는 것
    • DL 사용시 컨테이너 종속성이 증가하여, 주로 DI 사용함
  • DI (Dependency Injection, 의존성 주입)
    • 각 클래스간의 의존관계를 빈 설정 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
      • 개발자들은 단지 빈 설정파일에서 의존관계가 필요하다는 정보를 추가함
      • 객체 레퍼런스를 컨테이너로부터 주입 받아서, 실행 시에 동적으로 의존관계가 생성됨
      • 컨테이너가 흐름의 주체가 되어 애플리케이션 코드에 의존관계를 주입해 주는 것
    • 코드가 단순해지고, 컴포넌트 간의 결합도가 제거됨

5) Spring DI

  • Bean
    • 스프링이 IoC 방식으로 관리하는 객체
    • 스프링이 직접 생성과 제어를 담당하는 객체를 Bean이라고 부름
  • Bean Factory
    • 스프링의 IoC를 담당하는 핵심 컨테이너를 가리킴
    • Bean을 등록, 생성, 조회, 반환하는 기능을 담당함
    • Bean Factory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 주로 이용함
  • Application Context
    • BeanFactory를 확장한 IoC 컨테이너
    • Bean을 등록하고 관리하는 기능은 BeanFactory와 동일하지만, 스프링이 제공하는 각종 부가 서비스 추가 제공함
  • Configuration metadata
    • Bean Factory/Application Context가 IoC를 적용하기 위해 사용되는 메타 정보
    • 설정 메타정보는 IoC 컨테이너에 의해 관리되는 Bean 객체를 생성하고 구성할 때 사용

6) DI 유형

  • Setter Injection
    • Setter 메서드를 이용한 의존성 삽입
    • 의존성을 입력 받는 setter 메서드를 만들고 이를 통해 의존성 주입
    • <property> 태그
      • Setter 메서드를 통해 의존관계가 있는 Bean을 주입하려면 <property> 태그의 ref 속성 사용 (ref 속성은 사용하면 Bean의 id를 이용해 주입할 Bean을 찾음)
      • Setter 메서드를 통해 Bean의 레퍼런스가 아니라 단순 값을 주입하려고 할 때는 <property> 태그의 value 속성을 사용함 (value 속성은 단순 값 또는 Bean이 아닌 객체를 주입할 때 사용)
  • Constructor Injection
    • 생성자를 이용한 의존성 삽입
    • 필요한 의존성을 포함하는 클래스의 생성자를 만들고, 이를 통해 의존성을 주입함
    • <constructor-arg> 태그
      • constructor를 통해 의존 관계가 있는 Bean을 주입하려면 <constructor-arg> 태그를 사용할 수 있음
      • constructor 주입방식은 생성자의 파라미터를 이용하기 때문에 1번에 여러 개의 객체를 주입할 수 있음
      • 생성자 주입을 위한 설정1 (index 지정)
      <bean id="hello" class="myspring.di.xml.Hello">
      	<constructor-arg index="0" value="Srping"/>
          <constructor-arg index="1" ref="printer"/>
      </bean>
      • 생성자 주입을 위한 설정2 (파라미터 이름 지정)
      <bean id="hello" class="myspring.di.xml.Hello">
      	<constructor-arg name="name" value="Spring"/>
          <constructor-arg name="printer" ref="printer"/>
      </bean>
  • Method Injection
    • 일반 메서드를 이용한 의존성 삽입
    • 의존성을 입력 받는 일반 메서드를 만들고 이를 통해 의존성 주입
  • Collection Injection
    • Spring은 List, Set, Map, Properties와 같은 컬렉션 타입을 XML로 작성해서 프로퍼티에 주입하는 방법
    • List와 Set 타입
      • <list>와 <value> 태그를 이용
      • 프로퍼티가 Set 타입이면 <list> 대신에 <set> 사용
      <bean id="hello" class="myspring.di.xml.Hello">
      	<property name="names">
          	<list>
              	<value>Spring</value>
                  <value>IoC</value>
                  <value>DI</value>
              </list>
          </property>
      </bean>
    • Map 타입
      • <map>과 <entry> 태그를 이용
      <bean id="hello" class="myspring.di.xml.Hello">
      	<property name="ages">
          	<map>
              	<entry key="Kim" value="30" />
                  <entry key="Lee" value="35" />
                  <entry key="Ahn" value="40" />
              </map>
          </property>
      </bean>
  • scope 속성
    • default가 singleton임
    • < bean id="hello" class="myspring.di.xml.Hello" scope="prototype">
      ➡️ 테스트 결과 false (객체 항상 생성하여 주소값 다르기 때문)
    • < bean id="hello" class="myspring.di.xml.Hello" scope="singleton"> ➡️ 테스트 결과 true

7) DI 구현 클래스

  • POJO 클래스 다이어그램

  • 📋 Hello.java 📋

package myspring.di.xml;

public class Hello {
	String name;
    Printer printer;
    
    public Hello() {}
    
    public void setName(String name) {
    	this.name = name;
    }
    public void setPrinter(Printer printer) {
    	this.printer = printer;
    }
    public String sayHello() {
    	return "Hello" + name;
    }
    public void print() {
    	this.printer.print(sayHello());
    }
}
  • 📋 Printer.java 📋
package myspring.di.xml;

public interface Printer {
	public void print(String message);
}
  • 📋 StringPrinter.java 📋
package myspring.di.xml;

public class StringPrinter implements Printer {
	private StringBuffer buffer = new StringBuffer();

	public StringPrinter() {
		System.out.println(this.getClass().getName() + "생성자 호출됨");
	}
	
	public void print(String message) {
		this.buffer.append(message);
	}

	public String toString() {
		return this.buffer.toString();
	}
}
  • 📋 ConsolePrinter.java 📋
package myspring.di.xml;

public class ConsolePrinter implements Printer {
	public ConsolePrinter() {
		System.out.println(this.getClass().getName() + "생성자 호출됨");
	}
	
	public void print(String message) {
		System.out.println(message);
	}
}
  • 📋 spring-beans.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- StringPrinter 클래스를 Spring Bean으로 등록 -->
	<!-- getBean() 메서드 사용시 bean의 id 사용  -->
	<bean id="strPrinter" class="myspring.di.xml.StringPrinter" />
	
	<!-- ConsolePrinter 클래스를 Spring Bean으로 등록 -->
	<!-- 인터페이스는 작성할 필요X -->
	<bean id="conPrinter" class="myspring.di.xml.ConsolePrinter" />

	<!-- Hello 클래스를 Spring Bean으로 등록 -->
	<bean id="hello" class="myspring.di.xml.Hello">
		<property name="name" value="스프링"/>
		<!-- value는 setName의 값으로 들어감 -->
		<property name="printer" ref="strPrinter" />
	</bean>
</beans>

4. Spring DI 컨테이너

1) BeanFactory

  • Bean을 관리하는 Spring DI 컨테이너
  • DI 관점에서 객체의 생성과 객체 사이의 런타임 관계
  • Bean을 등록/생성/조회/반환 관리하는 기능
  • 보통은 BeanFactory를 바로 사용하지 않고, 확장한 ApplicationContext 사용
  • getBean() 메서드가 정의되어 있음

2) ApplicationContext

  • BeanFactory에 여러 가지 컨테이너 기능을 추가한 것
  • Bean을 등록/생성/조회/반환 관리하는 기능
  • Spring의 각종 부가 서비스를 추가로 제공함

5. jUnit 사용한 DI 테스트 클래스

1) jUnit

  • Java에서 독립된 단위 테스트를 지원해주는 프레임워크
  • jUnit은 보이지 않고 숨겨진 단위 테스트를 정형화시켜 단위테스트를 쉽게 해주는 테스트 지원 프레임워크
  • 특징
    • 단정(assert) 메서드로 테스트 케이스의 수행 결과를 판별함
      • 🗒️ 예시 : assertEquals(예상값, 실제값)
    • jUnit4부터는 테스트를 지원하는 어노테이션을 제공함 (@Test, @Before, @After)
    • 각 @Test 메서드가 호출할 때마다 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지게 함

2) Unit Test

  • 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차
  • 모든 함수와 메소드에 대한 테스트케이스를 작성하는 절차
  • Build Path > Configure Build Path > Libraries 탭 > Add Library > JUnit 선택 (JUnit5 버전)

3) jUnit 어노테이션

  • @Test
    • 선언된 메서드는 테스트를 수행하는 메소드
    • jUnit은 각각의 테스트가 서로 영향을 주지 않고, 독립적으로 실행됨을 원칙으로 하므로 @Test마다 객체를 생성함
  • @Disabled
    • 선언된 메서드는 테스트를 실행하지 않게 함
  • @BeforeEach
    • 선언된 메서드는 @Test 메서드가 실행되기 전에 반드시 실행되어 짐
    • @Test 메서드에서 공통으로 사용하는 코드를 @BeforeEach 메서드에 선언하여 사용하면 됨
  • @AfterEach
    • 선언된 메서드는 @Test 메서드가 실행된 후 실행
  • @BeforeAll
    • @Test 메서드보다 먼저 1번만 수행되어야 할 경우 사용
  • @AfterAll
    • @Test 메서드보다 나중에 1번만 수행되어야 할 경우 사용

4) 단정(assert) 메서드

  • assertEquals(a, b);
    • 객체 A와 B의 값이 일치함을 확인
  • assertArrayEquals(a, b);
    • 배열 A와 B의 값이 일치함을 확인
  • assertSame(a, b);
    • 객체 A와 B가 같은 객체임을 확인
    • 두 객체의 레퍼런스가 동일한가를 확인 (== 연산자)
  • assertTrue(a);
    • 조건 A가 참인가를 확인
  • assertNotNull(a);
    • 객체 A가 null이 아님을 확인함
  • 📋 HelloBeansJUnitTest.java 📋
package myspring.di.xml;

import static org.junit.jupiter.api.Assertions.*; // static 메서드를 테스트케이스에 많이 사용하여 static import를 사용하여  Assertions.생략 가능
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.GenericXmlApplicationContext;

public class HelloBeansJunitTest {
	
	@Test
	void hello() {
		// 1. Spring Bean Container 객체 생성
		// classpath에 xml 파일 경로 지정
		BeanFactory factory = new GenericXmlApplicationContext("classpath:spring-beans.xml");
		
		// 2. Container가 생성한 Bean을 요청하기
		Hello hello1 = (Hello) factory.getBean("hello");
		Hello hello2 = factory.getBean("hello", Hello.class); // 해당 방법 권장
		
		// 3. HelloBean의 레퍼런스 비교하기 
		System.out.println(hello1 == hello2); // 싱글톤인지 아닌지 확인 목적
		assertSame(hello1, hello2); // 주소가 같으면 테스트 성공으로 표시 
		
		assertEquals("Hello 스프링", hello2.sayHello()); // 예상한 값과 일치한지 확인
		hello2.print();
		
		Printer printer = factory.getBean("strPrinter", Printer.class);
		assertEquals("Hello 스프링", printer.toString());
	}
}

5) Spring-Test 어노테이션

  • @ExtendsWith
    • jUnit 프레임워크의 테스트 실행 방법을 확장할 때 사용하는 어노테이션
    • SpringExtension 클래스를 지정해 주면 jUnit 테스트를 진행하는 중에 ApplicationContext를 만들고 관리하는 작업을 진행해줌
    • 각각의 테스트 별로 객체가 생성되더라도 싱글톤의 ApplicationContext를 보장함
    • @ExtendWith(SpringExtension.class)
  • @ContextConfiguration
    • 스프링 빈(Bean) 설정 파일의 위치를 지정할 때 사용되는 어노테이션
    • classpath 경로설정 대신 사용
    • @ContextConfiguration(locations = "classpath:spring-beans.xml")
  • @Autowired
    • 변수, setter메서드, 생성자, 일반메서드 적용 가능
    • getBean 대신에 @Autowired 의존 받고 싶은 타입을 자동 연결
      • 해당 변수에 자동으로 빈을 매핑해 줌
      • 동일한게 없을 경우 찾을 수 없다고 표시
      • type이 동일할 경우 Bean의 변수명과 Bean의 id가 동일한 것을 찾아옴
    • @Autowired 없을 경우 객체가 생성되지 않아 NullPointerException 에러 발생
    • 스프링 빈 설정 파일을 읽기 위해 GenericXmlApplicationContext를 사용할 필요가 없음

6) Bean 등록 어노테이션

  • @Component
    • 컴포넌트를 나타내는 일반적인 스테레오 타입
    • 범용적인 general한 컴포넌트, spring에게 대신 객체 생성해달라는 표시
    • <bean> 태그와 동일한 역할
    • repository, service, controller 포함
  • @Repository
    • persistence 레이어, 영속성을 가지는 속성(파일, 데이터페이스)을 가진 클래스
  • @Service
    • 서비스 레이어, 비즈니스 로직을 가진 클래스
  • @Controller
    • 프리젠테이션 레이어, 웹 어플리케이션에서 웹 요청과 응답을 처리하는 클래스

7) Bean 의존관계 주입 어노테이션

  • @Autowired, @Resource 어노테이션은 의존하는 객체를 자동으로 주입해주는 어노테이션

  • @Autowired는 타입으로, @Resource는 이름으로 연결됨

  • @Autowired

    • 정밀한 의존관계 주입(Dependency Injection)이 필요한 경우에 사용
    • @Autowired는 변수, setter 메서드, 생성자, 일반 메서드에 적용이 가능함
    • 의존하는 객체를 주입할 때 주로 Type을 이용
    • @Autowired는 <property>, <constructor-arg> 태그와 동일한 역할을 함
      • Retention(runtime) : 실행시간에 동작 보장
  • @Resource

    • 어플리케이션에서 필요로 하는 자원을 자동 연결할 때 사용
    • @Resource는 변수, setter 메서드에 적용 가능함
    • 의존하는 객체를 주입할 때 주로 Name을 이용 (원하는 Bean의 id 값으로 가져옴)
  • @Value

    • 단순한 값을 주입할 때 사용되는 어노테이션
    • @Value("Spring")은 < property .. value = "Spring" />와 동일한 역할
    • Retention(runtime) : 실행시간에 동작 보장
  • @Qualifier

    • @Autowired 어노테이션과 같이 사용됨
    • @Autowired는 타입으로 찾아서 주입하므로, 동일한 타입의 Bean 객체가 여러 개 존재할 때 특정 Bean을 찾기 위해서는 @Qualifier를 같이 사용해야 함
  • @Override

    • 부모의 메서드 정의하는데, 메서드 선언부가 동일한지 체크하는 어노테이션
  • 📋 Test.java 📋

package myspring.di.xml;

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

import javax.annotation.Resource;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

// Spring Bean Container 객체 생성 대신 @ExtendWith 사용 (싱글톤의 Application Context 보장)
// classpath로 경로 설정 대신 @ContextConfiguration (스프링 빈 설정 파일의 위치)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans.xml")
public class HelloBeanSpringTest {
	// 1/ getBean 대신에 @Autowired 의존 받고 싶은 타입을 자동 연결
	// type이 동일할 경우 Bean의 변수명과 Bean의 id가 동일한 것을 찾아옴 (동일한게 없을 경우 찾을 수 없다고 표시)
	@Autowired
	Hello hello;
	
	@Autowired
	// 2. Qualifier : 범위 한정자 (Autowired와 함께 기재)
	@Qualifier("helloC")
	Hello hello2;
	
	// 3. 원하는 Bean의 id 값으로 가져옴
	@Resource(name = "helloC")
	Hello hello3;
	
	@Autowired
	@Qualifier("strPrinter")
	Printer printer;
	
	//@Autowired
	//StringPrinter printer;

	@Test
	void helloC() {
		assertEquals("Hello 생성자", hello2.sayHello());
		assertEquals("Hello 생성자", hello3.sayHello());
	}
	
	@Test // @Disabled
	void hello() {
		assertEquals("Hello 스프링", hello.sayHello()); // import static으로 인해 Assertions.assertEquals와 동일
		
		hello.print();
		assertEquals("Hello 스프링", printer.toString());
	}	
}

8) <context:component-scan> 태그

  • @ComponentScan
    • <context:component-scan base-package="myspring.di.annot"/>의 기능과 동일
    • 설정 역할을 하는 자바 클래스임을 알려줌
    • 메모리 상에 먼저 로딩됨
  • Component Scan을 지원하는 태그
  • @Component를 통해 자동으로 Bean을 등록
  • @Autowired로 의존관계를 주입받는 어노테이션을 클래스에서 선언하여 사용했을 경우에는 해당 클래스가 위치한 특정 패키지를 Scan하기 위한 설정을 XML에 해줌
<context:component-scan base-package=“myspring.di.annot" />
  • <context:include-filter> 태그와 <context:exclude-filter> 태그를 같이 사용하면 자동 스캔 대상에 포함시킬 클래스와 포함시키지 않을 클래스를 구체적으로 명시할 수 있음

9) Bean 등록과 설정 어노테이션

  • @Bean
    • 새로운 빈 객체를 제공할 때 사용
    • @Bean이 적용된 메서드의 이름을 Bean의 식별값으로 사용
  • @Configuration
    • 클래스에 @Configuration 어노테이션을 선언하는 것은 스프링 IoC 컨테이너가 해당 클래스를 Bean 정의의 설정으로 사용함
    • 새로운 자바 설정 지원의 핵심 부분
  • @Component와 @Bean 차이점
    • Spring Bean을 나타내는 어노테이션
    • @Component는 클래스 위에 선언하고, @Bean은 메소드 위에 선언함
    • 외부라이브러리에서 제공하는 클래스를 SpringBean으로 설정하는 경우에는 @Bean 어노테이션을 사용
    • 외부 라이브러리를 가져다가 재사용하고 싶을 경우
      • 일단 객체 생성하고, 메서드 생성한 후 메서드 위에 Bean이라고 작성
      • docket이라는 객체가 spring bean이 됨
      • 메서드 이름이 id가 됨
    • 해당 코드에 Qualifier 없어도 됨

10) Bean 등록 메타정보 구성 전략

1️⃣ : XML 설정 단독 사용
  • 모든 Bean을 명시적으로 XML에 등록하는 방법
  • 생성되는 모든 Bean을 XML에서 확인할 수 있는 장점이 있지만, Bean의 개수가 많아지면 XML 파일을 관리하기 번거로움
  • 여러 개발자가 같은 설정 파일을 공유해서 개발하면, 설정 파일을 동시에 수정하다가 충돌이 일어날 수 있음
  • DI에 필요한 적절한 setter 메서드 또는 constructor가 코드 내에 반드시 존재해야 함
  • 개발 중에는 어노테이션 설정 방법을 사용했지만, 운영 중에는 관리의 편의성을 위해 XML 설정으로 변경하는 전략을 쓸 수 있음
2️⃣ : 어노테이션과 XML 설정 혼용해서 사용
  • Bean으로 사용될 클래스에 @Component 어노테이션을 부여해주면, 클래스를 자동으로 찾아서 Bean으로 등록 (빈 스캐닝을 통한 자동인식 Bean 등록 기능)
  • 어노테이션을 부여하고 자동 스캔으로 Bean을 등록하면 XML 문서 생성과 관리에 따른 수고를 덜어주고, 개발 속도를 향상시킴
  • 어플리케이션에 등록될 Bean이 어떤 것들이 있고, Bean들 간의 의존관계가 어떻게 되는지 한 눈에 파악할 수 없음
3️⃣ : 어노테이션 설정 단독 사용
  • Spring JavaConfig 프로젝트는 XML이 아닌 자바 코드를 이용해서 컨테이너를 설정할 수 있는 기능 제공
  • @Configuration 어노테이션과 @Bean 어노테이션을 이용해서 스프링 컨테이너에 새로운 빈 객체를 제공할 수 있음
  • Spring 3.0부터는 어노테이션을 이용한 Bean의 등록 및 Bean들 간의 연결 설정을 자바 코드 내부에 하므로 XML을 전혀 사용하지 않음
1️⃣ : XML 설정 단독 사용
  • 📋 spring-beans.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- StringPrinter 클래스를 Spring Bean으로 등록 -->
	<!-- getBean() 메서드 사용시 bean의 id 사용  -->
	<bean id="strPrinter" class="myspring.di.xml.StringPrinter" />
	
	<!-- ConsolePrinter 클래스를 Spring Bean으로 등록 -->
	<!-- 인터페이스는 작성할 필요X -->
	<bean id="conPrinter" class="myspring.di.xml.ConsolePrinter" />

	<!-- Hello 클래스를 Spring Bean으로 등록 -->
	<bean id="hello" class="myspring.di.xml.Hello">
		<property name="name" value="스프링"/>
		<!-- value는 setName의 값으로 들어감 -->
		<property name="printer" ref="strPrinter" />
	</bean>
    <bean id="helloC" class="myspring.di.xml.Hello">
          <!--  Constructor Injection -->
          <constructor-arg index="0" value="생성자"/>
          <constructor-arg index="1" ref="conPrinter"/>
          <property name="names">
              <list>
                  <value>Spring Framework</value>
                  <value>Spring Boot</value>
                  <value>Spring Cloud</value>
              </list>
          </property>
      </bean>
</beans>
  • 📋 HelloBeansJUnitTest.java 📋
package myspring.di.xml;

import static org.junit.jupiter.api.Assertions.*; // static 메서드를 테스트케이스에 많이 사용하여 static import를 사용하여  Assertions.생략 가능

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.GenericXmlApplicationContext;

public class HelloBeansJunitTest {
	BeanFactory factory;
	
	@BeforeEach
	void init() {
		// 1. Spring Bean Container 객체 생성
		// classpath에 xml 파일 경로 지정
		factory = new GenericXmlApplicationContext("classpath:spring-beans.xml");
	}
	
	@Test
	void 생성자주입테스트() {
		Hello bean = factory.getBean("helloC", Hello.class);
		assertEquals("Hello 생성자", bean.sayHello());
		bean.print();
	}
	
	@Test @Disabled
	void hello() {
		// 2. Container가 생성한 Bean을 요청하기
		Hello hello1 = (Hello) factory.getBean("hello");
		Hello hello2 = factory.getBean("hello", Hello.class); // 해당 방법 권장
		
		// 3. HelloBean의 레퍼런스 비교하기 
		System.out.println(hello1 == hello2); // 싱글톤인지 아닌지 확인 목적
		assertSame(hello1, hello2); // 주소가 같으면 테스트 성공으로 표시 
        
		assertEquals("Hello 스프링", hello2.sayHello()); // 예상한 값과 일치한지 확인
		hello2.print();
		
		Printer printer = factory.getBean("strPrinter", Printer.class);
		assertEquals("Hello 스프링", printer.toString());
	}
}
2️⃣ : 어노테이션과 XML 설정 혼용해서 사용
  • 📋 spring-beans.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 어느테이션이 선언된 클래스들을 스캔하기 위한 설정 -->
	<!-- context라는 prefix 설정하는 이유 
	  - namespace : 기능별로 xml의 태그명이 똑같더라도 namespace가 다르면 구분 가능 
	  - beans는 default namespace라서 prefix 없이 사용
	  - context는 :context라는 prefix가 있어서 태그명에 작성해야함-->
	<context:component-scan base-package="myspring.di.annot"/>
</beans>
  • 📋 Hello.java 📋
package myspring.di.annot;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// xml파일의 bean 태그와 동일
@Component("helloBean")
public class Hello {
	
	// property 속성의 value와 동일
	@Value("어노테이션")
	String name;
	
	// preoverty 속성의 ref와 동일
	@Autowired
	@Qualifier("strPrinter")
	Printer printer;
	List<String> names;

	public Hello() {
		System.out.println(this.getClass().getName() + "생성자 호출됨");
	}

	public Hello(String name, Printer printer) {
		System.out.println(this.getClass().getName() + "오버로딩된 생성자 호출됨");
		this.name = name;
		this.printer = printer;
	}

	public List<String> getNames() {
		return this.names;
	}

	public void setNames(List<String> list) {
		System.out.println("Hello setNames() " + list);
		this.names = list;
	}

	// 어노테이션 사용시 없어도 됨
//	public void setName(String name) {
//		System.out.println("Hello setName() " + name);
//		this.name = name;
//	}
//
//	public void setPrinter(Printer printer) {
//		System.out.println("Hello setPrinter " + printer.getClass().getName());
//		this.printer = printer;
//	}

	public String sayHello() {
		return "Hello " + name;
	}

	public void print() {
		this.printer.print(sayHello());
	}

}
  • 📋 AnnotatedHelloBeanTest.java 📋
package myspring.di.annot;

import javax.annotation.Resource;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

// file:src/main/resources/spring-beans-annot.xml와 classpath:spring-beans-annot.xml 동일
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-annot.xml")
public class AnnotatedHelloBeanTest {
	// Hello가 종류가 1개밖에 없어서 Bean의 id와 일치하지 않아도 상관 없음 (type으로 찾기 때문에)
	@Autowired
	Hello hello;
	
	@Resource(name="stringPrinter")
	Printer printer;
	
	@Test
	public void helloBean() {
		Assertions.assertEquals("Hello 어노테이션", hello.sayHello());
		hello.print();
		Assertions.assertEquals("Hello 어노테이션", printer.toString());
	}
}
  • Construction Injection
    • 📋 HelloCons.java 📋
package myspring.di.annot;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("helloCons")
public class HelloCons {
//	@Value("어노테이션")
	String name;
//	@Autowired
//	@Qualifier("stringPrinter")
	Printer printer;
	List<String> names;

	public HelloCons() {
		System.out.println(this.getClass().getName() + "생성자 호출됨");
	}

	// 생성자 통해서 injection 받기 
	// 생성자를 argument에 적용 
	@Autowired
	public HelloCons(@Value("annot생성자") String name, @Qualifier("consolePrinter") Printer printer) {
		System.out.println(this.getClass().getName() + "오버로딩된 생성자 호출됨");
		this.name = name;
		this.printer = printer;
	}

	public List<String> getNames() {
		return this.names;
	}

	public void setNames(List<String> list) {
		System.out.println("Hello setNames() " + list);
		this.names = list;
	}

	// 어노테이션 사용시 없어도 됨
//	public void setName(String name) {
//		System.out.println("Hello setName() " + name);
//		this.name = name;
//	}
//
//	public void setPrinter(Printer printer) {
//		System.out.println("Hello setPrinter " + printer.getClass().getName());
//		this.printer = printer;
//	}

	public String sayHello() {
		return "Hello " + name;
	}

	public void print() {
		this.printer.print(sayHello());
	}

}
3️⃣ : 어노테이션 설정 단독 사용
  • 📋 AnnoatatedHelloConfig.java 📋
package myspring.di.annot.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//xml을 대신해서 설정 역할을 하는 클래스 
@Configuration
@ComponentScan(basePackages = {"myspring.di.annot"})
public class AnnotatedHelloConfig {
	
}
  • 📋 XmlHelloConfig.java 📋
package myspring.di.xml.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import myspring.di.xml.ConsolePrinter;
import myspring.di.xml.Hello;
import myspring.di.xml.Printer;
import myspring.di.xml.StringPrinter;

@Configuration
public class XmlHelloConfig {
	
	/*
	 * <bean id="strPrinter" class="myspring.di.xml.StringPrinter" />
	 */
	// qualifier 하지 않아도 메서드 이름이 bean의 id값
	@Bean
	public Printer strPrinter() {
		return new StringPrinter();
	}
	
	@Bean
	public Printer conPrinter() {
		return new ConsolePrinter();
	}
	
	@Bean
	public Hello hello() {
		Hello hello = new Hello();
		hello.setName("Java컨피그");
		hello.setPrinter(strPrinter());
		return hello;
	}
}
  • 📋 AnnoatatedHelloConfigTest.java 📋
package myspring.di.annot.config;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AnnotationConfigContextLoader;

import myspring.di.annot.Hello;

// Bean 컨테이너 종류가 바뀌어서 loader를 통해 가져옴
// AnnotationConfigContextLoader.class는 AnnotationConfigApplicationContext 라는 Spring Bean Container를 로딩해주는 Loader 클래스
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AnnotatedHelloConfig.class, loader = AnnotationConfigContextLoader.class)
public class AnnotatedHelloConfigTest {
	@Autowired
	Hello hello;
	
	@Test
	public void hello() {
		System.out.println(hello.sayHello()); // Hello 어노테이션
	}
}

11) XML 존재 이유

  • 프레임워크가 대신 객체를 생성하기 때문에, 해당 인스턴스를 알려줌
  • 빈 컨테이너 (GenericXmlApplicationContext)가 객체 생성

12) 프로퍼티 파일을 이용한 설정 방법

  • 환경에 따라 자주 변경되는 내용의 분리
    • XML의 Bean 설정 메타정보는 어플리케이션 구조가 바뀌지 않으면 자주 변경되지 않음
    • 프로퍼티 값으로 제공되는 일부 설정 정보(Bean이 사용하는 DB 연결 정보)는 어플리케이션이 동작하는 환경(개발/테스트/스테이징/운영)에 따라서 자주 바뀜
    • 변경되는 이유와 시점이 다르면 분리하는 것이 객체지향 설계의 기본 원칙 (설정도 적용 가능)
    • XML 처럼 복잡한 구성이 아닌 키와 값의 쌍으로 구성
  • 프로퍼티 파일로 분리한 정보는 ${} (프로퍼티 치환자)을 이용하여 설정
    • ${} 값을 치환해주는 기능은 <context:property-placeholder> 태그에 의해 자동으로 등록되는 PropertyPlaceHolderConfigurer Bean이 담당함

📕 MyBatis-Spring

1. MyBatis

1) 개념

  • 자바 객체와 SQL 사이의 자동 Mapping 기능을 지원하는 ORM 프레임워크
  • SQL을 별도의 파일로 분리해서 관리
    • 객체-SQL 사이의 파라미터 Mapping 작업을 자동으로 해줌
  • Hibernate나 JPA(Java Persistence Api)처럼 새로운 DB 프로그램 패러다임을 익히지 않고, SQL을 그대로 이용
    • JDBC 코드 작성의 불편함도 제거
    • 도메인 객체나 VO 객체를 중심으로 개발 가능

2) 특징

  • 쉬운 접근성과 코드의 간결함
    • 가장 간단한 persistence 프레임워크
    • JDBC의 모든 기능을 MyBatis가 제공 (XML 형태로 서술된 JDBC 코드 수준)
    • 복잡한 JDBC 코드를 없애고 깔끔한 소스코드 유지
    • 수동적인 파라미터 설정과 쿼리 결과에 대한 맵핑 구문 제거
  • SQL문과 프로그래밍 코드의 분리
    • SQL 변경이 있을 때마다 자바 코드를 수정하거나 컴파일 하지 않아도 됨
    • SQL 작성과 관리 또는 검토를 DBA같은 개발자가 아닌 다른 사람에게 맡겨도 됨
  • 다양한 프로그래밍 언어로 구현 가능
    • Java, C#, .NET, Ruby

3) MyBatis와 MyBatis-Spring을 사용한 DB 액세스 계층

4) MyBatis를 사용하는 데이터 엑세스 계층

5) MyBatis3 주요 컴포넌트

  • MyBatis 설정파일 (SqlMapConfig.xml)
    • 데이터베이스의 접속 주소 정보나 Mapping 파일의 경로 등의 고정된 환경정보를 설정
    • mybatis 설정 파일
      • alias 이름을 mapper에서 사용
      • settings : sql쿼리문을 로그의 개발모드 상에 보내주면, 로그를 볼 수 있는 설정 (log4J 필요)
      • logging 사용 이유 : sysout으로 로그 사용할 경우 관리하기 어렵기 때문에, log level에 따라 환경에 따라 사용 가능
  • SqlSessionFactoryBuilder
    • MyBatis 설정 파일을 바탕으로 SqlSessionFactory 생성
  • SqlSessionFactory
    • SqlSession 생성
  • SqlSession
    • 핵심적인 역할을 하는 클래스
    • SQL 실행이나 트랜잭션 관리를 실행
    • SqlSession 오브젝트는 Thread-Safe 하지 않으므로 thread마다 필요에 따라 생성함
  • Mapping 파일 (user.xml)
    • SQL문과 OR Mapping을 설정함

6) MyBatis-Spring 주요 컴포넌트

  • MyBatis 설정파일 (SqlMapConfig.xml)
    • VO 객체의 정보를 설정
  • SqlSessionFactoryBean
    • MyBatis 설정 파일을 바탕으로 SqlSessionFactory를 생성함
    • Spring Bean으로 등록해야 함
  • SqlSessionFactory
    • SqlSession 생성
  • SqlSessionTemplate
    • 핵심적인 역할을 하는 클래스로서 SQL 실행이나 트랜잭션 관리를 실행
    • SqlSession 인터페이스를 구현하며, Thread-safe
    • Spring Bean으로 등록해야 함
  • SqlSession
    • SQL 실행이나 트랜잭션 관리 설정
    • SqlSession 오브젝트는 Thread-safe 하지 않아 thread마다 필요에 따라 생성
    • mybatis-spring으로 해결
  • Mapping 파일 (UserMapper.xml)
    • SQL 쿼리문 작성
    • SQL문과 OR Mapping을 설정함
  • Spring Bean 설정파일 (beans.xml)
    • SqlSessionFactoryBean을 Bean 등록할 때 DataSource 정보와 MyBatis Config 파일 정보, Mapping 파일 정보를 함께 설정
    • SqlSessionTemplate을 Bean으로 등록함

2. DB 연결

1) DB 연결 과정

  • mariaDB
  • HikariCP
  • SpringJDBC

2) DataSource

  • Connection을 가져오는 객체 지원
  • JNDI(Java Naming and Directory API)는 DI와 유사
  • driver vendor (연결 객체 제공/ connection pooling 제공/dirtributed transaction 제공)
  • org.springframework.jdbc의 SimpleDriverDataSource
    • 개발에만 사용하고, 운영에 사용 X
    • 실제 connection pool이 아니고, pool Connections이 실제가 아님
  • HikariDataSource
    • 의존성 pom.xml 추가
    • Spring Bean 으로 설정 (setter 메서드 확인)
    • setDriverClassName, setJdbcUrl, setUsername, setPassword
    • spring-bean-user.xml
      • Properties file 정보 설정
      • property 하위 태그 대신 bean 태그의 attribute로 setter 메서드 지정
      • DataSource 구현체인 HikariDataSource를 SpringBean으로 등록
      • p:drvierClassName : setdriverClassName이 있고, 연결한다는 의미
<?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:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- Properties file 정보 설정 -->
	<context:property-placeholder location="classpath:value.properties"/>
	
	<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" 
		p:driverClassName="${db.driverClass}"
		p:jdbcUrl="${db.url}"
		p:username="${db.username}"
		p:password="${db.password}"
	/>
</beans>

3) Spring JDBC

  • 의존성 pom.xml 추가
  • value.properties
    • 환경설정 파일
    • DB의 환경 설정 정보 파일
db.driverClass=org.mariadb.jdbc.Driver
db.url=jdbc:mariadb://127.0.0.1:3306/boot_db?useUnicode=true&charaterEncoding=utf-8&useSSL=false&serverTimezone=UTC
db.username=boot
db.password=boot

myname=Spring
myprinter=printer
value1=JUnit
value2=AOP
value3=DI
printer1=stringPrinter
printer2=consolePrinter

4) DB 테스트

  • UserDBTest.java
 package myspring.user;

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

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {
	@Autowired
	DataSource dataSource;
	
	@Test
	public void conn() {
		try {
			Connection connection = dataSource.getConnection();
			DatabaseMetaData metaData = connection.getMetaData();
			System.out.println("DB Product Name : " + metaData.getDatabaseProductName()); //MariaDB
			System.out.println("DB Driver : " + metaData.getDriverName()); // MariaDB Connector/J
			System.out.println("DB URL : " + metaData.getURL()); // jdbc:mariadb://127.0.0.1/boot_db?user=boot&password=***&...
			System.out.println("DB Username : " + metaData.getUserName()); // boot
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

5) mybatis

  • 의존성 pom.xml 추가

6) SqlSession

  • 메서드
    • create
    • read
    • insert
    • select
    • SelectOne
    • update
  • spring-bean-user.xml
    • Mybatis-spring의 SqlSessionFactoryBean을 SpringBean으로 등록
    • setdatasource : hikari datasource를 연결
    • setconfigulation : mybatis/sqlmapconfig.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:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- Properties file 정보 설정 -->
	<context:property-placeholder location="classpath:value.properties"/>
	
	<!-- DataSource 구현체인 HikariDataSource를 SpringBean으로 등록 -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" 
		p:driverClassName="${db.driverClass}"
		p:jdbcUrl="${db.url}"
		p:username="${db.username}"
		p:password="${db.password}"
	/>
	
	<!-- Mybatis-spring의 SqlSessionFactoryBean을 SpringBean으로 등록 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
		<property name="mapperLocations">
			<list>
				<value>classpath:mybatis/*Mapper.xml</value>
			</list>
		</property>
	</bean>
  
  <!-- SqlSessionTemplate -->
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg ref="sqlSessionFactory"/>
		
	</bean>
</beans>
  • UserDBTest.java
package myspring.user;

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

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import myspring.user.vo.UserVO;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:spring-beans-user.xml")
public class UserDBTest {	
	@Autowired
	SqlSessionFactory sessionFactory;
	
	@Autowired
	SqlSession sqlSession;
	
	@Test
	public void session() {
		UserVO user = sqlSession.selectOne("userNS.selectUserById", "dooly");
		System.out.println(user);
	}
	
	@Test
	public void sessionFactory() {
		System.out.println(sessionFactory.getClass().getName()); // injection 잘 되었는지 확인 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory

	}	
}

7) log4j2.xml

appender를 이용해서 console에도 찍을 수 있고 file에 로그 정보 저장 가능

8) Mapper

  • sql 호출 인터페이스

  • selectOne : <T> T selectOne(String statement(select의 id), Object parameter(값을 파라미터로 넘김))
    ➡️ 해당 parameter 값이 value로 들어감

  • userVO 알려주면 getter/setter를 mybatis가 대신 해줌

    • mybatis가 query를 조회하여 결과를 userVO에 저장하기 위해서 setter 호출
    • 저장된 userVO를 가져와서 화면에 뿌리는 방식으로 사용
    • 단, UserVO의 getters/setter 메서드와 이름이 동일해야 함)
  • 주의사항

    • UserMapper.xml과 UserMapper 인터페이스의 메서드명 일치시키기
    • UserMapper 인터페이스가 있을 경우 xml이 수정될 때마다 업데이트 필요
    • UserMapeer과 SqlSession 부르기 위해 연결하기 위한 설정 필요 (mybatis-spring의 bean 추가)
  • spring-beans-user.xml

// 해당 구문 추가
<!-- Mybatis-Spring의 MapperScannerConfigurer을 SpringBean으로 등록 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!-- 참조하는 것 없어서 bean id 없어도 됨 -->
		<property name="basePackage" value="myspring.user.dao.mapper" />
		<property name="sqlSessionTemplateBeanName" value="sqlSession"/>
	</bean>
  • UserMapper.java
package myspring.user.dao.mapper;
import java.util.List;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import myspring.user.vo.UserVO;

public interface UserMapper {
	//@Select("select * from users where userid=#{id}")
	//UserVO selectUserById(@Param("id") String id);
	
	UserVO selectUserById(String id);
	List<UserVO> selectUserList();
	void insertUser(UserVO userVO);
	void updateUser(UserVO userVO);
	void deleteUser(String id);
}

3. Layered Architecture

1) 특징

  • 계층화 아키텍처
    • 효율적인 개발과 유지보수를 위해 계층화하여 개발
    • 대부분의 중/대규모 어플리케이션에서 적용
    • 각 레이어는 독립된 R&R을 가짐

2) 프리젠테이션 영역

  • 사용자와 상호작용을 담당
  • 사용자의 요청을 분석/응답

3) 서비스 영역

  • Biz 로직을 수행
  • 트랜잭션 수행

4) 데이터 영역

  • 데이터의 저장과 조회를 담당
  • 주로 데이터베이스와 연동하여 작업

5) web.xml

  • = tomcat 설정 파일

    • web.xml에 DispatcherServlet 맵핑
    • web.xml에 spring configurtion 설정 필요
  • tomcat이 spring web bean container 구동시켜주는 역할

  • GenericWebApplicationContext가 spring web bean container 역할을 함

  • web.xml

    • contextLoadListener 추가
    • listener
      • tomcat 서버가 start/stop 이벤트를 계속 확인하는 역할
      • tomcat 메모리 상에 application context를 로드하는 것
      • param location 태그에 spring-beans-user.xml을 알려줘야함
      • web에 servlet context 자리에 해당 설정값이 저장될 것임
  • dispatcherservlet 추가

  • servlet의 param location

    • controller 쪽에만 적용되는 xml 파일 있을 경우 적용
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
	id="WebApp_ID" version="3.1">
  <display-name>CustomerSpringWeb</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-beans-user.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring-beans-user.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
</web-app>

3. Mapper 인터페이스

1) 개념

  • Mapping 파일에 기재된 SQL을 호출하기 위한 인터페이스
    • Mapper 인터페이스는 SQL을 호출하는 프로그램을 Type Safe하게 기술하기 위해 MyBatis3.x부터 등장
    • Mapping 파일에 있는 SQL을 자바 인터페이스를 통해 호출할 수 있도록 함
  • Mapper 인터페이스를 사용하지 않았을 때
    • SQL을 호출하는 프로그램은 SqlSession 메서드의 argument에 문자열로 네임스페이스.SQL ID로 지정
    • 문자열로 지정하기 때문에 오타에 의해 버그가 숨어 있거나, IDE에 제공하는 code assist를 사용할 수 없음
  • Mapper 인터페이스를 사용하였을 때
    • UserMapper 인터페이스는 개발자가 작성
    • 패키지 이름.인터페이스 이름.메서드 이름네임스페이스.SQL ID가 되도록 네임스페이스와 SQL의 ID를 설정해야 함
    • 네임스페이스 속성에는 패키지를 포함한 Mapper 인터페이스 이름
    • SQL ID에는 매핑하는 메서드 이름을 지정하는 것

📕 Spring AOP

1. AOP (Aspect Oriented Programming)

1) 개념

  • 애플리케이션에서 관심사의 분리(기능의 분리)
    • 핵심적인 기능에서 부가적인 기능을 분리함
    • 분리한 부가기능을 Aspect라는 독특한 모듈 형태로 만들어서 설계하고 개발하는 방법 (객체지향적)
  • OOP를 적용하여도 핵심기능에서 부가기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP가 해결

2) 용어

  • Aspect
    • AOP의 기본 모듈
    • 부가 기능을 정의한 코드 (Advice) + 부가기능을 어디에 적용할 지 결정 (PointCut)
    • AOP 개념을 적용하면 핵심기능 코드 사이에 침투된 부가기능을 독립적인 aspect로 구분해 낼 수 있음
    • 구분된 부가기능 aspect를 런타임 시에 필요한 위치에 동적으로 참여할 수 있음
    • 싱글톤 형태의 객체로 존재함
  • Target
    • 핵심 기능을 담고 있는 모듈
    • 타겟은 부가기능을 부여할 대상이 됨
  • Advice
    • 타겟에 제공할 부가기능을 담고 있는 모듈
  • Pointcut
    • advice를 적용할 타겟의 메서드를 선별하는 정규 표현식
    • 포인트컷 표현식은 execution으로 시작하고, 메서드의 Signature를 비교하는 방법 주로 이용
  • Join Point
    • 어드바이스가 적용될 수 있는 위치
    • 타겟 객체를 구현한 인터페이스의 메서드 중에서 포인트컷에 의해 선택된 메서드
  • Advisor
    • Advisor = Advice + PointCut
    • Spring AOP에서만 사용되는 특별한 용어
  • Weaving
    • 포인트컷에 의해서 결정된 타겟의 조인 포인트에 부가기능(advice)를 삽입하는 과정
    • AOP가 핵심기능(타겟)의 코드에 영향을 주지 않으면서 필요한 부가기능(advice)를 추가할 수 있도록 해주는 핵심적인 처리 과정

2. Spring AOP

1) 특징

  • 1️⃣ : Spring은 프록시(proxy) 기반 AOP 지원
    • Spring은 타겟 객체에 대한 프록시를 만들어 제공함
    • 타겟을 감싸는 프록시는 runtime에 생성됨
    • 프록시는 어드바이스를 타겟 객체에 적용하면서 생성되는 객체
  • 2️⃣ : 프록시(proxy)가 호출을 가로챔 (intercept)
    • 프록시는 타겟 객체에 대한 호출을 가로챈 다음 어드바이스의 부가기능 로직을 수행하고 난 후에 타겟의 핵심기능 로직을 호출함 (전처리 어드바이스)
    • 타겟의 핵심기능 로직 메서드를 호출한 후에 부가기능(어드바이스)을 수행하는 경우도 있음 (후처리 어드바이스)
  • 3️⃣ : Spring AOP는 메서드 조인 포인트만 지원됨
    • Spring은 동적 프록시를 기반으로 AOP를 구현하므로 메서드 조인 포인트만 지원
    • 핵심 기능(target)의 메서드가 호출되는 런타임 시점에만 부가기능(advice)를 적용할 수 잇음
    • AspectJ 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메서드 호출 및 초기화 등 다양한 작업에 부가기능 적용할 수 있음

2) 구현 방식

  • 1️⃣ : XML 기반의 POJO 클래스를 이용한 AOP 구현
    • 부가기능을 제공하는 Advice 클래스를 작성함
    • XML 설정 파일에 <aop:config>를 이용해서 aspect를 설정함 (advice와 pointcut 설정)
  • 2️⃣ : @Aspect 어노테이션을 이용한 AOP 구현
    • @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스 작성
    • Aspect 클래스는 어드바이스를 구현하는 메서드와 포인트컷을 포함함
    • XML 설정 파일에 <aop:aspectj-autoproxy /> 설정

3. Spring AOP 구현 (XML)

1) Advice 종류

  • Around 어드바이스
    • 타겟의 메서드가 호출되기 이전(before) 시점과 이후(after) 시점에 모두 처리해야 할 필요가 있는 부가기능 정의
    • JoinPoint 앞과 뒤에 실행되는 Advice
  • Before 어드바이스
    • 타겟의 메서드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능 정의
    • JoinPoint 앞에서 실행되는 Advice
  • After Returning 어드바이스
    • 타겟의 메서드가 정상적으로 실행된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능 정의
    • JoinPoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice
  • After Throwing 어드바이스
    • 타겟의 메서드가 예외를 발생된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의
    • 예외가 던져질 때 실행되는 Advice

2) Advice 정의하는 태그

  • <aop:before>
    • 메서드가 실행 전에 적용되는 어드바이스를 정의
  • <aop:after-returning>
    • 메서드가 정상적으로 실행된 후에 적용되는 어드바이스 정의
  • <aop:after-throwing>
    • 메서드가 예외를 발생시킬 때 적용되는 어드바이스를 정의
    • try-catch 블록에서 catch 블록과 비슷
  • <aop:after>
    • 메서드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없이 어드바이스 정의
    • try-catch-finally에서 finally 블록과 비슷
  • <aop:around>
    • 메서드 호출 이전, 이후, 예외발생 등 모든 시점에 적용 가능한 어드바이스를 정의

3) Advice 클래스 정보

  • 클래스명 : PeformanceTraceAdvice.java
  • 클래스 기능 : 어드바이스는 타겟 객체의 메서드 실행 시간을 계산해서 출력해주는 부가기능을 제공
  • Advice 유형 : Around 어드바이스 (타겟 객체의 메서드 실행 전, 후의 시간을 측정하여 계산하면 타겟 객체의 메서드 실행 시간을 알 수 있음)
  • 구현 메서드명 : trace(ProceedingJoinPoint joinPoint)

4) Advice 클래스 작성

  • JoinPoint 인터페이스

    • Spring AOP 혹은 AspectJ에서 AOP가 적용되는 지점을 뜻함
    • 해당 지점을 AspectJ에서 JointPoint라는 인터페이스로 나타냄
    • 메서드
      • getArgs() : 메서드 아규먼트 반환
      • getThis() : 프록시 객체를 반환
      • getTarget() : 대상 객체를 반환
      • getSignature() : 어드바이즈되는 메서드 설명(description)을 반환
      • toString() : 어드바이즈되는 메서드의 설명 출력
    • 모든 어드바이스에서 org.aspectj.Lang.JoinPoint 타입의 파라미터를 어드바이스 메서드에 첫 번째 매개변수로 선언할 수 있음
    • Around 어드바이스는 JoinPoint의 하위 클래스인 ProceedingJoinPoint 타입의 파라미터를 필수적으로 선언해야 함
  • ProceedingJoinPoint 인터페이스

5) AOP 설정

4. PointCut 표현식 문법

1)

2)

3)

4)

5)

5. Spring AOP 구현 (Annotation)

1) @Aspect 어노테이션

2) Aspect 클래스 정보

3) Advice를 정의하는 어노테이션

📖 참고 📖

  • 🗒️ 예시

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️

📕 Spring Transaction

1.

1)

2)

3)

4)

5)

2.

1)

2)

3)

4)

5)

3.

1)

2)

3)

4)

5)

📖 참고 📖

  • 🗒️ 예시

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️

  • 📋 코드 📋
  • 📋 실행 📋

📕 Spring MVC

1.

1)

2)

3)

4)

5)

📖 참고 📖

  • 🗒️ 예시

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️

  • 📋 코드 📋
  • 📋 실행 📋

📕 Spring Restful Web Service

1.

1)

2)

3)

4)

5)

📖 참고 📖

  • 🗒️ 예시

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️

  • 📋 코드 📋
  • 📋 실행 📋

📒

📕

1.

📖 참고 📖

  • 🗒️ 예시

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣
⬆️⬇️➡️

  • 📋 코드 📋
  • 📋 실행 📋

profile
Notion으로 이동 (https://24tngus.notion.site/3a6883f0f47041fe8045ef330a147da3?v=973a0b5ec78a4462bac8010e3b4cd5c0&pvs=4)

0개의 댓글