저번시간에 순수한 JDBC를 통해 개발을 했다(고대 개발자들 따라하기) 오늘은 JDBC 라이브러리 활용 & JPA 활용 과 AOP까지 정리를 할예정이다.(스프링 입문 강의 드디어 끝!)
JDBC 라이브러리를 활용하면 쌩으로 처음부터 끝까지 개발하는거에 비해 코드가 훨씬 간결해진다. 중복된 코드들이 없어지기 때문
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; // JdbcTemplate 이란 객체를 활용
//생성자가 1개면 Autowired 생략가능
public JdbcTemplateMemberRepository(DataSource 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;
};
}
}
확실히 보면 저번에 비해 코드들이 많이 간소화 된것을 알 수 있다.
이상태에서 이제 Bin을 등록하면된다.(확실히 아무것도 안 건드리고 Bin만 건드리면 되니까 확실히 편하다
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private final DataSource dataSource;
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);
return new JdbcTemplateMemberRepository(dataSource);
}
}
참고로 코드를 보면 JDBCTemplate란 녀석이 있다. 이 녀석이 JDBC에 반복코드들을 제거해준 착한? 녀석이다.
이제 JPA로 개발을 진행해 볼것이다. JPA는 SQL마저 JPA가 직접 만들어서 실행해준다(이러한 기능을 ORM 기능이라고한다.)
(* ORM이란 Object Relational Mapping이라고 객체와 관게형 데이터베이스의 데이터를 자동으로 매핑해주는 역할을 의미함)
JPA가 Django에서 Django를 순수 백엔드로 쓰일때 많이 쓰이는 DjangoRestFramework(DRF)와 같은 역할을 해줌
JPA를 사용하면 데이터 중심의 설계에서 객체 중심의 설계로 패러다임이 전환이 가능하고 JPA를 사용하면 개발 생산성을 크게 늘릴 수 있다.(JPA적고 나면 스프링데티어 JPA도 적을거지만 스프링데이터JPA를 보면 그냥 와... 마술이다... 란 느낌이다. Django에서 Modelviewset을 본 느낌이랄까)
아무튼 이제 JPA 로 하는 방식을 적자면,
build.gradle에 라이브러리를 사용한다고 적어줘야한다.
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.13'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '11'
}
repositories {
mavenCentral() // 아래 라이브러리에 있는 것을 다운로드 받음
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // html만드는 템플릿 엔진
implementation 'org.springframework.boot:spring-boot-starter-web' // web
implementation 'org.springframework.boot:spring-boot-starter-jdbc' // JDBC 를 활용해서 데이터베이스와 연동
implementation 'org.springframework.boot:spring-voot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'// JPA 사용 (추가된 부분)
runtimeOnly 'com.h2database:h2' //(추가된 부분)
testImplementation 'org.springframework.boot:spring-boot-starter-test' // test 라이브러리가 기본적으로 제공
}
tasks.named('test') {
useJUnitPlatform()
}
추가된 부분을 추가하면 JPA와 하이버네이트 라이브러리가 설치가 된다. 하이버네이트란 ORM 프레임워크로 JPA의 구현체이고, JPA인터페이스를 구현하고, 내부적으로 JPA API를 사용한다.
이제 스프링부트에 JPA설정을 추가해줘야 하는데 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
마지막 밑 2줄이 추가된 부분이고 마지막 줄에 spring.jpa.hibernate.ddl-auto = none 이 말은 JPA가 테이블까지 직접 생성하는데 우린 이미 테이블을 만들었으니까 생성할 필요가 없어서 none으로 되어있다. 필요하다면 true로 바꾸면됨 그 윗줄은 JPA가 생성하는 SQL을 출력력해주는 것이다. (영어 그대로 이해하면 될듯)
이렇게 해준 후에 도메인 패키지 아래에 있는 Member class Entity를 매핑을 해줘야한다.
package hello.hellospring.domain;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import javax.persistence.*;
@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;
}
}
@Entity가 붙은 클래스는 JPA가 관리하는 클래스로 JPA를 사용해서 테이블과 매핑할 클래스는 저걸 꼭 붙여야한다.
중간에 있는 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 이건 기본키를 자동으로 생성하는 의미이다.
이러한 키(id)를 자동으로 생성해주는것을 아이덴티티 전략이라고 한다.(수동으로 만들지 않고 DB에게 만들라고 시키는것)
이제 JPA회원 레포지토리를 만든다.
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; //JPA가 자동으로 entitiymanager를 만들어줌 데이터소스를 내부적으로 다 들고있음
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member); // JPA가 쿼리로 다해주고 저장해줌
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();
}
}
과거 순수 JDBC랑 JDBCTemplate를 썼던거에 비해 코드가 많이 줄어들었다. 참고로 pk기반이 아니면 JPQL을 해줘야한다.
그리고 JPA를 통해서 데이터를 저장하고 변경할때는 항상 Transactional안에서 해줘야 하기때문에 Memberservice 클래스에 @Transactional 어노테이션을 붙이자!
import org.springframework.transaction.annotation.Transactional
@Transactional
public class MemberService {
private final MemberRepository memberRepository; // final은 한번만 사용할 수 있는 엔티티(데이터베이스 테이블과 매핑되는 자바 클래스) 정의할 때 사용
public Memberservice(MemberRepository memberRepository) { // 직접생성하는것이 아닌 넣어주는 식으로
this.memberRepository = memberRepository;
} // 회원 리포지토리 코드가 회원 서비스 코드를 DI 가능하게 변경한다.
public Long join(Member member){
validateDuplicateMember(member); // 중복회원검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) { // 위 중복회원 검증 메소드
memberRepository.findByName(member.getName()) //결과가 바로 Optional 임
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다."); // try catch 말고 이런식으로 예외처리가 가능
});
}
public List<Member> findMember(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
이제 마지막으로 JPA를 사용하도록 스프링 빈에 JPA를 등록해줘야한다.
기존에 있던 JDBC 빈을 제거하고 JpaMemberRepository를 추가해준다.
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
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 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);
}
}
이렇게 하면 이제 JPA로 동작을 한다.
이제 마지막으로 스프링데이터 JPA를 해볼차례다. 스프링 데이터 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 {
// JPQL select m from Member m where m.name = ?
@Override
Optional<Member> findByName(String name);
}
이러면 끝이다..(어제부터 시작해서 어렵고 긴 코드와 위 코드를 비교해보면..혁신 그 자체라고 할 수 있다.)
심지어 bin도 이친구가 해준다. 그래서 딱히 bin을 등록할 필요도 없다.
package hello.hellospring;
import hello.hellospring.aop.TimeTraceAop;
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 final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
} // 스프링 데이터 JPA가 만들어놓은 구현체를 등록
@Bean //스프링 빈에 등록해라 라는 의미
public Memberservice memberService(){
//return new Memberservice(memberRepository());
return new Memberservice(memberRepository);
}
}
스프링 데이터와 스프링 데이터 JPA 구조
이러한 구조로 이루어진다.
스프링 데이터 JPA 는 인터페이스를 통한 기본적인 CRUD 기능도 있고,메서드 이름만으로도 조회 기능을 할 수 있다.
그리고 페이징 기능도 자동으로 제공해준다.
실제 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 이용해서 해결한다. 만약 이 조합으로 해결하기 어려운것이 존재한다면, 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리나 JDBCTemplate를 이용한다.
글이 너무 길어졌기 때문에 AOP기능은 다음블로그에 정리하는 식으로 하겠다.
'JAVA & SPRING > Spring 입문' 카테고리의 다른 글
스프링 핵심 원리 - 7일차(빈 생명주기 콜백) (0) | 2023.07.31 |
---|---|
스프링 입문 강의 - 5일차(AOP) (0) | 2023.07.20 |
스프링 입문 강의 - 4일차 (MVC, 순수 JDBC) (0) | 2023.07.19 |
스프링 입문 강의 - 3일차 (스프링 빈, Controller) (0) | 2023.07.18 |
스프링 입문 강의 - 2일차 (스프링 계층 구조) (0) | 2023.07.14 |