์•ˆ๋…•ํ•˜์„ธ์š”, ์ผ€์ด์น˜์ž…๋‹ˆ๋‹ค.

์˜ค๋Š˜์€ ์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“œ๋Š” ์›น์•ฑ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉํ•  ๋„๊ตฌ๋Š” Eclipse 2020-06 R ์ด๊ณ ์š”, ์Šคํ”„๋ง๋ถ€ํŠธ์™€ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ, ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™์€ mamp์™€ mybatis๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์šฐ์„  ๊ธฐ๋ณธ์ ์ธ ๋„๊ตฌ๋“ค๊ณผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค ๊ทธ๋ฆฌ๊ณ  ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์„ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœํ™˜๊ฒฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

  1. MacOS Catalina (10.15.6)
  2. Eclipse 2020-06 R (Download)
  3. Eclipse Spring Tool Suite 4 (์„ค์น˜ํ•˜๊ธฐ ๊ฐ€์ด๋“œ)
  4. MAMP (Download) -> community ๋ฒ„์ „ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์œผ์„ธ์š”, Window์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ WAMP๋ฅผ ์„ค์น˜ํ•˜์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  5. DBeaver (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฌด๋ฃŒ ํˆด Download) -> community ๋ฒ„์ „ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์œผ์„ธ์š”

 

์ž, ์œ„ ๋„๊ตฌ๋“ค์„ ๋ชจ๋‘ ์„ค์น˜์™„๋ฃŒํ–ˆ๋‹ค๋ฉด ์ด์ œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์šฐ์„  ๊ฒฐ๊ณผ๋ฌผ์€ ์•„๋ž˜ ๋™์˜์ƒ์ฒ˜๋Ÿผ ๋‚˜์˜ฌ๊ฒ๋‹ˆ๋‹ค.

 

์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ ์›น์•ฑ

์ž, ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“œ๋Š”์ง€ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ง„ํ–‰์€ ์•„๋ž˜ ์ˆœ์„œ๋Œ€๋กœ ํ•ฉ๋‹ˆ๋‹ค.

  1. ํ…Œ์ด๋ธ” ์„ค๊ณ„
  2. ๋ฐฑ์—”๋“œ ๊ตฌํ˜„
  3. ํ”„๋ก ํŠธ ๊ตฌํ˜„

 

ํ…Œ์ด๋ธ” ์„ค๊ณ„

์šฐ์„  ํ…Œ์ด๋ธ” ์„ค๊ณ„๋ถ€ํ„ฐ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. (MAMP์„ค์น˜ ๋ฐ ํ…Œ์ด๋ธ” ์†Œ์œ ์ž ๊ถŒํ•œ ๋“ฑ์˜ ์„ค์ •์€ ์—ฌ๊ธฐ์„œ ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

ํ…Œ์ด๋ธ”์€ contact ๋ผ๊ณ  ๋ช…๋ช…์ง€์–ด์ฃผ๊ณ  ์ปฌ๋Ÿผ์€ ์•„๋ž˜์ฒ˜๋Ÿผ ๋‘ ๊ฐœ๋งŒ ์ถ”๊ฐ€๋ฅผ ํ•ด์ค„ ๊ฒ๋‹ˆ๋‹ค.

CREATE TABLE `contact` (
  `name` varchar(100) NOT NULL,
  `phone` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

 

 

DBeaver์—์„œ ์กฐํšŒํ•œ contact ํ…Œ์ด๋ธ” ๋ช…์„ธ

์ •๋ง ๊ฐ„๋‹จํ•œ ์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ ํ”„๋กœ๊ทธ๋žจ์ด๋ฏ€๋กœ ์ด๋ฆ„๊ณผ ์—ฐ๋ฝ์ฒ˜ ์ •๋ณด๋งŒ ๋‹ด์„ ์˜ˆ์ •์ด๊ณ  ๊ฐœ์ธ์ •๋ณด ์•”ํ˜ธํ™” ๊ฐ™์€๊ฑด ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ž…๋ ฅ๋ฐ›์€ ์ •๋ณด ๊ทธ๋Œ€๋กœ string์œผ๋กœ ์ €์žฅ์„ ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ…Œ์ด๋ธ” ์„ค๊ณ„๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์ด์ œ ๋ฐฑ์—”๋“œ์—์„œ ์ฟผ๋ฆฌํ•ด์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„์„ ๋“ค์–ด๊ฐ€๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ

์šฐ์„  ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ๊ฒ ์ฃ .

์•„๋ž˜ ์ˆœ์„œ๋Œ€๋กœ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

Project Explorer > New > Project ์„ ํƒ

 

Wizards์—์„œ spring์œผ๋กœ ๊ฒ€์ƒ‰ํ•œ ๋’ค Spring Starter Prject ์„ ํƒ (์ด๊ฒŒ ์•ˆ๋‚˜์˜จ๋‹ค๋ฉด STS์„ค์น˜ํ•˜๊ธฐ๋ถ€ํ„ฐ ํ•˜๊ณ  ์˜ค์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.)

 

 

Name ํ•ญ๋ชฉ์€ ํ”„๋กœ์ ํŠธ๋ช…์„ ์ž…๋ ฅํ•˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์— contact-manager ๋ผ๊ณ  ๋„ฃ์–ด์ฃผ์‹œ๊ณ  Java Version์€ 8 ์ด์ƒ์œผ๋กœ ์•„๋ฌด๊ฑฐ๋‚˜ ์“ฐ์…”๋„ ๋ฌด๋ฐฉํ• ๊ฒ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” Java 11 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

Next๋ฅผ ๋ˆŒ๋Ÿฌ ๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐ„ ๋’ค Available ๊ฒ€์ƒ‰์ฐฝ์—์„œ spring web, lombok, mybatis, mysql๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ์šฐ์ธก์˜ selected ํ•ญ๋ชฉ์— ์„ธ ๊ฐœ ํ•ญ๋ชฉ์ด ๋“ค์–ด๊ฐ€๋„๋ก ์ฒดํฌํ•˜๊ณ  Finish ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ด๋ฏธ ์‚ฌ์šฉํ•œ ์ ์ด ์žˆ์–ด์„œ Frequently Used์— ํ•ญ๋ชฉ์ด ๋‚˜ํƒ€๋‚˜์„œ ์ฒดํฌ๋งŒ ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ํ•˜์‹œ๋Š” ๋ถ„๋“ค์€ Available์—์„œ ๊ฒ€์ƒ‰ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์šฐ์ธก ํ•˜๋‹จ์— progress bar๊ฐ€ ์ง„ํ–‰๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋˜๋Š” Progress View์—์„œ ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ์ฃ . Progress View๊ฐ€ ์•ˆ๋ณด์ธ๋‹ค๋ฉด ์ดํด๋ฆฝ์Šค ์ƒ๋‹จ ๋ฉ”๋‰ด์—์„œ Window > Show View > Other > Progress๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

์ง„ํ–‰์ด ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฉด Project Explorer์—์„œ ์•„๋ž˜์ฒ˜๋Ÿผ ํŒŒ์ผํŠธ๋ฆฌ๊ฐ€ ๋ณด์ผ๊ฒ๋‹ˆ๋‹ค.

pom.xml ํŒŒ์ผ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์ด ๋“ค์–ด์žˆ์ฃ .

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>contact-manager</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>contact-manager</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>11</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.3</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

 

์ด์ œ ์ž๋ฐ”๋กœ ์ฝ”๋”ฉํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

์šฐ์„  ์œ„์—์„œ ๋งŒ๋“  contact ํ…Œ์ด๋ธ”์˜ ๋‚ด์šฉ์„ ์กฐํšŒํ•ด์˜ค๋Š” API๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

com.example.demo ํŒจํ‚ค์ง€ ํ•˜์œ„์— domain ๋ผ๋Š” ์ด๋ฆ„์˜ ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ํŒจํ‚ค์ง€์— ContactInfo.java ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์„ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ContactInfo {
    private String name;
    private String phone;
}

 

ContactInfo ํด๋ž˜์Šค๋Š” contact ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๋™ํ•  dto์ž…๋‹ˆ๋‹ค.

 

com.example.demo ํŒจํ‚ค์ง€ ํ•˜์œ„์— mapper ๋ผ๋Š” ์ด๋ฆ„์˜ ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ํŒจํ‚ค์ง€์— ContactInfoMapper.java ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค. ContactInfoMapper๋Š” ํด๋ž˜์Šค๊ฐ€ ์•„๋‹Œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ƒ์„ฑํ•ด์ฃผ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.

import com.example.demo.domain.ContactInfo;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface ContactInfoMapper {

    @Select("select * from contact")
    List<ContactInfo> selectAll();

    @Insert("insert into contact (name, phone) values (#{name}, #{phone})")
    int insert(ContactInfo contactInfo);

    @Delete("delete from contact where name = #{name}")
    int delete(String name);

    @Select("<script>" +
            "select * from contact " +
            "<where>" +
            "<if test=\"name != null and name.length > 0\">and name = #{name}</if>" +
            "<if test=\"phone != null and phone.length > 0\">and phone = #{phone}</if>" +
            "</where>" +
            "</script>")
    List<ContactInfo> selectBy(@Param("name") String name, @Param("phone") String phone);

    @Update("update contact " +
            "set phone = #{phone} " +
            "where name = #{name}")
    int update(String name, String phone);
}

 

์ด ContactInfoMapper ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค์™€ ์—ฐ๋™์ด ๋˜์–ด ์ฟผ๋ฆฌ๋ฅผ database๋กœ ์ „๋‹ฌํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

 

 

com.example.demo ํŒจํ‚ค์ง€ ํ•˜์œ„์— service ๋ผ๋Š” ์ด๋ฆ„์˜ ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ํŒจํ‚ค์ง€์— ContactInfoService.java ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

์ด ํŒŒ์ผ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚ด์šฉ์„ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

import java.util.List;

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

import com.example.demo.domain.ContactInfo;
import com.example.demo.mapper.ContactInfoMapper;

@Service
public class ContactInfoService {

	@Autowired
	private ContactInfoMapper contactInfoMapper;

	public List<ContactInfo> getEveryContactInfo() {
		return contactInfoMapper.selectAll();
	}

	public int addContactInfo(ContactInfo contactInfo) {
		return contactInfoMapper.insert(contactInfo);
	}

	public int delContactInfo(String name) {
		return contactInfoMapper.delete(name);
	}

	public List<ContactInfo> getContactInfos(String name, String phone) {
		return contactInfoMapper.selectBy(name, phone);
	}

	public int updateContactInfo(String name, String phone) {
		return contactInfoMapper.update(name, phone);
	}
}

 

ContactInfoMapper์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์—ญํ• ๋งŒ ํ•˜๊ณ  ์žˆ์ง€๋งŒ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด ๊ฐ์ข… ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋“ค์–ด๊ฐ€์•ผํ•  ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

์กฐํšŒํ•œ ๋’ค์— ์ •๋ ฌ์„ ํ•œ๋‹ค๋“ ์ง€, ํ•„ํ„ฐ๋ง์„ ํ•œ๋‹ค๋“ ์ง€ ํ•˜๋Š” ๋กœ์ง์€ ์ด ์„œ๋น„์Šค ํด๋ž˜์Šค์— ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์ด์ œ controller์ชฝ์—์„œ ์‚ฌ์šฉํ•  ์‘๋‹ต ๋„๋ฉ”์ธ ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋” ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

com.example.demo.domain ํŒจํ‚ค์ง€์— Response.java ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@JsonInclude(Include.NON_NULL)
public class Response<T> {

    private int code;
    private String message;
    private T data;

    public Response(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public Response(int code, String message, T data) {
        this(code, message);
        this.data = data;
    }
}

 

๋งˆ์ง€๋ง‰์œผ๋กœ com.example.demo ํŒจํ‚ค์ง€ ํ•˜์œ„์— controller ๋ผ๋Š” ์ด๋ฆ„์˜ ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ํŒจํ‚ค์ง€์— ContactInfoController.java ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์„œ ์•„๋ž˜ ๋‚ด์šฉ์„ ๋„ฃ์–ด์ฃผ์„ธ์š”.

import com.example.demo.domain.ContactInfo;
import com.example.demo.domain.Response;
import com.example.demo.service.ContactInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/contact-info")
public class ContactInfoController {

	@Autowired
	private ContactInfoService contactInfoService;

	@GetMapping("/list") // --> localhost:8080/contact-info/list
	public List<ContactInfo> getAllContactInfo() {
		return contactInfoService.getEveryContactInfo();
	}

	@GetMapping("/get")
	public ResponseEntity<Response> getContactInfos(
			@RequestParam(required = false) String name,
			@RequestParam(required = false) String phone) {
		List<ContactInfo> result = contactInfoService.getContactInfos(name, phone);
		if (result.size() == 0) {
			return ResponseEntity.status(HttpStatus.OK).body(new Response(204, "๋ฐ์ดํ„ฐ ์—†์Œ"));
		}
		return ResponseEntity.ok(new Response(200, "์กฐํšŒ ์„ฑ๊ณต", result));
	}

	@PostMapping("/add")
	public ResponseEntity<Response> addNewContactInfo(@RequestBody ContactInfo contactInfo) {
		contactInfoService.addContactInfo(contactInfo);
		return ResponseEntity.ok(new Response(200, "๋“ฑ๋ก ์„ฑ๊ณต"));
	}

	@DeleteMapping("/del")
	public ResponseEntity<Response> delContactInfo(@RequestParam String name) {
		int result = contactInfoService.delContactInfo(name);
		if (result == 0) {
			return ResponseEntity.status(HttpStatus.OK).body(new Response(204, "๋ฐ์ดํ„ฐ ์—†์Œ"));
		}
		return ResponseEntity.ok(new Response(200, "์‚ญ์ œ ์„ฑ๊ณต"));
	}

	@PutMapping("/update")
	public ResponseEntity<Response> updateContactInfo(@RequestParam String name, @RequestParam String phone) {
		int result = contactInfoService.updateContactInfo(name, phone);
		if (result == 0) {
			return ResponseEntity.status(HttpStatus.OK).body(new Response(204, "๋ฐ์ดํ„ฐ ์—†์Œ"));
		}
		return ResponseEntity.ok(new Response(200, "์ˆ˜์ • ์„ฑ๊ณต"));
	}

	@ExceptionHandler
	public ResponseEntity<Response> contactInfoErrorHandler(Exception e) {

		log.error("error!!, {}", e.getClass().getName(), e);

		if (e instanceof DuplicateKeyException) {
			return ResponseEntity.status(HttpStatus.CONFLICT).body(new Response(409, "์ด๋ฏธ ์ค‘๋ณต๋œ ์ด๋ฆ„์ด ๋“ฑ๋ก๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค."));
		} else {
			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new Response(500, "์„œ๋ฒ„์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค."));
		}
	}
}

์ด ํด๋ž˜์Šค๊ฐ€ ํ”„๋ก ํŠธ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์œผ๋กœ API url์„ ์ •์˜ํ•˜๊ณ  ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์„ ์ „๋‹ฌ๋ฐ›์•„์•ผ ํ•˜๋Š”์ง€, ์‘๋‹ต์€ ์–ด๋–ป๊ฒŒ ์ฃผ๊ณ  ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋Š” ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๋‚ด์šฉ์ด ๋‹ด๊ฒจ์žˆ์ฃ . 

 

์ด์ œ ๋ฐฑ์—”๋“œ ๊ตฌํ˜„์€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์•„ ์ฐธ!! ํ•˜๋‚˜ ๋นผ๋จน์€๊ฒŒ ์žˆ๋„ค์š”. database ์ ‘์† ์ •๋ณด๋ฅผ ๋นผ๋จน์—ˆ์Šต๋‹ˆ๋‹ค. ์ž, src/main/resources ํ•˜์œ„์˜ application.properties ํŒŒ์ผ์„ ์—ด์–ด์„œ ์ง์ ‘ ์ƒ์„ฑํ•˜์…จ๋˜ database ์ ‘์† ์ •๋ณด๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ๋„ฃ์–ด์ฃผ์„ธ์š”.

spring.datasource.url=jdbc:mysql://localhost:8889/javastudy?useSSL=false&serverTimezone=UTC
spring.datasource.username=javastudy
spring.datasource.password=javastudy
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

์ ‘์† ํฌํŠธ(8889)์™€ database๋ช…(javastudy) ๊ทธ๋ฆฌ๊ณ  username, password ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜์‹  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•ด์ฃผ์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด Project Explorer์— ์•„๋ž˜์™€ ๊ฐ™์ด ํŒŒ์ผ๋“ค์ด ์กด์žฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ContactManagerApplication์„ Sppring Boot App์œผ๋กœ ๊ธฐ๋™ํ•ด์„œ API๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ์„ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์•ฑ ๊ธฐ๋™์— ์„ฑ๊ณตํ•˜๋ฉด Console View์—์„œ ์•„๋ž˜ ๋กœ๊ทธ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

INFO 69998 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
INFO 69998 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
INFO 69998 --- [main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.37]
INFO 69998 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
INFO 69998 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1185 ms
INFO 69998 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
INFO 69998 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
INFO 69998 --- [main] c.e.demo.ContactManagerApplication       : Started ContactManagerApplication in 2.457 seconds (JVM running for 3.521)

 

์ด์ œ ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ์—ด๊ณ  http://localhost:8080/contact-info/get ์„ ์ฃผ์†Œ์ฐฝ์— ์ž…๋ ฅํ•˜๊ณ  ํ˜ธ์ถœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์ฒ˜๋Ÿผ ์ถœ๋ ฅ์ด ๋˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ž˜ ๋™์ž‘ํ•œ ๊ฒ๋‹ˆ๋‹ค.

{"code":204,"message":"๋ฐ์ดํ„ฐ ์—†์Œ"}

 

์—ฌ๊ธฐ์„œ ํ˜น์‹œ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค๋ฉด ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€์„œ ๋‹ค์‹œ ์ˆœ์„œ๋Œ€๋กœ ๋”ฐ๋ผํ•ด๋ณด์„ธ์š”.

๊ทธ๋ž˜๋„ ์•ˆ๋˜์‹ ๋‹ค๋ฉด....๋Œ“๊ธ€๋กœ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ํ•จ๊ป˜ ๊ณ ๋ฏผํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์ž, ์ด์ œ ํ”„๋ก ํŠธ ๊ฐœ๋ฐœ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

 

ํ”„๋ก ํŠธ ๊ฐœ๋ฐœ

ํ”„๋ก ํŠธ ์˜์—ญ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด์„œ pom.xml์— ๋‘ ๊ฐœ์˜ dependency๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>bootstrap</artifactId>
	<version>4.5.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

thymeleaf์™€ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ž…๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  controller ํŒจํ‚ค์ง€ ๋‚ด์— ViewController.java ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์งง์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ViewController {

    @GetMapping("/contact-info")
    public String contactInfo() {
        return "contact-info";	// contact-info.html์„ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• 
    }

}

 

๊ทธ๋ฆฌ๊ณ  src/main/resources ํ•˜์œ„์˜ templates ๋””๋ ‰ํ† ๋ฆฌ์— contact-info.html ํŒŒ์ผ์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ณ  ์•„๋ž˜ ๋‚ด์šฉ์„ ๋„ฃ์–ด์ฃผ์„ธ์š”.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">

    <!-- JS link -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="/webjars/bootstrap/4.5.0/js/bootstrap.min.js"></script>
    <script src="script/contact-info.js"></script>

    <!-- CSS link -->
    <link rel="stylesheet"
          href="/webjars/bootstrap/4.5.0/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="style/contact-info.css"/>

</head>
<body>

<div id="content">
    <h1>์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ</h1>
    <p>
        <label>์ด๋ฆ„:</label>
        <input id="name" type="text" placeholder="์ด๋ฆ„" pattern=""/>
        
        <label>์—ฐ๋ฝ์ฒ˜:</label>
        <input id="phone" type="text" placeholder="010-1234-5678"/>
        <button type="button" class="btn btn-primary btn-lg" onclick="addContact()">๋“ฑ๋ก</button>
        <button type="button" class="btn btn-danger btn-lg" onclick="delContact()">์‚ญ์ œ</button>
    </p>

    <div>
        <button type="button" class="btn btn-info btn-lg" onclick="getContacts()">์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ์กฐํšŒ</button>
        <table>
            <thead>
	            <tr>
	                <th class="name">Name</th>
	                <th class="phone">Phone</th>
	            </tr>
            </thead>
            <tbody id="contact-info-table"></tbody>
        </table>
    </div>
</div>


<!-- Update Modal -->
<div class="modal fade" id="updateModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel"
     aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="updateModalLabel">์—ฐ๋ฝ์ฒ˜ ์ˆ˜์ •</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary" onclick="updateContactInfo()">Save changes</button>
            </div>
        </div>
    </div>
</div>

</body>
</html>

 

๊ทธ๋ฆฌ๊ณ  src/main/resources ํ•˜์œ„์˜ static ๋””๋ ‰ํ† ๋ฆฌ ํ•˜์œ„์— style, script ๋‘ ๊ฐœ์˜ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

style ๋””๋ ‰ํ† ๋ฆฌ์—๋Š” contact-info.css ํŒŒ์ผ์„ ๋„ฃ์„๊ฑฐ๊ณ , script ๋””๋ ‰ํ† ๋ฆฌ์—๋Š” contact-info.js ํŒŒ์ผ์„ ๋„ฃ์„ ๊ฒ๋‹ˆ๋‹ค.

๊ฐ ํŒŒ์ผ์˜ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

src/main/resources/static/style/contact-info.css

body {background-color: beige;}
th {text-align:center}
.name {width:100px;}
.phone {width:200px;}
#content {margin:100px;}
#contact-info-table tr:hover {background-color: cornflowerblue;}

 

src/main/resources/static/script/contact-info.js

$( document ).ready(function() {
    getContacts();
});

function addContact() {
    let name = $('#name').val();
    if (!name) {
        alert('"name" is required!');
        return;
    }
    if (!validateName(name)) {
        alert('"name" is invalid! ํ•œ๊ธ€๋งŒ ์ž…๋ ฅ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
        return;
    }
    let phone = $('#phone').val();
    if (!validatePhone(phone)) {
        alert('"phone number" is invalid!\nvalid format: 000-0000-0000');
        return;
    }

    let contactInfo = new Object();
    contactInfo.name = name;
    contactInfo.phone = phone;

    $.ajax({
        url: 'contact-info/add',
        dataType: 'json',
        type: 'post',
        contentType: 'application/json',
        data: JSON.stringify(contactInfo)
    }).done(function(response) {
        alert(response.message);
        console.log(response);
        $('#name').val('');
        $('#phone').val('');
        getContacts();
    }).fail(function(jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
        console.log(jqXHR.responseText);
    });
}

function validateName(name) {
    return /^[๊ฐ€-ํžฃ]+$/.test(name);
// /^[A-Za-z]+$/
}
function validatePhone(phone){
    return /^\d{2,3}-\d{3,4}-\d{4}$/.test(phone);
}

function delContact() {

    let name = $('#name').val();
    if (!name) {
        alert('"name" is required!');
        return;
    }
    $.ajax({
        url: 'contact-info/del?name=' + name,
        dataType: 'json',
        type: 'delete',
        contentType: 'application/json'
    }).done(function(response) {
        alert(response.message);
        console.log(response);
        $('#name').val('');
        $('#phone').val('');
        getContacts();
    }).fail(function(jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
        console.log(jqXHR.responseText);
    });
}

function getContacts() {
    let name = $('#name').val();
    let phone = $('#phone').val();

    $.ajax({
        url: 'contact-info/get' + generateQueryParams(name,phone),
        dataType: 'json',
        type: 'get',
        contentType: 'application/json'
    }).done(function(response) {
        printContactInfos(response.data);
    }).fail(function(jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
        console.log(jqXHR.responseText);
    });
}

function generateQueryParams(name, phone) {
    let params = '';
    if (name || phone) {
        params = '?';
        if (name) {
            params += 'name=' + name;
        }
        if (phone) {
           if (params.length > 1) { params += '&';}
            params += 'phone=' + phone;
        }
    }
    return params;
}

function printContactInfos(contactInfos) {
    let rows = '';
    if (contactInfos) {
        contactInfos.forEach(function (item, index) {
            rows += '<tr onclick="popsUpUpdateModal(this)" ><td class="name">' + item.name
            + '</td><td class="phone">' + item.phone + '</td></tr>';
        });
    } else {
        alert('์กฐํšŒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.');
    }
    $('#contact-info-table').html(rows);
}
function popsUpUpdateModal(row) {
    let name = $(row).find('.name').text();
    let phone = $(row).find('.phone').text();
    let modalBody = '<input type="text" class="name" value="' + name + '" disabled>'
        + '<input type="text" class="phone" value="' + phone + '">';
    $('#updateModal .modal-body').html(modalBody);
    $('#updateModal').modal('show');
}
function updateContactInfo() {
    let name = $('#updateModal .modal-body .name').val();
    let phone = $('#updateModal .modal-body .phone').val();

    if (!validateName(name)) {
        alert('"name" is invalid! ํ•œ๊ธ€๋งŒ ์ž…๋ ฅ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
        return;
    }
    if (!validatePhone(phone)) {
        alert('"phone number" is invalid!\nvalid format: 000-0000-0000');
        return;
    }

    $.ajax({
        url: 'contact-info/update' + generateQueryParams(name,phone),
        dataType: 'json',
        type: 'put',
        contentType: 'application/json'
    }).done(function(response) {
        alert(response.message);
        console.log(response);
        getContacts();
        $('#updateModal').modal('hide');
    }).fail(function(jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
        console.log(jqXHR.responseText);
    });
}

 

๊ฐ ํŒŒ์ผ๋“ค ๋‚ด์—์„œ ํ•˜๋Š” ์—ญํ• ์ด ๋ญ”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค๋Š” ๋ถ„๋“ค์€ ๋Œ“๊ธ€ ๋‹ฌ์•„์ฃผ์‹œ๋ฉด ์•„๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ ๋‹ฌ์•„๋“œ๋ฆด๊ฒŒ์š”. 

ํฌ์ŠคํŒ…์ด ๋„ˆ๋ฌด ๊ธธ์–ด์ ธ์„œ ์ผ์ผ์ด ์„ค๋ช…์„ ๋‹ค ์ถ”๊ฐ€ํ•  ์ˆ˜๊ฐ€ ์—†์Œ์„ ์–‘ํ•ด๋ฐ”๋ž๋‹ˆ๋‹ค.

 

์ž, ์ด๋ ‡๊ฒŒ ์ด 4 ๊ฐœ์˜ ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ํ”„๋ก ํŠธ ๊ฐœ๋ฐœ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ๋‹ค์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ๊ธฐ๋™ํ•œ ๋’ค ํ™”๋ฉด์œผ๋กœ ์ ‘์†ํ•ด์„œ ๊ธฐ๋Šฅ๋“ค์„ ์‚ฌ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” localhost:8080/contact-info ์ฃผ์†Œ๋กœ ์ ‘์†์„ ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์•ž์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ /get ์ฃผ์†Œ๋Š” API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด๋‹ˆ ๋นผ๊ณ  ์ ‘์†ํ•ด์ฃผ์‹œ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ํ™”๋ฉด์ด ๋œฐ๊ฒ๋‹ˆ๋‹ค.

์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ ์›น์•ฑ ์™„์„ฑ

์ด ์›น์•ฑ์—์„œ๋Š” ๋“ฑ๋ก์‹œ์— ์ด๋ฆ„์„ ์ •์ƒ์ ์ธ ํ•œ๊ธ€์„ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•˜๊ณ  ์—ฐ๋ฝ์ฒ˜์˜ ๊ฒฝ์šฐ -๋ฅผ ํฌํ•จํ•˜์—ฌ ์ž๋ฆฌ์ˆ˜๋ฅผ ๊ฒ€์ฆํ•˜๋„๋ก ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒ€์ฆ์€ contact-info.js์—์„œ validateName(), validatePhone() ํ•จ์ˆ˜์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋‹ˆ ํ™•์ธํ•ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 

์˜ค๋ฅ˜๋ฐœ์ƒ์‹œ์—๋Š” ํ™”๋ฉด์— ์˜ค๋ฅ˜๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ๊ฒฝ๊ณ ํŒ์—…์ฐฝ์ด ๋œจ๋„๋ก ๋˜์–ด์žˆ์œผ๋ฉฐ, ์„œ๋ฒ„๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ์—ฐ๋ฝ์ฒ˜ ์ˆ˜์ •์€ ์ œ์ผ ์ฒ˜์Œ์— ๊ณต์œ ํ•ด๋“œ๋ฆฐ ๋™์˜์ƒ์„ ๋”ฐ๋ผํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€ 30๋ถ„๋งŒ์— ์—ฐ๋ฝ์ฒ˜ ๊ด€๋ฆฌ ์›น์•ฑ ๋งŒ๋“ค๊ธฐ ํฌ์ŠคํŒ…์„ ๋งˆ์น˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๊ถ๊ธˆํ•˜์‹  ์‚ฌํ•ญ์ด๋‚˜ ์ž˜ ์•ˆ๋˜์‹œ๋Š” ๋ถ„๋“ค์€ ๋Œ“๊ธ€ ๋‹ฌ์•„์ฃผ์‹œ๋ฉด ํ™•์ธํ•ด์„œ ๋‹ต๋ณ€๋“œ๋ฆฌ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์˜ค๋Š˜๋„ ์ฆํ”„ํ•˜์„ธ์š”~