원지의 개발
article thumbnail
728x90

관리자 홈페이지

  • 판매자 매니저(Sales Manager)
  • 판매자로 등록된 사람들이 물건을 등록하는 application의 일부
  • B2B는 기업과 기업 사이의 거래를 기반으로 만들어진 프로젝트

데이터 베이스

게시판 구현 순서

1. 테이블 생성

2. VO, DAO, Controller, Service 생성

3. 화면 처리

4. 등록, 리스트 구현

5. 수정, 삭제 구현

6. 페이지 구현

7. 검색 구현

프로젝트 UI

1. 메인

2. 상품 등록

  • textarea → textEditor 대체

<!-- 이런식으로 사용 -->
<textarea id="editor" name="prod_content">[[${vo.prod_content}]]</textarea>

1. CDN 사용

<script src="https://cdn.ckeditor.com/ckeditor5/34.0.0/classic/ckeditor.js"></script>
<script>
      ClassicEditor.create(document.querySelector('#editor'), {
			removePlugins: ['Heading'],
			toolbar: [
				'heading',
				'|',
				'bold',
				'italic'],
			language: "ko"
		} );
</script>

2. 다운로드 사용

https://ckeditor.com/ckeditor-5/online-builder/

 

CKEditor 5 Online Builder | Create your own editor in 5 steps

Create your own CKEditor 5 build with customized plugins, toolbar and language in 5 simple steps.

ckeditor.com

3. 상품 조회

4. 상품 상세


프로젝트 및 테이블 생성

1. springboot project 생성

build.gradle

//slq로그
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
	
//이미지 썸네일 (필수 아님, 0221 15:39)
implementation 'net.coobird:thumbnailator:0.4.8'
	
//제이슨 파서
implementation 'com.google.code.gson:gson:2.8.5'

application.properties

server.port=포트

################### 데이터베이스 연결, 히카리풀, 커넥션 풀 자동연결 ###################
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Seoul
#spring.datasource.username=아이디
#spring.datasource.password=패스워드

spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Seoul
spring.datasource.username=아이디
spring.datasource.password=패스워드


################### 마이바티스 관련 설정 ###################
# 매퍼 xml의 위치 - classpath:/ 리소스 폴더의 하위를 나타냄
mybatis.mapper-locations=classpath:/mapper/*.xml
# 단축명으로 사용할 클래스의 패키지명 (앨리어스 패키지)
mybatis.type-aliases-package=com.coding404.myweb.command

## jsp를 뷰로 사용하려면 리졸버 뷰 선언
#spring.mvc.view.prefix=/WEB-INF/views/
#spring.mvc.view.suffix=.jsp

2. 테이블 생성

  • 상품 테이블
CREATE TABLE PRODUCT(
PROD_ID INT PRIMARY KEY AUTO_INCREMENT, ## PK (자동증가값)
PROD_REGDATE TIMESTAMP DEFAULT NOW(), ## 등록일 (기본값 시간형)
PROD_ENDDATE VARCHAR(20), ## 판매종료일 (문자형 시간)
PROD_CATEGORY VARCHAR(20), ## 카테고리 키 FK
PROD_WRITER VARCHAR(20), ## 작성자 FK
PROD_NAME VARCHAR(300), ## 상품명
PROD_PRICE INT NOT NULL, ## 가격
PROD_COUNT INT NOT NULL, ## 재고
PROD_DISCOUNT INT DEFAULT 0, ## 할인율 (기본값 숫자 0)
PROD_PURCHASE_YN CHAR(1) DEFAULT 'N' CHECK (PROD_PURCHASE_YN IN ('Y', 'N')), ##체크제약
PROD_CONTENT VARCHAR(5000), ##내용
PROD_COMMENT VARCHAR(5000) ##메모
);
  • 카테고리 테이블
CREATE TABLE PRODUCT_CATEGORY(
CATEGORY_ID INT PRIMARY KEY AUTO_INCREMENT,
GROUP_ID VARCHAR(10),
CATEGORY_LV INT, ##1,2,3
CATEGORY_NM VARCHAR(100), ##대분류중분류소분류
CATEGORY_DETAIL_LV INT, ##ORDER순서
CATEGORY_DETAIL_NM VARCHAR(100), ##이름
CATEGORY_PARENT_LV INT , ##1,2,3에 대한 부모컬럼
CATEGORY_DETAIL_PARENT_LV INT ##ORDER순서에 대한 부모컬럼
);
  • 파일 업로드 테이블
CREATE TABLE PRODUCT_UPLOAD (
UPLOAD_NO INT PRIMARY KEY auto_increment,
FILENAME varchar(100) not null, ##실제파일명
FILEPATH varchar(100) not null, ##폴더명
UUID varchar(50) not null, ##UUID명
REGDATE TIMESTAMP default now(),
PROD_ID INT, ##FK
PROD_WRITER VARCHAR(20) ##FK
);

기본 연결

1. 파일 위치

  •  VO, DAO, Controller, Service 생성

2. 템플릿 쪼개기

템플릿 - basicLayout.html

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

<!-- 전체가 하나의 템플릿화, 함수에는 매개변수 받을 수 있음 -->
<th:block th:fragment="layout(section)"> <!-- 템플릿으로 쓸 곳 fragment, 함수(layout)을 하나 가져옴 -->

    <head>
        <title>연습용</title>
        <meta charset="UTF-8" />
        <meta charset="EUC-KR" />

        <!--공통 -->
        <link rel="stylesheet" type="text/css" href="/css/index.css" />
        <link rel="stylesheet" type="text/css" href="/css/footer.css" />
        <link rel="stylesheet" type="text/css" href="/css/main.css" />
        <link rel="stylesheet" type="text/css" href="/css/loading.css" />  
        <link rel="stylesheet" type="text/css" href="/css/jquery-ui.min.css" />
        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"
            integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
        <script src="/js/jquery-1.12.1.min.js"></script>
        <script src="/js/jquery-ui.min.js"></script>
        <script src="/js/style.js"></script>
        <script src="https://cdn.ckeditor.com/ckeditor5/40.0.0/classic/ckeditor.js"></script> <!--ckeditor-->
        <script src="https://cdn.ckeditor.com/ckeditor5/34.0.0/classic/translations/ko.js"></script> <!--한글 사용-->
    </head>

        <!--개인 -->
        <link rel="stylesheet" type="text/css" href="/css/modal.css" />

    <body>
        <!-- 헤더 -->
        <header class="wrap">
            ...
        </header>

        <!-- 좌측 사이드바 -->
        <aside>
            <span class="menuBtn">버튼</span>
            <ul class="sidenav">
                <li class="sub_menu_toggle">
                    <a href="#" >- 상품관리(예제)</a>
                    <ul class="sub_menu " >
                        <li><a th:href="@{/product/productReg}" >-상품등록</a></li>
                        <li><a th:href="@{/product/productList}">-상품조회 / 수정</a></li>
                        <li><a href="#">-하단메뉴</a></li>
                    </ul>
                </li>
        	</ul>
        </aside>

        <!-- 갈아끼울 수 있는 본문 -->
        <section th:replace="${section}">
        </section>

        <!-- 푸터 -->
        <footer id="footer">
            ...
        </footer>	
    </body>

</th:block>

</html>

1. 타임리프 선언

2. block fragment로 전체를 감싸기

3. layout 함수 사용 - 매개변수 section

4. replace로 section값 참조

+ css, js 파일은 절대경로로 맞추는게 안전

+ 경로 설정 - 값 넘길게 없으면 안해도 되는데 있으면 th붙이고 @{ 절대경로 }

내용

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

	<!-- ~{파일경로 :: 템플릿함수(~{:: 선택자}) } -->
	<th:block th:replace="~{./include/basicLayout :: layout( ~{:: .wrap} ) }">

	<div class="wrap"> //위의 .wrap과 같음
		...
	</div>
	
	</th:block>

</html>

1. 타임리프 선언

2. body, header는 남아 있을 필요가 없으므로 삭제

3. th:block으로 감싸고, replace로 이 파일에 이 함수로 전달해 줄 것을 표현 (선택자문법 사용)

    상대경로 (리눅스 쓰면 인식 안되니까 . 점 찍기)

메뉴 고정 - HandlerInterceptor (수정)

  • MenuHandler
  • WebConfig
  • basicLayout
<script th:inline="javascript">
	//console.log(JSON.parse('[[${uri}]]')) <!--캡쳐보면 필요한 값을 가지고 나갈 수 있음-->

	/* 
		메뉴선택 유지 - 
		sub_menu에 display:block,
		sub_menu ▶ a링크에 on 클래스,
		sub_menu_toggle ▶ a에 sub_menu_selectz 클래스
	*/
	var menu = JSON.parse('[[${uri}]]'); //postHandler에서 보내주는 uri

	
	//1. 반복 안 돌리기
	console.log($("a[href*='" + menu + "']"));
	$("a[href*='" + menu + "']").addClass("on");
	//이 아래처럼 쭉쭉쭉 같이하면 반복 안돌리고 가능

	//2. 반복 돌리기	
	$(".sub_menu a").each(function(index, item) { //반복을 돌려서 콜백함수로
		//console.log(index, item); //a태그
		//console.log($(item));

		//attr - 속성값 가져오기
		//console.log($(item).attr("href"));
		 
		if( $(item).attr("href").includes(menu) ) {
			//console.log("확인");
			$(item).addClass("on"); //a태그에 on클래스 추가 - 위 주석에서 a링크에 on클래스 부분
			$(item).closest(".sub_menu").css("display", "block"); //위 주석에서 display: block 부분
			$(item).closest(".sub_menu").prev().addClass("sub_menu_select"); //sub_menu쪽 세가지 작업 처리 
		}
	})
</script>

https://velog.io/@ette9844/Spring-HandlerInterceptor-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC

 

[Spring] HandlerInterceptor와 이를 활용한 로그인 처리

1. HandlerInterceptor 란? HandlerInterceptor는 특정한 URI 호출을 '가로채는' 역할을 합니다. 이를 이용하여 기존 컨트롤러의 로직을 수정하지 않고도, 사전이나 사후 제어가 가능합니다.

velog.io

3. 화면 및 데이터베이스 연결

  • form - action에 컨트롤로 PostMapping될 url 걸어주기 및 화면(html)에서 간단히 사용할 수 있는 옵션
    • readonly
    • required
    • 정수 pattern="\d*"
    • 날짜 pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
  • 컨트롤러에서 받아서 return "redirect:/product/productList"; 로 등록하면 상품 조회로 넘기기
  • ProductVO를 활용하여 서버에서도 유효값 처리 가능
  • RedirectAttributes - 특정 속성을 리다이렉트되는 화면에서 사용 가능하도록 전달
    리다이렉트되면 RedirectAttributes 객체의 내용이 사용되고, 리다이렉트되지 않는 경우 Model 객체의 내용이 사용

  • Service Interface - ServiceImpl
  • Mapper Interface (@Mapper 선언) - Mapper.xml (선언부 적고 namespace="경로 + 파일이름")

  • ServiceImpl : @Service("productService") 선언
  • Controller : 멤버변수(private ProductService productService) 선언 및 @Autowired, @Qualifier("productService") 넣기

  • Service Interface : regist 메서드 생성, 매퍼도 똑같이
  • ServiceImpl : 멤버변수 선언

  • ProductMapper.xml : insert 넣어줌 (테이블명 대/소문자 주의)
  • select는 특정 회원이 쓴 글만 가져오는 형태라 session 필요(로그인한 정보)

로그인한 회원만 조회 session

1. 강제로 admin으로 추가

2. interceptor로 session 활용 - 수정

상품 리스트 표시

  • 타임리프를 사용하여 동적으로 표시
  • list라는 모델의 속성에 있는 객체 목록을 순회하면서 처리
  • vo는 현재 순회중인 객체
  • status는 순회하는 반복문의 상태를 나타냄, 현재 순회중인 요소의 정보를 얻음
    status.index, status.count 등 다양한 상태 정보 활용 가능

유효성 검사

2023.02.14 - [Server/Spring boot] - [Spring Boot] Valiadation(유효성 검사), MyBatis

  • 유효성 검사는 Spring의 Validator 인터페이스를 구현하여 수행
  • 보통 @Valid 사용하여 ProductVO 객체에 적용, 이 객체는 사용자가 제출한 폼 데이터를 바인딩하며 Errors 객체를 통해 유효성 검사의 결과 확인
프론트에서는 검사 수행X
@Valid 사용하여 유효성 검사 후 (공백 및 정규식, min, max 확인)
카테고리 직접 입력 시 DB에 해당 카테고리가 있는지 확인

<tr>
	<th>판매종료일</th>
	<td colspan="5">
      <input type="text" class="datepicker" name="prod_enddate" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}" placeholder="날짜선택"
			th:value="${vo != null ? vo.prod_enddate : ''}"/>
			<span id="valid">[[${valid_prod_enddate}]]</span>
	</td>
</tr>

<tr>
	<th>카테고리</th>
	<td colspan="5">
		<input type="text" name="prod_category"
				th:value="${vo != null ? vo.prod_category : ''}"/>
				<span id="valid">[[${valid_prod_category}]]</span>
		<div class="loading" style="display: none;">
				<div class="loader"></div>
						<div class="loading-overlay"></div>
		</div>

		<div style="color:red">상품등록 완료 후에는 수정이 불가능하니 신중히 선택해주세요.</div>
			<div class="categoryListWrap">
            <!-- 카테고리 -->
		</div>
	</td>
</tr>


<tr>
	<th>판매가</th>
	<td colspan="3">
    	<input type="text" class="inputw150" name="prod_price"
					th:value="${vo != null ? vo.prod_price : ''}"
					placeholder="숫자로 입력" pattern="\d*">원 
					<span id="valid">[[${valid_prod_price}]]</span>
	</td>
</tr>
<tr>
	<th>판매수량</th>
	<td colspan="3"><input type="text" class="inputw150" name="prod_count"
					th:value="${vo != null ? vo.prod_count : ''}"
					placeholder="숫자로 입력" pattern="\d*">개 
					<span id="valid">[[${valid_prod_count}]]</span>
	</td>
</tr>
<tr>
	<th>할인율</th>
	<td colspan="3"><input type="text" class="inputw150" name="prod_discount"
					th:value="${vo != null ? vo.prod_discount : ''}"
					placeholder="숫자로 입력" pattern="\d*">% 
					<span id="valid">[[${valid_prod_discount}]]</span>
	</td>
</tr>
  • 화면에서 min, max 사용하려면 input type="number"여야함
    컨트롤러에서만 검사하려고 text로 씀
  • 처음 등록화면에 들어올 때 vo에는 아무것도 없으므로 null일 경우 '' 넣어주기
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductVO {
	
	/* 서버에서 유효성 검사
	 * @NotNull - null값만 허용하지 않음, 공백으로 주면 에러 안남 (wrapper의Integer, Long, String 등)
	 * @NotBlank - null값과 공백 허용하지 않음 (String에만 적용)
	 * @NotEmpty - null값을 허용하지 않음 (Array, list 적용)
	 * @Pattern - 정규표현식에 맞는 문자열을 정의할 수 있음 (String에만 적용)
	 * 
	 * @Email - 이메일 형식 검증 (공백은 통과)
	 * @Min - 최소값
	 * @Max - 최대값
	 */
	
	private int prod_id;
	private LocalDate prod_regdate;
	
	@NotBlank(message = "판매종료일은 필수로 입력해주세요")
	@Pattern(regexp = "[0-9]{4}-[0-9]{2}-[0-9]{2}", message = "판매종료일은 필수로 입력해주세요")
	private String prod_enddate;
	@NotBlank(message = "카테고리는 필수로 입력해주세요")
	private String prod_category; //insert할 때는 아직없음 (영어+숫자)
	private String category_nav; //insert할 때는 아직없음, 카테고리 조인된 결과(한글)
	
	@NotBlank(message = "공백일 수 없습니다")
	private String prod_writer;
	@NotBlank(message = "상품명은 필수로 입력해주세요")
	private String prod_name;
	
	@Min(value = 0)
	@NotNull(message = "0원 이상으로 입력해주세요")
	private Integer prod_price;
	@Min(value = 1)
	@NotNull(message = "1개 이상으로 입력해주세요")
	private Integer prod_count;
	@Max(value = 100)
	@NotNull(message = "100% 이하로 입력해주세요")
	private Integer prod_discount;
	private String prod_purchase_yn;
	@NotNull
	private String prod_content;
	@NotNull
	private String prod_comment;
}
  • prod_price, prod_count, prod_discount가 int이면 공백시 0이 입력되므로 자바 내부 에러로 넘어감
  • Integer일 경우 null로 넘어가서 @NotNull을 넣어주면 유효성 검사로 넘어감
	//등록요청
	@PostMapping("/registForm")
	public String registForm(@Valid ProductVO vo, Errors errors, Model model,
							 RedirectAttributes ra //특정 속성을 리다이렉트되는 뷰 페이지에서 사용할 수 있도록 전달
							 ) {
		
	    
		if (errors.hasErrors()) { // 유효성 검사
		    List<FieldError> errorList = errors.getFieldErrors();
		    for (FieldError err : errorList) {
		        if (err.isBindingFailure()) { // 자바 내부의 에러라면 true 반환
		            model.addAttribute("valid_" + err.getField(), "형식이 올바르지 않습니다");
		        } else { // 유효성 검사의 실패
		            model.addAttribute("valid_" + err.getField(), err.getDefaultMessage());
		        }
		    }
		    model.addAttribute("vo", vo); // 사용자가 적은 값은 VO에 담김
		    return "product/productReg";
		}

		// 카테고리 확인
		String categoryCheckMessage = productService.isCategoryExists(vo.getProd_category());
		if (categoryCheckMessage != null) {
		    model.addAttribute("msg", categoryCheckMessage);
		    model.addAttribute("vo", vo);
		    return "product/productReg";
		}
        
		 //글 등록
		int result = productService.regist(vo);
		
		String msg = result == 1 ? "정상 입력되었습니다" : "등록에 실패했습니다";
		ra.addFlashAttribute("msg", msg);
		
		return "redirect:/product/productList"; //목록으로
	}
  • 유효성 검사할 객체는 Errors 보다 앞에있어야 함
  • isBindingFailure 사용하여 자바 내부 에러인지 확인 후 유효성 검사 실행
  • 실패하면 getDefaultMessage로 메세지 출력
//ProductServiceImpl
	@Override
	public String isCategoryExists(String prod_category) {
		int count = productMapper.isCategoryExists(prod_category);
	    if (count == 0) {
	        return "올바른 카테고리가 아닙니다. 다시 선택해주세요.";
	    }
	    return null; // 카테고리가 존재하는 경우
	}
<select id="isCategoryExists" resultType="int">
    SELECT COUNT(*) FROM product_category 
    WHERE CONCAT(GROUP_ID, CATEGORY_ID) = #{prod_category}
</select>
  • 유효성 검사 후 카테고리 확인하여 DB에 존재하지 않을 경우 alert로 msg 띄우기

Paging 페이징 & 검색

2023.02.09 - [Server/Spring] - [Spring] SpringMyweb 실습(3) - 페이지네이션, 검색 기능

1. 페이지네이션 기본

페이지네이션 기본틀
페이지네이션은 page, amount값이 들어와야 함

2. criteria에 검색 키워드 추가

@Data
public class Criteria {
	
	//SQL에 전단할 page, amount 을 가지고 다니는 클래스
	private int page; //조회하는 페이지 번호
	private int amount; //데이터 개수
	
	//검색 키워드
	private String searchName; //상품명
	private String searchContent; //상품내용
	private String searchPrice; //정렬방식
	private String startDate; //판매시작일
	private String endDate; //판매종료일

	
	public Criteria() { //기본 생성자 setting
		this.page = 1;
		this.amount = 10;
	}

	public Criteria(int page, int amount) {
		super();
		this.page = page;
		this.amount = amount;
	}
	
	public int getPageStart() { //앞쪽에 들어갈 변수
		return (page - 1) * amount;
	}
	
}

3. getList, getTotal 동적쿼리

<select id="getList" resultType="ProductVO">
	select p.*,
	   	   c.category_nav
	from (
			select * from PRODUCT
	        where prod_writer = #{user_id}
	        
	        <if test="cri.startDate != null and cri.startDate != '' ">
	        <![CDATA[and date_format(prod_enddate, '%Y-%m-%d') >= date_format(#{cri.startDate}, '%Y-%m-%d')]]>
	        </if>
	
	        <if test="cri.endDate != null and cri.endDate != '' ">
	        <![CDATA[and date_format(prod_enddate, '%Y-%m-%d') <= date_format(#{cri.endDate}, '%Y-%m-%d')]]>
	        </if>
	        
		      <if test="cri.searchName != null and cri.searchName != '' ">
		      and prod_name like concat('%', #{cri.searchName}, '%')
		      </if>
		      <if test="cri.searchContent != null and cri.searchContent != '' ">
		      and prod_content like concat('%', #{cri.searchContent}, '%')
		      </if>
		      
		      order by
		      <if test="cri.searchPrice == 'asc' ">
		      prod_price asc,
		      </if>
		      <if test="cri.searchPrice == 'desc' ">
		      prod_price desc, 
		      </if>
		      prod_id desc
		      limit #{cri.pageStart}, #{cri.amount}
	) p
	left outer join (
		SELECT
		   #A1.*,
	       #A2.CATEGORY_DETAIL_NM,
	       #A3.CATEGORY_DETAIL_NM,
		   #CONCAT(A1.group_ID, A1.CATEGORY_ID, A1.category_LV,  A1.category_detail_LV) AS CATEGORY_KEY,
	       CONCAT(A1.group_ID, A1.CATEGORY_ID) AS CATEGORY_KEY,
	 	   CASE A1.category_parent_LV
	 	        WHEN 0 THEN A1.category_detail_NM
	 			WHEN 1 THEN CONCAT(A2.category_detail_NM,' > ', A1.category_detail_NM)
	 			WHEN 2 THEN CONCAT(A3.category_detail_NM, ' > ', A2.category_detail_NM,' > ', A1.category_detail_NM)
	 	   END as CATEGORY_NAV
	FROM PRODUCT_CATEGORY A1
	LEFT OUTER JOIN PRODUCT_CATEGORY A2
	ON A1.CATEGORY_PARENT_LV = A2.CATEGORY_LV AND A1.CATEGORY_DETAIL_PARENT_LV = A2.CATEGORY_DETAIL_LV AND A1.GROUP_ID = A2.GROUP_ID
	LEFT OUTER JOIN PRODUCT_CATEGORY A3
	ON A2.CATEGORY_PARENT_LV = A3.CATEGORY_LV AND A2.CATEGORY_DETAIL_PARENT_LV = A3.CATEGORY_DETAIL_LV
	ORDER BY CATEGORY_NAV ASC
	) c
	on p.PROD_CATEGORY = c.category_key

     </select>
  • getList로 모든 내역 조회
<select id="getTotal" resultType="int">
  		select count(*) as total from PRODUCT
  		where prod_writer = #{user_id}
  		
  		<if test="cri.startDate != null and cri.startDate != '' ">
  		<![CDATA[
  		and date_format(PROD_ENDDATE, '%Y-%m-%d') >= date_format(#{cri.startDate}, '%Y-%m-%d')
		]]> </if>
		<if test="cri.endDate != null and cri.endDate != '' ">
		<![CDATA[
		and date_format(PROD_ENDDATE, '%Y-%m-%d') <= date_format(#{cri.endDate}, '%Y-%m-%d')
		]]> </if>
		<if test="cri.searchName != null and cri.searchName != '' ">
		and prod_name like concat('%',#{cri.searchName},'%')
		</if>
		<if test="cri.searchContent != null and cri.searchContent != '' ">
		and prod_content like concat('%',#{cri.searchContent},'%')
		</if>
  	</select>
  	
  	<!-- 글 상세 -->
  	<select id="getDetail" resultType="ProductVO">
	select p.*,
	   	   c.category_nav
	from (
			select * from `product` where prod_id = #{prod_id}
	) p
	left outer join (
		SELECT
		   #A1.*,
	       #A2.CATEGORY_DETAIL_NM,
	       #A3.CATEGORY_DETAIL_NM,
		   #CONCAT(A1.group_ID, A1.CATEGORY_ID, A1.category_LV,  A1.category_detail_LV) AS CATEGORY_KEY,
	       CONCAT(A1.group_ID, A1.CATEGORY_ID) AS CATEGORY_KEY,
	 	   CASE A1.category_parent_LV
	 	        WHEN 0 THEN A1.category_detail_NM
	 			WHEN 1 THEN CONCAT(A2.category_detail_NM,' > ', A1.category_detail_NM)
	 			WHEN 2 THEN CONCAT(A3.category_detail_NM, ' > ', A2.category_detail_NM,' > ', A1.category_detail_NM)
	 	   END as CATEGORY_NAV
	FROM PRODUCT_CATEGORY A1
	LEFT OUTER JOIN PRODUCT_CATEGORY A2
	ON A1.CATEGORY_PARENT_LV = A2.CATEGORY_LV AND A1.CATEGORY_DETAIL_PARENT_LV = A2.CATEGORY_DETAIL_LV AND A1.GROUP_ID = A2.GROUP_ID
	LEFT OUTER JOIN PRODUCT_CATEGORY A3
	ON A2.CATEGORY_PARENT_LV = A3.CATEGORY_LV AND A2.CATEGORY_DETAIL_PARENT_LV = A3.CATEGORY_DETAIL_LV
	ORDER BY CATEGORY_NAV ASC
	) c
	on p.PROD_CATEGORY = c.category_key
     </select>
  • getTotal로 페이지네이션에 필요한 전체 개시글 수로 PageVO 계산

4. hidden 태그로 검색시 page=1, amount는 한번에 보여줄 데이터양

5. 검색 submit 처리

  • productListForm은 form의 id

6. 페이지 버튼에 서치 키워드 매개변수로 전달

<div class="page">
											<ul>
												<!-- 맨 앞으로 -->
												<li><a th:href="@{productList(page=1,
																			  amount=${pageVO.amount},
																			  startDate=${pageVO.cri.startDate},
																			  endDate=${pageVO.cri.endDate},
																			  searchName=${pageVO.cri.searchName},
																			  searchContent=${pageVO.cri.searchContent},
																			  searchPrice=${pageVO.cri.searchPrice}
																			   )}"><i class="fa fa-angle-double-left" aria-hidden="true"></i></a></li>
												
												<!-- 앞페이지 /  아직 -->
												<th:block th:if="${pageVO.prev}">
												<li style="margin-right:5px;"><a th:href="@{productList(page=${pageVO.start - 1},
																						  				amount=${pageVO.amount},
																						  				startDate=${pageVO.cri.startDate},
																					 					endDate=${pageVO.cri.endDate},
																					  					searchName=${pageVO.cri.searchName},
																					  					searchContent=${pageVO.cri.searchContent},
																					  					searchPrice=${pageVO.cri.searchPrice}
																					   					)}"><i class="fa fa-angle-left" aria-hidden="true"></i></a></li>
												</th:block>
												
												<!-- 페이지네이션 -->
												<th:block th:each="page, status : ${pageVO.pageList}" > <!-- 일반 for문이 없어서 vo안의 pageList를 받아서 page변수에 넣음, page변수는 1, 2, 3, 4,... 10 -->
												<li th:class="${page == pageVO.page ? 'on' : ''}"> <!-- pageVO.page는 현재 페이지 -->
													<a th:href="@{productList(page=${page},
																			  amount=${pageVO.amount},
																			  startDate=${pageVO.cri.startDate},
																			  endDate=${pageVO.cri.endDate},
																			  searchName=${pageVO.cri.searchName},
																			  searchContent=${pageVO.cri.searchContent},
																			  searchPrice=${pageVO.cri.searchPrice}
																			   )}">[[${page}]]</a></li>
												</th:block>

												<!-- 뒷페이지 -->
												<th:block th:if="${pageVO.next}">
												<li style="margin-left:5px;"><a th:href="@{productList(page=${pageVO.end + 1},
																									   amount=${pageVO.amount},
																									   startDate=${pageVO.cri.startDate},
																			  						   endDate=${pageVO.cri.endDate},
																			  						   searchName=${pageVO.cri.searchName},
																			  						   searchContent=${pageVO.cri.searchContent},
																			  						   searchPrice=${pageVO.cri.searchPrice})}"><i class="fa fa-angle-right" aria-hidden="true"></i></a></li>
												</th:block>
												
												<!-- 맨 뒤로 -->
												<li><a th:href="@{productList(page=${pageVO.realEnd},
																			  amount=${pageVO.amount},
																			  startDate=${pageVO.cri.startDate},
																			  endDate=${pageVO.cri.endDate},
																			  searchName=${pageVO.cri.searchName},
																			  searchContent=${pageVO.cri.searchContent},
																			  searchPrice=${pageVO.cri.searchPrice}
																			  )}"><i class="fa fa-angle-double-right" aria-hidden="true"></i></a></li>
											</ul>
										</div>

  • 날짜는 문자값 비교가 들어감
  • format으로 치환해서 대소 비교

 

7. 10개씩 보기 form 형식으로 변경

<select class="" id="handleAmount"> <!-- js역량, id 달아줌 -->
	<option value="10" th:selected="${pageVO.amount == 10}">10개 보기</option> <!-- 수정되면 그 값으로 고정 -->
	<option value="20" th:selected="${pageVO.amount == 20}">20개 보기</option>
	<option value="40" th:selected="${pageVO.amount == 40}">40개 보기</option>
	<option value="80" th:selected="${pageVO.amount == 80}">80개 보기</option>
</select>
	//dataHandler
		var handleAmount = document.getElementById("handleAmount"); //js 사용
		handleAmount.onchange = function(e) {
			//console.log(e.target.value); //e.target은 select 태그, 10,20,40,80
			
			//location.href="productList?page=1&amount=" + e.target.value;
			//폼을 체인지 시켜줌
			document.actionForm.amount.value = e.target.value;
			document.actionForm.submit();
  • 선택시 form 형식으로 변경 - 10개씩 보기 버튼도 검색 데이터를 넘겨줘야 하기 때문
  • onchange되면 form의 name=actionForm 으로 바로 submit 날림

8. 페이지 모듈화

 

아래에서 계속..

2023.02.20 - [클라이언트/jQuery] - [Jquery] BootMyweb (2) - 카테고리 처리, sql join

 

728x90
profile

원지의 개발

@원지다

250x250