[Spring] 6.์Šคํ”„๋ง DB ์ ‘๊ทผ ๊ธฐ์ˆ 

 

1. H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์น˜

- ๊ต์œก์šฉ์œผ๋กœ์ข‹์Œ

- download (1.4.200) -> all platforms)

- cd h2 -> cd bin -> chmod 755 h2.sh (๊ถŒํ•œ) -> ./h2.sh (์‹คํ–‰)

- ์‹คํ–‰๋˜๊ณ  ์•„์ดํ”ผ ์•ˆ์žกํž๋•Œ (์•„์ดํ”ผ:8082 -> localhost:8082)

- JDBC URL -> jdbc:h2~/test -> ์—ฐ๊ฒฐ

- ll -> ls -all -> test.mv.db ํŒŒ์ผ์ด ์žˆ์–ด์•ผํ•จ

- ํŒŒ์ผ ์ง์ ‘ ์ ‘๊ทผ ์•ˆํ•˜๊ณ  ์—ฌ๋Ÿฌ๊ตฐ๋ฐ์„œ ์ ‘๊ทผํ•˜๋„๋ก -> JDBC URL ->  jdbc:h2:tcp://localhost/~/test

- create table

- DDL ๊ด€๋ฆฌ ->src๋ฐ–์— sql ํด๋” ๋งŒ๋“ค์–ด์„œ ddl.sql ๋งŒ๋“ค์–ด์„œ ๊ด€๋ฆฌ (๋ช…๋ น์–ด) ex) drop table, create table~

 

2. ์ˆœ์ˆ˜ Jdbc

- ํ™˜๊ฒฝ์„ค์ •

- JdbcMemberRepository

public class JdbcMemberRepository implements MemberRepository {
  @Override
  public Member save(Member member) {
    return null;
  }
  
  @Override
  public Optional<Member> findById(Long id) {
    return Optional.empty();
  }
}

 

* ์กฐ์‹ฌํ•ด์•ผํ• ์ 

-> DataSourceUtils ํ†ตํ•ด์„œ getConnection & releaseConnection (connect, release ํ•ด์ค„๋•Œ)

-> ์ด๋ ‡๊ฒŒ ํ•ด์•ผ database ํŠธ๋žœ์žญ์…˜ ์œ ์ง€์‹œ์ผœ์คŒ

 

SpringConfig

-> @Bean ๋ ˆํฌ์ง€ํ† ๋ฆฌ return ๋ถ€๋ถ„ ์ˆ˜์ • -> MemoryMemberRepo -> JdbcMemberRepo(dataSource)

 

datasource

private DataSource dataSource;

@Autowired
public SpringConfig(DataSource dataSource) {
  this.dataSource = dataSource;
}

-> ์Šคํ”„๋ง ์ž์ฒด๋กœ ๋ฐ์ดํ„ฐ์†Œ์Šค ๋งŒ๋“ค์–ด์ฃผ๊ณ  ์ฃผ์ž…ํ•ด์คŒ

Controller๊ฐ€ Service ์˜์กดํ•˜๊ณ , Service๊ฐ€ Repo ์˜์กด

-> Jdbc๋กœ ์‰ฝ๊ฒŒ ๋ฐ”๊ฟ”์ค„์ˆ˜์žˆ์Œ

 

** ๊ฐœ๋ฐฉํ์‡„์›์น™ (OCP, open closed principle)

= ํ™•์žฅ์— ์—ด๋ ค์žˆ๊ณ  ์ˆ˜์ •/๋ณ€๊ฒฝ์—๋Š” ๋‹ซํ˜€์žˆ๋‹ค.

= ๊ธฐ๋Šฅ ๋ณ€๊ฒฝํ•ด๋„ ๋™์ž‘์ฝ”๋“œ ์ˆ˜์ •์•ˆํ• ์ˆ˜์žˆ์Œ (๊ธฐ์กด์ฝ”๋“œ)

= ์Šคํ”„๋ง์˜ DI

 

3. ์Šคํ”„๋ง ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

@SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜ + @Transactional ์–ด๋…ธํ…Œ์ด์…˜

@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
  @Autowired MemberService memberService;
  @Autowired MemberRepository memberRepository;
  
  @Test
  public void ํšŒ์›๊ฐ€์ž…() throws Exception {
    //Given
    Member member = new Member();
    member.setName("hello");
    
    //When
    Long saveId = memberService.join(member);

    //Then
    Member findMember = memberRepository.findById(saveId).get();
    assertEquals(member.getName(), findMember.getName());
  }
  
  @Test
  public void ์ค‘๋ณต_ํšŒ์›_์˜ˆ์™ธ() throws Exception {
    //Given
    Member member1 = new Member();
    member1.setName("spring");
    Member member2 = new Member();
    member2.setName("spring");
    
    //When
    memberService.join(member1);
    IllegalStateException e = assertThrows(IllegalStateException.class,
      () -> memberService.join(member2));//์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•œ๋‹ค. 
    
    assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.");
  } 
}

@SpringBootTest : ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ์‹คํ–‰ํ•œ๋‹ค.

@Transactional : ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ์ด ์• ๋…ธํ…Œ์ด์…˜์ด ์žˆ์œผ๋ฉด, ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „์— ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ํ›„์— ํ•ญ์ƒ ๋กค๋ฐฑํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด DB์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์ง€ ์•Š์œผ๋ฏ€๋กœ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.

-> aftereach, beforeeach ํ•„์š”์—†์Œ!

 

* ์ˆœ์ˆ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ์ข‹์Œ (ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋ณด๋‹ค)

* ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ ์—†์ด ํ…Œ์ŠคํŠธํ•˜๋Š”๊ฒŒ ์ข‹์Œ

 

4. ์Šคํ”„๋ง JdbcTemplate (Mybatis ๋น„์Šท)

- ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (์‹ค๋ฌด์—์„œ ๋งŽ์ด ์”€!)

- JDBC API์—์„œ ๋ฐ˜๋ณต์ฝ”๋“œ๋ฅผ ๋Œ€๋ถ€๋ถ„ ์ œ๊ฑฐํ•ด์คŒ

- SQL์€ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผํ•จ

 

- ์Šคํ”„๋ง JdbcTemplate

public class JdbcTemplateMemberRepository implements MemberRepository {
  private final JdbcTemplate jdbcTemplate;
  public JdbcTemplateMemberRepository(DataSource dataSource) {
    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 List<Member> findAll() {
    return jdbcTemplate.query("select * from member", memberRowMapper());
  }
  
  @Override
  public Optional<Member> findByName(String name) {
    List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
    return result.stream().findAny();
  }
    
  private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> {
      Member member = new Member();
      member.setId(rs.getLong("id"));
      member.setName(rs.getString("name"));
      return member;
    }; 
  }
}

* ์ƒ์„ฑ์ž ํ•˜๋‚˜๋ฉด @Autowired ์ƒ๋žต๊ฐ€๋Šฅ

 

5. JPA

- ๊ธฐ์กด์˜ ๋ฐ˜๋ณต์ฝ”๋“œ + ๊ธฐ๋ณธ sql ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ์‹คํ–‰

- sql๊ณผ ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ ์„ค๊ณ„ -> ๊ฐ์ฒด ์ค‘์‹ฌ์˜ ์„ค๊ณ„๋กœ ํŒจ๋Ÿฌ๋‹ค์ž„ ์ „ํ™˜ ๊ฐ€๋Šฅ

- ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ

 

์„ค์ •

dependencies

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    //implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.h2database:h2' testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
  }

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true // jpa๊ฐ€ ๋‚ ๋ฆฐ sql์„ ๋ณผ์ˆ˜์žˆ์Œ
spring.jpa.hibernate.ddl-auto=none // jpa ์‚ฌ์šฉํ•˜๋ฉด ํšŒ์›๊ฐ์ฒด(ํ…Œ์ด๋ธ”๋กœ) ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ๋Š” ์ƒ๊ด€์—†์Œ

 

- JPA ์“ฐ๋ ค๋ฉด ์—”ํ‹ฐํ‹ฐ๋ž‘ ๋งตํ•‘ํ•ด์•ผ๋จ

- JPA = ์ธํ„ฐํŽ˜์ด์Šค(์ž๋ฐ”์ง„์˜ ํ‘œ์ค€ ์ธํ„ฐํŽ˜์ด์Šค) / ํ•˜์ด๋ฒ„๋„ค์ดํŠธ = ๊ตฌํ˜„์ฒด

- ORM(Object Relational Mapping) ๊ธฐ์ˆ , ์–ด๋–ป๊ฒŒ? ์–ด๋…ธํ…Œ์ด์…˜

 

Member

 

@Entity
public class Member {

  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  @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;
  }
}

 

* @Id / @GenerateValue -> identity ์ „๋žต & id ์ž๋™ ์ƒ์„ฑ

* @Column -> db ์นผ๋Ÿผ๋ช…์ด๋ž‘ ๋งค์นญ  

 

JPA repo ์ƒ์„ฑ

public class JpaMemberRepository implements MemberRepository {
  private final EntityManager em;
  
  public JpaMemberRepository(EntityManager em) {
    this.em = em;
  }

  public Member save(Member member) {
    em.persist(member);
    return member;
  }

  public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);
    return Optional.ofNullable(member);
  }
  
  public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class).getResultList();
  }
  
  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();
  }
}

- ์Šคํ”„๋ง๋ถ€ํŠธ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ(์ž๋™) Entity Manager ๋งŒ๋“ค์–ด์คŒ

- ๊ทธ๋ž˜์„œ Entity Manager ์ฃผ์ž…๋ฐ›์•„์•ผํ•จ

 

์„œ๋น„์Šค ๊ณ„์ธต์— ํŠธ๋žœ์žญ์…˜ ์ถ”๊ฐ€

import org.springframework.transaction.annotation.Transactional

@Transactional
public class MemberService {}

 

Configuration

@Configuration
public class SpringConfig {
  
  private final DataSource dataSource;
  private final EntityManager em;
  
  public SpringConfig(DataSource dataSource, EntityManager em) {
    this.dataSource = dataSource;
    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);
  }
}

 

6. ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA

- JPA ๋ฐฐ์šฐ๊ณ  ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA ๋ฐฐ์šฐ๋Š” ๊ฒƒ์ด ์ข‹์Œ! 

 

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA ํšŒ์› ๋ฆฌํฌ์ง€ํ† ๋ฆฌ

public interface SpringDataJpaMemberRepository extends JpaRepository<Member,Long>, MemberRepository {
        Optional<Member> findByName(String name);
    }

- <Member, Long> : ๋ฉค๋ฒ„์˜ pk

- ์ธํ„ฐํŽ˜์ด์Šค ๋‹ค์ค‘์ƒ์† ๊ฐ€๋Šฅ

 

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA ํšŒ์› ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์Šคํ”„๋ง ์„ค์ • ๋ณ€๊ฒฝ

 @Configuration
public class SpringConfig {
  private final MemberRepository memberRepository;
  
  public SpringConfig(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }
  
  @Bean
  public MemberService memberService() {
    return new MemberService(memberRepository);
  }
  
}

- ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๊ฐ€ SpringDataJpaMemberRepository ๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ์ž๋™ ๋“ฑ๋กํ•ด์ค€๋‹ค.

 

[์›๋ฆฌ]

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA ์ œ๊ณต ํด๋ž˜์Šค

- jpa + ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ jpa ๊ธฐ๋ณธ๋‚ด์žฅ๋œ๊ฑฐ ๊ฐ€์ ธ๋‹ค ์“ฐ๋Š”๊ฒƒ

- ํ†ต์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒƒ๋“ค (name์œผ๋กœ ์ฐพ๋Š” ๊ฒƒ ๋“ฑ๋“ฑ)

-> ์ธํ„ฐํŽ˜์ด์Šค์— ์ถ”๊ฐ€

public interface SpringDataJpaMemberRepository extends JpaReopository<Member, Long>, MemberRepository {
  @Override
  Optional<Member> findByName(String name):
}

 

-> ์‹ค๋ฌด์—์„œ๋Š” ๋™์ ์ฟผ๋ฆฌ์˜ ๊ฒฝ์šฐ Querydsl ํ™œ์šฉ (JPA + Spring Data JPA + Querydsl)

->  ์œ„ ์„ธ์กฐํ•ฉ์ด ์•ˆํ†ตํ•˜๋ฉด JPA ์ˆœ์ˆ˜ ๊ธฐ์ˆ ๋„ ์ œ๊ณตํ•˜๊ธดํ•จ (JPA ๋„ค์ดํ‹ฐ๋ธŒ ์ฟผ๋ฆฌ) or JdbcTemplate

  • ๋„ค์ด๋ฒ„ ๋ธ”๋Ÿฌ๊ทธ ๊ณต์œ ํ•˜๊ธฐ
  • ๋„ค์ด๋ฒ„ ๋ฐด๋“œ์— ๊ณต์œ ํ•˜๊ธฐ
  • ํŽ˜์ด์Šค๋ถ ๊ณต์œ ํ•˜๊ธฐ
  • ์นด์นด์˜ค์Šคํ† ๋ฆฌ ๊ณต์œ ํ•˜๊ธฐ