스프링 입문 - 스프링 DB 접근 기술
이 글은 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 커리큘럼에 맞춰서 작성된 글입니다😀
H2 데이터베이스 설치
https://h2database.com/에 가서 H2 Database를 설치한 후
설치 경로/bin/h2.bat
을 실행시키면 스프링에서 Run 했던 것처럼 서버가 돌아간다.
(기본 설치 경로는 C:\Program Files (x86)\H2
)
이 때 브라우저에 창 하나가 뜨게 된다!
다음과 같은 화면이 보이지 않는다면 맨 앞에 IP 부분을 localhost로 바꿔주자.
이후 JDBC URL을 jdbc:h2:tcp://localhost/~/test로 바꿔주고 연결을 눌러주자.
JDBC URL을 변경해주는 이유는 충돌 이슈 때문이라고 한다.
연결을 눌렀다면 다음 화면으로 넘어갈거고 홈 경로에 test.mv.db
가 생성돼야 한다.
만약 화면이 넘어가긴 했는데 에러가 뜬다거나 기타 문제가 있다면 test.mv.db
를 제거한 후
다시 h2.bat
을 실행하는 순으로 진행해주면 된다!
넘어간 화면에서 빈 칸에 다음의 내용을 써주고 실행 버튼을 눌러주자.
MEMBER 라는 테이블을 만드는데 내용물에 id, name이 있으면서 primary key가 id임을 의미한다.
generated by default… 는 id에 대해 NULL값이 들어올 때 알아서 값을 채워주는 것을 말한다.
스프링에선 id의 자료형을 Long으로 했는데 여기서는 bigint를 쓴다고 한다.
실행 후 좌측을 보면 MEMBER라는 테이블이 생성됨을 확인할 수 있다!
drop table if exists member CASCADE;
create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);
아직 MEMBER 테이블에 아무 것도 삽입한 내용이 없으므로 조회해봐도 아무 내용도 없는 상태다.
insert into member(name) values('spring')
를 입력 후 실행하면 name이 spring인 값이 삽입되는데
칸의 내용을 다 지우고 좌측의 MEMBER 테이블을 클릭하거나, 다음의 SQL문을 집적 입력하면 조회가 가능하다.
SELECT * FROM MEMBER
이렇게 저장된 내용들은 서버를 닫았다 켰을 때도 유지됨을 확인할 수 있다!
순수 JDBC
점점 갈 수록 데이터베이스 면에서 빌드업 된 기술을 체험해볼텐데 JDBC는 20년 전의 기술이라고 한다.
과거에는 어떻게 다뤘는지 체험해보는 기회라고 언급하셨으니 가벼운 마음으로 시작하기!
자바를 켜서 build.gradle
에서 dependency에 다음 내용을 추가한다.
자바는 DB와 붙으려면 jdbc 드라이버가 꼭 있어야 한다고 한다. (연동하는 작업)
DB가 제공하는 클라이언트도 필요한데 그 역할을 h2database
가 해준다고 한다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
접속 정보를 입력하기 위해 main/application.properties
에 가서 다음의 내용을 입력하자.
첫 줄은 연동하려는 H2 DB 경로를 설정한 것이고, 두번째 줄은 H2 DB에 접근함을 의미한다.
그럼 이제 DB에 접근하기 위한 준비는 끝난 셈이다!
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
이전에는 메모리를 이용하는 MemoryMemberRepository
를 사용했지만
이번에는 DB와 연결해서 사용할 JdbcMemberRepository
를 만들어보자. 당연히 MemberRepository의 구현체로 만들자.
강의에서도 복붙하는 내용이 많기도 하고 코드가 좀 많이 길어질 것 같아서 링크로 대체한다!
(여기서 코드를 잘못 적어 이후에 조회 기능이 안되는 문제가 발생했으니 그냥 개념만 보자..😢)
이제는 Config를 설정해줘야 하는데 이전에 사용했던 service/SpringConfig
에서 내용을 수정하자!
필드에 dataSoruce가 추가되고 memberRepository()
의 리턴하는 내용이 달라지게 된다.
dataSource는 생성자를 만들면 스프링이 DataSource를 집어넣어준다!
그리고 Run을 하는데 반드시 H2 DB가 실행되고 있어야 한다!
H2 DB와 연동이 되고 있으므로 localhost:8080
에서 회원 목록을 조회한 결과와 H2 DB와의 결과가 같게 된다.
이전에 H2 DB만 실행했을 때 추가했던 내용들이 localhost:8080
에서도 확인할 수 있게 된다.
과거에는 DB를 교체할 때 지원하는 서비스가 여러 가지라면 교체되는 DB에 의존적이도록 코드를 모두 수정해줘야 했는데
그런 번거로움을 스프링이 기존의 코드를 건드리지 않은 채로 Config에서 내용을 수정함으로써 깔끔히 해결하는
장점을 보여준다. (MemoryMemberRepository
에서 JdbcMemberRepository
로 DB를 바꿀 때)
스프링의 장점이라고 적었지만 객체지향의 장점임을 강조하셨다!
스프링 통합 테스트
DB와의 연결 뿐만 아니라 스프링 컨테이너도 같이 올리는 통합 테스트를 진행해보자!
기존에 있던 MemberServiceTest
를 복사해 MemberServiceIntegrationTest
를 같은 경로에 만들고
클래스 위에 @SpringBootTest, @Transactional을 붙여주자.
beforeEach(), afterEach()와 구현되지 않은 findMembers(), findOne()은 지워주자.
그리고 필드에 @Autowired를 붙여주는데, 실제로는 이렇게 필드에 붙여 사용하지 않지만
테스트이기 때문에 가장 간편한 방법으로 설정한 것이라 한다. 실제로 테스트에서도 사용된다고 언급하셨다.
그리고 Run을 하면 다음과 같은 에러가 발생한다.
이건 이전에 name이 spring인 멤버를 집어넣은 DB와 연결되어 있는 상태인데
같은 이름의 멤버를 집어넣으려 해서 이미 존재하는 회원입니다라는 메시지의 에러가 발생한 것이다.
때문에 H2 DB에서 delete from MEMBER
를 입력하고 실행해주자. 그러면 기존의 멤버 내용이 다 사라지게 된다.
강의에서는 없었던 내용인데, 따라가다 발생한 에러가 있다. (에러 내용은 아래 사진 참고)
JDBC와 연동이 잘 안되는 모양이었는데 qna를 참고하면서 해결했다.
- JdbcMemberRepository에서 conn.close()를 close(conn)으로 수정
- (Spring 버전 차이가 원인) application.properties에서 spring.datasource.username=sa 추가
일단 이렇게 에러를 해결하고 다시 Run을 하면 문제없이 처리가 된다.
근데 H2 DB에 가서 SELECT * FROM MEMBER
로 전체 조회를 하면 이상하게도 회원가입()에서 저장한
spring이라는 이름을 가진 멤버가 조회되지 않는다!
그 이유는 @Transaction 때문인데, 해당 어노테이션에 테스트가 끝나면 Rollback 기능이 있기 때문이라 한다.
때문에 별도로 이전에 사용했던 afterEach()를 해줄 필요가 없다!
마지막에 테스트와 관련해 언급하신 내용이 있는데 테스트는 단위로 쪼개서 처리하는게 이상적이라고 한다.
지금처럼 스프링 컨테이너를 올려서 진행하는 테스트는 설계가 잘못됐을 확률이 높다고 한다.
꼭 그런건 아니지만 일반적으로 해당되는 말이니 인지하도록 하자!
스프링 JdbcTemplate
이전 과정에서 이미 했지만 JdbcTemplate을 사용하기 위해 필요한 설정은
build.gradle
에 implementation 'org.springframework.boot:spring-boot-starter-jdbc'
를
추가해주면 된다!
JdbcTemplate은 JDBC API의 반복 코드를 대부분 제거해주는 장점을 가지고 있다고 한다.
그러나 SQL문은 직접 작성해야 한다.
JdbcTemplateMemberRepository
에 다음의 내용을 입력한다.
- 생성자에 @Autowired를 붙여도 되지만, 빈으로 등록되면서 생성자가 1개라면 생략이 가능
- save()에서는 SQL문 없이도 insert가 가능
JdbcMemberRepository
와 비교했을 때 코드가 굉장히 간결함 (중복 제거)- 객체 생성은 memberRowMapper()에서 처리
설정 또한 이전에 JdbcMemberRepository
로 교체하듯이 해주면 된다! 굉장히 간단하다😄
테스트는 MemberServiceIntegrationTest
에서 Run하면 되고 H2 DB 키는 것 잊지 않기!
JPA
JdbcTemplate은 반복 코드를 줄인다는 장점을 갖고 있지만 SQL문은 직접 작성하는 단점이 있었다.
JPA에서는 이런 단점까지 커버하면서 데이터 중심 설계에서 객체 중심의 설계로 전환이 가능해진다!
먼저 build.gradle
에서 이전에 썼던 implementation ~jdbc
는 주석처리 해준 후
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
를 추가해주자.
그리고 application.properties
에서 다음 내용을 추가해주자.
첫 줄은 SQL문을 볼 수 있도록 설정하는 것이고,
둘째 줄은 일반적으로 JPA가 객체를 보고 테이블도 스스로 만들게 되는데
이미 만들어진 테이블을 사용할 경우 none을, 그렇지 않으면 create을 설정해주면 된다.
강사님은 none으로 진행하셔서 처음에 none으로 진행했으나 마지막에 Test에서 오류가 나가지고(..)
qna를 뒤져보니 이 부분에서 create으로 수정하면 된다는 말이 있어서 create으로 진행했다.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
Member.java
에서 설정해줘야 할 부분이 있다.
Member
클래스 위에 @Entity- 필드
id
위에 @Id, @GeneratedValue() : 후자에서 DB가 id를 자동으로 생성하도록 IDENTITY로 설정 - (참고) 필드
name
위에 @Column(name=”username”) : username과 매핑하고 싶다면 설정
JpaMemberRepository
생성 후 다음과 같이 코드를 적어주자.
이전에 build.gradle
에서 data-jpa
를 설정했기 때문에 스프링 부트에서 자동으로 EntityManager
를 생성한다.
현재 DB와 연결된 EntityManager
를 생성하는데 이걸 인젝션하면 된다! (필드와 생성자 참고)
JdbcTemplateMemberRepository
보다 코드가 상당히 간결해졌음을 알 수 있다!
findByName()
, findAll()
처럼 pk기반이 아닌 메소드에서는 jpql을 사용해서 처리하지만
그렇지 않은 메소드들은 상당히 간단히 처리하고 있음을 확인할 수 있다.
일단 jpql을 사용해 데이터를 저장하거나 변경하기 때문에 MemberService
에 @Transactional이 필요하다.
다음에 배우는 데이터 스프링 JPA에서는 위에서 쓰인 jpql 조차도 안짜도 된다고 한다😲
@Transactional
public class MemberService {
...
}
그리고 새로운 리포지토리인 JpaMemberRepository
를 쓰게 됐으니
SpringConfig
에서도 이전을 주석처리 하고 새로 설정해주자.
근데 JpaMemberRepository
의 생성자는 EntityManager
를 필요로 하므로 다음과 같이 인젝션을 해주자.
테스트는 이전과 같이 MemberServiceIntegrationTest
에서 해주면 된다!
스프링 데이터 JPA
지금까지 과정만 해도 개발 생산성이 많이 증가하는데 이 단계를 거치면 더 증가하게 됨으로서
핵심 비즈니스 로직 개발에 보다 집중할 수 있게 된다! 즉, 생산성이 증가해 효율적이게 된다.
실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 선택이 아닌 필수라고 한다😄
그러나, 스프링 데이터 JPA는 JPA를 편리하게 사용하기 위한 기술이므로 JPA에 대한 선행 학습이 필요하다!
설정은 앞의 JPA 설정을 따라가면 되겠다.
repository
폴더에 SpringDataJpaMemberRepository
라는 인터페이스를 만들어보자.
그리고 상속을 받는데 조금 특이하다. 코드는 다음과 같다.
이전에 만들어보거나 다뤄본적이 없었던 JpaRepository
를 상속받고 첫 번째 인자엔 멤버를, 두 번째는 id 타입을 넣는다.
Member
에서 id의 타입을 Long으로 썼기 때문에 두 번째 인자에는 Long을 적어준다.
그리고 마지막으로 MemberRepository
를 같이 상속받는다.
이후 SpringConfig
에서 필드를 MemberRepository
로 바꿔주고 인젝션을 받도록 설정해놓자.
때문에 memberRepository()
는 필요없으므로 주석 처리해주자.
그리고 H2 DB를 킨 후 MemberServiceIntegrationTest
에서 테스트하면 문제없이 돌아간다.
끝이다.
?
지금까지 인터페이스를 만들었으면 그걸 구현한 구현체를 갖고 다루곤 했었지만 이번엔 조금 다르다.
JpaRepository
를 상속받는 인터페이스기 때문에 데이터 스프링 JPA가 이 인터페이스를 보고
알아서 구현체를 생성해준 후 스프링 빈으로 등록해주는 작업까지 완료해준다.
심지어 자주 사용하는 기능(CRUD 등)들을 제공해주기 때문에 별도의 반복적인 작업이 필요하지 않게된다!
단, findByName()
는 인터페이스안에 적혀있음을 알 수 있는데 이는 비즈니스마다 공통적인 로직으로
활용이 불가능하기 때문이다. (가령, 이름을 지금까지 name으로 썼지만 다른데선 username으로 쓸 수도 있다)
때문에 메소드 이름을 findBy{대상}으로 등록해놓으면 {대상}을 찾는 쿼리를 알아서 짜서 기능을 만들어준다!
실무에선 JPA와 스프링 데이터 JPA를 기본으로 사용하는데 복잡한 동적 쿼리는 Querydsl 라이브러리를 사용한다.
이 때 장점은 안전한 자바 코드 작성, 편리한 동적 쿼리 작성 등이 있다고 한다.
이렇게 여러 조합을 활용하면서 해결하면 되는데 그렇게 하기 어렵다면 JPA가 제공하는
네이티브 쿼리나, JdbcTemplate을 사용하면 된다!
댓글남기기