Domain

Todo.java

package com.yedam.java.domain;

import lombok.Data;

@Data
public class Todo {
	private int no; // 할 일 목록 고유번호
	private int id; // 작성자에 따른 id : default 0으로 둘 것임
	private String contents; // 할 일 내용
	private String todoyn; // 체크 여부
}

todoList 에 필요한 필드 선언

lombok 라이브러리의 @Data 어노테이션을 사용

/**
 * Generates getters for all fields, a useful toString method, and hashCode and equals implementations that check
 * all non-transient fields. Will also generate setters for all non-final fields, as well as a constructor.
 * <p>
 * Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}.
 * <p>
 * Complete documentation is found at <a href="https://projectlombok.org/features/Data">the project lombok features page for &#64;Data</a>.
 * 
 * @see Getter
 * @see Setter
 * @see RequiredArgsConstructor
 * @see ToString
 * @see EqualsAndHashCode
 * @see lombok.Value
 */

@Data 어노테이션을 보면 

@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode 의 어노테이션과 동등한 기능을 한다고 한다.

 

Mapper

TodoMapper.java

package com.yedam.java.mapper;

import java.util.List;

import com.yedam.java.domain.Todo;

public interface TodoMapper {
	// 전체 조회
	public List<Todo> selectAll();
	// 등록
	public void insert(Todo todo);
	// 삭제
	public void delete(int no);
	// 수정
	public void update(Todo todo);
	// 고유번호 조회
	public Integer getTodoNo();
}

mapper interface 로 정의하고 구현체를 따로 둔다.

 

TodoMapper.xml

<?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.yedam.java.mapper.TodoMapper">

	<!-- 전체 조회 -->
	<select id="selectAll" resultType="Todo">
		select * from todo order by 1 desc
	</select>
	
	<!-- 고유 번호 조회 -->
	<select id="getTodoNo" resultType="Integer">
		select nvl(max(no), 0) + 1 as no from todo
	</select>
	
	<!-- 등록 -->
	<insert id="insert" parameterType="Todo">
		insert into todo values (#{no}, #{id}, #{contents}, #{todoyn})
	</insert>
	
	<!-- 삭제 -->
	<delete id="delete" parameterType="Todo">
		delete from todo where no = #{no}
	</delete>
	
	<!-- 수정 -->
	<update id="update" parameterType="Todo">
		update todo set todoyn = #{todoyn} where no = #{no}
	</update>
</mapper>

TodoMapper 의 구현체

MyBatis 를 사용하여 실제 쿼리문을 작성

DAO (Data Access Object) 역할을 해준다.

 

select nvl(max(no), 0) + 1 as no from todo

위 쿼리문은 primary key 인 todo.no 의 시퀀스 역할을 한다.

등록 기능 발생 시 위 쿼리문으로 얻은 no 를 todo 에 세팅

 

Service

TodoServcie.java

package com.yedam.java.service;

import java.util.List;

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

import com.yedam.java.domain.Todo;
import com.yedam.java.mapper.TodoMapper;

@Service
public class TodoService {
	
	@Autowired TodoMapper mapper; // @Autowired : 컨테이너 안에 존재하는 빈을 자동으로 주입
	
	// 전체조회
	public List<Todo> selectAll() {
		return mapper.selectAll();
	}
	
	// 고유번호조회
	public Integer getTodoNo() {
		return mapper.getTodoNo();
	}
	
	// 등록
	public void insertItem(Todo todo) {
		mapper.insert(todo);
	}
	
	// 삭제
	public void deleteItem(int no) {
		mapper.delete(no);
	}
	
	// 수정
	public void updateItem(Todo todo) {
		mapper.update(todo);
	}
}

실제 사용할 서비스 로직이 담긴 클래스

 

Controller

HomeController

package com.yedam.java.controller;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.yedam.java.domain.Todo;
import com.yedam.java.service.TodoService;

/**
 * Handles requests for the application home page.
 */

@RestController // method의 반환 결과를 JSON 형태로 반환
// cors 오류 방지 : 해당 주소로 들어오는 요청은 받아주겠다는 뜻
@CrossOrigin(originPatterns = "http://127.0.0.1:5500/") 
public class HomeController {
	
	@Autowired TodoService todoService;
	
	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@GetMapping("/todo")
	public List<Todo> home(Model model) {
		List<Todo> list = todoService.selectAll();
		System.out.println("list = " + list);
		model.addAttribute("list", list);
		
		return list;
	}
	
	@PostMapping("/todo")
	public void insertItem(Todo todo) {
		todo.setNo(todoService.getTodoNo());
		todo.setId(0);
		todo.setTodoyn("0");
		
		System.out.println("todo = " + todo);
		todoService.insertItem(todo);
	}
	
	@DeleteMapping("/todo/{no}")
	public void deleteItem(@PathVariable int no) {
		todoService.deleteItem(no);
	}
	
	@PutMapping("/todo")
	// 클라이언트에서 서버로 필요한 데이터를 요청하기 위해 JSON 데이터를 요청 본문에 담아서 서버로 보내면, 
	// 서버에서는 @RequestBody 어노테이션을 사용하여 
	// HTTP 요청 본문에 담긴 값들을 자바객체로 변환시켜, 객체에 저장
	public void updateItem(@RequestBody Todo todo) {
		System.out.println("update_todo = " + todo);
		todoService.updateItem(todo);
	}
}

rest 형식으로 컨트롤러 구성

*rest 개념 : HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미한다.

 

udpateItem 메서드에서 데이터를 받는 부분에서 헤멨다.

해결책 : 클라이언트에서 보내는 데이터 타입과 서버에서 받을 데이터 타입을 일치시켜줘야 한다.

              서버쪽에서는 @RequestBody 어노테이션을 사용하여 요청받은 데이터를 자바객체로 변환시킨다.

 

View - vue.js 로 구성

home.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- vue -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <!-- router vue -->
  <script src="https://unpkg.com/vue-router@3.5.3/dist/vue-router.js"></script>
  <!-- jquery -->
  <script src="https://code.jquery.com/jquery-3.6.1.js" integrity="sha256-3zlB5s2uwoUzrXK3BT7AX3FyvojsraNFxCc2vC/7pNI="
    crossorigin="anonymous"></script>
  <link rel="stylesheet" href="css/style.css">
  <!-- vue instance -->
  <script type="module" src="app.js"></script>
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div id="app">
	<!-- vue instance -->
  </div>
</body>
</html>

해당 프로젝트에 필요한 CDN 구성 (vue, vue-router, jquery)

<div id="app"></div> 태그에  vue 인스턴스가 들어갈 것임.

 

app.js

import router from "./router/router.js";
import todoHeader from "./component/todoHeader.js";

// 기본 템플릿 구성 - 태그이름은 vue 객체안에서 components 속성으로 정의
// router-view 태그 자리를 바꾸면서 사용이 가능함
let template = `<div>
                  <todo-header></todo-header>
                  <router-view></router-view>
                </div>`;
let app = new Vue({
  // div 태그의 id
  el: "#app",

  template,

  components: {
    // 사용할 태그의 이름 정의
    "todo-header": todoHeader,
  },

  router,
});

Vue 인스턴스 생성

 

router/router.js

import selectItems from "../component/selectItems.js";

export default new VueRouter({
  mode: "hash",

  // path 연결 시 해당 component 를 호출
  routes: [
    {
      path: "/",
      name: "selectItems",
      component: selectItems,
      props: true,
    },
    {
      path: "*",
      redirect: "/",
    },
  ],
});

router 들을 정의

순서가 중요 -> 시퀀셜하게 검색을 하여 아스타(*)는 마지막에 정의한다.

 

css/style.css

/* Include the padding and border in an element's total width and height */
* {
  box-sizing: border-box;
}

/* Remove margins and padding from the list */
ul {
  margin: 0;
  padding: 0;
}

/* Style the list items */
ul li {
  cursor: pointer;
  position: relative;
  padding: 12px 8px 12px 40px;
  background: #eee;
  font-size: 18px;
  transition: 0.2s;

  /* make the list items unselectable */
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* Set all odd list items to a different color (zebra-stripes) */
ul li:nth-child(odd) {
  background: #f9f9f9;
}

/* Darker background-color on hover */
ul li:hover {
  background: #ddd;
}

/* When clicked on, add a background color and strike out text */
ul li.checked {
  background: #888;
  color: #fff;
  text-decoration: line-through;
}

/* Add a "checked" mark when clicked on */
ul li.checked::before {
  content: "";
  position: absolute;
  border-color: #fff;
  border-style: solid;
  border-width: 0 2px 2px 0;
  top: 10px;
  left: 16px;
  transform: rotate(45deg);
  height: 15px;
  width: 7px;
}

/* Style the close button */
.close {
  position: absolute;
  right: 0;
  top: 0;
  padding: 12px 16px 12px 16px;
}

.close:hover {
  background-color: #f44336;
  color: white;
}

/* Style the header */
.header {
  background-color: #f44336;
  padding: 30px 40px;
  color: white;
  text-align: center;
}

/* Clear floats after the header */
.header:after {
  content: "";
  display: table;
  clear: both;
}

/* Style the input */
input {
  margin: 0;
  border: none;
  border-radius: 0;
  width: 75%;
  padding: 10px;
  float: left;
  font-size: 16px;
}

/* Style the "Add" button */
.addBtn {
  padding: 10px;
  width: 25%;
  background: #d9d9d9;
  color: #555;
  float: left;
  text-align: center;
  font-size: 16px;
  cursor: pointer;
  transition: 0.3s;
  border-radius: 0;
}

.addBtn:hover {
  background-color: #bbb;
}

css 와 기본 템플릿은 여기

https://www.w3schools.com/howto/howto_js_todolist.asp

 

How To Create a To Do List

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

component/todoHeader.js

let template = `
<div id="myDIV" class="header">
  <h2>My To Do List</h2>
  <input type="text" v-model="content" placeholder="Title...">
  <span @click="addItem()" class="addBtn">Add</span>
</div>
`;

// vue component 등록
export default {
  template: template,

  data: function () {
    return {
      // v-model 로 연결된 input 태그의 값을 받을 수 있음
      content: "",
    };
  },

  methods: {
    // @ 은 v-on: 과 같음
    // @click="addItem()" - 클릭이벤트시 발생하는 메서드
    addItem: function (e) {
      const component = this;
      console.log(this);

      $.ajax({
        type: "post",

        // 컨트롤러의 insertItem 메서드 url
        url: "http://localhost:8080/java/todo",

        // post 방식은 일반 객체로 전송해도 무관
        data: {
          id: 0,
          contents: component.content,
        },

        // dataType 미정의 - 컨트롤러의 반환이 없기 때문?
        // dataType 을 지정하면 success 가 발생안함

        success: function (data) {
          if (data != null) {
            console.log(data);
            alert("add success");
            // 등록 완료 후 새로고침
            component.$router.go(0);
          }
        },

        error: function (reject) {
          console.log(reject);
        },
      });
    },
  },
};

todoHeader.js 에서는 등록기능 발생

 

component/selectItems.js

// :class -> 삼항연산자를 이용해 css 를 줄지 말지 결정
// @click.stop -> 이벤트 버블링 방지
// 버블링 : span 태그의 이벤트 발생시 span 태그 이벤트 발생 후 li 태그 이벤트도 같이 발생
let template = `
<ul id="myUL">
<template v-for="item in items">
  <li :key="item.no" :class="{ checked : item.todoyn == 1 ? true : false }" @click="checkedItem(item)">
    {{item.contents}}
    <span class="close" @click.stop="deleteItem(item)">X</span>
  </li>
</template>
</ul>
`;

export default {
  template: template,

  data: function () {
    return {
      // selectAll 의 결과를 담을 배열
      items: [],
    };
  },

  // created : Vue 가 생성되기 전 created 메서드가 먼저 실행되어 데이터를 준비
  created: function () {
    const component = this; // 여기서 this 를 쓰면 js 객체를 가리킴
    console.log("created function");
    $.ajax({
      type: "get",

      url: "http://localhost:8080/java/todo",

      success: function (data) {
        if (data != null) {
          console.log(data);
          // 컨트롤러에서 리턴된 데이터를 items 배열에 담음
          component.items = data; // 여기서 this.empList 를 쓰면 this 는 ajax 객체를 가리킴
        }
      },

      error: function (reject) {
        console.log(reject);
      },
    });
  },

  methods: {
    checkedItem: function (item) {
      const component = this;
      // 아이템 클릭 시 체크 여부에 따라 변환
      // 바인딩 됨에 따라 item 의 속성 변화 시 실제 보이는 것도 바로 바뀜
      item.todoyn = item.todoyn == 1 ? 0 : 1;
      item.id = 0;
      console.log(item.todoyn);
      console.log(item);

      $.ajax({
        type: "put",

        url: "http://localhost:8080/java/todo",

        // 일반 타입으로 item 객체를 보내면 자바에서 받을 때 타입이 다른지, 데이터를 받아지지가 않음
        // --> 해결책 :
        // 자바스크립트에서 데이터를 전송할때 json 으로 지정해서 데이터를 전송,
        // 자바에서 객체를 받을 때 @RequestBody 어노테이션을 이용해 http 본문 내용에 있는 객체를 자바 객체로 변환

        // 보낼 데이터 타입 지정
        contentType: "application/json",

        // item 을 json으로 전송
        data: JSON.stringify(item),

        success: function (data) {
          console.log(data);
          alert("update complete");
        },

        error: function (reject) {
          console.log(reject);
        },
      });
    },

    deleteItem: function (item) {
      const component = this;
      $.ajax({
        type: "delete",

        // pathvariable 형태로 데이터 주고받음
        url: "http://localhost:8080/java/todo/" + item.no,

        success: function (data) {
          if (data != null) {
            console.log(data);
            alert("delete complete");
            console.log(component.items.length);

            // 페이지상에서 삭제 - 바인딩 되있기에 가능
            for (let i = 0; i < component.items.length; i++) {
              if (
                component.items[i].id == item.id &&
                component.items[i].no == item.no
              ) {
                component.items.splice(i, 1);
              }
            }
          }
        },

        error: function (reject) {
          console.log(reject);
        },
      });
    },
  },
};

전체조회, 수정(체크여부 수정), 삭제 기능 수행

 

Post 방식이나 Get 방식은 일반 객체로 데이터를 전송해도 받을 수 있으나,

Put 방식은 Json 객체로 데이터를 전송하고 받을 수 있다. 클라이언트와 서버 쪽 둘다 Json 형식으로 맞춰줘야 한다는 것이다.

 

 실행 화면

 

 

 

- Just Do It -

 

반응형
복사했습니다!