[인프런 워밍업 클럽 스터디 1기] 두번째 발자국
1. 2주차 학습한 내용
[6일차 - 스프링 컨테이너의 의미와 사용 방법]
1. UserController와 스프링 컨테이너
스프링 빈이란?
서버가 시작되면, 스프링 서버 내부에 컨테이너를 생성한다.
컨테이너 안에 클래스가 들어간다.
이때 다양한 정보도 들어있고, 인스턴스화도 이루어진다.
들어간 클래스를 스프링 빈이라고 한다.
JdbcTemplate 의존성 주입이 되어있다.
JdbcTemplate을 스프링 빈으로 등록해주는 설정 코드가 있다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
서버 시작 시
스프링 컨테이너 시작 -> 기본적으로 많은 스프링 빈 등록 -> 우리가 설정한 스프링 빈(UserController) 등록 -> 의존성 자동으로 설정
스프링 빈으로 등록
각 클래스가 JdbcTemplate을 가져오기 위해 스프링 빈으로 등록한다.
어노테이션 추가한다.
2. 스프링 컨테이너를 왜 사용할까?
Interface 활용
BookController는 BookService을 사용한다.
BookService는 BookRepository 인터페이스 사용한다.
또 다른 Repository(Repository1, Repository2)가 있다.
BookService 코드를 변경하지 않고 BookRepository을 변경할 수 있는 방법이 스프링 컨테이너이다.
스프링 컨테이너 사용시
컨테이너가 Repository1 또는 Repository2 중 하나를 선택한 후 BookService를 생성해준다.
이러한 방식을 제어의 역전(IoC, Inversion of Control)이라고 한다.
컨테이너가 BookService를 생성할 때, Repository1와 Repository2 중 하나를 선택해서 넣어준다.
이러한 과정을 의존성 주입(DI, Dependency Injection)이라도 한다.
우선권을 부여하는 어노테이션 활용
@Primary : 우선권을 결정하는 어노테이션을 의미한다.
@Primary 어노테이션을 사용 시 Service 코드 변경없이 해당 Repository를 사용할 수 있다.
3. 스프링 컨테이너를 다루는 방법
빈을 등록하는 방법
@Configuration
클래스에 붙이는 어노테이션이다.
@Bean을 사용할 때 함께 사용해야 한다.
@Bean
메서드에 붙이는 어노테이션이다.
메서드에서 반환하는 객체를 스프링 빈에 등록한다.
@Configuration과 @Bean 사용하는 경우
외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.
@Component
주어진 클래스를 컴포넌트로 간주한다.
이 클래스들은 스프링 서버를 시작할때 자동으로 감지된다.
언제 @Component가 사용될까?
Controller, Service, Repository가 아닌 추가적인 클래스를 스프링 빈으로 등록할 때 종종 사용된다.
스프링 빈 주입 받는 방법
생성자 사용(@Autowired 생략 가능) -> 가장 권장한다.
@Setter와 @Autowired 사용 -> 누군가 Setter를 사용하면 오작동할 수 있다.
필드에 직접 @Autowired 사용 -> 테스트를 어렵게 만드는 요인이다.
@Qualifer("value") 사용
여러가지 후보군이 있을 때 특정 하나를 가져오고 싶을때 사용한다.
스프링 빈을 사용하는 쪽과 스프링 빈을 등록하는 쪽 모두 사용할 수 있다.
적어준 값이 동일한 것끼리 연결된다.
@Primary와 @Qualifer 함께 사용할 경우
우선권을 결정해주는 @Primary와 @Qualifer을 함께 사용했을 때 누가 사용될까?
@Qualifer을 사용한다.> 스프링 빈을 사용하는 쪽에서 특정 빈을 지정해준 것의 우선순위를 더 높게 간주한다.
[7일차 - Spring Data JPA를 사용한 데이터베이스 조작]
1. 문자열 SQL을 직접 사용하면 좋지 않은 이유
문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다.
컴파일 시점에 발견되지 않고 런타임 시점에 발견된다.
특정 데이터베이스에 종속적이게 된다.
DB의 종류마다 문법이 조금씩 다르다.
반복 작업이 많아진다.
테이블 하나 만들 때마다 CRUD 쿼리가 항상 필요하다.
데이터베이스의 테이블과 객체는 패러다임이 다르다.
2. JPA(Java persistence API)
JPA란?
자바 진영의 ORM(Object-Relational Mapping) 기술 표준을 의미한다.
객체와 관계형 DB의 테이블을 매핑하여 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙을 의미한다.
Hibernate
규칙을 구현한 구현체를 Hibernate(하이버네이트)라고 한다.
Hibernate는 내부적으로 JDBC를 사용한다.
3. 유저 테이블에 대응되는 Entity Class 만들기 - Java 객체와 MySQL Table 매핑하기
JPA 어노테이션
@Entity : 저장되고, 관리되어야 하는 데이터를 의미한다.
@Id : 해당 필드를 primary key로 간주한다.
@GeneratedValue : primary key는 자동 생성되는 값이다.
@Column(생략 가능) : 객체의 필드와 Table의 필드를 매핑한다.
기본 생성자 추가
기본 생성자도 추가해야 한다.
@Entity
public class User {
protected User() {
}
}
application.yml - JPA 설정 옵션 추가
spring:
jpa:
hibernate:
ddl-auto: none
properties:
hibernate:
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
4. Spring Data JPA 사용 - 자동으로 쿼리 전송하기
SQL을 작성하지 않고 유저 생성/조회/업데이트 기능 리팩토링하기
유저 생성 기능 리팩토링하기
Interface UserRepository
인터페이스 UserRepository 생성 후 JpaRepository를 상속 받는다.
UserService
save() 메서드에 객체를 삽입하면 Insert SQL이 자동으로 전송된다.
저장이 된 후 User에는 id가 들어간다.
유저 조회 기능 리팩토링하기
UserResponse 생성자 추가
코드를 더 깔끔하게 만들기 위해 생성자를 추가한다.
UserService
findAll 메서드를 사용하면 모두 유저의 데이터를 조회하는 SQL이 전송된다.
그 결과, List가 반환되고, UserResponse로 변경해서 전달한다.
유저 수정 기능 리팩토링하기
User
findById를 사용하면 id를 기준으로 1개의 데이터를 가져온다.
Optional의 orElseThrow를 사용해 User가 없을 경우 예외처리한다.
객체를 수정해주고, save 메서드를 호출한다.
자동으로 UPDATE SQL이 전송된다.
SQL을 작성하지 않아도 동작 하는 이유
Spring Data JPA를 통해 SQL을 작성하지 않아도 동작한다.
Spring Data JPA : 복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리이다.
5. Spring Data JPA 사용 - 다양한 쿼리 작성하기
By 앞에 들어갈 수 있는 구절 정리
find : 데이터 1건을 가져온다. 반환 타입은 객체, Optional<타입>이다.
findAll : 쿼리의 결과물이 N개인 경우 사용한다. List<타입>을 반환한다.
exists : 쿼리 결과가 존재하는지 확인한다. 반환 타입은 boolean이다.
count : SQL의 결과 개수를 센다. 반환 타입은 long이다.
각 구절은 and 또는 or로 조합할 수 있다.
By 뒤에 들어갈 수 있는 구절 정리
GreaterThan : 초과
GreaterThanEqual : 이상
LessThan : 미만
LessThanEqual : 이하
Between : 사이에
StartsWith : ~로 시작하는
EndsWith : ~로 끝나는
[8일차 - 트랜잭션과 영속성 컨텍스트]
1. 트랜잭션(Transaction)
트랜잭션이란?
쪼갤 수 없는 업무의 최소 단위를 의미한다. (모두 성공하거나 실패하는 경우)
트랜잭션 동작 명령어
트랜잭션 시작하기 : start transaction;
트랜잭션 정상 종료하기 (SQL 반영) : commit;
트랜잭션 실패 처리하기 (SQL 미반영) : rollback;
2. 트랜잭션 적용과 영속성 컨텍스트
트랜잭션 적용하기
@Transactional 어노테이션을 사용한다.
@Service
public class UserServiceV2 {
//조회 - readOnly 옵션 사용 가능(SELECT 쿼리)
@Transactional(readOnly = true)
public List<UserResponse> getUsers() {
return userRepository.findAll().stream().map(UserResponse::new).collect(Collectors.toList());
}
}
메서드가 시작될때 Transactional 시작된다.
메서드 로직이 정상적으로 성공하면 commit되고, 문제가 생길 경우 rollback된다.
트랜잭션을 사용 하는 이유
트랜잭션이 없으면 코드가 한줄 한줄 반영된다.
오류가 생겨도 다른 코드는 반영된다.
주의사항
org.springframework.transaction.annotation.Transactional을 붙여야 한다.
IOException과 같은 Checked Exception은 롤백이 일어나지 않는다.
영속성 컨텍스트이란?
테이블과 매핑된 Entity 객체를 관리 및 보관하는 역할을 의미한다.
트랜잭션과 영속성 컨텍스트의 관계
트랜잭션 사용 -> 영속성 컨텍스트 시작
트랜잭션 종료 -> 영속성 컨텍스트 종료
영속성 컨텍스트의 특징
변경 감지(Dirty Check) : 영속성 컨텍스트 안에서 확인된 Entity는 명시적으로 save하지 않아도 변경을 감지해서 자동으로 저장한다.
쓰기 지연 : DB의 INSERT/UPDATE/DELETE SQL을 바로 전송하지 않고, 트랜잭션이 commit될 때 SQL을 모아서 한번만 전송한다.
1차 캐싱 : ID를 기준으로 Entity를 기억한다. 최초 1회만 쿼리가 전송되고, 나머지는 보관하고 있는 데이터를 활용한다. 이렇게 반환된 객체는 동일하다.
[9일차 - 조금 더 복잡한 기능을 API로 구성하기]
1. 책 생성/대출/반납 기능 API 개발하기
book 테이블 및 객체 생성, DTO, Repository, Service, Controller 구현
대출 기록에 대한 Table 추가, 대출 기록 Table에 대한 객체 생성, DTO, Repository, Service, Controller 구현
생성한 테이블로 구현이 충분하지만, DTO를 새로 만드는 게 낫다.
다른 기능들 중 한 기능에 변화가 생길 경우 더 유연하게 side-effect가 없이 대처가 가능하기 때문이다.
2. 미션
3. 회고
1주차보다 학습한 내용이 많아진 만큼 정리를 잘하고 공부를 많이 해야겠다고 느꼈다. 금요일에 해주신 특강도 너무 도움이 되었다. 내용이 나한테는 아직 어려웠지만 다른 사람들의 코드를 보면서 나도 좀 더 고민해보고 코드를 짜고 과제도 해봐야겠다.
댓글을 작성해보세요.