Java SpringBoot 입문 Part.02

SpringBoot FrameWork 사용을 위한 기초

Featured image

Chapter.01-1

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

com.example.app.domain.Customer.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity	// JPA 개체(Entity)임을 표시 합니다.

// @Table 어노테이션을 붙여 엔티티에 대응하는 테이블 이름을 지정합니다.
// 기본적으로 테이블 이름을 클래스 이름과 동일하게 맞추는게 컨벤션이지만 
// 여기선 다르게 설정 합니다.
@Table(name = "customers")

// class파일을 생성할때 각 필드의 setter/getter, toString, equals, hashCode 메소드가
// 생성되므로 소스코드가 간결해진다. 아래 필드는 final형태라 setter/getter가 생성되지 않는다.
@Data

// JPA 명세에 따르면 엔티티 클래스에는 인자를 받지 않는 기본 생성자를 만들어야 합니다.
// 롬복으로 기본 생성자를 만들려면 아래 어노테이션을 추가 합니다.
@NoArgsConstructor

// JPA와는 관계 없지만 쉽게 프로그래밍을 할 수 있게 롬복이 기본 생성자 외에 전체 필드를
// 인자로 받는 생성자를 만들도록 설정 합니다.
@AllArgsConstructor
public class Customer {
	@Id // 엔티티의 기본 키인 필드에 @Id 어노테이션을 붙입니다.
	@GeneratedValue // DB가 기본 키 번호를 자동으로 매기도록 이 어노테이션을 붙여 지정합니다.
	private Integer id;
	
	// 필드에 이 어노테이션을 붙여서 DB의 대응하는 컬럼의 이름이나 제약 조건을 설정합니다.
	// 여기서는 NotNull 제약조건을 설정 합니다.
	@Column(nullable = false)
	private String firstName;
	@Column(nullable = false)
	private String lastName;
}

Chapter.01-2 SpringData JAP로 리포지토리 클래스 작성하기

com.example.app.reposioCustomerRepository Class

public interface CustomerRepository extends JpaRepository<Customer, Integer> {

	/*
	 *  JpaRepository 클래스 에는 다음과 같은 CRUD(Create, Read, Update, Delete)
	 *  기본 조작용 메소드가 정의되ㅣ어 있으며, JpaRepository를 상속한 인터페이스를 만드는 작업만으로 아주 쉽게
	 *  리포지토리를 작성할 수 있습니다. 
	 *  
	 *  findOne, save, findAll, delete
	 */
}
// App 클래스
@SpringBootApplication
public class App implements CommandLineRunner{
	
	@Autowired
	CustomerRepository customerRepository;
	
	@Override
	public void run(String... args) throws Exception {
		// 더미 데이터 추가
		Customer created = customerRepository.save(new Customer(null, "Hidetoshi", "Dekisugi"));
		System.out.println(created + " is created!");
		
		// 데이터 표시
		customerRepository.findAll().forEach(System.out::println);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Chapter.01-3 JPQL로 쿼리 정의하기

// CustomerRepository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {

	/*
	 *  JpaRepository 클래스 에는 다음과 같은 CRUD(Create, Read, Update, Delete)
	 *  기본 조작용 메소드가 정의되ㅣ어 있으며, JpaRepository를 상속한 인터페이스를 만드는 작업만으로 아주 쉽게
	 *  리포지토리를 작성할 수 있습니다. 
	 *  
	 *  findOne, save, findAll, delete
	 */
	
	@Query("SELECT x FROM Customer x ORDER BY x.firstName, x.lastName")
	List<Customer> findAllOrderByName(); // JPQL을 기술할 떄 @query 어노테이션을 붙힌다.
}

Chapter.01-4 페이징 처리 구현하기

//CustomerRepository
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.example.app.domain.Customer;

public interface CustomerRepository extends JpaRepository<Customer, Integer> {

	/*
	 *  JpaRepository 클래스 에는 다음과 같은 CRUD(Create, Read, Update, Delete)
	 *  기본 조작용 메소드가 정의되ㅣ어 있으며, JpaRepository를 상속한 인터페이스를 만드는 작업만으로 아주 쉽게
	 *  리포지토리를 작성할 수 있습니다. 
	 *  
	 *  findOne, save, findAll, delete
	 */
	
	@Query("SELECT x FROM Customer x ORDER BY x.firstName, x.lastName")
	Page<Customer> findAllOrderByName(Pageable pageable); // JPQL을 기술할 떄 @query 어노테이션을 붙힌다.
}
//App
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import com.example.app.domain.Customer;
import com.example.app.repository.CustomerRepository;

@SpringBootApplication
public class App implements CommandLineRunner{
	
	@Autowired
	CustomerRepository customerRepository;
	
	@Override
	public void run(String... args) throws Exception {
		// 더미 데이터 추가
		int idx = 0;
		while (idx < 10) {
			customerRepository.save(new Customer(null, "Hidetoshi", idx));
			idx++;
		}
			
		// 데이터 표시
		// customerRepository.findAllOrderByName().forEach(System.out::println);/
		
		// 페이징 처리
		// 첫번째인자 : 페이지수(0부터 시작), 페이지당 들어갈 데이터 수
		Pageable pageable = PageRequest.of(0, 5);
		// findAll 메소드를 실행하여 지정한 페이지의 domain 데이터를 가져옵니다. 반환값은 Page<customer>
		Page<Customer> page = customerRepository.findAllOrderByName(pageable);
		// 페이지 처리후 정보 출력
		System.out.println("한 페이지당 데이터 수 = " + page.getSize());
		System.out.println("현재 페이지 = " + page.getNumber());
		System.out.println("전체 페이지 수 = " + page.getTotalPages());
		System.out.println("전체 데이터 수 = " + page.getTotalElements());
		// getContent() 로 해당 페이지의 데이터 리스트를 가져올 수 있음.
		page.getContent().forEach(System.out::println);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Chapter.02-1 REST Web Service 만들기

CUstomerService의 CURD처리 구현하기

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.example.app.domain.Customer;
import com.example.app.repository.CustomerRepository;

//이러한 클래스를 DI컨테이너 에서 가져오고 해당 클래스에 속한 각 메소드를 다른 클래스에서 호출 하면 DB 트랜잭션이 자동으로 이루어 집니다.
//메서드가 제대로 실행되면 DB 트랜잭션이 커밋됩니다.
//실행 도중 오류가 발생하면 DB 트랜잭션이 롤백 됩니다.
//DI 컨테이너는 각 메소드 앞뒤에 처리를 추가한 클래스를 동적으로 생성합니다.
@Transactional
public class CustomerService {
	@Autowired
	CustomerRepository customerRepository;
	
	public List<Customer> findAll() {
		return customerRepository.findAllOrderByName();
	}
	
	public Customer findOne(Integer id) {
		return customerRepository.getOne(id);
	}
	
	public Customer create(Customer customer) {
		return customerRepository.save(customer);
	}
	
	public Customer update(Customer customer) {
		return customerRepository.save(customer);
	}
	
	public void delete(Integer id) {
		customerRepository.deleteById(id);
	}
}
API 이름 HTTP 메서드 리소스 경로 정상 동작 시 응답 상태
모든 고객 정보 얻기 GET /api/customers 200 OK
고객 한 명의 정보 얻기 GET /api/customers/{id} 200 OK
신규 고객 정보 작성 POST /api/customers 201 CREATED
고객 한 명의 정보 업데이트 PUT /api/customers/{id} 200 OK
고객 한 명의 정보 삭제 DELETE /api/customers/{id} 204 NO CONTENT

Chapter.02-2 모든 고객 정보 얻기, 고객 한 명의 정보 얻기용 API 구현

API 이름 메소드 이름 반환 값의 타입
모든 고객 정보 얻기 getCustomers List
고객 한 명의 정보 얻기 getCustomer Customer
신규 고객 정보 작성 postCustomers Customer
고객 한 명의 정보 업데이트 putCustomer Customer
고객 한 명의 정보 삭제 deleteCustomer void

CustomerRestController 구현 부분

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.app.domain.Customer;
import com.example.app.service.CustomerService;

// REST 웹 서비스의 엔드 포인트인 컨트롤러 클래스에는 이 어노테이션을 붙입니다.
@RestController
// 이 REST 웹 서비스에 접속하기 위한 경로를 @RequestMapping 어노테이션에 지정합니다.
@RequestMapping("api/customers")
public class CustomerRestController {
	@Autowired // 이미 작성된 CustomerService 클래스를 주입합니다.
	CustomerService customerService;
	
	// 이 메소드에 HTTP메소드 중 하나인 GET을 할당 합니다.
	// @RequestMapping 에서 지정한 경로에 접근 하면 getCustomers()가 실행 됩니다.
	@RequestMapping(method = RequestMethod.GET)
	List<Customer> getCustomers() {
		List<Customer> customers = customerService.findAll();
		// 위 Mapping 어노테이션 붙인 메소드의 반환 값은 직렬화 되어 HTTP 응답 내용 안에 설정 됩니다.
		// 기본으로 자바 객체는 JSON 형식으로 직렬화
		return customers;
	}
	
	// 아래 메소드 에도 GET할당 
	// 주소값 지정을 플레이스홀더로 id값을 정해 주었기 떄문에
	// api/customers/id 값 으로 접근이 가능함
	@RequestMapping(value = "{id}", method = RequestMethod.GET)
	Customer getCustomer(@PathVariable Integer id) {
		Customer customer = customerService.findOne(id);
		return customer;
	}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.example.app.domain.Customer;
import com.example.app.service.CustomerService;

// REST 웹 서비스의 엔드 포인트인 컨트롤러 클래스에는 이 어노테이션을 붙입니다.
@RestController
// 이 REST 웹 서비스에 접속하기 위한 경로를 @RequestMapping 어노테이션에 지정합니다.
@RequestMapping("api/customers")
public class CustomerRestController {
	@Autowired // 이미 작성된 CustomerService 클래스를 주입합니다.
	CustomerService customerService;
	
	// 이 메소드에 HTTP메소드 중 하나인 GET을 할당 합니다.
	// @RequestMapping 에서 지정한 경로에 접근 하면 getCustomers()가 실행 됩니다.
	@RequestMapping(method = RequestMethod.GET)
	List<Customer> getCustomers() {
		List<Customer> customers = customerService.findAll();
		// 위 Mapping 어노테이션 붙인 메소드의 반환 값은 직렬화 되어 HTTP 응답 내용 안에 설정 됩니다.
		// 기본으로 자바 객체는 JSON 형식으로 직렬화
		return customers;
	}
	
	// 아래 메소드 에도 GET할당 
	// 주소값 지정을 플레이스홀더로 id값을 정해 주었기 떄문에
	// api/customers/id 값 으로 접근이 가능함
	@RequestMapping(value = "{id}", method = RequestMethod.GET)
	Customer getCustomer(@PathVariable Integer id) {
		Customer customer = customerService.findOne(id);
		return customer;
	}
	
	// 신규 고객 정보 작성
	// 아래 메서드에 POST 할당
	@RequestMapping(method = RequestMethod.POST)
	// API가 정상동작 했을떄 응답 지정
	// CREATE동작은 201반환 그렇지 않으면 200 OK 반환
	@ResponseStatus(HttpStatus.CREATED)
	// HTTP요청을 Customer 객체에 매핑하기 위해 @RequestBody 어노테이현을 설정합니다.
	Customer postCustomers(@RequestBody Customer customer) {
		return customerService.create(customer);
	}
	
	// 고객 한명 정보 업데이트
	// 아래 어노테이션으로 Http 메소드 중 PUT 할당
	@RequestMapping(value = "{id}", method = RequestMethod.PUT)
	Customer putCustomer(@PathVariable Integer id, @RequestBody Customer customer) {
		customer.setId(id);
		return customerService.update(customer);
	}
	
	// 고객 한 명의 정보 삭제
	// DELETE Http 메소드 할당
	@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
	@ResponseStatus(HttpStatus.NO_CONTENT) // 정상동작 시 204 NO_CONTENT 반환
	void deleteCustomer(@PathVariable Integer id) {
		customerService.delete(id);
	}
}

curl http://localhost:8080/api/customers -v POST -H “Content-Type: application/json” -d “{"firstName" : 999, "lastName" : "Nobi"}”

curl http://localhost:8080/api/customers/11 -v -X PUT -H “Content-Type: application/json” -d “{"firstName" : 333, "lastName" : "Nobi"}”

// Controller 클래스 POST 부분
	@RequestMapping(method = RequestMethod.POST)
	ResponseEntity<Customer> postCustomers(@RequestBody Customer customer,
			// 컨텍스트 상대경로 URI를 쉽게 만들게 해주는 UriComponentsBuilder를
			// 컨트롤러 메소드의 인자로 지정
			UriComponentsBuilder uriBuilder) {
		
		Customer created = customerService.create(customer);
		
		// UriComponentsBuilder와 Customer 객체의 id로 리소스 URI를 만듭니다.
		// path() 메소드에 있는 {id}플레이스홀더며. buildAndExpand() 메소드에 넘겨준 값으로 치환 됩니다.
		URI location = uriBuilder.path("api/customers/{id}")
				.buildAndExpand(created.getId()).toUri();
		
		HttpHeaders headers = new HttpHeaders();
		// HttpHeaders객체로 HTTP응답 헤더를 만들고 location을 인자값으로 줌
		headers.setLocation(location);

		// HTTP응답 헤더를 설정하려면 메소드에서 Customer 객체가 아닌 밑의 자료형으로 반환 해야 합니다.
		//아래 처럼 각각 customer객체, 응답헤더인 headers객체, 상태 코드인 HttpStatus를 설정 합니다.
		return new ResponseEntity<Customer>(created, headers, HttpStatus.CREATED);
	}

Chapter.02-3 페이징 처리 구현

// CustomerRestController 클래스
@RequestMapping(method = RequestMethod.GET)
Page<Customer> getCustomers(@PageableDefault Pageable pageable) {
	Page<Customer> customers = customerService.findAll(pageable);
	return customers;
}

// CustomerService 클래스
public Page<Customer> findAll(Pageable pageable) {
	return customerRepository.findAllOrderByName(pageable);
}

// CustomerRepository 클래스
@Query("SELECT x FROM Customer x ORDER BY x.firstName, x.lastName")
Page<Customer> findAllOrderByName(Pageable pageable);

전체 명령
curl -X GET http://localhost:8080/api/customers
pageable값 넘겨주기
curl -X GET http://localhost:8080/api/customers?page=1&size=3”


Chapter.02-4 Thymeleaf를 사용해 화면에 표시하는 웹 어플리케이션 개발

처리 이름 HTTP 메소드 리소스 경로 화면 이름
고객 정보 목록 표시 처리 GET /customers customers/list
신규 고객 정보 작성 처리 POST /customers/create 고객 정보 목록 표시 처리로 넘어감
고객 정보 편집 폼 표시 처리 GET /customers/edit?form&id={id} customers/edit
고객 정보 편집 처리 POST /customers/edit&id={id} 고객 정보 목록 표시 처리로 넘어감
고객 정보 삭제 처리 POST /customers/delete?id={id} 고객 정보 목록 표시 처리로 넘어감
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

customerService


Chapter.02-5 화면에 고객 정보 목록 표시하기

// CustomerController.java
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.domain.Customer;
import com.example.service.CustomerService;

// REST API와는 달리 화면 변경용 컨트롤러에는 이렇게 붙입니다.
@Controller
// URL이 /customers를 포함할 떄 list() 메소드에 매핑하도록 이렇게 붙입니다.
@RequestMapping("customers") 
public class CustomerController {
	@Autowired
	CustomerService customerService;
	
	// 스프링 MVC에서는 화면에 값을 넘겨주는 데 Model 객체를 사용 합니다.
	// 인자로 Model을 받아들이고 Model.addAttribute 메소드를 사용하여 화면에 넘겨줄 속성을 설정 합니다.
	String list(Model model) {
		List<Customer> customers = customerService.findAllList();
		// findAll의 결과를 model에 설정 합니다.
		// 속성이름은 customers로 지정합니다.
		// 사용자는 이 customers를 사용하여 접속할 수 있습니다.
		model.addAttribute("customers", customers);
		// 컨트롤러에서 @Controller가 붙은 요청 처리 메소드는 뷰 이름, 즉 변경될 화면 이름을 반환 합니다.
		// 스프링 부트에서는 기본값으로 classpath:templates/+ "뷰이름" + .html이 화면 경로가 됩니다.
		// 이 예제에서는 classpath:templates/customers/list.html을 표시합니다.
		return "customers/list";
	}
}
<!DOCTYPE html>
<!-- Thymeleaf의 th:*** 속성을 사용하기 위한 이름 공간을 지정합니다. -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="EUC-KR">
<title>고객 목록</title>
</head>
<body>
	<table class="table table-striped table-bordered table-condensed">
		<!-- th:each 속성을 사용해서 List<Customer>의 내용을 한 개씩 꺼냅니다 -->
		<!-- th:each="(루프 구간 안에서 사용하는 속성 이름) : ${(루프 대상 객체의 속성 이름)}"을 입력합니다. -->
		<tr th:each="customer : ${customers}">
			<!-- th:text 속성 값을 사용하여 HTML 태그 안에 포함된 문자를 치환할 수 있습니다. -->
			<!-- 서버 쪽에서 렌더링 할때 customer.id 값으로 치환 됩니다. -->
			<!-- 치환할 값은 기본적으로 HTML 이스케이프 크로스 사이트 스크립팅(XSS) 공격을 방지할 수 있습니다. -->
			<td th:text="${customer.id}"> 100 </td>
			<td th:text="${customer.lastName}"></td>
			<td th:text="${customer.firstName}"> 길동 </td>
			<td>
				<!-- form 태그의 action 속성 내용을 th:action 속성 값으로 치환할 수 있습니다. -->
				<!-- @(***) 형식으로 지정하여 컨텍스트 경로의 상대 경로를 절대 경로로 치환할 수 있습니다. -->
				<form th:action="@{/customers/edit}" method="get">
					<input type = "submit" name = "form" value="편집" />
					<!-- input 태그의 value 속성 내용을 th:value 속성 값으로 치환할 수 있습니다. -->
					<input type = "hidden" name = "id" th:value="${customer.id}" />
				</form>
			</td>
			<td>
				<form th:action="@{/customers/delete}" method="post">
					<input type="submit" value="삭제" />
					<input type="hidden" name="id" th:value="${customer.id}" />
				</form>
			</td>
		</tr>
	</table>
</body>
</html>

Chapter.02-6 신규 고객 정보 작성하기

// CustomerController class
이전 소스 생략...

@RequestMapping(value = "create", method = RequestMethod.POST)
String create(@Validated CustomerForm form, BindingResult result, Model model) {
	if(result.hasErrors()) {
		return list(model);
	}
	
	Customer customer = new Customer();
	BeanUtils.copyProperties(form, customer);
	customerService.create(customer);
	return "redirect:/customers";
}

// CustomerForm class
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class CustomerForm {
	@NotNull
	@Size(min = 1, max = 127)
	private String firstName;
	
	@NotNull
	@Size(min = 1, max = 127)
	private String lastName;
}

// 한글꺠짐 방지를 위한 AppConfig class
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.CharacterEncodingFilter;

@Configuration
public class AppConfig {
	@Order(Ordered.HIGHEST_PRECEDENCE)
	@Bean
	CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new CharacterEncodingFilter();
		filter.setEncoding("UTF-8");
		filter.setForceEncoding(true);
		return filter;
	}
}

HTML 파일

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8">
<title>고객 목록</title>

<link rel="stylesheet" type="text/css" href ="../../static/css/style.css" th:href="@{/css/style.css}">
</head>
<body>
	<div>
		<form th:action="@{/customers/create}" th:object="${customerForm}" method="post">
		<dl>
			<dt> <label for="lastName"></label></dt>
			<dd>
				<input type="text" id="lastName" name="lastName" th:field="*{lastName}" th:errorclass="error-input" value="홍" />
				<span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" class="error-messages">error!</span>
			</dd>
			
			<dt><label for="firstName"> 이름 </label></dt>
			<dd>
			    <input type="text" id="firstName" name="firstName" th:field="*{firstName}" th:errorclass="error-input" value="길동" />
				<span th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" class="error-messages">error!</span>
			</dd>
		</dl>
			<input type="submit" value="작성" />
		</form>
	</div>
	<hr/>
	<table>
		<tr th:each="customer : ${customers}">
			<td th:text="${customer.id}"> 100 </td>
			<td th:text="${customer.lastName}"></td>
			<td th:text="${customer.firstName}"> 길동 </td>
			<td>
				<form th:action="@{/customers/edit}" method="get">
					<input type="submit" name="form" value="편집" />
					<input type="hidden" name="id" th:value="${customer.id}" />
				</form>
			</td>
			<td>
				<form th:action="@{/customers/delete}" method="post">
					<input type="submit" value="삭제" />
					<input type="hidden" name="id" th:value="${customer.id}" />
				</form>
			</td>
		</tr>
	</table>
</body>
</html>

Chapter.02-7 고객 정보 편집하기

// CustomerController 클래스
@RequestMapping(value = "edit", params = "form", method = RequestMethod.GET)
String editForm(@RequestParam Integer id, @Validated CustomerForm form) {
	Customer customer = customerService.findOne(id);
	// 고객 편집 폼이 현재 고객 정보를 표시할 수 있도록
	// CustomerService.update() 메소드로 업데이트 합니다.
	// 업데이트 처리가 끝나면 목록 표시 화면으로 리다이렉트 합니다.
	BeanUtils.copyProperties(customer, form);
	return "customers/edit";
}

@RequestMapping(value = "edit", method = RequestMethod.POST)
String edit(@RequestParam Integer id, @Validated CustomerForm form,
		BindingResult result) {
	
	if(result.hasErrors()) {
		return editForm(id, form);
	}
	
	Customer customer = new Customer();
	BeanUtils.copyProperties(form, customer);
	customer.setId(id);
	customerService.update(customer);
	return "redirect:/customer"; // 다시 화면 로딩
}

@RequestMapping(value = "edit", params = "goToTop")
String goToTop() {
	return "redirect:/customers";
}

아래는 편집 폼(HTML) 입니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>고객 정보 편집</title>

<link rel="Stylesheet" type="text/css" href ="../../static/css/style.css" th:href="@{/css/style.css}" />
</head>
<body>
	<div>
		<form th:action="@{/customers/edit}" th:object="${customerForm}" method="post">
		<dl>
			<dt> <label for="lastName" class="col-sm-2 control-label"></label></dt>
			<dd>
				<input type="text" id="lastName" name="lastName" th:field="*{lastName}" class="form-control" value="홍" />
				<span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" class="help-block">error!</span>
			</dd>
			
			<dt><label for="firstName" class="col-sm-2 control-label"></label></dt>
			<dd>
			    <input type="text" id="firstName" name="firstName" th:field="*{firstName}" class="form-control" value="길동" />
				<span th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" class="help-block">error!</span>
			</dd>
		</dl>
			<!-- 한폼에 버튼을 여러 개 배치할 때는 name속성 값으로 컨트롤러 쪽 메소드를 구별합니다. -->
			<!-- 예를 들어, name="goToTop"이 설정된 버튼을 누르면 goToTop() 메소드가 호출됩니다. -->
			<input type="submit" class="btn btn-default" name="goToTop" value="돌아가기" />
			<!-- 고객 ID를 type="hidden"이 포함된 <input> 태그로 전송합니다. -->
			<!-- param.파라미터명 으로 요청 파라미터에 접근할 수 있습니다.(접근할 파라미터는 String[] 타입이라는 점에 주의 합니다.)-->
			<input type="hidden" name="id" th:value="${param.id[0]}"/>
			<input type="submit" class="btn btn-primary" value="업데이트" />
		</form>
	</div>
</body>
</html>
@RequestMapping(value = "delete", method = RequestMethod.POST)
String edit(@RequestParam Integer id) {
	customerService.delete(id);
	return "redirect:/customers";
}

여기까지 SpringBoot로 간단한 CURD 구현 이였습니다.