<< 학습 목표 >>

1. DB에 접속하기 위한 라이브러리를 추가할 수 있다

2. DB에 접속하기 위한 설정 값을 추가할 수 있다.

3. 원하는 쿼리를 실행하고 결과를 받아올 수 있다.


이번 챕터를 시작하기 전에 프로젝트 -> com.study.chapter04 패키지를 추가하고 시작하자

 

또한 practice DB를 생성(1)한 후 그 안에 아래와 같이 member 테이블을 추가(2)하자

 

위 프로그램은 HeidiSQL로 Mysql Workbench 를 사용한다면 DB가 아니라 Schema 라고 부를 것

따라서 Mysql Workbench 를 사용한다면 [ Create Schema ] 를 통해 practice Schema 를 생성한 후 그 안에 member 테이블을 추가하자


이번 챕터에서는 DB와 관련된 것을 배울 것

먼저, 자주 사용하지는 않지만 DB에 쿼리를 보내고 결과를 받아오는 방법 중 하나인 JdbcTemplate을 사용해보고 그 다음에는 자주 사용하는 방법은 Mybatis를 사용해보자


서블릿에서는 DB에 쿼리를 보내고 결과를 받아오기 위해 JDBC를 직접 사용했었음

 

간단한 SELECT 문 조차 아래와 같이 깨알 같은 코드를 입력해야함

여기서 SELECT 쿼리와 직접 연관된 코드는 단 몇 줄이지만 SELECT 쿼리를 실행하고 결과를 받아오기 위해 부가적인 코드가 수 십 줄이 붙고 그 수 십 줄의 코드 중 하나라도 오타가 있어도 안되고 빠져도 안됐음 

( Spring Framework는 JSP, Servlet이 필수 선행되야하므로 위 코드는 이미 잘 알고 있을 것, 따라서 자세하게 설명하진 않음 )

 

이번에는 Spring Framework 프로젝트에서 위와 똑같이 SELECT 쿼리를 실행하고 결과를 받아오는 코드를 보자

 

서블릿에서 DB와 통신하기 위한 코드랑 SpringFramework에서 DB와 통신하기 위한 코드를 비교해보면 비교도 안되게 짧아진 걸 알 수 있음

 

특히 인상적인 부분은 서블릿은 DB와 통신하기 위한 자원들( Connection, PreparedStatement, ResultSet 등 ) 을 반드시 해제 해줘야했지만 SpringFramework는 그런 부분이 전혀 없음

 

이런 점들 때문에 서블릿 대신 SpringFramework를 사용하는 이유임


이제 본격적으로 SpringFramework에서 DB와 통신을 해보자

 

SpringFramework에서 DB와 통신하기 위해서는 두 가지 라이브러리를 추가해야함

1. spring-boot-starter-jdbc

2. mariadb connector/j

 

이 라이브러리는 서블릿에서 추가 했던 라이브러리와 같음

서블릿에서도 DB와 통신하기 위해서 JDBC 또는 DBCP를 추가해야했고 mariadb connector/j 를 추가해야했음

spring-boot-starter-jdbc 는 HikariCP 라고하는 DBCP를 사용하는 JDBC임

 

maven repository에서 spring-boot-starter-jdbc 를 추가하자

인터넷에서 maven repository 검색(1) 후 해당 사이트로 접속(2) 후 spring-boot-starter-jdbc 검색(3) 후 Spring Boot Starter JDBC 라이브러리 페이지로 들어감(4)

 

라이브러리 URL에서 버전값은 지울 것이기 때문에 어떤 버전을 사용해도 상관 없음

아무 버전 중 하나를 선택(1)해 들어가 라이브러리 URL(2) 복사

 

이제 sts로 돌아가 프로젝트 -> pom.xml의 dependencies 에 복사한 URL 붙여넣기

그 후 version 태그는 삭제하자

 

 

이번에는 mariadb connector/j 라이브러리를 추가하자

maven repository 에서 mariadb 로 검색(1) 후 MariaDB Java Client 라이브러리 페이지(2)로 들어가자

mariadb connector/j 라이브러리 역시 라이브러리 URL에서 버전값은 지울 것이기 때문에 어떤 버전을 사용해도 상관 없음

아무 버전 중 하나를 선택(1)해 들어가 라이브러리 URL(2) 복사

이제 sts로 돌아가 프로젝트 -> pom.xml의 dependencies 에 복사한 URL 붙여넣기

그 후 version 태그는 삭제하자


여기까지 DB와 통신하기 위한 라이브러리들을 추가했으니 이제 본격적으로 DB와 통신할 코드를 작성하자

! 한가지 주의할 점 ! SpringFramework에서 JDBC Template 을 사용해 DB와 통신하는 경우는 거의 없음

주로 JPA라는 ORM Framework 또는 MyBatis라는 SQL Mapper를 사용하므로 이번에 배울 JDBC Template은 간단하게만 보고 넘어갈 것

 

SpringFramework에서 DB와 통신하려면 DB에 접속하기 위한 설정값이 필요한데 이는 프로젝트 -> src/main/resources -> application.properties에 적어둠

(1). JDBC 드라이버 명

(2). 접속할 DB 경로

(3). DB 사용자명

(4). DB 사용자 비밀번호

 

이 설정값들 또한 JSP/Servlet에서 DB와 통신하기 위한 설정값과 똑같으니 상세하게 설명하진 않겠음


이번에는 클라이언트의 요청을 받고 요청을 처리할 서비스 메서드를 호출하기 위한 컨트롤러를 추가하자

프로젝트 -> com.study.chapter04 -> JdbcTemplateController 클래스 추가 후 아래 코드를 추가하자

package com.study.chapter04;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {

	@GetMapping("/chapter04/members")
	public void getAllMembers() {
		
	}
	
	@GetMapping("/chapter04/member")
	public void getMemberByNickname() {
		
	}
	
	@PostMapping("/chapter04/member")
	public void insertMember() {
		
	}
	
	@PutMapping("/chapter04/member")
	public void updateMember() {
		
	}
	
	@DeleteMapping("/chapter04/member")
	public void deleteMember() {
		
	}
	
}

<< 코드 설명 >>

이 컨트롤러는 아직 완성된 컨트롤러가 아님

어떤 컨트롤러를 만들려고 하는지 전체적인 뼈대를 보여주는 코드

 

1. 클라이언트가 모든 회원의 정보를 조회하고 싶을 때 접근하는 컨트롤러

 

2. 클라이언트가 닉네임으로 특정 회원의 정보를 조회하고 싶을 때 접근하는 컨트롤러

 

3. 클라이언트가 회원 정보를 저장하고 싶을 때 접근하는 컨트롤러

 

4. 클라이언트가 회원 정보를 수정하고 싶을 때 접근하는 컨트롤러

 

5. 클라이언트가 회원 정보를 삭제하고 싶을 때 접근하는 컨트롤러

 

 

 

 

 

 

 

 

 

 

 

 

 

 

가장 먼저 3번 회원 정보 저장 ( 회원 가입 ) 컨트롤러를 구현하자

더보기

member 테이블을 보면 회원 가입을 하려면 클라이언트는 id, pw, nickname, tel 값을 보내야함

( idx, joinDateTime 칼럼의 값은 기본값이 들어있고 isDel 칼럼은 NULL 허용이고 기본값이 NULL이므로 )

 

 

클라이언트가 보낸 값을 서버에서 받으려면 DTO가 필요하니 DTO를 추가하자

프로젝트 -> com.study.chapter04 -> InsertMemberDto 클래스 를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import lombok.Data;

@Data
public class InsertMemberDto {
	private String id;
	private String pw;
	private String nickname;
	private String tel;
}

 

이제 insertMember 컨트롤러에 InsertMemberDto를 매개변수로 넣자

 ( 전체 컨트롤러의 코드가 길어 해당 컨트롤러 코드만 썼음 )

@PostMapping("/chapter04/member")
public void insertMember(InsertMemberDto newMember) {
	System.out.println(newMember);
}

 

우선 Sysout으로 클라이언트가 보낸 값들을 잘 받았는지 확인해보자

이제 [ Boot Dashboard ] 를 통해 서버를 실행시키고 Postman으로 해당 컨트롤러로 id, pw, nickname, tel 값을 보내자

 

이제 컨트롤러의 코드를 완전하게 채워 회원 가입 ( 클라이언트가 보낸 회원 정보를 DB에 저장 ) 하자

컨트롤러의 전체 코드가 길어 필요한 코드만 첨부함

package com.study.chapter04;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {
	@Autowired
	JdbcTemplate jt;
    
    // ...
    
	@PostMapping("/chapter04/member")
	public void insertMember(InsertMemberDto newMember) {
		System.out.println("<< 회원 가입 시작 >>");
		
		String id = newMember.getId();
		String pw = newMember.getPw();
		String nickname = newMember.getNickname();
		String tel = newMember.getTel();
		
		String query = "INSERT INTO member(id, pw, nickname, tel) VALUES(?, ?, ?, ?)";
		
		jt.update(query, id, pw, nickname, tel);
		
		System.out.println("<< 회원 가입 성공 >>");
	}
    
    // ...
    
}

 << 코드 설명 >>

(1). INSERT 쿼리를 실행시킬 때는 update 메서드를 사용함
      이때 INSERT 쿼리의 ? 는 update 메서드의 두 번째 매개변수부터 차례대로 할당됨

Servlet을 배울 때 사용했던 PreparedStatement를 떠올려보면 PreparedStatement 보다 JdbcTemplate이 훨씬 사용하기 편하다는걸 느낄 수 있을 것

 

 

서버를 재시작하고 Postman 을 사용해 서버로 다시 데이터를 보내 회원 가입을 해보자

아직 회원 정보 조회 컨트롤러를 구현하지 않았으므로 직접 테이블 내 데이터를 조회해 회원 가입이 정상적으로 이뤄졌는지 확인하자

 

회원 가입 컨트롤러를 통해 아래 회원 정보도 추가하자

id = id2, pw = pw2, nickname = 홍길동, tel = 010-2222-2222

id = id3, pw = pw3, nickname = 고영희, tel = 010-3333-3333



 

이번에는 1번 전체 회원 조회 컨트롤러를 구현하자

더보기

전체 회원 정보를 조회할 때 클라이언트가 보낼 값은 없음

대신 SELECT 쿼리를 사용해 회원 정보를 조회한 후 JDBC Template이 조회한 회원들의 정보를 DTO에 담아서 반환해야하므로 조회한 회원들의 정보를 담을 DTO인 SelectMemberDto 를 추가하자

 

프로젝트 -> com.study.chapter04 -> SelectMemberDto 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import java.time.LocalDateTime;

import lombok.Data;

@Data
public class SelectMemberDto {
	private String id;
	private String pw;
	private String nickname;
	private String tel;
	private int idx;
	private LocalDateTime joinDateTime;
	private boolean isDel;
	
	public boolean getIsDel() {
		return isDel;
	}
	
	public void setIsDel(boolean isDel) {
		this.isDel = isDel;
	}
}

! 잠시 ! SelectMemberDto에 대해서 자세히 알아보고 넘어가자

isDel 멤버 변수는 getter, setter를 직접 선언했는데 Lombok은 is 로 시작하는 멤버 변수의 경우 getter 메서드명을 멤버 변수명 그대로 isDel 로 만들어줌

또한 setter 메서드명은 is를 뺀 setDel 로 만들어줌

그렇게 되면 isDel 칼럼 값을 isDel 멤버 변수에 저장할 수 없음

따라서 is 로 시작하는 멤버 변수의 경우 위와 같이 getIsDel, setIsDel 처럼 직접 getter, setter 메서드를 선언해줘야함

 

다시 본론으로 돌아와서 전체 회원 정보를 조회하는 컨트롤러를 완성시켜 전체 회원 정보를 조회하자

package com.study.chapter04;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {
	@Autowired
	JdbcTemplate jt;
	
	@GetMapping("/chapter04/members")
	public void getAllMembers() {
		System.out.println("<< 회원 정보 조회 시작 >>");
		
		String query = "SELECT * FROM member";
		
		List<SelectMemberDto> memberList = jt.query(query, new BeanPropertyRowMapper<SelectMemberDto>(SelectMemberDto.class));
		for(SelectMemberDto member : memberList) {
			System.out.println(member);
		}
		
		System.out.println("<< 회원 정보 조회 종료 >>");
	}
    
	// ...
    
}

<< 코드 설명 >>

(1). SELECT 쿼리를 실행할 때는 query 메서드 또는 queryForObject 메서드 중 한 메서드를 호출함
       query 메서드는 조회 결과가 여러 개 일 때 사용하는 메서드
       queryForObject 메서드는 조회 결과가 하나일 때 사용하는 메서드
      전체 회원 정보를 조회할 것이므로 query 메서드를 호출했음
      전체 회원 정보를 조회한 후 조회한 회원 정보를 담기 위해 query 메서드의 두 번째 매개변수로 BeanPropertyRowMapper 클래스의 인스턴스를 넣었음
      BeanPropertyRowMapper 클래스는 제네릭스가 적용된 클래스로 인스턴스를 생성할 때 제네릭 타입을 지정해야함
      조회 결과를 담을 DTO를 BeanPropertyRowMapper 클래스의 제네릭 타입으로 지정하면 됨

 

 

서버를 재시작 한 후 Postman으로 이 컨트롤러를 호출해 전체 회원 정보가 조회되는지 확인해보자



이번에는 2번 닉네임으로 특정 회원의 정보를 조회 컨트롤러를 구현하자

더보기

클라이언트가 보낸 닉네임으로 특정 회원의 정보를 조회해야하므로 컨트롤러에서 클라이언트가 보낸 닉네임을 받아야함

클라이언트가 정보(데이터들)를 보낸다면 DTO로 받아야함

그래서 3번 회원 정보 저장 컨트롤러에서는 클라이언트가 보낸 정보를 DTO로 받았음

 

이번에는 클라이언트가 데이터(닉네임)을 보낼것이기 때문에 컨트롤러에 String 타입 매개변수 하나만 있으면 됨

 

컨트롤러에 매개변수가 필요하다는걸 인지하면서 아래와 같이 컨트롤러를 완성하자

package com.study.chapter04;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {
	@Autowired
	JdbcTemplate jt;
    
	@GetMapping("/chapter04/member")
	public void getMemberByNickname(String nickname) {
		System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
		
		String query = "SELECT * FROM member WHERE nickname = '" + nickname + "'";
		
		SelectMemberDto member = jt.queryForObject(query, new BeanPropertyRowMapper<SelectMemberDto>(SelectMemberDto.class));
		
		System.out.println(member);
		
		System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
	}
    
	// ...
}

<< 코드 설명 >>

(1). 이번에는 조회 결과가 하나이므로 SELECT 쿼리를 실행할 때 queryForObject 메서드를 사용했음
      이 메서드 역시 조회 결과를 담을 DTO를 두 번째 매개변수로 넣어줌
      특히, 이 메서드의 경우 조회 결과가 없으면 예외가 발생함
      예외가 발생하지 않도록 할 수도 있고 예외가 발생했을 다면 try ~ chatch 로 처리할 수도 있지만 이 글의 맨 처음에 언급했듯 JDBCTemplate은 거의 사용하지 않으므로 "그렇구나" 정도로만 생각하고 넘어가면 됨

 

 

서버를 재시작한 후 닉네임으로 회원 정보를 조회해보자



 

이번에는 4번 회원 정보 수정 컨트롤러를 구현하자

더보기

회원 정보 수정에서는 아이디는 변경할 수 없고 비밀번호, 닉네임, 연락처는 변경할 수 있게 구현하자

클라이언트는 정보를 수정할 회원의 아이디, 변경할 비밀번호, 변경할 닉네임, 변경할 연락처를 보내야함
비밀번호, 닉네임, 연락처 중 변경하지 않는것이 있다면 원래 데이터를 그대로 담아 보내야한다고 상황을 설정하자

 

이와 같이 회원 정보 수정 컨트롤러를 구현하려면 클라이언트가 수정할 회원의 정보를 보내므로 컨트롤러에서는 클라이언트가 보낸 값을 DTO로 받으면 됨

DTO는 회원 정보 때 구현한 InsertMemberDto를 그대로 활용하자

 

이제 아래와 같이 컨트롤러를 완성하자

package com.study.chapter04;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {
	@Autowired
	JdbcTemplate jt;
    
	@PutMapping("/chapter04/member")
	public void updateMember(InsertMemberDto newMember) {
		System.out.println("<< 회원 정보 수정 시작 >>");
		
		String id = newMember.getId();
		String pw = newMember.getPw();
		String nickname = newMember.getNickname();
		String tel = newMember.getTel();
		
		String query = "UPDATE member SET pw = ?, nickname = ?, tel = ? WHERE id = ?";
		
		jt.update(query, pw, nickname, tel, id);
		
		System.out.println("<< 회원 정보 수정 성공 >>");
	}
    
	// ...
}

 << 코드 설명 >>

(1). UPDATE 쿼리 역시 INSERT 쿼리와 같이 update 메서드를 사용해 실행함
     또한 ? 역시 INSERT 쿼리와 같이 두 번째 매개변수부터 순서대로 할당됨

 

 

서버를 재시작한 후 회원 정보 수정 컨트롤러가 정상적으로 동작하는지 확인해보자



 

이번에는 5번 회원 정보 삭제 컨트롤러를 구현하자

더보기

회원 정보를 삭제할 때는 클라이언트가 삭제할 회원의 아이디와 비밀번호를 보내야 한다고 하자

 

클라이언트가 삭제할 회원의 정보(아이디, 비밀번호) 를 보내므로 컨트롤러에서는 클라이언트가 보낸 정보를 DTO에 담아야하지만 클라이언트가 보내는 데이터가 2개(아이디, 비밀번호) 밖에 없으므로 매개 변수를 두 개 선언해도 됨

 

최대한 일관성을 유지하기 위해 DTO로 받도록 프로젝트 -> com.study.chapter04 -> DeleteMemberDto 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import lombok.Data;

@Data
public class DeleteMemberDto {
	private String id;
	private String pw;
}

 

이제 컨트롤러를 구현하자

package com.study.chapter04;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@Controller
public class JdbcTemplateController {
	@Autowired
	JdbcTemplate jt;
    
	@DeleteMapping("/chapter04/member")
	public void deleteMember(DeleteMemberDto deleteMember) {
		System.out.println("<< 회원 정보 삭제 시작 >>");
		
		String id = deleteMember.getId();
		String pw = deleteMember.getPw();
		
		String query = "DELETE FROM member WHERE id = ? AND pw = ?";
		
		jt.update(query, id, pw);
		
		System.out.println("<< 회원 정보 삭제 성공 >>");
	}
    
	// ...
	
}

<< 코드 설명 >>

INSERT, UPDATE와 동일하므로 별도의 설명은 생략함

 

서버를 재시작한 후 회원 정보 삭제가 정상적으로 이뤄지는지 확인해보자



여기까지 거의 사용하진 않지만 SpringFramework가 제공하는 JDBCTemplate을 사용해 쿼리를 실행하고 결과를 받아오는 방법을 봤음

 

JSP/Servlet을 배우며 쿼리를 실행하고 결과를 받아오는 코드와 비교해보면 굉장히 간단해졌다는 걸 알 수 있음

지금까지 SpringFramework를 사용하는 이유를 찾지 못했다면 여기서는 확 느꼈을 것

 

꼭 ! "그렇다니까 그런가보다' 하고 넘어가지 말고 JSP/Servlet을 배우며 쿼리를 실행하고 결과를 받아오는 코드와 JDBCTemplate을 사용해 쿼리를 실행하고 결과를 받아오는 코드를 비교해보자

728x90
LIST