01. Model 영역과 Spring
- -
이번 시간에는 스프링에서 Model 단 프로그래밍을 위한 내용에 대해 살펴보자.
Model
MVC 디자인 패턴
MVC 디자인 패턴이란 애플리케이션을 구성하는 가장 기본적인 패턴으로 애플리케이션을 Model, View, Controller 3가지 모듈로 분리해서 바라본다.
- Controller: 사용자의 입력을 처리하고 Model과 View 사이의 인터페이스 역할(@Controller)
- Model: 비즈니스 로직과 데이터 관리 로직을 처리하는 부분(@Service, @Repository)
- View: 사용자에게 보여지는 부분(JSP, Thymeleaf, ...)
이렇게 영역을 모듈로 분리해서 관리하는 이유는 대부분 모듈 교체에 따른 유지보수가 용이하게 하기 위함이다. 예를 들어 View를 구성할 때 JSP로 만들었다가 Thymeleaf로 변경해야 한다고 했을 때 MVC로 분리해 놓으면 Controller와 Model은 전혀 신경쓸 필요없이 View만 교체하면 된다.
View와 Controller는 Spring MVC 부분에서 살펴보고 여기서는 Model 부분에 대해서 알아보자.
Model
모델은 비지니스 로직을 다루며 일반적으로 Service Layer와 Data Access Layer로 나뉜다.
- Service Layer
- 기능 즉 비즈니스 로직을 구현하는 곳으로 컨트롤러에 의해 호출되며 필요에 따라 Data Access Layer 사용
- 업무의 단위로 트랜젝션 처리의 기점
- Data Access Layer
- 애플리케이션에서 데이터 저장소(RDB 등)에 접근하기 위한 계층으로 Service Layer에 의해 호출됨
- 단일 쿼리 단위의 작업 처리를 담당하는 DAO(Data Access Object) 또는 Repository 작성
Model을 Service Layer와 Data Access Layer로 구분하는 이유 역시 교체 시 발생하는 유지 보수 비용을 줄이기 위해서이다. 만약 Service Layer와 Data Access Layer가 하나로 합쳐져 있다고 생각해보자. 데이터를 RDB에 저장할 계획으로 작성하다가 NoSqlDB로 변경해야 한다면 Service 영역에서도 많은 변경이 필요하다. 하지만 둘을 Interface 기반으로 분리해서 작성했다면 RDB를 활용했던 객체를 NoSQL을 활용하는 객체로 변경하면 된다.
Service Layer를 위한 Spring
@Service
Spring은 Service Layer를 위해서 @Service라는 스테레오타입 애너테이션을 제공한다. @Service는 특별히 막강한 기능을 갖는다기 보다는 @Component 보다는 가독성을 높이고 해당 클래스가 명확히 서비스 계층의 역할을 한다는 것을 알린다. 또한 AOP의 Pointcut에서 within 지정자를 이용하면 해당 애너테이션이 선언된 빈만을 대상으로 트랜젝션, 보안 처리 등 공통 관심사를 손쉽게 처리할 수 있다.
예를 들어 모든 Service Layer 빈에서 동작할 AOP를 작성하고 싶다면 다음과 같이 pointcut을 사용할 수 있다.
@Pointcut("within(@org.springframework.stereotype.Service *)")
public void serviceLayer() {
// do something
}
@Transactional
Service Layer는 업무 단위로 작성하며 여러가지 Data Access Layer의 메서드들을 호출할 수 있다. 이 과정에서 반드시 처리해야 할 내용이 트랜젝션이다. 트랜젝션을 처리하기 위한 JDBC의 코드는 아래처럼 상당히 복잡하다.
Connection con = DBUtil.getUtil().getConnection();
try{
con.setAutoCommit(false); // 트랜젝션 처리 시작
cityRepo.delete(con, code); // repository 호출 1
countryRepo.delete(con, code); // repository 호출 2
con.commit(); // 성공 시 commit
}catch(Exception e){
DBUtil.getUtil().rollback(con); // 실패 시 rollback
}finally{
DBUtil.getUtil().close(con); // 자원 반납
}
Spring에서는 @Transactional이라는 애너테이션을 통해 복잡한 트랜젝션 관리 로직을 작성하지 않아도 되며 데이터의 일관성, 무결성을 보장할 수 있다.
보안 처리 지원
@Service는 빈이기 때문에 다양한 Spring Security를 적용할 수 있다. Spring Security는 인증 및 인가와 관련된 보안 기능을 제공한다. 예를 들어 다음은 ADMIN이라는 권한이 있는 사용자만 접근할 수 있도록 구성한 것이다.
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Secured("ROLE_ADMIN")
public void adminOnlyMethod() {
// 관리자 권한이 있는 사용자만 접근 가능한 메서드
}
}
Data Access Layer를 위한 Spring
@Repository
Spring에서 Data Access Layer를 구성하는 빈을 나타낼 때는 @Repository를 사용한다. @Service와 마찬가지로 @Repository를 통해 빈의 역할에 대해 가독성을 줄 수 있으며 좀 더 특별한 빈으로 관리할 수 있다.
대표적인 기능으로 빈에서 발생하는 예외를 Spring의 DataAccessException으로 자동으로 변환해서 던져줄 수 있다. 따라서 개발자가 예외를 직접 잡아서 처리하는 코드를 작성하지 않아도 되며 예외를 일관된 방식으로 처리할 수 있다.
https://goodteacher.tistory.com/741
또한 Spring Data에서도 @Repository를 사용하여 JPA, MongoDB 등에 대한 저장소를 쉽게 구현할 수 있도록 PSA를 제공한다.
범용적이고 체계적인 예외 처리 지원
Spring은 범용적이고 체계적인 예외 처리를 지원한다. 기존의 JDBC 코드는 어떤 DB를 사용하느냐에 따라 제각각의 오류 코드를 가지고 있었기 때문에 세밀한 컨트롤에 어려움이 많았다. 스프링에서는 이런 예외들을 모두 매핑해서 체계적으로 예외를 관리하기 때문에 스프링에서 던져주는 예외만 분석하면 되도록 깔끔하게 처리되었다.
Data Access Layer에 대한 스프링의 예외는 RuntimeException을 상속 받은 DataAccessException을 필두로 예외의 이름만 봐도 어떤 예외가 발생했는지 쉽게 알 수 있다.
다음은 MySql에 대한 에러 코드 정리의 예이다.
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>1054,1064,1146</value>
</property>
<property name="duplicateKeyCodes">
<value>1062</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>1</value>
</property>
<property name="cannotAcquireLockCodes">
<value>1205</value>
</property>
<property name="deadlockLoserCodes">
<value>1213</value>
</property>
</bean>
에러 코드에 대해 좀 더 자세히 알고 싶은 경우 [spring frramework에서의 sql error codes]을 참조하자.
스프링의 다양한 기술 지원
Data Access Layer를 사용하기 위해서는 가장 단순한 기술로는 순수 JDBC가 사용된다. 하지만 단순 반복되는 뻔한 코드들이 많기 때문에 실제로는 MyBatis 같은 Sql Mapping 프레임워크나 JPA 같은 OR Mapping 프레임워크로 JDBC 코드를 래핑해서 사용한다. Spring에서는 어떤 기술을 사용하던 그 기술과 연동할 수 있는 모듈을 제공해서 사용하는데 어려움이 없게 처리해준다.
일반적으로 Spring JDBC는 잘 사용되지 않고 MyBatis냐 JPA냐의 고민에 많이 빠지게 되는데 전통적으로 우리나라에서는 MyBatis가, 글로벌하게는 JPA가 점유율이 높았으며 점점 JPA의 사용도가 올라가는 추세이다.
구현 기술의 결정
이제 Spring의 Model에 대한 전략을 알았다면 본격적으로 Data Access Layer를 구현할 기술을 결정할 때이다.
Java를 이용해서 DB를 사용하기 위해서는 JDBC라이브러리를 이용하는게 필수다. 하지만 JDBC를 이용해서 직접 프로그래밍을 하는 경우는 대학 졸업 이후에는 아마 거의 없을 것 같다. 대부분 좀 더 편리한 프로그래밍을 위해서 MyBatis나 JPA로 JDBC를 랩핑해서 사용한다.
간단히 두 녀석의 차이점에 대해 살펴보자.
비교 항목 | MyBatis | JPA |
쿼리 | 개발자가 직접 SQL을 작성 - SQL 최적화가 용이하며 복잡한 쿼리 사용에 문제가 없음 |
객체 지향 쿼리 언어(JPQL) 사용, SQL이 자동 생성 - 자동생성되는 SQL에 대한 최적화가 제한적이며 복잡한 쿼리 작성이 어려움 |
객체 매핑 | 직접 매핑 코드 작성 | 객체와 테이블 간의 매핑을 자동으로 관리 |
용도 | 복잡한 쿼리와 절차적인 데이터 처리에 유리 | 객체 지향적인 개발로 엔티티와 비즈니스 로직이 밀접하게 연관된 경우 유리 |
학습 곡선 | 상대적으로 직관적이고 쉽게 접근 가능 | 객체 지향적인 접근 방식과 생명 주기 관리 등 복잡한 개념으로 인해 학습 곡선이 높음 |
막연하게 'JPA는 SQL을 직접 작성하지 않고 매핑도 자동으로 해주니까 좋더라'라고만 생각하고 JPA를 선택하면 문제가 생길 수 있다. 물론 JPA가 최근의 추세이지만 우리나라에서는 여전히 MyBatis로 진행되는 프로젝트가 많다. 특히 복잡한 쿼리를 JPA로 작성하려면 쿼리 자체가 복잡해져서 MyBatis로 가기도 한다.
각 기술의 장/단점이 있으므로 프로젝트의 요구사항과 개발 환경에 따라 적합한 기술을 선택하는 것이 좋다.
Spring 진영에서는 Spring Data라는 데이터 활용과 관련된 하위 프로젝트가 있는데 여기에는 Spring Data JPA가 포함되어 있다. 따라서 앞으로는 JPA 활용 쪽으로 살펴보자.
'Spring Model > 01. Model' 카테고리의 다른 글
@Repository의 역할 (0) | 2024.03.22 |
---|---|
Connection Pool (0) | 2024.03.22 |
Entity vs VO vs DTO (0) | 2023.10.19 |
03. 프로젝트 구성 (0) | 2022.11.13 |
02. Spring Data와 Spring Data JPA (0) | 2020.06.22 |
소중한 공감 감사합니다