[인프런 워밍업 스터디 클럽 1기] BE 2주차 회고록
두 번째 발자국
자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고
인프런 워밍업 클럽에 참여하여 쓰는 두 번째 회고록입니다.
학습 내용
static이 아닌 코드를 사용하려면 인스턴스화
를 해야합니다.
하지만, 학습에서 진행했던 UserController는 jdbcTemplate를 의존하여 사용하고 있습니다.
이를 가능케 하는 것은 @RestController
어노테이션으로 의해 의존이 가능해지고 해당 클래스를 스프링 빈
으로 등록시킵니다.
스프링 빈
서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너
를 만듭니다.
해당 컨테이너 안에는 클래스
가 들어가게 되고, 다양한 정보(이름, 타입) 도 함께 들어가고, 인스턴스화
도 이루어집니다.
서버가 시작되면 코드의 구현 순서에 대해 살펴보겠습니다.
스프링 컨테이너(즉, 클래스 저장소)가 시작됩니다.
기본적으로 생성되어있는 스프링 빈들이 등록됩니다.
사용자가 설정한 스프링 빈이 등록됩니다.
필요한 의존성이 자동으로 설정이 됩니다.
여기서 스프링 컨테이너를 사용하는 이유를 살펴보면 2가지의 스프링 프레임워크의 특성 때문입니다.
첫 번째는 제어의 역전(IOC) 이다. 말 그대로 메서드나 객체의 호출 작업을 개발자가 결정하는 것이 아닌, 외부(스프링 컨테이너)에서 결정되는 것을 의미입니다. 객체의 의존성을 역전시켜 객체간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 해서 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있도록 도와줍니다.
두 번째는 의존성 주입(DI) 로 객체를 직접 생성하는 것이 아닌 외부에서 생성할 후 주입시키는 방식을 의미한다.
이를 통해 모듈 간의 결합도가 낮아지고 유연성을 높일 수 있습니다.
스프링 빈으로 등록하는 방법
여기서 중점적으로 사용하는 어노테이션에 대해 살펴보겠습니다.
@Configuration
클래스
에 붙이는 어노테이션@Bean을 사용할 때 함께 사용
외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.
@Bean
메서드
에 붙이는 어노테이션메서드에서 반환되는 객체를 스프링 빈에 등록한다.
외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.
@Service, @Repository
개발자가 직접 만든 클래스를 스프링 빈으로 등록할 때 사용한다.
@Component
주어진 클래스를
컴포넌트
로 간주한다.해당 클래스들은 스프링 서버가 뜰 때
자동으로 감지
된다.
@Component 사용
컨트롤러, 서비스, 레포지토리 X
개발자가 직접 작성한 클래스를 스프링 빈으로 등록 시에 사용되기도 한다.
스프링 빈 주입 방법
생성자를 활용하여 주입하는 방법
setter
와@Autowired
를 사용하는 방법필드에 직접
@Autowired
를 사용하는 방법
JPA(Java Persistence API)
데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진
규칙
영속성
: 서버가 재시작되어도 데이터는 영구적으로 저장되는 속성
ORM(Object-Relational Mapping)
객체와 관계형 DB의 테이블을 짝짓는 방법
Hibernate
JPA를 구현(implement)해서 코드로 작성한 구현체
내부적으로
JDBC
를 사용한다
JPA 어노테이션
@Entity : 스프링이 객체와 테이블을 같은 것으로 바라본다.
Entity 뜻 : 저장되고, 관리되어야 하는 데이터
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id : 위 필드를 primary key로 간주
@GeneratedValue : primary key는 자동 생성되는 값
GenerationType.IDENTITY : MySQL의 auto_increment 전략과 매칭
JPA를 사용하기 위해서는
기본생성자
가 꼭 필요하다
@Column(nullable = false, length = 20, name = "name")
private String name;
@Column : 객체의 필드와 Table의 필드를 매칭한다.
null 여부, 길이 제한, DB Column 이름 등을 사용
@Column 어노테이션을 생략할 수도 있다.
트랜잭션(Transaction)
쪼갤 수 없는 업무의 최소 단위
모든 SQL 한번에 성공 or 하나라도 실패하면 모두 실패
트랜잭션 명령어
start transaction; : 트랜잭션 시작하기
commit; : 트랜잭션 정상 종료(SQL 반영)
rollback; : 트랜잭션 실패 처리(SQL 미반영)
트랜잭션 적용 방법
@Transactional
public void saveUser(UserCreateRequest request) {
userRepository.save(new User(request.getName(), request.getAge()));
}
SELECT 쿼리만 사용한다면
readOnly
옵션 사용 가능
@Transactional(readOnly = true)
public List<UserResponse> getUsers() {
return userRepository.findAll().stream()
.map(UserResponse::new)
.collect(Collectors.toList());
}
❗ 주의사항
IOException 과 같은
Checked Exception
은 롤백이 일어나지 않는다.
영속성 컨텍스트
테이블과 매핑된
Entity 객체
를 관리/보관하는 역할스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료
특징
변경 감지(Dirty Check)
영속성 컨텍스트 안에 불러와진 Entity는 명시적으로 save 하지 않더라도,
변경을 감지해
자동으로 저장된다.
@Transactional
public void updateUser(UserUpdateRequest request) {
User user = userRepository.findById(request.getId())
.orElseThrow(IllegalArgumentException::new);
user.updateName(user.getName());
}
쓰기 지연
DB의 insert/update/delete SQL문을 바로 날리는 것이 아닌,
트랜잭션이 commit
될때 모아서1번
만 날린다.
1차 캐싱
ID를 기준으로 Entity를 기억한다.
캐싱된 객체는 완전히 동일하다.(객체 주소도 같다)
JPA 연관관계
ex) 사람(person)과 실거주 주소(address)
사람 1명은 1개의 실거주 주소만을 가지고 있다.
@OneToOne
연관관계의 주인을 설정한다.(mappedBy 사용한다.)
연관관계의 주인 효과
상대 테이블을 참조하고 있으면 연관관계의 주인이다.
연관관계 주인이 아니면 mappedBy 사용
연관관계의 주인의 setter가 사용되어야만 테이블이 연결
@Transactional
public void savePerson() {
Person person = personRepotiory.save(new Person());
Address address = addressRepotiory.save(new Address());
person.setAddress(address);
}
연관 관계의 주의점
트랜잭션이 끝나지 않았기에, 한 쪽만 연결해두면 반대쪽의 값은 알 수 없다.
@Transactional
public void savePerson() {
Person person = personRepotiory.save(new Person());
Address address = addressRepotiory.save(new Address ());
person.setAddress(address);
System.out.println(address.getPerson); //null 반환
}
이를 방지하기 위해 setter 한 번에 둘을 같이 이어준다.
public void setAddress(Address address) {
this.address = address;
this.address.setPerson(this);
}
다대일,일대다관계
@ManyToOne을 단방향으로 사용할 수 있다.
@JoinColumn
연관관계의 주인이 활용할 수 있는 어노테이션
필드의 이름이나, null 여부, 유일성 여부, 업데이트 여부 등을 지정할 수 있다.
다대다관계(N : M 관계)
@ManyToMany
구조가 복잡하고, 테이블이 직관적으로 매핑되지 않기 때문에 사용하지 않는 것을 지양한다.
cascade 옵션
한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 흘러서
연결되어 있는 객체도 함께 저장되거나 삭제
되는 기능
orphanRemoval 옵션
객체간의 관계가 끊어진 데이터를
자동으로 제거
하는 옵션
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserLoanHistory> userLoanHistories = new ArrayList<>();
지연 로딩(Lazy Loading)
연결되어 있는 객체를 꼭
필요한 순간에 데이터를 로딩
한다.@OneToMany의 fetch() 옵션
연관관계의 장점
각자의 역할에 집중하게 된다.
새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.
테스트 코드 작성이 쉬워진다.
연관관계 사용 시 주의 사항
지나치게 사용하면 성능 상의 문제가 발생할 수 있다.
도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.
요구 사항 등 여러 부분을 고민해서 연관 관계 사용을 선택해야한다.
과제 내용
Day 4
Day2에서 진행했던 GET API와 POST API를 구현했었는데, 그 당시에는 database
를 사용하지 않은 방법으로 과제에서 주어진 조건에 맞게 답을 출력했었습니다. 이번 과제에서는 DB를 활용하여 정보를 저장하고, 수정할 수 있는 로직을 구현해야했습니다.
Layer를 분리해서 진행을 할 수 있지만, 간단하게 문제의 답만 도출하기 위해서 Controller Class에 구현을 진행했었습니다.
그렇기 때문에 Spring Data JPA가 아닌 JDBCTemplate
를 활용하여 진행했습니다. JPA
를 통한 DB 로직을 구현하는 것만 주구장창 했어서, 직접 쿼리문을 작성해서 진행하는 JDBCTemplate를 사용하는 방법에 대해 알 수 있었던 거 같습니다.
📋 4일차 미션 : GET API와 POST API 구현
Day 5
클린 코드는 코드를 아름답게 만드는 것이 아닌 코드의 질을 향상
시키는 데 중요하다는 것을 알았습니다.
클린 코드를 구현하기 위해서 명확하고 간결하게 코드를 구현하여 버그를 줄이고, 그로 인한 개발 속도를 향상시킬 수 있습니다.
또한, 잘 작성된 코드는 시간이 흘러도 이해하기 쉽기에, 유지 보수가 간편합니다. 이로 인해 개발자로서 팀 프로젝트를 진행할 시에
팀 커뮤니케이션을 원활하게 진행할 수 있도록 도움을 줄 수 있다는 것을 알게 되었습니다.
📋 5일차 미션 : 클린 코드 구현
회고
스프링 프레임워크의 핵심 개념인 제어의 역전(IOC)과 의존성 주입(DI)을 통해 더 나은 코드 작성과 유지 보수의 용이섬에 대해 알게 되었습니다. 또한 어노테이션들을 활용하여 클래스를 스프링 빈으로 등록하고, 스프링 컨테이너가 이를 관리함으로써, 필요한 의존성을 자동으로 설정할 수 있다는 사실을 배웠습니다. JPA를 통한 데이터의 영속성 관리와 ORM을 통한 객체와 DB의 매핑에 대해 이론적으로 정립할 수 있는 계기되었던 거 같습니다.
서비스를 구성하는 데 가장 중요한 @Transactional 어노테이션을 사용하여 데이터의 일관성을 유지하는 방법을 쉽게 공부하면서 더티 체크, 쓰기 지연 등 특성에 대해 알 수 있었던 거 같습니다.
객체와 DB를 효과적으로 설계할 수 있도록 연관관계를 설계하는 방법을 배움으로써 사용할 때의 주의사항과 성능 문제를 방지하기 위한 전략에 대해 알게 되면서 이를 통해 효과적이고 용이한 애플리케이션 개발을 할 수 있는 토대를 마련할 수 있었던 거 같습니다.
댓글을 작성해보세요.