본문 바로가기
Java/Spring Boot

[Spring Boot] JdbcTemplate - H2 CRUD 예제 (1)

by so-easy 2021. 8. 1.

JdbcTemplate은 Data를 다루는 여러가지 기술들 중 실무에서도 많이 쓰는 친구라고 한다.

JdbcTemplate으로 H2 Database에 연결해 간단한 CURD API를 만들어본다.

(H2 설치 및 커넥션은 지난글 에서 다 마쳤다는 가정하에 아래 내용을 진행한다.)

 

내용이 길어져 1장에서는 Create와 Read를, 2장에서는 Update와 Delete를 다룬다.

0. Overview

먼저 전체 구조를 한눈에 보고 시작해보자.

MVC 패턴에 따라 아래왜 같은 의존성 구조를 갖는다. 나는 항상 아래 순서대로 코드를 작성한다.

Domain -> Repository -> Service -> Controller

아래 과정을 쭉 끝까지 하고나면, 프로젝트 내 패키지 구조는 이렇게 된다.

1. Dependency

dependency 설정은 중요하니까 한번 더 짚고 넘어간다. 이미 되어있으면 패스

(어느순간 lombok없이는 못살게 되었기 때문에 꼭 추가해준다.)

Maven)_pom.xml_파일에 아래 내용 추가

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>

<!-- lombok도 추가해주기 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>

Gradle)_build.gradle_파일에 아래 내용 추가

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc', version: '2.5.2'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

2. Domain

domain 패키지 만들기

POJO class 만들기 (Member.java)

  • lombok의 @Data 어노테이션은 getter, setter, AllArgsConstructor 등을 알아서 다 만들어주는 아주 훌륭한 친구다.👍🏻
package com.easyfordev.study.h2crud.domain;

import lombok.Data;

@Data
public class Member {
    private Long id;
    private String name;
}

3. Repository

repository 패키지 만들기

Repository 인터페이스 만들기 (MemberRepository.java)

package com.easyfordev.study.h2crud.repository;

import com.easyfordev.study.h2crud.domain.Member;
import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    List<Member> findAll();
    Optional<Member> findByName(String name);
}

Repository 구현체 class 만들기 (MemberRepositoryImpl.java)

  • 멤버 변수 : JdbcTemplate 객체 (Spring이 만들어주는 걸 DI로 주입만 받아서 사용한다)
  • CRUD에 해당하는 메소드
    • Create - save()
    • Read -findAll(), findById()
    • Update - update()
    • Delete - delete()
  • RowMapper 구현
package com.easyfordev.study.h2crud.repository;

import com.easyfordev.study.h2crud.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 org.springframework.stereotype.Repository;

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;

@Repository
public class MemberRepositoryImpl implements MemberRepository {
    private final JdbcTemplate jdbcTemplate;

//   이렇게 그냥 생성해도 되긴 된다.
//    public MemberRepositoryImpl(DataSource dataSource) {
//        jdbcTemplate = new JdbcTemplate(dataSource);
//    }
    // DI 로 수정
    @Autowired
    public MemberRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @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 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 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;
            }
        };
    }

}

4. Service

service 패키지 만들기

Service 클래스 만들기(MemberService.java)

  • findOneMember()의 경우 인자로 넘어온 name값이 없는 회원일 수 있으므로, Optional로 감싸서 적절하게 처리해준다.
package com.easyfordev.study.h2crud.service;

import com.easyfordev.study.h2crud.domain.Member;
import com.easyfordev.study.h2crud.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * Create) 회원가입
     * @param member 회원 가입할 Member 객체
     * @return 가입 성공한 회원의 ID
     */
    public Long join(Member member){
        memberRepository.save(member);
        return member.getId();
    }

    /**
     * Read) 전체 회원 조회
     * @return List<Member> 전체 회원 목록
     */
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    /**
     * Read) 특정 회원 조회
     * @param name 회원 이름
     * @return Optional<Member> 이름이 name인 회원의 데이터
     */
    public Optional<Member> findOneMember(String name) {
        return memberRepository.findByName(name);
    }

}

5. Controller

controller 패키지 만들기

MemberForm 클래스 만들기(MemberForm.java)

POST로 사용자의 입력을 form을 받는다고 가정하고, Form 클래스를 정의한다. 아주 간단하게 name만 멤버로 있다.

package com.easyfordev.study.h2crud.controller;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MemberForm {
    private String name;
}

Controller 클래스 만들기(MemberController.java)

package com.easyfordev.study.h2crud.controller;

import com.easyfordev.study.h2crud.domain.Member;
import com.easyfordev.study.h2crud.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @PostMapping("/members/new")
    public ResponseEntity<?> create(MemberForm memberForm) {
        Member member = new Member();
        member.setName(memberForm.getName());

        memberService.join(member);

        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping("/members")
    public ResponseEntity<?> readAll() {
        List<Member> members = memberService.findMembers();

        // Response에 넣어준다
        Map<String, Object> response = new HashMap<>();
        response.put("members", members);

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping("/member/{name}")
    public ResponseEntity<?> readAll(@PathVariable("name") String name) {
        Optional<Member> member = memberService.findOneMember(name);

        Map<String, Object> response = new HashMap<>();

        if(!member.isPresent()) { // 그런 이름의 회원이 없으면
            response.put("message", "No such member");
            response.put("member", null);
        } else { // 정상적으로 조회됐으면
            response.put("message", "Success");
            response.put("member", member);
        }

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

}

6. 실행

⭐️ 이전 2개의 글 대로 셋팅을 다 마쳤다는 가정 하에 실행시켜야 잘 됨을 유의하자!

 

먼저 H2를 켜준다.

./h2/bin/h2.sh  -webAllowOthers

 

Spring Boot 프로젝트의 메인함수를 실행시키면 잘 돌아간다.

 

Postman으로 하나씩 테스트해본다.

1) Create

Form으로 name만 넣어준다. 응답 내용은 안해놔서 없지만 200 OK로 응답이 잘 왔다.

2) Read

2-1) Read All

아무것도 없이 URL만 넣고 요청을 보내면 응답이 잘 온다. 방금 추가한 seulgi도 확인된다.

2-1) Read One

DB에 있는 이름을 조회한 경우

없는 이름을 조회한 경우

 

코드

https://github.com/easyfordev/springboot-jdbctemplate-h2-crud-example

 

GitHub - easyfordev/springboot-jdbctemplate-h2-crud-example: Spring Boot에서 JdbcTemplate을 활용하여 DB에 접근하는

Spring Boot에서 JdbcTemplate을 활용하여 DB에 접근하는 심플한 CRUD 예제 - GitHub - easyfordev/springboot-jdbctemplate-h2-crud-example: Spring Boot에서 JdbcTemplate을 활용하여 DB에 접근하는 심플한 CRUD 예제

github.com


참고

인프런에서 김영한님의 스프링 입문 강의와 아래 링크 내용을 참고해서 각색했다.

 

https://springframework.guru/spring-jdbctemplate-crud-operations/

 

Spring JdbcTemplate CRUD Operations

JDBC is the Java API for working with relational databases. The Spring Framework provides JdbcTemplate which eliminantes the cerimonial code around JDBC.

springframework.guru

 

댓글