1. H2 데이터 베이스 설치
- 데이터 베이스 파일 생성
- JDBC URL을 jdbc:h2:~/test 으로 최초 접속
- ~/test.mv.db 파일 생성확인
- 이 후 JDBC URL : jdbc:h2:tcp://localhost/~/test - 테이블 생성
- Long(java) → bigint(db)
- generate by deault as identity : null 값 일 때, 자동으로 id값 채워짐
drop table if exists member CASCADE;
create table member
(
id bigint generated by default as identity, /* null 값 일 때, 자동으로 id값 채워짐 */
name varchar(255),
primary key (id)
);
2. 순수 JDBC
- 기능
- db에 연동하여 저장 - H2 데이터베이스 라이브러리 추가
# bundle.gradle
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
- 스프링 부트 H2 데이터베이스 연결 설정 추가
# application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
- JDBC 리포지토리 생성 및 구현
- 스프링 설정 변경
- DataSource
- 데이터 베이스 커넥션을 획득할 때 사용하는 객체
- 스프링이 설정파일(application.properties)를 보고 자체적으로 DataSource를 생성하고 스프링 빈으로 만듦 - 개방-폐쇄 원칙(OCP, Open-Closed Principle)
- 확장(=기능 추가)에는 열려 있고, (전체 코드) 수정/변경에는 닫혀있다. - 스프링의 DI 사용 → 기존 코드 수정 없이, 설정만으로 구현 클래스 변경 가능
- DB에 저장 → 스프링 서버 다시 실행해도 데이터 안전하게 저장
- DataSource
# SpringConfig
@Configuration
public class SpringConfig {
// 스프링이 설정파일(application.properties)를 보고 자체적으로 DataSource를 생성하고 스프링 빈으로 만듦 -> DI 가능
private DataSource dataSource; // 데이터베이스 커넥션을 획득할 때 사용하는 객체
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean // 스프링 빈 등록
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
// return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
}
3. 스프링 통합 테스트
- 기능
- 스프링 컨테이너와 DB까지 연결한 통합 테스트 - @SpringBootTest
- 스프링 컨테이너와 테스트 함께 실행 = 스프링이 테스트 할 때 사용 - @Transactional
- 테스트케이스에 해당 애노테이션 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 완료 후에 항상 롤백하여, DB에 데이터가 반영되지 않게 함
- 각 테스트 메소드마다 모두 적용됨 - @Commit
- 롤백되지 않고 커밋 ⇒ DB에 데이터 반영
# 회원 서비스 스프링 통합 테스트
@SpringBootTest // 스프릥이 테스트 할 때 사용, 진짜 스프링을 띄워서 테스트 실행
@Transactional // 동작 검증 후에 롤백해서 db에 데이터 반영되지 않게 하는 것
/* 테스트에 붙이면, 테스트 실행전에 트랜잭션을 실행하고 db에 데이터를 넣은 후에
테스트 끝나면 롤백하여 db에 데이터 반영 안됨 */
class MemberServiceIntegrationTest {
@Autowired // 테스트는 편한 방식으로 진행해도 되므로 -> 필드 주입
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
void 회원가입() { // test는 이름 한글로 변경 가능
//given : 이런 상황이 주어졌을 때
Member member = new Member();
member.setName("hello");
//when : 이것을 실행했을 때
Long saveId = memberService.join(member);
//then : 결과가 이렇게 나와야 함
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
}
4. 스프링 JdbcTemplate
- 기능
- 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거, SQL은 직접 작성해야함 → 순수 jdbc 보다 심플
# JdbcTemplate을 사용하기 위한 스프링 설정 변경
# SpringConfig
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
# 스프링 JdbcTemplate 회원 리포지토리
public class JdbcTemplateMemberRepository implements MemberRepository{
private final JdbcTemplate jdbcTemplate; // 의존성 주입 받을 수 있는 것 x
// @Autowired // 생성자가 1개 => @Autowired 생략 가능 // 스프링이 dataSource 주입
public JdbcTemplateMemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from member where id=?", memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name=?", memberRowMapper(), name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
private RowMapper<Member> memberRowMapper(){
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
5. JPA
- 기능
- JPA는 기존의 반복 코드는 뿐만아니라, 기본적인 SQL도 JPA가 직접 만들어 실행
- JPA 사용 → SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임 전환 - build.gradle 파일에 JPA 관련 라이브러리 추가
- spring-boot-starter-data-jpa : 내부에 jdbc관련 라이브러리 포함하므로, implementation 'org.springframework.boot:spring-boot-starter-jdbc 제거
# build.gradle
//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- 스프링 부트에 JPA 설정 추가 : application.properties
- JPA는 인터페이스만 제공, hibernate는 구현체
- JPA는 객체와 orm(object-relational database-mapping) 기술
- spring.jpa.show-sql=true
- JPA가 생성하는 SQL 출력 - spring.jpa.hibernate.ddl-auto=none
- JPA는 테이블을 자동으로 생성하는 기능 제공하는데 none을 사용하면, 해당 기능을 끈다. (기존의 우리가 만든 테이블 사용)
- create 사용하면, 엔티티 정보를 바탕으로 테이블 자동 생성해준다.
# application.properties
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
- JPA 엔티티 매핑
- @Entity
- JPA가 관리하는 엔티티가 됨 - @GeneratedValue(strategy = GenerationType.IDENTITY)
- 쿼리에 id값을 직접 넣는 것이 아니라, db에 다른 필드 값을 넣으면 자동으로 해당 id 값 생성 - @Column(name="username")
- db의 컬럼명(username)과 해당 필드이름(name)이 다를 때 매핑
- @Entity
# MemberService
@Entity // jpa가 관리하는 엔티티가 됨
public class Member {
@Id // pk 매핑
/* strategy = GenerationType.IDENTITY
쿼리에 id값을 직접 넣는 것이 아니라, db에 다른 필드 값을 넣으면 자동으로 해당 id 값 생성 */
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// @Column(name="username") // db의 컬럼명(username)과 해당 필드이름(name)이 다를 때 매핑
private String name;
...
}
- JPA 회원 리포지토리
- EntityManager
- JPA가 모든 동작 수행할 때 필요한 것, JPA에 관련 모든 정보를 가지고 있음
- JPA 라이브러리 설정하면, 스프링부트가 자동으로 DB에 연결해서 EntityManager 생성 - em.persist(member)
- JPA가 쿼리 만들어서 DB에 집어넣고 setId까지 모든 것을 실행 - em.find(Member.class, id)
- em.find(조회할 타입, PK)하면, 해당 아이디 조회 -
List<Member> result = em.createQuery("select m from Member m where m.name=:name", Member.class) .setParameter("name", name) .getResultList();
- PK가 아닌 다른 필드의 값을 조회 할 때, JPQL 객체지향 쿼리 언어 사용하여 조회
- JPQL : 테이블이 아닌 객체(엔티티)를 대상으로 쿼리를 날리면, SQL로 변역
: select 할 때, 객체 자체를 조회(id, name 이런식 아님) - ctrl + alt + n : 인라인
- EntityManager
public class JpaMemberRepository implements MemberRepository{
private final EntityManager em; // jpa는 EntityManager로 모든 동작 수행함
// build.gradle 에서 jpa 라이브러리 설정하면, 스프링부트가 자동으로 db에 연결해서 EntityManager 생성해줌
// 만든것을 DI 받으면 됨
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
// 이렇게 만들면, jpa가 쿼리만들어서 db에 집어넣고 setId까지 모든 것을 실행
em.persist(member); // persist : 영구저장하다
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id); // em.find(조회할 타입, PK)하면 조회됨
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
// 위에 id는 pk이니까 find쓰는데, name은 아니므로 아래와 같이 특별한 jpql 객체지향 쿼리언어 사용해야 함
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() {
// jpql : 테이블이 아닌 객체(엔티티)를 대상으로 쿼리를 날리면, sql로 번역이 됨
// select 할 때 객체 자체를 조회 ( id, name 이런식 아님)
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
- 서비스 계층에 트랜잭션 추가
- JPA를 사용하려면, 데이터를 저장하고 변경하는 곳에 항상 @Transactional 필요
- JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행되어야 함
@Transactional
public class MemberService {}
- JPA를 사용하도록 스프링 설정 변경
# SpringConfig
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
6. 스프링 데이터 JPA
- 기능
- 리포지토리에 구현 클래스 없이 인터페이스만으로 개발
- 기본 CRUD 기능 제공
- JPA를 편리하게 사용하도록 도와주는 기술 - 스프링 데이터 JPA 회원 리포지토리
- SpringDataJpaMemberRepository 인터페이스가 JpaRepository를 상속받으면, 해당 인터페이스의 구현체를 자동으로 만들어 스프링 빈에 자동으로 등록함
- 스프링 데이터 JPA가 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
// 해당 인터페이스가 JpaRepository를 상속받으면, 구현체를 자동으로 만들어 스프링 빈에 자동으로 등록함
// findBy()의 규칙을 통해 ()를 조회하는 JPQL를 제공하고 SQL로 번역됨
// JPQL : select m from Member m where m.name=?
@Override
Optional<Member> findByName(String name);
}
- 스프링 데이터 JPA 회원 리포지토리를 위한 스프링 설정 변경
private final MemberRepository memberRepository;
@Autowired // JpaRepository가 인터페이스 구현체를 만들고 스프링 빈에 등록 -> 주입 가능
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean // 스프링 빈 등록
public MemberService memberService(){
// return new MemberService(memberRepository());
return new MemberService(memberRepository);
}
// @Bean
// public MemberRepository memberRepository(){
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
// return new JpaMemberRepository(em);
// }
- 스프링 데이터 JPA 제공 기능
- 인터페이스를 통한 기본적인 CRUD 기능
- findByName(), findByEmail() 처럼 메서드 이름만으로 조회 기능
- id가 아닌 다른 컬럼을 통한 조회는 JpaRepository에 공통적으로 들어있지 않음
⇒ [해결법] : 스프링데이터JPA에서 Optional findByName(String name); 와 같은 추상 메소드 생성
⇒ findBy()의 규칙을 통해 ()를 조회하는 JPQL를 제공하고 SQL로 번역됨
→ JPQL : select m from Member m where m.name=? - 페이징 기능
※ 참고
: 실무 → JPA, 스프링 데이터 JPA 사용
: 복잡한 동적 쿼리는 Querydsl 라이브러리 사용 → 자바코드로 안전하게 작성 & 동적쿼리 작성 편리
: 해결하기 어려운 쿼리 → JPA가 제공하는 네이티브 쿼리를 사용 OR 스프링 JdbcTemplate 사용
[ 출처 ]
'Spring > 개념' 카테고리의 다른 글
[스프링기본] CH01. 객체지향설계와 스프링 (0) | 2021.08.16 |
---|---|
[스프링입문] CH07. AOP (0) | 2021.08.13 |
[스프링입문] CH05. 회원 관리 예제 - 웹MVC 개발 (0) | 2021.08.10 |
[스프링입문] CH04. 스프링 빈과 의존관계 (0) | 2021.08.10 |
[스프링입문] CH03. 회원 관리 예제 - 백엔드 개발 (0) | 2021.08.09 |
댓글