package hello.hellospring.repository;
import hello.hellospring.domain.Member;
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;
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;
};
}
}
@Bean
public MemberRepository memberRepository(){
//return new MemoryMemberRepository();
//return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
JPA를 사용하기 위해 build.gradle에 다음 이미지처럼 ---data-jpa를 추가하고 gradle refresh 버튼을 눌러준다.(코끼리 버튼)
application.properties에 다음을 추가한다.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
도메인 파일의 Member 클래스에 @Entity를 붙여 이 클래스는 JPA가 관리한다라는 표시를 해준다.
DB에 insert into member(name) values('spring1'); 를 실행할 때 id값을 우리가 설정하는 것이 아닌 DB에서 자동으로 생성해주는데 이것을 IDENTITY 전략이라고 한다.
-> id 위쪽에 이 기능을 추가해준다.
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() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
단축키 팁
windows 기준 ctrl+alt+n을 누르면 하나의 라인으로 합쳐진다.
ex)List<Member> result = parallel; return result -> return parallel;
※ JPA를 사용할 때 항상 @Transactional이 있어야 한다.
-> MemberService에 @Transactional을 넣어준다.
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
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);
}
}
※ 스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 도구이기 때문에 반드시 JPA 선행학습이 필요하다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
@Override
Optional<Member> findByName(String name);
}
인터페이스가 다른 인터페이스를 가져올 때는 implements가 아닌 extends이다.
스프링이 JpaRepository를 발견하면 spring bean에 자동으로 등록해준다.
-> 구현체를 만들어서 등록해준다.
springconfig 파일을 다음과 같이 변경해준다.
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService()
{
return new MemberService(memberRepository);
}
}
서비스 통합테스트를 돌려보면 정상적으로 작동이 되는 것을 확인할 수 있다.
기본적인 save(), findAll() 함수 등은 스프링 데이터 JPA에서 제공을 해주기 때문에 따로 짤 필요가 없다.
위와 같은 공통적인 부분은 쓸 수 있지만 비지니스 모델이 다른 경우 등에는 따로 써야한다.
이것은 스프링 데이터 JPA 리포지토리에 findByName을 통해 확인할 수 있다.
실무에서는 대부분 JPA, 스프링 데이터 JPA, Querydsl(복잡한 동적 쿼리 처리 라이브러리) 의 조합으로 사용한다.
이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 스프링 JdbcTemplate를 사용하면 된다.