-
Toy Project 기록카테고리 없음 2024. 8. 21. 14:57
일정
8/5 ~ 8/18 2주 간
5~7일: DB 논리 모델링, 물리 모델링, 더미 데이터 확인 작업, 컨벤션과 브랜치 전략 등에 관한 회의들
7~11일: dto, dao, mapper(mybatis), dao test(Junit)
12~16일: service, global util and exception, controller, controller test(Swagger)
17~18일: code organizing and review, document work
스프링
- 스프링 컨테이너는 자바 객체(Bean)의 생명 주기를 관리한다
- 스프링 컨테이너의 구조
- ApplicationContext는 인터페이스의 형태이기 때문에 때문에, 다형성을 적용할 수 있다. 인터페이스를 통해 서로 다른 구현체들이 서로 다른 방식으로 빈을 로드하고 관리하지만, 동일한 AC인터페이스를 통해 접근할 수 있고, 하나의 구현체에 의존하지 않는다.
- 스프링 컨테이너를 사용하는 이유 : 객체 생성 시간을 줄임, 객체의 생명 주기를 관리 시켜서 낮은 결합도와 높은 캡슐화를 지향한다.
- 스프링 컨테이너의 생성과 구성 : 부트에서는 Autoconfiguration이 있기 때문에 @Configuration 설정이 없어도 Bean이 자동 등록되는 것.
- Bean의 자동 등록은 Bean 관련 어노테이션이 붙은 컴포넌트를 자동 스캔하는 것도 있지만, 기본적으로는 @SpringBootApplication 어노테이션이 붙은 클래스가 위치한 패키지와 그 하위 패키지를 스캔한다.
인스턴스의 숫자와 의존성 사이에 어떤 상관관계가 있을까?
인스턴스가 많아지면 각 인스턴스가 서로 상호작용하는 경우가 많아져, 의존성이 늘어날 수 있다. 예를 들어, 여러 개의 객체 인스턴스가 서로의 메서드를 호출하거나 데이터를 공유할 때 의존성이 생김.
의존성이 강한 인스턴스 변경 시, 다른 인스턴스에 대해 미치는 영향력을 최소화 하기 위해, 의존성을 완화하고 인스턴스를 관리하기 위해 싱글톤 패턴, 팩토리 패턴, 의존성 주입 등의 디자인 패턴을 활용할 수 있다.
스프링 부트 프로젝트 시작하기: 스타터io 페이지를 통해 만들었음마이바티스 사용을 위해 필요한 것
- JDBC : 스프링 부트의 jdbc를 추가
- 데이터베이스 : my sql 의존성 추가
- DataSource : 스트링부트에 내장된 HikariCP 사용
- MyBatis : mybatis-spring-boot-starter 추가
스트링부트의 MyBatis-Spring-Boot-Starter가 자동화해주는 작업 :
- 존재하는 DataSource 조회
- SqlSessionFactoryBean에 DataSource를 전달하고 SqlSessionFactory 인스턴스를 생성 및 등록
- SqlSessionFactory에서 가져온 SqlSessionTemplate 인스턴스를 생성 및 등록
- 매퍼를 자동 스캔하고, SqlSessionTemplate에 연결하고, 매퍼를 빈에 주입할 수 있도록 스프링 컨텍스트에 등록한다.
SqlSession vs SqlSessionTemplate
SqlSession : db와의 상호작용을 관리하는 객체
SqlSessionTemplate : SqlSession을 감싸서 스레드 안정성을 제공하고, Spring의 트랜잭션 관리와 통합된 기능을 추가하는 래퍼 클래스
MyBatis Mapper
namespace: 해당 매퍼에 명시된 namespace를 사용해서 쿼리를 구분한다. 왜 패키지를 길게 작성? 고유성, 명확성을 위해. 논리적으로 조직화 및 관리 가능.
resultMap과 동적쿼리 사용해서 매퍼 두 개를 하나로 묶었는데, mybatis의 다양한 기능을 사용해볼 수 있었지만, 설계적으로 OOP 관점에서는 좋지 못한 선택이었음.
프론트에서 서버로 파일을 전송할 때 dto에 넣어서 파라미터 하나로 받으면 안 되나?
기본적으로는 불가.
http request/response의 본문 데이터 형식을 header에 content-type을 적어줘야 올바르게 데이터를 해석할 수 있다. multifpart/form-data 형식은 binary 기반, application/json 형식은 text 기반이기 때문에
- 멀티파트로 보내려면 dto를 byte[]형식으로 직렬화 하기 (byte[]는 바이너리 데이터를 표현하는 가장 일반적인 방법)
- 제이슨으로 보내려면 파일을 base64로 인코딩하는 방법이 있었다. (Base64는 바이너리 데이터를 ASCII 문자열로 변환)
(변형이 적고 호환성 높으며 구현이 간편한 변환 방법들이기 때문에 바이트 배열과 base64를 사용)
스프링에서 관련 동작
클라이언트가 HTTP요청을 보내면 Spring MVC가 해당 요청을 수신하고, 핸들러 매핑을 한다. 핸들러 어댑터는 매핑된 핸들러(컨트롤러)를 호출하는 과정에서 다음과 같은 동작을 수행한다.
Message Converter가 content-type을 확인하고 적절한 변환기를 선택하여 데이터를 처리한다
Arugment Resolver가 변환된 값으로 자바 객체를 만들어서 컨트롤러 메서드의 매개변수로 전달한다.
고로 resolver를 커스텀 해주면 가능하기도 하다.
@Repository 위치를 interface에 붙이면 안 되나?
인터페이스를 통해 구현체가 작동되기 위해 인터페이스를 만든 건데 구현체에 붙이는 게 이상해서 고민.
@Repository의 역할과 기능
: 해당 클래스가 데이터 액세스를 담당하는 컴포넌트임을 명시하고, db관련 예외 처리, 트랜잭션 관리 기능(@Transactional)을 추가로 제공한다. 예를 들면 SQLException 같은 데이터베이스 예외를 DataAccessException 같은 데이터 접근 계층 예외로 변환시켜준다. 그렇게 변환하면 특정 DB에 종속되지 않은 일관된 예외 처리가 가능하고, Spring의 예외 계층을 사용하면 예외 발생 시 더 많은 정보를 제공받을 수 있어서 디버깅과 로깅이 좀 더 용이해진다.
DataAccessException
: Spring의 org.springframework.dao 패키지에 속하는 예외의 최상위 클래스. 여러 서브클래스를 가지고 있으며, 각 서브클래스는 특정한 오류 상황을 나타낸다. 이하 서브 클래스.
- DataIntegrityViolationException: 데이터 무결성 위반 (예: 유니크 제약 조건 위반)
- OptimisticLockingFailureException: 낙관적 잠금 실패 (예: 여러 트랜잭션이 동시에 같은 데이터를 수정하려 할 때)
- DuplicateKeyException: 중복 키 예외
- CannotAcquireLockException: 데이터베이스 락을 획득할 수 없을 때 발생
결론
인터페이스에 붙이는 쪽이 더 많은 유연성과 추상화를 제공하며, 구현체 변경이 용이한 것이 당연하다.
Spring Data JPA와 같은 프레임워크를 사용할 경우, 메소드 이름을 기반으로 쿼리를 자동으로 생성할 수 있다. 인터페이스에 붙이면 구현체를 자동 생성해주니까 @Repository는 인터페이스에 붙이는 것이 옳다.
인터페이스를 통해 메소드 호출을 한다면 구현체에 @Repository가 필요할 이유는 없는 것 같다. 인터페이스를 통하지 않고 메소드를 호출하는 것도 oop적으로 좋은 구조가 아니기 때문에, 특정한 필요에 의해 구현체에 붙여야 하는 상황에서도, 구현체에 @Repository를 붙일 것이 아니라 인터페이스를 통하도록 변경하는 것이 옳은 결정으로 보인다.
테스트
실행 시간 줄이는 방법 없나?
ApplicationContext는 초기화를 매번 하는 데 시간이 너무 오래 걸리는 점 --> Mock 객체를 주입받아서 테스트 하기!
테스트 자동화 공부해보기
테스트 꼼꼼히 하기
함부로 가정 x 예를 들면 순서가 같다거나. 삭제도 하나만 지워지는지 다른 게 같이 지워지진 않는지 같은 부분.
DAO Service에서 메소드 이름은 왜 다를까?
서비스에서 처리해야 할 DAO 메소드가 2개 이상일 경우가 있기 때문이다. 예를 들면
상품 관련 -입고 insert 재고 update
이체 관련 -보내는 사람: 출금 받는 사람: 입금
두 클래스의 기능과 역할이 다르다! 그래서
- Service: 서비스 할 기능을 기준으로
- DAO: 실행하는 SQL을 기준으로
정하면 되겠다.
유효성 고민
데이터 검증에서 프론트에서 넘어온 값이 손상된 값인지 아닌지에 대해서는 서버가 알 수 없는 거 아닌가?
그렇다면 이걸 검증하는 건 프론트의 몫인가?
예를 들어 주문에서
주문 -> insert한 다음에 select로 주문 확인 페이지를 보여주고 -> 그다음에 비교를 하면 검증할 순 있을 것 같은데
이런 방법을 쓰나?
서버 사이드에서 요청 데이터의 무결성을 위해 할 수 있는 일들
- 유효성 검사 : 형식 검증하기, 누락 체크하기, 비즈니스 로직 규칙에 부합하는지 확인하기
- 데이터 암호화?
- 서명이나 해시값을 함께 넘겨 검증하기
- CSRF 토큰이라는 것을 사용하기도
예외처리
예외 처리의 기본 : 프로그램이 죽지 않게 하기
cursor based pagination
불러온 리스트의 마지막 항목의 key를 기준으로 다음 페이지를 불러오는 페이징 방법
커서 key의 조건: unique and sortable
offest VS cursor
offset cursor 원하는 페이지로 이동 데이터 불일치 가능성 데이터의 추가/삭제에 안정적 총 개수를 알아야 함 대규모 데이터에서 효율적 비교우위 (인덱싱 전제) Record
- Java 14부터 추가된 새로운 클래스 타입.
- 불변(immutable) 데이터 객체를 간결하고 빠르게 만들 수 있다!
- Record가 자동으로 해주는 것들
- 생성자, equals() hashCode() toString()메서드 생성
- 필드의 final화
The class is intended to serve as a simple "data carrier". 객체 간에 불변 데이터를 전달! DTO를 표현하는 데 적합한 클래스다.
패턴 매치와의 결합에 대해 생각해보기 https://docs.oracle.com/en/java/javase/17/language/pattern-matching.html
spring-boot-starter-data-jpa VS spring-data-jpa
스프링 깃헙(https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-starters/spring-boot-starter-data-jpa/build.gradle)을 확인하면 spring-boot-starter-data-jpa 안에 spring-data-jpa가 있음을 확인할 수 있음starter는 패키지, 번들임
ResponseEntity에서 정적 메소드를 사용할 수 있다는 점을 새로 배움
Git
git stash
stash 했더니 수정본이 날아가는 문제!!로부터 배운 것
- stash를 임시저장이라고만 기억하고 있었다. 사실 stash는 충돌 없이 pull 받기 위해 임시저장+이전커밋 버전으로 되돌리기임.
- 로컬 히스토리를 확인해서 복구하기
깃 되돌리기
커밋한 뒤 빼먹은 파일을 발견 ㅜㅜ
원격 저장소에 push까지 한 상황에서는 reset은 불가능하고
1. revert한 기록과 변경된 커밋을 그 위에 push하기 (ㅠㅠ)
2. 로컬에서 reset --hard한 후 push --force 하기 (해당 브랜치의 다른 커밋들이 삭제될 수 있음)
두 가지 방법으로 변경 가능했는데 2번은 사실상 협업에서 불가능한 방법에 가까워 보이기 때문에, 처음부터 커밋을 잘 해야 한다는 교훈을 얻었다. 스스로의 꼼꼼함을 믿지 말고 실수하지 않을 수 있는 시스템을 만들기!
=> 앞으로는 commit과 push를 한번에 하지 말고 commit 후 확인 절차를 거친 뒤 push 하도록 다짐.
git .. --mirror
fork한 repo에 커밋을 해서 프로젝트를 제출해야 했는데, 다른 repo에 커밋했기 때문에 커밋 내역까지 한번에 복사 할 수 있는 방법을 모색했다. git clone --mirror를 통해 가능해보였는데, 결론적으로는 fork한 repo의 브랜치를 삭제할 권한이 없어서 실패함. git push --mirror를 하면 브랜치까지 모두 복사가 되기 때문에 기존 브랜치 삭제가 필요했다. 어쩔 수 없이 mirror는 포기하고 폴더 내용을 수동으로 복사해서 커밋하게 되었다.
git 그룹 작업
그룹을 파서 포크한 다음에 브랜치를 파서 머지!!하는 수가 있다는 걸 마지막에 물어봐서 알게 됨.
앞으로는 그룹에 포크한 다음에 dev에 바로 pr 날리는 구조를 해볼 것이다.