TIL - 16주차 코드

 

1. SpringBoot 방법론 : 230313


1) 트랜잭션 방법론


1) 트랜잭션 개념 :

  • ACID(Automicity, Consistency, Isolation, Durability)
  • 원자성, 일관성, 고립성, 지속성



2) 트랜잭션 실습 :

a. 첫 번째 실습 :

  • 업데이트를 2번 실행했는데 1번만 처리한다.

  • 실습 코드 1 :


DefaultMenuService.java
package kr.co.rland.web.service;

import java.util.List;

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

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

// @Component 어노테이션 설정해서 IOC에서 빈객체를 만들 수 있다. 
@Service
public class DefaultMenuService implements MenuService {
	
	@Autowired
	private MenuRepository repository;
	

	// 객체를 만들어서 결합해준다! 
	// setter가 있어야 xml에서 bean 태그에서 property 속성을 이용할 수 있다. 
	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<Menu> getList() {
		return repository.findAll(0,10,"",1,3000,"regDate","desc");
	}

	@Override
	public void pointUp() {
		Menu menu = new Menu();
		
		menu.setId(785L);
		menu.setPrice(1111);
		repository.update(menu);

		menu.setId(785L);
		menu.setPrice(2222);
		repository.update(menu);
	
		
	}

}


DefaultMenuServiceTest.java
package kr.co.rland.web.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DefaultMenuServiceTest {

	@Autowired
	private MenuService service;
	
	@Test
	void test() {
		service.pointUp();
		System.out.println("작업 완료");
	}

}




MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
//	List<Menu> findAll();
//	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	// 마지막으로 필요한 count 함수로 마지막 페이지를 구분할 수 있다!!
	int count(
			String query,
			Integer categoryId,
			Integer price);
	
	// 반환 값 수정하기!! 원래 반환 타입이 Menu이다. 
	int insert(Menu menu);
	
	int update(Menu menu);
	
	void delete(long id);
}



b. 두 번째 실습 : 트랜잭션

  • 제약 조건을 걸어주고 그 조건에 맞지 않으면 업무가 롤백 처리가 된다.

  • ex) 제약 조건 : “30000” 미만인 Price 조건에서 “30000”으로 업데이트 시키면 어떻게 될까?


  • 실습 코드 :

  • DefaultMenuService.java

package kr.co.rland.web.service;

import java.util.List;

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

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

// @Component 어노테이션 설정해서 IOC에서 빈객체를 만들 수 있다. 
@Service
public class DefaultMenuService implements MenuService {
	
	@Autowired
	private MenuRepository repository;
	

	// 객체를 만들어서 결합해준다! 
	// setter가 있어야 xml에서 bean 태그에서 property 속성을 이용할 수 있다. 
	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<Menu> getList() {
		return repository.findAll(0,10,"",1,3000,"regDate","desc");
	}
	
	// 트랜잭션 추가해서 하나의 업무만 진행되게 할 수 있다.
	@Transactional
	@Override
	public void pointUp() {
		Menu menu = new Menu();
		
		menu.setId(785L);
		menu.setPrice(1111);
		repository.update(menu);

		menu.setId(785L);
		menu.setPrice(30000);
		repository.update(menu);
	
		
	}

}



  • 테스트 코드 :
    • DefaultMenuServiceTest.java
package kr.co.rland.web.service;

import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@AutoConfigureMybatis
class DefaultMenuServiceTest {

	@Autowired
	private MenuService service;
	
	@Test
	void test() {
		service.pointUp();
		System.out.println("작업 완료");
	}

}




2) AOP 방법론

1) AOP 개념

  • Aspect Oriented Programming라고 부른다.
  • 사용자가 요구하는 코드가 아니라 다양한 사람이 원하는(Aspect) 코딩
  • 실제 업무는 아니고 곁다리 업무와 같다.
  • 수 없이 반복되는 횟수, 수 없이 변경되는 횟수 때문에 나온 방법론이다.


  • 사용 용도 :
    • Log 출력, 보안 처리, 트랜잭션 처리


  • 동작 과정 :
    • Core concern나 Primary concern(주업무)를 위, 아래에 AOP 방법론을 적용한다. AOP 방법론에서는 위, 아래를 포함한 내부에 있는 코어를 Proxy로 이용한다.
    • proxy(대리자) 개념 : 원본의 이름과 같은 형태이고 실제로 원본은 완전 다른 곳에 있거나 원격에 있는 친구이다.
    • proxy는 기본의 원래 속성 + 다양한 속성들을 곁들일 수 있다.


  • 실습 코드 :
    • 첫번째 실습 기능 : “calc”로 이용
package kr.co.rland.web.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Program {

	public static void main(String[] args) {
		Calculator calc = new DefaultCalculator(3,4);
		
		// Proxy 테스트 : Calculator와 진짜 같은 가짜 만들기**
		// 인자 설명 : (실제 어떤 기능을 가지고 있는지, 하나의 클래스에 여러개의 인터페이스를 가지고 있는지, 자바에서 제공해주는 AOP)
		// new Class[] {Calculator.class}의 중괄호에서 여러 개의 클래스를 가지고 있다. 
		Calculator 가짜 = (Calculator) Proxy.newProxyInstance(
				DefaultCalculator.class.getClassLoader()
											// invoke() 람다식으로 사용하는법 : 함수 모양만 남기면 된다. args는 ags로 바꾸기(예약어?)
				, new Class[] {Calculator.class}, (Object proxy, Method method, Object[] ags) -> {
						
					// return 5; // 이렇게 쓰면 전부 5만 출력 된다.
					System.out.printf("호출되고 있는 메소드 이름 : %s \n", method.getName()); 
					System.out.printf ("%s 메소드 호출 전 n", method.getName());
					Object result = method.invoke(calc, ags);
					System.out.printf("결과값: %d\n", result);
					System.out.printf("%s 메소드 호출 후 \n", method.getName());
										
					return result;
				}); 
		
		
      int result = 0;
      
		// Proxy에서 진짜를 불러내고 싶으면 proxy 코드를 다 바꿔야 한다. 
		// 이것은 엄청 많이 바꿔줘야해서 이때 Proxy에서 스프링의 DI 기능을 이용한다.
      	// 이것은 원본 객체를 숨길 수 있다. 
      
      result = 가짜.plus();
	  System.out.printf("plus result : %d\n", result);
	  result = 가짜.sub();
	  System.out.printf("sub result : %d\n", result);
	  result = 가짜.multi();
	  System.out.printf("multi result : %d\n", result);
      
//      result = calc.plus();
//      System.out.printf("plus result : %d\n", result);
//      result = calc.sub();
//      System.out.printf("sub result : %d\n", result);
//      result = calc.multi();
//      System.out.printf("multi result : %d\n", result);
	}

}

  • DefaultCalculator.java
package kr.co.rland.web.aop;

public class DefaultCalculator implements Calculator {
	
	private int x;
	private int y;
	
	public DefaultCalculator() {

	
	}
	
	
	public DefaultCalculator(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}


	public int getX() {
		return x;
	}



	public void setX(int x) {
		this.x = x;
	}



	public int getY() {
		return y;
	}



	public void setY(int y) {
		this.y = y;
	}



	@Override
	public int plus() {
		
		int result = x+y;
		return result;
	}

	@Override
	public int sub() {
		int result = x-y;
		return result;
	}

	@Override
	public int multi() {
		int result = x*y;
		return result;
	}
	
}

  • Calculator.java

package kr.co.rland.web.aop;

public interface Calculator {
	int plus();
	int sub();
	int multi();
}




  • 두번째 실습 기능 : “진짜”로 이용
package kr.co.rland.web.aop;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Program {

	public static void main(String[] args) {
		Calculator 진짜 = new DefaultCalculator(3,4);
		
		// Proxy 테스트 : Calculator와 진짜 같은 가짜 만들기**
		// 인자 설명 : (실제 어떤 기능을 가지고 있는지, 하나의 클래스에 여러개의 인터페이스를 가지고 있는지, 자바에서 제공해주는 AOP)
		// new Class[] {Calculator.class}의 중괄호에서 여러 개의 클래스를 가지고 있다. 
		Calculator 가짜 = (Calculator) Proxy.newProxyInstance(
				DefaultCalculator.class.getClassLoader()
											// invoke() 람다식으로 사용하는법 : 함수 모양만 남기면 된다. args는 ags로 바꾸기(예약어?)
				, new Class[] {Calculator.class}, (Object proxy, Method method, Object[] ags) -> {
						
					// return 5; // 이렇게 쓰면 전부 5만 출력 된다.
					System.out.printf("호출되고 있는 메소드 이름 : %s \n", method.getName()); 
					System.out.printf ("%s 메소드 호출 전 n", method.getName());
					Object result = method.invoke(진짜, ags);
					System.out.printf("결과값: %d\n", result);
					System.out.printf("%s 메소드 호출 후 \n", method.getName());
										
					return result;
				}); 
		
		
      int result = 진짜;
      
		// Proxy에서 진짜를 불러내고 싶으면 proxy 코드를 다 바꿔야 한다. 
		// 이것은 엄청 많이 바꿔줘야해서 이때 Proxy에서 스프링의 DI 기능을 이용한다.
      	// 이것은 원본 객체를 숨길 수 있다. 
      
      result = 가짜.plus();
	  System.out.printf("plus result : %d\n", result);
	  result = 가짜.sub();
	  System.out.printf("sub result : %d\n", result);
	  result = 가짜.multi();
	  System.out.printf("multi result : %d\n", result);
      
//      result = calc.plus();
//      System.out.printf("plus result : %d\n", result);
//      result = calc.sub();
//      System.out.printf("sub result : %d\n", result);
//      result = calc.multi();
//      System.out.printf("multi result : %d\n", result);
	}

}

  • 위의 두 실습으로 뭐가 “진짜”인지 “calc”인지 알 수가 없다.


  • 스프링은 포인트 컷하는 기능이 있다. 원하는 부분만 넣어서 사용할 수 있다. 하지만 Aspect J에는 그러한 기능이 불필요하게 많다.



2) 포인트 컷 개념

  • 모든 메서드가 아니라 일부분만 변경하고 싶을 때, 사용한다.


  • target : 모든 메서드를 의미한다.
    • ex) target 1개에 join-point가 3개가 있을 수 있다.


  • join-point : core에 해당하는 부분


  • weaving : proxy가 가지고 있는 부분(앞과 뒤 처리 부분)과 joint-point가 가지고 있는 부분을 붙여주는 weaving이라고 한다.


  • point cut : weaving이 되는 부분을 원하는 범위에서만 한정한다. 자바에는 없고 스프링에서는 point-cut 기능이 어느정도 있다. Aspect J는 point-cut이 있지만 해당 관련 불필요한 기능들도 많다.


  • 정리 : 무조건 AOP는 원본 코드가 실행되기 전에 실행된다. 사전 작업이나 사후 작업에 사용된다. 그래서 나중에 라이브러리나 프레임워크를 만들 때 사용한다.



3) Transaction

  • 우리는 프로그램밍에서 트랜잭션의 특성 4가지 중에서 원자성, 고립화만 다룬다.


  • 스프링이 제공해주는 고립화는 전파 옵션, 고립도 옵션이 있다.


  • 전파 개념 : 일단 먼저 데이터를 보내는 과정!


  • 문제점 : 같은 자원들을 가지고 2개의 스레드들이 동시에 사용할 때, 처리하는 방법?



2. DB 정규화 개념 : 230314


1) 제 1 정규화 :

  • 정의 : 모든 도메인이 원자값으로만 이루어져야만 한다.


a. 도메인이란? :

  • 값을 이야기한다.
  • 유효한 범위의 값이다.


  • 간단히 말하면, “column”이다. 변수이자 변량이자 도메인이자 필드이다.


b. 제 1 정규화 분석 :

  • 하나의 칼럼에는 같은 데이터가 들어가면 안 된다.?


  • 정리** : 중복을 제거한다. 하나의 속성이 하나의 값을 갖도록 한다.**


  • 이렇게 하지 않으면, 하나의 칼럼에 여러 값이 들어간다. 이것은 제 1 정규화 위반!
  • ex) 제 1 정규화 위반 목록 : 전화번호, 팩스 번호, 이메일, 핸드폰 번호, 형태, 세금계산서용 이메일!!


c. 제 1 정규화 적용 :

  • 중요! : 예를 들어, 사진 같은 것은 프로필 사진이 여러 개가 필요해서 “구분자”나 따로 잘라내야 한다. 같이 넣을 수 없다.
  • 그래서, 제 1 정규형에 위반되면, 따로 테이블을 떼어 내야 한다. 그래서 “카테고리” 테이블을 잘라냈구나..


  • 예를 들어, 은행명, 예금주명, 계좌번호 등이 있다.


  • 만약에, 정규화로 잘라내지 않고 구분자(“,”)로 구분해서 사용한다면 java나 쿼리문으로 잘라내어야 한다.


2) 제 2 정규화 :

a. 제 2 정규화 정의 :

  • 부분 함수 종속성을 없앤다.


b. 제 2 정규화 분석 :

  • 여기서 “함수”는 “외래키”를 말한다.
  • 참조하는 것이 연산식에 참조한다.


  • 정리 : 외래키가 참고하는 외래키가 있으면 안된다. 그냥 있지 말고 외래로 가야한다.**
  • 부분함수에 대한 부분이 외래키에 있다.


  • 제 2 정규화는 복합키에서만 쓰이는 개념이고 복합키에서 문제가 발생한 경우이다.
  • 복합키가 없으면 제 3 정규화로 넘어가자.


3) 제 3 정규화 :

a. 제 3 정규화 정의 :

  • 이행적 함수 종속이 되지 않는 것


  • 이행 : 옮아가는 것
  • 이행적 함수 종속 : 함수는 외래키인데


b. 제 3 정규화 분석 :

  • 제 2 정규화는 기본키에서 다시 외래키로 식별하는 것이 문제가 있으며, 제 3정규화는 외래키가 다시 재 참조하는 키가 있으면 문제가 발생한다.
  • 외래키가 바로 접근하는 것이 아니라 거쳐서 접근하는 경우가 문제가 있다.


c. 제 3 정규화 정리 :

  • 결론 : X->Z를 X->Y와 Y->Z로 쪼개자!!
  • 그래서, 3 정규화가 쪼개지면, 쪼개진 함수는 부모가 된다. “1” : “N”에서 “1” 부분이 부모가 된다.
  • 반복될 것 같다고 그러면 거의 대부분 제 3 정규화 위반이다.


4) 제 4 정규화 :

a. 제 4 정규화 정의 :

  • “다대다” 관계를 풀어낸 것이다.


5) 프로젝트 DB 관련 정리

a. 피드백

  • 참여 테이블에서 채팅 내역으로 정하고 회원마다 채팅 내역을 가지고 있어야 하므로 그래서 참여 테이블을 만들어서 추가하는 것이 맞다.
    • (이렇게 웹소켓 넘겨주기)
  • 참여하는 순간 이후부터 채팅 내역이 보이게 된다.


b. 다른 피드백

  • null값이 꽉찰 속성은 할 필요가 없다.(설정창의 속성)

  • 공구 상품 테이블에서 따로 뺄 것 : 사진 속성, 모집상태 속성, 관심 속성,


c. 정리

  • 핵심 : 참여 테이블(액션 엔티티)에 자식의 테이블을 넣을 수 있다!!! 나중에 “메세지” 테이블 넣기!!!!


  • 중요 : 데이터가 중복되는 것과 키가 중복되는 것은 다르다(카테고리 테이블을 따로 뺀 이유!!)


  • 추가 : 제 3 정규화의 데이터 중복은 데이터 중복이 되는 해당 테이블이 생성되기 전에 발생하면 해당 테이블의 부모로 테이블을 따로 빼고, 테이블 생성된 이후의 데이터 중복은 해당 테이블의 자식으로 테이블을 따로 뺀다.



3. DB 제약 조건: 230315

1) MySQL 사용법 :

  • SQL문을 사용하기 전에
    • use “DB명”


a. Reverse Engineering :

  • EER Diagram 작업 가능
  • CREATE를 쓰지 않고 UI에서 툴을 사용하자!


b. Forward Engineering :

  • DROP하지 말고 진행하기(DROP하면 기존의 테이블들이 다 사라진다!)

  • Forward Engineering 작업을 진행해도 EER Diagram에서 계속 작업하고 넘길 수 있다.


중요!! :

  • FORWARD Forward라는 도구가 제대로 동작이 안되면 use_information라는 db로 이동하여 데이터 딕셔너리를 이용한다.
    • use use_information
    • show create table comment
    • 여기서 DDL 명령어를 복사해서 편집키에 붙여놓고 수동적으로 체크 체약 조건을 수정한다.


1) MySQL data 타입 종류


  • DATE 타입 :
    • DATETIME vs TIMESTAMP :
      • MySQL에서 타입을 Date를 길게 쓰고 싶으면 DATETIME을 쓰고 Date를 길게 쓰지 않을 때는 TIMESTAMP를 이용한다.


2) 제약 조건


a. 제약 조건 개념 :

  • 유효한 값의 범위를 말한다.
  • 테이블을 만들기 전에는 SQL문에서 제약조건을 걸어줄 수 있는데 테이블을 생성하고 나서는 테이블의 설정 UI에서 제약조건을 설정할 수 있다.


b. 제약 조건 종류 :


  • NOT NULL :
    • 그 칼럼은 데이터가 무조건 NULL이 아니여야 한다.


  • AI(Auto Increamental)
    • ID를 자동 증가 해준다.(자동 시퀀셜 기능)


  • 현재 시간 출력 함수 :
    • CURRENT_TIMESTAMP(); : 현재 날짜 + 시간 출력
    • CURRENT_TIME(); : 현재 시간만 출력
    • CURRENT_DATE(); : 현재 날짜만 출력


3) MySQL 체크 제약 조건 :

  • 보통 패턴을 사용한다.


  • 이미 만들어진 테이블에서 체크 제약을 하기 위해서 다음 명령어로 추가한다.
    • ALTER TABLE Persons ADD CHECK(AGE>=18);


  • 체크 제약 조건을 위해서 사용하는 변수 명칭(언더바 이용):
    • chk_menu_price 등등


  • MySQL에서 체크 제약 조건을 위해서 사용 방법이 없어서 명령어로 진행한다.


4) DB의 Key 종류


  • 기본키(Primary Key : PK) : 식별키이며 1개만 존재한다.
    • 하지만, 개인정보는 기본키로 쓸 수 없다. 보통, 비교할 때, 기본키를 사용하고 공개해야하므로
    • 이메일 주소도 PK로 설정하면 안 된다. 나중에 다시 재가입시, 이메일주소가 식별자라서 그 이메일로 가입할 수 없기 때문이다.
    • 따라서, 기본키는 개인정보를 빼는 것이 좋다.
    • 운영체제(윈도우)에서도 현재 사용자 ID(USER12)를 지우면 별도의 식별자(S11232132)로 변경되어 존재한다.


  • 후보키 : 기본키가 될 수 있는 모든 후보들
  • 대체키 : 기본키가 아니고 기본키가 아닌 후보키드


  • 대리키 : 개인정보가 아닌 진짜 식별키이며 일반적으로 번호이다. 예를 들어, 아이디, 번호 등등


  • 외래키 :
  • 수퍼키 : 복합키


  • 각 칼럼들에 동일한 데이터가 들어가면 데이터 결함이 존재한다.


5) 엔티티 제약 조건

  • 엔티티 제약 조건 : PK 제약 조건, 유닉 제약 조건이 있다.


  • 유닉 제한 조건 : PK가 아니면서 중복이 되지 않아야 경우에 사용된다. 사용 방법은 NULL값도 허용되면 안 된다. 따라서, MySQL에서 UQ와 NN을 동시에 조건으로 넣어줘야 한다.
    • 예를 들어, 서비스에서 회원이 회원을 탈퇴하면, 회원 아이디는 사라져도 식별자는 남아야 한다. 회원 아이디가 PK가 되면 그 회원아이디명으로 재가입을 할 수 없기 때문이다. 그래서 식별자(PK)는 따로 두고 아이디는 유닉 제한 조건을 걸어둔다.
    • 따라서, “아이디”는 “유닉 제한 조건”이여야 한다.


  • UQ : Unique Key

  • NN : NOT NULL



6) DB의 FK 제한 조건

  • 엔티티와 엔티티의 관계가 끊어지는 경우 방지


  • 1:N, N:N 관계에 있을 때, FK로 받으면 그 FK가 NOT NULL일 때, 관계가 끊어지는 것을 막아준다.


  • FK 제약 조건은 자식에 붙어야 한다.


  • DB를 만들 때, 체크 제약 조건에 에러가 발생하면, DB의 레코드를 다 지우고 처음부터 작성해야 한다.



2) 트랜잭션 동시성

  • 트랜잭션 전파 개념

a. 트랜잭션의 고립화 종류


  • Dirty Read :
    • 업데이트가 되고 인서트 하다가 에러가 발생하는 중에 어떤 스레드가 업데이트를 읽어버림, 그것은 원래 되돌가기 전의 값으로 읽어버림. 그래서 문제가 많다.


  • Non-repeatable read :
    • 다른 스레드가 읽기만 해도 기다린다!


  • Phantom read :
    • 삭제될 녀석을 읽어 들임. 내가 읽기도 전에
    • 내가 읽기만해도 내가 건드리는 테이블을 아무도 건드리지말고 기다려야 한다.
    • 누가 조금이라도 사용하기만 해도 테이블 전체를 잠궈버린다.


b. 고립화 필요 개념 :


  • DEFAULT : 아래 4개 옵션 중의 1개이다.


  • READ UNCOMMITTED : (가장 느슨한 옵션) 성능은 좋은데 Dirty Read를 읽을 수 있다.(롤백될 내용을 읽을 수 있다. )


  • READ_COMMITTED : 커밋된 것만 읽을 수 있어서 이것은 오라클DB에서 사용된다. MariaDB도 이것을 사용해서 Dirty Read에 문제는 없다.(기본 옵션)
    • 다른 스레드가 수정을 하면 기다린다.


  • REPEATABLE READ :
    • NON-REPEATABLE READ은 업데이트를 하고 그 값을 다시 읽으려고 하는데 다른 스레드가 없애버리면 그것을 읽을 수 없다. 동시성의 문제이다.
    • NON-REPEATABLE READ은 다른 스레드가 읽기만 해도 기다린다!(READ_COMMITTED와 차이점)
    • REPEATABLE READ와는 반대 개념이다.


  • SERIALIZABLE : 제일 세다. 내가 손대는 순간 잠궈 버린다.


  • 고립화를 느슨하게 해주는 것이 “성능”이다.
    • 즉, 못 읽게하는 것은 기다리는 것이다.


c. Spring-Boot 트랜잭션에 고립화 적용 :

a) isolation 속성

  • 어느 정도 고립화를 시킬지 어노테이션에 설정해주면 된다.

  • @Transaction(isolation = "고립화 레벨 선택")

  • 기본 옵션이 READ_COMMITTED 이다.


b) propagation 개념(더 찾아보기)

  • 업데이트하는데 같이 참여하거나 참여하지 않거나 하는 경우 따로 처리해줄 경우가 필요할 때, propagation 속성을 사용한다.
  • 원자성에 위배되는 개념이지만 따로 빠질 수 있다.


  • 사용방법 :
    • 다음의 실습 코드처럼 Transaction의 전파 옵션은 전파되는 “자식”이 갖는다.


  • 실습 코드 :
    • DefaultMenuService.java

@Service
public class DefaultMenuService implements MenuService {
	
	@Autowired
	private MenuRepository repository;
	

	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	// 트랜잭션 추가해서 하나의 업무만 진행되게 할 수 있다.
	@Transactional(propagation = )
	@Override
	public void pointUp() {
		repository.update([3,2,7,8], 1);
		repository.update(3);
	
		
	}
}


  • MenuService.java
package kr.co.rland.web.service;

import java.util.List;

import kr.co.rland.web.entity.Menu;

public interface MenuService {
	
	// 서비스 계층에서는 사용자 요청을 이름 그대로 그대로 만들어라!!
	List<Menu> getList();	
	void pointUp();
}


  • MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
//	List<Menu> findAll();
//	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	// 마지막으로 필요한 count 함수로 마지막 페이지를 구분할 수 있다!!
	int count(
			String query,
			Integer categoryId,
			Integer price);
	
	// 원래는 Menu가 반환 타입이다.
	int insert(Menu menu);
	
	int update(Menu menu);
	
	void delete(long id);
}


c) Propagation 속성 종류(더 찾아보기) :

  • Propagation.mandatory :
    • 서비스에서 내가 트랜잭션을 가지고 있지 않으면 호출자가 트랜잭션을 가지고 있어야 한다.


  • Propagation.nested :
    • 현재 트랜잭션이 존재하는데 서비스에서 트랜잭션을 가지고 오는데 안쪽에서 트랜잭션을 진행한다.


  • Propagation.supports :
    • 서비스에서 트랜잭션을 가지고 오면 지원정도는 해준다.(같이 따라간다.) Non-supports이면 트랜잭션을 하지 않는다.


  • Propagation.requiresnew :
    • 무조건 새로운 트랜잭션을 만든다. 나는 부모의 트랜잭션에 나를 엮지 마라, 항상 새 트랜잭션을 만든다.


  • Propagation.requires_new :
    • 현재 트랜잭션이 있으면 지원해주고,


  • Propagation.required :
    • 누군가가 나를 호출하면 나는 트랜잭션도 하지 않고 그냥 지나가라. 내가 실행되면 나 끝나기까지 기다리고 그 다음에 트랜잭션을 진행 suspend(기다려라)


  • Propagation.never :
    • 나를 트랜잭션에 실행하면 화낸다.


  • Propagation.nested(추가**) :
    • 현재 트랜잭션이 있으면, 부모의 트랜잭션은 같이 끼지 않고 트랜잭션 안쪽에서 중첩?해서 트랜잭션을 진행한다.


d) 정리** :

  • propagation 속성은 행위가 별도의 트랜잭션 처리를 해주거나 참여하던가 참여하지 않던가하는 처리 방법에 쓰인다.



4. Tiles : 230316

1) Tiles 개념 :

  • 페이지를 구성하는 공통분모를 잘라내서 집중화를 해야한다. (어느 메뉴를 들어가도 비슷하게 보이는 부분들이 있다)
  • view 폴더에 inc폴더 생성(include)
  • layout.jsp 생성 (공통분모를 넣을 것)
  • menu.list.html의 파일을 layout.jsp에 옮기기
  • view 폴더에 menu 폴더 생성해서 list.jsp 파일 만들기
  • list.html 의 main태그부분 list.jsp에 옮기기
  • layout.jsp 에는 main을 제외한 부분을 두고
  • list.jsp에 main을 둔다.
  • 기존에 썼던 menucontroller 수정해서 돌아가도록 만들기.(/menu/list)


  • layout.jsp 에서 메인이 있던 부분에 을 써서 다른 jsp의 내용을 끼워넣을 수 있다.
    • 하지만 지금은 이렇게 사용하지 않는다. (인클루드 하는 액션태그)
  • 이유 : list를 만들다보면 list에서 필요로 하는 내용들은 잘라놨다가 include 시키는게 초기의 방식이었다. 그런데 쓰다보니 불편.


  • 다음페이지를 만들려고 했더니 include가 반복되게 되는 문제가 발생함.
  • 자바코드에서 import 반복하는것은 딱히 불만이 아니었는데 이건 불만이었나봄.
  • include 를 집중화 해보자! include내용만 분리해서 재사용 할 수 있다면.. 그러나 jsp 자체에 이런 기능이 없다.
  • 레이아웃은 결국 부분을 모아서 합친 느낌. 이런걸 도와주는 라이브러리가 생김 : tilesLib. 그러나 요즘 트렌드가 바뀌면서 은퇴해버림
  • 페이지를 만드는게 자바스크립트로 가버림. (더이상 성숙할게 없어서 은퇴해버리기도 함)(서버사이드가 많이 사라짐)
  • 그래서 이 라이브러리는 아파치에 은퇴명단으로 옮겨짐.(그림3)


  • [메이븐 라이브러리에서 타일즈 찾기](https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp/3.0.8](https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp/3.0.8)
    • 최근버전으로 디펜던시에 추가해주자.


  • 타일즈 라는 새로운 알바생이 생김. 사용법을 알아보자.
  • inc 폴더에 header.jsp / footer.jsp 를 만들어서 layout.jsp에 있던 header와 footer를 옮겨주자.
  • 우리가 만드는 서비스도 위와같이 분리를 해야한다.
  • 여기까지 준비가 끝났다면. 이전처럼 페이지를 요청하지 않고 페이지 이름이 달라져야 한다.(타일즈에게 조립해달라는 조합에 대한 이름을 반환시켜야함. 이제 실질적으로 페이지 하나에 대한 이름이 아니다. 타일즈 설정을 위해 준비된 xml이 있다.)
  • 타일즈 xml 양식이 암기가 가능한 수준이 아니기때문에 검색해서 가져오자.


  • [레퍼런스 : 타일즈 xml 양식 찾기](https://tiles.apache.org/framework/tutorial/basic/pages.html](https://tiles.apache.org/framework/tutorial/basic/pages.html)
  • WEB-INF 에 tiles.xml을 만들고 위 링크의 Create a definition 부분을 집어넣자. (인코딩부분 제외!)
  • jsp 에서 집중화하는 방법은 tiles가 유일하기때문에 은퇴했지만 사용한다.
  • By default, the definition file is /WEB-INF/tiles.xml (경로를 올바르게 설정해야한다)

2) Tiles 기본 설정

  • 실습 코드 :
    • tiles.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
  <definition name="menulist" template="/WEB-INF/view/inc/layout.jsp">
    <put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
    <put-attribute name="main" value="/WEB-INF/view/menu/list.jsp" />
    <put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
  </definition>
</tiles-definitions>


  • tiles 주입 설정 :
    • layout.html

<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
// 상단에 태그 라이브러리를 추가해준다.

    <tiles:insertAttribute name="header" />
    <!-- ---------------------------------------------------------------------- -->   
    <!-- main이 있던 부분 -->
    <tiles:insertAttribute name="main" />
    <!-- ---------------------------------------------------------------------- -->   
    <!-- footer 자리 -->
    <tiles:insertAttribute name="footer" />

    // 각각의 태그가 있던 자리에 insertAttribute 해준다.
    // 지시서 작성, 레이아웃 준비, 조립해야 할 내용 지시 완료 => 그런데 아직 알바생을 안부름.
    


3) tiles 깨우는 법 :

  • TilesConfig- kr.co.rland.web.config 에 TilesConfig 클래스를 생성.
  • 두가지를 객체화 해야됨


  • 1) 타일즈를 깨워야하고 xml 파일이 어디있는지 말해주기 위한 객체를 만들어야 함.


  • 2) 지시파일이 있는것을 활용하는 실질적인 타일즈를 깨워야함.
    • 메모리에 올라가기 위해

package kr.co.rland.web.config;
    

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.view.UrlBasedViewResolver;

	@Configuration  
	public class TilesConfig {
	
	
	@Bean
	public UrlBasedViewResolver tilesViewResolver() {
	    UrlBasedViewResolver resolver = new UrlBasedViewResolver();
	    //resolver.setViewClass(Tiles);
	    return resolver;
	}


}  

// 명령어를 찾아나서는 녀석을 리졸버라고 한다.  
// ex)뷰 정보를 찾아주는 녀석은 뷰 리졸버  
// 파일즈는 뷰 리졸버 중에 하나다.  
// 디폴트리졸버가 지금까지 jsp를 찾아주고있었음.  
// 타일즈가 우선순위를 가지고 있기때문에 충돌이 생기진 않는다.
 
// 컨트롤러가 반환하는건 뷰인데 이제 타일즈가 먼저 받게된다. 지시서가 어딨는지몰라서 타일즈가 당황함.  
// 그래서 그 정보를 또 담아서 줘야한다.




5. Thymeleaf : 230317

0) 유닉스에서 터미널 사용

  • cd \ : 자기 터미널 경로 중 root로 이동
  • cd ~ : 자기 터미널 경로 중 home 디렉토리로 이동
  • 정리 : 이 개념은 타임리프 레이아웃에서 쓰일 예정이다.


1) 타임리프 기본 사용법

a. pom.xml에서 타임리프 라이브러리를 추가해준다.

  • thymeleaf-layout-dialect는 타임리프에서 layout을 사용하기 위한 라이브러리이다.
    • decorate : 알맹이를 꾸며주는 것을 “layout”이라고 한다.
    • 그래서, list.html에서 알맹이를 먼저 찾고 바깥을 꾸며주는 것인 header, footer가 있는 부분인 layout.html로 이동한다.
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect -->
<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>


b. 타임리프는 항상 html에 사용하고 무조건 html 파일 상단에서 html 태그 안에 xmlns=th="http://www.thymeleaf.org"를 추가해준다.

<html
	 xmlns=th="http://www.thymeleaf.org"
>	


c. 아래 코드처럼 html에서 태그 안에서 th라는 속성 값으로 사용한다.

<header class="header fixed-header" th:fragment="header">


2) replace vs insert **

  • 중요한 개념 정리 제대로 하기

a. replace 개념

  • replace는 div 태그로 감싸고 있는데 감싸고 있는 div 태그를 완전 대체 해버린다.


b. insert 개념

  • insert는 div 태그로 감싸고 있는데 감싸고 있는 div 태그의 모양을 유지하면서 그 안에서 관련 메인 태그를 넣어준다.

<!-- 타임리프는 속성으로 쓰기 때문에 속성으로 쓰기 위해서 div 태그에 사용한다. -->
<!-- <div th:replace=""></div> -->

<!-- replace는 대체, insert는 div 태그 모양을 유지한다. -->
<div th:insert="~{index:header}"></div>
~~~~

<div th:replace="~{index:header}"></div>
~~~~


c. 응용 : fragment 개념

  • fragment는 :에 의해서 따로 떼서 붙여주고 replace로 완전 대체 해준다.
  • html에서는 이러한 fragment를 완전 명명한다.


d. 주의 및 정리

  • 타임리프는 속성으로 쓰기 때문에 속성을 쓰기 위해서 꼭 div 태그같은 html 태그에 사용한다


a) 문제점 1 :
  • ::에 의해서 html 속성 값마다 selector를 하는 것이다. 마치 슈도 코드와 같은 형태이며 layout에서 경로로 사용할 때는 “/”로 이용해도 된다.
    • <div th:insert="~{index::header}"></div>
    • <div th:replace="~{inc/header}"></div>


  • 위의 코드처럼 타임리프의 속성 사용하는 2가지 방법 비교하기


b) 문제점 2 :
  • 문제점 1과의 코드와 문제점 2의 코드를 비교하여 사용법 정리하기
  • <div layout:fragment="main"></div>


c) 문제점의 최종 정리** :
  • 문제점 1과 문제점 2의 코드에서 th의 속성이나 layout의 속성처럼 div 태그에서 바로 속성으로 이용하면 :로 구분 짓는다.
  • 그리고 Selector로서 어느 곳에 지정해주는지 설정해주기 위해서는 ::로 구분 짓는다. 또한, 파일 경로로도 지정해줄 수 도 있어서 이때는 /로 구분짓는다.


e. th: insert의 실습 코드 :

  • insert와 replace를 넣어보면서 크롬의 개발자 모드에서 html 구조를 확인할 것
    • div 태그가 있나 없나만 확인하기


MenuController.java
package kr.co.rland.web.controller;

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

// FrontController(POJO 클래스)를 만드는 방법! 
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
@RequestMapping("/menu/")
public class MenuController {
	
	// 함수 이름은 보통 url이 된다.
	@RequestMapping("list")
	public String list() {
		return "/menu/list.html";
	}
	
	@RequestMapping("detail")
	public String detail() {
		return "/menu/detail";
	}

}


index.html
<!DOCTYPE html>
<html
	 xmlns=th="http://www.thymeleaf.org"
>	
	

<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>
    <link href="/css/reset.css" type="text/css" rel="stylesheet" >
    <link href="/css/style.css" type="text/css" rel="stylesheet" >
    <link href="/css/layout.css" type="text/css" rel="stylesheet" >
    <link href="/css/header.css" type="text/css" rel="stylesheet" >
    <link href="/css/footer.css" type="text/css" rel="stylesheet" >
    
    <link href="/css/user/find-id.css" type="text/css" rel="stylesheet" >
    <link href="css/buttons.css" type="text/css" rel="stylesheet" >
    <link href="css/icon.css" type="text/css" rel="stylesheet" >
    <link href="css/deco.css" type="text/css" rel="stylesheet" >
    <link href="css/utils.css" type="text/css" rel="stylesheet" >    
</head>

<body>    
    <header class="header fixed-header" th:fragment="header">
        <div>
            <h1 class="header-title"><a href="/index.html"><img class="logo" src="../image/logo-w.png" alt="알랜드"></a></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><a class="" href="/menu/list.html">카페메뉴</a></li>
                <li><a class="" href="/notice/list.html">공지사항</a></li>
                <li><a class="" href="/user/login.html">로그인</a></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">thymeleaf 부안에 오면 꼭! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 길</a></div> 
        </div>
    </nav>
    
    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄 수 있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="./image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 맛, 과테말라
                    산의 풍미, 그 외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="./image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
	            <div class="d-flex justify-content-between">
	                <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
	                <div class="d-flex align-items-center">
	                	<a class="btn btn-leaf mr-3" href="">전체명소</a>
	                </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                서 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 듯..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="./image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="./image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="./image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="./image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="./image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="./image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
    </section>

    <!-- ---------------------------------------------------------------------- -->
    <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>
		
        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>
    <footer class="footer">
        <h2>알랜드(Rland)</h2>
        <div>
            copyright @ rland.co.kr 2022-2022 All Right Reservved. Contact admin@rland.co.kr for more information
        </div>
    </footer>
</body>

</html>


list.html
<!DOCTYPE html>
<html
	 xmlns=th="http://www.thymeleaf.org"
	 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	 
>	
	
	<!-- 타임리프는 속성으로 쓰기 때문에 속성으로 쓰기 위해서 div 태그에 사용한다. -->
	<!-- <div th:replace=""></div> -->
	
	<!-- replace는 대체, insert는 div 태그 모양을 유지한다. -->
	<div th:insert="~{index::header}"></div>
	<main>
		<section>
			<header class="search-header">
				<h1 class="text-title1-h1" th:text="${data}">알랜드 메뉴</h1>
				<form>
					<input type="text">
					<input type="submit" class="icon icon-find">
				</form>
			</header>
			<aside class="aside-bar">
				<h1>aside</h1>
				<section class="aside-bar-content">
					<h1>메인메뉴</h1>
					<ul class="mt-3">
						<li><a href="">카페메뉴</a></li>
						<li><a href="">공지사항</a></li>
						<li><a href="/user/login.html">로그인</a></li>
					</ul>
				</section>
			</aside>
			<nav class="menu-category">
				<div>
					<h1 class="text-normal-bold">메뉴분류</h1>
				</div>
				<ul>
					<li class="menu-selected">
						<a href="/menu/list">전체</a>
					</li>
					<li>
						<a href="">커피음료</a>
					</li>
					<li>
						<a href="">수제청</a>
					</li>
					<li>
						<a href="">샌드위치</a>
					</li>
					<li>
						<a href="">쿠키</a>
					</li>
				</ul>
			</nav>

			<section class="cart-section">
				<h1 class="d-none">장바구니</h1>
				<span class="text-title3">커피음료</span>
				<div class="icon icon-basket icon-text">1</div>
			</section>

			<section class="menu-section">
		        <h1 class="d-none">메뉴목록</h1>
		        <div class="menu-list">
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <a href="detail.html"><img class="menu-img" src="/image/product/12.png"></a>
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		        </div>
		    </section>

			<div class="d-flex justify-content-center py-3">
				<a href="" class="btn btn-round w-100 w-50-md py-2">더보기(13+)</a>
			</div>

			<section class="new-menu menu-section-p">
				<h1 class="d-none">신메뉴 목록</h1>
				<!-- <ul>
                    <li>
                    </li>
                </ul>  -->
				<div class="list">
					<span>신규로 출시된 메뉴가 없습니다.</span>
				</div>
			</section>

		</section>
	</main> 

</html>




3) layout 동작 순서**

  • 동작 순서 : 컨트롤러 -> list.html의 html 태그 -> layout.html -> 각 부분마다 html에서 가져오기!!


  • decorate : 알맹이를 꾸며주는 것을 “layout”이라고 한다.**
    • 그래서, list.html에서 알맹이를 먼저 찾고 바깥을 꾸며주는 것인 header, footer가 있는 부분인 layout.html로 이동한다.
    • 그래서 전체적으로 decorate 되어 있는 layout.html에 fragment로 알맹이(main 부분)를 꽂아 준다.**
    • html 파일에서 html 태그의 속성을 주의하면서 볼 것!(decorate 속성)


  • 중요!!** :
    • header와 footer가 replace로 꽂혀 있는 이유는 header와 footer는 페이지가 달라져도 매번 바뀌지 않기 때문이다.(고정)
    • 그래서 자주 바뀌는 알맹이인 메인 부분을 fragment로 꽂아서 붙이고 떼고를 자유롭게 해준다.


  • 추가적으로 detail.html을 view 단으로 출력할 때, layout.html을 하나 더 만들 생각을 했지만 안에 알맹이만 바꿔 주면 된다. 집중화를 위한 것이며 Tiles에서는 tiles.xml라는 파일에서 알맹이 변경(list.html에서 detail.html로 변경)이 가능했었다.


  • 예시 : tiles.xml

<definition name="notice.*" template="/WEB-INF/view/customer/inc/layout.jsp">
<put-attribute name="title" value="공지사항"/>
<put-attribute name="header" value="/WEB-INF/view/inc/header.jsp"/>
<put-attribute name="visual" value="/WEB-INF/view/customer/inc/visual.jsp"/>
<put-attribute name="aside" value="/WEB-INF/view/customer/inc/aside.jsp"/>
<put-attribute name="body" value="/WEB-INF/view/customer/notice/{1}.jsp"/>
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp"/>
</definition>



a. 타임리프의 실습 코드(list.html 만들어주기) :


a) MenuController.java 경로 수정
  • 나중에 detail 페이지도 추가하기


MenuController.java
package kr.co.rland.web.controller;

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

// FrontController(POJO 클래스)를 만드는 방법! 
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
@RequestMapping("/menu")
public class MenuController {
	
	// 함수 이름은 보통 url이 된다.
	@RequestMapping("/list")
	public String list(Model model) {
		
		model.addAttribute("data","hello");
		
		// 상대경로이면 타임리프가 알아서 찾아준다. 
		return "menu/list";
	}
	
	@RequestMapping("detail")
	public String detail() {
		return "menu/detail";
	}

}


b) 레이아웃 설계
  • 문제점 : css파일이 섞이면 다른 페이지들과 안 겹치나?


layout.html

<!DOCTYPE html>
<html  
	xmlns=th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
>

<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>
    <link href="../css/reset.css" type="text/css" rel="stylesheet" >
    <link href="../css/style.css" type="text/css" rel="stylesheet" >
    <link href="../css/layout.css" type="text/css" rel="stylesheet" >
    <link href="../css/header.css" type="text/css" rel="stylesheet" >
    <link href="../css/footer.css" type="text/css" rel="stylesheet" >
	<link href="../css/component/aside.css" type="text/css" rel="stylesheet" >
	<link href="../css/component/cart-section.css" type="text/css" rel="stylesheet" >
	<link href="../css/component/menu-category.css" type="text/css" rel="stylesheet" >
    <link href="../css/component/menu-section.css" type="text/css" rel="stylesheet" >
	<link href="../css/component/search-header.css" type="text/css" rel="stylesheet" >

    <link href="../css/component.css" type="text/css" rel="stylesheet" >
    <link href="../css/buttons.css" type="text/css" rel="stylesheet" >
    <link href="../css/icon.css" type="text/css" rel="stylesheet" >
    <link href="../css/deco.css" type="text/css" rel="stylesheet" >
    
    <link href="../css/utils.css" type="text/css" rel="stylesheet" >    
	
	<link href="../css/component/menu-detail.css" type="text/css" rel="stylesheet" >
	<link href="../css/component/nutrition-info.css" type="text/css" rel="stylesheet" >

</head>

<body>
	
    <!-- header 부분-->
   <!--  <div th:replace=""></div> -->
	<div th:replace="~{inc/header}"></div>
    <!-- ------------------------------------------ -->
    <!-- main 부분-->
    <div layout:fragment="main"></div>
    
    <!-- footer 부분-->
    <div th:replace="~{inc/footer}"></div>
</body>

</html>



list.html
<!DOCTYPE html>
<html
	 xmlns=th="http://www.thymeleaf.org"
	 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	 layout:decorate="inc/layout"
>	

	
	<!-- 타임리프는 속성으로 쓰기 때문에 속성으로 쓰기 위해서 div 태그에 사용한다. -->
	<!-- <div th:replace=""></div> -->
	
	<!-- replace는 대체, insert는 div 태그 모양을 유지한다. -->
	
	<main layout:fragment="main">
		<section>
			<header class="search-header">
				<h1 class="text-title1-h1" th:text="${data}">알랜드 메뉴</h1>
				<form>
					<input type="text">
					<input type="submit" class="icon icon-find">
				</form>
			</header>
			<aside class="aside-bar">
			
				<h1>aside</h1>
				<section class="aside-bar-content">
					<h1>메인메뉴</h1>
					<ul class="mt-3">
						<li><a href="">카페메뉴</a></li>
						<li><a href="">공지사항</a></li>
						<li><a href="/user/login.html">로그인</a></li>
					</ul>
				</section>
			</aside>
			<nav class="menu-category">
				<div>
					<h1 class="text-normal-bold">메뉴분류</h1>
				</div>
				<ul>
					<li class="menu-selected">
						<a href="/menu/list">전체</a>
					</li>
					<li>
						<a href="">커피음료</a>
					</li>
					<li>
						<a href="">수제청</a>
					</li>
					<li>
						<a href="">샌드위치</a>
					</li>
					<li>
						<a href="">쿠키</a>
					</li>
				</ul>
			</nav>

			<section class="cart-section">
				<h1 class="d-none">장바구니</h1>
				<span class="text-title3">커피음료</span>
				<div class="icon icon-basket icon-text">1</div>
			</section>

			<section class="menu-section">
		        <h1 class="d-none">메뉴목록</h1>
		        <div class="menu-list">
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <a href="detail"><img class="menu-img" src="/image/product/12.png"></a>
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		            <section class="menu">
		                <form class="">
		                    <h1>알랜드 커피</h1> 
		                    <div class="menu-img-box">
		                        <img class="menu-img" src="/image/product/12.png">
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		        </div>
		    </section>

			<div class="d-flex justify-content-center py-3">
				<a href="" class="btn btn-round w-100 w-50-md py-2">더보기(13+)</a>
			</div>

			<section class="new-menu menu-section-p">
				<h1 class="d-none">신메뉴 목록</h1>
				<!-- <ul>
                    <li>
                    </li>
                </ul>  -->
				<div class="list">
					<span>신규로 출시된 메뉴가 없습니다.</span>
				</div>
			</section>

		</section>
	</main> 

</html>




4) SpringBoot에서 mariaDB의 VIEW 테이블 이용하기

a. VIEW 테이블을 이용하기 위한 Entity 설계


Menu.java
package kr.co.rland.web.entity;

import java.util.Date;

public class Menu {
	private Long id;
	private String name;
	private Integer price;
	private String img;
	private Date regDate;
	private Integer categoryId;
	private Long regMemberId;

	public Menu() {
		// TODO Auto-generated constructor stub
	}

	public Menu(Long id, String name, Integer price, String img, Date regDate, Integer categoryId, Long regMemberId) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
		this.img = img;
		this.regDate = regDate;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}

	public Menu(String name, Integer price, String img, Integer categoryId, Long regMemberId) {
		super();
		this.name = name;
		this.price = price;
		this.img = img;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getPrice() {
		return price;
	}

	public void setPrice(Integer price) {
		this.price = price;
	}

	public String getImg() {
		return img;
	}

	public void setImg(String img) {
		this.img = img;
	}

	public Date getRegDate() {
		return regDate;
	}

	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}

	public Integer getCategoryId() {
		return categoryId;
	}

	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}

	public Long getRegMemberId() {
		return regMemberId;
	}

	public void setRegMemberId(Long regMemberId) {
		this.regMemberId = regMemberId;
	}

	@Override
	public String toString() {
		return "Menu [id=" + id + ", name=" + name + ", price=" + price + ", img=" + img + ", regDate=" + regDate
				+ ", categoryId=" + categoryId + ", regMemberId=" + regMemberId + "]";
	}

}


MenuView.java
package kr.co.rland.web.entity;

import java.util.Date;

public class MenuView extends Menu{
	
	private String categoryName;
	
	 public MenuView() {
		// TODO Auto-generated constructor stub
	}

	public MenuView(Long id, String name, Integer price, String img, Date regDate, Integer categoryId,
			Long regMemberId) {
		super(id, name, price, img, regDate, categoryId, regMemberId);
		
		this.categoryName = categoryName;
	}
	
	public MenuView(String name, Integer price, String img, Integer categoryId,
			Long regMemberId ) {
		super(name, price, img, categoryId, regMemberId);
		
		this.categoryName = categoryName;
	}
	

	public String getCategoryName() {
		return categoryName;
	}

	public void setCategoryName(String categoryName) {
		this.categoryName = categoryName;
	}

	@Override
	public String toString() {
		return "MenuView [categoryName=" + categoryName + "]";
	}
	
	
}




b. mariaDB SQL문

create view menu_view
as
select m.*, c. name category_name
from menu m left join category c on m.category_id = c.id;


c. service 업무 로직


MenuService.java
package kr.co.rland.web.service;

import java.util.List;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;

public interface MenuService {
	
	// 서비스 계층에서는 사용자 요청을 이름 그대로 그대로 만들어라!!
	void pointUp();
	
	List<Menu> getList();	
	List<Menu> getList(int page);
	List<Menu> getList(int page, String query);
	List<Menu> getList(int page, int categoryId);
	List<Menu> getList(int page, int categoryId, String query);
	
	List<MenuView> getViewList();
	List<MenuView> getViewList(int page);
	List<MenuView> getViewList(int page, String query);
	List<MenuView> getViewList(int page, int categoryId);
	List<MenuView> getViewList(int page, int categoryId, String query);
	
}


DefaultMenuService.java


package kr.co.rland.web.service;

import java.util.List;

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

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;
import kr.co.rland.web.repository.MenuRepository;

@Service
public class DefaultMenuService implements MenuService {
	
	@Autowired
	private MenuRepository repository;

	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<Menu> getList() {
		
		// return repository.findAll(0,10,"",1,3000,"regDate","desc");
		return repository.findAll(0,10,"",null, null, null, null);
	}


	@Override
	public List<Menu> getList(int page) {
		
		int size=10;
		List<Menu> list = repository.findAll(page, size, null, null, null, null, null);
		
		return list;
	}

	@Override
	public List<Menu> getList(int page, String query) {
		int size=10;
		List<Menu> list = repository.findAll(page, size, query, null, null, null, null);
		
		return list;
	}

	@Override
	public List<Menu> getList(int page, int categoryId) {
		int size=10;
		List<Menu> list = repository.findAll(page, size, null, categoryId, null, null, null);
		
		return list;
	}

	@Override
	public List<Menu> getList(int page, int categoryId, String query) {
		int size=10;
		List<Menu> list = repository.findAll(page, size, query, categoryId, null, null, null);
		
		return list;
	}

	@Override
	public List<MenuView> getViewList() {
		int page=1;
		int size=10;
		List<MenuView> list = repository.findViewAll(page, size, null, null, null, null, null);

		return list;
	}

	@Override
	public List<MenuView> getViewList(int page) {
		int size=10;
		List<MenuView> list = repository.findViewAll(page, size, null, null, null, null, null);
		
		return list;
	}

	@Override
	public List<MenuView> getViewList(int page, String query) {
		int size=10;
		List<MenuView> list = repository.findViewAll(page, size, query, null, null, null, null);
		
		return list;
	}

	@Override
	public List<MenuView> getViewList(int page, int categoryId) {
		int size=10;
		List<MenuView> list = repository.findViewAll(page, size, null, categoryId, null, null, null);

		return list;
	}

	@Override
	public List<MenuView> getViewList(int page, int categoryId, String query) {
		int size=10;
		List<MenuView> list = repository.findViewAll(page, size, query, categoryId, null, null, null);
		
		return list;
	}
	
	@Transactional
	@Override
	public void pointUp() {
		Menu menu = new Menu();
		menu.setId(917L);
		menu.setPrice(7777);
		repository.update(menu);
		
		menu.setId(917L);
		menu.setPrice(999999);
		repository.update(menu);
	}
}



c. Mapper.xml 수정(SQL문)

  • Mapper.xml에서 SQL문 쓸 때, DB의 칼럼명과 일치하는지 확인하기!
    • 그래서, categoty_id 칼럼명 주의!!


MenuRepositoryMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
  	  <result property="categoryId" column="category_id"/>
  	  <result property="regMemberId" column="reg_member_id"/>
	</resultMap>
	<resultMap id="menuViewResultMap" type="MenuView">
	  <result property="regDate" column="reg_date"/>
  	  <result property="categoryId" column="category_id"/>
   	  <result property="categoryName" column="category_name"/>
  	  <result property="regMemberId" column="reg_member_id"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		select * 
		from menu
		<trim prefix="WHERE" prefixOverrides="AND | OR">
			<if test="query != null">
				name like '%${query}%' 
			</if>
			<if test="price != null">
		    	and price > #{price} 
		    </if>
		    <if test="categoryId != null">
		    	and category_id=#{categoryId}
		    </if>
		</trim>
	    <if test="orderField != null">
    		order by ${orderField} ${orderDir}
	    </if>
	    <if test="size != null">
    		limit #{size} offset #{offset};
	    </if>
		
	</select>
	
	<select id="findViewAll" resultMap="menuViewResultMap">
		select * 
		from menu_view
		<trim prefix="WHERE" prefixOverrides="AND | OR">
			<if test="query != null">
				name like '%${query}%' 
			</if>
			<if test="price != null">
		    	and price > #{price} 
		    </if>
		    <if test="categoryId != null">
		    	and category_id=#{categoryId}
		    </if>
		</trim>
	    <if test="orderField != null">
    		order by ${orderField} ${orderDir}
	    </if>
	    <if test="size != null">
    		limit #{size} offset #{offset};
	    </if>
		
	</select>
	
	<select id="findById" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>
	
	<select id="count" resultType="Integer">
		select count(id) count from menu
		<trim prefix="WHERE" prefixOverrides="AND | OR">
			<if test="query != null">
				name like '%${query}%' 
			</if>
			<if test="price != null">
		    	and price > #{price} 
		    </if>
		    <if test="categoryId != null">
		    	and categoryId=#{categoryId}
		    </if>
		</trim>
	</select>
	
	<insert id="insert" parameterType="Menu">
		insert into menu(name, price, img, category_id, reg_member_id)
		values(${name},${price},${img},${categoryId},${regMemberId})
	</insert>
	
	<update id="update">
		update menu
		<trim prefix="SET" suffixOverrides=",">
			<if test="name != null">name=#{name},</if>
			<if test="price != null">price=#{price},</if>
			<if test="img != null">img=#{img}</if>
		</trim>
		where id=#{id}
	</update>
	
 	<delete id="delete">
 		delete from menu where id=${id}
	</delete> 
</mapper>


MenuRepository.java

package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
//	List<Menu> findAll();
//	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	List<MenuView> findViewAll(Integer offset,
			Integer size, 
			String query,
			Integer categoryId,
			Integer price,
			String orderField,
			String orderDir
			);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	// 마지막으로 필요한 count 함수로 마지막 페이지를 구분할 수 있다!!
	int count(
			String query,
			Integer categoryId,
			Integer price);
	
	// 원래는 Menu가 반환 타입이다.
	// 확인을 위해 int형으로 반환 했다.
	int insert(Menu menu);
	
	int update(Menu menu);

	// 원래 코드!!
	// 원래는 Menu가 반환 타입이다.
	Menu update2(Menu menu);

	Menu insert2(Menu menu);
	
	void delete(long id);
	
}



d. Test 코드

  • List<MenuView> list = service.getViewList(1, 1); 보다는 더 많은 인자를 넣어주어서 VIEW 테이블의 모든 칼럼에 대해서 테스트 해보자!


DefaultMenuServiceTest.java
package kr.co.rland.web.service;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import kr.co.rland.web.entity.MenuView;

@SpringBootTest
@AutoConfigureMybatis
class DefaultMenuServiceTest {

	@Autowired
	private MenuService service;
	
	@Test
	void test() {
//		service.pointUp();
		List<MenuView> list = service.getViewList(1, 1);
		System.out.println(list);
		System.out.println("작업 완료");
	}
}




5) DB에서 데이터를 view 단으로 넘겨주기!

  • DB에서 데이터를 View 단으로 넘기기 위해서는 Model 객체를 이용하는데 타임리프에서는 Model을 받기 위해서 특별한 조치가 필요하다.


  • <h1><span th:text=${menu.name}>알랜드 커피/</span><span style="font-size:11px;" th:text="${menu.categoryName}">(커피음료)</span></h1>
    • 위의 코드처럼 타임리프는 html 태그의 속성으로 대체되기 때문에 꼭 태그로 묶어서 사용해야 한다.


  • 타임리프에서 태그의 속성으로 묶지 않고 바로 사용하는 방법 :
    • [[${data}]]를 이용하기


  • 실습 코드 :


MenuController.java


package kr.co.rland.web.controller;

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 kr.co.rland.web.entity.MenuView;
import kr.co.rland.web.service.MenuService;

// FrontController(POJO 클래스)를 만드는 방법! 
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
@RequestMapping("/menu")
public class MenuController {
	
	@Autowired
	private MenuService service;
	
	// 함수 이름은 보통 url이 된다.
	@RequestMapping("list")
	public String list(Model model) {
		
		List<MenuView> list = service.getViewList();
		
		model.addAttribute("list", list);
		
		// model.addAttribute("data","hello");
//		service.getList();			// 1, query:"",
//		service.getList(1);
//		service.getList(1, "아");
//		service.getList(1, 1 /* category */ );		/* category 있는 애들만!!*/
//		service.getList(1, 1 /* category */, "아");
		
		
		
		// 상대경로이면 타임리프가 알아서 찾아준다. 
		return "menu/list";
	}
	
	// @RequestMapping에서 detail을 "/detail" 이나 "detail"를 사용하면 된다! 상관없더라
	@RequestMapping("detail")
	public String detail() {
		return "menu/detail";
	}

}


list.html
<!DOCTYPE html>
<html
	 xmlns=th="http://www.thymeleaf.org"
	 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	 layout:decorate="inc/layout"
>	

	
	<!-- 타임리프는 속성으로 쓰기 때문에 속성으로 쓰기 위해서 div 태그에 사용한다. -->
	<!-- <div th:replace=""></div> -->
	
	<!-- replace는 대체, insert는 div 태그 모양을 유지한다. -->
	
	<main layout:fragment="main">
		<section>
			<header class="search-header">
				<h1 class="text-title1-h1" th:text="${data}">알랜드 메뉴</h1>
				<form>
					<input type="text">
					<input type="submit" class="icon icon-find">
				</form>
			</header>
			<aside class="aside-bar">
			
				<h1>aside</h1>
				<section class="aside-bar-content">
					<h1>메인메뉴</h1>
					<ul class="mt-3">
						<li><a href="">카페메뉴</a></li>
						<li><a href="">공지사항</a></li>
						<li><a href="/user/login.html">로그인</a></li>
					</ul>
				</section>
			</aside>
			<nav class="menu-category">
				<div>
					<h1 class="text-normal-bold">메뉴분류</h1>
				</div>
				<ul>
					<li class="menu-selected">
						<a href="/menu/list">전체</a>
					</li>
					<li>
						<a href="">커피음료</a>
					</li>
					<li>
						<a href="">수제청</a>
					</li>
					<li>
						<a href="">샌드위치</a>
					</li>
					<li>
						<a href="">쿠키</a>
					</li>
				</ul>
			</nav>

			<section class="cart-section">
				<h1 class="d-none">장바구니</h1>
				<span class="text-title3">커피음료</span>
				<div class="icon icon-basket icon-text">1</div>
			</section>

			<section class="menu-section">
		        <h1 class="d-none">메뉴목록</h1>
		        <div class="menu-list">
		        
		            <section class="menu" th:each="menu: ${list}">
		                <form class="">
		                    <h1><span th:text=${menu.name}>알랜드 커피/</span><span style="font-size:11px;" th:text="${menu.categoryName}">(커피음료)</span></h1>
		                    <div class="menu-img-box">
		                        <a href="detail"><img class="menu-img" src="/image/product/12.png"></a>
		                    </div>    
		                    <div class="menu-price">4500 원</div>
		                    <div class="menu-option-list">
		                        <span class="menu-option">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>ICED</label>
		                        </span>            
		                        <span class="menu-option ml-2">
		                            <input class="menu-option-input" type="checkbox">
		                            <label>Large</label>
		                        </span>
		                    </div>
		                    <div class="menu-button-list">
		                        <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
		                        <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
		                    </div>
		                </form>
		            </section>
		        </div>
		    </section>

			<div class="d-flex justify-content-center py-3">
				<a href="" class="btn btn-round w-100 w-50-md py-2">더보기(13+)</a>
			</div>

			<section class="new-menu menu-section-p">
				<h1 class="d-none">신메뉴 목록</h1>
				<!-- <ul>
                    <li>
                    </li>
                </ul>  -->
				<div class="list">
					<span>신규로 출시된 메뉴가 없습니다.</span>
				</div>
			</section>

		</section>
	</main> 

</html>