서론

이 포스팅은 개인적인 공부와 정리를 목적으로 다른 블로그의 글들을 짜집기한 내용들임을 밝힙니다.



Spring


DAO란?

Data Access Object의 약어로 실질적으로 DB에 접근하여 데이터를 조회하거나 조작하는 기능을 전담하는 객체를 말한다. DAO의 사용 이유는 효율적인 커넥션 관리와 보안성 때문이다. DAO는 저수준의 Logic과 고급 비즈니스 Logic을 분리하고 domain logic으로부터 DB관련 mechanism을 숨기기 위해 사용한다.


Mapper인터페이스란?

Mybatis 매핑XML에 기재된 SQL을 호출하기 위한 인터페이스이다. Mybatis3.0부터 생겼다.


Mapper(MyBatis)와 JPA를 함께 사용해야하는 이유?

  • JPA는 분명히 장점이 큽니다. 간단한 조회 쿼리를 JPA를 이용하면 매우 직관적으로 만들 수 있습니다.

SELECT * FROM notice WHERE id = :id
Optional findById(long id);

그러나, JPA를 이용해서 복잡한 쿼리를 나타내는 것에는 한계가 있습니다. 그다지 복잡하지 않은 아래의 쿼리를 나타내기 위해

SELECT * FROM notice WHERE create_timestamp = :createTimestamp1 AND status = :status1 OR create_timestamp >= :createTimestamp2 AND STATUS = :status2

이렇게 긴 메서드를 정의해야하며,

List findAllByCreateTimestampAndStatusOrCreateTimestampGreaterThanEqualAndStatus(Timestamp createTimestamp1, String status1, Timestamp createTimestamp2, String status2);


보통의 경우에는 JPA를 이용하며 복잡한 쿼리를 조회할 때엔 Mapper를 이용하는 것을 권장합니다.

SQL Mapper

SQL Mapper는 직접 SQL문을 작성해 DB를 접근하는 것이다. Mybatis가 SQL Mapper에 해당한다.

ORM (Object Relational Mapping)

DB의 데이터를 객체로 매핑시켜 데이터를 접근할 수 있는 것이다. ORM을 사용하면 SQL을 작성하지 않고도 메소드를 사용해 데이터를 조작할 수 있다. JPA, Hibernate 등이 해당한다.

Mybatis

Java에서는 DB에 접근할 수 있도록 JDBC라는 라이브러리를 제공한다. JDBC는 학습이 쉬워서 처음 DB 접근을 배울 때 자주 사용된다. 그렇지만 사용할 때마다 Connection을 생성해줘야 하고, 중복되는 코드가 많아 실제 개발에는 잘 안쓰이는 느낌이다.

이 JDBC를 사용하기 쉽게 만들어주는 것이 Mybatis이다. 아까 말했듯, SQL Mapper에 해당한다. JDBC로 처리하는 부분의 일부를 코드와 파라미터 설정으로 매핑을 대신 해준다.

장점

  • 학습이 쉽다.
  • 소스코드와 sql을 분리할 수 있다.

단점

  • 반복적인 작업이 반복된다.


JPA (Java Persistent API)

Java ORM 기술에 대한 API 표준 명세로, 이것 또한 Java에서 제공하는 API이다. JPA는 Java Persistence API의 약자로, 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.

사용할 때 JPA, Spring Data JPA, Hibernate를 혼동하기 쉽다.


Hibernate는 JPA라는 명세의 구현체이다

JPA와 Hibernate는 마치 자바의 interface와 해당 interface를 구현한 class와 같은 관계이다.

JPA는 자바 어플리케이션에서 RDBMS를 사용하는 방식을 정의한 인터페이스이다. 라이브러리가 아님! -> 구현이 없다. Spring Data JPA는 JPA를 쓰기 좋게 만들어놓은 모듈이다. JPA interface를 구현해 Repository라는 인터페이스를 제공한다. Hibernate는 JPA의 구현체이다. 장점

  • CRUD 쿼리를 자동으로 생성해준다.
  • Entity에 속성만 추가해준다면 쿼리를 건들 필요가 없다.

단점

  • 상대적으로 학습이 어렵다.
  • 복잡한 쿼리 작성이 어렵다고 한다.

ORM은 관계형 데이터베이스에서 관계를 Object에 반영하자가 목적인 반면, SQL Mapper은 단순히 필드를 매핑시킨 것이 목적이다.

JPA 처리를 담당하는 Repository는 기본적으로 4가지가 있다.

(T : @Entity의 타입클래스/ ID : P.K 값의 Type )

Repository<T, ID>

CrudRepository<T, ID>

PagingAndSortingRepository<T, ID>

JpaRepository<T, ID>

Spring Data JPA는 JPA를 쓰기 편하게 만들어놓은 모듈이다

필자는 Spring으로 개발하면서 단 한 번도 EntityManager를 직접 다뤄본 적이 없다. DB에 접근할 필요가 있는 대부분의 상황에서는 Repository를 정의하여 사용했다. 아마 다른 분들도 다 비슷할 것이라 생각한다. 이 Repository가 바로 Spring Data JPA의 핵심이다.

Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로, 개발자가 JPA를 더 쉽고 편하게 사용할 수 있도록 도와준다. 이는 JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다. 사용자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

Spring Data JPA가 JPA를 추상화했다는 말은, Spring Data JPA의 Repository의 구현에서 JPA를 사용하고 있다는 것이다. 예를 들어, Repository 인터페이스의 기본 구현체인 SimpleJpaRepository의 코드를 보면 아래와 같이 내부적으로 EntityManager을 사용하고 있는 것을 볼 수 있다.

package org.springframework.data.jpa.repository.support;

import ...

public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

    private final EntityManager em;

    public Optional<T> findById(ID id) {

        Assert.notNull(id, ID_MUST_NOT_BE_NULL);

        Class<T> domainType = getDomainClass();

        if (metadata == null) {
            return Optional.ofNullable(em.find(domainType, id));
        }

        LockModeType type = metadata.getLockModeType();

        Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

        return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
    }

    // Other methods...
}

Filter, AOP, Interceptor 차이

Spring

1. Filter(필터)

요청과 응답을 거른뒤 정제하는 역할을 한다. 필터는 스프링 컨텍스트 외부에 존재하여 스프링과 무관한 자원에 대해 동작한다.

보통 web.xml에 등록하고, 일반적으로 인코딩 변환 처리, XSS방어 등의 요청에 대한 처리로 사용된다.

<filter>

    <filter-name>encoding</filter-name>

    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

    <init-param>

        <param-name>encoding</param-name>

        <param-value>UTF-8</param-value>

    </init-param>

</filter>

<filter-mapping>

    <filter-name>encoding</filter-name>

    <url-pattern>/*</url-pattern>

</filter-mapping>

2. Interceptor(인터셉터)

인터셉터는 스프링의 DistpatcherServlet이 컨트롤러를 호출하기 전, 후로 끼어들기 때문에 스프링 컨텍스트(Context, 영역) 내부에서 Controller(Handler)에 관한 요청과 응답에 대해 처리한다.

스프링의 모든 빈 객체에 접근할 수 있다.

인터셉터는 여러 개를 사용할 수 있고 로그인 체크, 권한체크, 프로그램 실행시간 계산작업 로그확인 등의 업무처리

  • 인터셉터의 실행메서드

  • preHandler() - 컨트롤러 메서드가 실행되기 전

  • postHanler() - 컨트롤러 메서드 실행직 후 view페이지 렌더링 되기 전

  • afterCompletion() - view페이지가 렌더링 되고 난 후

3. AOP

OOP를 보완하기 위해 나온 개념

객체 지향의 프로그래밍을 했을 때 중복을 줄일 수 없는 부분을 줄이기 위해 종단면(관점)에서 바라보고 처리한다.

주로 ‘로깅’, ‘트랜잭션’, ‘에러 처리’등 비즈니스단의 메서드에서 조금 더 세밀하게 조정하고 싶을 때 사용합니다.

Interceptor와 Filter는 주소로 대상을 구분해서 걸러내야하는 반면, AOP는 주소, 파라미터, 애노테이션 등 다양한 방법으로 대상을 지정할 수 있다.

AOP의 Advice와 HandlerInterceptor의 가장 큰 차이는 파라미터의 차이다.

Advice의 경우 JoinPoint나 ProceedingJoinPoint 등을 활용해서 호출한다.

반면 HandlerInterceptor는 Filter와 유사하게 HttpServletRequest, HttpServletResponse를 파라미터로 사용한다.

Aspect-Oriented Programming의 약자이다. 흩어진 Aspect들을 모아서 모듈화 하는 기법이다. 흩어진 기능들을 모을 때 사용하는 것이 Aspect이다. 각각 Concern 별로 Aspect를 만들어주고, 어느 클래스에서 사용하는 지 입력해주는 방식이다.

AOP는 객체지향적인 사고에서 관점지향적인 사고를 덧붙인 것으로 서로 다른 3개의 객체에 모두 로그라는 기능이 필요할때 3개의 객체에 모두 로그 기능을 추가해야 한다. 즉 중복코드가 발생하게되어 유지보수에 어려움을 더하게 된다. 이 때 중심 기능 (비즈니스로직)과 부가 기능(로그,에러처리,트랜잭션)을 관점에 따라 분리한 것이다. 따라서 중요한지 중요하지 않은지에 대한 관점으로 특정 부분을 모듈화하여 분리 해낸것이 AOP적인 코드가 되는 것이다.

@Component
@Aspect
public class PerfAspect {

@Around("execution(* com.saelobi..*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed(); // 메서드 호출 자체를 감쌈
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}

위의 코드에서 PerfAspect 라는 관점지향적인 클래스를 하나 선언하고 Around 어노테이션을 통해 타겟클래스(중심 기능)의 앞뒤에 호출되게끔 설정한 후 해당 기능이 적용될 범위를 파라미터로 작성하면 해당 영역에 있는 메소드들이 호출 되기 전 후에 시간측정을 하는 부가기능이 수행되도록 설정하는 것이다

AOP의 포인트컷

@Before: 대상 메서드의 수행 전

@After: 대상 메서드의 수행 후

@After-returning: 대상 메서드의 정상적인 수행 후

@After-throwing: 예외발생 후

@Around: 대상 메서드의 수행 전/후



스프링의 주요 특징

  • POJO 기반의 구성 :
    POJO를 사용하는 이유 :
    • 코드의 간결함 (비즈니스 로직과 특정 환경/low 레벨 종속적인 코드를 분리하므로 단순하다.)
    • 자동화 테스트에 유리 (환경 종속적인 코드는 자동화 테스트가 어렵지만, POJO는 테스트가 매우 유연하다.
    • 객체지향적 설계의 자유로운 사용

    POJO를 이용한 애플리케이션 개발이 가진 특징과 장점을 그대로 살리면서 EJB에서 제공하는 엔터프라이즈 서비스와 기술을 그대로 사용할 수 있도록 도와주는 프레임워크 -> 하이버네이트와 스프링

    • 스프링 플랫폼의 이점에 대한 예제

    트랜잭션 API를 사용하지 않고도 데이터베이스 트랜잭션에서 자바메소드를 실행하도록 만든다. 원격 API를 사용하지 않고도 로컬 자바메소드를 원격 프로시저로 만든다 JMS API를 사용하지 않고도 로컬 자바메소드를 메시지 핸들러로 만든다.

  • 의존성 주입(DI)을 통한 객체 간의 관계 구성
    개발자는 의존적인 객체들과의 관계를 직접 처리할 필요가 없고, 인터페이스를 활용해서 유연한 구조를 사용할 수 있습니다.

    스프링은 의존성 주입을 프레임워크에서 처리하기 때문에 개발자는 자신이 만드는 객체나 클래스 외에는 신경 쓰지 않고 코드를 만들고,

    자신의 코드에 필요한 객체는 스프링을 통해서 주입받는 구조로 작성됩니다.

  • AOP(Aspect-Oriented-Programming) 지원
    반드시 처리가 필요한 부분을 스프링에서는 ‘횡단 관심사’라고 하며 스프링은 이러한 횡단 관심사를 분리해서 제작하는 것이 가능합니다.

    AOP(Aspect-Oriented-Programming)는 이러한 횡단 관심사를 모듈로 분리하는 프로그래밍의 패러다임입니다.

  • 편리한 MVC 구조

  • WAS에 종속적이지 않은 개발 환경

왜 Spring을 컨테이너라 할까?

Spring은 하나의 프레임워크이다. 그런데 왜 Spring 컨테이너, IoC 컨테이너라는 말을 사용할까? 그렇다면 컨테이너의 정의는 무엇인가? 컨테이너는 보통 인스턴스의 생명주기를 관리하며, 생성된 인스턴스들에게 추가적인 기능을 제공하도록하는 것이라 할 수 있다.

다시말해, 컨테이너란 당신이 작성한 코드의 처리과정을 위임받은 독립적인 존재라고 생각하면 된다. 컨테이너는 적절한 설정만 되어있다면 누구의 도움없이도 프로그래머가 작성한 코드를 스스로 참조한 뒤 알아서 객체의 생성과 소멸을 컨트롤해준다.

Servlet 컨테이너는 Servlet의 생성, 생성 후 초기화, 서비스 실행, 소멸에 관한 모든 권한을 가지고 있다. 개발자들이 직접 Servlet을 생성하고 서비스하지는 않는다. 이처럼 Servlet 인스턴스에 대한 생명주기를 관리하는 기능을 가진다.

스프링 컨테이너는 스프링 프레임워크의 핵심부에 위치하며, 종속객체 주입을 이용하여 애플리케이션을 구성하는 컴포넌트들을 관리한다.

다시 말하면, 프로그래머가 작성한 코드는 컨테이너를 사용하게 됨으로서 프로그래머의 손을 떠나 컨테이너의 영역으로 떠나버리게 된다. (정확히 말하자면 컨테이너가 맘대로 객체를 생성하는 게 아니라 프로그램을 이용하는 이용자의 호출에 의해 컨테이너가 동작하게 되는 구조이다.)

스프링 컨테이너의 두 종류

  1. 빈팩토리 BeanFactory (org.springframework.beans.factory.BeanFactory)
  • DI의 기본사항을 제공하는 가장 단순한 컨테이너

    팩토리 디자인 패턴을 구현한 것. Bean(이하 빈) 팩토리는 빈을 생성하고 분배하는 책임을 지는 클래스

    빈 팩토리가 빈의 정의는 즉시 로딩하는 반면, 빈 자체가 필요하게 되기 전까지는 인스턴스화를 하지 않는다 (lazy loading, 게으른 호출)

  1. 어플리케이션 컨텍스트 ApplicationContext


Spring Cloud Config를 이용해서 설정값 별도 관리하기

Spring 어플리케이션을 개발할 때, 설정파일을 .properties 나 .yml 로 관리하고 있습니다. 추가적으로 이 파일들을 각 profile별로 분리하여 각 시스템 환경에 따라서 적용되도록 관리하고 있는데, 이런 정적 파일로 관리되는 것들의 단점은 설정값에 변경사항이 발생한 경우, 빌드/배포를 다시 해야한다는 점입니다. 또한 혹시 모를 보안적인 이슈로 인하여 DB접속정보라던지 중요 Key값에 대한 정보가 담긴 설정파일이 유출 될 경우 심각한 보안사고를 초래할 수 있습니다.

이러한 문제들을 해결해 줄 수 있는 것이 Spring Cloud Config 입니다. 방식은 이렇습니다.

  1. 제공 할 설정파일들을 관리하는 git repository를 생성합니다. (당연히 Private Repository)
  2. Spring Cloud Config 의존성을 가진 config 서버를 생성합니다.
  3. config 서버는 위 생성한 git에 연결하여 설정파일들을 읽어옵니다.
  4. 설정정보들을 수신할 어플리케이션들은 config 서버에서 본인들의 설정들을 받아와서 사용합니다.
  5. 중간에 설정이 바뀌는 일이 발생하면 config 서버만 변경해주고 어플리케이션들은 설정을 갱신하면 재배포 없이 변경 된 설정을 이용할 수 있습니다.

이렇게 spring cloud config 를 사용하면 중요 민감정보 뿐만 아니라 서비스 별 / profile 별로 관리해야하는 환경설정 파일들을 중앙에서 한 곳에서 관리할 수 있는 이점과 재배포 없이 반영할 수 있는 이점을 얻을 수 있습니다. 프로그램 중단과 같이 민감한 작업들을 쉽게 관리할 수 있어 정말 편리한 서비스인 것 같습니다.

Spring에서 DB connection 방법

  1. JDBC 만을 이용한 접속 테스트
    Drivermanager 을 통해 connection 생성
 <!-- MySQL connector/j -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>


public class MySQLConnectionTest {
    
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/repacat_schema";
    private static final String USER = "repacat";
    private static final String PW = "repacat";
    
    @Test
    public void testConnection() throws Exception {
        Class.forName(DRIVER);
        
        try(Connection conn = DriverManager.getConnection(URL, USER, PW)) {
            System.out.println(conn);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
}

  1. Spring 에서 dataSource 를 정의하고 이를 통한 접속 테스트
    root-context.xml 에 dataSource bean 을 선언하여 DI 받을 수 있도록 설정한다. dataSource bean 은 spring-jdbc 모듈에 있는 클래스(org.springframework.jdbc.datasource.DriverManagerDataSource)를 이용하여 JDBC 드라이버를 통해 MySQL 서버에 접속할 수 있게한다.
  <!-- dataSource 설정, spring-jdbc 모듈 사용, spring 에서 jdbc 를 통해 mysql 에 접속할 수 있게 함 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/repacat_schema"></property>
        <property name="username" value="repacat"></property>
        <property name="password" value="repacat"></property>
    </bean>


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class DataSourceTest {
    
    @Inject
    private DataSource ds;
    
    @Test
    public void testConnection() throws Exception {
        try(Connection conn = ds.getConnection()) {
            System.out.println(conn);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
  1. Spring 에서 MyBatis 를 설정하고 이를 이용한 접속 테스트
  • Filter을 사용한 logging 처리 참고 블로그 https://velog.io/@sixhustle/log