스프링 프레임워크의 특징 3가지에는 어떤 것이 있을까?
너무 많은 특징 중 3개정도만 간추려 보았다.
- ApplicationContext
- 스프링은 ApplicationContext라는 IOC 컨테이너를 사용합니다. 이 컨테이너 덕분에 우리는 모든 API에 대해 설정 구성을 일일히 해 주지 않아도 됩니다. 또, 빈의 생성이나 관계를 설정해 주는 일에 대해 해방될 수 있고, 결국 과거에 위와 같은 코드를 짰던 시간만큼 조금 더 빠른 웹 어플리케이션을 만들 수 있습니다. 또한 확장성도 높아집니다.
- DI
- DI는 IOC와 함께 스프링의 핵심 개념 중 하나입니다. 세션에서 배웠듯 우리가 singleton 패턴을 사용해 stateless하게 설계해야하는 것들이 많습니다. 그러나 우리가 배운 SOLID 원칙을 지키려고 하거나 DI없이 api를 설계할 때면, 이러한 원칙들이 부딪히는 경우가 종종 등장합니다. DI는 이것들을 해결해 줍니다.
- 영속성과 관련된 다양한 인터페이스
- 스프링은 영속성과 관련된 다양한 라이브러리를 지원합니다. 데이터베이스의 가장 핵심적인 원칙은 ‘무결성’이라고 배웠습니다. 그 말마따나 데이터베이스에는 아무 데이터나 집어넣으면 안됩니다. 그러나 스프링은 자바의 (때로는 너무 깐깐한) 데이터 타입의 이점을 십분 활용해서, 그에 맞게 개발된 다양한 라이브러리를 제공하여 데이터베이스와 관련된 행위들을 사용자가 안심하고 진행할 수 있게 합니다.
스프링에서 DI (의존성 주입) 를 사용하는 이유가 무엇일까?
항해와 뗄레야 뗄 수 없는 기술매니저, 담임매니저로 예를 들어보겠습니다.
우리 C반이 하는 모든 작업을 코드로 작성한다고 가정했을 때,
우리 11 → 9조는 이번 주를 김태현 기술매니저님, 송민진 담임매니저님과 함께 하고 있죠?
그럼 상담, 시험, 공지안내 등은 송민진 인스턴스를 생성해서 코드를 작성해야 할 것이고
또 기술매니저 순회등은 김태현 인스턴스를 생성해서 코드를 작성해야 할 것입니다.
만약 9조 주차별 조 평가형식이 있다면 송민진 인스턴스와 김태현 인스턴스를 둘 다 생성해서 코드를 작성해야 합니다.
그런데 만약 송민진 매니저님이 아침에 저를 깨우다 지쳐서 담임매니저를 때려친다면?
우리가 작성한 모든 코드에서 송민진 매니저님을 지우고 새로운 매니저 인스턴스를 생성해주는 코드로 바꿔야 할 것입니다.
또 만약 다음 주차로 가서 김태현 매니저님이 아닌, 진유진 기술매니저님으로 바뀐다면?
수많은 코드들을 매주 지우고 매주 새로 짜야 할 것입니다.
그러나 인스턴스 생성 방식이 아닌, 매니저 인터페이스를 주입받고 맨 마지막에
담임매니저 = 송민진, 기술매니저 = 김태현으로 구체화해 준다면?
아 그냥 상상만 해도 누가 매니저하든 상관이 없을 것입니다.(제 의견 아님)
그래서 벌써 이렇게만 해도 두 가지 이점이 생깁니다.
- 수많은 코드를 덜 교체해도 됨
- 코드를 처음 보는 사람은 누가 기술매니저인지 누가 운영매니저인지 뭔 역할을 하는지 알수가없음
ORM, JPA, 그리고 Spring Data JPA
**ORM**은 객체(Instance)와 관계형 데이터베이스(RDBMS)의 데이터를 자동으로 매핑해주는것을 말합니다. 서블릿 컨테이너가 Web Request와 Servlet Component를 자동으로 매핑해주듯이요. 그래서 Object Relational Mapping입니다.
근데 생각해보니 이상하지 않나요?
Web Request가 “localhost:8080/api/hanghae”로 GET 메서드를 통과한다 치면 서블릿 컨테이너가 어 그래 /api/hanghae구나 어 그래 GET Method구나 해서 연결시켜주면 된다고 치고 (요청과 응답의 url type은 String으로 동일하니까)
@RestController
@RequestMapping("/api")
public class HangHaeController{
@GetMapping("/hanghae")
public String cry(){
return "응애"
}
}
우리는 자바 객체를 쓰니까 class를 사용하고, RDBMS는 table을 사용하는데(객체 모델과 관계형 모델 간의 불일치) 이 친구가 어떻게 알고 맵핑을 시켜주는 걸까요?
**JPA**라는 친구가 무한SQL 마법을 써서 알아서 해주는 거랍니다.
JPA 안의 JDBC API라는 친구가 SQL문을 써서 알아서 DB에 접근을 해주게요.
우리는 이제 JOIN문을 안쓰고도 관계 Annotation만 써주면 JPA가 알아서 이 문제를 해결 해 줍니다.
JPA의 장점이 뭘까요? 며칠전에 in-line memory를 써서 h2 데이터베이스로 데이터를 삽입해줬습니다.
근데 문제점이 뭐냐 : 끄면 내 데이터 다 사라짐, 누가 내 데이터 저장된거 보여주세요 하면 다시 SQL문써서 데이터 만들고 JOIN 쿼리로 데이터베이스의 table key값 연동시켜주는 수고를 해야함
그래서 h2같은 데이터베이스가 종료되고 걍 컴 껐다키고 U+가 내 인터넷을 조졌다가 다시 연결시켜줘도 알아서 관계가 맺어질 수 있도록 객체-테이블에 매핑을 해 놓은것입니다.
⇒ 그래서 휘발성이 아닌 영속성(Persistant) 이라는 말을 쓰고, 이걸 자바에서 제공한다고 해서
Java Persistant Api라는 단어가 나오게 된거죠.
또 여기 JPA와 다른 ORM인 MyBatis의 비교도 있습니다.
그럼 **Spring Data JPA**란 무엇일까요?
아까 위에서 제가 스프링 프레임워크의 장점에 대해서 작성할 때, 스프링 프레임워크는 영속성 데이터베이스와 관련된 다양한 인터페이스와 라이브러리를 지원하는 장점이 있다고 썼습니다.
이 Spring Data JPA는 JPA와 관련된 스프링에서 제공하는 모듈입니다.
이번주차 항해 인강을 들을 때, 요청이 들어오면 Entity Manager Factory라는곳에서 노예(EntityManager)들을 만들어서 이 친구들이 DB connection pool에 접근해서 DB를 건드린다는 설명을 들으셨을 겁니다.
그런데 우리가 지난주차에 숙제로 제출한 코드를 봅시다.
<Repository>
public interface LetterRepository extends JpaRepository<Letter, Long> {
List<Letter> findAllByOrderByModifiedAtDesc();
}
<Service>
@Service
@RequiredArgsConstructor
public class LetterService {
private final LetterRepository letterRepository;
@Transactional(readOnly = true)
public List<LetterResponseDto> findAll(){ // service Layer에서 Entity >> DTO 변환작업, 사유 : LazyInitializationException 위험부담 줄임
return letterRepository.findAllByOrderByModifiedAtDesc().stream().map(LetterResponseDto::new).collect(Collectors.toList());
}
}
우리는 EntityManager를 쓴 기억조차 없죠. 왜일까요? 우리가 Extend한 JpaRepository에 그 답이 있습니다.
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...
}
JPA Repository에서 이런 엔티티 코드를 다 만들어놓고 우리에게 적당한 타입과 ID만 넣어 주면 이 메소드를 자동으로 오버라이딩하게 만들어놔서 그렇습니다.
Spring Data JPA가 없었다면? 우리는 저 한줄로 끝낸 findAll 메소드를 다음과 같이 써야 할 것입니다.
public abstract class GenericDAOImpl<T, id> implements GenericDAO<T, id> {
private Class<T> type;
@PersistenceContext
protected EntityManager entityManager;
public GenericDAOImpl(Class<T> type) {
super();
this.type = type;
}
... save and delete classes go here
@Override
public List<T> findAll(T2 where) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
Root<T> rootQuery = criteriaQuery.from(type);
if (where != null) {
EntityType<T> entity = entityManager.getMetamodel().entity(type);
SingularAttribute<? super T, ?> attribute = null;
for (SingularAttribute<? super T, ?> singleAttribute: entity.getSingularAttributes()) {
// loop through all attributes that match this class
if (singleAttribute.getJavaType().equals(where.getClass())) {
// winner!
attribute = singleAttribute;
break;
}
}
// where t.object = object.getID()
criteriaQuery.where(criteriaBuilder.equal(rootQuery.get(attribute), where));
}
criteriaQuery.select(rootQuery);
TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
// need this to make sure we have a clean list?
// entityManager.clear();
return query.getResultList();
}
결론 : Spring Data JPA 쓰자
진짜 마지막
'웹개발' 카테고리의 다른 글
TIL - 스프링 부트와 리액트의 협업 "Hello Meohyun!" (1) | 2023.02.01 |
---|