TODAY TOTAL
Back-End/Spring (21)
[Spring] 한글 필터 설정 및 모달창

 

한글은 항상 말썽이다. 전에도 이클립스 한글 깨진 적이 있어서 속성 들어가서 바꾸고 그랬는데 이번에도.. 어김없이

 

하지만 이상하게 필터를 적용해도 한글이 ?????? 가 나와서 구글의 힘을 빌려 찾아보았다.

 

필터와 UTF-8 적용이 겹쳐서 그렇다.. 톰캣 서버 Server.xml에 커넥션에 UTF-8 추가하면 된다. 라는 정보가 많았지만

 

결국 물음표는 없어지지 않았다.

 

그러나 30분동안 찾다가 찾았다. 

 

원인은 register,list랑 연결된 footer.jsp 와 header.jsp도 같이 charset=UTF-8" pageEncoding="UTF-8"

 

바꿔줘야 했던 것이였다.

 

 

해결은 했지만 DB 데이터는 엉망진창...

 

ㅠㅠ

그래도 해결해서 다행 휴..

 

모달창은 소스 위치 상관 없이 위 아래에 놔도 적용이 된다.

 

난 맨 밑에 있는 스크립트 바로 위에 썼다. (부트스트랩 이용)

 

https://getbootstrap.com/docs/4.0/components/modal/

 

Modal

Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.

getbootstrap.com

 

모달창 소스 복사 붙여넣기 후  첫번째 div에 id="myModal" 를 준 뒤

 

$(document).ready(function() {

$("#regBtn").click(function(){
		self.location = "/board/register";
  });

자바스크립트로 클릭시 register로 가게 했다. 공부하는 영상에선

자바스크립트는 따로 파일을 만들어 개별로 관리 한다고 한다.

 

a 태그를 이용해서 할 수도 있다.

 

적용 후 모습

 

 

  Comments,     Trackbacks
[Spring] 화면 구현 및 등록 처리

 

영상을 보면서 얼마나 화면구현하는게 오래걸리는지 새삼 실감했다.

부트스트랩이 없었다면 진짜 진짜 오래걸렸을꺼같다.

 

영상도 화면구현이 제일 길다 ㅋㅋ

 

 

include로 헤더와 풋터를 넣었다. 그 이유는 공통적으로 쓰기 때문 view단에 똑같은 코드를 넣을 필요가 없다

 

 

또한 resources 폴더 js,css을 넣는 폴더로 부트스트랩 관련 css,js,html을 넣었주었다.

디자인은 최대한 html 소스를 보며 복사 붙여넣기를 하고 최대한 스프링 전체를 이해하는거에 중점을 두었다.

 

 

<div class="panel-body">
                            <table width="100%" class="table table-striped table-bordered table-hover" >
                                <thead>
                                    <tr>
                                        <th>BNO</th>
                                        <th>Title</th>
                                        <th>writer</th>
                                        <th>RegDate</th>
                                        <th>UpdateDate</th>
                                    </tr>
                                </thead>
                                <tbody>
                                <c:forEach items="${list}" var="board">
                                    <tr class="odd gradeX">
                                        <td>${board.bno }</td>
                                        <td>${board.title }</td>
                                        <td>${board.writer }</td>
                                        <td><fmt:formatDate pattern="yyyy-MM-dd"
						                   value="${board.regdate }" /></td>
						                <td><fmt:formatDate pattern="yyyy-MM-dd"
						                   value="${board.updateDate }" /></td>
                                    </tr>
                                </c:forEach>
                                </tbody>
                            </table>
                            
                        </div>

 

여기서 EL태그나 JSTL(C태그..)를 쓸려면

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

이걸 맨 위쪽에 붙여야한다. 그래야 태그를 쓸 수 있다.

 

c:forEach는 List,배열 요소를 순서대로 반복하는 태그다

 

items엔 BoardController에 있는 키값을 받아와야하는데  키값은 list 그래야 전체 테이블 List를 가져와 반복한다.

var는 변수다 아무렇게 써도 무방 하지만 연관짓는게 좋다.

 

BoardController.java

@GetMapping("/list")
	public void list(Model model) {
		
		log.info("list............");
		
		model.addAttribute("list", service.getList());
	}

 

등록은 보내는방식이 POST이지만 등록 할 화면을 보여주는건 GET방식이여야 한다.

전에 POSTMapping을 한건 실제 게시물의 등록 처리이고 지금 적을 GETMapping은 등록 화면이다.

 

@GetMapping("/register")
	 public void registerGET() {
	 }

 

<div class="panel-body">
                        
                            <form action ="/board/register" method="post"> 
                            
                            <div class="form-group">
                                <label>Title</label>
                                <input class="form-control" name="title">
                            </div>
                            
                            <div class="form-group">
                                <label>Content</label>
                                <textarea class="form-control" rows="5" cols="50" name="content">
                                </textarea>
                            </div>
                            
                            
                            <div class="form-group">
                                <label>Writer</label>
                                <input class="form-control" name="writer">
                            </div>

						<button type="submit" class="btn btn-default">Submit Button</button>
                        <button type="reset" class="btn btn-default">Reset Button</button>

                            </form>
                        </div>

 

 

 

맨 위에 추가된 것을 볼 수 있다.

 

 

  Comments,     Trackbacks
[Spring] 컨트롤러 등록,수정 및 삭제

 

POST 방식인 (등록, 수정, 삭제) POST는 '리소스를 생성,변경해야 할때 쓴다' 그리고 별도의 페이지. 삭제 되었으면

삭제 되었다고 다른 페이지에 가거나 등록되었습니다.. 알림메시지 같은거

 

그리고 POST방식 후에는 redirect/를 써 (등록,수정,삭제) 후 list.jsp에 이동 하게끔 하자

 

하지만 여기서 왜 redirect를 쓰는 것이냐는거다. redirect를 쓰지 않으면 사용자가 도배를 할 수 있기 때문이다.

 

무슨 말이라면, 쓰지 않으면 URL가 항상 같기 때문에 똑같은 글을 쓸 수 있다. 하지만 redirect를 쓰면 URL가 계속 바뀌기 때문에 똑같은 글을 쓸 수 없게 된다.

 

 

등록이나 수정, 삭제 작성하는 건 비슷비슷하다.

 

등록

 

//등록
	@PostMapping("/register")
	public String register(BoardVO board,RedirectAttributes rttr) {
	
		
		log.info("board:" + board);
		
		Long bno = service.register(board);
		
		log.info("BNO:" + bno);
		rttr.addFlashAttribute("result",bno);
		//아주 잠깐만 결과를 보내는 거, 한번은 전송되는데 그다음 브라우저 주소창엔 남지 않는다.
		//rttr.addAttribute("result",bno)남는다.
		
		return "redirect:/board/list";
	}

매개변수에 RedirectAttributes가 있다. 처음본다..

 

찾아보니 RedirectAttributes의 클래스를 통해 string형태와 map,list,vo 등의 object형태로 넘겨줄 수 있다.

그리고 String으로 하는 이유는 rttr 문자열 접두어로 보내기 때문에 String으로 한다.

 

addFlashAttribute는 이름 그대로 플래쉬.. 잠깐만 결과를 보여주는 것이다. 그 뒤론 URL창에 남지 않는다. (휘발성)

 

 

 

수정과 삭제 ( 똑같다)

 

@PostMapping("/modify")
	public String modify(BoardVO board,RedirectAttributes rttr) {
		
		int count = service.modify(board);

		
		if(count == 1) {
			rttr.addFlashAttribute("result", "success");
		}
		return "redirect:/board/list";
	}
	
@PostMapping("/remove")
	public String remove(@RequestParam("bno")Long bno,RedirectAttributes rttr) {
		
		int count = service.remove(bno);

		
		if(count == 1) {
			rttr.addFlashAttribute("result", "success");
		}
		return "redirect:/board/list";
	}

 

int count를 쓴 이유는 Service에 modify와 remove를 int형으로 썼기 때문이고 if를 활용해 잠깐 결과 확인

 

 

BoardService.java

public interface BoardService {
	
	Long register(BoardVO vo);
	
	BoardVO get(Long bno);
	
	int modify(BoardVO board);
	
	int remove(Long bno);
	
	List<BoardVO> getList();

}

 

  Comments,     Trackbacks
[Spring] 컨트롤러 등록 테스트

 

 

테스트.. 공부하면서 뭐이리 귀찮은걸 테스트패키지에 파일을 만들어서 할까? 생각했다.

 

하지만 영상을 보면서 테스트는 중요하다고 다시 한번 생각하게 되었다.

 

먼저 확신을 할 수 있다는 것. 만약 BoardController에 등록부분을 테스트를 해서 등록테스트를 확인했다면

오류가 날 시 등록을 제외하고 다른 것을 빠르게 확인할 수 있다. 하지만 지금은 배우는 입장이고 코드량이 많이 없어 빠르게 확인 가능하지만 나중에 실무에선 엄청난 코드량을 어떻게 확인 할 수 있을까? 야근각

테스트가 처음에는 귀찮을지 몰라도 나중을 생각한다면 꼭 Test를 하자! 그리고 log도 수시로 찍자

log찍는다고 뭐라 할 사람 없다!

 

//등록
	@PostMapping("/register")
	public void register(BoardVO board) {
		
		log.info("board:" + board);
		
		Long bno = service.register(board);
		
		log.info("BNO:" + bno);
	}

 

GET은 '서버로부터 정보를 조회하기 위해' URL를 보면 파리미터와 같이 넘어온다. ?가 찍히고 옆에 id 등등

 

POST는 '리소스를 생성,변경하기 위해' 쉽게 생각하면 카톡을 사용할 때 '링크' 만 보내는 것(브라우저로 바로 못 보낸다)

           내용이 눈에 보이지 않다고 안전하다고 생각하지만 그렇지 않음. (개발자도구로 확인가능) 반드시 암호화로..

 

GET, 동일한 요청을 여러번 전송하더라도 같은 응답 (list)

POST, 동일한 요청을 여러번 전송하더라도 응답이 항상 다를 수 있음 (Register,Modify,remove(delete))

 

그래서 등록이라 @PostMapping

 

 

 테스트

 

@Test
	  public void testRegister() throws Exception {
	    log.info(
	        mockMvc.perform(
	            MockMvcRequestBuilders.post("/board/register")
	            .param("title","Test 테스트")
                    .param("content","Content")
	    	    .param("writer","user10")
	    		)
	        .andReturn());
	  }

 

MockMvc, 가짜 객체를 만들어서 MVC동작을 재현 할 수 있는 클래스(테스트 굿굿)

 

.perform, 요청을 전송하는 역할

 

.param(info), 키=값의 파라미터를 전달

 

.andReturn, 말그대로임

 

 

파라미터 수집된것을 알 수 있다.

  Comments,     Trackbacks
[Spring] 컨트롤러 정의 및 설정

 

 

스프링 MVC 중에 C에 해당하는 컨트롤러 다른 것중에 제일 많이 일하는 녀석인거 같다.

 

사용자의 모든 요청은 컨트롤러에 전달되기도 하고 모델생성... 심지어 뷰도 생성하기도 한다.

 

뷰와 모델의 중간다리 역할...

 

이렇게 컨트롤러가 비중이 엄청 커서 MVC의 단점이기도 하다. 그래서 컴포넌트로 분리한다는데

 

컴포넌트분리라는 말도 그렇고 그 다음부터는 잘 모르겠다.

 

 

BoardController를 만들건데 

 

먼저 목록페이지 - > 등록/입력 처리 -> 조회 -> 수정/삭제 순으로 진행 할 예정이다.

 

 

package org.zerock.controller;

@Controller 
@RequiredArgsConstructor 
@RequestMapping("/board/*") 
@Log4j 
public class BoardController {
	
	private final BoardService service;

	@GetMapping("/list")
	public void list(Model model) {
		
		log.info("list............");
		
		model.addAttribute("list", service.getList());
		
	}
}

 

@Controller는 Model 객체를 만들어 데이터를 담고 View를 찾고, 스프링 MVC 컨트롤러 객체임을 명시해준다.

 

@RequiredArgsConstructor

어노테이션은 final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 만들어준다. (필드주입)

 

@RequestMapping("/board/*")

URL을 컨트롤러의 메서드와 매핑할 때 사용하는 스프링 프레임워크의 어노테이션이다. list나 view 등 보면 모두 링크가 board로 시작하기때문에 ()에 보드라고 씀.

 

 

@GetMapping은 /list 메소드 하나당 전부다 URL 하나씩 매핑 하는 것 (?freeId=asdasd )

 

중요한 Model부분 (request.setAttribute()와 비슷하다)

Model이라는 타입의 객체를 파라미터로 받을 수 있다.

 

전달할때는 데이터가 없지만, DB를 통해서 이것의 결과 값이 필요할때는 모델로 써야한다.

 

(DB에서 게시물을 가져와야하는데.. 스프링에선 Model을 사용한다 라고 생각하기)

 

 

 

 

 

 

 

  Comments,     Trackbacks
[Spring] Mybatis와 스프링 페이징 처리

 

 

페이징 처리때 쓸 클래스 'Criteria'

 

전체목록을 페이지 별로 나눠서 보여줄려면, Criteria를 생성해야한다.

 

즉, 검색에 사용되는 여러 종류의 데이터를 하나의 객체로 묶기 위한 용도

 

 

@Getter 
@Setter
@ToString
public class Criteria {
 
   private int pageNum;
   private int amount;
   
   public Criteria() {
      this(1,10);
   }
   
   public Criteria(int pageNum, int amount) {
      this.pageNum = pageNum;
      this.amount = amount;
   }   
   
}

 

그리고 페이징을 할때는 조금의 산수가 필요하다...ㅠ

 

	private int curPage;          // 현재 페이지 번호
	private int rowSizePerPage;   // 한 페이지당 레코드 수
	private int totalPageCount;   // 총 페이지 건수
	private int pageSize;   // 페이지 리스트에서 보여줄 페이지 갯수
	
	
	 // --내부에서 계산 (getter) 왜? 사용자로부터 받는게 없기 때
	private int startRow ;        // 시작 레크드 번호   
	private int endRow;          // 마지막 레크드 번호 
	private int totalRowCount ;   // 총 레코드 건수
	
	
	private int startPage;  // 페이지 리스트에서 시작  페이지 번호 
	private int endPage;   // 페이지 리스트에서 마지막 페이지 번호

 

	/*
	 * 입력 받은 변수를 기반으로
	 * 페이징 관련 변수 계산 
	 */
	public void setting() {
		if(rowSizePerPage < 1) rowSizePerPage = 10;
		if(curPage < 1) curPage = 1;
		if(pageSize < 1) pageSize = 10;
		 
		 //totalPageCount = (int)Math.ceil(totalRowCount /rowSizePerPage); //현재 33이 되야 함
		 totalPageCount = (totalRowCount - 1) / rowSizePerPage + 1; //현재 33이 되야 함
		 System.out.println("totalPageCount = " +totalPageCount);
		 startRow = (curPage -1) * rowSizePerPage + 1;
		 //curPage * rowSizePerPage
		 endRow = startRow + rowSizePerPage - 1 ; // curPage + rowSizePerPage
		 System.out.println("curPage = " +curPage);
		 System.out.println("pageSize = " +pageSize);
		 
		 startPage = (curPage -1) /pageSize * pageSize + 1; //11
		 endPage = startPage + pageSize - 1 ; //15
		 if(endPage > totalPageCount) endPage = totalPageCount;
		 
    }

<페이징 계산을 적은 것>

 

 

 

<JSP 화면 번호 출력>

 

 <div class='pull-right'>
          <ul class="pagination">
 
            <c:if test="${pageMaker.prev}">
              <li class="paginate_button previous"><a href="#">Previous</a>
              </li>
            </c:if>
 
            <c:forEach var="num" begin="${pageMaker.startPage}"
              end="${pageMaker.endPage}">
              <li class="paginate_button"><a href="#">${num}</a></li>
            </c:forEach>
 
            <c:if test="${pageMaker.next}">
              <li class="paginate_button next"><a href="#">Next</a></li>
            </c:if>
          </ul>
        </div>
        <!--  end Pagination -->
      </div>

 

<수정/삭제 후 이동>

 

 @PostMapping("/remove")
  public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
 
    log.info("remove..." + bno);
    if (service.remove(bno)) {
      rttr.addFlashAttribute("result", "success");
    }
    rttr.addAttribute("pageNum", cri.getPageNum());
    rttr.addAttribute("amount", cri.getAmount());
 
    return "redirect:/board/list";
  }

 

 

 

제목 클릭 후 상세보기 그리고 수정페이지

 

 

  Comments,     Trackbacks
[Spring] 페이징 처리 시 쿼리문

 

 

오라클은 페이징 시 DB에 쿼리문으로 ROWNUM을 사용한다. 이게 은근 까다롭다.

 

ROWNUM의사 컬럼으로 참조만 될 뿐 데이터베이스에 저장되지 않습니다.

 

또한 SELECT절에 데이터(row)에 붙는 순번입니다.

 

그리고 ROWNUM과 ORDER BY를 같이 사용한다면 먼저 ROWNUM이 먼저 부여 되고

그다음으로 ORDER BY가 실행된다.

 

주로 <, <= 사용하며 >, >= 인 경우 ROWNUM가 실행되지 않습니다.

 

ROWNUM보다 작거나 큰건 말도 안되는 일

 

그리고 ORDER BY는 가끔적 사용하지 말자, 어쩔 수 없이 써야 된다면 쓸 수 있지만

 

속도를 저하 시키기 때문에 좋지는 않다.

 

지금은 데이터가 많이 없어서 ORDER BY를 쓰든 안쓰든 별 차이가 없지만 데이터가 수만개가 된다면 

1초나 2초가 차이가 나버린다. 

 

이럴경우 고객을 비롯해 선임에게 재떨이를 맞을 수 있다.

 

 

인덱스(Index)란?

인덱스는 데이터베이스 테이블에 있는 데이터를 빨리 찾기 위한 용도의 데이터베이스 객체이며 일종의 색인기술입니다. 테이블에 index를 생성하게 되면 index Table을 생성해 관리합니다. 인덱스는 테이블에 있는 하나이상의 컬럼으로 만들 수 있습니다. 가장 일반적인 B-tree 인덱스는 인덱스 키(인덱스로 만들 테이블의 컬럼 값)와 이 키에 해당하는 컬럼 값을 가진 테이블의 로우가 저장된 주소 값으로 구성됩니다.

 

 

 

ORDER BY 대신 INDEX를 쓰는 것이 좋다.

 

 

SELECT절에 주석같이 생긴게 있다. 저건 힌트라는 것인데

 

힌트란? 

일종의 지시 구문인데, 오라클이 항상 최적의 실행 경로를 만들어 내기는 불가능하기 때문에

직접 최적의 실행경로를 작성해주는 것이다.

 

  • 힌트를 사용하여 아래와 같은 것들을 할 수 있다.
  • 액세스 경로, 조인 순서, 병렬 및 직렬 처리, Optimizer의 목표(Goal)를 변경 가능하다.
  • 데이터 값을 정렬해야 하는 경우, 힌트의 사용이 필요하다.
  • 또한, 드라이빙 테이블을 원하는 대로 선정하고자 할 때도 사용된다.

 

일단 몇가지만 소개

 

 

/*+ FULL(table_name) */

 

:: Table을 모두 스캔 하길 원할 때 사용합니다.

 

 

/*+ INDEX(table_name index_name) */


::   지정된 index를 강제적으로 쓰게끔 지정
  - in list predicat에 대해서도 가능.
  - Multi-column inlists는 index를 사용할 수 없다.

 

 

 

 

 

 

 

 

:: 참고한 블로그

https://coding-factory.tistory.com/419

  Comments,     Trackbacks
[Spring] 5. 프로젝트의 구성

 

 

일반적인 웹 프로젝트의 구조는 3-Tier의 구조를 활용

 

 

 

Presentation Tier
(화면 계층)
화면에 보여주는 기술을 사용하는 영역입니다. (즉, Servlet/JSP나 스프링 MVC가 담당하는 영역) Presentation Tier는 프로젝트의 성격에 맞춰 앱으로 제작하거나, CS(Client-Server)로 구성되는 경우도 있습니다. 이전 파트에서 학습한 스프링 MVC와 JSP를 이용한 화면 구성이 이에 속합니다.
Business Tier
(비즈니스 계층)
순수한 비즈니스 로직을 담고 있는 영역입니다. 이 영역이 중요한 이유는 고객이 원하는 요구 사항을 반영하는 계층이기 때문입니다. 이 영역의 설계는 고객의 요구 사항과 정확히 일치해야 합니다. 이 영역은 주로 'xxxService'와 같은 이름으로 구성하고, 메서드의 이름 역시 고객들이 사용하는 용어를 그대로 사용하는 것이 좋습니다.
Persistence Tier
(영속 계층 혹은 데이터 계층)
데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층입니다. 일반적인 경우에는 데이터베이스를 많이 이용하지만, 경우에 따라서 네트워크 호출이나 원격 호출 등의 기술이 접목될 수 있습니다. 이 영역은 MyBatis와 mybatis-spring을 이용해서 구성했던 파트 1을 이용합니다.

 

 

스프링 MVC를 이용하는 예제의 구성

 

 

각 영역의 네이밍 규칙

 

 

xxxController: 스프링 MVC에서 동작하는 Controller 클래스

 

xxxSerivce, xxxServiceImpl: 비즈니스 영역을 담당하는 인터페이스는 ‘xxxService’라는 방식을 사용하고, 인터페이스를   구현한 클래스는 ‘xxxServiceImpl’이라는 이름을 사용

 

xxxDAO, xxxRepository: DAO(Data-Access-Object)Repository(저장소)라는 이름으로 영역을 따로 구성하는 것이   보편적. 예제에서는 별도의 DAO를 구성하는 대신에 MyBatisMapper 인터페이스를 활용.

 

VO, DTO: VO의 경우는 주로 Read Only의 목적이 강하고, 데이터 자체도 Immutable(불변)하게 설계. DTO는 주로 데     이터 수집의 용도

 

 

 

기본적인 게시물의 CRUD 흐름

 

 


 

프로젝트 폴더 구성

 

 

1.

src/main/java

.java 파일이 모여 있는 곳. 스프링에서 MVC패턴의 서블릿 구조를 잡아주기 때문에 스프링

구조에 맞춰 클래스 파일들을 작성

 

 

2.

src/main/resources

 

자바 클래스에서 사용하는 리소스를 보관하는 곳. DB 연결을 위한 자원, 의존성 주입(DI)을

위한 xml 파일 등 자바 코드 외 모든 자원은 이곳에 넣어주면 됩니다.

 

 

3.

src/test

테스트. 자바 코드와 리소스를 보관하는 곳

 

 

4.

Maven Dependencies

(너무 길게 찍었네;;)

 

Maven에서 auto로 관리해주는 라이브러리 폴더. "pom.xml"에 작성된 라이브러리들을 자동으로

다운받아 (원하는 버전으로 만들려면 https://mvnrepository.com/) 접속해 원하는 것을 다운

빌드툴을 사용함으로써 개발자가 직접 관리해주지 않아도 되는 영역이 되었음

 

 

5.

 

src

Web에 관련된 자원이 담겨있는 루트 폴더.

 

 

6.

src/main/webapps/resources

 

웹에 필요한 자원들을 보관하는 곳. 사용자가 직접 접근 할 수 있는 공간이다.

컨트롤러가 요청을 가로채지 않고 바로 접근할 수 있도록 따로 설정해서 사용

하는 곳입니다.

 

 

7.

src/main/webapp/WEB-INF

 

웹에 필요한 코드 파일과 컴파일된 파일,환경설정 파일들이 보관되는 곳. (보안이 중요한 곳)

외부 사용자가 직접 접근 할 수 없다. 내부적으로만 접근 가능

 

 

8.

src/main/webapp/WEB-INF/classes

 

 

컴파일 된 파일이 보관되어 있는 곳.

 

 

9.

src/main/webapp/WEB-INF/spring

 

스프링 환경설정 파일(context)가 보관 되는 곳.

 

 

10.

src/main/webapp/WEB-INF/views

 

JSP,HTML이 보관 된 곳이며 이 폴다가 루트(/)의 기준점입니다.

사용자가 입력, 컨트롤러가 받아주는 URL이 이 폴더의 구조를 따라가기 때문에 잘 정리 하기

 

 

  Comments,     Trackbacks