α Spring

@雲原ジェリー·2021년 12월 6일
1

Spring Framework

목록 보기
1/6

수업 때 배운 내용을 정리 & 공유하기 위해 해당 velog를 생성했습니다.
부족한점이 많지만 이해해주시고 봐주시길 바랍니다.

프레임워크(Framework) 종류

  • spring

  • js framework (React.js , node.js , vue.js 등)


수업에서 사용하는 Spring FrameWork 버젼

  • tool : STS 3.9.18

  • Spring Frame Work Version : 5.2.9

  • JDK : 11(Amozon Corretto open JDK)


Spring Project 제작순서

  • New File → Other → New Spring Legacy Project → 프로젝트 이름 입력 & Spring MVC Project 선택 → 패키지 이름 입력 → finish

초기 설정

  • workspace, css, html, js 의 인코딩설정을 UTF-8로 설정

스프링 프로젝트 구조 ( .xml 파일들이 어디있는지, 무슨 역할을 하는지 알아둘것!)

  • src/main/java

    • 자바 클래스 폴더
  • src/main/resources

    • 로그, DB관련 설정파일

    • 주로 .xml 파일을 사용

  • src/test/*

    • 테스트 관련

    • 테스트는 Spring Boot에서 사용할 예정

  • JRE System Library

    • jdk 관련 라이브러리
  • Maven Dependencies

    • 해당 프로젝트에서 관리되고 있는 의존성(dependency)

    • 의존성은 외부 라이브러리라고 일단 생각

    • Maven

      • 버젼관리(빌드) 도구

      • 외부 라이브러리 관리


  • src/이하 폴더들

    • main/java/com/~~~

      • 자바 클래스

      • 위의 src/main/java 와 동일함

    • main/resources

      • 위의 src/main/resources 와 동일함
    • main/webapp

      • resources

  • 웹 관련 정적 파일들 (css, js, image)

    • WEB-INF/spring

      • 중요! root-context.xml (DB관련 설정 내용)

      • 중요! appServlet/servlet-context.xml (서블릿 관련 설정 내용)

    • WEB-INF/views

      • jsp (java server page)

        • *.jsp 파일이 위치함

        • 사용자에게 보여지는 화면 파일

    • 중요! WEB-INF/web.xml

      • 프로젝트 실행시 가장 먼저 읽어지는 파일

      • root-context.xml servlet-context.xml 파일을 차례대로 읽음

    • 중요! pom.xml

      • dependency 관련 설정 파일

      • 라이브러리 설정


Spring Project 실행 구조

  • web.xml → root-context.xml → servlet-context → 기본주소( / ) 호출 → 기본주소를 처리하는 Controller 클래스 메서드 호출 → 해당 jsp 화면 브라우저 출력

주소

  • 스프링 프로젝트를 실행하면 기본주소는 가장 마지막 패키지 이름 뒤에 ' / '가 붙음

  • com.icia.study가 기본 패키지라면 기본 주소는 localhost:포트번호/study/

  • 회원가입 요청 : localhost:포트번호/study/study/join

  • 로그인 요청 : localhost포트번호/study/login

  • 회원목록 출력 요청 : localhost포트번호/study/list


V(jsp) → C(Controller) 파라미터 변환

  • form

  • query string

    • Ex) Search라는 주소로 '날씨'라는 파라미터를 전송시 → search?q=오늘날씨
  • @RequestMapping

    • value : 요청을 처리할 주소

    • Controller에서 정보를 받을때는 @RequestParam 또는 @ModelAttrubute 등 여러개를 씀

  • method

    • 정보를 어떤 형식으로 받을지 결정하는 부분.
      • RequestMethod.GET
        • 정보노출이 되도 상관이 없을 때(ex : 검색)
      • RequestMethod.POST
        • 정보노출이 되면 안될 때, 또는 주소창이 너무 길어질 때(ex : 회원가입, 로그인 등)

@어노테이션

  • 역할을 지정할 때 씀.

  • 스프링이 관리하는 객체

  • 프로젝트를 시작할 때 스프링이 해당 클래스를 Spring Bean으로 등록하여 관리해줌

  • 중요! 어노테이션으로 선언하는 모든것들은 어노테이션 아래 한줄만 적용


@Controller

  • Controller Class로 사용할 클래스에 붙임

  • Controller Class의 역할

    • 주소 요청에 대한 매핑(Request Mapping을 통해서)

    • 비즈니스 로직 처리를 위해 Service Class 호출

    • 처리결과에 따라 브라우저에 띄울 jsp 리턴


@Service

  • Service Class로 사용할 클래스에 붙임

  • Service Class의 역할

    • Controller로 부터 전달받은 데이터에 대한 처리(비즈니스 로직(Business Logic)

    • DB작업이 필요하면 Repository Class 호출

    • 처리결과를 Cotroller Class로 리턴


@Repository

  • Repository Class로 사용할 클래스에 붙임

  • Repository Class의 역할

    • 쿼리를 수행할 mybatis 함수 호출

    • 처리결과를 Serivce Class로 리턴


@RequestMapping

  • 주소를 받는 어노테이션

  • 주소 요청이 있을 시 @RequestMapping이 붙은 메서드중에 일치하는 내용의 주소를 출력


@RequestParam

  • 하나의 값을 가져올 때 사용하는 어노테이션

  • 단 하나의 값을 가져오기 때문에 여러개의 값을 가져오려면 갯수에 맞게 써야함


@ModelAttribute

  • 보내는 모든 정보를 받을 때 쓰는 어노테이션

  • @RequestParam보다 코드를 간소화 시킬 수 있음


@ResponseBody

  • ajax를 사용해 비동기 처리방식을 할 때 리턴으로 주소를 출력하는 것이 아닌 String 값 자체를 리턴

  • 생성자 타입 앞에 씀


@Autowired

  • new로 객체를 만들면 그 공간만큼 데이터를 차지하기 때문에 가볍게 만들고자 사용

  • class 주입이라고도 함

.xml

  • pom.xml

    • 스프링 설정을 하는 .xml 파일 (각 파일 버젼 등)

    • dependency 작성시 maven이 해당 라이브러리를 사용자/.m2/repository 폴더에 다운받아 관리


  • servlet-context.xml

    • ViewResolver라는 내용이 있기 때문에 String값 뒤에 .jsp를 자동으로 붙여줌

    • base-pakage="com.이름.이름" 맞는지 확인


  • root-context.xml

    • url 부분 value에 데이터베이스 부분을 자기의 데이터베이스 이름으로 변경

    • url 부분 value에 serverTimezone=UTC 부분을 Asia/Seoul로 변경

    • username 부분 value값 사용할 계정의 이름으로 변경

    • password 부분 value값 사용할 계정의 비밀번호로 변경

    • mapperLocations 부분 경로 본인걸로 변경 ex) com/이름/이름/repository/mapper/*.xml


  • web.xml

    • 마무리 설정을 마치는 .xml 파일 (@어노테이션 확인 및 중복 주소 체크)

    • 전부 복사&붙여넣기


MVC(Model 2방식)

  • 웹서비스 구축을 위한 디자인 패턴

  • M(Modal) V(View) C(Controller)

  • Ex)

    • 회원가입

    • MemberJoin.jsp(V) ↔ MemberController.memberJoin() ↔ MemberService.memberJoin() (C) ↔ MemberRepository.memberJoin() ↔ DB.memberTable Insert (M)


오류 발생시

  • 프로젝트 refresh

  • Project 메뉴에서 clean 실행

  • .m2 repository 삭제

  • servlet-context.xml의 base-pakage 이름 확인

  • 서버 지우기

  • 위 방법이 해결 방안은 아님


일관성

  • @ModelAttribute 쓸 때

  • form에서 사용하는 name 속성값, 필드이름, DB컬럼이름이 같아야함


@Controller

  • Service의 각 기능을 호출하는 클래스

  • 호출 결과를 return으로 출력해주는 역할

    // Controller class 정의
    @Contorller
    
    // Service class 주입
    @Autowired
    private MemberService ms;
    
    // 회원가입 페이지에서 회원가입 요청
    @RequestMapping(value = "/insert", method = RequestMethod.POST)
    public String insert(@ModelAttribute MemberDTO member) {
    	String result = ms.insert(member);
    	return result;
    }

@Service

  • Controller와 Repository를 연결해주는 클래스

  • 데이터베이스를 쓰는 작업이 아닐시 Service에서 처리후 Controller로 보냄.

    // Service class 정의
    @Service
    
    // Repository class 주입
    @Autowired
    private MemberRepository mr;
    
    // 회원가입, 회원가입 성공 시 index 페이지로 이동
    public String insert(MemberDTO member) {
    	int result = mr.insert(member);
    	if(result>0) {
    		return "index";
    	} else {
    		return "insert";
    	}
    }

@Repository

  • 데이터베이스와 직접적으로 연관되있는 클래스
    // Repository class 정의
    @Repository
    
    // SqlSessionTemplate class 주입 (mapper를 호출하는 Spring 제공 class)
    @Autowired
    private SqlSessionTemplate sql;
    
    // 회원가입
    public int insert(MemberDTO member) {
    	return sql.insert("Member.insertMember", member);
    }

JSTL

  • 태그를 사용할 수 있게 해주는 코드

  • forEach, choose, when, otherwise 등이 있다.

    // 현재 예시에서 쓰고있는 jstl을 사용하기 위한 코드
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

${ } , #{ }

  • ${필드이름}

    • jsp에서 DTO의 필드값을 가져올 때
  • #{필드이름}

    • mybatis에서 매개변수값의 값을 가져올 때

Spring에서의 CRUD

  • @RequestMethod가 다르면 (GET, POST) 주소값이 같더라도 다른 주소라고 인식

  • C (Create)

    • 데이터 생성
	<mapper namespace="이름1">
		<insert id="이름2" parameterType="DTO주소">
        		insert into 테이블이름(컬럼 이름)
            			values(#{필드이름})
        	</insert>
    	</mapper>

  • R (Read)
    • 데이터 확인
  1. 다수의 데이터를 확인할 때
	<mapper namespace="이름1">
		<select id="이름2" resultType="DTO주소">
			select * from 테이블이름
		</select>
	</mapper>
  1. 한개의 데이터를 확인할 때
	<mapper namespace="이름1">
		<select id="이름2" parameterType="pk타입" resultType="DTO주소">
			select * from 테이블이름 where 번호=#{번호}
		</select>
	</mapper>

  • U (Update)

    • 데이터 수정

    • 수정화면(update.jsp) 요청

    • 수정화면은 데이터를 입력받아야 하기 때문에 form 태그 사용

    • 데이터 수정 후 최종 return값에 redirect:/ 을 붙임

    • redirect:/주소 → 주소에 해당하는 메서드 실행 후 주소 출력

	<mapper namespace="이름1">
		<update id="이름2" parameterType="DTO주소">
			update 테이블이름 set 수정컬럼=#{수정컬럼} where 번호=#{번호}
		</update>
	</mapper>

  • D (Delete)

    • 데이터 제거
	<mapper namespace="이름1">
		<delete id="이름2" parameterType="pk타입">
			delete form 테이블이름 where 번호=#{번호}
		</delete>
	</mapper>

  • 과정 순서 예시

    1. 삭제할 데이터 링크 선택

    2. Controller delete( ) 호출

    3. Service delete( ) 호출

    4. Repository delete( ) 호출

    5. mapper에서 delete 실행

    6. delete후 역순으로 delete 결과를 넘김

    7. Controller delete( ) 의 return "redirect:/주소" 실행 ex) listView

    8. 서블릿이 Controller listView( ) 호출

    9. Service listView( ) 호출

    10. Repository listView( ) 호출

    11. mapper에서 select 실행

    12. select후 역순으로 select 결과를 넘김

    13. model에 담아서 listView.jsp를 출력


POSTMAN

  • API 작동 프로그램 (Spring으로 만드는 파일도 API중의 하나)

  • 데이터를 보내 잘 작동하는지 쉽게 볼 수 있게 해주는 프로그램

  • 순서

    • 데이터베이스가 작동하고 있어야함!

      1. 회원가입페이지로 예시

      2. Workspace 생성

      3. 보내는 타입 설정 (회원가입 : POST)

      4. 주소 입력 (회원가입 페이지 주소)

      5. body에 form-data 선택

      6. 각 Key(name)값과 value값 입력

      7. Send로 데이터 보내기

      8. 결과 확인 (status 200 OK, Priview 화면으로 설정한 주소로 넘어갔는지 확인)

      9. 데이터베이스에 데이터가 들어갔는지 확인


AJAX

  • ajax

    • 비동기 처리방식 (페이지를 이동하지 않고 다른 작업을 동시에 수행하는 처리방식)

    • jQuery (javascript 라이브러리)

    • 회원가입 아이디 중복 체크

// jQuery를 사용하기 위한 코드
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>

// 회원가입 ID 중복 체크
            
<script>
// 아이디 입력을 하는 동안에 idDuplicate() 함수를 호출하고 입력된 값을 콘솔에 출력
function idDuplicate() {
const id = document.getElementById('m_id').value
// console.log(id)
const checkResult = document.getElementById('id-dup-check')

// jQuery ajax 시작 문법
	$.ajax({
		type: 'post', // 전송방식(get, post, delete, put 등)
		url: 'idDuplicate', // 요청 주소 (컨트롤러로 요청하는 주소)
		data: {'m_id': id}, // 전송할 데이터 {'key': value, 'key': value}
		dataType: 'text', // 요청 후 리턴받을 때의 데이터 형식
		success: function(result) { // 요청 후 성공적으로 처리됐을 때 실행할 함수
			// console.log('ajax 성공')
			// console.log(result)
			if(result=="ok") {
				checkResult.style.color="green"
				checkResult.innerHTML="멋진 아이디네요!"
			} else {
				checkResult.style.color="red"
				checkResult.innerHTML="이미 사용중인 아이디입니다."
			}
		},
		error: function(result) { // 요청이 실패했을 때 실행할 함수
			// console.log('오타 찾으세요')
			// console.log(result)
		}
	})

}
</script>
  • 목록 조회 페이지에서 세부목록 조회
// jQuery를 사용하기 위한 코드
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>

<script>
	function detailAjax(m_number) {
	// console.log(m_number);
	const view = document.getElementById('detail-view')
	$.ajax({
		type: 'post',
		url: 'detailAjax',
		data: {'m_number': m_number},
		dataType: 'json', // 데이터를 toString 타입으로 가져옴.
		success: function(result) { // 처리결과를 받는 부분
			// console.log(result.m_number)
			// console.log(result.m_id)

			let output = "<table class='table table-hover text-center mt-5'>";
			output += "<tr><th>number</th> <th>id</th> <th>password</th> <th>name</th>";
			output += "<th>email</th> <th>phone</th> </tr>";
			output += "<tr>";
			output += "<td>"+result.m_number+"</td>";
			output += "<td>"+result.m_id+"</td>";
			output += "<td>"+result.m_password+"</td>";
			output += "<td>"+result.m_name+"</td>";
			output += "<td>"+result.m_email+"</td>";
			output += "<td>"+result.m_phone+"</td>";
			output += "</tr>";
			output += "</table>";

			view.innerHTML = output;

		},
		error: function(result) {
			console.log('오류(오타)')
		}
	})
}
</script>

@ResponseBody

  • 컨트롤러에서 해당 String 리턴을 하게되면 스프링은 해당 String값에 .jsp를 붙여서 화면 출력

    • @ResponseBody를 붙이면 주소가 아닌 데이터 값을 그대로 리턴

session 객체

  • 주로 서버가 열려있는동안 로그인 정보를 유지시킬 때 사용
  • 브라우저를 모두 종료하거나 서버를 닫기전까지 세션 객체에 저장된 데이터는 계속 유지
  • 일반 model에 담은 데이터는 페이지가 변경되거나 새로운 주소를 요청하면 사라짐
  • model addAttribute를 통해 담은 데이터는 request 객체에 담기는 것
  • 세션에는 최소한의 필수정보만 저장 (로그인정보)
	// 클래스 주입
	@Autowired
	private HttpSession session;

	// session에 정보 저장
	// key는 원하는 이름으로 지정
	session.setAttribute("key",저장할 데이터);

	// session에 저장한 데이터 출력
	${sessionScope."key"}

기본주소 "/"로 변경

  • Tomcat Server

    • Server 폴더 → Tomcat v9.0 → server.xml → Context부분 path="/" 로 변경
  • @Controller

    • HomeController와 MainController 분리
  1. HomeController

    • 기본주소 담당
	// HomeController

	@Controller
	public class HomeController {

		@RequestMapping(value = "/", method = RequestMethod.GET)
		public String index() {
			return "index";
		}

	}
  1. MainController

    • 요청주소 담당
	// MainController
	// ex) com.main.spring

	@Controller
	@RequestMapping(value="/spring/*") // /spring/* 으로 요청하는 주소를 받음
	public class MainController {

	}

Paging 구조

  1. findAll.jsp
<div>
	<!-- c태그를 사용하기 위해서는 JSTL을 선언해야함. -->
	<!-- choose : if문 -->
	<c:choose>
		<!-- 현재 페이지가 시작페이지일 때 -->
		<c:when test="${paging.page<=1}">
			[이전]$nbsp;
		</c:when>
		<!-- otherwise : else -->
		<c:otherwise>
			<!-- 현재 페이지가 시작페이지가 아니라면 이전을 눌렀을 때 1페이지 전으로 가게 함 -->
			<a href="/board/paging?page=${paging.page-1}">[이전]</a>$nbsp;
		</c:otherwise>
	<c:choose>

	<!-- for(int i=startPage; i<=endPage; i++) -->
	<!-- begin : 시작 조건 -->
	<!-- end : 끝 조건 -->
	<!-- var : 지역변수 -->
	<!-- step : 증가하는 수 -->
	<c:forEach begin="${paging.startPage}" end="${paging.endPage}" var="i" step="1">
		<c:choose>
			<c:when test="${i eq paging.page}">
				${i}
			</c:when>
			<c:otherwise>
				<a href="/board/paging?page=${i}">${i}</a>
			</c:otherwise>
		</c:choose>
	</c:forEach>

	<c:choose>
		<c:when test="${paging.page>=paging.maxPage}">
			[다음]
		</c:when>
		<c:otherwise>
			<a href="/board/paging?page=${paging.page+1}">[다음]</a>
		</c:otherwise>
	</c:choose>
</div>

  1. @Controller
	@RequestMapping(value="paging", method=RequestMethod.GET)
	public String paging(@RequestParam(value="page", required=false, defaultValue="1") int page, Model model)
	// (value : 파라미터 이름, requried : 필수여부, defaultValue : 기본값(page 요청값이 없으면 1로 세팅)
	List<BoardDTO> boardList = bs.pagingList(page);
	PageDTO paging = bs.paging(page);
	model.addAttribute("bList", boardList);
	model.addAttribute("paging", paging);
	return "board/findAll";
	}

  1. @Service
	private static final int PAGE_LIMIT = 3; // 한페이지에 보여질 글 개수
	private static final int BLCOK_LIMIT = 3; // 한화면에 보여질 페이지 개수
	// 데이터베이스에서 limit 문에 쓸 값을 변수로 선언

	@Override
	public List<BoardDTO> pagingList(int page) {
		// 1페이지 limit : 0, 3 
		// 2페이지 : 3, 3
		// 3페이지 : 6, 3
		int pagingStart = (page-1)*PAGE_LIMIT;
		Map<String, Integer> pagingParam = new HashMap<String, Integer>();
		// mapper에 변수를 2개이상을 못주기때문에 Map에 담아서 보냄.
		pagingParam.put('start', pagingStart);
		paigngParam.put('limit', PAGE_LIMIT);
		List<BOardDTO> pagingList = br.pagingList(pagingParam);
		return pagingList;
		}

	// 필요한 총 페이지 갯수 계산
	// 현재 사용자가 요청한 페이지가 2페이지라면 화면에는 1,2,3을 보여주고
	// 요청 페이지가 6페이지라면 홤녀에 4,5,6을 보여줌
	// 요청페이지가 마지막이라면 화면에 마지막 페이지까지만 보여줌
	@Override
	public PageDTO paging(int page) {
		int boardCount = br.boardCount();
		int maxPage = (int)(Math.ceil((double)boardCount/PAGE_LIMIT));
		// ceil : 소수점이 있으면 그 다음 정수로 올림해줌.
		// 2페이지를 요청했으면 1페이지, 4페이지를 요청했으면 4페이지, 8페이지를 요청했으면 7페이지의 값을 갖도록 계싼
		int startPage = (((int)(Math.ceil((double)page/BLCOK_LIMIT)))-1)*BLOCKLIMIT+1;
		int endPage = startPage+BLOCK_LIMIT-1;
		if(endPage>maxPage) {
			endPage = maxPage
		}
		PageDTO paging = new PageDTO();
		paging.setPage(page);
		paging.setStartPage(startPage);
		paging.setEndPage(endPage);
		paging.setMaxPage(maxPage);
		// 한번에 모든값을 보내주기위해 PageDTO타입 객체를 선언해 정보를 담음
		return paging;
	}

  1. @Repository
	// 게시글의 갯수를 보기위한 코드
	public int boardCount() {
		return sql.selectOne("board.count");
	}

	// 페이징 처리를 위해 Map에담은 정보를 보내기 위한 코드
	public List<BoardDTO> pagingList(Map<String, Integer> paginParam) {
		return sql.selectList("Board.pagingList", pagingParam);
	}

  1. mapper
	// 페이징 처리를 하는 쿼리문
	// Map을 받기 때문에  parameterType에 java.util.HashMap을 씀
	// 처리가 끝난 후 findAll로 보내야 하기 때문에 resulType에 bdto를 씀
	<select id="pagingList" parameterType="java.util.HashMap" resultType="bdto">
		select * from board order by b_number desc limit #{start}, #{limit}
	</select>

	// 게시글의 갯수를 세는 쿼리문
	// 갯수를 보내야 하기 때문에 resultType에 int를 씀
	<select id="count" resultType="int">
		select count(b_number) from board
	</select>

Search 구조

  1. findAll.jsp
	<!-- 검색기능 -->
	<form action="/board/search" method="get">
		<select name="searchtype">
			<option value="b_title">제목</option>
			<option value="b_writer">작성자</option>
		</select>
		<input type="test" name="keyword">
		<input type="submit" value="검색">
	</form>

  1. @Controller
	@RequestMapping(value="search", method=RequestMethod.GET)
	public String search(@RequestParam("searchtype") String searchtype, @RequestParam("keyword") String keyword, Model model) {
		List<BoardDTO> bList = bs.search(searchtype, keyword);
		model.addAttribute("bList", bList);
		return "board/findAll";
	}

  1. @Service
	@Override
	public List<BoardDTO> search(String searchtype, String keyword) {
		Map<String, String> searchParam = new HashMap<String, String>();
		// mapper는 두개이상의 변수를 못받기 때문에 Map에 담아서 넘김
		searchParam.put("type", searchtype);
		searchParam.put("word", keyword);
		List<BoardDTO> bList = br.search(searchParam);
		return bList;
	}

  1. @Repository
	public List<BoardDTO> search(Map<String, String> searchParam) {
		return sql.selectList("Board.search", searchParam);
		// 검색결과가 여러개일수도 있으므로 selectList를 씀.
	}

  1. mapper
	<select id="search" paramterType="java.util.HashMap" resultType="bdto">
		select * from board
		<include refid="sear"><include>
		<!-- include : refid값과 같은 값을 가진 sql의 id 부분을 참조 -->
	</select>

	<!-- 아래와 같이 쓰면 오류발생 concat을 써서 따로따로 입력해야함 -->
	<!-- select * from board where ${type} like '%#{word}% -->

	<sql id="seal">
		<choose>
			<when test="type=='b_title'">
				where b_title like concat ('%', #{word}, '%')
			</when>

			<when test="type=='b_writer'">
				where b_writer like concat ('%', #{word}, '%')
			</when>
		</choose>
	</sql>

img 저장&출력 구조

  1. 이미지 저장&출력 설정

    • pom.xml, root-context.xml
	<!-- pom.xml에서 확인할 것 -->
	<!-- fileupload -->
	<dependency>
		<groupId>commons-fileupload</groupId>
		<artifactId>commons-fileupload</artifactId>
		<version>1.3.1</version>
	</dependency>

	<!-- root-context에서 확인할 것 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="defaultEncoding" value="UTF-8" />
		<property name="maxUploadSize" value="10000000" />
		<!-- UTF-8로 되있는지, maxUploadSize value값이 충분한지 -->
		<!-- maxUploadSize의 value값보다 큰 파일을 업로드 시 파일이 안불러와짐. -->
	</bean>

  1. img 저장

    • save.jsp (insert.jsp)
	<form actio="/board/savefile" method="post" enctype="multipart/form-data">
		<div class="mb-3">
			<label for="formFileMultiple" class="form-label">사진을 올려보세요!</label>
			<input class="form-control" type="file" name="b_file">
		</div>
	</form>
	<!-- enctype="multipart/form-data" : 파일이나 이미지를 서버로 전송할 때 사용 이를 안쓸 시 파일 경로만 넘어감 -->
	<!-- type="file" : 파일을 받을 수 있는 타입 -->

  1. @Controller
	@RequestMapping(value="savefile",method=RequestMethod.POST)
		public String saveFile(@ModelAttribute BoardDTO board) throws IllegalStateException, IOException{
			bs.saveFile(board);
			return "redirect:/board/paging";
	}
	// throws IllegalStateException, IOExceptio
	// 파일저장 및 불러오는 과정에서 생길 수 있는 예외를 잡기 위한 코드
	// Service에서 이와 같은 작업을 하기 때문에 Controller에서도 똑같이 붙여줘야함
  1. @Service
	@Override
	public void saveFile(BoardDTO board) throws IllegalStateException, IOException {
		// dto에 담긴 파일을 가져옴 
		MultipartFile b_file = board.getB_file();
		// 파일 이름을 가져옴(파일이름을 DB에 저장하기 위해)
		String b_filename = b_file.getOriginalFilename();
		// 파일명 중복을 피하기 위해 파일이름앞에 현재 시간값을 붙임. 
		b_filename = System.currentTimeMillis() + "-" + b_filename;
		System.out.println("b_filename: " + b_filename);
		// 파일 저장경로 세팅
		String savePath = "/Users/sky/EclipseJava/source/Spring/BoardProject/src/main/webapp/resources/upload/"+b_filename;
		// 맥 -> /, 윈도우 -> \\
		// b_file이 비어있지 않다면(즉 파일이 있으면) savePath에 저장을 하겠다.
		if(!b_file.isEmpty()) {
			b_file.transferTo(new File(savePath));
			// java.io.File로 임포트
			// transferTo : 지정된 경로로 파일을 전송
		}
		// 여기까지의 내용은 파일을 저장하는 과정 

		// DB에 저장하기 위해 dto에 파일 이름을 담는다.
		board.setB_filename(b_filename);
		// Add throw 임포트

		// DB의 board에 파일이름을 저장할 b_filename 이라는 컬럼 추가(타입은 varchar(100))
		br.saveFile(board);
		// 마찬가지로 파일을 다루는 코드이기 때문에 throw를 해줘야함.
	}

  1. @Data (DTO)
	private MultipartFile b_file;
	// jsp에서 컨트롤러로 넘어올 때 파일 자체를 담는 필드
	private String b_filename;
	// DB에 파일을 담는것이 아니라 파일 이름을 담는 것. 파일은 별도의 경로에 저장

  1. @Repository
	public void saveFile(BoardDTO board) {
		sql.insert("Board.saveFile",board);
		// DB에는 파일을 담는것이 아닌 파일명을 담는 것이기 때문에 기존 insert와 동일
}

  1. mapper
<!-- 게시글 작성(파일) -->
	<insert id="saveFile" parameterType="bdto">
		insert into board(b_writer, b_password, b_title, b_contents, b_date, b_filename)
		values(#{b_writer},#{b_password},#{b_title},#{b_contents}, now(), #{b_filename})
		// DB에 파일의 이름을 담는것이기 때문에 기존 insert와 방법이 동일
	</insert>

img 출력

  1. detail.jsp
	<img src="/resources/upload/${board.b_filename}">
	<!-- 이미지를 출력하기 위해선 경로를 지정해줘야함. -->
	<!-- upload/ 뒤에 파일명을 DB에서 불러와서 붙임 -->

comment 구조

  1. detail.jsp(상세조회)
<!-- body 부분 -->
<!-- 댓글 작성 -->
	<div id="comment-writer">
		<label for="">댓글 작성</label>
		<input type="text" id="c_writer" placeholder="작성자">
		<input type="text" id="c_contents" placeholder="댓글 내용">
		<button id="comment-write-btn">댓글 등록</button>
	</div>

	<!-- 댓글목록 출력 -->
	<div id="comment-list">
		<table class="table">
			<tr>
				<th>댓글번호</th>
				<th>작성자</th>
				<th>내용</th>
				<th>작성시간</th>
			</tr>
			<c:forEach items="${commentList}" var="comment">
				<tr>
					<td>${comment.c_number}</td>
					<td>${comment.c_writer}</td>
					<td>${comment.c_contents}</td>
					<td>${comment.c_date}</td>
				</tr>
			</c:forEach>
		</table>
	</div>

	// script 부분
	<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
	// jQuary를 사용하기 위한 코드

	// const commentBtn = document.getElementById("comment-write-btn")
	// commentBtn.addEventListener("click", function() {
	// console.log('함수 호출')
	// })

	// 위의 내용을 jQuary를 이용해 아래같이 바꿀 수 있음.
	$("#comment-write-btn").click(function(){
	// $("작동id").작동조건(함수이름() { 실행 내용 })
	console.log('함수 호출(jQuary)')

	// ajax 문법을 이용하여 댓글 작성자, 작성 내용을 comment/save 주소로 post 방식으로 전송하는 내용을 작성

	// const c_writer = document.getElementById('c_writer').value
	// const c_contents = document.getElementById('c_contents').value
	// 위의 내용을 jQuary를 이용하여 아래같이 바꿀 수 있음
	const commentWriter = $("#c_writer").val();
	const commentContents = $("#c_contents").val();
	const boardNumber = '${board.b_number}';
	console.log(commentWriter, commentContents, boardNumber);
	// const comment = document.getElementById('comment-list')
	const comment = $("#comment-List")

	// jQuery ajax 시작 문법
	$.ajax({
		type: 'post', // 전송방식(get, post, delete, put 등)
		url: '/comment/save', // 요청 주소 (컨트롤러로 요청하는 주소)
		data: {'c_writer': commentWriter, 'c_contents':commentContents, 'b_number': boardNumber},
          	// 전송할 데이터 {'key': value, 'key': value}
		dataType: 'json', // 요청 후 리턴받을 때의 데이터 형식
		success: function(result) { // 요청 후 성공적으로 처리됐을 때 실행할 함수
			let output = "<table class='table'>";
			output += "<tr><th>댓글번호</th>";
			output += "<th>작성자</th>";
			output += "<th>내용</th>";
			output += "<th>작성시간</th></tr>";
			for(let i in result) {
			output += "<tr>";
			output += "<td>"+result[i].c_number+"</td>";
			output += "<td>"+result[i].c_writer+"</td>";
			output += "<td>"+result[i].c_contents+"</td>";
			output += "<td>"+result[i].c_date+"</td>";
			output += "</tr>";
			}
			// 댓글의 갯수가 여러개인 경우를 대비해 for문 사용
			output += "</table>";
			// id가 comment-list인 div에 출력
			document.getElementById('comment-list').innerHTML = output;
			// 댓글작성 완료시 입력창을 비움.
			document.getElementById('c_writer').value='';
			document.getElementById('c_contents').value='';
		},
		error: function(result) { // 요청이 실패했을 때 실행할 함수
			console.log('오타 발생')
		}
	})
})

  1. @Controller
  • CommentController
	@Autowired
	private CommentServiceImpl cs;

	@RequestMapping(value="save", method=RequestMethod.POST)
	public @ResponseBody List<CommentDTO> save(@ModelAttribute CommentDTO comment) {
		// System.out.println("CommentController.save() : "+comment);

		/* 새로운 댓글을 DB에 저장하고
		* 최신의 리스트를 DB에서 가져와서 ajax  쪽으로 리턴해야함.
		* 이렇게 처리를 해야 화면 새로고침 없이 댓글이 실시간으로 추가되는것처럼 보임.
		* 
		* 댓글 작성하기
		* 1. DB에 댓글 저장
		* 2. DB로 부터 댓글 목록을 가져온다. (모두다 가져오는 것이 아니라 현재 게시글에 대한 댓글만 가져와야함.
		* 3. 댓글 목록을 리턴한다.
		* 
		* CommentService 인터페이스 생성
		* CommentServiceImpl 클래스에서 메서드 실행내용 작성
		* 
		*/ 
		cs.save(comment);
		// 댓글을 저장하는 메서드를 호출
		List<CommentDTO> commentList = cs.findAll(comment.getB_number());
		// 각 게시글마다 저장되있는 댓글을 불러오기 위해 메서드를 호출
		// System.out.println(commentList);
		return commentList;
	}

  • BoardController
	@Autowired
	private BoardServiceImpl bs;

	@Autowired
	private CommentServiceImpl cs;

	@RequestMapping(value="detail",method=RequestMethod.GET)
	public String detail(@RequestParam("b_number") long b_number, Model model, @RequestParam(value="page", required=false, defaultValue="1")int page) {
		BoardDTO board = bs.detail(b_number);
		model.addAttribute("board",board);
		model.addAttribute("page", page);

		List<CommentDTO> commentList = cs.findAll(b_number);
		model.addAttribute("commentList",commentList);
		// 상세조회를 할 때 댓글 목록도 같이 가져가게 해주는 코드.
		return "board/detail";
	}

  1. @Service
	@Autowired
	private CommentRepository cr;

	// 댓글을 저장하는 메서드를 호출 및 전달
	@Override
	public void save(CommentDTO comment) {
		cr.save(comment);
	}

	// 댓글 목록을 가져오는 메서드를 호출 및 전달
	@Override
	public List<CommentDTO> findAll(long b_number) {
		return cr.findAll(b_number);
	}

  1. @Repository
	@Autowired
	private SqlSessionTemplate sql;

	// 댓글을 저장하는 sql.insert 호출
	public void save(CommentDTO comment) {
		sql.insert("Comment.save", comment);
	}

	// 댓글목록을 가져오는 sql.selectList 호출
	public List<CommentDTO> findAll(long b_number) {
		return sql.selectList("Comment.findAll", b_number);
	}

  1. mapper
	<insert id="save" parameterType="cdto">
		insert into comment_table(b_number, c_writer, c_contents, c_date)
			values(#{b_number},#{c_writer},#{c_contents}, now())
			// 댓글에 게시글 번호를 같이 저장(각 게시글마다 댓글이 따로 있기 때문)
	</insert>

	<select id="findAll" parameterType="long" resultType="cdto">
		select * from comment_table where b_number= #{b_number}
		// 각 게시글마다 댓글이 있기 때문에 b_number로 조건을 줌
	</select>

0개의 댓글