5. 스프링 DB 접근 기술
1. H2 데이터베이스 설치
2. 순수 Jdbc
3. 스프링 통합 테스트
4. 스프링 JdbcTemplate
5. JPA
6. 스프링 데이터 JPA
[목표] ➤ 데이터를 영구적으로 보존하기 위해선 단순 메모리 저장은 무리가 있다.
본격적으로 DB를 설치해 저장해보며 데이터를 지켜보자!
1) H2 데이터베이스 설치
H2: 개발이나 테스트 용도의 가벼운 DB, 웹화면 제공
H2 파일을 설치 후 (아예 spring-study 폴더 안에 저장함) cmd 창을 통해 h2.bat을 bin에서 실행 시켜주면, 위와 같은 화면이 뜬다. 최초에는 데이터베이스 파일을 생성해줘야 한다. 설정을 그대로 둔 채 연결하면,
이런 화면이 뜬다. 이때 데이터 베이스가 잘 생성됐는지 확인 하려면
홈 디렉터리에 test.mv.db 파일이 잘 생성됐는지 확인하면 된다.
이후부터 DB에 접속하기 위해선 JDBC URL : jdbc:h2:tcp://localhost/~/test 로 경로를 수정해준다.
(∵ 파일로 직접 접근하게 되면, 웹 콘솔과 동시접근할 경우 오류가 발생할 수 있음 → 소켓을 통해 접근하도록 수정 )
예상치 못한 오류에 당황했고... 이것저것 찾아보니 h2 저장 경로의 문제인 것 같다.
위에서 스프링 스터디 폴더 안에 설치해줬다고 했는데, 스프링 폴더가 아닌 User/"사용자이름"/ >>에 H2 폴더를 옮겨준 뒤, test.mv.db를 삭제하고 cmd창에서 다시 h2.bat을 실행시켜줬더니 경로 수정후에도 잘 접속된다!
테이블 만들기
- H2 DB에 접근해, member table을 생성한다.
- id > member Attribute의 id, 자바에선 long형인데 sql문에선 bigint형
- generated by default as identity : null값으로 설정 불가능하게 자동 생성되게 함 (기본키 설정시 필요)
- PK로 설정됨
- 이후엔 select문으로 기본 sql 문처럼 조회 가능하다.
- 또한, insert into ~ values~ 로 데이터를 삽입할 수 있다.
- 데이터를 insert 할때마다 id가 +1 돼서 자동 갱신된다.
- 또한 sql폴더를 별도로 생성해, 사용한 sql문들을 ddl.sql 파일에 정리하는 것을 추천
DB 접속을 유지하기 위해선 h2.bat을 실행한 cmd 창을 유지해야 한다!
2) 순수 JDBC
실제 DB에 연동시켜서, 메모리에 저장이 아닌 DB에 데이터 in&out 하기 (쿼리문 이용)
(그런데 가장 오래된 방식)
- 기존에 repository에 만들어놓은 MemoryMemberRepository 를 대체하자. (MemberRepository로 인터페이스를 미리 생성해놨기에 저장 방식인 구현체만 생성하면된다.
- JdbcMemberRepository 생성
- DB에 붙으려면 jdbc 드라이버, 접속 정보, 데이터 소스가 필요하다
- private final DataSource dataSource >>> javax.sql.DataSource
- 나중에 스프링이 만들어놓은 접속정보를 통해 데이터 소스를 주입받음 > getConnection()을 통해 실제 DB와 연결 된 상태로 데이터 처리 가능
<처리방법>
-- 얘네 모두를 try-exception 안에서 처리
- String으로 DB에 날릴 sql문을 작성해놓음 (추후 바인딩 할 파라미터는 ?로 대체)
- conn(Connection 객체)로 datasource.getConnection() 즉, 연결 자체를 가져옴
- prepareStatement(1번에서 작성한 sql문...) >> pstmt(PreparedStatement 객체)로 저장
- pstmt.setString/int ... (파라미터 index, member.getName()등의 getter/setter로 데이터 받아옴)
- pstmt.executeUpdate()로 쿼리 전송 후 처리
- 쿼리 처리 결과를 ResultSet으로 받아와 확인
- ... 작업 처리 후 꼭 release 를 해줘야 함!!! (계속 연결된 상태이므로)
상당히 복잡하다...
(근데 내가 한 데베 텀프가 이 방식이다... ㅋㅋㅋ 무식하면 몸이 고생한다더니^^ㅋ)
- dataSource.getConnection을 직접 하기보단, 스프링 프레임워크를 통해 경유하는게 나음
- 즉, DataSourceUtils를 통해 Connection을 획득해야 함
- return DataSourceUtils.getConnection(dataSource)
- 닫을 때도 마찬가지
~~~ Configuration 해 줄 차례 ~~~
- 그 전까지 SpringConfig를 통해 memoryMemberRepository를 스프링 빈에 등록해 저장함
- return new MemoryMemberRepository() > return new JdbcMemberRepository(dataSource)로 대체
- DataSource는 Constructor 을 생성 해준다음 @AutoWired로 묶음
- DataSource : DB Connection 획득 시 사용하는 객체. DB에 연결 가능한 정보가 있다
- 스프링 부트는 DB Connection 정보를 바탕으로 DataSource를 생성 → 스프링 빈으로 올려둠 → DI 받을 수 있음
- JdbcMemberRepository를 만든 뒤, 인터페이스를 확장 후 @Configuration 수정만으로 DB이용이 가능해짐
- 스프링의 장점: 다형성 활용 가능 (인터페이스 생성 후 구현체만 바꾸면 됨)
- 스프링 컨테이너가 지원 (DI)
- 기존의 코드 수정 없이 애플리케이션 조립용 코드만 간단 수정해주면 끝!
- (좌) MemberService ---의존---> MemberRepository (구현체: MemoryMemberRepository, JdbcMemberRepository)
- SOLID 中 개방,폐쇄 원칙(OCP) >> 확장(기능추가)에는 열려있고, 수정(변경)에는 닫혀있다.
- 객체 지향에서 다형성의 이점
- 스프링의 DI를 이용 ➔ 기존 코드 수정 없이, 설정만으로 구현체 클래스 변경 가능
3) 스프링 통합 테스트
스프링과 엮어서 DB 테스트를 해보자 - DB까지 연결한 버전
기존의 MemberServiceTest를 약간만 수정 ➔ MemberServiceIntegrationTest
- Spring과 엮어서 테스트 하고자 할땐, @SpringBootTest, @Transactional를 사용
- 자체적으로 생성해 테스트하던 MemberSerivce , MemoryMemberRepository는 @Autowired로 스프링한테 패스
- 기존에는 Constructor을 선언해줬지만, 테스트 코드는 간단하게 구현 (일회성이므로)
- MemoryMemberRepository ➔ MemberRepository로 변경
- 이후 구현체는 SpringConfig에서 올라올 예정
- beforeEach, afterEach는 필요 없으므로 삭제 (메모리에 데이터를 저장하므로, 다음 테스트에 영향을 주는것을 방지하기 위해 작성한 메서드였음 ➔ @Transactional이 대체)
이후 테스트는 MemberServiceIntegrationTest로 진행
1. 회원가입() Test (@ Transactional 이 없이 진행하는 경우)
** '이미 존재하는 회원입니다' 에러 발생
>> test 용 회원 데이터의 이름 == DB에 저장된 회원 이름 이기때문에 발생. ∴ 테스트 전 DB 초기화 필요
delete from member
(때문에 보통 test 전용 DB를 따로 생성해 할때마다 초기화한다)
테스트 시 SpringConfig가 모두 올라왔다가 끝나면 내려간다.
<회원가입 test를 중복해서 실행한 경우>
마찬가지로 동일 회원이 존재한다는 오류가 발생한다. 이미 한 번 테스트를 수행했기에 DB에 hello라는 이름을 가진 데이터가 존재하는 상황이고, 따라서 이름 중복이므로 발생하는 오류이다.
@Transactional 없이 진행해서 발생하는 오류이다.
- @Transactional
- 기존 test에서 위와 같은 오류를 방지하기 위해 메서드 beforeEach, afterEach 가 DB를 주기적으로 초기화해줬다.
- 스프링에서 제공하는 동일한 역할을 하는 것이 바로 @Transactional Annotation 이다.
- DB에 데이터를 반영하기 위해선 commit이 필연적으로 필요한데, 이와 관련된 것이 바로 트렌젝션 이다.
- 즉, DB에 완전히 반영하도록 commit을 날리기 전에, test 하고 잽싸게 Rollback 해서 DB를 초기화 시켜버리는 것.
- commit만 안할 뿐 insert 쿼리는 수행 하므로 데이터를 넣어 하는 다른 테스트도 가능하다.
테스트 케이스에 @Transactional을 달면, 테스트 수행 시 트렌젝션을 먼저 수행하고 DB에 data를 insert 쿼리로 넣은 뒤, 테스트를 마친 후 commit을 하지 않고 rollback 해준다!
∴ @Transactional을 단 후 테스트 케이스를 여러번 실행해도 중복 오류가 발생하지 않는다.
2. 전체 MemberServiceIntegrationTest 테스트
중복 요소를 없앴기에 두 메서드 다 무사히 테스트 완료!
♘ Spring을 이용한 Test에서 사용하는 Annotation 정리
@SpringBootTest
스프링 컨테이너와 테스트를 함께 실행하게끔 함
@Transactional
테스트 케이스에만 달아준다. 각각의 테스트 시작 전 트랜젝션 ON → 테스트 마친 후 롤백
∴ DB를 테스트마다 초기화 해줄 필요가 없다! (테스트 하나마다 모두 적용됨)
(Test외 Service 등 다른쪽에 붙었을 경우에는 롤백 안함)
⇔ @Commit : 테스트 케이스가 끝나면 Commit 해버림
♘ 단위테스트 vs. 통합테스트
- 단위테스트 : 순수 자바 코드로만 진행한 테스트 (MemberServiceTest) >> 수행 속도가 빠르다
- 통합테스트 : 스프링 부트를 이용해 진행한 테스트 (MemberServiceIntegrationTest) >> 수행 속도가 느림
∴ Good Test일 확률: 순수 단위테스트 >>>> 통합테스트 (스프링 컨테이너를 배제한 테스트를 연습할 것)
'Spring > Spring boot 입문' 카테고리의 다른 글
[스프링 부트] 스프링 DB 접근 기술(5) JPA - 스프링 입문 강의 / 인프런 (0) | 2022.08.30 |
---|---|
[스프링 부트] 스프링 DB 접근 기술(4)- 스프링 입문 강의 / 인프런 (0) | 2022.08.30 |
[스프링 입문 강의] H2 DB 접근 오류 - Table "MEMBER" not found (this database is empty) (0) | 2022.08.28 |
[스프링 부트] 화면 웹 기능 - 스프링 입문 강의 / 인프런 (0) | 2022.08.19 |
[스프링 부트] 스프링 빈과 의존관계 - 스프링 입문 강의 / 인프런 (0) | 2022.08.05 |