동적쿼리(Dynamic Query)

JOY🌱·2023년 1월 27일
0

🐦 MyBatis

목록 보기
3/4
post-thumbnail

💁‍♀️ 동적쿼리(Dynamic Query)란,
Parameter 값에 의해 쿼리를 동적으로 변환 해주는 기능


📌 초기 Setting

  • lib 폴더에 ojdbc8.jar / mybatis-3.5.11.jar 파일 삽입
  • Classpath에 ojdbc8.jar / mybatis-3.5.11.jar 파일 추가

📌 Ref. mybatis.jar 파일 다운로드
mybatis-3.5.11.jar

◼ connection-info.properties

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:xe
username=C##GREEDY
password=GREEDY

◼ mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <!--connection-info.properties 불러오기 -->
  <!--아래 value에 직접 입력하지않고 ${}를 이용하여 작성(보안을 위해 분리하여 사용)-->
  <properties resource="connection-info.properties"/>
  
  <!-- 별칭 설정 -->
  <!-- type="클래스경로" alias="별칭"-->
  <typeAliases>
    <typeAlias type="com.greedy.common.SearchCriteria" alias="SerchCriteria"/> 
  </typeAliases>

  <environments default="dev">
    <environment id="dev">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  
  <mappers>
 		// package의 명칭을 작성하여 해당 패키지 하위에 있는 모든 파일을 mapper로 사용
	  <package name="com.greedy.section01.xml"/> 
      	// SelectBuilderMapper인터페이스를 사용하기 위해 section02 패키지도 mapper로 등록
	  <package name="com.greedy.section02.provider"/> 
  </mappers>
</configuration>

◼ Template

public class Template {

	private static SqlSessionFactory sqlSessionFactory; // 정적 메모리에 SqlSessionFactory 생성 (static)
	
	public static SqlSession getSqlSession() {
		
		if(sqlSessionFactory == null) { // SqlSessionFactor가 null일 때만 생성 (싱글톤패턴)
			String resource = "mybatis-config.xml";
			
			try {
				InputStream inputStream = Resources.getResourceAsStream(resource);
				sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		return sqlSessionFactory.openSession(false);
	}
}

◼ MenuDTO

/* 필드 */
private int menuCode;
private String menuName;
private int menuPrice;
private int categoryCode;
private String orderableStatus;

/* 기본 생성자, 모든 매개변수가 있는 생성자 */

/* getter & setter */

/* toString */

◼ SearchCriteria

💁‍ 검색의 기준이 되는 클래스 (Criteria : 기준)

/* 필드 */
private String condition; 	>>> 검색 조건
private String value; 		>>> 검색하려는 단어

/* 기본 생성자, 모든 매개변수가 있는 생성자 */

/* getter & setter */

/* toString */

👀 XML

🙋‍ 잠깐 ! Mapper.java와 Mapper.xml이 동일한 패키지명을 갖고 있는지 확인 !

  • com.greedy.section01.xml

💁‍ 나열된 클래스 및 파일 순서가 곧 작성 순서이므로, 흐름 잘 파악하기 🔥중요🔥
(자세한 작성 순서는 [ ] 안에 있는 설명을 참고할 것)

👉 Application

public class Application {

	public static void main(String[] args) {
		
		Scanner sc = new Scanner(System.in);
		
		do {
			System.out.println("===== Mybatis 동적 SQL =====");
			System.out.println("1. if 확인하기");
			System.out.println("2. choose(when, otherwise) 확인하기");
			System.out.println("3. foreach 확인하기");
			System.out.println("4. tream(where, set) 확인하기");
			System.out.println("9. 종료하기");
			System.out.print("메뉴 선택 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : ifSubMenu(); break;
			case 2 : chooseSubMenu(); break;
			case 3 : foreachSubMenu(); break;
			case 4 : trimSubMenu(); break;
			case 9 : System.out.println("프로그램을 종료합니다."); return;
			}
			
		} while(true);
	}

[1] if 확인하기

	private static void ifSubMenu() { // 외부에서 사용하지 않고 반환값이 없으므로, private static void
		
		Scanner sc = new Scanner(System.in);
		MenuService menuService = new MenuService(); // [MenuService 클래스 생성]
		
		do {
			System.out.println("===== if 서브 메뉴 =====");
			System.out.println("1. 원하는 금액대에 적합한 추천 메뉴 목록 보여주기");
			System.out.println("2. 메뉴 이름 혹은 카테고리명으로 검색하여 메뉴 목록 보여주기");
			System.out.println("9. 이전 메뉴로");
			System.out.print("메뉴 선택 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : menuService.selectMenuByPrice(inputPrice()); break; // [여기에 inputPrice() 생성 및 MenuService에 selectMenuByPrice() 생성]
			case 2 : menuService.searchMenu(inputSearchCriteria()); break; // [여기에 inputSearchCriteria() 생성 및 MenuService에 searchMenu() 생성]
			case 9 : return;
			}
			
		} while(true);
		
	}
◼ [1-1] 원하는 금액대에 적합한 추천 메뉴 목록 표시
	private static int inputPrice() {
	
		Scanner sc = new Scanner(System.in);
		System.out.print("검색하실 가격의 최대 금액을 입력해주세요 : ");
		
		return sc.nextInt(); // MenuService의 selectMenuByPrice()의 인자로 전달될 것
	}
◼ [1-2] 메뉴 이름 혹은 카테고리명으로 검색하여 메뉴 목록 표시
	private static SearchCriteria inputSearchCriteria() { // [SearchCriteria 클래스 생성]
		
		Scanner sc = new Scanner(System.in);
		System.out.print("검색 기준을 입력해주세요 (name or category) : ");
		String condition = sc.nextLine();
		System.out.print("검색어를 입력해주세요 : ");
		String value = sc.nextLine();
		
		return new SearchCriteria(condition, value);
	}

[2] choose(when, otherwise) 확인하기

	private static void chooseSubMenu() {
		
		Scanner sc = new Scanner(System.in);
		MenuService menuService = new MenuService();
		
		do {
			System.out.println("===== choose 서브 메뉴 =====");
			System.out.println("1. 카테고리 상위 분류별 메뉴 보여주기(식사, 음료, 디저트)");
			System.out.println("9. 이전 메뉴로");
			System.out.print("메뉴 선택 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : menuService.searchMenuBySupCategory(inputSupCategory()); break; // [여기에 inputSupCategory() 생성 및 MenuService에 searchMenuBySupCategory() 생성]
			case 9 : return;
			}
			
		} while(true);
		
	}
◼ [2-1] 카테고리 상위 분류별 메뉴 표시
	private static SearchCriteria inputSupCategory() {

		Scanner sc = new Scanner(System.in);
		System.out.print("상위 분류를 입력해주세요(식사, 음료, 디저트) : ");
		String value = sc.nextLine();
		
		return new SearchCriteria("category", value);
	}

[3] foreach 확인하기

	private static void foreachSubMenu() {

		Scanner sc = new Scanner(System.in);
		MenuService menuService = new MenuService();
		
		do {
			System.out.println("===== foreach 서브 메뉴 =====");
			System.out.println("1. 랜덤한 메뉴 5개를 추출해서 조회하기");
			System.out.println("9. 이전 메뉴로");
			System.out.print("메뉴 선택 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : menuService.searchMenuByRandomMenuCode(createRandomMenuCodeList()); break;
			case 9 : return;
			}
			
		} while(true);
		
	}
◼ [3-1] 랜덤한 메뉴 5개를 추출해서 조회
	private static List<Integer> createRandomMenuCodeList() {
		
		Set<Integer> set = new HashSet<>(); // set은 중복값을 저장하지않는다는 특징을 가지고 있으므로 알아서 걸러줌
		while(set.size() < 5) { // 5개가 담기는 순간 while문 종료
			/* 현재 메뉴 번호는 1~21번 사이*/
			int temp = ((int) (Math.random() * 21)) + 1;
			set.add(temp);
		}
		
		/* Set과 List는 컬렉션의 후손이므로 변환 필요없이 바로 전달 가능 */ 
		List<Integer> list = new ArrayList<>(set); 
		
		return list;
	}

[4] tream(where, set) 확인하기

	private static void trimSubMenu() {
		
		Scanner sc = new Scanner(System.in);
		MenuService menuService = new MenuService();
		
		do {
			System.out.println("===== tream(where, set) 서브 메뉴 =====");
			System.out.println("1. 메뉴 혹은 카테고리 코드로 검색, 단 메뉴와 카테고리 둘 다 일치하는 경우도 검색하며, 검색 조건이 없는 경우 전체 검색");
			System.out.println("2. 원하는 메뉴 정보만 수정하기");
			System.out.println("9. 이전 메뉴로");
			System.out.print("메뉴 선택 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : menuService.searchMenuByNameOrCategory(inputSearchCriteriaMap()); break;
			case 2 : menuService.modifyMenu(inputChangeInfo()); break;
			case 9 : return;
			}
			
		} while(true);
		
	}
◼ [4-1] 메뉴 혹은 카테고리 코드로 검색
	private static Map<String, Object> inputSearchCriteriaMap() {

		Scanner sc = new Scanner(System.in);
		System.out.print("검색할 조건을 입력하세요(category or name or both or null) : ");
		String condition = sc.nextLine();
		
		Map<String, Object> criteria = new HashMap<>();
		
		if("category".equals(condition)) {
			
			System.out.print("검색할 카테고리 코드 입력 : ");
			int categoryValue = sc.nextInt();
			
			criteria.put("categoryValue", categoryValue);
			
		} else if("name".equals(condition)) {
			
			System.out.print("검색할 메뉴 이름 입력 : ");
			String nameValue = sc.nextLine();
			
			criteria.put("nameValue", nameValue);
			
		} else if("both".equals(condition)) {
			
			System.out.print("검색할 카테고리 코드 입력 : ");
			int categoryValue = sc.nextInt();
			sc.nextLine();
			System.out.print("검색할 메뉴 이름 입력 : ");
			String nameValue = sc.nextLine();
			
			criteria.put("categoryValue", categoryValue);
			criteria.put("nameValue", nameValue);
		}
		
		return criteria;
	}
◼ [4-2] 원하는 메뉴 정보만 수정
	private static Map<String, Object> inputChangeInfo() {
		
		Scanner sc = new Scanner(System.in);
		
		System.out.print("변경할 메뉴 코드 입력 : ");
		int code = sc.nextInt();
		System.out.print("변경할 메뉴 이름 입력 : ");
		sc.nextLine();
		String name = sc.nextLine();
		System.out.print("변경할 카테고리 코드 입력 : ");
		int categoryCode = sc.nextInt();
		System.out.print("판매 여부 결정(Y/N) : ");
		sc.nextLine();
		String orderableStatus = sc.nextLine();
		
		Map<String, Object> criteria = new HashMap<>();
		criteria.put("code", code);
		criteria.put("name", name);
		criteria.put("categoryCode", categoryCode);
		criteria.put("orderableStatus", orderableStatus);
		
		return criteria;
	}
}

👉 MenuService

public class MenuService {

	/* 직접 DynamicSqlMapper인터페이스를 생성하여 필드에 작성 */
	private DynamicSqlMapper mapper;
◼ [1-1] 원하는 금액대에 적합한 추천 메뉴 목록 표시
	public void selectMenuByPrice(int price) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		/* 기본 자료형으로는 조건문의 값을 비교하기 어렵다. Map의 key 혹은 DTO의 getter를 시용해서 값을 확인한다. */
		Map<String, Integer> map = new HashMap<>();
		map.put("price", price);
		
		/* price(기본 자료형)가 아닌 map 전달 */
		List<MenuDTO> menuList = mapper.selectMenuByPrice(map); // [MenuDTO생성 및 DynamicSqlMapper인터페이스에 selectMenuByPrice() 생성]
		
		/* Result View */
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
	
		/* close */
		sqls.close();
	}
◼ [1-2] 메뉴 이름 혹은 카테고리명으로 검색하여 메뉴 목록 표시
	public void searchMenu(SearchCriteria searchCriteria) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		List<MenuDTO> menuList = mapper.searchMenu(searchCriteria);
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
	
		sqls.close();
	}
◼ [2-1] 카테고리 상위 분류별 메뉴 표시
	public void searchMenuBySupCategory(SearchCriteria searchCriteria) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		List<MenuDTO> menuList = mapper.searchMenuBySupCategory(searchCriteria);
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
	
		sqls.close();
		
	}
◼ [3-1] 랜덤한 메뉴 5개를 추출해서 조회
	public void searchMenuByRandomMenuCode(List<Integer> randomMenuCodeList) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		/* Map에 집어넣기 */
		Map<String, List<Integer>> criteria = new HashMap<>();
		criteria.put("randomMenuCodeList", randomMenuCodeList);
		
		List<MenuDTO> menuList = mapper.searchMenuByRandomMenuCode(criteria);
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
	
		sqls.close();
			
	}
◼ [4-1] 메뉴 혹은 카테고리 코드로 검색
	public void searchMenuByNameOrCategory(Map<String, Object> searchCriteria) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		List<MenuDTO> menuList = mapper.searchMenuByNameOrCategory(searchCriteria);
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
	
		sqls.close();
		
	}
◼ [4-2] 원하는 메뉴 정보만 수정
	public void modifyMenu(Map<String, Object> changeInfo) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(DynamicSqlMapper.class);
		
		int result = mapper.modifyMenu(changeInfo);
		
		/* INSERT이므로 트랜잭션 관리 */
		if(result > 0) {
			sqls.commit();
			System.out.println("메뉴 정보 변경에 성공하였습니다.");
		} else {
			sqls.rollback();
			System.out.println("메뉴 정보 변경에 실패하였습니다.");
		}
		
		sqls.close();
	}
}

👉 DynamicSqlMapper

public interface DynamicSqlMapper {

	/* [1-1] 원하는 금액대에 적합한 추천 메뉴 목록 표시 */
	List<MenuDTO> selectMenuByPrice(Map<String, Integer> map);

	/* [1-2] 메뉴 이름 혹은 카테고리명으로 검색하여 메뉴 목록 표시 */
	List<MenuDTO> searchMenu(SearchCriteria searchCriteria);

	/* [2-1] 카테고리 상위 분류별 메뉴 표시 */
	List<MenuDTO> searchMenuBySupCategory(SearchCriteria searchCriteria);

	/* [3-1] 랜덤한 메뉴 5개를 추출해서 조회 */
	List<MenuDTO> searchMenuByRandomMenuCode(Map<String, List<Integer>> criteria);

	/* [4-1] 메뉴 혹은 카테고리 코드로 검색 */
	List<MenuDTO> searchMenuByNameOrCategory(Map<String, Object> searchCriteria);

	/* [4-2] 원하는 메뉴 정보만 수정 */
	int modifyMenu(Map<String, Object> changeInfo);
}

👉 DynamicSqlMapper.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를 DAO 인터페이스와 동일한 패키지에 두고 DAO 인터페이스의 풀네임을 namespace로 지정한다.
  		또한 매퍼 인터페이스와 XML의 이름이 동일해야한다.
  		작성한 메소드의 이름과 id가 일치하며 리턴 타입이 일치하는 쿼리문을 모두 작성해야 한다. 
  -->
<mapper namespace="com.greedy.section01.xml.DynamicSqlMapper">

	<!-- 조회한 컬럼과 DTO를 매핑(연결)시키기 위한 설정으로 resultMap을 선언한다. -->
	<resultMap type="com.greedy.common.MenuDTO" id="menuResultMap">
		<id property="menuCode" column="MENU_CODE"/> <!-- property에는 DTO의 필드 명칭을 넣는 것 --> <!-- 사이에 입력할 값이 없으면 그냥 /로 닫기 가능 -->
		<result property="menuName" column="MENU_NAME"/> <!-- result에는 id에 입력하지않은 나머지 모두 입력 -->
		<result property="menuPrice" column="MENU_PRICE"/>
		<result property="categoryCode" column="CATEGORY_CODE"/>
		<result property="orderableStatus" column="ORDERABLE_STATUS"/>
	</resultMap>
◼ [1-1] 원하는 금액대에 적합한 추천 메뉴 목록 표시

<select> <if> <!-- <![CDATA[]]>

  <select id="selectMenuByPrice" parameterType="hashmap" resultMap="menuResultMap"> <!-- parameterType은 Mybatis api참고 -->
	SELECT
    		MENU_CODE
    	,	MENU_NAME
    	,	MENU_PRICE
    	,	CATEGORY_CODE
    	,	ORDERABLE_STATUS
    	FROM TBL_MENU
    	WHERE ORDERABLE_STATUS = 'Y'
    	<if test="price gte 0 and price lte 10000"> <!-- gte_greater than equal(>=), lte_less than equal(<=) -->
    	<![CDATA[ 
    	AND MENU_PRICE < #{ price } 
    	]]> <!-- <![CDATA[]]>로 감싸면 이스케이프 ('<'등을 인식 못 할 때 연산자로 사용하기 위함) -->
    	</if> 
    	<if test="price gt 10000 and price lte 20000"> <!-- if로 감싼 것은 그 조건일 때만 WHERE/AND가 실행됨 -->
    	AND MENU_PRICE BETWEEN 10000 AND #{ price }
    	</if>
    	<if test="price gt 20000 and price lte 30000">
    	AND MENU_PRICE BETWEEN 20000 AND #{ price }
    	</if>
    	<if test="price gt 30000">
    	AND MENU_PRICE BETWEEN 30000 AND #{ price }
    	</if>
    	ORDER BY MENU_CODE
  </select>
◼ [1-2] 메뉴 이름 혹은 카테고리명으로 검색하여 메뉴 목록 표시

<select> <if>

  <select id="searchMenu" parameterType="SerchCriteria" resultMap="menuResultMap"> <!--  등록해둔 별칭으로 parameterType 설정 -->
  	SELECT
    		MENU_CODE
    	,	MENU_NAME
    	,	MENU_PRICE
    	,	CATEGORY_CODE
    	,	ORDERABLE_STATUS
    	FROM TBL_MENU
    	<if test="condition == 'category'"> <!-- : condition이 category일 때만 JOIN 동작 -->
    	JOIN TBL_CATEGORY USING (CATEGORY_CODE)
    	</if>
    	WHERE ORDERABLE_STATUS = 'Y'
    	<if test="condition == 'name'"> <!-- : 검색조건이 name일 경우, -->
    	AND MENU_NAME LIKE '%' || #{ value } || '%'
    	</if>
    	<if test="condition == 'category'"> <!-- : 검색조건이 category일 경우, -->
    	AND CATEGORY_NAME = #{ value }
    	</if>
    	ORDER BY MENU_CODE
  </select>
◼ [2-1] 카테고리 상위 분류별 메뉴 표시

<select> <choose> <when> <otherwise>

  <select id="searchMenuBySupCategory" parameterType="SerchCriteria" resultMap="menuResultMap">
  SELECT
    		MENU_CODE
    	,	MENU_NAME
    	,	MENU_PRICE
    	,	CATEGORY_CODE
    	,	ORDERABLE_STATUS
    	FROM TBL_MENU
    	WHERE ORDERABLE_STATUS = 'Y'
    	<choose>
    		<when test="value == '식사'">
    		AND CATEGORY_CODE IN (4, 5, 6, 7)
    		</when>
    		<when test="value == '음료'">
    		AND CATEGORY_CODE IN (8, 9, 10)
    		</when>
    		<otherwise>
    		AND CATEGORY_CODE IN (11, 12) <!-- 나머지인 '디저트'는 otherwise로 처리 -->
    		</otherwise>
    	</choose>
  		ORDER BY MENU_CODE
  </select>
◼ [3-1] 랜덤한 메뉴 5개를 추출해서 조회

<select> <foreach>

  <select id="searchMenuByRandomMenuCode" parameterType="hashmap" resultMap="menuResultMap">
  SELECT
    		MENU_CODE
    	,	MENU_NAME
    	,	MENU_PRICE
    	,	CATEGORY_CODE
    	,	ORDERABLE_STATUS
    	FROM TBL_MENU
    	WHERE ORDERABLE_STATUS = 'Y'
    	AND MENU_CODE IN
    	<foreach collection="randomMenuCodeList" item="menuCode" open="(" separator="," close=")"> 
    	<!-- : collection="randomMenuCodeList에 있는 List<Integer>를 사용할 것", item="변수명", open="시작할 때 필요한 문자", separator="분리기준", close="끝날 때 필요한 문자" -->
    		#{ menuCode }
    	</foreach>
    	<!-- foreach(반복문)를 사용하여 List 출력 -->
  </select>
◼ [4-1] 메뉴 혹은 카테고리 코드로 검색

<select> <where> <if> <trim>

  <select id="searchMenuByNameOrCategory" parameterType="hashmap" resultMap="menuResultMap">
  SELECT
    		MENU_CODE
    	,	MENU_NAME
    	,	MENU_PRICE
    	,	CATEGORY_CODE
    	,	ORDERABLE_STATUS
    	FROM TBL_MENU
 
    	<!-- WHERE절이 실행되지않으면 아래의 AND절이 문제 발생, 그렇다고 WHERE을 제거할 시, AND절만 실행할 때 문제 발생 -->
<!--  	<if test="nameValue != null">
    	WHERE MENU_NAME LIKE '%' || #{ nameValue } || '%'
    	</if>
    	<if test="categoryValue != null">
    	AND CATEGORY_CODE = #{ categoryValue }
    	</if>
-->
 
    	<!-- 해결 방법 [1] <where> 태그를 사용한다.
    		where 태그는 앞에 where 구문이 없으면 자동으로 구문을 붙여준다.
    		또한 where 내부에 모두 쿼리문이 추가되지 않는 상황인 경우에는 where을 무시한다.
    		and나 or로 시작하게 되면 자동으로 해당 단어를 지워준다.  
    	-->
<!--    아래의 <trim>을 실행시키기 위해 잠시 주석 	
		<where>
	    	<if test="nameValue != null">
	    	MENU_NAME LIKE '%' || #{ nameValue } || '%'
	    	</if>
	    	<if test="categoryValue != null">
	    	AND CATEGORY_CODE = #{ categoryValue }
	    	</if>
    	</where>
 -->
    	<!-- 해결 방법 [2] <trim> 태그를 사용한다.
    		조회 조건에 and 혹은 or로 시작하지 않는 다른 문자로 시작하는 경우
    		where 엘리먼트가 기본적으로 처리하는 기능에 추가 규칙을 정의하기 위해 trim 엘리먼트를 제공한다.
    	-->
    	<trim prefix="WHERE" prefixOverrides="AND | OR">
    		<if test="nameValue != null">
	    	MENU_NAME LIKE '%' || #{ nameValue } || '%'
	    	</if>
	    	<if test="categoryValue != null">
	    	AND CATEGORY_CODE = #{ categoryValue }
	    	</if>
    	</trim>
  </select>

📌 Ref.

* prefix : 처리 후 엘리먼트의 내용이 있으면 가장 앞에 붙여주는 내용을 기술한다.
* prefixOverrides : 처리 후 엘리먼트의 내용 중 가장 앞에 해당하는 문자들이 있다면 자동으로 지워준다.
* suffix : 처리 후 엘리먼트 내에 내용이 있으면 가장 뒤에 붙여준다.
* suffixOverrides : 처리 후 엘리먼트 내용 중 가장 뒤에 해당 문자들이 있다면 자동으로 지워준다.
◼ [4-2] 원하는 메뉴 정보만 수정

<update> <if> <set> <trim>

  <update id="modifyMenu" parameterType="hashmap"> <!-- DML 구문은 resultMap 작성 필요 X (이미 int로 결과값) -->
<!-- 
	UPDATE
  		TBL_MENU  
  	<if test="name != null and name !=''">
	 SET MENU_NAME = #{ name }
  	</if>
  	<if test='categoeyCode != null and categoryCode gt 0'>
  	,	CATEGORY_CODE = #{ categoryCode }
  	</if>
	<if test="orderableStatus != null and orderableStatus !=''">
	,	ORDERABLE_STATUS = #{ orderableStatus }
	</if>
  	WHERE MENU_CODE = #{ code }	
-->
  	<!-- 하지만, SET절의 일부만 실행될 시 SET또는 콤마(,)로 인해 문제 발생 -->
  	<!-- 해결 방법 [1] <set> 태그 사용
  		<set> 엘리먼트 내부에 내용이 있을 시 set 구문을 자동으로 추가해준다.
  		앞 또는 뒤에 붙은 콤마를 제거해준다. -->
  		
<!--  아래의 <trim>을 실행시키기 위해 잠시 주석 	
  UPDATE
  		TBL_MENU
  	<set>
	  	<if test="name != null and name !=''"> 
	  	MENU_NAME = #{ name }
	  	</if>
	  	<if test='categoeyCode != null and categoryCode gt 0'>
	  	,	CATEGORY_CODE = #{ categoryCode }
	  	</if>
		<if test="orderableStatus != null and orderableStatus !=''">
		,	ORDERABLE_STATUS = #{ orderableStatus }
		</if>
	</set>
  	WHERE MENU_CODE = #{ code }			
  	</update>
-->

  	<!-- 해결 방법 [2] <trim> 태그 사용 -->
  	UPDATE
  		TBL_MENU
  	<trim prefix="SET" prefixOverrides=","> <!-- : 앞에 SET이나 ,가 오게 되면 제거 -->
	  	<if test="name != null and name !=''"> <!-- 값을 입력한 컬럼만 수정하기 위해 각각 if절 설정해둠 -->
	  	MENU_NAME = #{ name }
	  	</if>
	  	<if test='categoeyCode != null and categoryCode gt 0'>
	  	,	CATEGORY_CODE = #{ categoryCode }
	  	</if>
		<if test="orderableStatus != null and orderableStatus !=''">
		,	ORDERABLE_STATUS = #{ orderableStatus }
		</if>
	</trim>
  	WHERE MENU_CODE = #{ code }			
  	</update>

</mapper>

👀 Provider

👉 Application

public class Application {

	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);
		
		do {
			System.out.println("===== 구문 Builder API를 이용한 동적 SQL =====");
			System.out.println("1. SelectBuilder 테스트");
			System.out.println("2. SqlBuilder 테스트");
			System.out.println("9. 프로그램 종료");
			System.out.print("메뉴 입력 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : selectBuilderSubMenu(); break;
			case 2 : sqlBuilderSubMenu(); break;
			case 9 : System.out.println("프로그램을 종료합니다."); return;
			}
			
		} while(true);

	}

[1] SelectBuilder 테스트

	private static void selectBuilderSubMenu() {
		
		Scanner sc = new Scanner(System.in);
		SelectBuilderService selectBuilderService = new SelectBuilderService(); // [SelectBuilderService 클래스 생성]
		
		do {
			
			System.out.println("===== SelectBuilder 서브 메뉴 =====");
			System.out.println("1. SelectBuilder 기본 구문 사용하기");
			System.out.println("2. SelectBuilder를 이용한 동적 SQL 사용하기");
			System.out.println("9. 이전 메뉴");
			System.out.print("메뉴 입력 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : selectBuilderService.testSimpleStatement(); break; // [SelectBuilderService에 testSimpleStatement()생성]
			case 2 : selectBuilderService.testDynamicStatement(inputSearchCriteria()); break;
			case 9 : return;
			}
			
		} while(true);
	}
◼ [1-2] SelectBuilder를 이용한 동적 SQL 사용하기
	private static SearchCriteria inputSearchCriteria() {

		Scanner sc = new Scanner(System.in);
		System.out.print("검색 기준을 입력해주세요 (name or category) : ");
		String condition = sc.nextLine();
		System.out.print("검색어를 입력해주세요 : ");
		String value = sc.nextLine();
		
		return new SearchCriteria(condition, value);
	}

[2] SqlBuilder 테스트

	private static void sqlBuilderSubMenu() {
		
		Scanner sc = new Scanner(System.in);
		SqlBuilderService sqlBuilderService = new SqlBuilderService();
		
		do {
			System.out.println("========== SqlBuilder 서브 메뉴 ==========");
			System.out.println("1. 새로운 메뉴 추가하기");
			System.out.println("2. 기본 메뉴 수정하기");
			System.out.println("3. 마음에 들지 않는 메뉴 삭제하기");
			System.out.println("9. 이전 메뉴로");
			System.out.print("메뉴를 입력하세요 : ");
			int no = sc.nextInt();
			
			switch(no) {
			case 1 : sqlBuilderService.registMenu(inputNewMenu()); break;
			case 2 : sqlBuilderService.modifyMenu(inputModifyMenu()); break;
			case 3 : sqlBuilderService.deleteMenu(inputMenuCode()); break;
			case 9 : return;
			}
		} while(true);
	}
◼ [2-1] 새로운 메뉴 추가하기
	private static MenuDTO inputNewMenu() {
		
		Scanner sc = new Scanner(System.in);
		System.out.print("등록할 메뉴 이름을 입력하세요 : ");
		String name = sc.nextLine();
		System.out.print("등록할 메뉴 가격을 입력하세요 : ");
		int price = sc.nextInt();
		System.out.print("등록할 카테고리를 입력하세요 : ");
		int categoryCode = sc.nextInt();
		System.out.print("판매 등록 여부를 입력하세요(Y/N) : ");
		sc.nextLine();
		String orderableStatus = sc.nextLine();
		
		MenuDTO menu = new MenuDTO();
		menu.setMenuName(name);
		menu.setMenuPrice(price);
		menu.setCategoryCode(categoryCode);
		menu.setOrderableStatus(orderableStatus);
		
		return menu;
		
	}
◼ [2-2] 기본 메뉴 수정하기
	private static MenuDTO inputModifyMenu() {
		
		Scanner sc = new Scanner(System.in);
		System.out.print("수정할 메뉴 코드를 입력하세요 : ");
		int code = sc.nextInt();
		System.out.print("수정할 메뉴 이름을 입력하세요 : ");
		sc.nextLine();
		String name = sc.nextLine();
		System.out.print("수정할 메뉴 가격을 입력하세요 : ");
		int price = sc.nextInt();
		System.out.print("수정할 카테고리를 입력하세요 : ");
		int categoryCode = sc.nextInt();
		System.out.print("수정할 판매 등록 여부를 입력하세요(Y/N) : ");
		sc.nextLine();
		String orderableStatus = sc.nextLine();
		
		return new MenuDTO(code, name, price, categoryCode, orderableStatus);
		
	}
◼ [2-3] 마음에 들지 않는 메뉴 삭제하기
	private static int inputMenuCode() {
		
		Scanner sc = new Scanner(System.in);
		System.out.print("삭제할 메뉴 번호를 입력하세요 : ");
		int code = sc.nextInt();
		
		return code;
	}
}

💁‍ SelectBuilder 테스트 시작 !

👉 SelectBuilderService

public class SelectBuilderService {

	private SelectBuilderMapper mapper; // [mybatis-config.xml파일에 mapper 새로 등록 후 사용]
◼ [1-1] SelectBuilder 기본 구문 사용하기
	public void testSimpleStatement() {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(SelectBuilderMapper.class);
		
		List<MenuDTO> menuList = mapper.selectAllMenu();
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
		
		sqls.close();
	}
◼ [1-2] SelectBuilder를 이용한 동적 SQL 사용하기
	public void testDynamicStatement(SearchCriteria searchCriteria) {
		
		SqlSession sqls = getSqlSession();
		mapper = sqls.getMapper(SelectBuilderMapper.class);
		
		List<MenuDTO> menuList = mapper.searchMenuByCondition(searchCriteria);
		
		if(menuList != null && !menuList.isEmpty()) {
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} else {
			System.out.println("검색 결과가 존재하지 않습니다.");
		}
		
		sqls.close();
	}
}

👉 SelectBuilderMapper

public interface SelectBuilderMapper {

	@Results(id = "menuResultMap", value= { // 어떤 컬럼에 어떤 값을 넣을지 작성
			@Result(id = true, property = "menuCode", column = "MENU_CODE"), // id = true라는 특정값을 줌
			@Result(property = "menuName", column = "MENU_NAME"),
			@Result(property = "menuPrice", column = "MENU_PRICE"),
			@Result(property = "categoryCode", column = "CATEGORY_CODE"),
			@Result(property = "orderableStatus", column = "ORDERABLE_STATUS")
	}) 
◼ [1-1] SelectBuilder 기본 구문 사용하기
	@SelectProvider(type=SelectBuilderProvider.class, method="selectAllMenu") // 사용할 클래스명, 메소드명 명시 [SelectBuilderProvider 클래스 생성]
	List<MenuDTO> selectAllMenu();
	// SelectBuilderProvider를 호출하여 SQL구문 반환받기
◼ [1-2] SelectBuilder를 이용한 동적 SQL 사용하기
	@ResultMap("menuResultMap") // : id 값으로 자동 매핑하겠다.
	@SelectProvider(type=SelectBuilderProvider.class, method="searchMenuByCondition")
	List<MenuDTO> searchMenuByCondition(SearchCriteria searchCriteria);

}

👉 SelectBuilderProvider

public class SelectBuilderProvider { // 개신기하당

	/* SQL문을 문자열 형태로 반환하도록 반환 타입을 String으로 지정한다. */
	/* SelectBuilderMapper에서 호출하여 사용할 예정 */
◼ [1-1] SelectBuilder 기본 구문 사용하기
	public String selectAllMenu() {
		
		return new SQL() // SQL객체를 생성하여 SELECT할 컬럼과 테이블, 조건을 SQL객체에 설정해주는 과정
				.SELECT("MENU_CODE")
				.SELECT("MENU_NAME")
				.SELECT("MENU_PRICE")
				.SELECT("CATEGORY_CODE")
				.SELECT("ORDERABLE_STATUS") 
				.FROM("TBL_MENU")
				.WHERE("ORDERABLE_STATUS = 'Y'").toString(); // 문자열로 만들어 반환
	}
◼ [1-2] SelectBuilder를 이용한 동적 SQL 사용하기
	public String searchMenuByCondition(SearchCriteria searchCriteria) {
		
		SQL sql = new SQL();
		
		sql
		.SELECT("MENU_CODE")
		.SELECT("MENU_NAME")
		.SELECT("MENU_PRICE")
		.SELECT("CATEGORY_CODE")
		.SELECT("ORDERABLE_STATUS") 
		.FROM("TBL_MENU");
		
		if("category".equals(searchCriteria.getCondition())) {
			sql
			.JOIN("TBL_CATEGORY USING(CATEGORY_CODE)")
			.WHERE("ORDERABLE_STATUS = 'Y'")
			.AND()
			.WHERE("CATEGORY_NAME = #{ value }");
			
		} else if ("name".equals(searchCriteria.getCondition())){
			/* 가변인자를 이용하면 자동 AND로 처리하기 때문에 OR를 사용해야 하는 경우는 .OR() 메소드를 사용해야함 */
			sql 
			.WHERE("ORDERABLE_STATUS = 'Y'", "MENU_NAME LIKE '%' || #{value} || '%'"); 
			// WHERE절에 가변인자를 넣을 수 있음 (인자 여러 개 나열 가능)
		}
		
		return sql.toString(); // String으로 변환하여 반환
	}
}

💁‍ SqlBuilder 테스트 시작 !

👉 SqlBuilderService

public class SqlBuilderService {
	
	private SqlBuilderMapper mapper;
◼ [2-1] 새로운 메뉴 추가하기
	public void registMenu(MenuDTO menu) {
		
		SqlSession sqlSession = getSqlSession();
		mapper = sqlSession.getMapper(SqlBuilderMapper.class);
		
		int result = mapper.registMenu(menu);
		
		if(result > 0) {
			System.out.println("신규 메뉴 등록에 성공하셨습니다.");
			sqlSession.commit();
		} else {
			System.out.println("신규 메뉴 등록에 실패하셨습니다.");
			sqlSession.rollback();
		}
		
		sqlSession.close();
		
	}
◼ [2-2] 기본 메뉴 수정하기
	public void modifyMenu(MenuDTO menu) {
		
		SqlSession sqlSession = getSqlSession();
		mapper = sqlSession.getMapper(SqlBuilderMapper.class);
		
		int result = mapper.modifyMenu(menu);
		
		if(result > 0) {
			System.out.println("메뉴 정보 수정에 성공하셨습니다.");
			sqlSession.commit();
		} else {
			System.out.println("메뉴 정보 수정에 실패하셨습니다.");
			sqlSession.rollback();
		}
		
		sqlSession.close();
		
	}
◼ [2-3] 마음에 들지 않는 메뉴 삭제하기
	public void deleteMenu(int code) {
		
		SqlSession sqlSession = getSqlSession();
		mapper = sqlSession.getMapper(SqlBuilderMapper.class);
		
		int result = mapper.deleteMenu(code);
		
		if(result > 0) {
			System.out.println("메뉴 삭제에 성공하셨습니다.");
			sqlSession.commit();
		} else {
			System.out.println("메뉴 삭제에 실패하셨습니다.");
			sqlSession.rollback();
		}
		
		sqlSession.close();
	}
}

👉 SqlBuilderMapper

public interface SqlBuilderMapper {
◼ [2-1] 새로운 메뉴 추가하기
	@InsertProvider(type=SqlBuilderProvider.class, method="registMenu") // [SqlBuilderProvider 클래스 생성]
	int registMenu(MenuDTO menu);
◼ [2-2] 기본 메뉴 수정하기
	@UpdateProvider(type=SqlBuilderProvider.class, method="modifyMenu")
	int modifyMenu(MenuDTO menu);
◼ [2-3] 마음에 들지 않는 메뉴 삭제하기
	/* Map이나 getter가 있는 DTO가 아닌 기본 자료형 값을 전달해야 하는 경우에는 @Param 어노테이션을 이용한다.
	 * 또는 전달해야 하는 값이 2개 이상인 경우도 @Param 어노테이션을 이용할 수 있다.
	 * 단, Provider 메소드의 매개변수 선언부는 없어야 한다. */
	@DeleteProvider(type=SqlBuilderProvider.class, method="deleteMenu")
	int deleteMenu(@Param("code") int code); // @Param("code") : 파라미터를 "code"로 지칭할 것이다

}

👉 SqlBuilderProvider

public class SqlBuilderProvider {
◼ [2-1] 새로운 메뉴 추가하기
	public String registMenu(MenuDTO menu) {
		
		SQL sql = new SQL();
		
		sql
		.INSERT_INTO("TBL_MENU")
		.VALUES("MENU_CODE", "SEQ_MENU_CODE.NEXTVAL") // (컬럼명, 삽입할 값 이름)
		.VALUES("MENU_NAME", "#{ menuName }")
		.VALUES("MENU_PRICE", "#{ menuPrice }")
		.VALUES("CATEGORY_CODE", "#{ categoryCode }")
		.VALUES("ORDERABLE_STATUS", "#{ orderableStatus }");

		return sql.toString();
	}
◼ [2-2] 기본 메뉴 수정하기
	public String modifyMenu(MenuDTO menu) {
		
		SQL sql = new SQL();
		
		/* 비어있지 않은 값만 업데이트되는 동적 쿼리로 작성 */
		sql
		.UPDATE("TBL_MENU");
		
		if(menu.getMenuName() != null && !"".equals(menu.getMenuName())) {
			sql.SET("MENU_NAME = #{ menuName }");
		}
		
		if(menu.getMenuPrice() > 0) { // null은 의미가 없고 유효한 값인지 판단하기 위해 > 0 입력
			sql
			.SET("MENU_PRICE = #{ menuPrice }");
		}
		if(menu.getCategoryCode() > 0) {
			sql
			.SET("CATEGORY_CODE = #{ categoryCode }");
		}
		if(menu.getOrderableStatus() != null && !"".equals(menu.getOrderableStatus())) {
			sql
			.SET("ORDERABLE_STATUS = #{ orderableStatus }");
		}
		
		sql.WHERE("MENU_CODE = #{ menuCode }");
		
		return sql.toString();
	}
◼ [2-3] 마음에 들지 않는 메뉴 삭제하기
	public String deleteMenu() { // 매개변수부에 int code를 선언하지 않음 !!!!!!!!!
		
		SQL sql = new SQL();
		
		sql
		.DELETE_FROM("TBL_MENU")
		.WHERE("MENU_CODE = #{ code }"); // @Param("code")라고 명시했으므로 code로 값을 입력
		
		return sql.toString();
	}
}

profile
Tiny little habits make me

0개의 댓글