BE/Spring
Spring Framework
soohykim
2025. 4. 10. 13:37
728x90
반응형
📒 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
- Portable Service Abstraction (서비스 추상화)
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 메서드가 호출할 때마다 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지게 함
- 단정(assert) 메서드로 테스트 케이스의 수행 결과를 판별함
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번만 수행되어야 할 경우 사용
- @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을 설정함
- 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 설정
728x90
반응형