인프런 워밍업 0기 2주차 JPA와 트랜잭션
인프런 워밍업 2주차 정리 및 회고
강의 요약
자바 진영에서 객체지향적으로 코드를 작성할 수 있도록 도와주는
프레임워크인 스프링 부트와 ORM인 JPA를 활용할 수 있습니다.
JPA란
객체와 관계형 DB의 테이블을 매핑하여 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙을 JPA
라고 할 수 있습니다.
JPA는 말 그대로 API
이기 때문에 실제로 구현한 라이브러리중 하나인 Hibernate
를 사용합니다.
JPA나 Mybatis나 모두 JDBC
를 사용합니다.
JPA 사용하는 이유
SQLMapper를 사용하면 다음과 같은 단점이 있습니다.
1. 문자열로 작성하기 때문에 오류가 런타임 시점에 발견된다.
2. 특정 데이터베이스 SQL 문법을 사용하기 때문에 종속적이 된다.
3. CRUD를 작성하기 위한 반복작업이 많아진다.
4. 데이터베이스의 테이블과 객체의 패러다임이 다르다.
스프링 컨테이너
스프링 부트는 자바의 객체지향적인 코드를 작성하기 위해 사용하는 IoC 컨테이너
입니다.
IoC
는 제어의 역전 원칙이라고 합니다.
클래스의 단일책임의 원칙을 유지하기 위해서 클래스 내부 코드로 직접 의존성 객체를 생성하는게 아니라 인터페이스만 의존하도록 코드를 작성하고 외부에서 필요한 의존성을 주입하도록 하는 것을 말합니다.
스프링 컨테이너는 빈 오브젝트로 등록된 인스턴스를 관리하여 불필요한 인스턴스 생성을 줄이고,
빈 오브젝트 생성시 필요한 의존성을 대신 주입해주는 역할을 합니다.
트랜잭션와 영속성 컨텍스트
트랜잭션은 더 이상 나눌 수 없는 최소한의 작업 단위로
스프링 부트에서 @Transactional
을 추가하여 사용할 수 있습니다.
영속성 컨텍스트는 트랜잭션이 동작하여 하나의 작업이 완료되는 기준(`COMMIT`)으로 동작합니다.
영속성 컨텍스트는 테이블과 매핑된 객체(`Entity`)를 관리/보관하는 역할로
1. 변경 감지
2. 쓰기 지연
3. 1차 캐싱
기능을 제공합니다.
2주차에 접어들면서
2주차에 목표는 왜 사용하는지에 대해서 초점을 맞추고 학습을 했습니다.
이미 이전에 JPA에 대해서 학습했기 때문에 다시 되짚어보는 시간이 되었습니다.
1주차 주말에 쉬다보니 2주차를 시작할 때 집중하지 못할까봐 걱정을 했는데
2주차 시작인 월요일에 디스코드를 확인하러 들어가보니 모각공 탭에 다들 퇴근하고 밤 늦게까지 학습하시는걸 보고 다시 의지를 불태우게 되었습니다.
이미 배운 내용이라서 집중이 잘 되지 않아 다시 반복해서 들었던 기억이 있고, 아는 내용이라 생각하고 막상 코드로 작성해보려니 코드 작성이 어려웠습니다.
간단하게 스트림으로 풀수 있는 문제를 QueryDSL로 풀어보겠다고 하루를 날려보낸게 아직도 후회가 됩니다.
JPA를 학습해서 간단하게 해결할 수 있다고 생각했는데 SQLMapper와 다른 방향으로 문제를 해결해야하는게 생각보다 많아서 역시 코드를 직접 작성하지 않는 이상 내꺼가 아니라는걸 다시 느꼈습니다.
다음주는 연관관계와 배포가 주제인데 예전에 Jenkins랑 Ansible을 스터디로 배운걸 적용해서
직접 코드로 작성해보려고 합니다.
미션 회고
Service 계층이 Repository 계층을 의존할 때 직접 클래스를 의존하지 않고
인터페이스를 의존하면 두 계층의 결합도가 낮아지기 때문에 인터페이스를 구현한 다양한 구현체를 변경할 수 있다는걸 배웠습니다.
Repositoryinterface의 API로 INPUT 값과 반환값만 알면 서비스 로직은 거기에 따른 코드만 작성하면 되니까 서비스 로직은 수정하지 않고 변경이 가능했습니다.
그래서 JPARepository를 만들어 기존 코드에 연결을 해야하는 미션이 있었는데
스프링 부트는 JpaRepository
인터페이스를 상속한 인터페이스는 빈 오브젝트로 등록을 합니다.
결국 JpaRepository를 상속한 인터페이스를 의존하는건 JPA 구현 클래스를 직접 의존하는 것과
다를게 없다고 생각합니다.
물론 JPA를 사용하면 방언으로 다양한 데이터베이스를 JPQL과 메서드로 공용으로 사용할 수 있다고 했지만, 만약 QueryDSL이나 Jdbc을 사용하는 상황이 오고, 그리고 QueryDSL로 작성한 코드가
QueryDSL의 업데이트가 없다는 문제로 다른 Jpql 빌더를 사용해야 된다고 한다면
다른 계층에도 영향이 가기 때문에 리포지토리 계층은 인터페이스를 추가로 만들었습니다.
public interface FruitRepositoryInterface {
void save(String name, LocalDate warehousingDate, long price);
void updateSellStatus(long id) ;
List<Fruit> getSalesInfo(String fruitName);
long getAmountBy(String name);
List<Fruit> getFruitLte(Long price);
List<Fruit> getFruitGte(Long price);
}
이 인터페이스를 구현한 클래스를 만들고
JpaRepository를 상속한 인터페이스를 스프링 컨테이너를 통해 주입받는 방식으로 코드를 작성했습니다.
public interface FruitJpaRepository extends JpaRepository<Fruit, Long> {
List<Fruit> findAllByName(String name);
long countByName(String name);
List<Fruit> findAllByPriceGreaterThanEqual(Long price);
List<Fruit> findAllByPriceLessThanEqual(Long price);
}
리포지토리 구현체
@Primary
@Repository
public class FruitRepositoryImp implements FruitRepositoryInterface {
private final FruitJpaRepository repositoryJpa;
public FruitRepositoryJpaImp(FruitJpaRepository repositoryJpa) {
this.repositoryJpa = repositoryJpa;
}
@Override
public void save(String name, LocalDate warehousingDate, long price) {
Fruit fruit = new Fruit(name, price,null,"HAVING");
repositoryJpa.save(fruit);
}
@Override
public void updateSellStatus(long id) {
Fruit fruit = repositoryJpa.findById(id).orElseThrow(() -> new IllegalArgumentException("데이터 없음"));
fruit.selling();
repositoryJpa.save(fruit);
}
@Override
public List<Fruit> getSalesInfo(String fruitName) {
return repositoryJpa.findAllByName(fruitName);
}
@Override
public long getAmountBy(String name) {
return repositoryJpa.countByName(name);
}
@Override
public List<Fruit> getFruitLte(Long price) {
return repositoryJpa.findAllByPriceLessThanEqual(price);
}
@Override
public List<Fruit> getFruitGte(Long price) {
return repositoryJpa.findAllByPriceGreaterThanEqual(price);
}
}
이렇게 JpaRepository도 빈으로 주입받고, 관련된 ORM 기술도 해당 FruitRepositoryImp
에서 빈으로 주입받아 사용한다면 QueryDSL에서 다른 빌더나 다른 ORM 기술을 사용하더라도
서비스 계층의 코드는 수정없이 작성이 가능합니다.
추가로 좋은점은 테스트 코드를 작성하는데 있습니다.
JpaRepository를 상속한 인터페이스를 그대로 서비스 계층이 사용하면
Mock
없이 테스트 코드를 작성하여 서비스 계층만 단위 테스트로 작성할 때 JpaRepository<>
의 모든 인터페이스를 구현해야했고 필요한 메서드만 구현한다고 해도 가독성이 너무 떨어지게 됩니다.
그래서 리포지토리는 인터페이스를 추가로 만들어 로컬,MySQL-Jdbc,MySql-JPA를 자유롭게 변경할 수 있게됩니다.
미션 회고
테스트 코드를 작성하면서 테스트하기 편한 코드로 구조를 변경하다보니
조금 복잡할 수 있어도 인터페이스를 추가하면 다른 계층도 테스트하기 쉬워지는 구조가 된다는걸 느꼈습니다.
댓글을 작성해보세요.