앞에 만든 회원관리 예제는 회원 데이터를 MemoryMemberRepository 클래스의 객체에 저장한다. 서버가 실행되면서 객체가 생성되고 데이터가 저장되었다. 하지만 서버를 종료하는 순간 메모리 상의 객체는 사라지기 때문에 데이터를 보관할 수 없다.
H2 database를 설치하고 데이터를 데이터베이스에 저장하는 방식으로 repository를 변경해보려고 한다.
http://www.h2database.com/html/main.html 에 접속하여
os에 맞는 버전을 설치한다.
압축을 풀고 bin 안에 있는 h2.sh을 실행한다.
Jdbc url: jdbc:h2:~/test 로 설정하고 연결한다.
홈 디렉토리에 test.mv.db 파일이 생성되었는지 확인하고
이후부터는 Jdbc url을 jdbc:h2:tcp://localhost/~/test 로 설정하여 연결하면 된다.
인터페이스를 설계하고 구현체를 바꾸며 사용할 수 있다 -> 다형성을 활용한다.
스프링에서는 스프링컨테이너가 Dipendency Injection을 해주어 이를 가능하게 한다.
기존의 코드는 건드리지 않고 어플리케이션을 어셈블하는 코드만 수정한다면 손쉽게 변경이 가능하다.
MemberService 는 MemberRepository 에 의존하고 있다
MemberService 객체가 생성될때 MemberRepository 를 주입받는다. Configuration 에서 MemberRepository를 스프링빈으로 등록할 때 어떤 구현체를 생성하여 인터페이스에 담을지만 변경한다면 기존 코드에 영향을 주지 않고 리포지토리를 변경할 수 있다.
데이터베이스에 데이터를 저장하기 위해 Jpa를 사용한다.
JPA는 자바 ORM 표준 기술이다. ORM 이란 Object Relation Mapping 의 약어로 객체-관계 매핑을 뜻한다. 자바 어플리케이션의 객체와 관계형 데이터베이스에서의 엔티티를 매핑하여 관계형 데이터베이스에 객체의 데이터를 저장할 때 자바 콜렉션에 넣는 것과 같이 사용할 수 있도록 해주는 api이다.
간단한 사용법만 알아보고 이후 깊이 있게 다루어볼 예정이다.
Build.gralde에서 dependencies에 Jpa를 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
데이터베이스에 데이터를 저장할 수 있는 JpaMemberRepository를 구현한다.
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() {
List<Member> result = em.createQuery("select m from Member m", Member.class).getResultList();
return result;
}
}
인터페이스가 포함하는 메서드를 구현한다.
엔티티와 매핑할 객체에 어노테이션을 붙인다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
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를 쓰려면 트랜잭션이 있어야한다.
MemberRepositry의 메서드를 호출하는 MemberService 클래스에 @Transactional 어노테이션을 사용한다.
@Transactional
public class MemberService {
...
}
Configuration에서 MemberRepositry의 구현체를 변경하면 된다.
@Configuration
public class SpringConfig {
private EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
this.em = em;
}
@Autowired private MemberRepository memberRepository;
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
@Bean
public MemberRepository memberRepository() {
//return new MemoryMemberRepository();
return new JpaMemberRepository(em);
}
}
스프링부트가 실행될 때 EntityManager를 생성해서 가지고있는다. Jpa에서 사용할 클래스를 구현할 때 생성자로 주입 받으면 된다.
Configuration에서 MemberRepository에 구현체를 바꾸는 것 만으로 기존 코드에 영향을 주지 않고 리포지토리를 변경할 수 있었다. 스프링의 DI는 이러한 다형성을 활용할 수 있도록 해준다.