IT 프로그래밍-Web

[Web] Spring Boot로 Rest API만들기(3/3)

godsangin 2019. 12. 7. 16:02
반응형

오늘은 지난 시간에 이어 jdbc라이브러리를 통하여 local 서버의 mysql 데이터 베이스를 사용한 rest api만들기 실습을 진행해 보도록 하겠습니다.

 

우선 build.gradle에 새로운 라이브러리를 추가합니다.

// https://mvnrepository.com/artifact/org.mybatis/mybatis
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.0.1")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.15'

그런 뒤에 추가한 라이브러리를 사용하겠다는 것을 알리기 위해서 application.properties를 다음과 같이 작성합니다.

server.servlet.context-path=/
spring.mvc.view.suffix=.html
spring.datasource.url=jdbc:mysql://localhost:3306/my_database?&useSSL=false&useUniCode=yes&characterEncoding=UTF-8&serverTimezone=Asia/Seoul&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.config-location=classpath:mybatis-config.xml

여기서 localhost:3306/my_database는 Workbench 등을 이용하여 미리 데이터베이스를 생성해 주어야합니다.(또한 뒤에 나올 Phone 테이블도 따로 생성해주어야합니다...!)

데이터베이스에 접근하기 위하여 생성한 데이터베이스의 이름과 비밀번호를 입력해줍니다.

그리고 마지막 줄의 config-location을 설정하였는데 이 부분은 mybatis-config라는 파일에서 앞으로 데이터베이스 쿼리문을 작성하고 java단으로 넘겨줄 mapper가 어디에 정의되어있는지 알려주는 부분입니다.

src/main/resources하위에 mybatis-config.xml이라는 파일을 생성하고 다음과 같이 입력합니다.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <mappers>
    	<mapper resource="mapper/PhoneMapper.xml"/>
    </mappers>
</configuration>

 

이는 mapper/PhoneMapper.xml에서 phone이라는 테이블과 연관된 쿼리문을 저장하겠다는 의미입니다. 만약 테이블이 추가될 경우에 이곳에 Mapper와 경로를 추가해 주어야합니다.

 

그런 뒤에 sql 쿼리문과 자바 코드를 연결시킬 mapper를 생성합니다.(resources/mapper 패키지 생성)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.PhoneMapper">
	<select id="getPhoneByModel" resultType="com.example.demo.dto.Phone">
		SELECT *
		FROM phone
		WHERE model=#{model}
	</select>
	<select id="getTotalPhoneNum">
		SELECT COUNT(*)
		FROM phone
	</select>
	<select id="getTotalPhone" resultType="map">
		SELECT *
		FROM phone
	</select>
	<select id="getPhoneListByPageNum" resultType="map">
		select *
		from attachment
		as a left join board 
		as b on a.a_bnumber = b.b_number
		LIMIT #{a_start}, #{a_limit}
	</select>
	<insert id="insertPhone" useGeneratedKeys="true" keyProperty="model">
		INSERT INTO phone (model, price, volume) VALUES
		(#{model}, #{price}, #{volume})
	</insert>
	<update id="updatePhone">
		UPDATE phone set price=#{price}, volume=#{volume}, quantity=#{quantity}
		WHERE model=#{model}
	</update>
	<delete id="deletePhoneByModel">
		DELETE FROM phone
		WHERE model=#{model}
	</delete>
	
</mapper>

<PhoneMapper.xml>

여기서 중요한 점은 mapper의 namespace부분과 각 쿼리문의 인자와 resultType입니다.

첫번째로 namespace는 java단에서 mapper를 가져올 때 키워드로 사용되는 부분입니다.

두번째로 쿼리문의 인자는 이 mapper를 사용할 때 매개변수로 넘겨주는 인자로 #{...}과 같이 나타냅니다(매개변수를 넘길 경우 커스텀 객체 또는 map을 넘길 수도 있지만 변수명 또는 key값을 동일하게 맞춰줘야합니다.)

마지막으로 resultType입니다. resultType은 select문 실행 결과 java에서 전달받을 객체를 의미합니다. 여기서는 dto패키지의 Phone객체를 반환받을 것이기 때문에 phone으로 설정하거나, list로 반환받을 경우에 형변환이 자동으로 이루어지는 map으로 설정하였습니다.

 

자, 이제 db와의 연동을 위한 준비는 끝났습니다. 이제 java에서 나의 쿼리문을 수행하고 결과를 확인하는 일만 남았습니다. PhoneDao클래스를 아래와 같이 수정합니다.

package com.example.demo.dao;

import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.demo.dto.Phone;
@Repository
public class PhoneDao {
//	ArrayList<Phone> myPhoneList;//추후에 db로 바뀔 예정
	@Autowired(required=true)
	private SqlSession sqlSession;
	private String ns = "com.example.PhoneMapper.";
	
	public boolean addPhone(Phone p) {
		if(p == null) {
			return false;
		}
		try {
			sqlSession.insert(ns + "insertPhone", p);
		}catch(Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	public List<Phone> getPhoneList() {
		return sqlSession.selectList(ns + "getTotalPhone");
	}
	
	public boolean deletePhone(Phone p) {
		try {
			sqlSession.delete(ns + "deletePhoneByModel", p);
		}catch(Exception e) {
			return false;
		}
		return true;
	}
	
	public boolean updatePhone(Phone p) {
		try {
			sqlSession.update(ns + "updatePhone");
		}catch(Exception e) {
			return false;
		}
		return true;
	}
	
}

 

기존에 자바의 힙 영역에 저장되어 프로젝트가 종료되면 가비지콜렉터에 의해 메모리가 반환되었던 myPhoneList대신 local db와 연동되는 SqlSession객체를 사용하여 영구적으로 저장되는 데이터베이스를 사용할 수 있습니다.

여기서 String ns는 위에서 말씀드린 namespace와 동일하게 사용되고 sqlSession객체를 사용할 때 쿼리문에 따라 뒤에 추가되는 키워드를 사용할 수 있습니다. 그리고 매개변수를 넘겨주는 상황일 경우 해당 insert,select,update,delete메소드에 매개변수를 추가하기만 하면 됩니다.

 

여기까지 따라오셨다면 마지막으로 테스트를 해보겠습니다.

테스트를 위하여 매장에 휴대폰이 새로 추가되는 상황을 의미하는 api를 생성하도록 하겠습니다. PhoneService와 MyController를 다음과 같이 수정합니다.

package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.PhoneDao;
import com.example.demo.dto.Phone;

@Service
public class PhoneService {
	@Autowired
	PhoneDao phoneDao;
	
	public List<Phone> getAllPhone() {
		List<Phone> phones = phoneDao.getPhoneList();
		return phones;
	}
	public boolean insertPhone(Phone p) {
		return phoneDao.addPhone(p);
	}
	public boolean updatePhone(Phone p) {
		return phoneDao.updatePhone(p);
	}
	public boolean deletePhone(Phone p) {
		return phoneDao.deletePhone(p);
	}
}

<PhoneService.java>

package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.dto.Phone;
import com.example.demo.service.PhoneService;

@Controller
public class MyController {
	@Autowired
	PhoneService phoneService;
	@GetMapping("/")
	public String getHTMLHello() {
		return "hello";
	}
	@GetMapping("/allprice")
	@ResponseBody
	public List<Phone> getAllPhone() {
		return phoneService.getAllPhone();
	}
	
	@GetMapping("/insertPhone")
	@ResponseBody
	public boolean insertPhone() {
		Phone phone = new Phone("MyPhone", 1,1);
		return phoneService.insertPhone(phone);
	}
}

<MyController.java>

실제로는 insertPhone을 Post형식으로 받아 각각 다른 데이터를 service에 전달하지만 지금은 백엔드만을 다루기 때문에 샘플 데이터로 대체하였습니다.(절대로 프론트까지 하기 귀찮아서 그런게 아닙니다..절대..!)

 

자, 이제 프로젝트를 실행하고 insertPhone을 수행하면 다음과 같이 db에 insert되는 것을 확인할 수 있습니다.

그리고 추가된 휴대폰 정보를 불러오는 allphone도 잘 수행되는 것을 알 수 있었습니다.

만약 db생성 및 테이블 생성에서 오류를 겪고 계신 분들을 위하여 쿼리문을 첨부해 드리도록 하겠습니다. 저는 workbench를 사용하여 예제를 개발하였습니다.

create database my_database;
use my_database;
create TABLE phone(
	phone_id INT NOT NULL primary key auto_increment,
    model VARCHAR(20) NOT NULL,
    price INT NOT NULL,
    volume INT NOT NULL,
    quantity INT) DEFAULT CHARSET=utf8;
select * from phone;

 

이상으로 간단한 rest api를 만들어보았습니다. 생각보다 어렵지는 않죠 ?? ㅎㅎㅎㅎ

하지만 개발하려는 서비스가 테이블이 많이 필요하다던지 복잡한 조인문을 사용해야할 경우에는 훨씬 더 복잡한 로직을 어딘가에서(자바단이 될수도 있고, sql단이 될수도있죠...어쩌면 프론트단이 될수도..?) 처리해야만 할 것입니다. 

자 !! 그럼 여기까지로 rest api 서버 개발실습을 마무리 하도록 하겠습니다.

다들 좋은 하루 보내시고 궁금하신 점이 있으시다면 댓글 남겨주시길 바라겠습니다. 안농~~!!