구조분석

  배열은 포인터와 동일한 방식으로 동작하며 배열의 이름은 배열의 원소의 첫번째 주소가 된다.

  유일한 차이점이라고 한다면 포인터는 변수이며 배열의 이름은 상수이다.

#include <stdio.h>

int main(void){
  int a = 10;
  int b[10];
  b = &a;
}

  위 예제에서는 배열인 b에 a의 주소값을 넣어주겠다는 것인데 에러가 발생한다.

  배열의 이름은 상수이기 때문에 변경할 수 없는데 a의 주소값으로 변경하겠다고 하니 에러가 발생하는 것이다.

 

  하지만 반대로 포인털르 배열처럼 사용하는 것은 문제가 없다.

#include <stdio.h>

int main(void){
  int a[5] = {1, 2, 3, 4, 5};
  int *b = a;
  printf("%d\n", b[2]);
}

  이렇게 작성하게 된다면 a배열의 3번째 값인 3이 출력되게 된다.

  a라는 배열을 포인터변수 b에 넣어주고 포인터 변수 b에서의 2번 인덱스의 값을 가져오라고 하니 포인터변수의 값인

  a 배열의 값 중에서 2번인덱스의 값을 가져오는 것이다.

 

  배열의 이름은 배열의 첫번째 원소의 주소이다.

#include <stdio.h>

int main(void) {
  int a[5] = {1, 2, 3, 4, 5};
  int *b = &a[0];
  printf("%d\n", b[2]);
}

  이 코드와 위의 코드의 차이점은 a를 주소값으로 불러왔는지 그냥 배열이름으로 불러왔는지의 차이다.

  '배열의 이름은 배열의 첫번째 원소의 주소' 라는 말이 이 말이다.

  배열의 이름을 써도 a배열의 0번인덱스의 주소가 되는 것이고 주소값으로 &a[0]으로 사용해도 똑같은 값을 찾는다는

  것이다.

  

  포인터는 연산을 통해 자료형의 크기만큼 이동한다는 특징이 있다.

  따라서 int 포인터는 4byte씩 이동한다.

#include <stdio.h>

int main(void) {
  int a[5] = {1, 2, 3, 4, 5};
  int i;
  for(i = 0; i < 5; i++){
    printf("%d ", a + i);
  }
}

  위 코드를 출력하면 4만큼씩 증가하는 값을 확인할 수 있다.

  출력문에서 *(a + i)로 바꿔준다면 값을 출력할 수 있게 되며 1 2 3 4 5 라는 결과값이 출력되는데 a[i]와 동일하다.

 

 

#include <stdio.h>

int main(void) {
  int a[5] = {1, 2, 3, 4, 5}
  int *p = a;
  printf("%d\n", *(p++));
  printf("%d\n", *(++p));
  printf("%d\n", *(p + 2));
}

  위 예제의 경우 1 3 5가 출력이 된다. 포인터 변수 p에 배열 a를 넣어주었다. p는 a[0]이 되는것이기 때문에 출력했을때

  1이 나오게 되는데  첫 출력문에서는 p가 후치증가이기 때문에 1을 출력한 뒤에 p가 1증가하여 a[1]이 된다.

  그 다음 출력문은 전치증가로 먼저 증가한다음 출력하기 때문에 a[2]의 값을 출력하게 되므로 3이 출력되고

  마지막 출력문은 p에 2를 더한 뒤 출력하라고 했기 때문에 a[4]의 값을 출력하게 되고 5가 출력되는 것이다.

 

#include <stdio.h>

int main(void) {
  int a[2][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10} };
  int (*p)[5] = a[1];
  int i;
  for(i = 0; i < 5; i++){
    printf("%d ", p[0][i]);
  }
}

  위 예제의 결과값은 6 7 8 9 10이다.

  포인터 변수 p는 크기는 5이며 a배열의 1번 인덱스의 행 주소를 갖고 있게 된다.

  a의 1번 행은 6, 7, 8, 9, 10이기 때문에 출력값 역시 6 7 8 9 10이 되는 것이며 출력시에는 이중포인터이기 때문에

  p[0][i]로 처리해야 하는 것이다.

 

 

레퍼런스

패스트캠퍼스 올인원 패키지 - 소프트웨어 베이직

'C' 카테고리의 다른 글

구조체  (0) 2020.12.30
함수포인터  (0) 2020.12.29
동적메모리 할당  (0) 2020.12.29
전역변수, 지역변수, 정적변수, 레지스터변수, 함수의 매개변수처리  (0) 2020.12.27
C언어 기초  (0) 2020.12.27

전역변수(Global Variable)

  프로그램의 어디서든 접근 가능한 변수이다.

  말 그대로 전체를 총괄하는 변수이며 main함수가 실행되기 전에 프로그램의 시작과 동시에 메모리에 할당된다.

  프로그램의 크기가 커질수록 전역변수로 인해 프로그램이 복잡해질 수 있고 메모리의 데이터영역에 적재(load)된다.

#include<stdio.h>

int a = 5;

void ChangeValue(){
  a = 10;
}

int main(void){
  printf("%d\n", a);
  changeValue();
  printf("%d\n", a);
}

  위 코드는 전역변수의 가장 기본적인 사용방법이며 int a = 5; 이것이 전역변수이다.

  전역변수이기 때문에 main함수가 아닌 changeValue 함수에서도 바로 사용이 가능하다.

  함수가 여러개 있는 코드일 때 a의 값이 계속 필요하다면 전역변수로 사용한다면 편해진다.

 

지역변수(Local Variable)

  프로그램에서 특정한 블록에서만 접근할 수 있는 변수이다.

  함수가 실행될때마다 메모리에 할당되어 함수가 종료되면 메모리에서 해제되고 메모리의 스택영역에 기록된다.

#include<stdio.h>

int main(void){
  int a = 7;
  if(1) {
    int a = 5;
  }
  printf("%d", a);
}

  위 코드에서 결과값은 7이다.

  5라고 혼동 될 수 있지만 지역변수의 특성상 5라는 값은 if문 안에서만 영향을 끼치고 if문을 벗어나는 순간 다시 a의

  값은 7이 되기 때문이다.

  7의 값을 갖고 있는 a와 5의 값을 갖고 있는 a는 다르다고 생각하는게 좀 더 편할 것 같다.

  자바 공부할때는 같은 변수명으로 사용한 적이 없어서 이 코드를 보자마자 어? 이랬었는데

  'if문 안에 있는 a는 if문이 종료되자마자 사라지는 것'이라고 생각하니 좀 이해가 편했다.

  if문 안에 새로운 변수를 만들었다고 생각하면 된다.

  만약 7을 5로 바꿔서 출력하고 싶은것이라면 if문안에 있는 a의 자료형을 빼고 a = 5; 이렇게 작성한다면

  출력값은 5가 된다.

 

정적변수(Static Variable)

  특정한 블록에서만 접근할 수 있는 변수이다.

  프로그램이 실행될 때 메모리에 할당되어 프로그램이 종료되면 메모리에서 해제되며 데이터영역에 적재(load)된다.

#include<stdio.h>

void process(){
  static int a = 5;
  a = a + 1;
  printf("%d\n", a);
}

int main(void){
  process();
  process();
  process();
}

  결과값은 6 7 8이 출력된다.

  정적변수로 처음 한번 메모리에 적재되고 나면 그 다음 호출 시 부터는 값이 변경되지 않고 기존 연산이 처리된 a의

  값을 계속해서 갖고 있게 되는 것이다.

  그래서 메인함수에서 첫 process로 넘어왔을 때 5의 값을 갖고 시작해서 연산이 끝난 후 6이 되고

  두번째 process에서는 a의 값이 6이 되고 연산처리해서 7이 출력되는 것이다.

 

레지스터변수(Register Variable)

  메인 메모리 대신 CPU의 레지스터를 사용하는 변수이다.

  레지스터는 매우 한정되어 있으므로 실제로 레지스터에서 처리될지는 장담할 수 없다.

#include<stdio.h>

int main(void){
  register int a = 10, i;
  for(i = 0; i < a; i++){
    printf("%d ", i);
  }
}

  위 코드의 출력값은 0 1 2 3 4 5 6 7 8 9이다.

  레지스터 변수를 사용했기 때문에 일반변수를 사용했을때 보다 더 빠를거라는 기대를 할 수 있게 된다.

  위 예제는 큰 연산이 없어서 체감은 별로 없고 알아두었다가 나중에 복잡한 연산을 할 때 사용해야 체감할 것 같다.

 

함수의 매개변수 처리

  함수를 호출 할 때 함수에 필요한 데이털르 매개변수로 전달한다.

  전달 방식은 값에 의한 전달 방식과 참조에 의한 전달방식이 있다.

  값에 의한 전달 방식은 단지 값을 전달하므로 함수 내에서 변수가 새롭게 생성된다.

  참조에 의한 전달 방식은 주소를 전달하므로 원래의 변수 자체에 접근할 수 있다.

#include<stdio.h>

void add(int a, int b){
  a = a + b;
}

int main(void){
  int a = 7;
  add(a, 10);
  printf("%d\n", a);
}

  위 코드의 결과값은 7이다.

  처음 봤을 때 add함수의 a에 7을 b에 10을 넣어주니 17이 나올거라고 생각할 수 있지만

  add함수 내에서만 적용이 된 것이기 때문에 기존에 있던 a의 값인 7이 그대로 출력되는 것이다.

  만약 add함수에서 출력을 했다면 17이 출력이 되었겠지만 메인에서는 그게 안된다.

 

  이럴때 17을 출력하고 싶다면 참조에 의한 전달 방식을 사용해야 한다.

  참조에 의한 전달 방식은 함수의 매개변수로 값을 전달하는 것이 아니라 변수의 주소를 전달한다.

  이러한 방식을 이용하면 원래 변수의 값에 접근하여 값을 변경할 수 있다.

 

#include<stdio.h>

void add(int *a, int b){
  *a = (*a) + b;
}

int main(void){
  int a = 7;
  add(&a, 10);
  printf("%d\n", a);
}

  이 결과값은 17이 나오게 된다.

  두 코드의 차이점은 매개변수로 포인터 변수를 보냈느냐 아니냐의 차이일 뿐이다.

  a는 7이라는 값을 갖고 있고

  add함수에 보낼때 &a로 a의 주소값을 보내게 된다. 그럼 add함수에서는 포인터 변수를 받아서 b에 넘겨준 10을 더한

  값인 17을 만들고 이것을 a의 값을 참조해서 더한 값이 되기 때문에 a가 17로 변하게 되는 것이다.

  첫 코드는 7을 단순히 가져와서 연산처리한거고 두번째 코드는 7이 있는데로 가서 연산처리 했다고 이해하면 좀 더

  편할듯하다.

 

 

레퍼런스

패스트캠퍼스 올인원 패키지 - 소프트웨어 베이직

'C' 카테고리의 다른 글

구조체  (0) 2020.12.30
함수포인터  (0) 2020.12.29
동적메모리 할당  (0) 2020.12.29
포인터 배열의 구조  (0) 2020.12.28
C언어 기초  (0) 2020.12.27

Visual Studio 2019 에서의 프로젝트 생성.

  파일 - 새로만들기 - 프로젝트 - 빈프로젝트 - 솔루션 탐색기 - 소스파일 오른쪽클릭 - 추가 - 새항목.

  기본적으로 .cpp로 나와있지만 .c로 바꿔서 생성한다면 c로 생성이 가능하다.

  C++이 C를 기반으로 한 것이기 때문에 확장자 변경만으로 C파일로 변경이 가능하다.

  

프로젝트 빌드 결과

  솔루션 탐색기 오른쪽클릭 - 파일탐색기에서 폴더 열기 - debug로 들어가면 실행파일이 보인다.

 

라이브러리

  C/C++에서는 #include 명령어를 이용해 다양한 라이브러리를 불러올 수 있다.

  제일 많이 봤던 stdio는 Standard Input output 으로 입출력 라이브러리다.

 

코드의 시작

  자바와 마찬가지로 main함수로부터 시작한다.

  함수는 return 값이 없을 수 있으나 메인함수에서는 항상 0을 반환하는 것이 일반적이라고 한다.

  C에서의 기본 출력함수는 printf이다.

  system함수는 운영체제의 기본적인 기능을 이용할 수 있다.

  공부 중에는 system("pause"); 를 가장 많이 사용하였고 pause명령어는 키보드를 입력하기 전까지 대기하는 기능을

  수행한다.

 

기본적인 자료형

  int, string, char, double, long long, bool.

  int는 정수, string은 문자열, char은 문자, double은 실수, long long은 숫자가 긴 정수형, bool은 boolean이다.

  string은 C 문법에는 포함되지 않지만 C++에서는 사용한다.

  C에서 문자열을 포현하기 위해서는 char을 배열형태로 만들어서 사용하면 된다.

  char a[20] = "HELLO WORLD" 이렇게 사용하면 문자열 형태처럼 사용할 수 있다.

 

입력

  C에서 값을 입력받기 위해서는 scanf를 사용한다. scanf(%d, &a);

  보통 실무에서는 많이 사용하지 않고 처음 이해하고 배울때만 사용을 많이 한다고 한다.

  scanf는 C에서 취약한 함수로 분류되기에 visual studio에서는 바로 사용할 수 없도록 막아두었다.

  그래서 #define _CRT_SECURE_NO_WARNINGS 를 include위치에 정의해줘야 사용할 수 있다.

  scanf에 &를 사용하는 이유는 특정한 메모리 주소에 접근하여 데이터를 수정할 수 있도록 하기 위해서이다.

  C언어에서는 포인터를 사용할 때 &를 사용한다.

  메모리주소에 얼마만큼의 크기로 데이터를 쓸 지 경정해야 하는데 그럴때 사용하는 것이 형식지정자이다.

int(4byte)

%d를 이용해서 정수형 데이터를 입력 및 출력

long long(8byte)

%lld 를 이용해서 큰 정수형 데이터를 입력 및 출력

double(8byte)

입력시 %lf 출력시 %f로 큰 실수형 데이터를 처리

float(4byte)

%f를 이용해서 실수형 데이터를 입력 및 출력

string(제한없음)

%s를 이용해서 문자열 데이터를 입력 및 출력

char(1byte)

%c를 이용해서 문자 데이터를 입력 및 출력

  double에서 입출력시 사용하는 형식지정자가 다른 이유는 입력할 때는 크기를 지정해줘야 하지만 출력할때는

  값 자체를 이용해서 출력하는 것이므로 구체적인 크기를 지정하지 않아도 되는 것이다.

  만약 %자체를 문자로 출력하고 싶다면 %%로 입력해줘야 문자로 인식하고 % 한개가 출력되게 된다.

  실수형에서는 .0으로 소수점 자리수를 지정해준다.

  %.2f 이런식으로 표시하는데 .은 소수점을 의미하고 2는 소수점 자리 수, f는 실수라는 의미이다.

  

  한자리씩 끊어서 입력하는 방법도 있는데

  scanf("%1d%1d%1d", &a, &b, &c);

  printf("%d %d %d",a,b,c);

  이렇게 코드를 작성하고 576을 입력한다면 5 7 6 이렇게 출력되게 된다.

  한자리씩 끊어서 변수에 입력된다고 생각하면 된다.

 

이스케이프시퀀스(Escape Sequence)

  특정한 표현을 출력하기 위해 사용하는 문법이다.

\n

줄바꾸기

\t

수평 탭 넣기

\\

백슬레시 넣기

\”

큰 따옴표 넣기

\b

백스페이스 넣기

 

포인터

  C에 대해 얘기를 들을 때 마다 나왔던 포인터이다. 굉장히 중요하다는 얘기만 많이 들었다.

  포인터 변수는 특이한 변수로 메모리 주소를 저장한다.

  int a = 5;

  int *b = &a;

  이러한 코드가 있다면 b가 포인터 변수다. b는 &a라는 값을 갖고 있는데 &는 주소값을 의미하는 주소연산자이기

  때문에 a의 주소값을 포인터 변수인 b가 갖고 있게 되는 것이다.

  C에서는 그 주소값으로 해당 주소의 값을 가져오기 때문에 포인터 변수 b를 이용해 5라는 a의 값을 가져오는 것이

  가능하다.

  포인터는 특정 메모리의 주소값에 직접적으로 접근할 수 있기 때문에 사용할 때 조심해야 한다.

 

포인터 관련 연산자

 

주소연산자(&)

변수 앞에 붙어서 변수의 메모리 시작 주소값을 구한다

포인터(*)

포인터 변수를 선언할 때 사용한다

간접 참조 연산자(*)

선언된 포인터 변수가 가리키는 변수를 구한다

 

문자 입출력

  char은 아스키 코드를 기준으로 받아 들인다.

  a = 65;

  printf("%c\n", a);

  이 코드를 출력하게 되면 아스키코드표의 65에 해당하는 A가 출력되게 된다.

  그래서 문자 입출력함수가 따로 있는데 getchar()이다.

  이것은 단 하나의 문자를 입력 받는다.

 

  입력 버퍼로 인해 흔히 발생하는 오류가 있다.

int main(){
  int a;
  char c;
  scanf("%d", &a);
  printf("%d\n", a);
  scanf("%c", &c);
  printf("%c\n", c);
}

  위 코드에서 숫자를 입력하고 엔터를 치게 되면 그대로 입력이 끝나게 된다.

  숫자는 a에 들어가게 되지만 엔터 또한 문자로 입력되면서 엔터가 c에 들어가면서 입력이 끝나게 되는 것이다.

  char은 아스키코드로 관리하다보니 엔터도 문자로 인식하기에 문자로 입력을 받게 되는 것이다.

 

  이때는 남아있는 입력버퍼를 지워줘야 한다.

int main(){
  int a;
  char c;
  scanf("%d", &a);
  printf("%d\n", a);
  
  int temp;
  while((temp = getchar()) != EOF && temp != '\n'){}
  
  scanf("%c", &c);
  printf("%c\n", c);
}

  위 코드에서 EOF는 End Of File의 약자로 파일의 끝을 의미한다.

  while문을 보면 파일의 끝이나 개행 문자를 만났을 때 입력을 멈추고 버퍼를 비운다는 의미라고 한다.

  이 부분이 조금 이해가 안되는 부분이다.

  자바를 사용하면서는 !=는 not의 의미이고 &&는 AND의 의미인데 왜 파일의 끝이나 개행문자를 만났을 때 입력을

  멈추고 버퍼를 비운다는 것인지 잘 이해가 되지 않는다.

  그래서 생각해보니 temp 아래로 코드가 더 있기 때문에 'EOF가 아니다.'는 true가 되고 temp에는 값을 주지 않았기

  때문에 \n이 아닌게 되므로 입력버퍼를 비워준다는게 맞는 것 같다. 이거는 다른 C 강의도 찾아보고 기억해야

  할 것 같다.

  

문자열

  C언어에서 문자열은 메모리 구조상에서 마지막에 null값을 포함한다.

  printf함수를 실행하면 컴퓨터는 내부적으로 null을 만날때까지 출력하도록 되어있기 때문이다.

  HELLO WORLD를 출력한다고 하면

0

1

2

3

4

5

6

7

8

9

10

11

H

E

L

L

O

 

W

O

R

L

D

\0
(null)

  이렇게 들어가는 것이다.

  그래서 HELLO WORLD는 중간 공백을 포함해서 11글자에 마지막 null값까지 총 12자리의 배열 크기가 되는 것이다.

 

  문자열 형태로 포인터를 사용하면 포인터에 특정한 문자열의 주소를 넣게 된다.

int main(void) {
	char *a = "Hello World";
	printf("%s\n", a);
}

  이런식으로 배열 형태가 아닌 포인터 변수에 문자열 자체를 마치 상수처럼 넣을 수 있다.

  이때 이렇게 큰 따옴포안에 문자열이 들어가는 구조를 문자열 리터럴이라고 한다.

  이렇게 작성하게 되면 컴파일러가 알아서 문자열이 특정한 메모리 주소에 저장될 수 있도록 남아있는 메모리

  공간중에서 주소를 결정해준다.

  그리고 그 주소를 포인터인 *a가 갖고 있게 되는 것이다.

  이렇게 문자열을 리터럴 방식으로 넣어준다면 읽기전용으로 상수형태로 넣어주는 것이다.

  말 그대로 읽기전용이기 때문에 중간에 문자열을 바꿀 수 없고 굳이 변경해야 한다면 포인터에 선언한 문자열 자체를

  변경해야 한다.

  포인터로 문자열을 선언했다고 하더라고 배열처럼 처리하는 것은 가능하다.

 

int main(void){
  char *a = "Hello World";
  printf("%c\n", a[1]);
  printf("%c\n", a[4]);
  printf("%c\n", a[8]);
}

  출력문을 이렇게 변경해준다면 출력결과는

   e

   o

   r

  이렇게 출력된다.

  포인터로 선언되었지만 그 인덱스를 갖고 출력하는 것은 배열과 동일한 것이다.

 

  C에서 문자열 함수는 <string.h> 라이브러리에 포함되어 있다.

strlen()

문자열의 길이를 반환

strcmp()

문자열 1 문자열 2보다 사전적으로 앞에 있으면 -1 뒤에 있으면 1 반환

strcpy()

문자열을 복사

strcat()

문자열 1 문자열2 더한다

strstr()

문자열 1 문자열 2 어떻게 포함되어 있는지를 반환

 

  strlen은 중간 공백가지 포함해서 문자의 수를 출력해준다.

  strlen의 경우는 <string.h>를 선언하지 않아도 최근에는 사용이 가능하다고 한다.

 

  strcmp는 사전순이라고 되어있는데 오름차순이라고 생각하면 된다.

  a와 c를 비교한다면 a가 사전순으로 앞에 있기 때문에 -1이 출력되는 것이고 반대로 비교한다면 1이 출력된다.

 

  strcpy는 

int main(void){
  char a[20] = "Hello";
  char b[20] = "World";
  strcpy(a, b);
  printf("복사된 문자 : %s\n", a);
}

  이렇게 작성했을 때 World가 출력되게 된다.

  앞에 있는 문자열에 뒤에 있는 문자열을 복사해서 붙여넣기 하는 것이다.

 

  strcat은 두 문자열을 붙여준다고 생각하면 된다.

  위 코드에서 strcpy가 아닌 strcat으로 바꿔준다면

  HelloWorld가 출력되게 되는 것이다.

  여기서 주의해야 할 점은 a에 b가 합쳐지는 것이기 때문에 배열 크기를 조절해줘야 한다.

  b가 들어가도 될만큼의 여유공간을 a가 갖고 있어야 한다.

 

  strstr은 긴 문자열에서 짧은 문자열을 찾아 그 위치를 반환한다.

int main(void){
  char a[20] = "I like you";
  char b[20] = "like";
  printf("문자열 : %s\n", strstr(a, b));
}

 

  위 코드에서  b의 값인 like의 위치를 찾아 그 뒤의 문자열까지 전체 출력해주기 때문에 like you가 출력된다.

  

 

문자열 입출력 함수(gets)

  gets는 문자열 입출력을 수행하는 함수이며 scanf는 공백을 만날때까지 입력을 받지만

  gets는 공백까지 포함하여 한줄을 입력 받는다.

  scanf("%s", &a); 이렇게 사용했지만 gets는 gets(a)이렇게 사용한다.

  

  실무에서는 gets보다는 gets_s를 많이 사용한다고 한다.

  gets_s(a, sizeof(a));

  이렇게 사용하는데 버퍼의 크기를 지정해주는 방식이다.

  gets_s는 버퍼의 크기를 벗어나게 된다면 런타임 오류가 발생하기 때문에 안정성이 보장된다.

  기존의 gets함수는 버퍼 이상으로 덮어쓰기 때문에 취약점이 존재한다.

 

 

 

레퍼런스

패스트캠퍼스 올인원 패키지 - 소프트웨어 베이직

'C' 카테고리의 다른 글

구조체  (0) 2020.12.30
함수포인터  (0) 2020.12.29
동적메모리 할당  (0) 2020.12.29
포인터 배열의 구조  (0) 2020.12.28
전역변수, 지역변수, 정적변수, 레지스터변수, 함수의 매개변수처리  (0) 2020.12.27

포폴 올리려고 가볍게 만드는데 Controller에서 mapper를 못찾는다고 에러발생..

org.apache.ibatis.binding.BindingException : Invalid bound statement (not found):com.portpolio.mapper.ptMapper.PortpolioList

 

처음에는 xml이랑 mapper에서 오타있는줄 알고 싹 다 뒤지고 복사 붙여넣기까지 다 해가면서 해봤지만 안됨..ㅠㅠ

오타는 아닌거같아서 검색해봤으나 오타 아니면 classpath잘못 넣었거나 아니면 xml이 저장될 경로를 잘못 설정한 경우라는 글을 찾았다.

 

그래서 classpath를 확인했지만 정상.

 

경로를 혹시.... 하면서 하나하나 디렉토리를 다시 만들었더니 계단식으로 보이네...?

인텔리제이로 작성중인데 resources/mybatis/mapper/*.xml로 해놓았으나

디렉토리는 resources 밑에 mybatis.mapper로 생성되있었다.

이번에 처음 알게 된건데 디렉토리명이 mybatis.mapper로 생성된거지 mybatis 밑에 mapper로 생성된게 아니라고했다.

 

그래서 아! 이거구나 하고 다시 열어봤지만 안되네????

 

yml이랑 gradle다 뒤졌지만 못찾다가 오타 발견...

yml에서 mybatis를 mybaits로 잘못쳤다... 굉장히 자주 내는 오타였는데 이거부터 찾았으면 금방 해결했을 것을 ㅠㅠ

 

인텔리제이에서 디렉토리 만들 때 조심하고 오타 조심하자! 

BoardProject 기능 구현이 끝났다.

마지막으로 기능들 체크하는 도중에 찾은 문제점.

Oracle을 사용한 계층형 게시판에서 원글을 삭제하면 답글을 삭제하지 않았음에도 리스트에 출력이 되지 않는다.

 

SELECT  *
FROM (
      SELECT  ROWNUM AS RNUM, A.*
      FROM  (
             SELECT * 
             FROM HierarchicalBoard 
             WHERE BoardNo > 0
             <include refid="search"></include>
             START WITH BoardUpperNo = 0
             CONNECT BY PRIOR BoardNo = BoardUpperNo
             ORDER SIBLINGS BY BoardGroupNo DESC
             ) A  
     ) 
WHERE RNUM BETWEEN ${rowStart} AND ${rowEnd}

쿼리문을 이렇게 작성해서 썼는데

START WITH BoardUpperNo=0 으로 작성했기 때문에

UpperNo 가 0인 원글이 존재해야 그 하위에 있는 답글 역시 출력되기 때문이다 ㅠㅠ

그래서 이것저것 알아보니 생각보다 처리방법에 대해 별다르게 적어주신 분이 없었다 ㅠ

보통 원글과 답글을 같이 삭제하거나 아니면 원글을 삭제하는것이 아닌 삭제된 글이라고 UPDATE를 하는 방법을 많이들 사용하셨다.

하지만 원글은 삭제하고 답글만 남겨놓고 싶었기에.... 이래저래 머리를 굴려보니 UpperNo가 0인 원글이 존재하지 않기 때문에 출력이 안된거니까 그럼 바로 밑 Indent 가 1인 답글의 UpperNo를 바꾸면 되지 않을까? 라는 생각이 들어 테스트했다.

 

원글 삭제 전에 해당 글번호를 UpperNo에 갖고 있는 row를 Count해주고.

그 Count가 0이면 그냥 삭제. 0이 아니라면 UpperNo가 원글의 BoardNo인 row들의 UpperNo를 0으로 Update한 뒤에!

원글을 삭제하도록 했다.

댓글도 있었기에 글 삭제 하나에 댓글삭제, Count, Update, 원글 삭제 이렇게 총 네개의 쿼리문을 사용했지만 일단 원하는 화면은 출력되었다!!!!!

mysql로 만들었을때는 각 컬럼들의 차순을 이용해서 만들었다보니 이런 문제가 없이 그냥 삭제만 해줘도 가능했는데 요고는 조금 쿼리문 자체를 고민을 해봐야하는 문제인것 같다.

삭제할일이 없게 되는 계층형을 출력하게 된다면 편하게 사용할 수 있을 것 같은데 이번처럼 게시판 형태로 삭제가 가능하다면 다른 방식의 쿼리문을 고민해봐야 할 것 같다.

 

지금까지 프로젝트 진행하면서 항상 하나의 데이터만 넘기거나 두개정도만 넘겨서 컨트롤러에서는 

@RepuestParam("ooo") String ooo

이런식으로 받아왔다.

 

대댓글을 구현하기 위해 만들다보니 데이터를 5개정도 한번에 보내서 받도록 하고싶었는데 해왔던것 처럼

Controller에서 RequestParam으로 다 받자니 너무 길어지고 보기도 싫고 불필요해보여서 한번에 받을 수 있는 방법을 찾기 시작함..

 

$(document).on("click", "#CommentReplyInsert", function(){
	
  var commentData = {
    CommentNo : $("#CommentReplyInsert").val(),
    CommentGroupNo : $("#CommentGroupNo").val(),
    CommentIndent : $("#CommentIndent").val(),
    CommentContent : $("#CommentReplyContent").val(),
    BoardNo : $("#BoardNo").val(),
  };
    
  var str = JSON.stringify(commentData);
    
  $.ajax({
    url: "/board/CommentReply",
    method: 'POST',
    dataType: 'json',
    data: str,
    contentType: "application/json; charset=UTF-8",
    success: function(data){
      alert("댓글 OK!");
      location.reload();
    },
    error : function(request, status, error){
      alert("code:" + request.status + "\n"
              + "message : " + request.responseText
              + "\n" + "error : " +error);
    }
  })
})

이렇게 보내주고!

@RequestMapping("/CommentReply")
@ResponseBody
  public void CommentReply(@RequestBody Map<String, Object> commentData) throws Exception{
    System.out.println("Data : "+commentData);
    System.out.println("BoardNo : "+commentData.get("BoardNo"));
    System.out.println("CommentNo : "+commentData.get("CommentNo"));
    System.out.println("CommentGroupNo : "+commentData.get("CommentGroupNo"));
    System.out.println("CommentIndent : "+commentData.get("CommentIndent"));
    System.out.println("CommentContent : "+commentData.get("CommentContent"));		
  }

컨트롤러에서는 이렇게 받았다!

 

방법이 좀 다양해서 이것저것 해봤는데 일단 지금 구성에서는 이렇게 하는 방법만 정상작동한다.

@RequestBody List<Map<String, Object>> 이렇게 받는것도

Gson 이용해서 받는것도 다 해봤지만 안됨...

 

올려주신 분들이 본인 데이터 처리를 어떻게 했다는 예시는 안보여주셔서 사실 차이점을 아직 잘 모르겠다...

JSON을 좀 더 알아봐야할듯 ㅠㅠ

 

만들던 프로젝트에서 .append로 input 과 button을 추가해주는 기능(?)을 구현해놓았다.

문제점은 추가한 button을 눌러도 반응이 없다는것...

 

$("#ReplyComment").append(
  "<input type=\"text\" id=\"CommentReplyContent\" name=\"CommentReplyContent\">" +
  "<button type=\"button\" id=\"CommentReplyInsert\">"+"작성"+"</button>"
)	

이렇게 추가하도록 만들었고

$(function(){
	$("#CommentReplyInsert").click(function(){
		alert("hi");
	})

당연하게 이렇게 불러오려고 작성했었다.

 

그러나 아예 버튼이 먹히지 않았다.

button에 onclick으로 넘기게 되면 되긴 되던데 별로 사용하고 싶지 않았었기에 방법을 찾아보니!!!

 

.append로 추가해준 것은 저렇게 받아올수가 없는 듯하다.

 

$(document).on("click", "#CommentReplyInsert", function(){
	alert("button hi");
})

 

찾은 방법이 이것이다.

 

이렇게 변경해주니 너무 잘됨!!

 

$(document).on은 DOM트리보다 위에 바인딩이 되어서 Selector와 일치하는 태그를 찾아서 실행한다.

 

대부분의 DOM 이벤트 들이 Tree의 꼭대기에서 시작해서 나뭇가지처럼 아래로 퍼지기 때문에 이것이 가능한 것이다.

 

DOM에 대해 좀 더 공부해야 할 필요성을 느낀다 ㅠㅠ

이미지 게시판 수정을 위해 JSON을 이용해서 해당 번호의 게시글 이미지를 가져오기를 원했다.

 

제목, 글 내용, 글 번호는 Controller에서 먼저 보내준 데이터로 사용했고 이미지데이터만 JSON을 사용해서 가져오는 방법을 사용했다.

 

$(document).ready(function(){
  var ImageNo = '<c:out value="${list.imageNo}"/>';
  $.getJSON("AttachList", {ImageNo: ImageNo}, function(arr){
    $(arr).each(function(i, attach){
      $("#preview").append(
        "<div class\"preview-box\" value=\"" + attach.imageStep + "\">" +
        "<img class=\"thumbnail\" src=\"IMG/" + attach.imageData + "\"\/>" +
        "<p>" + attach.oldName + "</p>" +
        "<a href=\"#\" value=\"" + attach.imageStep +"\" onclick=\"deletePreview(this)\">" +
        "삭제" + "</a>" +
        "</div>"
      );
    )};
)};

 

@RequestMapping("/AttachList")
@ResponseBody
public ResponseEntity<List<ImageDataVO>> getAttachList(int ImageNo) throws Exception{
		
  return new ResponseEntity<>(imageBoardMapper.getAttachList(ImageNo), HttpStatus.OK);
}

 

이렇게 작성해서 ImageData로 설정해둔 IMG경로를 갖고 있는 값을 불러오도록 했다.

그러나 아무리 페이지를 열어도 무반응....

alert창을 추가해서 ImageNo를 받아봤지만 정상적으로 넘어오고 있었다.

 

JSON을 처음 써봐서 여기저기 찾아보고 책도 뒤져봤지만 같은 방식으로 짜여져있는 코드만 발견할 뿐... 해결방법을 찾을 수 없었다 ㅠㅠ

 

그렇게 이틀을 날리고... 찾은 방법!! 보자마자 아 왜 이 생각을 못했지... 했다 ㅠㅠ

 

$.getJSON("AttachList", {ImageNo: ImageNo}, function(arr){            	
  $(arr).each(function(i, attach){
    $("#preview").append(
      "<div class\"preview-box\" value=\"" + attach.imageStep + "\">" +
      "<img class=\"thumbnail\" src=\"IMG/" + attach.imageData + "\"\/>" +
      "<p>" + attach.oldName + "</p>" +
      "<a href=\"#\" value=\"" + attach.imageStep +"\" onclick=\"deletePreview(this)\">" +
      "삭제" + "</a>" +
      "</div>"
    );
  });
})
.fail(function(err){
  alert(err.responseText);
})

 

$.getJSON 아래에 .fail을 추가! 어떤 에러가 발생하는지를 알면 해결법도 찾을 수 있으니 최고의 방법이다 ㅠㅠ

이렇게 수정하기전에는 JSON이 아예 동작을 안하는것인지 동작은 하는데 어디에선가 에러가 발생해서 멈춘건지 알수도 없었다.

AJAX를 사용할 때도 맨 아래 error 를 볼 수 있도록 만들어서 사용했는데 왜 이생각을 못해서 이틀이나 날렸는지.......ㅠㅠ

 

발생한 에러는 

illegalStateException.

Optional int parameter ImageNo is present but declared as a primitive type.

Consider declaring it as object wrapper for the corresponding primitive type.

이런 에러이다. 

 

ImageNo는 기본유형으로 선언되며 해당 기본 유형에 대한 객체래퍼로 선언하는 것을 고려해야 한다. 라는 것이다.

primitive type변수는 null이 될 수 없기 때문이다.

 

primitive Type이란 언어에서 사전 정의되어 있는 데이터 타입으로 int, char, byte등 일반 타입이라고 생각하면 된다.

 

그래서 ImageNo 를 반드시 초기화 해주거나 Wrapper class 를 사용해야 한다.

에러에 대해 검색했을 때 발견한 부분인데 여기서는 VO에 int ImageNo 가 아닌 Integer ImageNo 로 변경하라고 조언들을 해주셨다.

그래서 VO를 변경했으나 동일. 그래서 Controller에서도 Integer로 변경했더니 해결되었다!!!!!!!!!!!!!!!!

굳이 jquery 에서는 변경하지 않아도 문제가 발생하지 않았다.

 

그리고 VO에서도 int로 다시 변경해봤더니 문제가 없다. Controller에서 받아주는 것만 Integer로 받아준다면 문제가 발생하지 않았다.

 

내 경우에는 ResponseEntity를 사용했지만 해당 질문글 작성자분은 ModelAndView를 사용하셨다. 그래서 발생하는 차이가 아닐까 싶다.

 

JSON에 대해 좀 더 공부가 필요하다!!!

 

JSON만 잘 해결했으면 프로젝트 벌써 마무리했을건데..ㅠㅠ

 

+ Recent posts