(1) 
porm.xml에 라이브러리 추가
<ScribeJava>
==> 웹서비스를 제작할 때 다양한 SNS 서비스의 OAuth를 이용하는 경우가 많은데
이것들을 하나로 처리할 수 있는 통합 라이브러리입니다.


<!-- Naver  -->
<dependency>
<groupId>com.github.scribejava</groupId>
<artifactId>scribejava-core</artifactId>
<version>2.8.1</version>
</dependency>    

servlet-context.xml 에 beans에 naver Bo 추가
<beans:bean id="naverLoginBO" class="test.domain.NaverLoginBO" />


(2) NaverLoginBO.java 클래스 생성

package main.naver.com;

import java.io.IOException;
import java.util.UUID;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
 
 
public class NaverLoginBO {

    // 발급받은 client id;
    private String CLIENT_ID = "ID";
    
    // 발급받은 client Secret
    private String CLIENT_SECRET="SECRET";

    // 로그인성공시 리턴될 URL
    private String REDIRECT_URI="http://localhost:8080/mytest/member/oauth_naver";
    
    // 세션 유효값검증용 체크용
    private String SESSION_STATE = "oauth_state"; 

    // 프로필 조회 API URL
    private final static String PROFILE_API_URL = "https://openapi.naver.com/v1/nid/me";

    public String getAuthorizationUrl(HttpSession session) {
        // 세션 유효성 검증을 위하여 난수를 생성
        String state = generateRandomString();
        // 생성한 난수 값을 session에 저장
        setSession(session, state);

        // Scribe에서 제공하는 인증 URL 생성 기능을 이용하여 네아로 인증 URL 생성
        OAuth20Service oauthService = new ServiceBuilder()
                .apiKey(CLIENT_ID)
                .apiSecret(CLIENT_SECRET)
                .callback(REDIRECT_URI)
                .state(state) //앞서 생성한 난수값을 인증 URL생성시 사용함
                .build(NaverLoginApi.instance());
        return oauthService.getAuthorizationUrl();
    }

    public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state) throws IOException {

        // Callback으로 전달받은 세선검증용 난수값과 세션에 저장되어있는 값이 일치하는지 확인
        String sessionState = getSession(session);
        if (StringUtils.pathEquals(sessionState, state)) {

            OAuth20Service oauthService = new ServiceBuilder()
                    .apiKey(CLIENT_ID)
                    .apiSecret(CLIENT_SECRET)
                    .callback(REDIRECT_URI)
                    .state(state)
                    .build(NaverLoginApi.instance());

            // Scribe에서 제공하는 AccessToken 획득 기능으로 네아로 Access Token을 획득
            OAuth2AccessToken accessToken = oauthService.getAccessToken(code);
            return accessToken;
        }
        return null;
    }

    // 세션 유효성 검증을 위한 난수 생성기
    private String generateRandomString() {
        return UUID.randomUUID().toString();
    }

    // http session에 데이터 저장
    private void setSession(HttpSession session, String state) {
        session.setAttribute(SESSION_STATE, state);
    }

    // http session에서 데이터 가져오기
    private String getSession(HttpSession session) {
        return (String) session.getAttribute(SESSION_STATE);
    }

    // Access Token을 이용하여 네이버 사용자 프로필 API를 호출
    public String getUserProfile(HttpSession session, OAuth2AccessToken oauthToken) throws IOException {

        OAuth20Service oauthService = new ServiceBuilder()
                .apiKey(CLIENT_ID)
                .apiSecret(CLIENT_SECRET)
                .callback(REDIRECT_URI).build(NaverLoginApi.instance());

        OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService);
        oauthService.signRequest(oauthToken, request);
        Response response = request.send();
        return response.getBody();
    }
}

(3) NaverLoginApi 클래스 생성

package main.naver.com;

import com.github.scribejava.core.builder.api.DefaultApi20;

public class NaverLoginApi extends DefaultApi20 {
     protected NaverLoginApi(){
    }

    private static class InstanceHolder{
        private static final NaverLoginApi INSTANCE = new NaverLoginApi();
    }


    public static NaverLoginApi instance(){
        return InstanceHolder.INSTANCE;
    }

    @Override
    public String getAccessTokenEndpoint() {
        return "https://nid.naver.com/oauth2.0/token?grant_type=authorization_code";
    }

    @Override
    protected String getAuthorizationBaseUrl() {
        return "https://nid.naver.com/oauth2.0/authorize";
    }
}

(4) 컨트롤러

@RequestMapping("/navergo")
	public @ResponseBody String getNaverAuthUrl(HttpSession session) throws Exception {
	    String reqUrl = (new NaverLoginBO()).getAuthorizationUrl(session);
	    return reqUrl;
	}
	 

	// 네이버 연동정보 조회
	@RequestMapping(value = "/oauth_naver")
	public String oauthNaver(HttpServletRequest request, HttpServletResponse response) throws Exception {
	    JsonParser parser = new JsonParser();
	    Gson gson = new Gson();

	    HttpSession session = request.getSession();
	    String code = request.getParameter("code");
	    String state = request.getParameter("state");
	    String error = request.getParameter("error");
	    
	    // 로그인 팝업창에서 취소버튼 눌렀을경우
	    if ( error != null ){
	        if(error.equals("access_denied")){
	            return "redirect:/login";
	        }
	    }

	    OAuth2AccessToken oauthToken;
	    oauthToken = new NaverLoginBO().getAccessToken(session, code, state);
	    //로그인 사용자 정보를 읽어온다.
	    String loginInfo = new NaverLoginBO().getUserProfile(session, oauthToken);
	    System.out.println(loginInfo);
	    // JSON 형태로 변환
	    Object obj = parser.parse(loginInfo);
	    JsonObject jsonObj = (JsonObject)obj;
	    JsonObject callbackResponse = (JsonObject) jsonObj.get("response");
	    String naverUniqueNo = callbackResponse.get("id").toString();
	    
	    
	    if (naverUniqueNo != null && !naverUniqueNo.equals("")) {
	    	System.out.println(naverUniqueNo);
	    	
	        /** 
	            TO DO : 리턴받은 naverUniqueNo 해당하는 회원정보 조회 후 로그인 처리 후 메인으로 이동
	        */
	    	return "/member/login";
	    // 네이버 정보조회 실패
	    } else {
	        throw new Exception("네이버 정보조회에 실패했습니다.");
	    }

	}

(5)view 네이버로그인 JS

<script>
	$("#naverLoginGo").on("click",function(){
	    $.ajax({
	        url: 'navergo',
	        type: 'get',
	        success: function(res){
		    	 location.href = res;
		    },
			error : function(req, status, error){
                console.log("에러");
                console.log(req.responseText);
        	}
	    
		});
	    })
</script>

'API 적용해보기' 카테고리의 다른 글

스프링에 썸머노트 적용해보기  (0) 2022.01.16

썸머노트 실행 + 이미지 별도처리 JS

//썸머노트 실행 함수
        function notesummer(){
            //썸머노트 실행
            $('#summernote').summernote({
            lang:"ko-KR",
            placeholder: '내용을 입력해주세요',
            tabsize: 2,
            width : 1200,
            height: 600,
            toolbar: [
                ['style', ['style']],
                ['font', ['bold', 'underline', 'clear']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['insert', ['link', 'picture', 'video']],
                ['view', ['fullscreen', 'codeview', 'help']]
            ],
            callbacks : { 
                //onImageUpload = 이미지 업로드시 작동하는 콜백함수
                onImageUpload : function(files, editor, welEditable) {
            // 파일 업로드(다중업로드를 위해 반복문 사용)
                for (var i = files.length - 1; i >= 0; i--) {
                        uploadSummernoteImageFile(files[i],
                        this);
                        }
                }
            }//end callbacks 
            });
            // 이미지 업로드시 ajax로 파일 업로드를 하고 성공 후 파일 경로를 return받음
            function uploadSummernoteImageFile(file, editor) {
            data = new FormData();
            data.append("file", file);
            $.ajax({
                url : "summernoteImage",
                data : data,
                type : "POST",
                dataType : 'JSON',
                contentType : false,
                processData : false,
                success : function(data) {
                    //항상 업로드된 파일의 url이 있어야 한다.
                    $(editor).summernote('insertImage', contextPath+data.url);
                }
            });
            } 
        }

 

 

이미지 처리 컨트롤러 코드

	//썸머노트 이미지처리 ajax
	@PostMapping("summernoteImage")
	//썸머노트 이미지 처리
	public String insertFormData2(
			@RequestParam(value="file", required=false) MultipartFile file,HttpSession session
			) {
		Gson gson = new Gson();
		Map<String, String> map = new HashMap<String, String>();
		// 2) 웹 접근 경로(webPath) , 서버 저장 경로 (serverPath)
		String WebPath = "/resources/images/summernoteImages/"; //DB에 저장되는 경로
		String serverPath = session.getServletContext().getRealPath(WebPath);
		String originalFileName=file.getOriginalFilename();
		String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
		String savedFileName = UUID.randomUUID() + extension;	//저장될 파일 명
		File targetFile = new File(serverPath + savedFileName);	
		try {
			InputStream fileStream = file.getInputStream();
			FileUtils.copyInputStreamToFile(fileStream, targetFile);	//파일 저장
			// contextroot + resources + 저장할 내부 폴더명
			map.put("url", WebPath+savedFileName);
			map.put("responseCode", "success");
		} catch (IOException e) {
			FileUtils.deleteQuietly(targetFile);	//저장된 파일 삭제
			map.put("responseCode", "error");
			e.printStackTrace();
		}
		return gson.toJson(map);
	}

'API 적용해보기' 카테고리의 다른 글

스프링에 네이버로그인 적용해보기  (0) 2022.01.16

스프링 스케쥴러 : 스프링이 알아서 때에 맞춰 코드 수행해 줌

-> 코드를 수행하려면 해당 클래스가 객체 형대로 존재해야함

-> Bean 필요 -> @Component

 

@Scheduled : Spring 에서 제공하는 스케쥴러 : 시간에 따른 특정 잡업의 순서를 지정하는 방법

 

설정방법 

1. servlet-context.xml -> Namespaces 탭 -> task 체크 후 저장

2. servlet-context.xml -> Source 탭 -> <task:annotation-driven/> 추가

 

@Scheduled 속성

  - fixedDelay : 이전 작업이 끝난 시점으로 부터 고정된 시간(ms)을 설정.
  - fixedRate  : 이전 작업이 수행되기 시작한 시점으로 부터 고정된 시간(ms)을 설정.

  -cron 속성 : UNIX계열 잡 스케쥴러 표현식으로 작성 - cron="초 분 시 일 월 요일 [년도]" - 요일 : 1(SUN) ~ 7(SAT) 

    * - 특수문자 * : 모든 수. 
    * - : 두 수 사이의 값. ex) 10-15 -> 10이상 15이하 
    * , : 특정 값 지정. ex) 3,4,7 -> 3,4,7 지정 
    * / : 값의 증가. ex) 0/5 -> 0부터 시작하여 5마다 
    * ? : 특별한 값이 없음. (월, 요일만 해당) 
    * L : 마지막. (월, 요일만 해당)

 

* 주의사항 - @Scheduled 어노테이션은 매개변수가 없는 메소드에만 적용 가능.

 

@Scheduled(fixedDelay = 3000) : 3초마다

@Scheduled(cron = "0 * * * * *")//모든 년월일시분, 요일 0 초 마다: 매분 0초마다 라는 뜻(매분마다) 

@Scheduled(cron ="( 0 0 * * .. == 0분 0초 == 정시 마다) 

@Scheduled(cron ="( 0 0 0 * .. == 0시 0분 0초 == 자정 마다)

 

 

// +스케쥴러를 이용한 가비지 파일 삭제

String serverPath = servletContext.getRealPath("/resources/images/board");
		//이미지가 저장되어있는 실제 경로
		//위에서 DI로 ServletConetx를 받아놓는다
        
		File[] imgArr = new File(serverPath).listFiles();
		//지정된 경로에 있는 모든 파일 리스트를 File배열로 반환
		//File객체 : 파일을 참조할 수 있는 객체
		
		//배열을 리스트로 변환
		List<File> serverImgList = Arrays.asList(imgArr);
		
		//이미지가 저장된 폴더에 있는 파일 목록을 잘 가져왔는지 확인
		/*
		 * for(File img : serverImgList) { System.out.println(img); }
		 */
		
		//DB에서 파일명 목록 조회
		List<String> dbImgList = boardService.selectImgList();
		System.out.println(dbImgList);
		//serverImgList : 서버에 저장된 파일 목록
		//dbImgList : DB에 저장된 파일명 목록
		
		//서버 DB 모두 비어있지 않은 경우
		if(!serverImgList.isEmpty() &&!dbImgList.isEmpty() ) {
			for(File img : serverImgList) {
				
				String serverImgName = 
						img.toString().substring(img.toString().lastIndexOf("\\")+1);
					
						//img.toString : 경로 + 파일명
						//.substring -> 문자열 시작부터 지정된 index 이전까지 문자열을 모두 삭제
				if(dbImgList.indexOf(serverImgName) == -1) {
					//DB파일명 목록에 서버 파일명과 같은 이름이 없다면
					System.out.println(serverImgName+"삭제");
					img.delete();
				}
			}
		}

 

오라클의 계층형 쿼리문 - START WITH , CONNECT BY , ORDER SIBLINGS BY

-> 상위 타입과 하위 타입간의 관계를 계층식으로 표현할 수 있게 하는 질의어

 

1. START WITH : 상위 타입(부모) 로 사용 될 행을 지정 (서브쿼리 가능 )

2. CONNECT BY : 상위 타입과 하위 타입간의 관계를 규정

-- PRIOR 연산자와 같이 사용하여 현재 행 이전에 상위 타입이 있을지 하위 타입이 있을지 지정

EX) CONNECT BY PRIOR  자식컬럼 = 부모컬럼 : 부모 -> 자식 계층구조

EX) CONNECT BY PRIOR  부모컬럼 = 자식컬럼 : 자식 -> 부모 계층구조

3. ORDER SIBLINGS BY : 계층형 쿼리의 정렬구문

 

ex) ORIGINAL 을 참조해 PARENT_NO 생성 시

SELECT LEVEL, C.* FROM 컬럼 C

START WITH PARENT_NO IS NULL

CONNECT BY PRIOR ORIGINAL_NO = PARENT_NO

ORDER SIBLINGS BY ORIGINAL_NO

 

--추가기능--

*LEVEL 가상컬럼

-> START WITH로 지정된 행의 LEVEL을 1로 부여하고

-> 하위 계층으로 내려갈수록 단계별 LEVEL을 1씩 증가시켜서 표현

 

*조건이 반영된 형태의 계층구조를 조회해고 싶다면 

FROM 절에 서브쿼리(인라인뷰) 를 사용해서

미리 조건에 맞는 형태의 행으로 구성해 둠

 

 

1. New organization

2. 무료로 해본다

3. 이름 / 이메일 / 조직설명  -> NEXT

4. 팀원 이메일 제출받기

5. 설문조사 -> 생성됨

6. create a new repository

7. 저장소 이름 입력 -> private 

8. readme file 체크 후 생성

9. settings 가서 권한 제어 -> Manage access -> add people

10. .ignore 추가필요 -> add file 후 파일이름에 .gitignore 입력

-> gitignore.io 접속 -> 검색창에 언어 ( Java , Eclipse ) 추가 -> 내용 복사 후 git file에 입력

*.jar  삭제- / +추가 : .project   .classpath  .settings/  classes/    src/main/webapp/META-INF/

입력 후 커밋 뉴파일

-->로컬 설정

11. 이클립스에서 프로젝트 생성 + 설정

12.  소스트리에서 Create 해당 프로젝트 경로 입력 후 생성

13. git 에서 settings 들어가서 Applications

14. OAuth Apps -> 소스트리 클릭 -> 팀원들 grant

15. 원격저장소 URL 복사 후 

16. 소스트리 - 설정 - 원격 - 이름 + 디폴트 원격 + URL 입력 + 계정 선택

-> Pull에 main 뜨는지 확인

17.임시 파일 하나씩 넣어놓고 push 해서 틀 생성 -> pull request -> merge

18.  브랜치 나누기 ->git 창에서 main 클릭하면 아래 창이뜨고 생성하면 만들 수 있음

(브랜치란? 하나의 저장소 내에 여러 버전을 나누기)   

19.소스트리에서 브랜치 클릭-> 새브랜치(자기이름)

----------------------------------------------------------- 

20. (팀원부분) 깃에서 코드 URL 복사 -> CLONE (저장소 안뜨면 소스트리 끄고 C드라이브-사용자-AppData-Local-Atlasian-> userhosts,passwd 삭제 -> 소스트리 켜서 다시 CLONE 진행 후 로그인하라고 뜨면 다시 비밀번호에 personalAcessToken 입력

21. 폴더 경로 새로 생성

22. Local Folder-> 고급옵션 -> 자신의 브랜치로  -> 클론 완료 -> 프로젝트 내려받는다

------.ignore때문에 설정 안들어가므로 이클립스 설정 필요----

23. import - Git - Projects from Git - Existing local reposiroty - add -> (21.)소스트리에서 설정한 폴더(.git 보이는위치)

24. import as general project -> dynamic으로 전환을 해야 오류가 적다

25. 프로젝트 우클릭 configure -> convert to faceted form

26. Dynamic Web Module -> 3.1 /   Java 1.8  /   JS  / 오른쪽 탭 톰캣  완료-> 빌드패스에서 확인

27. Java Build Path -> Default output -> Brouse -> src-main-webapp-Web-INF 클릭 후 Create -> classes  추가

-------------------------------------------------

 

test 파일 push 해보고 오류나면? -> 관리자가 권한을 설정해줘야함

 

사용시!

1. 다같이 push -> pull request

2. merge 처리

3. (충돌 안나게 같은파일 수정하면 안됨) + 충돌시 다른 부분 수기로 삭제해줘야함

4. 끝나면 반드시 다같이 main -> 자신의 브랜치로 pull 받는다

 

'개발자로 업그레이드 되자 > git사용법' 카테고리의 다른 글

git이란? +소스트리 사용  (0) 2022.01.13

형상 관리 시스템의 하나이다

 

형상 관리 ? 스프트웨어의 변경 사항을 체계적으로 추적하고 통제하는 것

 

 

구조

1. Working Directory : 작업을 진행하는 폴더

2. Staging Area : 변경 내역들만 저장된 곳

3. Local Repository : 내  PC 내에 있는 저소 - 깃 자체에 저장공간 (형상관리 진행됨)

4. Remote Repository : 원격 저장소 

 

원래 cmd 창에서 해야하지만 

쉬운 사용을 위해 Sourcetree를 이용해본다

 

머큐리얼 설치 + 소스트리 설치

->  SSH키를 불러오라고 함 -> 아니오

 

Local 설정하기 

 

1. CREATE => 폴더 선택 -> Git으로 생성 => 이미 존재한다 경고 -> 예 누르고 덮어씌움

( .git 폴더 생김 == Local Repository 임 )

2. 파일상태 -> 스테이지에 올린다 == Staging Area 에 놓고 Local에 놓을지 대기상태

==>메세지 + 커밋 하면 Local로 이동

 

원격 Remote 설정하기

1. github 싸이트에서  로그인 후 Create a new repository

-> name , public/private 설정 , (.gitignore 는 따로 추가한다 ) == 설정한것들이 깃에 올라오지 못하게 막는것

2. 생성 -> 주소가 생김

3. 소스트리에서 REMOTE -> 계정추가(깃허브계정)

-> 호스팅서비스 : GitHub  / 프로토콜 : HTTPS / 인증 : Oauth 

-> 새로고침 누르면 로그인해놓은 깃허브에서 자동 연동됨 

-> 완료되면 확인 -> Code란에 깃주소 복사

4.  Local에서 Remote로 데이터 넣기

-> 소스트리에서 원격 누르고 + 디폴트 원격

-> URL에 주소 복사

-> 계정 선택 -> 추가 -> 완료

5. 원격 저장소에서 내려받으려면 Clone

-> 주소 넣고  아래 폴더 경로는 다른 폴더 생성 후 설정해준다

 

 

 

 

(1) <resultMap> 태그를 이용한 VO 연결

 

<resultMap type="VO클래스명" id="별칭">

    <id property="PK변수명" column="PK컬럼명"/>

   <result property="일반변수명" column="일반컬럼명"/>

    .....

    <collection property="컬렉션변수명" column="컬럼명"

      javaType="java.util.ArrayList"   ofType="VO클래스명"   select="selectBoardImageList" >

</resultMap>

 

%% <collection> 태그 : 서브쿼리 형식으로 데이터를 가지고오고 싶을때 사용하며 자바 컬렉션 객체에 담아준다

 

(2) ![CDATA[ SQL문 ]  ] 

 

내부에 작성된 태그 문자를 일반 문자 자체로 인식하게 해준다

ex) SQL문 안에 < 같은 기호를 사용할때 태그로 인식되는 등의 문제 방지

 

 

(3) <selectKey>태그 : insert, update 시 사용된 시퀀스 값을 반환하는 태그

 

   <selectKey order="BEFORE" resultType="_int" keyProperty="boardNo">
   SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
   </selectKey>

 

-keyProperty : selectKey구문의 결과가 셋팅될 대상 프로퍼티

-keyColumn : 리턴되는 결과셋의 컬럼명은 프로퍼티에 일치한다.

-order : BEFORE 또는 AFTER로 셋팅할 수 있다.

BEFORE( 키를 먼저 조회하고 셋팅 후 실행)

AFTER(실행을 먼저 한 뒤 키를 반환)

 

(4) 동적 SQL : 마이바티스의 핵심 기능으로 SQL 수행 중 조건 ,반복 ,특정 구문 추가 등을 동적으로 수행할 수 있음

 

<forEach> 태그 

 

<foreach collection="list" item="img" 
open="(" close=") A"  separator=" UNION ALL ">

</foreach>

 

   - collection : 반복 접근할 컬렉션 형태의 파라미터
   - item : collection에서 반복 시 마다 순차적으로 하나씩 접근한 요소
   - index : 현재 반복 인덱스 
   - open : forEach 시작 전 추가할 구문
   - close : forEach 종료 후 추가할 구문
   - separator : 반복 시 마다 SQL 사이에 추가할 구분자

 

<if> 문 또는 <choose> 태그 

 

<if test="sv != null">

   <bind name="val" value="'%'+sv+'%'" />  --반복되는 문장 정의
<choose>

  <when test="sk == 'title'">

   

  </when>

</choose>
</if>

 

 

 

 

 

스프링에서는 MultipartFile 객체를 제공한다

 

1. 라이브러리 설정

- 파일 업로드를 위한 MultipartResolver 구현체 라이브러리 등록

 

-> maven repository에서 라이브러리
( commons-fileupload ) 검색 후 아파치 pom 추가

 

2. Bean 등록 + 설정

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize"  value="104857600" />
<property name="maxUploadSizePerFile"  value="104857600" />
<property name="maxInMemorySize"  value="104857600" />
</bean>

 

 

/*

maxUploadSize : 한 요청당 허용되는 최대 용량(byte 단위) - 무제한은 -1
maxUploadSizePerFile : 한 파일당 허용되는 최대 용량(byte단위) , 무제한은 -1

maxInMemorySize : 디스크에 저장되지 않고 메모리에 유지하도록 허용하는 바이트 단위
지정된 용량을 초과할 경우 자동으로 파일이 저장된다
메모리가 파일을 기억해놨다가 mULTIPARTrESOLVER 이용해서 DB 수행하고 와도
메모리에 파일이 있음, DB삽입 성공 시 메모리에서 꺼내서 파일로 저장
기본값 : 10240 byte (10KB)
104857600 = 100MB

*/

 

3.  파라미터로 받기

 

List<MultipartFile> "input태그 name"

으로 리스트로 받아낼 수 있다

 

(servers 에서 module 체크해야 서버와 실제 경로가 일치한다)

--> 파일이 업로드 안됐어도 모두 전송되므로 파일 존재 여부를 체크하고 존재하는 파일만 다시 list를 담아야한다

 

--list 새로 생성해서 MutipartResolver에서
 실제 이미지 담겨있는것만 분별+ DB에 필요한 것만 for문으로 추출

 

!imgList.isEmpry()  = true 면 이미지 삽입 [Mybatis 반복삽입진행 ]

 

4. 이미지 리스드 DB 삽입

 

String webPath 

= "/resources/images/board/"; //DB에 저장되는 경로

String reaPath

= session.getServletContext().getRealPath(WebPath); // 실제 물리경로

 

< mapper >

<insert id="insertImgList" parameterType="list">
INSERT INTO BOARD_IMG
SELECT SEQ_IMG_NO.NEXTVAL, A.* FROM 

<foreach collection="list" item="img" 
open="(" close=") A"  separator=" UNION ALL ">
SELECT 

#{img.imgPath} IMG_PATH , 

#{img.imgName} IMG_NM,
#{img.imgOriginal} IMG_ORIGINAL, 

#{img.imgLevel} IMG_LEVEL, 

#{img.boardNo} BOARD_NO 
FROM DUAL
</foreach>


</insert>

 

  <!-- 동적 SQL : 마이바티스의  기능으로 SQL 수행 중 조건, 반복, 특정 구문 추가 등을 동적으로 수행할 수 있음. -->
  <!-- <forEach>  태그 
   - collection : 반복 접근할 컬렉션 형태의 파라미터
   - item : collection에서 반복 시 마다 순차적으로 하나씩 접근한 요소
   - index : 현재 반복 인덱스 
   - open : forEach 시작 전 추가할 구문
   - close : forEach 종료 후 추가할 구문
   - separator : 반복 시 마다 SQL 사이에 추가할 구분자
   -->

 

+ 5. 이미지 업로드시 미리보기 출력 (JS)

	//파일 올라가있는지 check
	const filecheck = [0 , 0 , 0 , 0 , 0];
	
	//이미지 영역 클릭시 파일첨부창 뜨도록
	$(".images").on("click", function(){
		var index = $(".images").index(this);
		if(filecheck[index]==0){
			$("input[name=images]").eq(index).click()
		}
		else{
			if(confirm("이미지를 삭제하시겠습니까?")){
				$(this).children("img").removeAttr("src");
				$(this).children("img").css("display","none");
				$("input[name=images]").eq(index).val("");
				filecheck[index]=0;
			}
		}
	})
	
	//이미지 첨부되면 미리보기 뜨도록
	$("input[name=images]").on("input",function(){
		var index = $("input[name=images]").index(this);
		
		if(this.files[0]){
			console.log("done"+index);
			filecheck[index]=1;
			console.log(filecheck[index]);
			var reader = new FileReader();
			reader.readAsDataURL(this.files[0]);
			reader.onload = function(e){
				$(".images").eq(index).children("img").attr("src", e.target.result);
				$(".images").eq(index).children("img").css("display", "block");
			}
		}
	})

 

 

 

스프링의 핵심 가치 = 객체지향 프로그래밍이다

그런다면 좋은 객체지향 프로그래밍이란?
--객체를 설계할 떄 역할과 구현을 명확히 분리해야 함--

==  구현을 뭐로 바꾸든 역할은 변함없어야한다 ==
1) SRP : 단일책임원칙
-한 클래스는 하나의 책임만 가져야한다
2) OCP : 개방-폐쇄 원칙 
- 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다 (다형성 의미)
3) LSP 리스코프 치환 원칙 : 

-프로그램의 객체는 정확성을 깨지 않으면서 하위 타입 인스턴스로 바꿀 수 있어야한다
4) ISP 인터페이스 분리 원칙 : 

-인터페이스 여러개가 범용 한개보다 낫다
5) DIP 의존관계 역전 원칙 : 

-추상화에 의존해야지 구체화에 의존하면 안된다 ( 인터페이스에 의존해라)

 

이를 위해 다형성이 중요하다! 하지만 다형성 만으로는 쉽게 갈아끼우듯 사용이 어려움 -> 스프링의 탄생

 

EX) 인터페이스1 = new 구현체 1

이라는 구문에서 구현체 1이 구현체 2로 바뀐다면?

---->정의하는 new 코드를 바꿔주어야한다

 

결국 해당 클래스는 인터페이스를 사용하며 DIP를 준수해서 추상화에 의존한것처럼 보였지만 

사실 인터페이스와 구현체 둘 전부에 의존해있는 상태인 것이다

-> 이러한 문제를 해결하기 위해 생성자 안에 무엇을 전달받던 그냥 받기만 하는 상태로 만들어

자신의 역할에만 집중할 수 있도록 해주고

누군가가 생성의 역할을 해줘야 좋은 설계이다. 그리고 이것이 스프링의 원리가 된다!

 

 

스프링에서 트랜잭션을 처리하는 방법

 

1. 코드 기반 처리 방법 (기존 commit, rollback 을 이용)

 

2. 선언적 트랜잭션 처리방법 ( <tx: advice> xml 방식 / @Transactional 어노테이션 방식 ) 

※ 어노테이션 방식은 조건이 있다

2-1 트랜잭션 매니저가 Bean으로 등록 됨

2-2 <tx:annotation-driven /> 태그가 존재

@Transactional 어노테이션은 rollback을 위한 어노테이션이다 

(커넥션 반환 시 아무 트랜잭션 처리가 되어있지 않다면 자동 commit이기 때문이다)

- 기본적으로 스프링은 SQLException 을 unChecked Exception ( 명시적으로 예외처리 안해도 됨 )

으로 바꿔놨기 떄문에 예외처리를 해놓지 않고 Exception 발생시 트랜잭션으로 rollback을 수행한다는 개념이다

 

2-1, 2-2 ->

root-context에 가서

1. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
   </bean>

 <!-- 스프링에서 사용하는 proxy를 이용한 트랜잭션 제어가 안될 경우 추가적인 트랜잭션 매니저를 추가해서 문제 해결 -->

2. namespace - tx 체크 후

<tx:annotation-driven /> 작성

 

=> @Transactional(rollbackFor = Exception.class)

rollbackFor  속성 : 어떤 예외 발생 시 롤백 수행할 지 지정

스프링에서 예외처리하는 방법

 

1. 메소드별 try-catch /throws 예외 처리

 

2. 컨트롤러 별로 예외 처리 -@ExceptionHandler

 

3. 전역(모든 클래스) 에서 발생하는 예외를 하나의 클래스에서 처리

 

 

[  Mybatis  ]
데이터의 CRUD를 보다 편하게 하기 위해 xml로 구조화한 Mapper 설정 파일을 통해
 JDBC를 구현한 영속성 프레임워크

 

=> SQL 내부에 값을 직접 작성 가능 (? 안쓰고 바로 가능)
=> 조회 rs -> VO 객체로 옮겨담기 과정 축소됨
=> stmt, pstmt 기호화 (생성 X , 섞어서 사용)
=> 동적 SQL (SQL 내부에 if, for 등을 사용 가능)

 

<설정>

1. pom.xml 에 OJDBC 라이브러리 추가

 

<!-- mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 -->
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.1.0.0</version>
</dependency>

 

2. spring - jdbc 추가

<!-- mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

3. mybatis 추가

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

 

4. mybatis Spring 추가

<!-- mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

 

4. DBCP 사용 추가

<!-- mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>

 

5. mybatis.config 설정하기 

<동작구조>

--> SqlSession 동작을 살펴보면 됨

SqlSession Factory Bean => SqlSessionTemplate  => 실행

(mybatis.config.xml에 설정)       (mapper.xml에 설정)

 

1. Mybatis 설정용 xml파일 DTD(Document Type Definition 추가 

 

(경로) Window - Preferences - XML Catalog - User Specified Entries -add

 

<Config>

[ location 작성] = http://mybatis.org/dtd/mybatis-3-config.dtd (빈칸없이 그대로)

 

[Key 작성] = -//mybatis.org//DTD Config 3.0//EN

 

<Mapper>

[ location 작성] = http://mybatis.org/dtd/mybatis-3-mapper.dtd (빈칸없이 그대로)

 

[Key 작성] = -//mybatis.org//DTD Mapper 3.0//EN

 

 

2. resources에 mybatis-config.xml 생성 후 설정하기

<settings>

     <setting name="jdbcTypeForNull" value="NULL"/>
</settings>

// insert update 에 사용되는 값 중 null 이 있을 경우 NULL을 대입하도록 함 (NOT NULL제약이 없는 경우만 가능)

 

<!-- mapper(SQL이 작성된 파일) 위치 등록 부분 -->
<mappers>
       <mapper resource="/mappers/member-mapper.xml" />
</mappers>

 

<!-- mapper에서 사용되는 VO를 간단히 부르기 위한 별칭 지정 -->
<typeAliases>
       <typeAlias alias="Member" type="edu.kh.fin.member.model.vo.Member"></typeAlias>
</typeAliases>

 

3. root-context.xml 설정 추가

 

<!-- Root Context: defines shared resources visible to all other web components -->
<!-- root-context.xml
web.xml 파일이 가장 먼저 읽어 들이는 설정 파일.
프로젝트 전체에 공유되는 자원(DB연결, 트랜잭션처리, 파일업로드 등)
설정 내용을 작성
 -->
 
<!-- DBCP 사용을 위한 DataSource를 Bean으로 등록!! -->
   <!-- DataSource란? : java에서 Connection Pool을 지원하기 위한 인터페이스 -->
   <!-- BasicDataSource : DataSource인터페이스를 구현한 클래스, 아파치 commons.dbcp에서 제공 -->
   <!-- destroy-method="close" : 주어진 세션을 자동으로 반환(close)하라는 설정 -->
   <bean id="dataSource"
      class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close">

      <property name="driverClassName"
         value="oracle.jdbc.driver.OracleDriver" />
      <property name="url"
         value="jdbc:oracle:thin:@  주소  :xe" />
      <property name="username" value=" DB아이디 "/>
      <property name="password" value=" DB비밀번호 " />

      <!-- defaultAutoCommit: SQL 수행 후 자동 COMMIT 설정. (기본값 : true) -->
      <property name="defaultAutoCommit" value="false" />

      <!-- 커넥션 풀 설정 -->
      <property name="initialSize" value="10" /> <!-- 초기 커넥션 수, 기본 0 -->
      <property name="maxTotal" value="500" /> <!-- 최대 커넥션 수, 기본 8 -->
      <property name="maxIdle" value="100" /> <!-- 유휴 상태로 존재할 수 있는 커넥션 최대 수, 기본 8 -->
      <property name="minIdle" value="10" /> <!-- 유휴 상태로 존재할 수 있는 커넥션 최소 수, 기본 0 -->
      <property name="maxWaitMillis" value="-1" /> <!-- 예외 발생 전 커넥션이 반환 될 떄 까지 대기하는 최대 시간(ms), 기본 -1(무기한) -->
   </bean>
   
      <!-- SqlSession : sql구문을 DB에 전달, 실행하는 객체 (Connection 역할)
      SqlSessionFactory : SqlSession을 만드는 객체 
      sqlSessionFactoryBean : mybatis 설정 파일(mybatis-config.xml)과 Connection Pool 정보를 이용하여 SqlSessionFactory를 만드는 객체 
      sqlSessionTemplate : SqlSession 객체에 트랜잭션 처리 역할이 가능하도록 하는 객체 -->
   <!-- 마이바티스 SqlSession 등록하기 (xml 방식으로 bean 등록) -->
   <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
      <!-- mybatis-config.xml 설정 불러오기 -->
      <property name="configLocation" value="classpath:mybatis-config.xml" />
      <property name="dataSource" ref="dataSource" />
   </bean>
   <!-- SqlSessionTemplate : 기본 SQL 실행 + 트랜잭션 관리 역할을 하는 SqlSession을 생성할 수 있게 하는 객체(Spring bean으로 등록해야함.) -->
   <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
      <constructor-arg ref="sqlSessionFactoryBean" />
   </bean>
   <!-- 스프링에서 사용하는 proxy를 이용한 트랜잭션 제어가 안될 경우 추가적인 트랜잭션 매니저를 추가해서 문제 해결 -->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
   </bean>

 

5. mapper 설정하기

 

resources’폴더에 ‘mappers’ 폴더 생성 후 mapper.xml 파일 생성

 

[ 사용 ] 

---- 최상단 : 마이바티스 매퍼 설정임을 선언 & namespace 선언 ----

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >


<mapper namespace="memberMapper">

    <!-- 내용 -->

</mapper>

 

---- <resultMap> 태그 ----

 

-> SELECT 조회 결과(ResultSet)의 컬럼명과 VO객체의 필드명이 일치하지 않을 때 어떤 컬럼과 필드가

매칭되어야 하는지 지정하는 태그

 

->resultMap의 type 속성은 실제로 구현해 놓은 자바 POJO 객체를 사용해야 하며, mybatis-config.xml에서 typeAlias를 지정하지 않은 경우, 패키지 명부터 클래스 명 까지 모두 기술해야 됨

 

---- <insert> <update> <delete> 태그로 SQL문 작성 ----

(개별로 id , parameterType , resultType 등 속성 작성)

 

 

서블릿에서 일일이 getParameter로 받아내던 것과 달리 스프링에서는 더 쉽게 파라미터들을 받아올 수 있다!

 

파라미터 받는 방법 5

 

1. HttpServletRequest

-> 서블릿 방식

2. @RequestParam

-> value / required / defaultValue 속성을 가지고 있음

-> value : input태그 name

-> required : 파라미터 필수 여부 ( 기본값 : true)

-> defaultValue : 전달 받은 파라미터 값이 없을 때 기본 값

3. @RequestParam을 생략

-> ("태그명==매개변수명") 일때

4. @ModelAttribute == 커맨드객체

-> 요청 시 전달 받은 파라미터를 객체 형태로 매핑하는 역할을 해줌

-> 해당 객체 클래스에 기본생성자 getter setter등 생성 

-> 해당 객체 멤버 변수명과 input 태그 name 일치

5. @ModelAttribute을 생략

 

 

 

+ Recent posts