implementation 'org.springframework.boot:spring-boot-starter-jdbc'
그대로 사용src/main/java/hello/hellospring/repository/JdbcTemplateMemberRepository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired // 생성자가 하나만 있다면 @Autowired는 생략 가능하다.
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 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;
};
}
}
참고: 자바에서 extends와 implements의 차이
// @Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@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());
}
jdbcTemplate에서 query()메소드를 통해 DB에 쿼리를 날린다.
그 결과를 RowMapper를 통해서 맵핑을 한다.
2-1. resultSet의 결과를 member객체로 맵핑한 후 돌려준다. → ??
변환된 값의 리턴타입(query()메소드의 리턴타입은 List)
private RowMapper<Member> memberRowMapper() {
return new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
}
};
}
// lambda로 변환
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
@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)); // key를 받아서
member.setId(key.longValue()); // member.setId()메소드를 통해 넣어준다.
return member;
}
SimpleJdbcInsert
: jdbcTemplate를 넘겨서 만드는 객체이제 조립, 즉 SpringConfig에서 바꿔 끼워줘야 한다.
src/main/java/hello/hellospring/SpringConfig.java
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
이제 테스트를 해보자.
전에 만들어둔 스프링 통합 테스트(MemberServiceIntegrationTest)를 통해서 테스트를 할 수 있다.
control + R을 통해 마지막 실행했던 걸 다시 실행 가능
테스트 성공!
60~70%의 시간은 테스트코드 개발을 한다고 한다. 나머지 30~40% 시간은 Production code 개발(실제 사용할 코드)
실무에서는 작은 버그 하나가 큰 피해가 되므로 테스트코드를 잘 짜는 게 중요하다.
JPA: Java Persistence API
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
가 Jdbc관련 라이브러리를 포함하므로 기존의implementation 'org.springframework.boot:spring-boot-starter-jdbc'
는 제거해도 된다.src/main/java/hello/hellospring/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
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
: JPA가 생성하는 SQL을 출력해준다.
spring.jpa.hibernate.ddl-auto=none
: JPA는 원래 객체를 보고 DB의 테이블을 자동으로 만들어주는데, 우리는 이미 H2 데이터베이스에 만들어 놓았으므로 해당 기능을 끈다.
none을 create로 바꾸면 엔티티 정보를 바탕으로 테이블을 직접 생성해준다.
src/main/java/hello/hellospring/domain/Member.java
package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*
DB에서 만약 Column name이 "username"이라면,
@Column(name="username")
이라는 어노테이션을 붙여주면 DB에 있는 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;
}
}
@Entity 어노테이션을 붙이면 JPA가 관리하는 엔티티가 되는 것.
그리고 PK(Primary Key)를 맵핑해줘야 한다. 여기서는 id / @Id
그리고 현재 PK를 DB에서 자동으로 생성해주는 중이다.
@GeneratedValue
이 어노테이션들을 통해 데이터베이스와 맵핑을 한다.
맵핑된 정보를 가지고 SQL을 만들 수 있다. → JPA가 동작하는 방식
src/main/java/hello/hellospring/repository/JpaMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
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() {
// command + option + n : inline variable
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}