"인프런의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 글 입니다."
[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세
www.inflearn.com
이전에 순수 JDBC 에서 JdbcTemplate 으로 바꿨더니 개발하는데 작성해야할 코드의 양이 확 줄었었다.
그런데 아직도 해결이 안되는 것이 SQL 은 개발자가 직접 작성해줘야 하는 부분이었다.
JPA 는 기존의 반복 코드는 물론이고, 기본적인 SQL 도 JPA 가 직접 만들어서 실행해준다.
마치 메모리에 객체를 적재하듯 JPA 에 객체를 집어 넣으면, JPA 가 중간에서 DB 에 SQL 을 전달하고 그 결과로 데이터를 반환받는 등의 과정을 JPA 가 알아서 다 처리해준다.(SQL 쿼리를 JPA 가 자동으로 처리해준다.)
단순히 SQL 을 작성해주는 것을 넘어서 JPA 를 사용하면 SQL 보다는 객체 중심으로 고민을 할 수 있게된다.
(JPA 를 사용하면 SQL 과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.)
즉, JPA 를 사용하면 개발 생산성을 크게 높일 수 있는 것이다.
(강사님이 초반부에 본인의 JPA 책과 인프런 강의에 대해서 소개를 해주셨는데, 책은 이미 예전에 ebook 으로 구매했고 인프런 강의 로드맵도 대학교 친구랑 비용을 절반씩 내서 로드맵 전체를 구매했다, 필요할 때 보면서 공부하자.)
(실무에서 JPA 를 본격적으로 사용하는 순간이나, 토이 프로젝트 같은걸로 개인적으로 공부할 때 크게 역할을 해줄 것으로 기대하고 있다.)
* build.gradle 파일의 의존성 단에 다음과 같은 설정을 추가해주자.(jdbc 의존성은 주석처리 해준다. data-jpa에 jpa 랑 jdbc 모두 포함되어 있다.)
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
의존성 주입이 완료되면 External Libraries 에 hibernate 와 jpa 관련 라이브러리가 들어와 있는지 찾아보자.
해당 라이브러리 들이 존재한다면 본격적으로 JPA 를 사용할 수 있다.
이후 application.properties 파일에 다음과 같은 설정을 추가해주자.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
* spring.jpa.show-sql=true
: JPA 가 데이터베이스에 전달해주는 SQL 의 내용을 로그로 확인해 볼 수 있는 설정이다.
* spring.jpa.hibernate.ddl-auto=none
: JPA 는 회원 객체(Member)를 보고 테이블까지 모두 만들어준다. 그러나 우리는 지금 실습용 테이블을 이미 만들어둔 상태이기 때문에 자동 테이블 생성 기능을 지원하는 ddl-auto 설정을 끄도록 한다.
(이 설정을 none 이 아닌 create 으로 바꾸면 각 객체별로 테이블을 어플리케이션 실행 때마다 자동으로 생성해주는 기능을 제공한다.)
* 참고
JPA 는 인터페이스, hibernate 는 JPA 라는 인터페이스를 구현하는 구현체이자 라이브러리 이다.
JPA 는 자바 진영의 표준 인터페이스이며, 이를 구현하는 것은 여러 업체들이 제공하는 라이브러리 들을 활용할 수 있는데 대표적으로 hibernate 를 많이 사용한다.
JPA 는 ORM(Object Releation Mapping) 이라는 기술이다. 즉, 객체와 관계형 데이터베이스 사이에서 이 둘을 매핑해주는 역할을 하는 기술이라는 뜻이다.
여기서 매핑을 어떻게 해주느냐, 어노테이션 으로 간단하게 해준다.
- Member.java
import javax.persistence.*; // import 패키지 참고 - javax.persistence (JPA 풀 네임이 JAVA Persistence API 이다.)
@Entity
public class Member {
// DB 에서 기본키 값을 생성해주고 있다.
// 이와 같이 DB 에서 기본 키 값을 자동으로 생성시키는 기본키 매핑 전략을 identity 전략이라고 한다.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// @Column 어노테이션을 통해 생성시킬 테이블에서 각 컬럼의 이름을 지정해 줄 수 있다.
// @Column(name = "username")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
* 참고
: 작년에 친구랑 공유하고 있는 JPA 강의에서 들었던 기억으로는 기본키를 생성할 때 DB 에서 숫자별로 순차적으로 생성해주는 전략을 사용하는 건 굉장히 좋지 않고, 보통 기본키를 지정할 땐 문자열 데이터를 기본키로 지정해 주는 것이 좋다고 했던게 기억난다.
숫자로 기본키를 사용할 경우 어플리케이션이 운영되면서 계속 각 객체별로 삽입, 수정, 삭제가 이루어질 텐데 어느 순간 이 숫자들간의 순서가 꼬이게 되는 상황이 생길경우 테이블에 저장되어 있는 각 객체들의 관리가 힘들어진다고 했었던것 같다.
이제 JpaMemberRepository 클래스를 만들고 다음과 같이 코드를 작성해주자.
- JpaMemberRepository.java
public class JpaMemberRepository implements MemberRepository{
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class).getResultList();
}
}
- JPA 는 EntityManager 로 모든게 동작한다. build.gradle 파일에 spring-boot-starter-data-jpa 의존성을 작성해주면 스프링 부트가 알아서 EntityManager 클래스 객체를 만들어서 스프링 빈으로 등록해준다.
(application.properties 파일에 DataSource 관련 설정을 작성했을 때 DataSource 클래스 객체를 스프링 부트가 자동으로 스프링 빈으로 등록해준 것과 비슷한 이치이다.)
- EntityManager 클래스는 DataSource 와 같은 클래스들을 내부적으로 지니고 있어서 데이터베이스 연결과 같은 작업도 알아서 다 처리해준다.
- save 메소드를 보면 em.persist(member); 라고 쓰여져 있는데, 단순히 데이터를 데이터베이스에 저장하는 작업일 경우 persist 메소드 한 줄만 적어줘도 JPA 가 insert 쿼리 부터 id 값 세팅 까지 전부 다 해준다.
(이에 대한 JPA 의 자세한 매커니즘은 이 블로그의 JPA 강의 정리글에 작성되어 있으니 참고하자.)
(아마 영속성 컨텍스트에 일단 데이터가 저장되고(영속화), insert 와 같은 쿼리들은 쓰기 지연 SQL 저장소에 적재되어 있다가 데이터베이스 커밋때 해당 쿼리가 데이터베이스로 전달되면서 동시에 영속성 컨텍스트에 있던 데이터가 데이터베이스로 함께 전달되는 구조였던 것 같다.)
- findById 메소드와 같이 기본키가 검색 조건일 경우 EntityManager 객체의 find 메소드를 통해 파라미터로 넘어온 id 값을 기준으로 객체 값을 찾아준다.
- select 쿼리에 where 조건 값으로 id 를 세팅해서 객체를 검색한 후 반환해주는 것이다.
- findByName, findAll 메소드와 같이 기본키가 검색조건이 아닐 경우, JPQL 을 사용해서 select 쿼리를 작성해줄 수 있다.
- JPQL 은 테이블이 아닌 객체를 중심으로 작성하는 쿼리이며, 이와 같이 쿼리를 작성하면 JPA 가 이를 SQL 로 번역하여 데이터베이스로 전달해준다.
- 여기서 m 은 검색하고자 하는 객체(Member) 에 대한 별칭(alias) 이다.
- SQL 에서 * 이나 컬럼 명을 기준으로 검색하는 것과 다르게, JPQL 에서는 객체 그 자체를 대상으로 검색한다.
Spring Data JPA?
다음 강의에서 간단하게 배우겠지만 JPA 를 스프링에서 한번 감싸서 제공해주는 기술이 있는데, 이를 Spring Data JPA 라고 한다.
이를 사용하며 위의 코드에서 findByName 이나 findAll 메소드에서 작성했던 JPQL 을 사용하지 않아도 된다.
자세한 내용은 다음 강의 정리글에서 보도록 하자.
JPA 를 쓸 때 주의할 점?
JPA 를 사용할 때 주의할 점은 항상 트랜잭션이 있어야 한다는 것이다.
(데이터를 저장하거나 변경하는 등의 작업을 수행할 때는 항상 트랜잭션이 있어야 한다.)
JPA 는 데이터의 변경이 있을 경우 반드시 해당 작업이 트랜잭션 안에서 이루어져야 한다.
그렇기 때문에 MemberService 클래스에서(서비스 계층) 클래스 이름 위에 @Transactional 어노테이션을 작성해두자.
(회원가입 메소드 같이 트랜잭션이 필요한 메소드에만 해당 어노테이션을 작성해 두어도 무방하다.)
- MemberService.java
@Transactional
public class MemberService {
// 이하 코드 동일
}
그럼 이제 SpringConfig 클래스 파일에 Repository 스프링 빈 등록에 대한 내용을 JPA 로 바꿔주자.
- SpringConfig.java
@Configuration
public class SpringConfig {
// private DataSource dataSource;
private EntityManager em; // JPA 를 위한 EntityManager 객체 필드 선언
// @Autowired
// public SpringConfig(DataSource dataSource) {
// this.dataSource = dataSource;
// }
@Autowired
public SpringConfig(EntityManager em){ // 생성자를 통해 EntityManager 의존성을 SpringConfig 객체에 주입해준다.
this.em = em;
}
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em); // JPAMemberRepository 를 스프링 빈으로 등록한다.
}
}
위와 같이 코드를 작성해준 후 MemberServiceIntegrationTest 클래스에서 회원가입 메소드와 같은 테스트 코드를 수행시켜 보면 테스트를 정상적으로 잘 통과하는 것을 확인해 볼 수 있다.
여기서 테스트 통과 로그를 보면 아래와 같은 내용이 출력된 것을 확인해 볼 수 있다.
Hibernate: select member0_.id as id1_0_, member0_.name as name2_0_ from member member0_ where member0_.name=?
Hibernate: insert into member (id, name) values (null, ?)
JPA 를 활용하면 JPA 에 대한 구현체를 hibernate 가 사용된다.
이 hibernate 를 통해 JpaMemberRepository 에서 사용한 JPQL 쿼리가 데이터베이스로에 맞는 select 쿼리로 변환되어 전달되면서, 회원가입 메소드 내부에 있는 동일한 이름에 대한 유효성 검증 메소드가 수행됨을 알 수 있고,
그 이후 persist 메소드를 통해 insert 쿼리가 작성되어 데이터베이스로 전달되었다는 것 또한 알 수 있다.
(id 필드는 null 로 되어 있는데, 이런식으로 데이터베이스에 전달되면 지정해놓은 자동 키 생성 전략으로 인해 데이터베이스에 저장될 때 키 값이 자동으로 할당되어 저장된다.)
* 참고
: 회원가입 메소드에 @Commit 어노테이션을 붙여주면, @Transactional 어노테이션으로 인해 데이터베이스가 롤백됨에도 불구하고 저장한 데이터가 그대로 남아있게 된다.
@Commit 어노테이션의 내용을 자세히 확인해보면 @Rollback(false) 어노테이션이 작성되어 있는 것을 확인할 수 있는데, 이로 인해 데이터베이스가 롤백되지 않는것 같다.
- @Commit 어노테이션 세부 내용
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Rollback(false)
public @interface Commit {
}
이후 테스트 코드 전체를 모두 돌려보면 모든 테스트 들이 정상적으로 잘 수행되는 것을 확인해 볼 수 있다.
(당연히 @Commit 어노테이션은 주석 처리 해주어야 한다. 그렇지 않으면 동일한 이름 유효성 검증 메소드를 통과하지 못하게 된다.)
* JPA 는 스프링 만큼 깊이있게 공부해야 할 내용이 많다. 열심히 공부하도록 하자.
'Spring basic' 카테고리의 다른 글
스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - AOP #1 (0) | 2021.11.17 |
---|---|
스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - Spring Data JPA (0) | 2021.11.16 |
스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 스프링 JdbcTemplate (0) | 2021.11.11 |
스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 스프링 통합 테스트 (0) | 2021.11.10 |
스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 순수 JDBC (0) | 2021.11.10 |