우주위키

uc·2024년 2월 7일
post-thumbnail

2024-02-07

- 클래스 이름

  • 첫글자는 반드시 대문자
  • 중복되면 안됨
  • 특수문자는 _ $ 두개만 사용 가능
  • 첫글자 숫자 불가능
class Ex01 {
	public static void main(String[] args) {

	}
}

- 실행문

변수 선언, 값 저장, 메소드 호출에 해당하는 코드

샐행문 끝에는 반드시 세미콜론(;)을 붙여 실행문의 끝 표시

		int x = 1;						// 변수 x를 선언하고 1을 저장
        int y = 2;						// 변수 y를 선언하고 2를 저장
        int result = x + y;				// 변수 result를 선언하고 변수 x와 y를 더한 값을 저장
        System.out.println(result);		// 콘솔에 출력하는 메소드 호출

- 변수

하나의 값만을 저장할 수 있는 메모리 공간

변수 이름을 위한 명명 규칙(naming convention)

  • 첫번째 글자는 문자이거나 ' $ ' , ' _ ' 여야 하고 숫자로 시작할 수 없다. (필수)
  • 영어 대소문자가 구분된다. (필수)
    firstname 과 firstName 은 다른 변수
  • 첫문자는 영어 소문자로 시작하되, 다른 단어가 붙을 경우 첫자를 대문자로 한다. (관례)
    maxSpeed, firstName, carBodyColor
  • 문자 수(길이)의 제한은 없다.
  • 자바 예약어는 사용할 수 없다. (필수)

변수는 중괄호 블록 { } 내에서 선언되고 사용

	public static void main(String[] args) {
    			// 이 안에 공간
    }

- 선언과 대입

데이터타입 변수이름;

		int age;		// 변수 선언
        age = 30;		// 값 저장

변수이름 = 값;

		int age = 30;	// 초기값은 변수를 선언함과 동시에 줄 수도 있다

잘못된 코딩의 예시

		int age;				// 변수 age 선언 (초기화 안됨)
        int result = age + 10;	// 변수 age 값을 읽고 10을 더한 결과값을 변수 result에 저장

맞게 고친 후의 코드

		int age = 30;			// 변수 age 가 30 으로 초기화 됨
        int result = age + 10;	// 변수 age 값을 읽고 10을 더한 결과값(40)을 변수 result에 저장 

- 리터럴(literal)

소스 코드 내에서 직접 입력된 변수의 초기값

종류

  • 정수 리터럴
  • 실수 리터럴
  • 문자 리터럴
  • 문자열 리터럴
  • 논리 리터럴

데이터 타입

- 기본(primitive) 타입

정수, 실수, 문자, 논리 리터럴을 직접 저장하는 타입

메모리의 최소 기억단위인 bit가 모여 byte 형성

정수형

  • byte
    -128 ~ 127
  • char
    한 글자의 문자 표현, ' '(작은따옴표)사용, 아스키코드표
    'A' = 65 , 'a' = 97 , '0' = 48
		char c1 = 'A';
    	int a = c1;
    	System.out.println(c1);			// 문자 출력					A
    	System.out.println(a);			// 문자에 해당하는 정수값 출력	65
    
		char c2 = 66;					// 정수값 대입
		System.out.println(c2);			// 정수값에 해당하는 문자 출력	B
	
		char c3 = 67;
    	System.out.println(c3);			// 정수값에 해당하는 문자 출력	C
    	System.out.println((int)c3);	// char에 더 큰 타입인 정수형 int를 대입
																	67
  • short
    -32,000 ~ 32,000
  • int
    약 -21억 ~ 21억
  • long
    2의 63제곱-1
		//int num1 = 12345678900;		// 범위가 넘었기 때문에 오류 발생
    	long num2 = 12345678900L;		// 자바는 정수형 기본을 int로 처리
    									// 식별자 사용 - L , l (대문자를 주로 사용)
		long a = 3000000000L;
    	int b = 1000000000;				// int 범위 내 허용
		long result = (a + b);			// long 에 result 선언
    	System.out.println(result);		// result 출력

byte < char < short < int < long

실수형

  • float
    약 소수점 7자리정도 표현
		float f = 3.14F;			// 식별자 F , f 를 붙여준다
  • double
    소수점 16자리까지 표현
		double d = 3.14;
    	float f = 3.14F;
    	System.out.println(d);		// 3.14
    	System.out.println(f);		// 3.14

float < double

논리형

  • boolean
    결과가 true / false
		boolean a;						// 변수 선언
		a = true;						// 값 대입
		System.out.println(a);			// true

		boolean b;
		b = false;
		System.out.println(b);			// false

		int c = 5;
		int d = 10;
		int result = c+d;

		boolean result2 = ( c > d );
		System.out.println(result2);	// false

- 참조타입

문자형

  • String
    " "(큰따옴표)사용, 공백도 문자
    기본타입 아님
		String a;
		a = "hello";					// ""(큰따옴표) 사용
		System.out.println(a);			// hello

		String b;
		b = " ";						// 띄어쓰기, 공백 문자로 인식
		System.out.println(b);			// ( )

		String c;
		c = "world";
		System.out.println(c);			// world

		System.out.println(a+b+c);		// hello world

타입 변환

- 타입 변환

데이터 타입을 다른 타입으로 변환하는 것

  • byte ↔ int , int ↔ double

종류

  • 자동(묵시적) 타입 변환 : Promotion
  • 강제(명시적) 타입 변환 : Casting

- 자동 타입 변환

프로그램 실행 도중 작은 타입은 큰 타입으로 자동 타입 변환 가능

  • byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)
char a = 'A';
		System.out.println(a);		// A
		int b = a;
		System.out.println(b);		// 65
		double c = a;				// 한개의 소수점을 가진다
		System.out.println(c);		// 65.0

- 강제 타입 변환

( ) : 캐스팅 연산자

큰 타입을 작은 타입 단위로 쪼개기

변환 '하려는' 타입 명시해주기

		int a = 65;
		// char b = a;				// 대입불가
		char b = (char)a;			// 캐스팅 연산자() 사용
		System.out.println(a);		// 65
		System.out.println(b);		// A
        
        // 실수 > 정수
		double x = 10.5;
		int y = (int)x;				// 0.5의 정보 손실 발생
		System.out.println(x);		// 10.5
		System.out.println(y);		// 10

		double z = y;				// 다시 double 타입으로 바꾸어도 돌아오지 않는다
		System.out.println(z);		// 10.0

		// 서로 다른 타입끼리의 변환 생략하지 마라
		// 변환(대입)하려는 쪽의 타입 반드시 적어라 

- 연산식에서 자동 타입 변환

연산은 같은 타입의 피연산자(operand)간에만 수행

  • 서로 다른 타입의 피연산자는 같은 타입으로 변환
  • 두 피연산자 중 크기가 큰 타입으로 자동 변환
		int a =10;
        double b = 5.5;
        double result = a + b;		// 15.5
						^ double타입으로 자동 변환

Ex) int type으로 계산 결과를 얻고 싶다면?
Double type 변수를 먼저 int로 변환 후 계산

2024-02-08

연산자 #1

- 부호 연산자

( + , - )

boolean 타입과 char 타입을 제외한 기본 타입에 사용 가능
부호 연산자의 산출 타입은 int

		int num1 = 10;
		int num2 = 20;
		System.out.println(+num1);		// 						 10
		System.out.println(-num2);		// 대입 된것 아님			-20
		System.out.println(num2);		// 값이 바뀐것은 아님		 20

		num2 = -num2;					// 대입을 해야 값이 바뀐다
		System.out.println(num2);		// 						-20

- 증감 연산자

( ++ , -- )

boolean 논리형 타입에서 사용 불가
변수의 값을 1증가 시키거나(++) 1감소(--) 시키는 연산자
증감 연산자가 변수 뒤에 있으면 다른 연산자 먼저 처리 후 증감 연산자 처리

		int a = 10;
		System.out.println(a++);		// 출력 10 -> 연산 11
		System.out.println(++a);		// 연산 12 -> 출력 12
		System.out.println(a++);		// 출력 12 -> 연산 13
		System.out.println(a++);		// 출력 13 -> 연산 14
		System.out.println(++a);		// 연산 15 -> 출력 15
						    a++;		// 혼자실행 연산	 16
		System.out.println(++a);		// 연산 17 -> 출력 17
		System.out.println(a);			//				 17
		int b = 10;
        System.out.println(b++ + b++);	// 10 + 11 = 21
        								// b변수 메모리에는 연산자가 사용되어도 하나밖에 저장할 수 없다
                                        // 선행부분의 증감 연산 실행

- 산술 연산자

( + , - , * , / , % )

boolean 타입을 제외한 모든 기본 타입에 사용 가능
결과값 산출할 때 Overflow 주의
정확한 계산은 정수를 사용
NaN과 Infinity 연산은 주의할 것

		int mathScore = 90;
		int engScore = 70;

		int totalScore = mathScore + engScore;
		System.out.println(totalScore);					// 160
		
		double avgScore = totalScore / 2.0;				// 앞에서 뒤를 나눔
		System.out.println("평균 = "+avgScore+"%");		// 평균 = 80.0%
		10 % 2 = 0
		10 % 3 = 1
		10 % 4 = 2
		10 % 7 = 3
		10 % 12 = 10		// 나눌수가 없다. 남는값은 10

- 문자열 연산자

피연산자 중 문자열이 있으면 문자열로 결합

		String str1 = "JDK" + 6.0;		// String 변수에 ""(큰따옴표)를 대입하면 숫자 붙이기 가능
		String str2 = str1 + " 특징";
		System.out.println(str2);		// JDK6.0 특징
										
		String str3 = "JDK" + 3 + 3.0;
		String str4 = 3 + 3.0 + "JDK";
		System.out.println(str3);		// JDK33.0
        								// "" 뒤에 숫자 : 문자 연결로 붙여짐
		System.out.println(str4);		// 6.0JDK
        								// "" 앞에 숫자 : 산술 연산으로 더해짐

- 비교 연산자

( == , != , > , < , >= , <= )

  • 대소( < , <= , > , >= ) 또는 동등( == , != ) 비교해 boolean 타입인 true / false 산출
		int num1 = 5;
		int num2 = 3;
		boolean value = (num1 > num2);
		System.out.println(value);		// true

		value = (num1 < num2);
		System.out.println(value);		// false

- 논리 연산자

( && , || , & , | , ^ , ! )

  • if, for 등의 제어문과 함께 쓰임
  • 논리곱( && ), 논리합( || ), 배타적 논리합( ^ ), 논리 부정( ! ) 연산 수행
  • 피연산자는 boolean 타입만 사용 가능
    && (and 연산자) : 조건이 모두 true일때 결과가 true
    || (or 연산자) : 조건 중 하나라도 true이면 결과 true
    ! (not 연산자) : 결과를 반대로
		int a = 10;
		int b = 5;
		boolean result = (a > 0 && a > 1000);	// 비교 -> &&
		System.out.println(result);				// false

		boolean result2 = (a > 0 || a > 1000);
		System.out.println(result2);			// true

		System.out.println(!result2);			// false
												// 결과 반대
		boolean result3 = result2;				// 전에 not 연산자를 사용했지만 result2의 값은 true
		System.out.println(result3);			// true

		int num = 10;
		int a = 2;
		boolean result = ((num = num+10) < 10) && ((a = a+2) < 10) ;
						   // num+=10				 a+=2
		System.out.println(result);		// false
		System.out.println(num);		// 20
		System.out.println(a);			// 2
        								// && 앞의 식을 진행하고 false가 나왔으므로 num = 20
										// && 뒤의 식은 앞에서 이미 false가 나와 진행되지 않으므로 a = 2

- 복합대입 연산자

( = , += , -= , *= , /= , %= , &= , ^= , |= , <<= , >>= , >>>= )

  • 오른쪽 피연산자의 값을 좌측 피연산자인 변수에 저장
  • 모든 연산자들 중 가장 낮은 연산 순위 -> 제일 마지막에 수행
  • 종류
    단순 대입 연산자
    복합 대입 연산자
		int a = 10;
		System.out.println(a+20);		// 30
		System.out.println(a);			// 10

		System.out.println(a+=20);		// 30
		System.out.println(a);			// 30

- 삼항 연산자

조건식 ? (true일때의 값 또는 조건식) : (false일때의 값 또는 조건식);

  • 조건식 - 결과가 논리형으로 나오도록 구성
  • 세 개의 피연산자를 필요로 하는 연산자
  • 앞의 조건식 결과에 따라 콜론 앞 뒤의 피연산자 선택 -> 조건 연산식
		System.out.println(10 > 0 ? 100 : -100);				// 100 
		System.out.println(10 < 0 ? 'A' : "F");					// F

		int a = 10;
		System.out.println(a > 0 ? "크다" : "작다");				// 크다
		String result = a > 0 ? "크다" : "작다";
		System.out.println(result);// 크다

		int age = 85;
		String result2 = !(age > 90) ? "가" : "나";				// not 연산자
		System.out.println(result2);							// 가

		int score = 85;
		char grade = score > 90 ? 'A' : score>80 ? 'B' : 'C';
		System.out.println(grade);								// B

		int c = a > 5 ? a > 10 ? 500 : 100 : 0;
//				a > 5 ? 의 결과가 F일때 0
//				a > 5 ? 의 결과가 T일때 [a > 10 ? 500 : 100] 들어와서 진행
		System.out.println(c);									// 100

2024-02-13

진수

- 2진수

2진수는 이진수 라고도 불리며, 컴퓨터에서 가장 기본적인 숫자 표현 방식 중 하나

2진수는 0과 1 두 개의 숫자만을 사용하여 숫자를 표현

  • 2진수는 앞에 0b를 붙임 (binary)
  • 10진수 16을 2진수로 표현
		int num1 = 16;					// 10진수
// 16을 몫이 0이 될 때 까지 2로 나누어 나머지를 끝에서부터 기입함
// 16/2 = 8 나머지 0
// 8/2 = 4 나머지 0
// 4/2 = 2 나머지 0
// 2/2 = 1 나머지 0
// 1/2 = 나눌 수 없음 1
// 1 0 0 0 0        
		int num2 = 0b10000;
        System.out.println(num1);		// 16
        System.out.println(num2);		// 16
  • 2진수 -> 10진수
		int binary = 0b10000;
// 끝에서부터 0 , 1 , 2 , 4 , 8 , 16 , 32 , ~
// 1 0 0 0 0
// ^ ^ ^ ^ ^
//16 8 4 2 1
// 1에 해당한 자리 숫자를 더함
// 16
		System.out.println(binary);		// 16

- 8진수

8진수는 0부터 7까지의 숫자로만 이루어진 숫자 체계

각 자리가 8의 거듭제곱을 나타냄

  • 8진수는 앞에 0을 붙임 (octal)
  • 10진수 16을 8진수로 표현
// 16을 몫이 0이 될 때 까지 8로 나누어 나머지를 끝에서부터 기입함
// 16/8 = 2 나머지 0
// 2/8 = 나눌 수 없음 2 (자기자신)
// 2 0
		int num8 = 020;
		System.out.println(num8);		// 16
  • 10진수 -> 2진수 -> 8진수
// 10진수 16을 2진수로 변환
// 1 0 0 0 0
// 끝에서부터 3자리씩 묶음
// 0 1 0 / 0 0 0
//   ^   /   0
//   2   /   0		= 20
		int octal = 020;
        System.out.println(octal);		// 16
  • 8진수 -> 10진수
		int octal = 020;
// 각 자리의 수를 8의 거듭제곱으로 곱한 후 합산
//		2*8^1 + 0*8^0 = 2*8 + 0*1 = 16
		System.out.println(octal);		// 16

- 16진수

16진수는 0부터 9까지의 숫자와 A부터 F까지의 문자로 표현되는 숫자 체계

각 자리가 16의 거듭제곱을 나타냄

  • 16진수는 앞에 0x를 붙임 (hex)
  • 10진수 16을 16진수로 표현
// 16을 몫이 0이 될 때 까지 16으로 나누어 나머지를 끝에서부터 기입함
// 16/16 = 1 나머지 0
// 1/16 = 나눌 수 없음 1
// 1 0
		int num16 = 0x10;
        System.out.println(num16);		// 16
  • 10진수 -> 2진수 -> 16진수
// 10진수 16을 2진수로 변환
// 1 0 0 0 0
// 끝에서부터 4자리씩 묶음
// 1 / 0 0 0 0
// ^ /    0
// 1 / 0
		int hex = 0x10;
        System.out.println(hex);		// 16
  • 16진수 -> 10진수
		int hex = 0x10;
// 각 자리의 수를 16의 거듭제곱으로 곱한 후 합산
//		1*16^1 + 0*16^0 = 1*16 + 0*1 = 16
		System.out.println(hex);		// 16

연산자 #2

- 비트 연산자

( & , | , ^ , ~ )

비트 값을 기반으로 하는 연산자
비트 단위로 연산 이루어짐

  • & (and 연산자)
// 두개의 비트 값이 모두 1인 경우에만 연산의 결과 값이 1 
        int num1 = 5;
        int num2 = 7;
        int result1 = num1 & num2;
        System.out.println(result1);		// 5
// 2진수로 변환
// 5 =   101
// 7 = 	 111 ↓
//       101  <- 맞물린 자릿수끼리 더함
//		 4+1 = 5  
  • | (or 연산자)
// 비트 값이 하나라도 1이면 결과 값이 1
		int num1 = 5;
		int num2 = 7;
		int result1 = num1 | num2;
		System.out.println(result1);		// 7
// 2진수로 변환
// 5 =   101
// 7 = 	 111 ↓
//       111  <- 내린 자릿수끼리 더함
//		 4+2+1 = 7  
  • ^ (xor 연산자)
// 같으면 0 , 다르면 1
		int num1 = 5;
        int num2 = 7;
        int result1 = num1 ^ num2;
        System.out.println(result1);		// 2
// 2진수로 변환
// 5 =   101
// 7 = 	 111 ↓
//       010  <- 맞물린 수 0으로
//		  2  
  • ~ (반전 연산자)
// 0 -> 1 , 1 -> 0 바꾸는 연산자
		int num1 = 10;
        int result1 = ~num1;
        System.out.println(result1);		// -11
// 0 0 0 0 1 0 1 0
// 1 1 1 1 0 1 0 1 
// 컴퓨터에서는 2의 보수 표현 방식을 사용하여 음수를 나타냄
// 음수의 이진 표현은 해당 양수의 이진 표현의 반전된 값에 1을 더한 것

- 비트 이동 연산자 (시프트 연산자)

<< , >>

  • << 연산자 : 비트를 왼쪽으로 이동
		int num1 = 5;
		int result1 = num1 << 2;
		System.out.println(result1);		// 20
		// 0 1 0 1 <-방향
		// 0 1 0 1 0 0 
		//   16 +4		= 20
        
        int num2 = 10;
		int result2 = num2 >> 2;
		System.out.println(result2);		// 2
		// 1 0 1 0 -> 방향
		// 0 0 1 0
		//     2		= 2

조건문

- if문

if(조건식) {실행문;}

  • 조건식이 true일 때 { } 안 실행
  • 조건식의 결과가 true / false로 나오도록 구성
		int age = 8;
		if(age >= 8){
			// 조건식의 결과가 true일 때 수행
			System.out.println("학교에 다닙니다.");
		}
// 학교에 다닙니다.

- if-else문

조건식을 만족하는 경우, 만족하지 않는 경우 모두 나타낼 때 사용

if(조건식) {실행문1;} else {실행문2;}

		int age = 7;
		if(age >= 8){
			System.out.println("학교에 다닙니다.");
		}else{			// 필수 아님. 선택적 사용
			System.out.println("학교에 다니지 않습니다.");
		}
// 학교에 다니지 않습니다.

- if-else if-else문

하나의 상황에 조건이 여러개인 경우에 사용

하나의 조건에 만족하면 이후의 조건은 비교하지 않음

		int age = 12;
		int charge;
		if(age < 8){
			charge = 1000;
			System.out.println("미취학 아동입니다.");
		}else if(age < 14){			// true		이후의 조건 확인하지 않음
			charge = 2000;
			System.out.println("초등학생 입니다.");
		}else if(age < 20){			// 여러개 사용 가능
			charge = 2500;
			System.out.println("중고등학생 입니다.");
		}else{						// 문법상 제일 마지막에 위치
			charge = 3000;
			System.out.println("성인입니다.");
		}
		System.out.println("입장료는 "+charge+"원 입니다.");
// 초등학생 입니다.
// 입장료는 2000원 입니다.

- 중첩 if문

		// 중첩 if문
		int score = 97;
//		String grade;				// 오류
		String grade = "";			// 공백도 문자. 공백으로 초기화
		if(score >= 90){			// true
			if(score >= 95){		// true
				grade = "A+";
			}else{					// false
				grade = "A";
			}
		}
		System.out.println(grade);	// 초기화 해줘야함. false일때 나타낼 값이 없음
// A+

- switch-case문

변수나 연산식의 값에 따라 실행문 선택할 때 사용

		String position  = "과장";
		switch(position){
			case "부장":
				System.out.println("700만원");
				break;
			case "과장":
				System.out.println("500만원");
				break;
			case "대리":
				System.out.println("300만원");
				break;
			default:	// if문의 else와 같은 역할. 위치 상관없음
				System.out.println("200만원");	
		}
// 500만원
  • case(조건)는 다르지만 실행문이 같은 경우
		int month = 7;
		int day;
		switch(month){
			case 1: case 3: case 5: case 7: case 8: case 10: case 12:
				day = 31;
				break;
			case 4: case 6: case 9: case 11:
//			case 4,6,9,11:		java 14버전 이후 사용 가능
				day = 30;
				break;
			case 2:
				day = 28;
				break;
			default:
				day = 0;
				System.out.println("존재하지 않는 달 입니다.");
		}
		System.out.println(month+"월은 "+day+"일까지 있습니다.");
// 7월은 31일까지 있습니다.

- while문

조건식이 참인 동안 실행문 반복 수행 = 거짓(false)일 때 반복하지 않음

while(조건식){실행문;}

  • 1 ~ 10까지 출력
		int i = 1;					// 초기식
		while(i <= 10){				// 조건식
			System.out.println(i);	// 실행문
			i++;					// 증감식
		}
// 1 2 3 4 ~ 10
  • 1 ~ 100까지 홀수만 출력
        int i = 1;
		while(i <= 100){
			System.out.println(i);
			i += 2;
		}
// 1 3 5 7 ~ 99
  • 1 ~ 10 까지의 합
// 두가지 필요 : 합이 될 변수 , 반복할 변수
		int sum = 0;
		int num = 1;
		while(num<=10){
			sum += num;
			num++;
		}
		System.out.println("1~10까지의 합 = "+sum);
// 1~10까지의 합 = 55

2024-02-14

- do while문

{실행문;}안의 실행부분 반드시 한 번 실행 후 조건 검사

조건식이 만족하는지 마지막에 검사

{ }영역안의 실행문을 반드시 실행해야 할 때 사용

// 1 ~ 10까지의 합
		int num = 1;
		int sum = 0;
		do{
			sum += num;
			num++;
		}while(num <= 10);
		System.out.println(sum);

		System.out.println("=====");
        
		int number = 2;
		do{									// 반드시 한 번 실행
			System.out.println("hello");
			number++;
		}while(number == 1);				// 조건식 false
		System.out.println(number);			// 3 실행 되었기 때문

- for문

가장 많이 사용하는 반복문. 배열과 주로 사용

반복문을 구현하는데 필요한 요소들을 함께 작성

한줄에 작성하기 때문에 간결, 가독성이 좋음

  • for(초기식; 조건식; 증감식){
    실행문;
    }
  • 초기식 - for문 시작 할 때 한번만 실행
  • 조건식 - 언제까지 반복 할 것인지 구현
  • 증감식 - 증가/감소 시킴
  • 수행 순서가 매우 중요
		int a;
		for(a=1; a<=5; a++){
			System.out.println(a);		// 1 ~ 5 출력
		}		// for문 끝

/*		int d = 1;
		for(; ; d++){
			System.out.println(d);		// 문법상 되지만 쓰지 않음
*/
  • 1~100까지 홀수, 짝수 출력
		// 홀수
		int i1;
		for(i1=0; i1<100; i1++){
			i1+=1;
			System.out.println(i1);		// 1~99 홀수 출력
        }
            
            System.out.println("=====");
            
        // 짝수    
		for(int b=2; b<101; b+=2){
			System.out.println(b);		// 1~99 짝수 출력
		}
  • 1~100까지 홀수 합, 짝수 합 출력
    반복 할 수, 합을 담을 변수 필요
		// 홀수 합
		int num1;
		int sum1 = 0;
		for(num1=1; num1<101; num1+=2){
			sum1+=num1;
		}
		System.out.println(sum1);		// 2500

		System.out.println("=====");
        
        // 짝수 합
		int num2;
		int sum2 = 0;
		for(num2=2; num2<=100; num2+=2){
			sum2 += num2;
		}
		System.out.println(sum2);		// 2550

- 중첩 for문

  • for문 속 for문
		for(int a=1; a<=3; a++){
			for(int b=1; b<=3; b++){
				System.out.println(a+"=="+b);
			}
		}
  • for문 속 for문 2개
		for(int a=1; a<=5; a++){
			for(int b=1; b<=5; b++){
				System.out.println(a+"=="+b);
			}
			for(int b=1; b<=5; b++){			// 같은 이름의 변수 사용 가능
				System.out.println("hello");	// {} 영역이 다르기 때문
			}
		}
// 1==1 ~ 1==5 출력 후 hello 5번 출력
// 그리고 바깥 for문으로 돌아가서 다시
// 2==1 ~ 2==5 출력 후 hello 5번 출력
// 5==1 ~							~까지
  • 구구단
		int dan;
		int num;
		for(dan=2; dan<=9; dan++){
			for(num=1; num<=9; num++){
				System.out.println(dan +" x "+num+" = "+(dan*num));		
			}								// 수학적 연산 되려면 괄호로 묶어야함
				System.out.println();		// 보기 편한 줄바꿈
		}
// 2 x 1 = 2  ~~  9 x 9 = 81 까지 출력

- 반복문 속 조건문 <-> 조건문 속 반복문

		for(int a=1; a<=10; a++){
			// for문 true
			if(a%2 == 0){
				System.out.println(a+" = 짝수");
			}else{
				System.out.println(a+" = 홀수");
			}	// if문 끝
		}	// for문 끝
// 1 = 홀수 , 2 = 짝수 ~ 10까지 출력
  • 홀수의 합, 짝수의 합 출력
		int num , odd = 0 , even = 0;	// , 로 한번에 선언할 때 초기값 유무 확인
		for(num=1; num<=100; num++){
			if(num%2 == 1){				// 홀수
				odd += num;				// 1+3+5+..+99
			}else{						// 짝수
				even += num;			// 2+4+6+..+100
			}
		}
		System.out.println("홀수 합 = "+odd);
		System.out.println("짝수 합 = "+even);
//		System.out.println(num);	// 101
// 홀수 합 = 2500 , 짝수 합 = 2550 출력
  • 합이 100을 넘을 때의 num 구하기
		int sum = 0;
		int num = 0;
		for(num=0; sum<100; num++){
			sum += num;		// 1+2+..+? < 100
		}
		System.out.println("num : "+num);
		// 조건식이 false라서 반복을 수행 안할 뿐 증감식에서 이미 +1 되어서 15
		// 합이 100을 넘었을 때의 num 값은 14임
		System.out.println("sum : "+sum);
		// 조건식이 false라서 반복을 수행 안할 뿐 105라는 값이 대입 되어있음
// num : 15 , sum : 105 출력
//       ^ -1 = 14

- break문

break문을 만나면 그 지점에서 더이상 수행하지 않고 반복문을 빠져나옴

		for(int a=1; a<=100; a++){
			if(a%5 == 0){
				break;
			}
			System.out.println(a);
		}	// for문 끝
		System.out.println("hello");
		System.out.println("java");
// 1~4 출력 후 hello 출력 후 java 출력
// 5가 나오면 실행문 안 break가 실행되어 5는 출력되지 않음
  • 중첩 반복문 속 break문
		int a , b;
		for(a=1; a<=5; a++){
			for(b=1; b<=5; b++){
				if(b == 3){
					break;		// 가까운쪽에 걸림. 바깥쪽 for문엔 영향 안미침
								// b for문 종료
				}
				System.out.println(a+"=="+b);
			}
		}
// 1==1 , 1==2 , 2==1 , 2==2 ~ 5==2 까지 출력됨
// for문 속 for문이 3을 실행하지않고 break되어 바깥 for문으로 되돌아감

- continue문

반복문 안에서 continue; 를 만나면,

이후의 코드 실행하지 않고 증감식으로 가서 for문 수행

반복문 수행 중 특정 조건에서는 수행하지 않고 건너뛰어야 할때 사용

		int a;
		for(a=1; a<=3; a++){
			if(a == 3){			// a가 3일 경우
				continue;		// 이후 코드 진행하지 않고 증감식으로
			}
			System.out.println(a);
		}
// 1 , 2 출력
  • 1~100 홀수 출력
		int a;
		for(a=1; a<=100; a++){
			if((a % 2) == 0){		// 짝수인 경우
				continue;			// 이후 코드 진행하지 않고 증감식으로
			}
			System.out.println(a);
		}
  • 1~100 짝수 합 출력
		int num;
		int sum = 0;
		for(num=1; num<=100; num++){
			if(num%2 == 1){			// 홀수
				continue;			// 이후 코드 진행x
			}
			sum += num;
		}
		System.out.println(sum);	// 2550
  • 3의 배수 출력
		int num;
		for(num=1; num<=100; num++){		// 1~100 반복
			if(num%3 != 0){					// 3의 배수가 아닌것
				continue;					// 증감식으로
			}
			System.out.println(num);		// 3, 6, 9 ~ 99
		}
			// %3 == 0 : 3의 배수

2024-02-16

- 객체

눈에 보이는 모든 사물

눈에 보이지 않는 것들도 객체가 될 수 있음

ex) 로그인, 주문, 생산, 관리 등 어떤 행동을 나타내는 것도 가능

- 자바

객체를 기반으로 하는 프로그래밍

어떤 대상(객체)을 가지고 프로그래밍 함

언어의 특징

- 절차지향 언어

  • 순서대로 일어나는 일을 시간순으로 프로그래밍 하는 것
  • 직접 접근이 아닌 순차적 접근
    대표적인 언어 - C언어

- 객체지향 언어

  • 객체를 정의하고 객체 간 협력을 프로그래밍 하는 것
  • 객체를 만들고, 객체 사이에서 일어나는 일을 구현하는 것

- 관점지향 언어

  • 기술 구별이 아닌 개념적 구별
  • 목적이 아니라 사용에 따라 다르게 쓰임

클래스(class)

- 클래스

객체 지향 프로그램은 클래스를 기반으로 프로그래밍 한다.

객체는 '속성'과 '기능'을 코드로 구현.

클래스를 정의 한다 : 객체를 클래스로 구현 하는 것.

- 학생 이라는 객체

먼저 객체를 표현 할 클래스의 '이름'이 필요

: Student

객체가 가지고 있는 '속성'

: 이름, 학년, 나이, 학번, 연락처, 주소 등등
: 이러한 클래스의 속성은 클래스 내부에 변수로 선언
: 클래스 속성의 변수를 '멤버 변수'라고 함

- 클래스 정의

변수의 선언: 자료형 이름;

 		class 클래스이름{
 			멤버 변수;
 		}

변수 선언 대입

	타입 변수이름 =;
	클래스 변수이름 = new 클래스();

new 클래스(); - 객체를 생성하고 주소를 변수에 대입(리턴)함

- 클래스의 용도

라이브러리 클래스: 실행 불가, 다른 클래스에서 사용 할 목적

실행 클래스: main() 메서드를 가지고 있는 실행 가능한 클래스

- 클래스의 구성 요소

필드

  • 클래스 변수를 필드라고 함
  • 객체의 데이터가 저장되는 곳
  • 변수의 종류들.. 그것들을 통칭

생성자

  • 객체 생성시 초기화 담당

메서드

  • 객체의 동작/기능으로 호출 시 실행하는 {} 가지고있음

자동차 만들기

		// 변수
		String company;
		String model;
		String color;
		int speed;

		// 생성자
		Car(){	// 매개변수가 없는 생성자 : 기본 생성자
        
        }
        Car(String company){
        this.company = "벤츠";
        }
        Car(String company, String model, String color){
        this.company = company;
        this.model = model;
        this.color = color;
        }
		// 객체 생성
		Car car1 = new Car();
		System.out.println(car1.company);
        		// 벤츠 출력
		System.out.println(car1.color);
        		// null 출력
        Car car2 = new Car("벤츠","s580","black");
        System.out.println(car2.company);	// 벤츠
        System.out.println(car2.model);		// s580
        System.out.println(car2.color);		// black

2024.02.19

- 생성자

  • new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화 담당
  • 객체의 초기화 : 클래스 변수를 초기화, 메서드를 호출해서 객체를 사용할 준비하는 것

- 기본 생성자

  • 모든 클래스는 반드시 하나 이상의 생성자를 가짐
  • 클래스 내부에서 생성자를 생략했다면, 컴파일러는 기본 생성자를 자동 추가시킴
  • 매개변수가 있는 생성자를 만든다면, 기본 생성자 사용 불가능
  • 둘다 사용하싶을 때, 기본 생성자를 만들면 사용 가능

- 생성자 선언

클래스(매개변수, ..){객체의 초기화 코드}

  • 클래스 이름과 동일
  • 메서드 형태와 동일
  • 리턴 타입 없음

- 매개변수 유/무

  • 매개변수 선언 생략 가능
  • 여러개 선언 가능
  • new 연산자로 생성자를 호출(사용)할때,
    외부의 값을 생성자 영역{} 내부로 전달하는 역할

- 클래스 변수 초기화

  • 클래스로부터 객체가 생성될 때, 기본 초기값으로 자동 설정됨
  • 다른 값으로 초기화 하고싶다면 두가지 방법
  • 클래스 변수에 선언과 동시에 대입 : 초기화
    동일한 클래스로부터 생성되는 객체들은 모두 같은 데이터를 갖게됨
    대입으로 변경 가능
  • 생성자 초기화
    객체 생성 시, 외부에서 제공되는 다양한 값들로 초기화 되어야 할 때 사용
    생성자는 매개값으로 값들을 받아서 초기화

- this.

  • 매개변수의 이름이 너무 짧으면 가독성이 좋지 않음
    가능하면 초기화시킬 클래스 변수의 이름과 동일하게 사용할 것을 권함
  • 이 때, 매개변수와 클래스변수의 이름이 같아서 생성자 내부에서 해당 클래스 변수로 접근 불가
    지역변수의 특성으로 매개변수의 사용 우선순위가 높기 때문
  • 객체 자신을 참조
    우리가 나 자신 이라고 하듯, 객체 자신을 this 라고 함

- 생성자 오버로딩(overloading)

  • 외부로부터 제공되는 다양한 데이터들을 이용해서 객체를 초기화 하려면 생성자도 다양화 할 필요가 있음
    예) Car 객체에서 model 데이터 제공, model 과 color 제공
  • 다양한 방법으로 객체를 생성 할 수 있도록 생성자 오버로딩 제공
  • 매개변수를 달리하는 생성자를 여러개 선언하는 것을 말함
  • 주의) 매개변수의 타입, 순서, 갯수 달리해야함
    다 같을 경우 오버로딩 아님
    변수의 이름 상관 없음

- 다른 생성자 호출(사용)

this()

  • 생성자 오버로딩이 많아지면, 중복되는 코드들이 발생

  • 매개변수의 갯수만 다르고, 초기화 내용이 비슷한 경우
    이 때, 클래스 변수의 초기화 코드를 가지고있는 생성자를 호출해서 사용

  • 하나의 생성자에 초기화 코드를 집중적으로 작성

  • 나머지 생성자는 초기화 코드를 가지고있는 생성자를 호출해서 사용

  • 생성자에서 다른 생성자를 호출 할 때 사용

    		클래스(매개변수, ..){
    			this(매개변수, .. 값);
    			실행문;
    		}
  • this. / this()
    반드시 생성자 내부의 첫줄에 사용

- 메서드

  • 클래스 변수의 값을 변경하는 역할, 객체를 생성한 후 다양한 기능을 수행하는 역할
  • 객체간의 데이터를 전달하는 수단으로 사용됨
  • 외부로부터의 매개값을 받을 수 있고, 실행 후 결과를 리턴 할 수 있음
  • 메서드는 호출(사용)한 곳으로 돌아옴

- 메서드 선언

  • 선언부, 구현부 로 이루어져 있음
    리턴타입 메서드이름( ) { 코드 구현 부분 }
  • 리턴타입 : 메서드가 리턴하는 결과값의 타입
  • 메서드이름 : 어떤기능을 하는지 유추가능한 이름 사용 (축약x)
  • () : 메서드가 실행할 때 필요한 데이터를 받기위한 변수
  • {} : 메서드의 실행 영역(블럭)

- 리턴타입

  • 메서드가 실행한 후 결과값의 데이터(자료형) 타입
  • 실행한 후 호출한 곳으로 넘겨줄 값이 있는 경우
    있는 경우 / 없는 경우 로 나뉨
    예) 전원을 켜는 powerOn() 메서드
    : 전원만 켜면 끝
    나누는 기능 divide() 메서드
    : 나눗셈의 결과값을 리턴

컴퓨터 만들기

메서드

public class Computer {
	// 클래스 변수 - 속성
	// 제조사 모델 색상 크기
	String company;
	String model;
	String color;
	int inch;

/*	생성자
	클래스(매개변수, ..){}
	Computer(String c){
		company = c;
	}
	주로 매개변수의 이름은 클래스 변수 이름과 같게 사용
*/	
	Computer(String company){
		this.company = company;
	}
	Computer(String company, String model){
		this.company = company;
		this.model = model;
	}
	Computer(String company, String model, String color){
		this.company = company;
		this.model = model;
		this.color = color;		
	}
	Computer(String company, String model, String color, int inch){
		this.company = company;
		this.model = model;
		this.color = color;
		this.inch = inch;
}

메인

public class ComputerMain {
/*		객체 생성
		Computer c1 = new Computer("삼성");
		값 읽기
*/
		Computer ct1 = new Computer("SAMSUNG");
		System.out.println(ct1.company);
		
		// 객체 생성 시 매개변수 순서 지켜줘야함
		System.out.println("=====");
        
		Computer ct2 = new Computer("SAMSUNG","Galaxy Book4 Pro");
		System.out.println(ct2.company);
		System.out.println(ct2.model);
		
		System.out.println("=====");
        
		Computer ct3 = new Computer("SAMSUNG","Galaxy Book4 Pro","Black");
		System.out.println(ct3.company);
		System.out.println(ct3.model);
		System.out.println(ct3.color);
		
		System.out.println("=====");
        
		Computer ct4 = new Computer("SAMSUNG","Galaxy Book4 Pro","Black",16);
		System.out.println("제조사 = "+ct4.company);
		System.out.println("모델명 = "+ct4.model);
		System.out.println("색상 = "+ct4.color);
		System.out.println(ct4.inch+"인치");
	}

계산기 만들기

메서드

public class Calculator {
	// 메서드
	// 리턴타입 메서드이름(매개변수){구현부}
	int plus(int x, int y) {
		// x+y; 이 기능을 할것
		return x+y;
	}
	double divide(int x, int y) {
		double result = (double)x/(double)y;
        						// result에 담음
		return result;
	}
	
	int minus(int x, int y) {
		int result2 = (int)x-(int)y;
		return result2;
	}
	
	double multi(int x, int y) {
		double result3 = (double)x*(double)y;
		return result3;
	}
	
	// 리턴 타입이 없는 메서드
	void powerOn() {
		
		System.out.println("전원을 켭니다.");
	}
	
	void powerOff() {
		System.out.println("전원을 끕니다.");
	}
}

메인

public class CalculatorMain {
	public static void main(String[] args) {
		// 객체 생성
		Calculator c = new Calculator();
		
		// 메서드 호출(실행)
		c.powerOn();
		
		
		// 메서드 호출(사용)
		c.plus(5, 6);
		System.out.println(c.plus(5, 6));
		int result = c.plus(5, 6);
		System.out.println(result);
		
		// divide() 호출
		c.divide(10, 5);
		double result2 = c.divide(10, 5);
		System.out.println(result2);
		
		byte a = 10;
		byte b = 4;
		double result3 = c.divide(a, b);
		System.out.println(result3);
		
		int x = 50;
		byte y = 4;
		double result4 = c.divide(x, y);
		System.out.println(result4);

		c.minus(15, 6);
		int result5 = c.minus(15, 6);
		System.out.println(result5);
		
		
		int i = 10;
		int n = 5;
		double result6 = c.multi(i, n);
		System.out.println(result6);
		
		// 리턴 타입이 없는 메서드 호출
		c.powerOff();
	}
}

2024.02.20

- 정적(static) 멤버

  • 정적 변수 / 정적 메서드
  • 메모리에 올라오는 순서가 다름
  • 프로그램이 시작됨과 동시에 메모리에 올라감(사용준비 됨)
    상주 메모리
  • 객체 생성 없이 사용 가능
class Data{
	static int a = 10;
	int b = 1;
	
//	int c = 10;
//	static int d = c;	// 메모리에 올라가는 순서 중요
	
	static void add() {
		System.out.println("a : "+a);
	}
	void bbb() {
		System.out.println("bbb() 실행");
	}
}

public class StaticEx01 {
	public static void main(String[] args) {
		System.out.println(Data.a);	// 10 출력
		Data.add();					//  a : 10 출력

class A{
	int x = 100;	// 인스턴스 기본형 변수
}

class B{
	int y = 200;	// 인스턴스 기본형 변수
	A a = new A();	// 인스턴스 참조형 변수
}

public class StaticEx02 {
	public static void main(String[] args) {
		// 객체 생성
		B b = new B();
		System.out.println(b.y);
		System.out.println(b.a.x);
        // b클래스를 타고 a클래스로 진입 가능
	}
}

- final (상수)

  • 변경할 수 없는, 변경 불가의 의미
  • 클래스 앞에 붙으면 상속 못함
  • 변수의 이름 대문자로 사용해서 구별
public class FinalEx01 {
	public static void main(String[] args) {
		// final 변수 사용할 때
		// 초기화 언제? - 선언과 동시에, 사용 전에 한 번
		final int MAX_NUM = 100;		// 선언과 동시에 초기화
		final int MIN_NUM;
		MIN_NUM = 0;
		System.out.println(MAX_NUM);
		System.out.println(MIN_NUM);
		
//		MIN_NUM = 1000;		// 다시 대입 불가능. 변경 불가
	}
}

final 변수의 초기화 방법

  • 선언과 동시에 초기화
  • 생성자 초기화
class Fin{
	final int X = 100;
	final static int Y = 500;		// static 상주 메모리	// final, static 순서 바뀌어도 됨
	final int Z;
	
	Fin(int Z){
		this.Z = Z;
	}
}

public class FinalEx02 {
	public static void main(String[] args) {
/*
		Fin f = new Fin(20);
		System.out.println(f.Z);
		System.out.println(f.X);
		System.out.println(f.Y);
       						 // 객체 생성 필요x 
*/
		System.out.println(Fin.Y);
//		Fin.Y = 100;		// final 변경 불가
		
//		final int b = 10;
//		b = 20;
//		b++;
//		b+=1;				// 모두 불가

- 임포트 (import)

패키지

  • 클래스들의 묶음 , 폴더

임포트

  • 기술이 아님
  • 편의를 위해 생겨남
  • java.lang 패키지를 제외한 다른 패키지의 클래스 객체 생성시 사용
  • * : 전체를 의미
import java.util.Date;		// 자바 API(설명서) 를 통해
							// 자유롭게 꺼내 쓸 수 있음
public class ImportEx {
	public static void main(String[] args) {
		Date d = new Date();
//		java.util.Date d1 = new java.util.Date();
//		java.util.Date d2 = new java.util.Date();
//		java.util.Date d3 = new java.util.Date();
//		java.util.Date d4 = new java.util.Date();
//		java.util.Date d5 = new java.util.Date();
// 이렇게 길게 안쓰고도 import를 통해 간결하게 작성 가능
		int result = d.getDate();
		System.out.println(result);

- 접근 제어자

모든 문법에서 가장 우선 적용

  • public : 모든 접근 허용
  • protected : 같은 패키지, 상속 관계
    extends 패키지이름 을 사용하여 상속관계 만들어줄 수 있음
  • default : 같은 패키지
    : 앞에 아무것도 없는 경우
  • private : 본인 클래스 내에서만

클래스 - public , default 만 사용 가능 (안쓰면 default)
변수 - 모두 사용 가능 : 인스턴스, 정적 가능 / 지역변수 사용 불가
생성자/메서드 - 모두 사용 가능

  • 다른 패키지, 다른 클래스 에서 실행 테스트
	package day0220;
    
	public class AccessEx20 {
    
	public		static int a;
	protected	static int b;
				static int c;	// default
	private		static int d;

package day0219;
import day0220.AccessEx20;		// import 다른패키지 사용

public class AccessExMain extends AccessEx20{	// extends 로 상속시켜줌
		// 다른 패키지 내의 AccessEx20 클래스 변수
		System.out.println(AccessEx20.a);
		System.out.println(AccessEx20.b);
        // extends AccessEx20 로 다른클래스로 상속관계가되어 protected 실행 가능
//		System.out.println(AccessEx20.c);	// 에러 - 같은패키지만 사용 가능
//		System.out.println(AccessEx20.d);	// 에러 - 본인 클래스 내에서만 가능

2024.02.21

Getter와 Setter

클래스 선언할 때 필드는 일반적으로 private 접근 제한

읽기 전용 필드가 있을 수 있음 (Getter의 필요성)
외부에서 엉뚱한 값으로 변경할 수 없도록 (Setter의 필요성)

- Getter

  • private 필드의 값을 리턴 하는 역할 - 필요할 경우 필드 값 가공
  • getFieldName() 또는 isFieldName() 메소드
    필드 타입이 boolean 일 경우 isFieldName()

- Setter

  • 외부에서 주어진 값을 필드 값으로 수정
    필요할 경우 외부의 값을 유효성 검사
  • setFieldName(타입 변수) 메소드
    매개 변수 타입은 필드의 타입과 동일

- Singletone(싱글톤)

인스턴스를 단 하나만 생성하는 디자인 패턴

class School{				// 메모리에 올라가는 순서대로 정리하는게 정석
	private School() {}		// 컴파일러가 추가하는 기본생성자 public
							// 외부 클래스에서 인스턴트를 여러개 생성 가능
							// private : 클래스 내부에서만 생성자 사용 가능
	// 메서드
	// 리턴타입 메서드이름(){}
	private static School instanse = new School();	// static - instanse 변수를 바로 사용하기위해 붙임
													// 사용할 인스턴스 한개는 필요
													// 클래스 내부에서 하나의 인스턴스 생성
	public static School getInstanse() {	// 생성한 객체를 static 으로 만들어놔서 get instanse 도 static 이여야함
		if(instanse == null) {				// static 이 없으면 객체생성을 따로 해줘야함
			instanse = new School();
		}
		return instanse;					// 유일하게 생성된 인스턴스(객체) 리턴, 반환
	}
}
public class SingleToneEx01 {
	public static void main(String[] args) {
		// static 이기 때문에 클래스이름으로 호출
		// 참조변수로 받음
		 School s1 = School.getInstanse();
		 School s2 = School.getInstanse();
		 
		 // 유일한 인스턴스인지 확인
		 // 같은 주소인지 확인
		 System.out.println(s1);			// s1, s2 모두
		 System.out.println(s2);			// 같은 경로 해시코드 나옴
	}
}

배열

  • 데이터 타입(자료형)이 같은 데이터를 한 번에 모아놓은 것
  • 연속적으로 나열된 구조
  • 각 값에 index를 부여
  • 생성과 동시에 길이(크기,개수)가 정해짐
  • 길이를 변경할 수 없음
	public static void main(String[] args) {
    
		int num1 = 10;
		int num2 = 20;
		int num3 = 30;
		int num4 = 40;
		int num5 = 50;
//		... 100 까지
		int num100 = 1000;	// 타입이 같다는 공통점 -> 배열 가능
		
		// 배열의 선언
		// 타입 변수이름;
		int[] intArray;
		double[] doubleArray;
		String[] stingArray;
		
		// 배열 생성 #1
		// 1. 선언할 때, 값 초기화
		int[] arr = {1, 2, 3, 4, 5};
		
		// 자바 프로그램에서는 0부터 시작
		// 인덱스 연산자 [] : 배열 요소가 지정된 메모리 위치를 찾아주는 역할
		// 배열의 값 읽기
		System.out.println("arr[1] : "+arr[1]);		// 2
		System.out.println("arr[0] : "+arr[0]);
		System.out.println("arr[3] : "+arr[3]);		// 4
		System.out.println("arr[4] : "+arr[4]);
		
		// 배열의 값 변경
		arr[0] = 30;
		System.out.println("arr[0] : "+arr[0]);		// 30
		
		// 배열 생성 #2
		// 배열의 개수를 지정하고 선언한다면, 타입에 따라 초기화
		int[] arr2 = new int[5];
		System.out.println(arr2);
		
		arr2[0] = 100;
		arr2[1] = 200;
		arr2[2] = 300;
		System.out.println("arr2[2] = "+arr2[2]);
		System.out.println("arr2[3] = "+arr2[3]);
		System.out.println("arr2[4] = "+arr2[4]);
		
		// 배열 생성 #3
		// 배열 선언 후 생성
		int[] arr3;
//		arr3 = {30,40,50,60}	// 배열은 객체임. 값만 넣으면 안되고 new 를 통해 생성해주고 값을 넣어줘야함
		arr3 = new int[] {30,40,50,60};
	}
}

- 배열 (for문 사용)

.length : 개수, 크기

인덱스 : [] - 순서(0부터 시작)

향상된 for문

for(변수선언 : 반복대상)

		int[] a = new int[4];
		int[] b = {1, 2, 3, 4, 5, 6, 7, 8};
		
		// 개수 출력
		System.out.println(a.length);	// 4
        // 인트(a)의 API 참조
		System.out.println(b.length);	// 8
        
       	a[0] = 1;		// 값 대입
		a[1] = 2;
		a[2] = 3;
		a[3] = 4;
		
		for(int i=0; i<a.length; i++) {		// 초기값 0, a의 갯수-1 만큼 반복되게
			System.out.println(a[i]);		// 1~4 출력
		}
		
		System.out.println("=====");
		
		for(int y=0; y<b.length; y++) {
			System.out.println(b[y]);		// 1~8 출력
		}
        // 향상된 for문
        // for(변수선언 : 반복대상)
        for(int x : b) {
        System.out.println(x);				// 1~8 출력

2024.02.22

배열 #2

- for문 사용 출력

  • 배열 생성의 3가지 방법
//		1.
		int[] arr1 = {1,2,3,4,5};
        for(int i=0; i<arr1.length; i++) {
        // 인덱스 안의 값을 모를때 .length 사용
			System.out.println(arr1[i]);
		}
        
		System.out.println("=====");
//		2.        
        int[] arr2 = new int[5];
		arr2[0] = 1;
		arr2[1] = 2;
		arr2[2] = 3;
		arr2[3] = 4;
		arr2[4] = 5;
        for(int x=0; x<arr2.length; x++) {
			System.out.println(arr2[x]);
		}
        
        System.out.println("=====");
//      3.
        int[] arr3;
		arr3 = new int[] {1,2,3,4};
		for(int y=0; y<arr3.length; y++) {
			System.out.println(arr3[y]);
		}
// 3가지 방법 모두 0부터 시작함으로 초기식은 0
// 조건문은 인덱스 안의 값 수 만큼 주기위해 .length 사용

- 총 합 구하기

  • 향상된 for문
    for(변수선언 : 반복대상)
		int[] scores = {70, 85, 87, 90};
		
		// 총 합 구하기
		int sum = 0;		// 합을 담을 변수에 0 대입
		for(int i=0; i<scores.length; i++) {
			sum += scores[i];
		}
		System.out.println("총 합 = "+sum);
		
		System.out.println("=====");
		
		// 향상된 for문 사용
		// for(변수선언 : 반복대상)
		int[] a = {1,2,3};
		int sum2 = 0;
		for(int y : a) {
			sum2 += y;
		}
		System.out.println(sum2);
		
		System.out.println("=====");
		
		int[] ar1 = new int[] {1,5,9};
		for(int s : ar1) {
			System.out.println(s);
		}

- 개수 지정 출력

		double[] dou = new double[5];
		// for(int i = 0; i<(값을 저장한 요소의 개수); )
		int size = 0;	// 저장할 개수만큼 증가시킬 변수 생성
		
		dou[0] = 10.0;
		size++;			// 저장될 때마다 1씩 증가
		dou[1] = 20.0;
		size++;
		dou[2] = 30.0;
		size++;
		
		for(int i=0; i<size; i++) {	
        // 0부터 2까지 반복되게 조건문
			System.out.println(dou[i]);
		}

- 객체 배열

  • 도서관 만들기
  • 메서드
public class Book {
	// 변수
	private String bName;			// 책 제목
	private String pName;			// 작가 이름
	
	// 생성자
	public Book() {}
	
	public Book(String bName, String pName) {
		this.bName = bName;
		this.pName = pName;
	}
	
	// 메서드
	// 리턴타입 메서드이름(선언부) {구현부}
	
	// 책 제목 입력받을 메서드
	public void setBookName(String bName) {
		this.bName = bName;
	}
	// 책 제목 꺼내는 메서드
	public String getBookName() {
		return bName;
	}
	// 작가 이름 입력받을 메서드
	public void setPName(String pName) {
		this.pName = pName;
	}
	// 작가 이름 꺼내는 메서드
	public String getPName() {
		return pName;
	}
	// 책 정보 출력 메서드
	public void bookInfo() {
		System.out.println(bName+" : "+pName);
	}
}
  • 메인
public class BookMain {
	public static void main(String[] args) {
		Book[] lib = new Book[5];
		// 인스턴스 생성 후 배열에 대입(저장)
		lib[0] = new Book("AA","aa");
		lib[1] = new Book("BB","bb");
		lib[2] = new Book("CC","cc");
		lib[3] = new Book("DD","dd");
		lib[4] = new Book("EE","ee");
		
		// [0] ~ [4] 5개출력
		// 각각 Book 인스턴스의 주소
		for(int x=0; x<lib.length; x++) {
			System.out.println(lib[x]);
		}
		
		
		// 주소가 가지고있는 메서드 호출
		for(int x=0; x<lib.length; x++) {
			lib[x].bookInfo();
		}
	}
}

상속

- inheritance (상속) #1

  • 변수, 메서드 물려받음 (생성자는 메모리에 올라가지 않음)
  • 조상은 하나의 클래스만 가능 1:1, 상속
  • 자손의 객체를 생성하면 조상의 객체도 생성됨 - 상속받음 : 조상의 주소를 참조
class A{
	int x = 10;
	
}
class B extends A{
	int y = 20;
}
class C extends A{
	int z = 30;
}
class D extends B{
	int i = 40;
}
public class InheritanceEx01 {
	public static void main(String[] args) {
		// 객체 생성
		B b = new B();
		System.out.println(b.y);		// 20
		System.out.println(b.x);		// 10
        								// A를 상속받아 x값 사용 가능
		
		System.out.println("=====");
		
		D d = new D();					// D만 생성해도 B,A 객체 생성
		System.out.println(d.i);		// 40
		System.out.println(d.y);		// 20 . B로부터 상속 받음
		System.out.println(d.x);		// 10 . A로부터 상속 받음
	}
}

- super.

  • 조상의 변수 사용
  • 자손의 변수와 이름이 같을 때 사용

super()

  • 조상의 생성자 호출
  • 생성자 내에서만 사용
  • 첫 줄에만 사용 가능 (this()와 함께 사용 불가)
    super. / super() 모두 조상의 것을 의미
class AAA{
	int x = 100;
}

class BBB extends AAA{
	int y = 111;
	int x = 222;		// BBB 클래스의 변수 x		
				// 클래스가 달라서 같은 이름 변수 사용 가능
	
	public void bb() {
		int x = 50;
		System.out.println(x);			// 지역 변수
		System.out.println(this.x);		// 객체 자신
		System.out.println(super.x);	// 조상
	}
}

public class InheritanceEx03 {
	public static void main(String[] args) {
		BBB bbb = new BBB();
		bbb.bb();					// 50 - 가까운 x
									// 222 - bbb의 x
									// 100 - 조상의 x
	}
}

- 오버라이딩

덮어쓰기

선언부(: 매개변수 부분까지) 같고, 구현부 다르게 한 것

접근제어자 강하게(범위 좁게) 못함

  • public을 protected, default, private로 변경 불가

- 언노테이션

@

구별 쉽게 알려주는 역할

생략 가능

오버라이딩 @Override (첫글자 대문자)

class AA{
	// 인스턴스 변수
	int x;
	
	// 생성자
	AA(){
		this(100);
	}
	AA(int x){
		this.x = x;
	}
	// 메서드

	public void aa()	// 선언부
	{					// 구현부
		System.out.println("조상의 메서드 AA");
	}
}
class BB extends AA{
	// 변수
	int y;
	
	// 생성자
	BB(){
//		x = 444;		// 클래스가 다르기때문에 값 대입가능
		super(444);		// 조상 값 호출
		y = 500;
	}

	@Override				
	public void aa() {			// 오버라이딩 : 덮어쓰기
		System.out.println("자손이 수정한 BB");
	}
	public void aa(int i) {		// 오버로딩 : 이름만 같은것
		
	}
}
	public class InheritanceEx02 {
	   public static void main(String[] args) {
			BB bb = new BB();
			System.out.println(bb.x);
			System.out.println(bb.y);
			bb.aa();
	   }
}

2024.02.24

- inheritance (상속) #2

파이(PI) 출력

  • 조상 클래스 생성
public class Calculator {
	public double areaCircle(double r) {
		System.out.println("Calculator 객체의 areaCircle() 실행");
		return 3.14159 *r*r;
	}
}

  • 자손 클래스 생성
public class Computer extends Calculator {	// 상속받음
	@Override
	public double areaCircle(double r) {
		System.out.println("Computer 객체의 areaCircle() 실행");
		return Math.PI *r*r;		// Math API 참조
	}
}
  • main에서 실행

public class Main {
	public static void main(String[] args) {
		Calculator cal = new Calculator();
		Computer com = new Computer();
		
		int r = 1;
		double r2 = cal.areaCircle(r);
		System.out.println(r2);				// 3.14159
		System.out.println("=====");
		double r3 = com.areaCircle(r);
		System.out.println(r3);
        // 3.1415926535 .. Math.PI 를 사용하여 보다 정확한 값 출력
	}
}

- final 메서드의 오버라이딩

오버라이딩 할 수 없음

  • 조상 클래스 생성
public class Car {
	// 변수
	public int speed;
	
	// 메서드
	public void speedUp() {
		speed += 1;
	}
	
	// final 메서드
	public final void stop() {
		System.out.println("차를 멈춤");
		speed = 0;
	}
}
  • 자손 클래스 생성
public class SportsCar extends Car {
	@Override
	public void speedUp() {				// 선언부 같게
		speed += 5;						// 구현부 다르게
	}
	
/*	
	// 오버라이딩 할 수 없음
	@Override
	public final void stop() {
		System.out.println("차를 멈춤");
		speed = 0;
	}
*/

- final 클래스의 상속

final 붙이면 상속할 수 없음

  • 조상 final 클래스 생성
package day0224;

public final class FinalSuper {
	
}
  • 자손 클래스 생성
package day0224;
// public class FinalSub extends FinalSuper {}
// final 붙이면 상속할 수 없음
public class FinalSub {

}

2024.02.26

- 강제 타입 변환 (Casting)

  • 부모 타입을 자식 타입으로 변환 하는 것
  • 모든 부모 타입을 자식 타입으로 강제변환 할 수 있는것은 아님
  • 자식 타입이 부모 타입으로 자동 타입 변환된 후,
    다시 자식 타입으로 변환할 때 강제 타입변환 사용

자식 -> 부모 : 자동 변환

  • 부모타입에 선언된 변수와 메서드만 사용 가능하다는 제약
  • 자식타입의 변수와 메서드를 사용해야 한다면,
    자식타입으로 변환(강제 타입 변환) 후에 자식의 변수/메서드 사용

  • 부모 클래스
public class Parent {
	// 변수
	public String var1;
	
	// 메서드
	public void method1() {
		System.out.println("Parent - method1()");
	}
	public void method2() {
		System.out.println("Parent - method2()");
	}
}

  • 자손 클래스
public class Child extends Parent {
	// 자식 클래스의 변수
	public String var2;
	
	// 자식 객체의 메서드
	public void method3() {
		System.out.println("Child - method3()");
	}
}

  • 메인 클래스
	public static void main(String[] args) {
		// 자동 타입 변환
		Parent parent = new Child();
		parent.var1 = "data1";
		parent.method1();
		parent.method2();
		
		// 자식 객체만의 변수, 메서드
//		parent.var2 = "data2";				// 불가능
//		parent.method3();					// 불가능
		
		// 강제 타입 변환
		// 자식 객체로 변환
		Child child = (Child)parent;
		child.var2 = "aaa";					// 가능
		child.method3();					// 가능
	}
}

- instanceof 연산자

  • 어떤 객체가 어떤 클래스의 인스턴스인지 확인할 때 사용
  • instanceof 기준 왼쪽에 객체, 오른쪽에 타입이 옴
  • 왼쪽의 객체가 오른쪽의 타입으로 생성 -> true / 아니면 -> false
  • 매개값의 타입 조사할때 주로 사용
  • 메서드 내에서 강제 타입 변환이 필요한 경우,
    매개값이 어떤 객체인지 instanceof 연산자로 확인한 후
    안전하게 강제 타입 변환을 해야 함
  • ClassCastException 예외 발생

  • 부모 클래스
public class Parent2 {

}

  • 자손 클래스
public class Child2 extends Parent2 {

}

  • 메인 클래스
	public static void method1(Parent2 parent2) {
		if(parent2 instanceof Child2) {
			Child2 child2 = (Child2) parent2;
			System.out.println("method1 - Child2 변환 성공");
		}else {
			System.out.println("method1 - Child2 변환되지 않음");
		}
	}
	
	public static void method2(Parent2 parent2) {
		Child2 child2 = (Child2) parent2;
		System.out.println("method2 - Child2 변환 성공");
	}
	
	public static void main(String[] args) {
		Parent2 parentA = new Child2();

		method1(parentA);		// 변환 성공
		method2(parentA);		// 변환 성공
		
		Parent2 parentB = new Parent2();
		method1(parentB);		// 변환되지 않음
		method2(parentB);		// 예외발생
	}

- 추상(abstract)

  • 사전적 의미 : 실체들간의 공통된 특성
    동물, 회사 실체들의 공통적인 특성을 가지고있는 추상적인 것
  • 클래스에서도 추상 클래스 존재
  • 객체를 직접 생성할 수 있는 클래스 - 실체 클래스
    이 클래스들의 공통적인 특성을 추출해서 선언한 클래스 - 추상 클래스
  • 추상 - 실체 (조상 - 자손)
  • +) 추가적인 특성을 가질 수 있음
  • 추상 클래스는 new 를 이용해서 객체를 생성할 수 없음
  • 상속을 통해서 자식 객체를 만들어서 사용
  • 클래스들이 동일한 멤버(변수/메서드)를 가져야 할 때,
    추상 클래스로 만들어두면 편함

추상 클래스 선언

  • 조상 클래스
// 패키지에 추상 클래스 A 표시
public abstract class Phone {
	// 변수
	public String user;
	
	// 생성자
	public Phone(String user){			// 구현부
		this.user = user;
	}
	
	// 메서드		
	// 자식 객체에서 super사용해야함. new 사용 못하기 때문
	public void turnOn() {
		System.out.println("폰 전원을 켭니다.");
	}
	public void turnOff() {
		System.out.println("폰 전원을 끕니다.");
	}
}

  • 자손 클래스
public class SmartPhone extends Phone {
	// 생성자
	public SmartPhone(String user) {
		super(user);		// 조상의 생성자 호출(사용)
	}
	
	// 메서드
	public void internetSearch() {
		System.out.println("인터넷 검색을 합니다.");
	}
}

  • 메인 클래스
public class AbstractEx01 {
	public static void main(String[] args) {
		// 추상 클래스인 조상의 객체 생성 불가
		// Phone phone = new Phone();			// x
		
		SmartPhone smartPhone = new SmartPhone("java");
		
		// 자식 객체로 조상의 멤버(변수/메서드) 사용
		smartPhone.turnOn();
		smartPhone.internetSearch();		// 자식 객체의 메서드
		smartPhone.turnOff();
	}
}

2024.02.28

- 인터페이스(interface)

객체의 사용방법을 정의한 타입

개발 코드를 수정하지 않고, 객체를 변경할 수 있도록 하기 위함

여러 객체들과 사용이 가능하므로, 어떤 객체를 사용하는지에 따라 실행결과, 리턴값이 다를 수 있음

- 인터페이스의 선언

public interface 인터페이스명{...}

public : 다른 패키지에서도 인터페이스에 접근이 가능

interface 키워드 사용

인터페이스명 : 클래스 명명규칙과 같음

- 구성 요소

클래스 : 변수, 생성자, 메서드 와는 달리, 상수 변수, 추상 메서드 를 가짐

  • 8버전 부터 default 메서드, static 메서드 사용 가능
	interface 인터페이스명{
		// 상수 변수
		[public static final] 타입 변수명 =;
		// 추상 메서드
		[public abstract] 리턴타입 메서드명(매개변수, ..);
		// 8버전 이상
		// 디폴트 메서드
		[public] default 리턴타입 메서드명(매개변수, ..){구현부}
		// 정적 메서드
		[public] static 리턴타입 메서드명(매개변수, ..){구현부}	
	}

- 구현 클래스

보통의 클래스와 동일

인터페이스 타입으로 사용할 수 있음을 알려주는 implements 키워드 사용 후 인터페이스명 써줌

추상 메서드들을 반드시 구현해야함

  • public 접근제어자 생략 안됨

구현객체를 인터페이스 변수에 대입해서 사용

  • 인터페이스 변수;
  • 변수 = 구현객체;
  • 인터페이스 변수 = 구현객체

리모컨 인터페이스

  • interface
public interface RemoteControl {
	// 상수 변수
	public int MAX_VOLUME = 10;
	public int MIN_VOLUME = 0;
	
	// 추상 메서드
	// 리턴 메서드명 매개변수 ;
	public void turnOn();
	public void turnOff();
	public void setVolume(int volume);
	
	// 디폴트 메서드
	// 실행 내용까지 작성 -> 구현부 완성
	public default void setMute(boolean mute) {
		if(mute) {
			System.out.println("무음처리를 합니다.");
		}else {
			System.out.println("무음 해제 합니다.");
		}
	}
	
	// 정적 메서드
	// 배터리 교환 기능
	public static void changeBettery() {
		System.out.println("건전지를 교환합니다.");
	}
}

  • TV 클래스
public class Television implements RemoteControl {
	// 변수
	private int volume;			// 변수에 직접접근을 막고 활용. 캡슐화

	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
	}

	@Override
	// 인터페이스의 상수를 이용하여 volume 값을 제한
	public void setVolume(int volume) {
		if(volume > RemoteControl.MAX_VOLUME) {
			this.volume = RemoteControl.MAX_VOLUME;
		}else if(volume < RemoteControl.MIN_VOLUME) {
			this.volume = RemoteControl.MIN_VOLUME;
		}else {
			this.volume = volume;
		}
		System.out.println("현재 TV 볼륨 : "+this.volume);
	}	
}

  • 메인
	public static void main(String[] args) {
		RemoteControl rc;
		rc = new Television();
		rc = new Audio();
        // 대입 가능 확인. 인터페이스를 구현하고있는 클래스
		
		// 인터페이스 변수 선언
		RemoteControl rc2 = null;
		
		// TV 객체를 인터페이스에 대입
		rc2 = new Television();
		rc2.turnOn();
		rc2.setVolume(5);
		rc2.setMute(true);
		rc2.setMute(false);
		rc2.turnOff();
		
		System.out.println("=====");
		
		rc2 = new Audio();
		rc2.turnOn();
		rc2.setVolume(7);
		rc2.setMute(true);
		rc2.setMute(false);
		RemoteControl.changeBettery();
		rc2.turnOff();
	}
}

- 다중 인터페이스

객체는 다수의 인터페이스 타입으로 사용할 수 있음

(반드시) 모든 인터페이스의 추상 메서드를 구현해야 함

	public class 구현클래스 implements 인터페이스A, 인터페이스B, .. {
		// 인터페이스A의 추상 메서드 구현
		// 인터페이스B의 추상 메서드 구현
	}

다중 인터페이스 구현

  • Searchable 인터페이스
public interface Searchable {
	// 검색하는 메서드
	void search(String url);
}

  • 리모컨 인터페이스
public interface RemoteControl {
	// 상수 변수
	public int MAX_VOLUME = 10;
	public int MIN_VOLUME = 0;
	
	// 추상 메서드
	// 리턴 메서드명 매개변수 ;
	public void turnOn();
	public void turnOff();
	public void setVolume(int volume);
	
	// 디폴트 메서드
	// 실행 내용까지 작성 -> 구현부 완성
	public default void setMute(boolean mute) {
		if(mute) {
			System.out.println("무음처리를 합니다.");
		}else {
			System.out.println("무음 해제 합니다.");
		}
	}
	
	// 정적 메서드
	// 배터리 교환 기능
	public static void changeBettery() {
		System.out.println("건전지를 교환합니다.");
	}
}

  • 스마트TV 클래스
public class SmartTelevision implements RemoteControl, Searchable {
	// 변수
	private int volume;
	
	@Override
	public void turnOn() {
		System.out.println("스마트티비를 켭니다.");
	}

	@Override
	public void turnOff() {
		System.out.println("스마트티비를 끕니다.");		
	}

	@Override
	public void setVolume(int volume) {
		if(volume > RemoteControl.MAX_VOLUME) {
			this.volume = RemoteControl.MAX_VOLUME;
		}else if(volume < RemoteControl.MIN_VOLUME) {
			this.volume = RemoteControl.MIN_VOLUME;
		}else {
			this.volume = volume;
		}
		System.out.println("현재 SmartTelevision 볼륨 : "+this.volume);
	}
	
	@Override
	public void search(String url) {
		System.out.println(url+" 을 검색합니다.");
	}
}

  • 메인
	public static void main(String[] args) {
		SmartTelevision st = new SmartTelevision();
		
		RemoteControl rc = st;
		Searchable sc = st;
		
		st.turnOn();
		rc.setVolume(7);
		rc.setMute(true);
		rc.setMute(false);
		sc.search("www.google.com");
		rc.turnOff();
		RemoteControl.changeBettery();
		rc.turnOn();
		st.turnOff();
	}
}

2024.02.29

- 디폴트 메서드

  • 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위함

  • 기존 인터페이스 이름과 추상 메서드의 변경 없이 디폴트 메서드의 추가만으로 이미 구현된 객체는 수정 없이 그대로 사용 가능

  • 새로 구현할 객체는 추가된 기능 활용 가능

  • 인터페이스

public interface MyInterface {
	// 추상 메서드
	public void method1();

/*
	기능 추가 필요
	public void method2();
	추상 메서드는 하위 클래스에서 구현이 필수
	구현을 하지 못할 경우 디폴트 메서드를 사용
*/
	// 디폴트 메서드
	public default void method2() {
		System.out.println("MyInterface - method2() 실행");
	}
}

  • 클래스 A
// MyInterface 를 구현할 클래스
public class MyClassA implements MyInterface {

	@Override
	public void method1() {
		System.out.println("MyClassA - method1() 실행");
	}
}
// 완성 프로그램
// 기능의 추가가 필요하다면
// MyInterface에 기능을 추가해야함

  • 클래스 B
public class MyClassB implements MyInterface {

	
	public void method1() {
		System.out.println("MyClassB - method1() 실행");
	}

	
	public void method2() {
		System.out.println("method2() - Override 실행");
	}
}
  • 메인

public class Main {
	public static void main(String[] args) {
		MyInterface m1 = new MyClassA();
		m1.method1();	// 추상 메서드 구현 완료한 메서드 호출
		m1.method2();	// 오버라이딩 안함. 받은 그대로 실행
		
		MyInterface m2 = new MyClassB();
		m2.method1();	// 구현 완료 메서드
		m2.method2();	// 오버라이딩 한 메서드
	}
}

- 중첩 클래스

  • 클래스 내부에 선언한 클래스
  • 보톤은 클래스-클래스 관계를 맺을때 각각 독립적으로 선언
  • 특정 클래스하고만 관계를 맺을때 사용
    클래스 내부에 선언하는 것이 좋음
		- 기본 형태
		class ClassName{
			class NestedClass{
				
			}
		}
        
		- 중첩 인터페이스
		class ClassName{
			interface NestedInterface{
				
			}
		} 

선언된 위치에 따라 두 가지로 분류됨

멤버클래스

  • 인스턴스 멤버클래스
    객체를 생성해야만 사용 가능한 중첩 클래스
  • 정적 멤버클래스
    객체 생성 없이 접근 가능한 중첩 클래스

지역 클래스

  • 지역변수처럼 메서드 안에 위치함
class A {						
	class B {}			// 인스턴스 클래스	- 사용 : 객체 생성 후
	static class C {}	// 정적 클래스		- 사용 : 클래스명.클래스명
	void add() {				
		class D {}		// 지역 클래스		- 사용 : 해당 지역 내에서만
	}
}

  • ex)
class AA {
	static int num = 100;				// 정적 변수
	static void add() {					// 정적 메서드
		System.out.println("num = "+num);
	}
	static class BB {					// 정적 클래스
		static int x = 500;				// 정적 변수
		int y = 777;					// 인스턴스 변수
	}
}
public class NestedEx02 {
	public static void main(String[] args) {
		System.out.println(AA.num);		// 정적 - 객체생성 없이 사용
		AA.add();						// 정적 - 객체생성 없이 사용
		System.out.println(AA.BB.x);	// 정적 BB 내부 클래스 속 정적 변수
										// AA BB 객체생성 없이 사용 가능
//		System.out.println(AA.BB.y);
		
		AA.BB b = new AA.BB();			
		System.out.println(b.y);		// B 라는 내부 클래스 속 인스턴스 멤버
	}
}

  • ex2)
class AAA {
	int aaa = 10;
	class BBB {						// 인스턴스 클래스
		static final int z = 500;	// 정적 변수는 상수만 가능
		int x = 100;
	}
}

public class NestedEx03 {
	public static void main(String[] args) {
		AAA a = new AAA();
		AAA.BBB b = a.new BBB();	// a. 으로 들어가서 new 사용
									// 중첩 인스턴스 클래스 객체 생성
		a.aaa = 20;
		System.out.println(b.x);
		System.out.println(AAA.BBB.z);
	}
}

  • ex3)
class N {
	void add() {			// 메서드
		class M {			// 지역 클래스
			int x = 100;	// 이 메서드 내에서만 사용
		}
		M m = new M();		// 해당 지역 내에서만 사용 가능
							// 메서드 내부에서 생성하고 사용해야함
		System.out.println(m.x);
	}
}

public class NestedEx04 {
	public static void main(String[] args) {
		N n = new N();
		n.add();
	}
}

- 익명 구현 객체

  • 구현 클래스 만들어서 객체로 사용 (일반적임)
    클래스 재사용할 수 있어서 편리
  • 일회성의 구현객체가 필요할 때
    클래스 만들고 생성하는 것이 비효율적
  • 익명 구현 객체를 생성하여 인터페이스 변수에 대입하여 사용
    하나의 실행문이므로 끝에는 ;(세미콜론)을 붙여야함 {};
	인터페이스 변수 = new 인터페이스() {
		// 인터페이스에 선언된 추상 메서드 모두 구현
		// 추가적 변수 / 메서드 익명객체 안에서만 사용 가능
		// 인터페이스 변수로 접근 불가능
	};

  • 리모컨 인터페이스
public interface RemoteControl {
	// 추상 메서드(매개변수) ; -> 선언부
	public void turnOn();
	public void turnOff();
	public void setVolume(int volume);
}

  • 메인
	public static void main(String[] args) {
		RemoteControl rc = new RemoteControl() {

			@Override
			public void turnOn() {
				System.out.println("On");
			}

			@Override
			public void turnOff() {
				System.out.println("Off");
			}

			@Override
			public void setVolume(int volume) {
				System.out.println(volume);
			}
		};									// 실행문이기때문에 세미콜론 붙여줘야함
		rc.turnOn();
		rc.setVolume(11);
		rc.turnOff();
	}
}
// 많이 쓰이진 않지만 필요하다면 쓰이는 방법

2024.03.04

예외 (Exception)

- 예외

자바에서는 에러 외의 오류로는 예외가 있음

예외가 발생하면 프로그램 곧바로 종료됨 (에러와 동일)

예외 처리를 통해 프로그램을 종료하지 않고 정상 실행상태가 유지되도록 함

  • 모든 예외 클래스는
    java.lang.Exception
    클래스를 상속받음

- 일반 예외

코드 작성 실수로 발생함

컴파일러 체크 예외 라고도 함

예외 처리 반드시 필요

- 실행 예외

예외 처리 필수 아님

경험에 의해 예외처리 코드 삽입 필요

모든 예외 알 수 없음 -> 자주 발생하는 실행 예외 알아둬야함

- NullPointerException

가장 빈번하게 발생

객체 참조가 없는 상태

참조변수.~ -> 객체가 없는데 객체를 사용하려고 했으니 예외 발생

  • ex)
	public static void main(String[] args) {
		String data = null;		// String 객체를 참조하고있지 않음
		System.out.println(data.toString());
		// .toString() : String 객체가 가지고있는 메서드
	}
// NullPointerException 예외 발생

- ArrayIndexOutOfBoundsException

배열에서 인덱스 범위를 초과, 부족하게 사용 할 경우 발생

매개값이 없는 경우

길이(개수, 크기) 먼저 조사해야 함

  • ex)
	public static void main(String[] args) {
		if (args.length == 2) {	// 개수가 2개인지 확인
			String data1 = args[0];
			String data2 = args[1];
			System.out.println("args[0] = "+args[0]);
			System.out.println("args[1] = "+args[1]);
		}else {
			System.out.println("[실행 방법]");
			System.out.print(" java ArrayIndexOutOfBoundsException ");
			System.out.println("값1 값2");
		}
	}	// 매개값이 없어서 예외 발생

- NumberFormatException

문자열의 데이터를 숫자로 변경하는 경우 자주 발생

문자열을 숫자로 변환하는 방법 여러가지 있음

  • 가장 많이 사용되는 코드로 확인
  • ex)
	public static void main(String[] args) {
		String data1 = "100";
//		String data2 = "a100";
		String data2 = "200";
		
//		Integer 클래스의 .parseInt(String s) 메서드 호출
//		주어진 문자열을 정수로 변환 후 리턴
		int value1 = Integer.parseInt(data1);
		int value2 = Integer.parseInt(data2);
//		예외 발생
//		숫자로 변환 할 수 없는 문자가 포함되어 발생함
//		data2 대입값 변경
		
		int result = value1 + value2;
		
		System.out.println(data1+" + "+data2+" = "+result);
	}

- try-catch-finally

프로그램에서 예외가 발생하면, 갑작스러운 종료가 일어남

이러한 종료를 막고, 정상 실행을 마칠 수 있도록 처리하는 코드

		try {
			예외발생 가능 코드
		}catch (예외클래스 e (Exception 앞글자 e)) {
			예외처리
		}finally {
			항상 실행하는 코드;
		}

정상 실행

  • 예외처리 catch 부분을 넘어가고 finally 부분 실행

예외 발생

  • 예외처리 catch 부분 실행 후, finally 부분 실행
    이 때, finally 부분은 생략 가능
    예외 발생 여부와 상관없이 항상 실행 할 내용이 있을 경우에 사용
  • ex)
	public static void main(String[] args) {
		try {
//		.forName() : 매개값으로 주어진 class 가 존재하면,
//					 이 클래스 객체를 리턴함
//			존재하지 않으면 ClassNotFoundException 예외 발생

			Class c = Class.forName("java.lang.String2");
//			일반예외 이므로, 컴파일러가 알려줌
		} catch (ClassNotFoundException e) {
			System.out.println("클래스가 존재하지 않습니다.");
		}
	}

- 예외 떠넘기기 throws

경우에 따라서 메서드를 호출한 곳으로 예외를 떠넘길 수 있음

메서드 선언부 끝에 작성되어, 메서드에서 처리하지 않은 예외를 호출한 곳으로 떠넘기는 역할을 함

throws Exception 만으로 모든 예외를 간단히 떠넘길 수 있음

          리턴타입 메서드명(매개변수, ..) throws Exception {
//		  반드시 try-catch 블럭 내에서 호출되어야 함
//		  catch 블럭에서 떠넘겨 받은 예외를 처리해야 함
		}
  • ex)
	public static void main(String[] args) {
		try {
			thMethod();				// try 블럭 내에서 호출
		} catch (ClassNotFoundException e) {
			// 호출한 곳에서 예외 처리
			System.out.println("클래스가 존재하지 않습니다.");
		}
	}
	
	public static void thMethod() throws ClassNotFoundException {
		Class c = Class.forName("java.lang.String2");
	}

2024.03.05

Object

- toString

객체(인스턴스) 정보를 문자열로 리턴

원형 : 객체의 클래스이름과 주소값

클래스 이름 16진수 해시코드 값

상속받은 클래스들 중에는 미리 재정의한 클래스들 많음

재정의(오버라이딩)된 메서드 호출

메서드를 직접 재정의하면, 객체의 참조변수를 이용해 원하는 문자열을 표현할 수 있음

  • 클래스
class Book {
//	변수
	String bookTitle;	// 책 제목
	int bookNumber;		// 책 번호
	
//	생성자
//	책제목, 책번호를 매개값으로 받음
	Book(String bookTitle, int bookNumber) {
		this.bookTitle =  bookTitle;
		this.bookNumber = bookNumber;
	}

//	@Override	없어도됨
//	책제목,책번호 정보를 가져오도록 구현부 바꿔줌
	public String toString() {
		String result = "책제목 : "+bookTitle + ", 책번호 : "+bookNumber;
		return result;
	}
}

  • 메인
	public static void main(String[] args) {
//		객체 생성
		Book book = new Book("java",2024);
		System.out.println(book);
//		참조변수 -> 객체의 정보(클래스이름, 주소값) 확인
//		toString() 메서드 자동 호출
//		Object 클래스의 toString() 메서드
		System.out.println(book.toString());
		
//		출력결과가 클래스이름과 주소값이 아닌 경우
//		String, Integer 클래스 -> 이미 오버라이딩 되어있음
		String str = new String("test");
		System.out.println(str);
		
		Integer i = new Integer(100);
		System.out.println(i);
	}

- hashCode()

참조변수를 출력할 때 본 16진수의 숫자 값 : 해시코드 값

힙 메모리에 저장된 객체의 주소 값

두 객체가 같다면, hashCode() 메서드에서 해시코드의 값이 같아야 함

.equals() 메서드를 재정의(오버라이딩) 했다면,
.hashCode() 메서드도 재정의 필요

	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
//		.hashCode()
		System.out.println(str1.hashCode());
		System.out.println(str2.hashCode());
		
//		같은 문자열을 가진 경우
//		.equals() 메서드의 결과가 true 인 경우
//		hashCode() 메서드도 동일한 해시코드 값 리턴
		
		Integer i1 = new Integer(100);
		Integer i2 = new Integer(100);
		
		System.out.println(i1.hashCode());
		System.out.println(i2.hashCode());
		
//		같은 정수를 가진 경우
//		.equals() 메서드의 결과가 true 인 경우
//		hashCode() 메서드도 동일한 해시코드 값 리턴
	}

- .clone()

원본 객체를 복사(복제)

동일한 값을 가지는 새로운 객체 생성

원본 객체를 보호할 때 사용

반드시 원본 객체에 java.lang.Cloneable 인터페이스 구현하지 않으면 예외 발생 -> 예외처리 필수

  • 원본객체 생성
public class Member implements Cloneable {	// 복제 가능 명시
//	변수
	public String id;		// 아이디
	public String name;		// 이름
	public String pw;		// 비밀번호
	public int age;			// 나이
	public boolean adult;	// 성인인증
	
//	생성자
	public Member(String id, String name, String pw, int age, boolean adult) {
		this.id = id;
		this.name = name;
		this.pw = pw;
		this.age = age;
		this.adult = adult;
	}
	
//	메서드
	public Member getMember() {
		Member clone = null;
		try {							// 예외처리 필수
			clone = (Member) clone();	// 리턴타입 Object 때문에 강제변환 필요
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return clone;
	}
}

  • 메인
	public static void main(String[] args) {
//		원본 객체 생성
		Member origin = new Member("java","jsp","12345",10,true);
		
//		복제 객체 받음
		Member clone = origin.getMember();
		clone.pw = "678910";	// 복재 객체의 비번값 변경
		
		System.out.println("[ 원본 객체의 값 ]");
		System.out.println(origin.id);
		System.out.println(origin.name);
		System.out.println(origin.pw);		// 원본 변함 없음
		System.out.println(origin.age);
		System.out.println(origin.adult);
		
		System.out.println("=====");
		
		System.out.println("[ 복제 객체의 값 ]");
		System.out.println(clone.id);
		System.out.println(clone.name);
		System.out.println(clone.pw);		// 값 변함
		System.out.println(clone.age);
		System.out.println(clone.adult);
	}

- String 클래스

자바 문자열을 사용할 수 있도록 String 클래스 제공

  • 문자열을 다양한 생성자를 이용하는 방식
  • 코드상에서 리터럴 String 객체가 자동으로 생성되는 방식
	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(str1 == str2);		// F new 새로운 객체. 주소 다름
		System.out.println(str1.equals(str2));	// T
		
		String str3 = "abc";		// 상수 풀에 존재 "abc".
		String str4 = "abc";		// 직접 쓰는 리터럴값들은 자바에서 준비되어있음
									// 변수가 가리키는 것들은 같은 주소라고 보면 됨
//		str3 += "ggg";
//		System.out.println(str3);	// abcggg 출력
		
		System.out.println(str3 == str4);		// T
		System.out.println(str3.equals(str4));	// T
	}

  • API 참조
	public static void main(String[] args) {
		String str = "자바 프로그래밍";
		
//		.length() : 문자열의 길이, 크기, 개수
		int len = str.length();
		System.out.println(len);	// 8 공백도 문자
		
//		.replace() : 특정 문자열을 다른 문자열로 대체
//		앞에 있는 문자열을 : 뒤에 문자열로 바꿔줌.
//					: java 프로그래밍
		String str2 = str.replace("자바", "java");
		System.out.println(str2);
//		java 프로그래밍 . 이라는 새로운 객체가 생성됨
		
//		문자열 잘라내기
//		.substring(int 시작index) : 시작부터 끝까지
		String str3 = str.substring(4);
		System.out.println(str3);	// 로그래밍. 시작인덱스부터 끝까지 잘라냄
		
//		.substring(시작(포함), 끝(미만))
		String str4 = str.substring(3, 5);	// 프로
		System.out.println(str4);
		
//		.indexOf() : 문자열에서 특정 문자열의 위치
		int index
		= str.indexOf("프로그래밍");	// 3. 0 부터 3번째
		System.out.println(index);
		if (index == -1) {
			System.out.println("포함되어있지 않습니다.");
		}else {
			System.out.println("포함되어 있습니다.");
		}
		
//		.contains() : 단순히 포함 여부를 물을 때
//		있으면 true / 없으면 false 리턴
		boolean contains = str.contains("프로그래밍");
		System.out.println(contains);
		if (contains) {
			// 포함 true
		}else {
			// 포함 안함 false
		}
		
//		문자열 분리
//		.split() : 구분할 수 있는 것으로 분리할 때
		String str5 = "사과,딸기,포도,복숭아,바나나";
		String[] tokens = str5.split(",");
		System.out.println(tokens[0]);
		System.out.println(tokens[1]);
		System.out.println(tokens[2]);
		System.out.println(tokens[3]);
		System.out.println(tokens[4]);
		
		System.out.println("=====");
		
		for (int i=0; i<tokens.length; i++) {
			System.out.println(tokens[i]);
		}
		
		System.out.println("=====");
		
		for (String s : tokens) {
			System.out.println(s);
		}
		
		System.out.println("=====");
		
		System.out.println(str.toString());
		
//		.valueOf() : static -> 클래스이름.메서드(매개값)
//		매개값을 문자열로 변환 후 리턴
		String str6 = String.valueOf(100);
		System.out.println(str6);
		
//		.parseInt() : 문자열을 int 타입으로 변환
//		이 때, 정수로 변환할 수 있는 문자열 이여야 함
		int str7 = Integer.parseInt("1000");
		System.out.println(str7);
	}

	public static void main(String[] args) {
		String s1 = "hello world";
		String s2 = "  hello world ";
		String s3 = "HELLO WORLD";
		System.out.println(s1.charAt(0));		// h	0번째의 문자 리턴
		System.out.println(s1.indexOf('w'));	// 6	문자의 위치 리턴
		System.out.println(s1.indexOf('k'));	// -1	해당 문자가 없을때 -1 리턴
		System.out.println(s1.lastIndexOf('l'));	// 9	마지막에 오는 해당 문자의 위치 리턴
		System.out.println(s1.replace('h', 'a'));	// 왼, 오 : 왼쪽을 오른쪽으로 대체
		System.out.println(s1.replaceAll("hello", "aaa"));	// 문자열 바꿔서 리턴
		System.out.println(s1.toUpperCase());	// 소문자 -> 대문자 변환
		System.out.println(s3.toLowerCase());	// 대문자 -> 소문자 변환
		System.out.println(s1.substring(6));	// index 6번 부터 자른것을 리턴
		System.out.println(s1.substring(6, 9));	// index 6번 포함 9 미만을 자른것을 리턴
		System.out.println(s1.length());		// 문자열 개수, 크기, 길이
		System.out.println(s2.trim());			// 앞,뒤 공백 제거
		
		System.out.println("=====");
		
		String s4 = "java html css jsp spring";
		String[] s = s4.split(" ");
		
		for (int i=0; i<s.length; i++) {
			System.out.println(s[i]);
		}
	}

- StringBuilder

java.lang.StringBuilder 클래스, StringBuffer 클래스

문자열을 변경하거나 연결해야할 때 사용

ex) String str = "hello";
		   str += " world";
				 "hello world"
		  한 개의 String 객체가 사용되었다고 생각하지만
		  새로운 String 객체가 생성됨
		  그리고 str 변수는 새로운 객체를 참조함

공통점

  • 두 클래스는 내부 버퍼(buffer : 데이터 임시저장 메모리)에 문자열을 저장해두고, 그 안에서 추가, 수정, 삭제 가능
    사용 방법 동일

차이점

  • 여러 작업(스레드)이 동시에 문자열을 변경하려 할 때, 문자열이 안전하게 변경되도록 보장해 주는지, 그렇지 않은지

StringBuffer

  • 안전하게 보장
    멀티스레드 환경에서 사용 가능하도록 동기화 적용되어 있음
public class StringBuilderEx {
	public static void main(String[] args) {
		String str = new String("java");
		System.out.println("str 문자열 주소 : "+System.identityHashCode(str));
		
//		StringBuilder 생성
		StringBuilder sb = new StringBuilder(str);
		System.out.println("추가 전 sb 주소 : "+System.identityHashCode(sb));
		
//		.append() : 문자열 추가
		sb.append(" Programming");
		sb.append(" is");
		sb.append(" fun");
		
//		주소가 같음 -> 하나의 메모리에 계속 연결된다는 것을 알 수 있음
		System.out.println("추가 후 sb 주소 : "+System.identityHashCode(sb));
		
		str = sb.toString();
		System.out.println(str);
//		새로 생성됨
		System.out.println("새로 대입된 str 문자열 주소 : "+System.identityHashCode(str));
	}
}

2024.03.06

- 컬렉션 프레임워크

java.util.패키지 에서는 컬렉션 프레임워크 가장 중요

컬렉션 인터페이스를 구현하고있는 모든 인터페이스들은 Collection<Ε> 를 조상으로 두고있음

컬렉션 : 수집. 데이터의 보관 방법. 배열과 비슷(여러개의 값 보관)

배열의 단점을 보완하면서 세가지로 나뉨 (List, Set, Map 인터페이스)

배열의 단점

  • 저장할 수 있는 객체의 수가 배열을 생성할 때 결정됨
    즉, 정해진 메모리가 생섬됨
    컬렉션 프레임워크는 정해지지 않음
  • 객체를 삭제했을 때, 해당 인덱스가 비어짐
    컬렉션 프레임워크는 당겨져 채워짐
  • 들어가는 값의 타입이 지정되어있음
    불특정 다수의 객체를 저장하기에 문제 있음

컬렉션은 모든 타입 가능

<Ε> : 제네릭 으로 타입 지정 가능

- List 인터페이스

순서 유지 -> 인덱스 사용

값의 중복 허용

구현 클래스

ArrayList

import java.util.ArrayList;

class Data {}

public class ArrayListEx01 {
	public static void main(String[] args) {
//		객체 생성
		ArrayList list = new ArrayList();
		System.out.println(list);
		
//		.add() : 값을 넣음
		list.add(100);
		list.add("hello");
		System.out.println(list);
		
		list.add(100);
		list.add("hello");
		System.out.println(list);	// 중복 허용
		
		Data data = new Data();
		list.add(data);
		System.out.println(list);	// 타입 상관없음
		
		list.add(0, 77);			// add(인덱스, 값) 오버로딩
		System.out.println(list);	// 0번째, 77 // 뒤에 값은 한칸씩 밀려남
		
//		값 수정
//		.set(인덱스, 수정값) : 인덱스 번호의 값을 수정값 으로 수정함
		list.set(0, 700);			// 0번째, 700으로 수정
		System.out.println(list);
		
//		.size() : 저장된 데이터의 수를 구하는 메서드
		System.out.println(list.size());	// 인덱스 번호는 사이즈-1
		
//		.get(인덱스) : 지정된 인덱스 값을 리턴함
		System.out.println(list.get(0));
//		System.out.println(list.get(10));	// 지정된 값이 없어서 오류
		
//		.remove(인덱스) : 지정된 인덱스 값을 삭제함
		list.remove(0);
		System.out.println(list);	// 0번째 삭제. 뒤에값이 한칸씩 앞으로
		
//		.clear() : 전체 삭제
		list.clear();
		System.out.println(list);
	}
}

  • for문 이용한 정렬
import java.util.ArrayList;

public class ArrayListEx02 {
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<Integer>();

		for (int i=0; i<10; i++) {
			list.add(i+1);
			System.out.println(list.get(i));
		}
	}
}

  • API 활용
import java.util.ArrayList;
import java.util.Collections;

public class ArrayListEx05 {
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(new Integer(10));	// list.add(10); 와 같지만, 객체 활용
		list.add(new Integer(5));	// 1번째 index 에 5 대입
		list.add(new Integer(4));	// : 정확히는 Integer 의 주소가 들어감
		list.add(new Integer(2));
		list.add(new Integer(0));
		list.add(new Integer(1));
		list.add(new Integer(3));
		
		System.out.println(list);
		
		list.subList(1, 4);
//		1번째 부터 4번째 미만 까지
		ArrayList list2 = new ArrayList(list.subList(1, 4));
		System.out.println(list2);
		
		System.out.println("=====");
		
//		.sort() : 정렬 하는 기능 - 오른차순
		Collections.sort(list);
		Collections.sort(list2);
		
		System.out.println("sort()");
		System.out.println(list);
		System.out.println(list2);
		
		System.out.println("=====");
		
//		.reverseOder() : 역차순 정렬 - 내림차순
		Collections.sort(list, Collections.reverseOrder());
		Collections.sort(list2, Collections.reverseOrder());
		
		System.out.println("reverseOder()");
		System.out.println(list);
		System.out.println(list2);
		
//		.contains - 포함 유무 true/false
		System.out.println(list.contains(0));
		System.out.println(list2.contains(9));
		
//		.containsAll - 비교객체가 기준객체에 포함 되어있는지 비교
		System.out.println(list.containsAll(list2));
		System.out.println(list2.containsAll(list));
		
//		.retainAll - 같은것(교집합)만 남기고 다른것은 삭제함
//		list.retainAll(list2);
//		System.out.println(list);
//		System.out.println(list2);

	}
}

- Set 인터페이스

저장 순서가 없음

객체의 중복저장 불가 (오류 안남)

  • 구현 클래스
    HashSet
    TreeSet
  • 정렬 기능
    프로그래밍에서 제일 비효율적
    데이터의 양이 많으면, 정렬을 하려고 비교를 하기 때문에 많을수록 느려짐
  • HashSet
import java.util.Date;
import java.util.HashSet;

class Test{}

public class SetEx01 {
	public static void main(String[] args) {
		HashSet hs = new HashSet();
//		.add() : 값 넣음
		hs.add(100);
		hs.add("hello");
		hs.add(7);
		Date d = new Date();
		Date d2 = new Date();	// 중복값이라 한개만 입력됨
		hs.add(d);
		hs.add(d2);
		hs.add(new Date());
		hs.add(7);				// 중복값이라 한개만 입력됨
		hs.add(7);
		System.out.println(hs);	// 순서 없음. 중복값 허용 안함
		
//		객체 생성 -> 주소가 다 다르기 때문에 값이 들어감
		hs.add(new Test());
		hs.add(new Test());
		hs.add(new Test());
		System.out.println(hs);
	}
}

  • HashSet for문 활용
import java.util.HashSet;
import java.util.Set;

public class SetEx03 {
	public static void main(String[] args) {
		Object[] objarr = {"1", new Integer(1), "2", "2", "3", "4", 4, "4"};
		Set set = new HashSet();
		
		for (int i=0; i<objarr.length; i++) {
			set.add(objarr[i]);
		}
		System.out.println(set);	// 같은타입 같은값 중복안됨
	}
}

  • TreeSet
import java.util.TreeSet;

public class SetEx02 {
	public static void main(String[] args) {
		TreeSet ts = new TreeSet();		// 자동정렬. 비효율적
		ts.add('F');					// 마지막에 정렬되는것이 효율적
		ts.add('A');
		ts.add('z');
		ts.add('c');
		ts.add('B');
		ts.add('K');
		
		System.out.println(ts);
		
		TreeSet ts2 = new TreeSet();
		for (int i=0; ts2.size()<6; i++) {
			int num = (int)((Math.random()*45)+1);
			ts2.add(new Integer(num));
		}
		System.out.println(ts2);
	}
}

- Map 인터페이스

저장 순서 없음

Value 의 중복 허용

컬렉션에서 값을 저장하는 두 가지 방법

값만 저장 : List, Set ..
값에 이름을 붙여 저장 : Map ..

Key 와 Value 라는 형식으로 저장 (Key + Value 하나의 Entry 라고 함)

Key 중복 허용하지 않음. Value 중복 허용

  • 구현 클래스
    HashMap
    TreeMap

  • HashMap

import java.util.HashMap;

public class MapEx01 {
	public static void main(String[] args) {
		HashMap<Integer, String> hm = new HashMap<>();
		
//		.put(키, 값) : 값 넣기
		hm.put(33, "pw");
		hm.put(100, "java");
		System.out.println(hm);
		
		hm.put(100, "test");	// 키는 그대로 값이 변경됨
		System.out.println(hm);
		
		System.out.println(hm.get(100));	// 값을 꺼낼 때 Key 로 꺼냄
//		제네릭 String 으로 원래 Object 타입 이였던 get 을 String 타입으로 바꿔줌
		String sValue = hm.get(33);
		System.out.println(sValue);
	}
}

  • Scanner 활용
import java.util.HashMap;
import java.util.Scanner;

public class MapEx02 {
	public static void main(String[] args) {
		HashMap<String, String> hm = new HashMap<String, String>();
		
		hm.put("java", "1234");
		hm.put("jsp", "1111");
		hm.put("html", "1234");
		hm.put("html", "gggg");		// 같은 값 들어감. 덮어쓰기 됨
		System.out.println(hm);
		
//		Scanner s = new Scanner(System.in);
//		String id = s.nextLine().trim();
//		if (!hm.containsKey(id)) {}
		
		Scanner s = new Scanner(System.in);
//		String id = s.nextLine().trim();
		while (true) {
			System.out.println("아이디와 비번을 입력해주세요.");
			System.out.println("아이디 : ");
			String id = s.nextLine().trim();
			System.out.println("비밀번호 : ");
			String pw = s.nextLine().trim();
			System.out.println();
			if (!hm.containsKey(id)) {
				System.out.println("존재하지 않는 아이디 입니다. 다시 입력 바람");
				continue;
			}else {
				if (!(hm.get(id)).equals(pw)) {
					System.out.println("비밀번호가 일치하지 않습니다.");
				}else {
					System.out.println("아이디와 비밀번호가 일치합니다.");
					break;
				}
			}
		}
	}
}

  • Iterator, while문 활용
import java.util.*;
import java.util.Map.Entry;

public class MapEx04 {
	public static void main(String[] args) {
		Map<String, Integer> map = new HashMap<>();
//		^ 조상의 인터페이스				^ 구현 클래스
//		객체 저장
		map.put("java", 85);
		map.put("jsp", 90);
		map.put("html", 80);
		map.put("spring", 95);
		map.put("spring", 50);
		
//		.size() : 객체의 수
		System.out.println("총 엔트리 수 : "+map.size());
		
		System.out.println("=====");
		
//		.get(key) 
		Integer i1 = map.get("java");
		System.out.println(i1);
		
		System.out.println("=====");
		
//		키 Set 컬렉션을 얻고, 반복으로 키와 값을 꺼냄
		Set<String> keySet = map.keySet();		// 모든 키들을 모아주는 역할
		Iterator<String> kIter = keySet.iterator();
		
		while (kIter.hasNext()) {
			String k = kIter.next();
			Integer i = map.get(k);
			System.out.println(k+" = "+i);
		}
		
		System.out.println("=====");
		
//		키 Set 컬렉션을 얻고, 반복해서 키, 밸류 꺼냄
		Set set = map.entrySet();
		System.out.println(set);
		Iterator iter = set.iterator();
		
		while (iter.hasNext()) {
			Map.Entry e = (Map.Entry) iter.next();
			System.out.println(e.getKey()+" = "+e.getValue());
		}
		
		
//		엔트리 Set 컬렉션을 얻고, 반복해서 키, 밸류 꺼냄
		Set<Entry<String, Integer>> sEn = map.entrySet();
		Iterator<Entry<String, Integer>> sEI = sEn.iterator();
		while (sEI.hasNext()) {
			Entry<String, Integer> sEIe = sEI.next();
			String k = sEIe.getKey();
			Integer v = sEIe.getValue();
			System.out.println(k+" = "+v);
		}
		
		map.remove("java");
		System.out.println("총 엔트리 수 : "+map.size());
		
		map.clear();
		System.out.println("총 엔트리 수 : "+map.size());

- Queue 인터페이스

First In First Out

선입선출

2024.03.07

- Stack 인터페이스

FILO / LIFO : First In Last Out

순서대로 쌓임. 인덱스 없음

push() : 값 넣기

peek() : 위에 값 확인

pop() : 위에 값 꺼내서 제거 -> 완전히 제거됨

	public static void main(String[] args) {
		Stack st = new Stack();
		st.push("1");
		st.push("2");
		st.push("3");
		System.out.println(st.peek());		// 3
		
		st.pop();
		System.out.println(st.peek());		// 2
		
		st.pop();							// 1
		st.pop();							// 에러
		
		st.push("5");
		System.out.println(st.peek());		// 5
	}

스레드 (Thread)

- 프로세스

운영체제 에서 실행중인 하나의 어플리케이션

하나의 어플리케이션은 다중(멀티) 프로세스를 만들기도 함

ex) 크롬 2개 이상 사용

멀티 프로세스들은 운영체제로부터 각자의 메모리를 할당받아 실행함

서로 독립적

- 멀티 태스킹

두 가지 이상의 작업을 처리하는 것

꼭 멀티 프로세스를 의미하는 것은 아님

한 프로세스 내에서 멀티 태스킹을 하도록 만들어진 어플리케이션도 있음

ex) 카톡 - 채팅, 파일 업로드

- 멀티 스레드

하나의 프로세스가 두 가지 이상의 작업을 처리하는 것

- 스레드

한 가지 작업을 실행하기 위해 순차적으로 실행할 코드를 실처럼 이어놓음

하나의 스레드는 하나의 코드 실행의 흐름

하나의 프로세스 내에서 스레드가 2개라면, 2개의 코드 실행이 생긴다는 의미

멀티 프로세스가 어플리케이션 단위의 멀티 태스킹이라면, 멀티 스레드는 어플리케이션 내부의 멀티 태스킹

- 메인 스레드

모든 자바 어플리케이션은 메인 스레드가 메인 메서드를 실행하면서 시작됨

메인 스레드는 메인 메서드의 코드를 순차적으로 실행하고, 메인 메서드의 마지막 코드를 실행하거나, return 문을 만나면 종료됨

필요에 따라 작업 스레드들을 만들어서 병렬로 코드를 실행할 수 있음

멀티 스레드를 생성해서 멀티 태스킹을 수행함

멀티 스레드 어플리케이션에서 실행중인 스레드가 하나라도 있다면, 프로세스는 종료되지 않음

- 작업 스레드 생성과 실행

자바에서는 메인 스레드가 반드시 존재함

메인 이외에 추가적인 병렬 작업의 수 만큼 스레드를 생성하면 됨

객체로 생성되기 때문에 클래스 필요

- java.lang.Thread 클래스를 직접 객체화해서 생성

  • 스레드 객체 생성시 반드시 Runnable 인터페이스 타입의 구현객체를 생성해야함
    Thread t = new Thread(Runnable r);

  • Runnable 은 작업 스레드의 실행 코드를 가짐 -> 객체

  • Runnable 은 run() 메서드를 구현해야 함 -> 실행 코드가 들어갈 부분

  • 작업 스레드가 생성되는 즉시 실행되는 것은 아님
    start() 메서드를 호출해야만 실행됨
    t.start();

class Thread01 implements Runnable {
	@Override
	public void run() {
		for (int i=0; i<5; i++) {
			System.out.println(Thread.currentThread().getName());
		}
		System.out.println("t1 종료");
	}
}

public class Ex {
	public static void main(String[] args) {
		Runnable r = new Thread01();		// 생성자에 들어갈 매개값 객체 생성
		Thread t1 = new Thread(r);			// 스레드 객체를 생성
		
		t1.start();
		
		System.out.println("메인 종료");
	}
}

- Thread 를 상속해서 하위클래스를 만들어서 생성

  • Thread 클래스를 상속 후, run()메서드 재정의(오버라이딩) 해서 스레드가 실행 할 코드를 작성
			  class 작업스레드 extends Thread {
			  	코드들 ..
			  	public void run(){
			  		..
			  		스레드가 실행할 코드
			  		..
			  	}
			  }
  • 일반적인 객체 생성과 방법 동일
    Thread t = new 작업스레드();
    t.start();
    작업 스레드 자신의 run() 메서드를 실행함
class Thread02 extends Thread {		// Thread 상속
	@Override
	public void run() {				// 재정의(오버라이딩) 작업 클래스 실행 코드
		for (int i=0; i<5; i++) {
			System.out.println(getName());	// 상속받고있기 때문에 getName()
		}
		System.out.println("t2 종료");
	}
}
public class Ex {
	public static void main(String[] args) {
		
		Thread t2 = new Thread02();			// 일반적인 객체 생성과 동일
		
		t2.start();
		
		System.out.println("메인 종료");
	}
}

- join()

스레드는 다른 스레드와 독립적으로 실행하는 것이 기본이지만, 다른 스레드가 종료될때까지 기다렸다가 실행해야하는 경우도 있음

  • 클래스
public class SumThread extends Thread {
	private long sum;
	
	public void setSum(long sum) {
		this.sum = sum;
	}
	public long getSum() {
		return sum;
	}
	
	@Override
	public void run() {
		for (int i=1; i<=100; i++) {
			sum += i;
		}
	}
}

  • 메인
	public static void main(String[] args) {
		SumThread st = new SumThread();
		
		st.start();
//		System.out.println("1~100 까지의 합 : "+st.getSum());
//		0출력. 스레드가 종료될때까지 기다려줘야함
		
		try {
			st.join();
		} catch (InterruptedException e) {	// 일반예외. 처리해줘야함
			e.printStackTrace();
		}
		
		System.out.println("1~100 까지의 합 : "+st.getSum());
	}

- sleep()

실행중인 스레드를 일정시간 멈추게 함

class Thread1 extends Thread {

	@Override
	public void run() {
		for (int i=0; i<300; i++) {
			System.out.print("-");
		}
		System.out.println("<<th1 종료>>");
	}
}

class Thread2 extends Thread {
	@Override
	public void run() {
		for (int i=0; i<300; i++) {
			System.out.print("|");
		}
		System.out.println("<<t2 종료>>");
	}
}

public class ThreadEx04 {
	public static void main(String[] args) {
		Thread1 th1 = new Thread1();
		Thread2 th2 = new Thread2();
		
		th1.start();
		
		
		try {
			th1.sleep(3000);		// 1/1000 : 1초
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th2.start();
	}
}

2024.03.08

스트림 (Stream)

- 스트림

자바에서 모든 입출력은 스트림을 통해 이루어짐

자료 흐름이 물의 흐름 같다는 의미에서 사용됨

자바는 입출력 장치와 무관하게 일관성 있게 프로그램을 구현할 수 있도록 일종의 통로인 스트림 제공

모든 입출력은 스트림 클래스로 제공됨

스트림은 단방향이기 때문에 입력, 출력을 동시에 할 수 없음

입력 : FileInputStream, FileReader (InputStream / Reader)

출력 : FileOutputStream, FileWriter (OutputStream / Writer)

- FileInputStream

FileInputStream(File f) : file 을 매개변수로 받음

FileInputStream(String name) : file 이름 name(경로 포함)을 매개변수로 받아서 생성함

import java.io.FileInputStream;

public class FileInputStreamEx {
	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("C:/Test.txt");
			int i = 0;			// 한 글자씩 읽기 때문에
											
			while ((i=fis.read()) != -1) {	// .read() 메서드로
											// 읽어들여 저장한 값이 -1이 아닌 동안 반복
				System.out.println((char)i);	// 아스키 코드값이 나오므로
			}									// 값의 문자형이 char 자료형으로 형변환 해줘야 함
	}
}

- FileOutputStream(File f, boolean append)

append 기본값 : false : 오버라이트, 덮어쓰기

true 이면, 파일의 끝에 이어서 작성

import java.io.FileOutputStream;
import java.util.Scanner;

public class FileOutputStreamEx {
	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("C:/aaa.txt", true);
			Scanner s = new Scanner(System.in);		// in : static. System 을 바로 사용할 수 있도록
			System.out.print("입력 >> ");
			String note =  s.nextLine();		// 라인으로 입력 받음
			byte[] n = note.getBytes();			// 바이트로 변환
			fos.write(n);		// 변수를 넣음
			fos.write(65);		// 아스키 코드를 넣으면 해당되는 값인 char 로 변환됨
			fos.write(66);
			fos.write(67);
			System.out.println("파일 생성 완료");
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

- FIle

File 만들기

import java.io.File;
import java.io.IOException;

public class FileEx01 {
	public static void main(String[] args) {
		File f = new File("C:/abc.txt");
		boolean result = false;
		try {
//			result = f.createNewFile();		// 새 파일 생성
			result = f.delete();			// 파일 삭제
			System.out.println(result);
		} catch (Exception e) {}
	}
}

API 활용

import java.io.File;

public class FileEx02 {
	public static void main(String[] args) {
		File f = new File("C:/Test.txt");
		System.out.println(f.canExecute());		// 실행할 수 있는지 여부
		System.out.println(f.canRead());		// 읽을 수 있는지 여부
		System.out.println(f.canWrite());		// 쓸 수 있는지, 편집 가능 여부
		System.out.println(f.isFile());			// 파일인지 여부
		System.out.println(f.isDirectory());	// 파일이 디렉토리, 폴더 인지 여부
		System.out.println(f.getName());		// 파일명
		System.out.println(f.length());			// 파일 개수. long 타입으로 받음
												// 인트타입이지만, 인트범위를 넘긴 파일이있을 수 있음
	}
}

- DataOutput

Data

  • 기본 자바 데이터 유형을 읽고 쓸 수 있음
import java.io.*;

public class DataOutputEx {
	public static void main(String[] args) {
		try {
		File f = new File("C:/bbb.txt");
		FileOutputStream fos = new FileOutputStream(f);
		DataOutputStream dos = new DataOutputStream(fos);
		dos.writeBoolean(true);
		dos.writeInt(100);
		dos.writeDouble(5.7);
		dos.writeChar(66);
		dos.writeChars("hihi");
		System.out.println("생성 완료");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}		// 문자 깨짐. Input 으로 읽어야 함

- DataInput

import java.io.*;

public class DataInputEx {
	public static void main(String[] args) {
		try {
			File f = new File("C:/bbb.txt");
			FileInputStream fis = new FileInputStream(f);
			DataInputStream dis = new DataInputStream(fis);
			System.out.println(dis.readBoolean());
			System.out.println(dis.readInt());
			System.out.println(dis.readDouble());
			System.out.println(dis.readChar());
			System.out.println(dis.readLine());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}			// 타입 순서에 맞게 읽어야함

2024-03-13

HTMl

- HTML 태그

브라우저에게 HTML 문서임을 알려줌

파일의 확장자는 반드시 .htm 혹은 .html 으로 저장해야 함

시작태그와 종료태그로 HTML 문서는 구성됨

- 태그

< 와 > 로 감싸져있는 것들을 말함

<..> 시작태그와 </..> 종료태그가 있음

엘리먼트(Element) 라는 명령 내용(= 요소)과 어트리뷰트(Attribute) 라는 엘리먼트의 특성 및 특성 값(= 속성)이 들어감

대부분 시작 태그와 종료 태그를 가지고 있는데, 종료 태그가 없는 태그도 있음 : 싱글 태그

- HEAD, BODY

 <head>
 	제목을 포함하는 HTML 문서의 머리(HEAD) 부분임을 알려줌
	모든 HTML 문서에는 HEAD 가 필요함
	일반적으로 HTML에서는 대소문자를 구별하지 않음 < HEAD > 와 < head > 는 같음
  <title>Document</title>
  	문서의 제목(TITLE)을 나타내므로, 전체적인 문서의 내용을 대표할 수 있는 주제어를 담게함
	문서를 표시하는 영역에는 나타나지 않으나, 웹 브라우저 대체적으로 맨 위에 표시됨
	문서를 대표하는 목록, 북마크, 창 등으로 표시되고 검색에서도 사용되기 때문에
	생략하지 않는것이 좋음
 </head>
 <body>
	HTML 문서에서 문서의 본체(BODY)를 말하며, 문서의 내용이 들어가는 주된 부분임
	거의 모든 부분의 명령 엘리먼트(= 요소)들이 이곳에 들어감
 </body>

- BR 태그

	BR 태그		<br />
	01, 02 파일을 실행해 보면	<BR>
	줄바꿈을 했음에도 적용되지 않음		<BR />
	주로 사용하는 줄바꿈 태그는 <br /> 태그임

- 머릿글자(Headings)

각 단계의 제목을 표시하는 태그

 <body>
	<H1>hello HTML</H1>
	<H2>hello HTML</H2>
	<H3>hello HTML</H3>
	<H4>hello HTML</H4>
	<H5>hello HTML</H5>
	<H6>hello HTML</H6>
	6 단계가 있으며 가장 큰 글자는 H1 임

	머릿글자는 일반 문서의 글자에 비하여 크기가 크고 더 굵은 글꼴로 표현되고,
	제목들을 표시하기 위한 것이므로 앞의 문장과 뒤의 문장으로부터 분리하기 위해
	'자동 줄바꿈' 을 함

	머릿글자 시작태그와 종료태그 안에 내용을 넣으면 됨		<br />
	<H1>hello HTML</H1>
 </body>

- FONT 태그

글자의 표현 형태(스타일)를 나타내며 여러가지의 특성 및 특성 값(= 속성)을 가질 수 있는데, 어트리뷰트(Attribute = 속성)라고 함

HTML은 대소문자를 구별하지 않으나, 명령 요소 : ELEMENT는 대문자로, 속성 : Attribute는 소문자로 표현하여 구분하기 쉽도록 함

Font의 기본적인 속성

  • size, color, face 들이 있음

  • size

	<FONT size="4">폰트 크기</FONT>
		1 ~ 7 사이의 정수
		7이 가장 크고, 1이 가장 작음
	<br />
	<FONT size="7">77777</FONT>	<br />
	<FONT size="6">66666</FONT>	<br />
	<FONT size="5">55555</FONT>	<br />
	<FONT size="4">44444</FONT>	<br />
	<FONT size="3">33333</FONT>	<br />
	<FONT size="2">22222</FONT>	<br />
	<FONT size="1">11111</FONT>

  • color
	<FONT color="색상명">폰트색상</FONT>
		색상값은 색상명이나 색상의 번호가 됨
		색상 번호는 빨강(Red), 초록(Green), 파랑(Blue)을 나타내는 rrggbb 색상의 정도를
		16진수로 표시한 것으로, 높은 값이 ff, 낮은 값이 00임
		색상명은 이 번호들을 기억하는 대신 기억하기 좋은 영문으로 미리 정한 값임
		rgb 색상표 검색하여 사용 가능
	<br />

	<FONT color="ff0000">FONT color="ff0000"</FONT>	<br />
	<FONT color="ccccc">FONT color="ccccc"</FONT>	<br />
	<FONT color="gold">FONT color="gold"</FONT>	<br />
	<FONT color="blue">FONT color="blue"</FONT>	<br />

  • face
	<FONT face="궁서">FONT face="궁서" 글자</FONT>	<br />
	<FONT face="바탕">FONT face="바탕" 글자</FONT>	<br />
	<FONT face="Comic Sans MS">FONT face="Comic Sans MS" 글자</FONT>	<br />
	<FONT face="Impact">FONT face="Impact" 글자</FONT>

- 라인모드 (Line mode)

자동적으로 줄바꿈을 하지 않음

	B 태그, STRONG 태그 <br />
	<br />
	<B>문장 중에 굵게 표시되는 내용</B>	<br />
	<STRONG> 강조 표시되는 내용</STRONG>	<br />
	<br />
	I 태그, CITE 태그, DFN 태그, EM태그	<br />
		- 태그 안의 글자를 기울인 이태릭 글자로 표현함
	<br />
	<I>이태릭 글자로 표시되는 내용</I>	<br />
	<CITE>CITE 글자로 표시되는 내용</CITE>	<br />
	<DFN>DFN 글자로 표시되는 내용</DFN>	<br />
	<EM>EM 글자로 표시되는 내용</EM>	<br />
	<br />
	U 태그, INS 태그 <br />
		- 태그 안의 글자를 지운글자로 표시함
	<br />
	<DEL>지운 글자로 표시되는 내용</DEL>	<br />
	<U>밑줄라인 들어간 글자로 표시되는 내용</U>	<br />
	<S>줄 친 글자(Strike)로 표시되는 내용</S>	<br />
	<STRIKE>줄 친 글자로 표시되는 내용</STRIKE>	<br />

- 블럭모드(Block type)

자동적으로 줄바꿈 적용, 여러줄의 문장을 표현하기 위하여 제공됨

	P 태그 <br />
		- 태그 안의 문장을 하나의 문단으로 표시함	<br />
		시작과 끝 (즉, P태그의 위/아래)에 빈 줄을 삽입하면서 자동 줄바꿈을 함
	<br />
	문장 중에서
	<P>하나의 P 문단 표시</P>
	이런식으로 표현됨

	<br />
	<br />
	ADDRESS 태그 <br />
		- 태그 안의 문장을 이태릭 글자로 표현하는 하나의 문단으로 표시함	<br />
		  시작과 끝에 자동 줄바꿈을 하는데 빈 줄을 삽입하지는 않음
	<br />
	문장 중에서
	<ADDRESS>하나의 ADDRESS 문단 표시</ADDRESS>
	이런식으로 표현됨

	<br />
	<br />
	DIV 태그 <br />
		- 태그 안의 문장을 하나의 문단으로 표시함 <br />
		  시작과 끝에 자동 줄바꿈을 하는데 빈 줄을 삽입하지는 않음
	<br />
	문장 중에서
	<DIV> 하나의 DIV 문단 표시</DIV>
	이런식으로 표현됨
	
	<br />
	<br />
	PRE 태그 <br />
		- 태그 안의 문장을 하나의 문단으로 표현함
		  시작과 끝에 자동 줄바꿈을 하는데 빈 줄을 삽입하며,
		  이 태그 안에서는 빈 칸, 줄바꿈, 탭(tab)을 모두 그대로 표현함
	<br />
	문장 중에서
	<PRE>하나의 PRE 문단 표시
	줄바꿈 표현,	탭 표현 함</PRE>
	이런 식으로 표현 됨

- 주석

< !-- -- >

		프로그램 수행과 관련 없이 참고사항을 넣어 참고하기를 원할 때는
		주석(Comment) 표시를 하면 됨
		브라우저를 통하여 직접 볼 수 없으며 아무런 영향을 주지 않음
		원본(Soutce) 파일을 볼 때만 주석을 읽을 수 있음
		프로그램 작성 시 참고사항을 적어두는데 유용함
	<!-- 주석의 내용. 다른 태그들에 영향을 주지 않음 -->

- 특수문자

    <H2>- 특수문자는 일종의 기호 문자이며 두 가지 기능을 위해 사용됨</H2>
       1. &lt;HEAD&gt; 표시하면 < HEAD >로 표현됨	<br />
          왼쪽 꺽쇠( < )는 태그의 시작, 오른쪽 꺽쇠( > )는 태그의 종료를 의미함
          앤드기호( & )는 특수문자의 시작을 알림
          HTML 문서에서 특수한 의미를 지니고 있으므로,
          일반 문서 내부에서는 사용할 수 없음
       2. 일반적으로 문서 편집기에 없는 문자를 표현하기 위해 사용됨	<br />

	<br />
	이중 따옴표( " )는 &quot; 로 사용해도 되고,
	고급의 복잡한 문서에서는 반드시 &quot; 로 사용해야 할 경우도 있음
	<br />
	문자들을 HTML 에서 사용하려면, 그 문자들의 문자기호(escape 기능)를 사용해야 함
	<br />
	&lt; 혹은 &#60; : ( < )의 문자 기호	<br />
	&gt; 혹은 &#62; : ( > )의 문자 기호	<br />
	&nbsp; 혹은 &#160; : ( ) 공간의 문자 기호	<br />
	&copy; 혹은 &#169; : 저작권의 문자 기호	<br />
	<br />
	&int; 혹은 &#8747; : 인테그랄	<br />
	&sum; 혹은 &#8721; : 합계

- CENTER 태그

시작 태그에서부터 서류 내용을 유용한 창의 수평 중앙에 위치시키며, 종료 태그까지 계속됨

	<CENTER><H4>중앙에 위치</H4></CENTER>

- HR 태그

서류의 내용을 구분하거나 장식을 위하여 수평 줄을 넣을 때 사용됨

사용 가능한 속성 size, width, align, color

	size
		- 줄의 높이를 숫자로 지정
		  디폴트 값이 브라우저에 따라 다름
	<br />
	width
		- 줄의 폭을 길이(length)로 표시함
		  이 값은 픽셀이 될 수도 있고,
		  전체 사용 가능한 수평 폭에 대한 백분율이 될 수도 있음
		  디폴트는 100% : 속성을 지정하지 않았을 때 브라우저가 알아서 처리하는 기본 값
	<br />
	align
		- left | center | right
		left : 줄을 왼쪽에 붙여 표현
		center : 줄을 중앙에 붙여 표현
		right : 줄을 오른쪽에 붙여 표현
		디폴트는 align : center
	<br />
	color
		- 줄의 색상을 색상값으로 지정함
		  브라우저마다 적용되고 안되는것들이 있음
	<br />
	<br />
	<HR>
	<HR width=100>
	<HR width=50%>
	<HR width=50% size=4 color=silver>
	<HR width=60% size=1 align=right>
	<HR width=60% size=3 color=aqua align=left>

- BODY bgcolor

< BODY bgcolor="색상값" text="색상값">

  • bgcolor : 서류 전체의 배경 색상을 지정하는데 사용됨
  • text : 서류 전체의 글자 색상을 지정하는데 사용됨
  • BODY 태그에 지정하는 것이므로 한 문서에서 한 번 밖에 지정할 수 없음
 <BODY bgcolor="#ffffdd" text="#00a0a0">
	<CENTER>
		<br />
		<HR size=2 width=60% color=red>
	</CENTER>
 </BODY>

- BODY background

< BODY background="이미지 파일명과 그 경로" >

  • background 는 문서 전체의 배경에 깔리는 이미지 파일을 지정하는데에 사용됨
  • 이미지가 바둑판 형식으로 좌우, 상하 반복하여 적용됨
  • 파일명과 그 파일이 위치한 경로를 같이 표시하여야 하는데, 이것을 URL 이라 함
    URL : 전체 주소
	<BODY background="배경 이미지 경로">
		<CENTER>
			<H2><FONT color=green>BODY에서 background 지정 예제</FONT></H2>
			<HR size=2 width=60% color=blue>
			한개의 이미지	<br />
			<img src="단일 이미지 경로" border=1>
			<HR size=2 width=60% color=blue>
		</CENTER>
	</BODY>

- A 태그

문서나 이미지를 HTML 상에서 불러 표현하기 위해서는 해당 파일을 지정하여야 하는데 이 때, 이 앵커를 사용함

 <body>
	<A name="top"></A>
	<a href="#bottom">맨 아래로</a>
	name	<br />
		- 현재 앵커(A)가 다른 연결과 구별되도록 이름을 지정하는 것
		  부위 지정에 사용됨
	<br />
	href = URL	<br />
		- 웹 안에서 위치를 지정하여 연결시켜줌
	<br />
	<A href="http://naver.com">네이버에 연결</A>
	<H3>부위 지정</H3>
		<A name="bottom"></A>
		<a href="#top">맨 위로</a>	
 </body>

- HTML 문서의 목록

상위와 하위가 있는 체계화된 목록을 만들 때 사용됨

  • 정의 목록(DL : definition List),
  • 번호가 없는 목록(UL : unodered list),
  • 번호가 있는 목록(OL : odered list)

정의 목록

  • DL 태그
    정의 목록의 태그이며,
    그 내용으로 갖는 속성은 정의 목록의 제목(DT), 데이터(DD)가 있음
  • DT 태그
    자동 줄바꿈을 하고, 들여쓰기는 하지 않음
    종료 태그는 선택적임
  • DD 태그
    자동 줄바꿈을 하고, 들여쓰기를 함
    종료 태그는 선택적임
	<DL>
		<DT> 목록 제목1
			<DD>목록 내용 1-1
			<DD>목록 내용 1-2
		<DT> 목록 제목2
			<DD>목록 내용 2-1
			<DD>목록 내용 2-2
	</DL>

번호 없는 목록

  • UL 태그
    번호 없는 목록(UL)의 태그이며,
    그 내용을 갖는 속성은 목록 항목(LI : list item)이 들어감
  • LI 태그
    목록 항목임
    자동 줄바꿈을 하고, 들여쓰기를 함
    종료 태그는 선택적임
  • UL 과 LI 요소에서 사용되는 속성에는 type 이 있음
  • UL에 type이 지정되는 경우 그 하위 LI에는 모두 같은 type이 적용됨
  • 각 값이 어떻게 나타나는지, 디폴트는 어떤지는 브라우저에 따라 다름
	<LI type="disc"> 값이 "disc" 일 때 : 꽉 찬 원으로 표현
	<LI type="circle"> 값이 "circle" 일 때 : 원의 테두리로 표현
	<LI type="square"> 값이 "square" 일 때 : 네모 꼴의 테두리로 표현
	<br />

	<UL type="disc">
		<LI> 번호 없는 목록 항목 1
		<LI> 번호 없는 목록 항목 2
		<LI> 번호 없는 목록 항목 3
		<UL type="circle">
			<LI> 번호 없는 목록 항목 3-1
			<LI> 번호 없는 목록 항목 3-2
		</UL>
		<LI> 번호 없는 목록 항목 4
		<UL type="square">
			<LI> 번호 없는 목록 항목 4-1
			<LI> 번호 없는 목록 항목 4-2
		</UL>
	</UL>

- 번호 있는 목록

  • OL 태그
    번호 있는 목록(OL)의 태그,
    그 내용으로 갖는 요소 LI이 들어있음
  • LI 태그
    목록 항목 임
    자동 줄바꿈을 하고, 들여쓰기를 함
    종료 태그는 선택적임
  • OL과 LI 요소에서 사용되는 속성은 type이 있음
    OL에 type이 지정되는 경우, 그 하위 LI에는 모두 같은 type이 적용됨
    OL의 추가적인 속성 start=""
	<OL type="A">
		<LI> 번호 있는 목록 항목 1
		<LI> 번호 있는 목록 항목 2
		<LI> 번호 있는 목록 항목 3
		<OL type="i">
			<LI> 번호 있는 목록 항목 4
			<LI> 번호 있는 목록 항목 5
		</OL>
		<LI type="1"> 번호 있는 목록 항목 6
		<OL type="a" start="1">
			<LI> 번호 있는 목록 항목7
			<LI> 번호 있는 목록 항목8
		</OL>
	</OL>

- TABLE 태그

표(테이블)를 만들 때 사용

표의 줄(TR), 표의 칸(TD), 표의 제목(TH) 요소를 가짐

TABLE 에서 사용 가능한 속성

  • bgcolor = 색상값 : 표의 바탕색을 색상값으로 지정
  • width = 길이값 : 표의 너비를 픽셀 혹은 백분율로 지정
  • border = 두께값 : 표의 테두리 두께를 지정
  • cellspacing = 간격값 : 칸 사이의 간격을 지정
  • cellpadding = 간격값 : 칸 안에서의 간격을 지정

TR 태그

  • 표의 줄 태그이며, TABLE 태그 내에서 반드시 한 쌍 이상 있어야함

TR 에서 사용할 수 있는 속성

  • bgcolor = 색상값 : 표의 줄 바탕색을 색상값으로 지정
  • width = 길이값 : 표의 줄 폭을 지정
  • height = 길이값 : 표의 줄 높이 지정
  • align = left | center | right : 표의 줄에서 내용의 수평 정렬을 지정
  • valign = top | middle | bottom : 표의 줄에서 내용의 수직 정렬을 지정

브라우저에 따라 표현이 차이가 있음

TH 와 TD 태그

  • 표의 제목(TH) 요소도 표의 칸(TD)의 한 종류이며, 그 속성들도 거의 같음

TD 에서 사용할 수 있는 속성

  • bgcolor = 색상 : 표의 칸 바탕색을 색상값으로 지정
  • background = URL : 표의 칸 배경 이미지를 URL로 지정
  • width = 길이값 : 표의 칸 폭을 지정
  • height = 길이값 : 표의 칸 높이를 지정
  • align = left | center | right : 표의 칸에서 내용의 수평 정렬을 지정. 이 때, 디폴트는 left
  • valign = top | middle | bottom : 표의 칸에서 내용의 수직 정렬을 지정. 디폴트는 middle
  • colspan = 개수 : 표의 칸에서 가로 통합 칸의 개수 지정
  • rowspan = 개수 : 표의 칸에서 세로 통합 칸의 개수 지정

브라우저에 따라 표현이 차이가 있음

- FORM 태그

HTML 문서에서 데이터를 주고 받을 때 FORM을 사용하며, 요소들은 < FORM > < /FORM > 사이에 위치해야 함

이름(name)과 값(value)이 짝을 지어서 이동함

여러개의 FORM이 나올 수 있지만, name은 반드시 각각 달라야 함

요소 이름(name)과 값(value)을 임의로 넣음

  • name = 'java'
  • gender = '여'
  • marry = '미혼'
  • select = 'seleted'

- INPUT 태그

INPUT 안에서 사용 가능한 속성

  • text, password, checkbox, radio, button, hidden 등

type="text"

  • INPUT의 디폴트 type="text"이고, 데이터를 입력하면
    textname의 value="입력 데이터"가 됨
	<FORM>
		단순 텍스트 : <INPUT type="text" name="textname" />
	</FORM>

	<H3>글자수와 크기 지정 가능 ex) size=10 maxlength=20 지정 가능</H3>
	<FORM>
		입력 : <INPUT type="text" name="textname1" value="지정값" size=10 maxlength=20>
	</FORM>

	<H2>type="password"</H2>
	<FORM>
		비밀번호 : <INPUT type="password" name="pw" />
	</FORM>

	<H2>type="checkbox" 와 type="radio"</H2>
	<H3>최초값을 지정. checked</H3>
	<H3>라디오 버튼에서는 한 가지만 선택 가능,
		선택 항목들의 name은 모두 같게 하고,
		value는 각각 달라야 함</H3>

	<FORM>
		현재 살고있는 곳은?	<br />
		<INPUT type="radio" name="living" value="Seoul" checked /> 서울	<br />
		<INPUT type="radio" name="living" value="Busan" /> 부산	<br />
		<INPUT type="radio" name="living" value="Daegu" /> 대구	<br />

		결혼 하였는가? <br />
		<INPUT type="radio" name="marry" value="Y" /> 기혼	<br />
		<INPUT type="radio" name="marry" value="N" /> 미혼	<br />

		가보고 싶은 곳은?	<br />
		<INPUT type="checkbox" name="city1" value="Y" /> 로마	<br />
		<INPUT type="checkbox" name="city2" value="Y" checked /> 파리	<br />
		<INPUT type="checkbox" name="city3" value="Y" /> 런던	<br />
		<INPUT type="checkbox" name="city4" value="Y" /> 뉴욕	<br />
	</FORM>
	<H3> 서울, 미혼, 파리만 선택했을 경우 이동되는 값</H3>
	living=seoul , marry=N , city2=Y

- BUTTON 태그

  • type="button"은 입력 버튼을 만들고,
    type=image는 이미지를 연결하여 이벤트를 작동하게 함

BUTTON 에서 사용할 수 있는 속성

  • name, value, type(submit, reset)
	<FORM>
		기본 버튼 : <INPUT type="button" name="buttonname" value="확인" />	<br />
		이미지 : <INPUT type="image" name="imagename"
				 src="이미지 주소" onClick="alert('클릭하면 나오는 팝업 내용')" />
	</FORM>

- 선택 SELECT, OPTION 태그

SELECT, OPTION에서 사용할 수 있는 속성

  • name, size : 한 번에 보이는 항목 개수
	<FORM>
		통신사 선택
		<SELECT>
			<OPTION selected value="KT">KT</OPTION>
			<OPTION value="U+">U+</OPTION>
			<OPTION value="SKT">SKT</OPTION>
			<OPTION value="알뜰폰">알뜰폰</OPTION>
		</SELECT>	<br />
		<INPUT type="submit" value="선택완료"	/>	<br />
	</FORM>
    <br />
	<FORM>
		선택 사항	<br />
		<SELECT size="4">
			<OPTION>선택 항목 1</OPTION>
			<OPTION>선택 항목 2</OPTION>
			<OPTION selected>선택 항목 3</OPTION>
			<OPTION>선택 항목 4</OPTION>
			<OPTION>선택 항목 5</OPTION>
			<OPTION>선택 항목 6</OPTION>
			<OPTION>선택 항목 7</OPTION>
		</SELECT>	<br />
		<INPUT type="submit" value="선택완료"	/>
		<INPUT type="reset" value="다시선택"	/>
	</FORM>

- TEXTAREA 태그

TEXTAREA 에서 사용할 수 있는 속성

  • name, rows, cols
	<FORM>
		입력하세요. <br />
		<TEXTAREA name="text" rows="5" cols="50"></TEXTAREA>
		<INPUT type="submit" value="입력완료"	/>
		<INPUT type="reset" value="다시입력"	/>
	</FORM>

2024-03-14

CSS

- Cascading Style Sheet(캐스케이딩 스타일 시트)

웹 문서의 내용과 상관없이 디자인만 바꿀 수 있음

HTML 로는 웹 문서/사이트의 내용을 나열하고, CSS 로는 웹 문서의 디자인을 구성함

내용과 디자인이 구분되어 있으면 사이트의 내용을 수정할 때
디자인에 전혀 영향을 미치지 않아 편리하고,
반대로 디자인을 수정할 때도 내용을 건드리지 않고 수정 가능함

CSS 소스에서 한 줄이 하나의 스타일에 해당하고, 줄 마다 형태가 비슷함

		기본형	선택자 { 속성1:속성값1; 속성2:속성값2; }
  • 선택자 : 스타일을 어느 태그에 적용할 것인지 알려줌
  • {} 중괄호 사이에는 스타일 정보를 넣음
  • 스타일 규칙 : 속성과 값이 하나의 쌍으로 이루어짐
  • ; (세미콜론)으로 구분해서 스타일 규칙을 여러개 지정할 수 있음

P태그에 스타일 적용

  • 선택자 P 태그, 텍스트정렬 속성:값; 글자색 속성:값;
	P { text-align:center; color:blue; } 

	P {
		text-align:center;	/* 텍스트 정렬 - 중앙 */
		color:blue;			/* 글자색 - 파랑 */
	}

	: 두가지 모양 모두 같은 것임
      두 번째 것이 가독성이 좋고, 주석을 쓸 수도 있음
	  /* 주석 */
      
	<P>안녕하세요</P>

Style Sheet

  • 웹 문서 안에서는 스타일 규칙을 여러개 사용하는데,
    이런 스타일 규칙을 한 눈에 확인하고
    필요할 때마다 수정하기 쉽도록 한 군데에 묶어놓은 것을 말함

- 인라인 스타일

태그 안에 style 속성을 사용하여 해당 태그만 스타일을 적용함

<body>
  <h1>레드향</h1>
  <p style="color:blue;">껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
  <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
  <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
</body>

- 내부 스타일 시트

웹 문서 안에서 사용할 스타일을 같은 문서 안에 정리한 것을 말함

스타일 정보는 웹 문서를 브라우저 화면에 표시하기 전에 결정해야하므로, 모든 스타일 정보는 < HEAD > 태그 안에 정의하고, < style > 과 < /style > 태그 사이에 작성함

  <style>
    h1 {      
      padding:10px;
      background-color:#222;
      color:#fff;
    }
  </style>
<body>
  <h1>레드향</h1>
  <p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
  <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
  <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
</body>

- 외부 스타일 시트

웹 사이트를 만들때 하나의 웹 문서로 끝나는 경우는 거의 없음

대부분 디자인에 일관성 있도록 같은 스타일을 여러 웹 문서에 사용함

그 때마다 웹 문서를 똑같은 내부 스타일 시트로 만들면 서버/시간 이 낭비됨

별도의 파일로 저장해 놓고 필요할 때마다 가져와서 사용함

  • .css 라는 확장자를 사용함
  • < style > 태그를 사용하지 않음
  • 이렇게 만든 외부스타일시트는 웹 문서에 연결해야 스타일이 문서에 적용됨
  • 연결할 때 사용하는 태그는 < link > 태그
	<!-- 외부 style.css -->
    
h1 {      
  padding:10px;
  background-color:#222;
  color:#fff;
}

<head>
  <title>상품 소개 페이지</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <h1>레드향</h1>
  <p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
  <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
  <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
</body>

- 전체 선택자(*)

문서의 모든 요소에 적용할 때 사용

주로 모든 하위 요소에 스타일을 한꺼번에 적용할 때 사용함

전체 선택자는 *(별표)를 사용함

	기본형
	* {속성:값; ..}

  <style>
    * {
      margin:80;
      padding:50;
    }
  </style>
<body>
  <img src="images/cat.jpg">
</body>

- 타입 선택자

특정 태그에 사용한 모든 요소에 스타일을 적용함

타입 선택자를 이용해 스타일을 지정하면, 해당 태그를 사용한 모든 요소에 적용됨

	기본형
	태그명 {스타일 규칙(속성:값;)}

  <style>
    p {
      font-style: italic;
    }
  </style>
<body>
  <div>
    <h1>레드향</h1>
    <p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
    <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
    <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
  </div>
</body>

- 클래스 선택자

같은 태그라도 일부는 다른 스타일을 적용하고 싶다면, 특정 부분만 선택해서 스타일을 적용하려면 클래스 선택자(class selector)를 사용함

클래스 선택자는 클래스 이름을 사용해서 다른 선택자와 구별하는데 이 때, 클래스 이름 앞에 마침표(.)를 반드시 붙여야함

클래스명은 임의로 지정함

	기본형
	.클래스명 {스타일 규칙}
	클래스 스타일은 여러곳에 적용 가능함
	또한, 요소 하나에 클래스 스타일을 2개 이상 적용할 수도 있음
	: 공백으로 구분해서 스타일 이름을 적으면 됨

  <style>
    p {
      font-style: italic;  /* 이탤릭체 */
    }
    .accent {
      border:1px solid #000;  /* 테두리 */
      padding:5px;  /* 테두리와 내용 사이의 여백 */
    }
    .bg {
      background-color:#ddd;    /* 배경색 */  
    }
  </style>
<body>
  <div>
    <h1 class="accent bg">레드향</h1>
    <p>껍질에 붉은 빛이 돌아 <span class="accent">레드향</span>이라 불린다.</p>
    <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
    <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
  </div>
</body>

- id 선택자

id 선택자(id selector)도 클래스 선택자와 마찬가지로 웹 문서의 특정 부분을 선택해서 스타일을 지정할 때 사용함

# 기호를 사용한다는 점만 제외하면 스타일을 정의하는 방법은 클래스 선택자와 동일함

웹 요소에 적용할 때, id="아이디명" 처럼 사용

	기본형
	#아이디명 {스타일 규칙}
  • 클래스 선택자는 문서에서 여러번 적용할 수 있는 반면,
    id 선택자는 문서에서 한 번만 적용할 수 있음
  • 주로 문서의 레이아웃과 관련된 스타일을 지정하거나,
    웹 요소에 자바스크립트를 사용하면서 요소를 구별할 때 사용함
  <style>
    #container {
      width:500px;  /* 너비 */
      margin:10px auto;  /* 바깥 여백 */
      padding:10px;  /* 테두리와 내용 사이 여백 */ 
      border:1px solid #000;  /* 테두리 */
    }    
  </style>
<body>
  <div id="container">
    <h1>레드향</h1>
    <p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
    <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
    <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
  </div>
</body>

- 그룹 선택자(,)

같은 스타일 규칙을 사용하는 요소들을 묶어줌

여러 선택자에서 같은 스타일 규칙을 사용하는 경우, 쉼표(,)로 구분해 여러 선택자를 나열한 후, 스타일 규칙을 한 번만 정의함

	기본형
	선택자1, 선택자2 {스타일 규칙}

  <style>
    /* 
    h1 {
      text-align:center;
    }      
    p {
      text-align:center;
    } 
    */
    h1, p {
      text-align: center;
    }
  </style>
<body>
  <div>
    <h1>레드향</h1>
    <p>껍질에 붉은 빛이 돌아 <span class="accent">레드향</span>이라 불린다.</p>
    <p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
    <p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
  </div>
</body>

- 캐스케이딩

CSS에서 C는 캐스케이딩의 줄임말

스타일 시트에서는 우선순위가 위에서 아래 즉, 계단식으로 적용된다는 의미로 사용됨

CSS는 우선순위가 있는 스타일 시트라고 해석됨

캐스케이딩 : 우선순위들 끼리의 충돌을 막아주는 중요한 개념

2가지 방법이 있음

스타일 우선순위

스타일 규칙의 중요도와 적용범위에 따라 우선순위가 결정됨

    1. 사용자 스타일
    1. 제작자 스타일
    1. 브라우저 기본 스타일

스타일 상속

  • 태그의 포함관계에 따라 부모 요소의 스타일을 자식 요소로, 위에서 아래로 전달함

적용 범위

  • 중요도가 같은 스타일이라면, 스타일 적용 범위에 따라 우선순위를 정함
  • 스타일의 적용 범위가 좁을수록 즉, 필요한 요소에만 적용할 스타일일수록 우선순위가 높음
    단, 스타일 규칙에 !important를 붙이면 가장 우선순위가 높음

!important

  • 어떤 스타일보다 우선 적용하는 스타일

인라인 스타일

  • 태그 안에 style 속성을 사용하여 해당 태그만 스타일을 적용함

id 스타일

  • 지정한 부분에만 적용되는 스타일이지만,
    한 문서에 한 번만 적용할 수 있음 (# 기호 사용)

클래스 스타일

  • 웹 문서에서 지정한 부분에만 적용되는 스타일
    한 문서에 여러번 적용할 수 있음 (마침표. 사용)

타입 스타일

  • 웹 문서에 사용한 특정 태그에 스타일을 똑같이 적용함
  <style>
    p {
      color:black;
    } 
    h1 {
      color: brown !important;
    } 
    p {
      color:blue;
    }
  </style>
<body>
  <h1 style="color:green">레드향</h1>
  <p style="color:red;">껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
  <p>레드향은 한라봉과 귤을 교배한 것으로</p>
  <p>일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>    
</body>

2024-03-15

- 자바스크립트(JAVA SCRIPT)

HTML - 웹 문서의 내용을 구성.

CSS - 웹 문서의 레이아웃이나 색상, 디자인 구성.

웹 문서의 각 요소를 각져와서 필요에 따라 스타일을 변경하거나 움직이게 할 수 있음.

웹 사이트의 UI(User Interface) 부분에 많이 사용됨.

  • 메뉴, 텍스트, 팝업창 등 사용자가 사이트를 편리하게 둘러볼 수 있도록 만드는 모든 디자인적 요소.

- 자바스크립트의 쓰임

웹 어플리케이션을 만들 때 다양하게 제공되는 API(Application Programing Interface : 미리 약속해 놓은 규칙 )

많이 사용하는 카카오 지도 API

다양한 라이브러리

  • 리액트, 앵귤러, 뷰 같은 프레임워크도 있고
    제이쿼리 라이브러리도 있다.
    다양한 라이브러리 -> 웹 개발 도움
  • 서버 개발
    노드.js : 프론트엔드에서 사용하던 자바스크립트를 백엔드 개발에 사용할 수 있도록 만든 프레임워크

HTML과 CSS와 달리
자바스크립트는 대소문자 구별한다.
변수 이름이나 함수를 지정할 때에는 대소문자를 정확하게 구별해야 한다.

- 자바 스크립트 작성

웹 문서 안에< script >< /script > 태그 사용

  • 자바스크립트 소스코드가 짧을 경우
    웹문서에서 자바스크립트를 실행할 위치에 바로 코드 작성하여 사용함
  • 웹 문서안에 위치는 어디든 가능.
  • < script > 태그는 하나의 문서에 여러개 사용 가능.

외부 스크립트 파일로 연결

  • CSS와 마찬가지로 자바스크립트 소스도 따로 파일로 저장한 후
    웹 문서에 연결해서 사용 가능하다.

  • 웹 문서 안에서는 직접 자바 스크립트 소스가 드러나지 않고
    HTML 태그와 CSS 만 유지할 수 있어서 코드가 깔끔하다.

  • 외부 자바스크립트 파일은 < script > 태그 없이 소스만 작성하고,
    확장자 *.js 파일로 저장한다.
    src 속성을 이용하여 자바스크립트 파일을 연결한다.

		기본형	<script src="외부 스크립트 파일 경로" ></script>

 <head>
  <title>Document 03</title>
  <style>
	body { text-align:center; }
	#heading { color:blue; }
	#text { 
		color:gray;
		font-size:15px;
	}
  </style>
 </head>
 <body>
	<h1 id="heading">자바스크립트</h1>
	<p id="text">위 텍스트를 클릭해 보세요.</p>

	<script>
		var heading = document.querySelector('#heading');
		heading.onclick = function(){
			heading.style.color = "red";
		}
	</script>
 </body>

외부 js

		var heading = document.querySelector('#heading');
		heading.onclick = function() {
			heading.style.color = "red";
		}

  • 메인
 <head>
  <title>Document</title>
	<style>
		body {text-align:center;}
		#heading {color:blue;}
		#text {
			color:gray:
			font-size:15px;
		}
	</style>
 </head>
 <body>
 	<h1 id="heading">자바 스크립트</h1>
	<p id="text">위 텍스트를 클릭해 보세요.</p>
	
	<script src="경로 파일이름.js"></script>
 </body>

- 자바스크립트 식(expresion)과 문(statement)

식은 표현식이라고도 하는데, 연산식 뿐만 아니라 실제 값도, 함수를 실행하는 것도 식이 됨

즉, 어떤 값을 만들어 낼 수 있다면 모두 식이 될 수 있으며 변수에 저장됨

문은 명령이라고 생각 할 수 있음

문의 끝에는 세미콜론(;)을 붙여서 구분함

조건문이나 제어문 등을 예로 들 수 있음

- 간단한 입출력 방법

알림창 출력

  • 알림창(alert)은 가장 많이 사용하는 간단한 대화 상자다.
  • 웹 브라우저에서는 작은 알림창을 열어 메시지 표시.
	기본형	alert(메시지)
			: 괄호 안에 따옴표( "" 또는 '' )와 함께 메시지를 넣어주면 된다.

 <body>
	<script>
		alert("안녕하세요.");
	</script>
 </body>

확인창 출력

  • 알림창은 단순히 메시지를 보여주는 기능만 했다면,
    확인창(confirm)은 버튼이 2개 있음
  • 사용자가 [확인]이나 [취소] 버튼 중에 직접 클릭할 수 있고,
    어떤 버튼을 눌렀는지 결과를 변수에 저장한 후
    그 값에 따라 선택한 결과에 맞게 프로그램이 동작함
	기본형	confirm(메시지)

 <body>
	<script>
		var reply = confirm("창을 닫으시겠습니까?");
	</script>
 </body>

프롬프트 창에서 입력

  • 프롬프트(prompt) 창은 텍스트 부분이 있는 작은 창임
  • 텍스트 안에 간단한 메시지를 입력할 수 있으며
    그 내용을 가져와 프로그램에서 사용할 수 있음
	기본형	prompt(메시지) 또는 prompt( 메시지,기본값 )
			: 기본값을 지정하거나 지정하지 않을 수 있다.
			기본값을 지정하면 텍스트 부분 안에 기본값이 표시된다.
			기본값을 지정하지 않으면 빈 텍스트 부분으로 표시된다.

 <body>
	<script>
		var neme = prompt("이름을 입력해주세요" , "김자바");
	</script>
 </body>

웹 브라우저 화면에 출력을 담당하는 document.write() 문

  • 자바 스크립트의 실행 결과는 텍스트나 이미지로 출력하거나,
    따로 지정한 영역에 내용을 표시하는 경우가 많음
  • 웹 문서(document)에서 괄호 안의 내용을 표시(write)하는 명령문 정로도 이해하면 좋음
  • 괄호 안에 넣는 값을 변수에 저장할 수 있음
  • 괄호 안에 따옴표("" 또는 '') 사이에 입력한 내용은 브라우저에 그대로 표시됨
  • 따옴표 안에 HTML 태그 사용 가능함
	기본형	document.write()

 <body>
	<script>
		document.write("<h1>어서오세요.</h1>");
	</script>
 </body>

콘솔창에 출력하는 console.log() 문

  • 괄호 안의 내용을 콘솔창에 표시함
  • 콘솔창은 웹 브라우저의 개발자 도구 창에 포함되어 있는 공간임
  • 코드의 오류를 발견할 수도 있고 변수 값을 확인할 수도 있음
  • 괄호 안에 변수가 들어갈 수도 있고, 따옴표 사이에 표시할 텍스트를 넣을 수도 있음
  • 이때, 따옴표 안에 HTML 태그는 사용할 수 없음
 <body>
 	<script>
		var name = prompt("이름을 입력해주세요.");
		console.log(name + "님, 환영합니다.");
	</script>
 </body>
<!--
	웹 브라우저 화면에는 아무런 변화 없다.
	ctrl + shift + J 를 눌러 콜솔창을 열 수 있다.
	실행 결과가 콘솔창에 표시된다.
-->

- onChange

<form>
	<input type="text" onChange="alert(this.value)" value=""/>	<br />
	<input type="text" onChange="alert(this.value)" 
		value="Hello Javascript" />	<br />
	<input type="text" onChange="alert(this.value)" value="안녕하세요."		/>
</form>

- onClick

<form>
	<input type="button" value="클릭해 보세요." onClick="alert('안녕하세요!')"	/>
</form>

2024-03-18

자바스크립트

- 자바스크립트 코딩 규칙

스타일 가이드, 코딩 컨벤션(관습이나 규칙), 코딩 스타일, 표준 스타일

웹 문서에 동적인 효과를 주기 위해 출발한 언어

다른 프로그래밍 언어에 비해 타입(type)이 유연해서 작성자가 주의를 기울이지 않으면 오류가 발생

스타일 가이드에 맞게 작성하면 코드의 오류도 줄이고 일관성이 생겨 가독성이 좋아짐

웹 사이트나 애플리케이션의 유지보수 시간, 비용 절감 효과

- 소스 작성 규칙

들여쓰기

세미콜론으로 문장을 구분함

공백을 넣어서 읽기 쉽게 작성

코드 설명 주석(comment) 작성

		// 한줄주석
		/* 여러줄 주석 */

식별자

  • identifier : 개발자가 변수, 함수, 속성 등을 구별하려고 붙인 특정 단어

첫 글자 반드시 소문자, _, $ 만 사용 가능

두 단어의 연결 -, _, 뒷단어 첫글자 대문자

예약어

  • keyword - 자바스크립트에서 미리 정해놓은 단어
  • 예) var 변수 선언시 사용 예약어 -> 식별자로 사용 불가

- 자바스크립트 기본 문법

변수(variable)

  • 프로그램 실행동안 값이 여러 번 달라질 수 있는 데이터
  • 상수(constant) : 값을 한 번 지정하면 바뀌지 않는 데이터

변수 선언

  • 변수를 구별할 수 있도록 이름 붙이는 것을 의미
  • 이름을 불러서 값을 사용함
  • 바뀐 값을 다시 변수에 저장할 수 있음
    반드시 사용할 변수의 이름은 서로 달라야 함

변수 명명 규칙

  • 첫글자 대소문자, _, $
  • 예약어 사용 불가
  • 의미 유추 가능하게
		// 기본형 var 변수명;
		var currentYear;	<!-- 올해 연도 -->
		var birthYear;		<!-- 태어난 연도 -->
		var age;			<!-- 나이 -->

	* 값 저장/할당
		= 기호 사용
	예)
		var currentYear;
		currentYear = 2024;

		var currentYear = 2024;
		var birthYear = 1984;
		var age = 10;

- 자료형

컴퓨터가 처리할 수 있는 자료의 형태

기본 유형

  • 숫자(number) : 정수 / 실수 - 소수점 유무
  • 자바와는 다르게 정수 실수 명확하게 구분되지 않음
  • 실수 계산이 정확하지 않음
  • 문자(String) : 따옴표("" 또는 '')
  • 논리형(boolean) : true / false 소문자로 사용

복합 유형

  • 배열(array) : 하나의 변수에 여러개의 값 저장
  • 배열명[값1, 값2, 값3, ..]
  • [] -> 빈 배열 선언
  • 방 번호가 index이며, 0부터 시작
  • 객체 : 함수와 속성 포함

특수 유형

  • undefined : 변수에 할당된 값이 없을 때
  • 선언만 한 상태
  • null : 변수에 할당된 값이 유효하지 않을 때의 상태

- 연산자

산술 연산자

  • +, -, *, /, %, ++, --
  • 나누기( / ) : 결과값은 나눈 값 자체
  • 나머지( % ) : 결과값은 나눈 후 남은 나머지 값
  • ++, -- : 선/후증감 주의

할당 연산자

  • =, +=, -=, *=, /=, %=

연결 연산자

비교 연산자

  • 2개의 값을 비교하여 참/거짓 으로 결과값 반환(리턴)
  • ==, !=, <, >, <=, >=, ===, !==
  • == : 3 == "3" : true
  • === : 3 === "3" : false (자료형까지 같아야 true)
  • != : 3 != "3" : flase
  • !== : 3 !== "3" : true (값이 다르거나 자료형이 다른 경우)
  • 대소문자 비교
    아스키값으로 비교함

논리 연산자

  • ||, &&, !
  • or : 하나만 true
  • and : 모두 true
  • not : 반대값

- 조건문

if문

  • if (조건) {
    조건이 true 일 때, 실행할 명령
    }
 <body>
	<script>
		var userNumber = prompt("숫자를 입력하세요.");
<!--
		입력창 prompt();
			- 아무것도 쓰지 않더라도 입력을 누르면 값이 넘어감
			- 공백도 값임
			-, 기본값 null 아님
			- 취소를 눌러야만 null
-->

		// prompt(); 입력창 - 기본값이 null 아님
		//					  따라서, 취소를 눌러야 null

		if (userNumber % 3 === 0){
			alert("3의 배수 입니다.");
		}else {
			alert("3의 배수가 아닙니다.");		// 입력값이 null
		}
	</script>
 </body>

else문

  • if (조건) {
    조건이 true 일 때 실행할 명령
    }else {
    조건이 false 일 때 실행할 명령
    }

  • 주로 함께 쓰이는 연산자
    삼항 연산자(조건 연산자) - 조건 ? true일때 : flase일때;
    논리 연산자

 <body>
	<script>
		var userNumber = prompt("숫자를 입력하세요.");

		if (userNumber !== null){
			(userNumber % 3 === 0) ? alert("3의 배수 입니다.") : alert("3의 배수가 아닙니다.");
		}else {
			alert("입력이 취소되었습니다.");
		}
	</script>
 </body>

switch문

  • 값만 사용하고 식은 사용할 수 없음
  • switch (조건) {
    case 값1: 명령1
    break;
    case 값2: 명령2
    break;
    ..
    ..
    default: 명령n
    // break; 사용 안함
    }
 <body>
	<script>
		var session = prompt("관심 세션을 선택해주세요. 1-마케팅, 2-개발, 3-디자인");
		
		switch (session){
			case "1": document.write("<p>마케팅 세션은 <strong>201호</strong>에서 진행됩니다.</p>");
				break;
			case "2": document.write("<p>개발 세션은 <strong>202호</strong>에서 진행됩니다.</p>");
				break;
			case "3": document.write("<p>디자인 세션은 <strong>203호</strong>에서 진행됩니다.</p>");
				break;
			default: alert("잘못 입력하셨습니다.");
		}
	</script>
 </body>

- 자바스크립트 활용 구구단

  • for문 사용
 <body>
 	<h1>구구단</h1>
	<script>
		var i, j;		// 한 번에 선언 가능
		for (i=2; i<10; i++){
			document.write("<h2>"+i+" 단</h2>");
			for (j=1; j<=9; j++){
				document.write(i+" x "+j+" = "+(i * j)+"<br />");
			}
		}
	</script>
 </body>

  • break문 사용
 <body>
	<script>
		var i, j;
		
		for (i=2; i<10; i++){
			document.write("<h2>"+i+"단</h2>")
			for (j=1; j<=9; j++){
				document.write(i+" x "+j+" = "+(i*j)+"<br />");
			}
			if (i === 3){
				break;
			}
		}
	</script>
 </body>

- while문 사용 팩토리얼

 <body>
 	<h1>while문을 사용한 팩토리얼 계산</h1>
	<script>
		var n = prompt("숫자를 입력하세요.", "20");
		var msg = "";

		if (n !== null){
			var nFact = 1;				// 곱을 담을(팩토리얼) 변수
			var i = 1;					// 반복할 카운터 변수

			while (i <= n){
				nFact *= i;
				i++;
			}
			msg = n+"! = "+nFact;		// 결과값을 표시할 문자열
		}else {							// 취소 눌렀을 경우 = null
			msg = "값을 입력하지 않았습니다.";
		}
		document.write(msg);			// 결과 출력
	</script>
 </body>

- continue문 활용 짝수 합

 <body>
	<h1>짝수의 합</h1>
	<script>
		var sum = 0;
		var n = prompt("숫자를 입력해주세요.");
		for (var i=1; i<n; i++){
			if (i%2 === 1){
				continue;
			}
			sum += i;
		}
		document.write("<h3>1부터 "+n+"까지 짝수의 합 = "+sum+"</h3>");
	</script>
 </body>

- 반복문-조건문 활용한 좌석 구하기

 <body>
  	<script>
		var memNum = prompt("입장객이 몇명인가요?");			// 전체 입장객 수
		var colNum = prompt("한 줄에 몇 명씩 앉을건가요?");		// 한 줄에 앉을 인원 수
		var seatNo;

		if (memNum % colNum === 0){
			rowNum = parseInt(memNum / colNum);
		}else {
			rowNum = parseInt(memNum / colNum)+1;
		}

		for (i=0; i<rowNum; i++){
			for (j=1; j<=colNum; j++){
				seatNo = (i * colNum) + j;		// 좌석 번호
				if (seatNo > memNum){
					break;
				}
				document.write("좌석"+seatNo+" ")
			}
			document.write("<br />");
		}
	</script>
 </body>

- 함수

동작해야 할 목적대로 묶은 명령

각 명령의 시작, 끝 명확하게 구별

이 묶은 기능에 이름을 붙여 어디서든 이름으로 명령 실행 가능

- 내장 함수

자바스크립트 내에 미리 만들어놓은 함수

- 함수 선언

  • function 함수명(){
    명령
    }

 <body>
	<script>
		function addNumber(){
			var num1 = 2;
			var num2 = 3;
			var sum = num1+num2;
			alert("결과값 : "+sum);
		}
		addNumber();
		addNumber();
	</script>
 </body>

- 함수 호출

  • 함수명(); 또는 함수명(변수);

- 변수의 범위

스코프(scope) : 변수가 적용되는 범위

스크립트 소스 전체에서 사용할 수 있는 변수

  • 전역변수 또는 글로벌변수

한 함수 안에서만 사용할 수 있는 변수

  • 지역변수 또는 로컬변수
 <body>
	<script>
		function addNumber(){
			var sum	= 10+20;		// 지역변수
			multi	= 10*20;		// 전역변수
		}
		addNumber();
		console.log(multi);
	</script>
 </body>

  • 변수의 재선언과 재할당
 <body>
	<script>
		function addNumber(num1, num2){
			return num1+num2;
		}
		var sum = addNumber(10, 20);	// sum변수 선언, 함수 호출
		console.log(sum);

		sum = 50;						// 변수 재할당
		console.log(sum);

		var sum = 100;					// 변수 재선언	// 자바 에서는 불가능
		console.log(sum);
	</script>
 </body>

2024-03-19

- 변수 예약어

var 예약어를 빠뜨리면 의도치않게 전역 변수가 될 수 있음

변수를 재선언하거나, 재할당 하는 경우도 발생함

- var - let/const

가장 큰 차이는 스코프(변수가 가지는 범위)임

var : 함수 영역 전체 범위

let, const : 지정된 블럭{} 영역의 범위

- 전역 변수

변수 이름만 쓰고 값을 할당함

- let

블럭{} 에서만 유효하고, 벗어나면 사용 못함

재할당 가능 / 재선언 불가능

그러나, 예약어 var와 같이 실수로 같은 변수의 이름을 사용할 걱정이 없음
같은 변수의 이름을 중복 사용 못함

 <body>
	<script>
		function calcSum(n){
			let sum = 0;
			for (let i=1; i<n; i++){
				sum += i;
			}
			sum = 100;			// let 변수 재할당 - 가능
			console.log(sum);

//			let sum = 100;		// let 변수 재선언 - 오류
//			console.log(sum);
		}
		calcSum(10);
	</script>
 </body>

- const

블럭{} 에서만 유효하고, 벗어나면 사용 못함

상수 변수(constant variable)

상수 : 프로그램 안에서 변하지 않는 값을 뜻함

즉, 변하지 않는 값을 변수로 선언할 때 사용함

재할당 / 재선언 불가능

 <body>
	<script>
		const currentYear = 2024;
		console.log(currentYear);
//		const currentYear;			// 재선언 불가 - 오류
//		currentYear = 2023;			// 재할당 불가 - 오류
		console.log(currentYear);
	</script>
 </body>

- 매개변수(parameter)

함수를 선언할 때, 외부에서 값을 받아줄 변수를 미리 만들어야 함

함수를 호출할 때, 괄호안에 매개변수의 이름을 넣음

일반적인 변수의 명명 규칙과 같음

선언된 지역 안에서만 사용 가능

매개변수가 여러 개 사용될 때, 콤마(,)를 찍어 나열

 <body>
	<script>
		function multiple(a, b=5, c=10){	// b=5, c=10 으로 기본값 지정
			return a*b+c;
		}
		var result1 = multiple(5, 5, 10);					// 함수 호출할 때, a=5, b와 c는 넘겨받은 기본값
		document.write("multiple(5, 5, 10)을 실행하면 "+result1+" 입니다.	<br />");

		var result2 = multiple(10, 20);
		document.write(
			"multiple(10, 20)을 실행하면 세 번째 매개변수는 기본값을 사용하고, 결과는 "
			+result2+
			" 입니다.	<br />"
		);

		var result3 = multiple(30);
		document.write(
			"multiple(30)을 실행하면 두 번째, 세 번째 매개변수는 기본값을 사용하고, 결과는 "
			+result3+
			" 입니다.	<br />"
		);
	</script>
 </body>

- 인수(argument)

매개변수가 있는 함수를 호출할 때, 괄호 ()안에 넣어주는 실제 값

 <body>
	<script>
		function addNumber (num1, num2){
			var sum = num1 + num2;
			return sum;					// 반환하는 값 지정
		}
		var result = addNumber(2, 3);	// 결과를 받을 변수 선언 = 함수 호출(2, 3 : 인수);
		document.write("두 수를 더한 값 : "+result);
	</script>
 </body>

- 익명 함수

이름 없음

함수 자체가 식임

  • 변수 이름으로 함수 실행
  • 예) sum 변수 이름 사용
 <body>
	<script>
		var sum = 
		function (a, b){
			return a+b;
		}
		document.write("함수 실행 결과 : "+sum(10, 20));
	</script>

- 즉시 실행 함수

한 번만 실행할 함수

함수를 식 형태로 선언하므로, 마지막에 세미콜론(;)을 붙임

  • (function(매개변수){
    명령
    }(인수));
 <body>
	<script>
		(function(a, b){		// 함수 선언을 위한 매개변수
			sum = a+b;			// 명령
		}(100, 200));			// 함수 실행을 위한 인수
		document.write("함수 실행 결과 : "+sum);
	</script>
 </body>

- 이벤트

웹 브라우저나 사용자가 행하는 어떤 동작

  • 마우스 이벤트
    마우스를 이용해서 버튼이나 휠 버튼을 조작할 때 발생
<body>
	<ul>
		<li><a href="#" onclick="alert('Green 버튼을 클릭했습니다.')">Green</a></li>
		<li><a href="#" onclick="alert('Orange 버튼을 클릭했습니다.')">Orange</a></li>
		<li><a href="#" onclick="alert('Purple 버튼을 클릭했습니다.')">Purple</a></li>
	</ul>
 </body>
  • 키보드 이벤트
    키보드에서 특정 키를 조작할 때 발생

  • 문서 로딩 이벤트
    서버에서 웹 문서를 가져오거나
    문서를 위/아래 로 스크롤 하는 등
    웹 문서를 브라우저 창에 보여주는 것과 관련

  • 폼 이벤트
    폼은 로그인, 검색, 게시판, 설문조사 처럼
    사용자가 입력하는 모든 요소를 가리킴
    폼 요소에 내용을 입력하면서 발생하는 이벤트

모든 동작이 이벤트는 아님

  • 웹 페이지를 읽어오거나, 링크를 클릭하는 것처럼
    웹 문서 영역 안에서 이루어지는 것들을 말함
  • 사용자가 웹 문서 영역을 벗어나 클릭하는 행위는 이벤트가 아님
    예) 브라우저 창 맨 위의 제목 표시줄 클릭 등

- 이벤트 처리기

웹 문서에서 이벤트가 발생하면 처리하는 함수

- 이벤트 처리의 가장 기본적인 방법

이벤트가 발생한 HTML 태그에 이벤트 처리기를 직접 연결하는 것

  • <태그명 on이벤트명 = "함수명">

DOM (Document Object Model)

- DOM

자바스크립트를 이용하여 웹 문서에 접근하고, 제어할 수 있도록 객체를 사용해 웹 문서를 체계적으로 정리하는 방법

웹 문서를 하나의 객체로 정의함

웹 문서를 다루는 텍스트, 이미지, 표 등 각각의 요소들도 객체로 정의함

웹 문서 전체는 document 객체이고, 삽입한 이미지는 image 객체임

- DOM 요소에 접근하고, 속성을 가져오기

id 선택자로 접근

  • HTML 태그의 id 속성은 HTML요소가 문서 안에서
    중복되지 않도록 사용하는 CSS 선택자
    getElementById()를 이용하면 특정한 id가 포함된 DOM 요소에 접근할 수 있음
    -document.getElementById("id명");

class 값으로 접근

  • 지정한 class 선택자 이름이 포함된 DOM 요소에 접근할 수 있음
  • document.getElementByClassName("class명");

- 태그 이름으로 접근

class나 id를 지정하지 않은 DOM 요소에 접근하려면 태그를 이용해야함

복수형 Elements

  • 예) 웹 문서 내의 모든 p태그에 적용
  • document.getElementsByTagName("tag명");

- 다양한 방법으로 접근

querySelector() 메서드 사용

  • id 선택자 처럼 반환값이 하나인 경우

querySelectorAll() 메서드 사용

  • class 선택자나 태그 이름을 사용하여 여러 값이 한꺼번에 반환될 경우

선택자를 표시할 때

  • id 이름 앞에 해시기호(#)를 붙이고,
    class 이름 앞에는 마침표(.)를 붙임
    -태그는 기호 없이 태그명만 사용
  • querySelector() 메서드에서 class 이름으로 접근할 때,
    class 이름을 사용한 여러 요소 중에 첫 번째 요소만 반환

- getAttribute(), setAttribute()

속성을 가져오거나 수정함

웹 요소를 문서에 삽입할 때, 태그 속성을 함께 사용하면 DOM 트리에 노드가 추가되면서 속성값이 저장됨

  • getAttribute("속성명");
  • setAttribute("속성명", "값");

- 트리 노드 루트

부모와 자식을 구조로 표시하면 마치 나무 형태와 같이 되므로 트리 라고 함

노드(node)

  • 트리에서 가지가 갈라져 나온 항목

루트(root)노드

  • 트리의 시작 부분인 노드는 나무 뿌리에 해당
  • 각 노드 사이의 관계를 부모-자식, 형제간으로 표현 가능
  • 부모(parent) 노드에는 자식(child) 노드가 있고,
    부모 노드가 같은 형제(sibling) 노드도 있음

2024-03-20

JSP

- 정적 웹 페이지와 동적 웹 페이지

정적 웹 페이지

  • 컴퓨터에 저장된 텍스트 파일을 그대로 보는 것
  • HTML(HyperText Markup Language)

동적 웹 페이지

  • 저장된 내용을 다른 변수로 가공 처리하여 보는 것
  • PHP(Personal Home Page), ASP(Active Server Page), JSP

- 웹 프로그래밍과 JSP

웹 프로그래밍 언어

  • 클라이언트 측 실행 언어와 서버 측 실행 언어로 구분
  • 자바를 기반으로 하는 JSP는 서버 측 웹 프로그래밍 언어 중 하나

JSP의 특징

  • JSP는 서블릿 기술의 확장
  • JSP는 유지 관리가 용이
  • JSP는 빠른 개발이 가능
  • JSP로 개발하면 코드 길이를 줄일 수 있음

- JSP 개발 환경 도구

자바 개발환경 : JDK

웹서버 : 톰캣

통합 개발 환경 : 이클립스

- 스크립트 태그

<% ... %> 사용

JSP 페이지가 서블릿 프로그램에서 서블릿 클래스로 변환할 때

  • JSP 컨테이너가 자바 코드가 삽입되어 있는 스크립트 태그를 처리하고,
    나머지는 HTML 코드나 일반 텍스트로 간주

- 선언문 태그

변수나 메소드를 선언

  • 변수 - 전역변수로 사용
  • 메소드 - 전역 메소드로 사용
  • <%! 자바 코드; %> 각 행이 세미콜론으로 끝나야 함
<body>
	<%!
		// 선언문 - 클래스 영역
		static String name="java"; 
		int number = 10;
		public String getName () {
			return name;
		}
	%>
</body>

- 스크립틀릿 태그

자바 코드로 이루어진 로직 부분을 표현

out 객체를 사용하지 않고도 쉽게 HTML 응답을 만들어냄

  • <% 자바 코드; %> 각 행이 세미콜론으로 끝나야 함
	<%!
		// 선언문 - 클래스 영역
		static String name="java"; 
		int number = 10;
		public String getName () {
			return name;
		}
	%>
	<%
		out.println("<h1>"+name+"</h1>");
		out.println(number+"<br />");
		out.println(getName()+"<br />");
	%>

- 표현문 태그

웹 브라우저에 출력할 부분을 표현

표현문 태그에 숫자, 문자, 불린(Boolean) 등의 기본 데이터 타입과 자바 객체 타입도 사용 가능

  • <%= 자바 코드 %> 각 행을 세미콜론으로 종료할 수 없음
<body>
	<%!
		// 선언문 - 클래스 영역
		static String name="java"; 
		int number = 10;
		public String getName () {
			return name;
		}
	%>
	<h1><%=name %></h1>
	<%=number %>
	<%=getName() %>
</body>

- 주석처리

  • <%-- JSP 주석 처리 내용 --%>
<body>
	<h2>A Test of Comments</h2>
    <%-- This comment will not be visible in the page source --%>
</body>

- JSP 구구단

<body>
	<%for (int dan=2; dan<=9; dan++) {	%>
		<%for (int num=1; num<=9; num++) {	%>
			<%=dan%> x <%=num%> = <%=dan*num %><br/>
		<%} %>
	<%} %>
</body>

- JSP 날짜 출력

import : <%@ page import="경로" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.Date" %>
<%@ page import="web.test.jsp.Data" %>

<% Date day = new Date(); %>
날짜 : <h1><%= day %></h1>
<hr />

<h1>count : <%= Data.count %>	</h1>
<h1>name : <%= Data.name %>		</h1>
<hr color="orange"	/>

<% Data data = new Data(); %>
<h1>number : <%= data.number %>	</h1>
<h1>str : <%= data.str %>		</h1>

2024-03-21

JSP

- 클래스의 영역

변수, 생성자, 메서드

jsp : 컴파일을 서버가 하기 때문에 생성자를 만들 수 없음

재사용 불가 = 상속 불가 = 접근 제어자 필요 없음

클래스 부분이 필요없음

<%
// 인스턴스 메서드 영역
	int z = 500;
	web.test.jsp.Tv t = new web.test.jsp.Tv();
%>
표현문 (jsp에서의 출력문)	<br />
<%= a %>	<br />
<%= x %>	<br />
<%= z %>	<br />
<%= t %>

- info = ""

간단한 정보

화면에 나오지 않음

- language = ""

속성 생략 가능

기본값 "java"

java 만 지원하기 때문에 안써도 됨

- pageEncoding

인코딩 정보

- contentType = ""

현재 JSP 페이지의 내용이 어떤 타입의 문서로 생성되는지 지정하는 속성

여러 형태의 문서를 생성할 수 있음

기본값 text/html

  • 응답결과를 html 문서 형식으로 생성하고 출력하겠다는 의미

- extends = ""

상속받을 클래스 풀네임

쓰이지 않음

- import = ""

java의 import와 같음

이 속성은 유일하게 한 페이지 내에서 여러 개 사용 가능

- buffer = "8kb"

현재 페이지에서 한 번에 출력될 수 있는 양

buffer : 입출력 데이터 등의 정보를 정달할 때 사용되는 임시 저장소

  • 한 장소에서 다른 장소로 데이터를 송신할 때 일어나는
    시간의 차이나 데이터 흐름 속도의 차이를 맞추기 위해 사용

기본값 "8kb"

  • none : 사용하지 않겠다는 의미
    바로 웹 브라우저로 출력 됨

더 많은 내용 출력할 때, 크기를 늘려주면 됨

- autoFlush = "true"

JSP 페이지의 내용들이 웹 브라우저에 출력되기 전에 출력 버퍼가 다 찰 경우,

buffer의 양을 늘리겠느냐 = "true" : 자동으로 늘리겠다는 의미

- errorPage = ""

에러가 났을 때 보여줄 페이지 지정

- isErrorPage = "false"

errorPage 에 설정

  • true : 예외처리를 자동으로 처리

web.xml 파일에서 < error-page >로 처리하도록 되어있기 때문에 쓸모없는 기능

isThreadSafe = "true"

멀티 스레드(여러 브라우저에서 동시에 실행) 사용 할 수 있는지 설정

false : 다수의 사용자들의 요청을 동시에 처리하지 못하고, 순차적으로 처리하기 때문에 진행 시간이 많이 소요됨

- isELIgnored = "false"

EL(표현 언어)식 출력에 관한 것. 반드시 사용함

- deferredSyntaxAllowedAsLiteral = "false"

표현식에 관한 것. EL과 같이 사용됨

- trimDirectiveWhitespace = "false"

공백 제공. 큰 차이 없는 기능

- 인크루드 디렉티브

<%@ include file="포함 될 파일의 URL" %>

포함시킬 파일명을 file 속성의 값으로 지정

해당 JSP 페이지의 코드 복사로 처리방식이 정적이라고 할 수 있음

위치 상관 없음 (위/아래)

여러 번 사용 가능

페이지의 설정(인코딩 등)이 다르면 에러남

  • top.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>top</h1>
<hr color="skyblue"	/>

  • include
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>

- form action

text type - if문 활용

  • form
<form action="pro.jsp" method="post">
	이름 :	<input type="text" name="name"	/>	<br />
	나이 :	<input type="text" name="age"	/>	<br />
			<input type="submit" value="입력완료"	/>
</form>

  • post
<% request.setCharacterEncoding("UTF-8"); %>

<%
	String name	= request.getParameter("name");
	int age	= Integer.parseInt(request.getParameter("age"));
	
	if (age >= 20) {		// 나이 20 이상
		out.println("<h1>"+name+" 님의 나이는 20세이상입니다.</h1>");
	}else {					// 나이 20 미만
		out.println("<h1>"+name+" 님은 <h2>미성년자</h2> 입니다.</h1>");
	}
%>

select - if문 활용

  • form
<form action="pro2.jsp" method="post">
	이름 :	<input type="text" name="name"	/>	<br />
	전화번호 :
	<select name="local">
		<option value="인천">인천</option>
		<option value="서울">서울</option>
		<option value="경기">경기</option>
	</select>
	- <input type="text" name="tel"		/>	<br />
	  <input type="submit" value="전송"	/>
</form>

  • post
<% request.setCharacterEncoding("UTF-8"); %>

<%
	String name		= request.getParameter("name");
	String local	= request.getParameter("local");
	String tel		= request.getParameter("tel");
	String localNum	= "";
	
	if (local.equals("인천")) {
		localNum = "032";
		out.println("<b>"+name+" 님의 지역은 "+local+" 이고, 지역번호는 "+localNum+" 입니다.</b>");
	}else if (local.equals("서울")) {
		localNum = "02";
		out.println(name+" 님의 지역은 "+local+" 이고, 지역번호는 "+localNum+" 입니다.");
	}else if (local.equals("경기")) {
		localNum = "031";
		out.println(name+" 님의 지역은 "+local+" 이고, 지역번호는 "+localNum+" 입니다.");
	}
%>

권역 - switch문 활용

  • form
<form action="pro3.jsp" mathod="host">
	<input type="radio" name="localNum" value="0" checked	/>0권역	<br />
	<input type="radio" name="localNum" value="1"	/>1권역	<br />
	<input type="radio" name="localNum" value="2"	/>2권역	<br />
	<input type="radio" name="localNum" value="3"	/>3권역	<br />
	<input type="radio" name="localNum" value="4"	/>4권역	<br />
	<input type="radio" name="localNum" value="5"	/>5권역	<br />
	<input type="radio" name="localNum" value="6"	/>6권역	<br />
	<input type="radio" name="localNum" value="7" 	/>default	<br />
	<input type="submit" value="전송"	/>
</form>

  • host
<% request.setCharacterEncoding("UTF-8"); %>

<%
	int localNum = Integer.parseInt(request.getParameter("localNum"));
	String localName = "";

	switch (localNum) {
		case 0 :
			localName = "종로, 중구, 용산";
		break;
		case 1 :
			localName = "도봉 , 강북 , 노원 , 성북";
		break;
		case 2 :
			localName = "동대문 , 성동 ,중랑 , 광진";
		break;
		case 3 :
			localName = "강동, 송파";
		break;
		case 4 :
			localName = "서초, 강남";
		break;
		case 5 :
			localName = "동작, 관악, 금천";
		break;
		case 6 :
			localName = "강서, 양청, 영등포, 구로";
		break;
		default :
			out.println("없는");
		break;
	}
	out.println("해당 권역은 "+localName+" 권역입니다.");
%>

text, radio, select type - table 활용

  • form
<form action="pro4.jsp" mathod="post">
	이름 : <input type="text" name="name"	/>	<br />
	학번 : <input type="text" name="num"	/>	<br />
	1학년 : <input type="radio" name="grade" value="1"	/>	&nbsp;
	2학년 :	<input type="radio" name="grade" value="2"	/>	&nbsp;
	3학년 : <input type="radio" name="grade" value="3"	/>	&nbsp;
	<br />
	선택과목 : <select name="subject">
		<option value="java">java</option>
		<option value="jsp">jsp</option>
		<option value="html">html</option>
		<option value="국어">국어</option>
	</select>
	<br />
	<input type="submit" value="전송"	/>
</form>

  • post
<head>
	<style>
		table {
			text-align: center;
		}
	</style>
</head>
<h1>pro4.jsp</h1>
<% request.setCharacterEncoding("UTF-8"); %>

<%
	String name	= request.getParameter("name");
	String num	= request.getParameter("num");
	String grade= request.getParameter("grade");
	String subject = request.getParameter("subject");
%>
<table border="1">
	<tr>
		<td>이름</td>
		<td>학번</td>
		<td>학년</td>
		<td>선택과목</td>
	</tr>
	<tr>
		<td><%= name %></td>
		<td><%= num %></td>
		<td><%= grade %></td>
		<td><%= subject %></td>
	</tr>
</table>

2024-03-22

- 내장 객체

9개의 내장객체 존재

  • request
  • session
  • application
  • pageContext
  • 속성(attribute)값을 저장하고 읽을 수 있는
    setAttribute()메서드와 getAttribute()메서드가 있음

- Enumeration 객체

java.util.Enumeration 인터페이스는 객체를 저장하는 컬렉션

저장된 객체들은 모두 Object 타입으로 저장함

주로 사용하는 메서드

  • boolean hasMoreElements();
    더 이상의 객체가 없는지 있는지를 판단
    객체가 있으면 true 리턴, 객체가 없으면 false 리턴
  • Object nextElement();
    다음 객체를 가져오는 메서드
    이 때, Object 타입으로 받아온 객체를 원래의 객체 형태로 형변환하여 사용함
<%@ page import="java.util.Enumeration" %>

<%
	String[] names = {"프로토콜 이름", "서버이름", "Method 방식",
						"컨텍스트 경로", "URI", "접속한 클라이언트의 IP"};
	String[] values = {request.getProtocol(), 
						request.getServerName(),
						request.getMethod(),
						request.getContextPath(),
						request.getRequestURI(),
						request.getRemoteAddr()};
	
	Enumeration<String> en = request.getHeaderNames();
	String headerName = "";
	String headerValue = "";
	
%>
<H2>웹 브라우저의 웹 서버 정보 표시</H2>
<%
	for(int i=0; i<names.length; i++) {
		out.println(names[i]+" : "+ values[i]+"<br />");
	}
%>

<H2>헤더의 정보 표시</H2>
<%
	while(en.hasMoreElements()) {
		headerName = en.nextElement();
		headerValue = request.getHeader(headerName);
		out.println(headerName+" : "+headerValue+"<br />");
	}
%>

- URL / URI

URL (Uniform Resource Locator)

  • 웹 상에서 서비스를 제공하고 있는 각 서버들이 제공하고 있는 파일들의 위치를 명시하기 위한 것
    따라서 URL에는 접속해야 할 서비스 종류, 도메인 명, 파일의 위치 포함됨

URI (Uniform Resource Identifier)

  • URL로 부터 존재하는 자원들을 식별하기 위한 일반적인 식별자를 규정하기 위한 것
    따라서 URI는 URL에서 HTTP 프로토콜, 호스트명, port번호를 제외한 부분이 됨
  • 예) http://localhost:8080/web/views/home.jsp에서
    URL : 전체
    URI : web/views/home.jps

- response 내장 객체

페이지 이동: 사용자가 애플리케이션의 특정 페이지를 요청했지만, 해당 페이지가 이동하거나 삭제되었을 때, 대신 새로운 위치로 이동시키기 위해 리다이렉션이 사용됨

  • ex03.jsp
<H3>현재 페이지는 ex03(responseRedirect).jsp 입니다.</H3>

<%
	response.sendRedirect("ex04.jsp");
%>

  • ex04.jsp
<H3>현재 페이지는 ex04(Redirect된 페이지).jsp 입니다.</H3>

ex03.jsp -> ex04.jsp 이동

- out 내장 객체

JSP페이지가 생성한 결과를 웹 브라우저에 전송해주는 출력 스트림

println()메서드와 표현식 < %= % > 모두 브라우저에서 출력시키는 역할을 함

단지 편의성을 위해서 표현식 형태로 제공함

	<H2>out 내장객체 - out.println() 사용</H2>
<%
	String name = "HTML";
	out.println("출력되는 내용 <B>"+name+"</B> 입니다.");
%>

	<H2>out 내장객체 - 표현식 사용</H2>
<%
	String name2 = "JSP";
	
%>
	출력되는 내용 <B><%=name2 %></B> 입니다.

- application 내장객체

웹 어플리케이션의 설정 정보를 갖는 context와 관련이 있는 객체

서버의 설정 정보, 자원에 대한 정보를 얻거나 어플리케이션이 실행되고있는 동안 발생할 수 있는 이벤트 로그 정보와 관련된 기능을 제공함

application 객체는 웹 어플리케이션 당 한 개의 객체가 생성됨

하나의 웹 어플리케이션에서 공유하는 변수로 사용됨

<%
	String info = application.getServerInfo();
	String path = application.getRealPath("/");
	application.log("로그 기록 : ");
%>
웹 컨테이너의 이름과 버전 : <%= info %>	<br />
웹 어플리케이션 폴더의 로컬 시스템 경로 : <%= path %>

include

서블릿 간에 요청 및 응답을 포함하는 메커니즘

하나의 서블릿이 다른 서블릿에게 자신의 요청과 응답을 포함시킬 때 사용

include를 사용하면 클라이언트의 요청이 여러 서블릿을 거치며 처리될 수 있음

  • ex07.jsp
<h1>/0322/ex07.jsp</h1>
<h2>include 액션태그</h2>
<form action="ex08.jsp" method="post">
	이름: 		<input type="text" name="name"					/>	<br />
	페이지 이름:<input type="text" name="pageName" value="ex07"	/>	<br />
				<input type="submit" value="전송"				/>
</form>

  • ex08.jsp
<% request.setCharacterEncoding("UTF-8"); %>
<%
	String pageName = request.getParameter("pageName");
		   pageName += ".jsp";
%>

<h2>포함하는 페이지 ex08.jsp 입니다.</h2>
<hr color="blue"	/>
<jsp:include page="<%= pageName %>"	/>
<hr color="red" />
ex08.jsp 의 나머지 부분

- forward

클라이언트의 요청을 다른 서블릿으로 전달하는 메커니즘

하나의 서블릿이 다른 서블릿에게 요청을 위임할 때 사용됨

클라이언트는 전달된 서블릿으로부터 직접 응답을 받음

이 과정에서 클라이언트는 전달된 서블릿이 실제로 요청을 처리한 서블릿인지 알 수 없음

  • ex09.jsp
<form action="ex10.jsp" method="post">
	<input type="text" name="id"	/>	<br />
	취미:
	<select name="hobby">
		<option value="영화">영화</option>
		<option value="운동">운동</option>
		<option value="수면">수면</option>
	</select>
	<input type="submit" value="전송"	/>
</form>

  • ex10.jsp
<% request.setCharacterEncoding("UTF-8"); %>
<h1>포워드 하는 페이지 /0322/ex10.jsp</h1>

<%
	String id	= request.getParameter("id");
	String hobby= request.getParameter("hobby");
	
	System.out.println(id);
	System.out.println(hobby);
%>
<jsp:forward page="ex09.jsp"></jsp:forward>

ex09 포워드 -완-		<%-- 내용 출력되지 않음 --%>

2024-03-25

- 데이터 전달 방식

  • get/post 두 가지 방식

method="get"

  • post 방식 보다 전송속도 빠름
  • 문자만 전송 가능, 파일 전송 불가
  • 최대 256바이트 전송
  • URL로 파라미터 노출 -> 보안 취약

method="host"

  • get 방식보다 전송속도 느림
  • 전송량의 한계가 없음
  • URL 노출 안됨

- 인코딩 방식

post 방식

반드시 첫 줄에 사용해야함

request.setCharacterEncoding("UTF-8");

get 방식

server.xml 에 63줄

URIEncoding="UTF-8" 추가

- 인크루드

<%@ include file="" %>

  • 디렉티브 인크루드
  • 해당 페이지의 코드를 합침
  • 변수가 있는 페이지를 합칠 때 좋음

<jsp:include page="" />

  • 액션태그 인크루드
  • 따로따로 실행 후 에러가 없으면 결과를 합침
  • 페이지 끼리 인코딩이 달라도 사용 가능
  • 헤더 / 푸터 페이지 합칠 때 좋음

- 페이지 이동 방식

  • redirect / forward

redirect

  • post 방식 불가 : get 방식 가능
  • 파라미터 전달 불가
  • 단순 페이지 이동
  • request : 한 페이지만 가능

forward

  • get / post 둘 다 가능
  • 파라미터를 전달할 때 사용

- 쿠키

세션과 달리 클라이언트의 상태 정보를 클라이언트 측에 보관, 저장함

쿠키를 항상 가지고 다님 - 자동 로그인

쿠키 - 브라우저에 저장 = 브라우저에서 확인

해당 폴더 내에서만 사용 가능

내부 객체가 아니기 떄문에 생성해야함

	Cookie coo = new Cookie("coo", "test");

	// 쿠키 유효기간 : 초 단위
	coo.setMaxAge(30);		// 유효기간 30초 설정 - 생성 후 30초 지나면 자동 삭제
	// .setMaxAge(0);		// 로그아웃
	// 클라이언트(브라우저) 전달
	response.addCookie(coo);
%>
<h1>쿠키 생성 -완-</h1>

  • cookie.jsp
<%
	// 쿠키 생성
	Cookie coo1 = new Cookie("cid", "java");
	Cookie coo2 = new Cookie("cqw", "1234");
	Cookie coo3 = new Cookie("cname", "jsp");
	
	// 쿠키 유효시간
	coo1.setMaxAge(60);
	coo2.setMaxAge(60);
	coo3.setMaxAge(60);

	// 클라이언트(브라우저) 전달
	response.addCookie(coo1);
	response.addCookie(coo2);
	response.addCookie(coo3);
%>
<h2>쿠키 생성 -完-</h2>

  • cookieResult.jsp
<%
	Cookie[] cookies = request.getCookies();
	for (Cookie c : cookies) {
		out.println(c.getName()+" : "+c.getValue());	// key : value
		out.println("<br />");
	}
	
	// 60초 후 새로고침으로 다시 확인
%>

- error

<%--
	404 error
		- 문서를 찾을 수 없음
		- 주로 사용자가 잘못된 페이지를 요청했을 경우
		- URL 확인. 올바르게 입력했는지 확인
	500 error
		- 서버 내부 오류
		- 웹 서버가 요청 사항을 수행할 수 없을 경우에 발생
		- 프로그램 코딩 확인
	
	실제 현재 에러처리 방법
		- web.xml 페이지에 작성
		- 전체 파일 설정
		- 프로젝트이름.xml 생성
	
	.xml
		- 태그 사용하는 것은 HTML 과 같음
		- 그러나 지정된 태그는 없음
		- DTD 스키마 파일의 맨 위에 있음
--%>

- scope

<%
	// 서버 실행(켜저있는) 상태에서 어디서든 사용 가능
	// 서버에 저장되기 때문에 어디서든(페이지가 달라도) 꺼낼 수 있음
	application.setAttribute("data", "app");
// 	application.removeAttribute("data");
	application.setAttribute("id", "app_i");

	// 클라이언트의 상태 정보를 서버상에 저장, 기록
	// 해당 브라우저 유지 시 어디서든(페이지 달라도) 사용 가능
	// 브라우저를 껐다 켜면 다른 브라우저에서 사용 못함
	session.setAttribute("sdata", "session");
// 	session.removeAttribute("sdata");
	session.setAttribute("sid", "session_i");
	
	// 해당 페이지, 포워드로 전달 받은 페이지에서만 사용 가능
	request.setAttribute("rdata", "request");
	request.setAttribute("rid", "request_i");
	request.getRequestDispatcher("/views/0325/scopeResult.jsp")
								.forward(request, response);
// 	request.removeAttribute("rdata");
%>

<h2>attribute 생성 -완-</h2>

  • scopeResult.jsp
<%
	Object obj	= application.getAttribute("data");
	Object obj2	= session.getAttribute("sdata");
	Object obj3	= request.getAttribute("rdata");
	
	Object obj4	= application.getAttribute("id");
	Object obj5	= session.getAttribute("sid");
	Object obj6	= request.getAttribute("rid");
%>

<h2>data	: <%= obj  %></h2>
<h2>sdata	: <%= obj2 %></h2>
<h2>rdata	: <%= obj3 %></h2>

<h2>id		: <%= obj4 %></h2>
<h2>sid		: <%= obj5 %></h2>
<h2>rid		: <%= obj6 %></h2>
<%--
	서버측에 생성되기 때문에 브라우저에서 출력하여 확인
--%>

- 파일 업로드

  • uploadForm.jsp
<%--
	form 태그 설정 시
	전송 방식 : post <-> get 은 URL만 보낼 수 있음
	enctype : multipart/form-data : multipart 는 모든 데이터 타입 가능
	
	문자가 아니기 때문에
	request.getParameter(); / <jsp:useBean	/> 으로 받지 못함
	외부 라이브러리 필요
	http://servlets.com/cos/
	- cos-22.05.zip 다운
	- cos : WEB-INF/lib 폴더에 넣음
--%>

<form action="uploadPro.jsp" method="post" enctype="multipart/form-data">
	name : 	<input type="text" name="name"	/>	<br />
	file : 	<input type="file" name="save"	/>	<br />
			<button type="submit">업로드</button>
</form>
  • uploadPro.jsp
<%@ page import="upload 폴더 경로" %>
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %>
<%@ page import="java.io.File" %>

<%
	String path = "C:/Users/woori/Desktop/wjs/upload";
// 	경로 : 작업폴더 속 upload 폴더 생성
	int max = 1024*1024*10;		// 크기
	String enc = "UTF-8";		// 인코딩
	DefaultFileRenamePolicy dp = new DefaultFileRenamePolicy();
// 	같은 파일이 있으면 1,2,.. 붙여서 이름 지어줌
	
// 	new 할 때 이미 업로드가 되고있음
	MultipartRequest mr = new MultipartRequest(request, path, max, enc, dp);
	
	String name = mr.getParameter("name");
	File f 		= mr.getFile("save");
%>
<h1>name 				: <%= name		  %></h1>
<h1>파일 이름 			: <%= f.getName() %></h1>
<h1>파일 크기 			: <%= f.length()  %></h1>
<h1>파일 업로드 이름 		: <%= mr.getFilesystemName("save") 	%></h1>
<h1>파일 원본 이름 		: <%= mr.getOriginalFileName("save")%></h1>
<h1>파일 타입 			: <%= mr.getContentType("save")		%></h1>

2024-03-26

Oracle SQL Developer

- 계정 생성

// 계정 생성
alter session set "_ORACLE_SCRIPT"=true;

// create user 계정명 identified by 비밀번호;
create user scott identified by tiger;
 
// 계정 권한 추가
// grant 권한명, 권한명 to 계정명;
grant resource, connect, dba to scott;

commit;

- 테이블 생성

  • 테이블 생성
    create table 테이블명(
        컬럼명 타입 옵션,
        컬럼명 타입 옵션,
        ..
        컬럼명 타입 옵션
);

  • 실습용 테이블
/*
    Create DEPT table which will be the parent table of the EMP table.
*/
create table dept(                          // 부서 테이블
  deptno     number(2,0),                   // 부서 번호
  dname      varchar2(14),                  // 부서 이름
  loc        varchar2(13),                  // 위치
  constraint pk_dept primary key (deptno) 
);

/*
    Create the EMP table which has a foreign key reference to the DEPT table.
    The foreign key will require that the DEPTNO in the EMP table exist in the DEPTNO column in the DEPT table.
*/
create table emp(                           // 사원 테이블
  empno    number(4,0),                     // 사원 번호
  ename    varchar2(10),                    // 사원 이름
  job      varchar2(9),                     // 업무
  mgr      number(4,0),                     // 상관
  hiredate date,                            // 입사 일자
  sal      number(7,2),                     // 급여
  comm     number(7,2),                     // 수당
  deptno   number(2,0),                     // 부서 번호
  constraint pk_emp primary key (empno),    
  constraint fk_deptno foreign key (deptno) references dept (deptno) 
);      // 부서 테이블과 이어주는 foreign 키

/*
    Insert row into DEPT table using named columns.
*/
insert into DEPT (DEPTNO, DNAME, LOC)
values(10, 'ACCOUNTING', 'NEW YORK');

/*
    Insert a row into DEPT table by column position.
*/
insert into dept 
values(20, 'RESEARCH', 'DALLAS');

insert into dept 
values(30, 'SALES', 'CHICAGO');

insert into dept 
values(40, 'OPERATIONS', 'BOSTON');

/*
    Insert EMP row, using TO_DATE function to cast string literal into an oracle DATE format.
*/
insert into emp 
values( 
 7839, 'KING', 'PRESIDENT', null, 
 to_date('17-11-1981','dd-mm-yyyy'), 
 5000, null, 10 
);

insert into emp 
values( 
 7698, 'BLAKE', 'MANAGER', 7839, 
 to_date('1-5-1981','dd-mm-yyyy'), 
 2850, null, 30 
);

insert into emp 
values( 
 7782, 'CLARK', 'MANAGER', 7839, 
 to_date('9-6-1981','dd-mm-yyyy'), 
 2450, null, 10 
);

insert into emp 
values( 
 7566, 'JONES', 'MANAGER', 7839, 
 to_date('2-4-1981','dd-mm-yyyy'), 
 2975, null, 20 
);

insert into emp 
values( 
 7788, 'SCOTT', 'ANALYST', 7566, 
 to_date('13-07-1987','dd-mm-yyyy') - 85, 
 3000, null, 20 
);

insert into emp 
values( 
 7902, 'FORD', 'ANALYST', 7566, 
 to_date('3-12-1981','dd-mm-yyyy'), 
 3000, null, 20 
);

insert into emp 
values( 
 7369, 'SMITH', 'CLERK', 7902, 
 to_date('17-12-1980','dd-mm-yyyy'), 
 800, null, 20 
);

insert into emp 
values( 
 7499, 'ALLEN', 'SALESMAN', 7698, 
 to_date('20-2-1981','dd-mm-yyyy'), 
 1600, 300, 30 
);

insert into emp 
values( 
 7521, 'WARD', 'SALESMAN', 7698, 
 to_date('22-2-1981','dd-mm-yyyy'), 
 1250, 500, 30 
);

insert into emp 
values( 
 7654, 'MARTIN', 'SALESMAN', 7698, 
 to_date('28-9-1981','dd-mm-yyyy'), 
 1250, 1400, 30 
);

insert into emp 
values( 
 7844, 'TURNER', 'SALESMAN', 7698, 
 to_date('8-9-1981','dd-mm-yyyy'), 
 1500, 0, 30 
);

insert into emp 
values( 
 7876, 'ADAMS', 'CLERK', 7788, 
 to_date('13-07-1987', 'dd-mm-yyyy') - 51, 
 1100, null, 20 
);

insert into emp 
values( 
 7900, 'JAMES', 'CLERK', 7698, 
 to_date('3-12-1981','dd-mm-yyyy'), 
 950, null, 30 
);

insert into emp 
values( 
 7934, 'MILLER', 'CLERK', 7782, 
 to_date('23-1-1982','dd-mm-yyyy'), 
 1300, null, 10 
);

commit;

- 테이블 컬럼 조회/검색(SELECT)

// SELECT 컬럼명
// FROM 테이블명;

SELECT empno
FROM emp;

SELECT empno, ename, sal
FROM emp;

- DISTINCT 컬럼 중복 제거 후 검색

// SELECT distinct 컬럼명
// FROM 테이블명;

SELECT distinct deptno
FROM dept;

SELECT distinct job
FROM emp;

SELECT distinct deptno, job
FROM emp;

- 별칭을 붙여 검색

// SELECT 컬럼명 as 별칭
// FROM 테이블명;
// AS 생략 가능

SELECT job AS j
FROM emp;

SELECT empno AS ep, ename en
FROM emp;

SELECT empno, sal, sal+500 AS b
FROM emp;

- 정렬 (내림차순-DESC, 오름차순-ASC)

// SELECT 컬럼명
// FROM 테이블명 ORDER BY 컬럼명;

SELECT *
FROM emp ORDER BY sal;

SELECT *
FROM emp ORDER BY deptno, sal DESC, hiredate;

- WHERE 조건문

// SELECT 컬럼명
// FROM 테이블명 WHERE 조건식;

// 등가 비교
// SELECT 컬럼명
// FROM 테이블명 WHERE 컬럼명 = 값;

SELECT *
FROM emp
WHERE deptno=10;

SELECT *
FROM emp
WHERE job='SALESMAN'; // 문자컬럼의 값은 대소문자 구분함

- 비교 연산자 >, <, >=, <=

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 > 값;

SELECT *
FROM emp
WHERE sal >= 3000;

SELECT *
FROM emp
WHERE ename > 'C';    // 문자는 > 이상, < 이하

SELECT ename, hiredate
FROM emp
WHERE hiredate > '1985/01/01';  // 숫자는 >초과, <미만

- AND 연산자

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 > 값 AND 컬럼명 = 값;

SELECT *
FROM emp
WHERE sal >= 1500 AND deptno = 30;

SELECT *
FROM emp 
WHERE sal >= 1500 AND deptno = 30 AND job = 'MANAGER';

- OR 연산자

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 > 값 OR 컬럼명 = 값;

SELECT * 
FROM emp 
WHERE deptno = 10 OR deptno = 20;

SELECT ename, job 
FROM emp 
WHERE job = 'MANAGER' OR job = 'CLERK';

- NOT 연산자

// SELECT 컬럼명
// FROM 테이블명
// WHERE NOT 컬럼명 > 값;

SELECT *
FROM emp
WHERE NOT sal = 3000;       // NOT 은 컬럼명 앞에 붙음

SELECT *
FROM emp
WHERE sal != 3000;

- IN연산

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 IN(값, 값, 값, ..);

SELECT *
FROM emp
WHERE deptno IN(10,30,50);

SELECT *
FROM emp
WHERE deptno = 10 OR deptno = 30 or deptno = 50;

SELECT *
FROM emp
WHERE job IN('MANAGER', 'SALESMAN');

- NOT IN

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 NOT IN(값, 값, 값, ..);

SELECT *
FROM emp
WHERE deptno NOT IN(10, 30, 50);

- BETWEEN ~ AND 연산자

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 BETWEEN 값 AND 값;

SELECT *
FROM emp
WHERE sal BETWEEN 1500 AND 3000;

SELECT *
FROM emp
WHERE sal >= 1500 AND sal <= 3000;

SELECT *
FROM emp
WHERE hiredate BETWEEN '1980/05/01' AND '1990/10/10';

- NOT BETWEEN ~ AND 연산

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 NOT BETWEEN 값 AND 값;

SELECT *
FROM emp
WHERE sal NOT BETWEEN 1500 AND 3000;

- IS NULL, IS NOT NULL 연산

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 IS NULL;

SELECT *
FROM emp
WHERE comm IS NULL;

SELECT *
FROM emp
WHERE comm IS NOT NULL;

- LIKE 연산자

// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 LIKE '%%';

/*
'A%'     - A로 시작하는 문자
'%A'     - A로 끝나는 문자
'%A%'    - 문자 중 A가 포함된 문자
'_A%'    - 두 번째 문자가 A인 문자
'__A%'   - 세 번째 문자가 A인 문자
*/

SELECT *
FROM emp
WHERE ename LIKE 'S%';

SELECT ename
FROM emp
WHERE ename LIKE '%T';

SELECT ename
FROM emp
WHERE ename LIKE '%S%';

SELECT ename
FROM emp
WHERE ename LIKE '_A%';

SELECT ename
FROM emp
WHERE ename LIKE '%R%I%';

- UNION 합집합

// 비교하는 대상의 컬럼수와 타입이 같아야함
// SELECT 컬럼명
// FROM 테이블명
// UNION
// SELECT 컬럼명
// FROM 테이블명;

SELECT *
FROM emp
WHERE deptno = 10 
UNION
SELECT *
FROM dept           // 컬럼 수가 달라 오류남
WHERE deptno = 20;

SELECT job
FROM emp
WHERE deptno = 10
UNION
SELECT deptno
FROM dept           // 타입이 달라 오류남
WHERE deptno = 20;

2024-03-27

Oracle SQL Developer

- 함수

문자 관련 함수

  • UPPER - 모두 대문자로 변경
  • LOWER - 모두 소문자로 변경
  • INITCAP - 첫글자만 대문자로 변경
SELECT UPPER(ename), LOWER(ename), INITCAP(ename)
FROM emp;

SELECT *
FROM emp
WHERE UPPER(ename) = UPPER('scott');

SELECT *
FROM emp
WHERE UPPER(ename) LIKE UPPER('%s%');   // 대문자로 변경되어 소문자 기입함

SELECT *
FROM emp
WHERE ename LIKE ('%S%');

- LENGTH

함수 이름, 컬럼 같이나옴

SELECT ename, LENGTH(ename)
FROM emp
ORDER BY LENGTH(ename) DESC;

SELECT ename, sal, deptno
FROM emp
WHERE LENGTH(ename) = 4;

- LENGTHB

문자의 바이트 수 (한글, 한 글자 당 3byte)

함수만 실행 시, 테이블 이름이 불필요한 경우 DUAL(더미테이블) 적용

SELECT LENGTH('한글'), LENGTHB('한글')
FROM DUAL;

- SUBSTR

문자 추출

오라클은 인덱스 1부터 시작

// SUBSTR(컬럼명, 시작, 포함)

SELECT job, SUBSTR(job, 1, 3)
FROM emp;		// 처음부터 3번째 까지

SELECT job, SUBSTR(job, 3, 2)
FROM emp;   	// 3번째 부터 2글자


SELECT job, SUBSTR(job, 3)
FROM emp;		// 3번째 부터 끝까지

SELECT job, SUBSTR(job, -1)
FROM emp;		// 마지막 문자

// 뒤에서부터 세글자
SELECT job, SUBSTR(job, -3)
FROM emp;

// 뒤에서 3번째부터 2글자
SELECT job, SUBSTR(job, -3, 2)
FROM emp;

SELECT SUBSTR('java Programing', 5, 3)
FROM DUAL;      // 공백, 대문자 구분

- INSTR

문자 인덱스 위치 찾기

SELECT INSTR('hello oracle','l')
FROM DUAL;      // 같은 문자는 처음에 오는 문자를 인식함

SELECT INSTR('hello oracle', 'l', 5)
FROM DUAL;      // 검색할 인덱스의 번호 5에서부터 나오는 l 검색

SELECT INSTR('hello oracle', 'l', 1, 2)
FROM DUAL;      // 1 인덱스 부터 검색해서 2번째 l의 인덱스의 위치를 검색

- REPLACE

문자 변경

SELECT REPLACE('010-1234-5678','-',' ')
FROM DUAL;		// - 를 띄어쓰기 로 변경

SELECT job, REPLACE(job, 'M', '-')
FROM emp;		// M을 - 로 변경

- 문자의 빈 공간 채우기

  • 왼쪽 공간 : LPAD
  • 오른쪽 공간 : RPAD
SELECT LPAD('oracle', 10)
FROM DUAL;

SELECT LPAD('oracle', 10, '*')
FROM DUAL;

SELECT RPAD('oracle', 10, '*')
FROM DUAL;

SELECT RPAD('123456-1', 14, '*')
FROM DUAL;

- CONCAT

두 문자열의 데이터 합치기

SELECT CONCAT(ename, job)
FROM emp;

SELECT CONCAT(empno, ename)
FROM emp;

SELECT ename||'-'||job
FROM emp;		// 이름, 직업 사이에 - 기호 추가

- TRIM, LTRIM, RTRIM

특정 문자 지우기

SELECT TRIM(' oracle ')
FROM DUAL;		// 양쪽 공백 지움

SELECT LTRIM('<oracle>', '<')
FROM DUAL;		// 왼쪽 < 기호 지움

SELECT RTRIM('<oracle>', 'le>')
FROM DUAL;		// 오른쪽 le> 문자와 기호까지 지움

- 숫자 관련 함수

ROUND - 반올림

CEIL - 올림

SELECT ROUND(1234.5678)
FROM DUAL;

SELECT ROUND(1234.5678, 0)
FROM DUAL;

SELECT ROUND(1234.5678, 1)
FROM DUAL;      // 소수점 1번째 자리에 반올림됨

SELECT ROUND(1234.5678, 2)
FROM DUAL;      // 소수점 2번째 자리에 반올림됨

SELECT ROUND(1234.5678, -1)
FROM DUAL;      // 정수 부분의 1번째 자리를 2번째 자리로 반올림

SELECT ROUND(1234.5678, -2)
FROM DUAL;      // 정수 부분의 2번째 자리를 3번째 자리로 반올림

SELECT CEIL(1234.5678)
FROM DUAL;

SELECT CEIL(-1234.5678)
FROM DUAL;      // 음수는 올림 하지 않음

- TRUNC, FLOOR

버림

SELECT TRUNC(1234.5678)
FROM DUAL;

SELECT TRUNC(1234.5678, 1)
FROM DUAL;		// 소수점 1자리 밑으로 버림

SELECT TRUNC(1234.5678, 2)
FROM DUAL;		// 소수점 2자리 밑으로 버림

SELECT TRUNC(1234.5678, 3)
FROM DUAL;		// 소수점 3자리 밑으로 버림

SELECT sal, TRUNC(sal/12)
FROM emp;       // 급여를 12로 나눔. 월급

SELECT TRUNC(3.14)
FROM DUAL;		// 소수점 버림

SELECT FLOOR(3.14)
FROM DUAL;		// 소수점 버림

SELECT TRUNC(-3.14)
FROM DUAL;		// -3 
// 소수 부분을 잘라내어 지정된 자릿수까지의 정수 부분만 남김 즉, 소수를 반올림하지 않고 단순히 잘라냄

SELECT FLOOR(-3.14)
FROM DUAL;      // -4
// 주어진 숫자보다 작거나 같은 가장 큰 정수를 반환함 즉, 내림하는 역할을 함

- MOD 나머지

SELECT MOD(15, 6)
FROM DUAL;      // 앞자리 수를 뒷자리 수로 나눈 나머지 값 %

SELECT MOD(15,0)
FROM DUAL;      // 0 나누면 자기자신

- 날짜 관련 함수

SYSDATE

SELECT SYSDATE
FROM DUAL;      // 오늘 날짜 출력

SELECT SYSDATE, SYSDATE-1, SYSDATE+8
FROM DUAL;		// 오늘날짜-1일, 오늘날짜+8일

- ADD_MONTHS

SELECT ADD_MONTHS(SYSDATE, 3)
FROM DUAL;		// 3개월 뒤

SELECT ename, hiredate, ADD_MONTHS(hiredate, 516) AS 정년
FROM emp;		// 사원 이름, 입사 일자, 입사 일자의 516개월 후

SELECT empno 사원번호, ename AS 사원이름, hiredate AS 입사일, ADD_MONTHS(hiredate, 120) AS 입사10주년
FROM emp;

- MONTHS_BETWEEN

두 날짜간의 개월 수 차이

SELECT MONTHS_BETWEEN(SYSDATE, hiredate)
FROM emp;       // 날짜 차이가 소수점자리 까지 나옴

SELECT MONTHS_BETWEEN(SYSDATE, hiredate), ROUND(MONTHS_BETWEEN(SYSDATE, hiredate)) AS MONTH
FROM emp;		// 소수점 반올림

- NEXT_DAY, LAST_DAY

SELECT SYSDATE, NEXT_DAY(SYSDATE, '월요일')
FROM DUAL;      // 오늘부터 다음 월요일의 날짜

SELECT SYSDATE, NEXT_DAY(SYSDATE, '수요일')
FROM DUAL; 

SELECT SYSDATE, NEXT_DAY(SYSDATE, 1)
FROM DUAL;      // 일요일 1 - 토요일 7

SELECT NEXT_DAY(NEXT_DAY(SYSDATE, '월요일'), '월요일')
FROM DUAL;

SELECT SYSDATE, LAST_DAY(SYSDATE)
FROM DUAL;      //기준 달의 마지막 날짜

SELECT LAST_DAY('20/02/15')
FROM DUAL;

SELECT hiredate AS 고용일, LAST_DAY(hiredate) AS 월말
FROM emp;

2024-03-28

Oracle SQL Developer

- 자료형 변환 TO_CHAR, TO_DATE, TO_NUMBER

TO_CHAR - 문자로 변경

TO_DATE - 날짜로 변경

TO_NUMBER - 숫자로 변경

SELECT TO_CHAR(SYSDATE)
FROM DUAL;      // 날짜형 타입 -> 문자형

SELECT TO_DATE('24.03.28')+8 AS 오늘로부터8일후
FROM DUAL;

SELECT TO_DATE('24/03/01')+8
FROM DUAL;

SELECT TO_NUMBER('100')
FROM DUAL;

SELECT TO_NUMBER('100')+100
FROM DUAL;

SELECT TO_NUMBER('abcd')+100
FROM DUAL;      // 숫자로 변경할 수 없는 문자 에러

SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD HH24:MI:SS') AS 현재날짜시간
FROM DUAL;

SELECT TO_CHAR(TO_DATE('0207', 'MM/YY'), 'MM/YY') AS 문자형변경
FROM DUAL;

- NULL 처리 함수, NVL, NVL2

NVL - NULL값을 대체할 값

NVL2 - NULL이 아닐 때 값

NULL 일 때의 값 처리는 자바의 삼항연산자와 같음

  • 조건 ? T : F ;
SELECT comm, NVL(comm, 0)
FROM emp;

SELECT sal, comm, sal+comm AS 총급여
FROM emp;       // NULL 값은 연산이 불가능해서 SAL도 NULL이 됨

SELECT sal, comm, NVL(comm, 0), sal+comm
FROM emp;       // 대체하는것뿐 comm의 값은 변하지 않음

SELECT sal, comm, sal+NVL(comm,0) AS 총급여
FROM emp;       // NULL 값을 0으로 변환시켜 NVL에 연산 가능

SELECT comm, NVL2(comm, 'O', 'X') AS 보너스유무
FROM emp;       // 

SELECT sal AS 월급, NVL(comm, 0) AS 보너스, NVL2(comm, sal*12+comm, sal*12) AS 연봉
FROM emp;

- DECODE 조건 함수

자바의 스위치문과 비슷함

SELECT
    DECODE(job,
        'MANAGER',  sal * 1.1,
        'SALESMAN', sal * 1.2,
        'ANALYST',  sal * 1.3
    ) AS upsal
FROM emp;

SELECT DECODE(job,
    'MANAGER',  sal * 1.1,
    'SALESMAN', sal * 1.2,
    'ANALYST',  sal,
    sal * 1.03
) AS upsal
FROM emp;

- CASE 조건 함수

자바의 if-else if-else 와 비슷함

SELECT CASE job
    WHEN 'MANAGER'  THEN sal * 1.1
    WHEN 'SALESMAN' THEN sal * 1.2
    WHEN 'ANALYST'  THEN sal * 1.3
    ELSE sal
    END AS upsal
FROM emp;

SELECT CASE
    WHEN comm IS NULL THEN '해당사항 없음'
    WHEN comm = 0     THEN '보너스 없음'
    WHEN comm > 0     THEN '보너스' || sal * 1.1
    END AS comm
FROM emp;       // 기준 컬럼 없이 비교 가능

- 집계함수

SUM, MAX, MIN, COUNT, AVG

SELECT SUM(sal)
FROM emp;

SELECT  SUM(sal)   AS 총합,
        MAX(sal)   AS 최대,
        MIN(sal)   AS 최소,
        COUNT(sal) AS 인원,
        AVG(sal)   AS 평균
FROM emp;

SELECT COUNT(comm)
FROM emp;       // COUNT - 행의 개수. NULL 값은 제외
  • 부서번호가 10번인 부서
  • 급여 합, 최대값, 최소값, 평균-소수점버림, 인원수
SELECT  SUM(sal)        AS 총급여,
        MAX(sal)        AS 최대급여,
        MIN(sal)        AS 최소급여,
        TRUNC(AVG(sal)) AS 평균급여,
        COUNT(*)        AS 사원수
FROM  emp
WHERE deptno = 10;

- GROUP BY, HAVING

GROUP BY - 특정 컬럼을 그룹으로 구분함

HAVING - 그룹에서 조건 처리 시 사용됨

  • 부서번호로 그룹 만들고 각 부서별 사원수, 부서별 총 급여 조회
SELECT  deptno   AS 부서번호,
        COUNT(*) AS 사원수,
        SUM(sal) AS 총급여
FROM emp
GROUP BY deptno;
  • 급여합 5000 초과하는 직무, 단 세일즈맨 제외
    job, 급여합 내림차순 정렬 조회
SELECT  job      AS 직무,
        SUM(sal) AS 총급여
FROM emp
GROUP BY job
HAVING SUM(sal) > 5000 AND NOT job = 'SALESMAN'
ORDER BY SUM(sal) DESC;

SELECT job, SUM(sal) AS 급여합
FROM emp
WHERE job != 'SALESMAN'
GROUP BY job
HAVING SUM(sal) > 5000
ORDER BY SUM(sal) DESC;

- 그룹함수 ROLLUP, CUBE

ROLLUP - 데이터를 그룹화하고, 각 그룹의 부모 그룹까지 포함한 집계 함수 결과 제공

CUBE - 그룹바이 절에 지정된 모든 컬럼의 모든 조합에 대한 정보 제공

SELECT deptno, job, COUNT(*), MAX(sal), MIN(sal)
FROM emp
GROUP BY ROLLUP(deptno, job)
ORDER BY deptno;
// 부서번호 10전체, 20전체, 30전체, 전체부서 급여정보 출력

SELECT deptno, job, COUNT(*), MAX(sal), MIN(sal)
FROM emp
GROUP BY CUBE(deptno, job)
ORDER BY deptno;
// 부서번호 10전체, 20전체, 30전체,
// 직무 Clerk전체, Analyst전체, Manager전체,
// 전체 부서의 총 급여정보 출력

- 그룹함수 LISTAGG

SELECT deptno, 
       LISTAGG(ename,',') WITHIN GROUP(ORDER BY sal DESC) AS enames
FROM emp
GROUP BY deptno;

- 조인

관계형 데이터베이스에서 둘 이상의 테이블 간에 연결을 만들어 주는 기능

두 테이블 간의 관계를 활용하여 하나의 결과 집합으로 데이터를 검색하거나 조작할 수 있음

SELECT *
FROM emp, dept
WHERE emp.deptno = dept.deptno;

SELECT e.*, d.*
FROM emp e, dept d
WHERE e.deptno = d.deptno;

SELECT e.*, d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno
ORDER BY empno;

외부 조인

OUTER - 등가조인에서 값이 없는 행 조회

  • (+)
SELECT *
FROM emp, dept
WHERE emp.deptno(+) = dept.deptno;

- SELF 조인

하나의 테이블을 두 번 사용하여 조인함

SELECT a.ename, b.ename AS MANAGER
FROM emp a, emp b
WHERE a.empno = b.mgr;

- 서브 쿼리

SELECT문 안에 SELECT문 사용

SELECT (SELECT)

SELECT e.ename, e.sal, d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno
AND e.sal > (SELECT AVG(sal) 
		     FROM emp);
   
SELECT *
FROM emp
WHERE sal > (SELECT sal
             FROM emp
             WHERE ename='JONES');

- 이름이 'FORD' 인 사원의 이름, 부서명 출력

SELECT e.ename AS 이름,
       d.dname AS 부서명
FROM emp e, dept d
WHERE e.deptno = d.deptno
AND e.ename = (SELECT ename
               FROM emp
               WHERE ename = 'FORD');

2024-03-29

- Oracle SQL Developer

- CRUD

Create(생성), Read(읽기), Update(갱신), Delete(삭제)

Create

  • 새로운 데이터를 데이터베이스에 추가하는 작업
  • 보통 INSERT 문을 사용하여 새로운 레코드를 데이터베이스 테이블에 추가
  • ex) 사용자가 새로운 고객을 등록하거나 새로운 제품을 추가하는 경우가 이에 해당

Read

  • 데이터베이스에서 데이터를 검색하는 작업
  • 주로 SELECT 문을 사용하여 데이터베이스에서 특정 레코드 또는 레코드 그룹을 가져옴
  • ex) 사용자가 특정 고객의 정보를 검색하거나 모든 제품 목록을 확인하는 경우가 이에 해당

Update

  • 데이터베이스의 기존 데이터를 수정하는 작업
  • 보통 UPDATE 문을 사용하여 특정 레코드의 값을 업데이트함
  • ex) 사용자가 고객의 주소를 변경하거나 제품의 가격을 조정하는 경우가 이에 해당

Delete

  • 데이터베이스에서 데이터를 제거하는 작업
  • 보통 DELETE 문을 사용하여 특정 레코드를 데이터베이스에서 삭제함
  • ex) 사용자가 고객을 회원 목록에서 삭제하거나 더 이상 판매하지 않을 제품을 제거하는 경우가 이에 해당

- 테이블 생성

    create table 테이블(
        컬럼명 타입 옵션,
        컬럼명 타입 옵션,
        컬럼명 타입 옵션,
        ......
        컬럼명 타입 옵션
    );

타입

  • 선택아님 필수
  • 문자 : varchar2(문자의 길이)
  • 숫자 : number (정수 실수 모두 포함)
  • 날짜 : date

옵션

  • 필수아님 선택
      primary key	: 고유값, 컬럼값 중복 불가
            		  하나의 컬럼만 가능
      not null		: null값 불가능
      default		: 입력하지 않았을때 들어가는 기본값
      unique		: 중복값 불가
      check			: 조건문, 검사 같은 기능
         			: check age > 0 and age < 150;

CREATE TABLE member (
   id VARCHAR2(50) PRIMARY KEY,    -- 아이디
   pw VARCHAR2(50) NOT NULL,       -- 비밀번호
   birth DATE,                     -- 생일
   age NUMBER DEFAULT 1,           -- 나이
   name VARCHAR2(50),              -- 이름
   reg DATE DEFAULT SYSDATE        -- 가입날짜
);
COMMIT;

- 데이터 추가 Insert

insert into 테이블명 values(값,값,값,...);

  • 테이블의 모든 컬럼에 값을 넣음.
    컬럼의 순서에 맞게, 컬럼의 타입에 맞게
    값을 넣어줘야 함
INSERT INTO member VALUES('java', '0000', '89/08/26', 24, 'oracle', SYSDATE);
INSERT INTO member VALUES('html', '1234', '90/01/01', 23, 'www', SYSDATE);
// name = null
INSERT INTO member VALUES('test', '5678', '91/01/01', 22, NULL, SYSDATE);
COMMIT;

  • 컬럼을 지정해서 insert
INSERT INTO member(id,pw) VALUES('spring','1111');
INSERT INTO member(id, pw, name) VALUES('jsp', '2222', 'aaaa');

insert into member(id) values('css');

  • pw - not null 반드시 추가

컬럼 옵션

  • not null
  • primary key
    반드시 추가해야 함
INSERT INTO member(id, pw, birth) VALUES('css', '3333', '92/03/03');
COMMIT;

- 수정 Update

UPDATE 테이블명 SET 컬럼명 = 변경값;

UPDATE 테이블명 SET 컬럼명 = 변경값, 컬럼명 = 변경값;

UPDATE member SET pw = '5555';
UPDATE member SET pw = '1234', name = 'guest', age = 20;

UPDATE 테이블명 SET 컬럼명 = 변경값 WHERE 조건식;

UPDATE member SET pw='0000' WHERE id = 'java';
UPDATE member SET pw='1212' WHERE birth > '91/01/01';
UPDATE member SET pw='1111' WHERE birth = '91/01/01';
UPDATE member SET pw='2222' WHERE birth = '90/01/01';
UPDATE member SET pw='3333' WHERE id = 'jsp';

UPDATE member SET age=10 WHERE id='java';
UPDATE member SET age=22 WHERE birth > '91/01/01';
UPDATE member SET age=36 WHERE birth = '91/01/01';
UPDATE member SET age=43 WHERE birth = '90/01/01';
UPDATE member SET age=54 WHERE id = 'jsp';

UPDATE member SET name='aaa' WHERE id='java';
UPDATE member SET name='bbb' WHERE birth > '91/01/01';
UPDATE member SET name='ccc' WHERE birth = '91/01/01';
UPDATE member SET name='ddd' WHERE birth = '90/01/01';
UPDATE member SET name='eee' WHERE id = 'jsp';

- 삭제 DELETE

DELETE FROM 테이블명;

DELETE FROM member;

delete from 테이블명 WHERE 조건식;

DELETE FROM member WHERE id = 'java';
SELECT * FROM member;
COMMIT;

- 테이블 삭제 DROP

DROP TABLE 테이블명;

DROP TABLE member;
COMMIT;

2024-04-01

- DBMS

데이터 베이스 관리 시스템

- SQL

구조화된 질의 언어

데이터베이스의 생성, 레코드 검색 등의 작업을 수행할 때 사용됨

정의문(DDL)

  • CREATE, ALTER, DROP

제어문(DCL)

  • GRANT, REOKE

조작문(DML)

  • UPDATE, INSERT, DELETE

쿼리

  • SELECT

트랜잭션처리

  • COMMIT, ROLLBACK
    JSP와의 연동을 위한 테이블 생성, 삭제, 레코드 삽입/수정/삭제/검색 위주로 사용함

- SQL Developer

명령문 대소문자 구별 안함 -> 대부분 소문자 사용

보관되는 값은 대소문자 구별

줄바꿈 해도 줄글로 들어가기 때문에 반드시 ;(세미콜론)으로 끝을 내야함

문자는 무조건 ''(홑따옴표) 사용

,(쉼표)로 나열하고 마지막은 쓰지않음

CRUD 후 반드시 COMMIT; 실행

실행 : 초록색 재생버튼 / Ctrl + 엔터

- 트랜잭션

데이터베이스 작업의 단위

하나의 트랜잭션은 여러 SQL 문장으로 구성될 수 있음

COMMIT

  • 트랜잭션을 완료하고, 변경사항을 저장함

ROLLBACK

  • 트랜잭션을 취소하고, 변경사항을 되돌림

INSERT, UPDATE, DELETE 쿼리 실행 후 COMMIT;

  • 추가완료, 수정완료, 삭제완료 이며, COMMIT; 후에는 ROLLBACK으로 되돌아 갈 수 없음

INSERT, UPDATE, DELETE 쿼리 실행 후 ROLLBACK;

  • 이전 상태로 되돌아감

- 테이블 추가

CREATE TABLE test (
    num NUMBER PRIMARY KEY,
    name VARCHAR2(100),
    reg DATE DEFAULT SYSDATE
);
COMMIT;

- 테이블에 컬럼 추가

ALTER TABLE 테이블명 ADD(컬럼명 타입 옵션);

NOT NULL, PRIMARY KEY 사용 못함

컬럼의 순서 변경 불가, 맨 마지막 순서에 추가

테이블에 레코드(데이터)가 있을 때, 옵션 사용 불가능

SELECT *
FROM test;

ALTER TABLE test ADD(age NUMBER DEFAULT 1);

SELECT *
FROM test;

INSERT INTO test VALUES(1, 'java', SYSDATE, '10');
COMMIT;

- 테이블 컬럼 이름 변경

ALTER TABLE 테이블명 RENAME COLUMN 컬럼명 TO 변경명;

ALTER TABLE test RENAME COLUMN name TO nick;

SELECT *
FROM test;

- 테이블 컬럼 삭제

ALTER TABLE 테이블명 DROP COLUMN 컬럼명

ALTER TABLE test DROP COLUMN nick;

- DB의 계정과 테이블 확인

계정에 생성된 모든 테이블 확인

SELECT TABLE_NAME
FROM USER_TABLES;

- DB의 모든 계정 테이블 확인

SELECT OWNER, TABLE_NAME
FROM ALL_TABLES;

- DBA의 모든 계정 테이블 상세정보 확인

SELECT *
FROM DBA_TABLES;

- DBA의 모든 계정 확인

SELECT *
FROM DBA_USERS;

- DB의 'SCOTT' 계정 확인

SELECT *
FROM DBA_USERS
WHERE USERNAME = 'SCOTT';

인덱스

- 인덱스

오라클 인덱스는 테이블의 특정 컬럼(열)에 대한 검색 속도를 향상시키는 데이터 구조

책의 목차와 비슷하게 인덱스는 데이터베이스 검색엔진이 특정 값을 지닌 행을 빠르게 찾을 수 있도록 도와줌

테이블에 인덱스를 적용시켜 놓으면

WHERE, JOIN, ORDER BY, GROUP BY 에서 인덱스가 적용된 컬럼을 사용시, 검색 속도가 향상됨

- 현재 계정의 모든 인덱스에 대한 정보 조회

SELECT *
FROM USER_INDEXES;

- 현재 계정의 모든 인덱스에 속한 컬럼 정보 조회

SELECT *
FROM USER_IND_COLUMNS;      -- INDEX 줄임말

- IND_EMP_SAL

SAL 컬럼에 EMP 테이블 인덱스 생성

CREATE INDEX IND_EMP_SAL ON emp(sal);

- 인덱스 삭제

DROP INDEX IND_EMP_SAL;

- 뷰(view)

오라클 뷰는 실제 테이블에 저장되지 않는 가상의 테이블

실제 테이블처럼 사용될 수 있으며, 데이터 추가, 수정, 삭제 등을 제외한 대부분의 SQL 작업을 수행할 수 있음

SELECT로 자주 검색하는 테이블의 필요한 컬럼 및 조건으로 검색된 것을 뷰로 만들고 사용

장점

  • 데이터 통합
    여러 테이블의 데이터를 하나의 뷰로 결합할 수 있음
  • 데이터 추상화
    복잡한 쿼리를 간단하게 표현할 수 있음
  • 데이터 보안
    민감한 데이터를 숨길 수 있음
  • 데이터 접근 제어
    사용자에게 특정 데이터만 제공할 수 있음

- vw_emp20 이름으로 뷰 생성

CREATE VIEW vw_emp20 AS
(SELECT empno, ename, job, deptno
FROM emp
WHERE deptno = 20);

- 계정의 모든 뷰 확인

SELECT *
FROM USER_VIEWS;

- 뷰의 특정 정보만 확인

SELECT VIEW_NAME, TEXT_LENGTH, TEXT
FROM USER_VIEWS;

- 뷰 검색 - 테이블 검색과 같음

SELECT *
FROM vw_emp20;

- 뷰 삭제

DROP VIEW vw_emp20;

- ROWNUM

쿼리 결과에서 각 행의 순번을 나타내는 가상의 컬럼

SELECT empno, ename, sal
FROM emp;

SELECT empno, ename, sal, ROWNUM
FROM emp;

ROWNUM 순번이 뒤죽박죽 된 것을 확인할 수 있음

ROWNUM이 적용된 후 정렬이 진행되기 때문

SELECT empno, ename, sal, ROWNUM
FROM emp
ORDER BY sal;

SELECT e.*, ROWNUM
FROM (
    SELECT empno, ename, sal
    FROM emp
    ORDER BY sal
) e;
  • 1~5 까지 결과나오도록
SELECT *
FROM (
    SELECT e.*, ROWNUM AS r
    FROM (
        SELECT empno, ename, sal
        FROM emp
        ORDER BY sal
    ) e)
WHERE r >= 1 AND r <= 5;

- 시퀀스

자동으로 증가하는 일련번호를 생성하는 객체

테이블의 특정 컬럼에 유일한 값을 지정하기 위해 사용

이름을 마음대로 지어도 되지만, 테이블명_SEQ 라고 유추 가능하게 지어줌

  • 테이블 생성
CREATE TABLE test (
    num NUMBER PRIMARY KEY,
    name VARCHAR2(100)
);
  • CREATE SEQUENCE 시퀀스명 옵션;
CREATE SEQUENCE test_seq;

INSERT INTO test VALUES(test_seq.NEXTVAL,'test01');
INSERT INTO test VALUES(test_seq.NEXTVAL,'test02');
INSERT INTO test VALUES(test_seq.NEXTVAL,'test03');

SELECT *
FROM test;

COMMIT;

- 시퀀스 옵션

START WITH

  • 시작값 (기본값 1)

INCREAMENT BY

  • 증가값 (기본값 1)

MAXVALUE

  • 최대 증가값(~까지)

NOMAXVALUE

  • 기본값(최대값 없음)

MINVALUE

  • 기본값 1
  • 최소값 / 최대값 설정시 최대값까지 증가하면 다시 최소값부터 시작됨

CYCLE

  • 기본값 NOCYCLE

NOCYCLE

  • 반복 적용 안함

CACHE

  • 기본값 20
  • 미리 만들어놓은 값
  • 사용할 때마다 제공, 사용되지 못한 값은 폐기

NOCACHE

  • 기본값 CACHE 20 / NOCACHE로 설정 가능
CREATE SEQUENCE test1_seq
    START WITH 10
    INCREMENT BY 2
    MAXVALUE 100
    MINVALUE 1
    CYCLE
    NOCACHE;

JDBC

- Connection

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<h1>/0401/connection.jsp</h1>

<%
// 1. 임포트
// 2. JDBC 드라이버 로딩
Class.forName("oracle.jdbc.driver.OracleDriver");
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
// 슬래시(/) 도 사용 가능. 1521 : 디벨로퍼 포트번호
String user="scott";
String password="tiger";
// 3. conn 객체 생성
Connection conn = DriverManager.getConnection(url, user, password);
// 4. 쿼리문 작성
String sql = "select * from emp";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 5. 쿼리문 실행
// 두 가지 방법
// pstmt.executeQuery();	// select문
							// 결과 있음 - return타입 ResultSet
// pstmt.executeUpdate();	// insert, update, delete
							// 결과 없음 - return타입 int
// 이 때 결과는 레코드 한 줄씩 나옴

ResultSet rs = pstmt.executeQuery();
while (rs.next()) {			// 있으면 true / 없으면 false : 레코드 (가로 한 줄)
	int empno = rs.getInt("empno");
	String ename = rs.getString("ename");
	Timestamp hiredate = rs.getTimestamp("hiredate");
	out.println("<h2>"+empno+" : "+ename+" : "+hiredate+"</h2>");
}
// 6. 연결 끊기
// 연결해서 이루어지는 경우 반드시 연결을 끊어줘야함
conn.close();
pstmt.close();
rs.close();
%>

- 사원 관리 홈페이지 만들기

  • empDTO (Data Transfer Object)
package web.test.bean;

import java.sql.Timestamp;

public class EmpDTO {			// Data Transfer Object
	private int empno;			// 사원 번호
	private String ename;		// 사원 이름
	private String job;			// 사원 업무
	private int mgr;			// 사원 상사
	private Timestamp hiredate;	// 입사 날짜
	private int sal;			// 사원 급여
	private int comm;			// 사원 보너스
	private int deptno;			// 부서 번호
	
	public void setEmpno(int empno) {
		this.empno = empno;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public void setJob(String job) {
		this.job = job;
	}
	public void setMgr(int mgr) {
		this.mgr = mgr;
	}
	public void setHiredate(Timestamp hiredate) {
		this.hiredate = hiredate;
	}
	public void setSal(int sal) {
		this.sal = sal;
	}
	public void setComm(int comm) {
		this.comm = comm;
	}
	public void setDeptno(int deptno) {
		this.deptno = deptno;
	}
//	-----
	public int getEmpno() {
		return empno;
	}
	public String getEname() {
		return ename;
	}
	public String getJob() {
		return job;
	}
	public int getMgr() {
		return mgr;
	}
	public Timestamp getHiredate() {
		return hiredate;
	}
	public int getSal() {
		return sal;
	}
	public int getComm() {
		return comm;
	}
	public int getDeptno() {
		return deptno;
	}
}

  • empForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>/0401/empForm.jsp</h1>

<form action="empInsert.jsp" method="post">
	사원번호 : <input type="text" name="empno"		/>	<br />
	사원이름 : <input type="text" name="ename"		/>	<br />
	사원업무 : <input type="text" name="job"			/>	<br />
	사원상사 : <input type="text" name="mgr"			/>	<br />
	사원급여 : <input type="text" name="sal"			/>	<br />
	보너스   : <input type="text" name="comm"		/>	<br />
	부서번호 : <input type="text" name="deptno"		/>	<br />
			   <input type="submit" value="사원등록"	/>
</form>

  • empInsert.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<h1>/0401/empInsert.jsp</h1>
<jsp:useBean id="empDTO" class="web.test.bean.EmpDTO"	/>
<jsp:setProperty name="empDTO" property="*"				/>

<%
// 	JDBC 드라이버 로딩
	Class.forName("oracle.jdbc.driver.OracleDriver");
	String url = "jdbc:oracle:thin:@localhost:1521:orcl";
	String user="scott";
	String password="tiger";
	
// 	conn 객체 생성
	Connection conn = DriverManager.getConnection(url, user, password);
	
// 	쿼리문 작성
	String sql = "insert into emp values(?,?,?,?,sysdate,?,?,?)";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt	(1, empDTO.getEmpno());
	pstmt.setString	(2, empDTO.getEname());
	pstmt.setString	(3, empDTO.getJob());
	pstmt.setInt	(4, empDTO.getMgr());
	pstmt.setInt	(5, empDTO.getSal());
	pstmt.setInt	(6, empDTO.getComm());
	pstmt.setInt	(7, empDTO.getDeptno());
	
	int result = pstmt.executeUpdate();
	
// 	연결 끊기
	conn.close();
	pstmt.close();
%>
<h2><%= result %> 행이 추가되었습니다.</h2>

  • empSearch.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>/0401/empSearch</h1>

<form action="empSelect.jsp" method="post">
	사원번호 :	<input type="text" name="empno"			/>	<br />
				<input type="submit" value="사원찾기"	/>
</form>

  • empSelect.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<h1>/0401/empSelect.jsp</h1>
<jsp:useBean id="empDTO" class="web.test.bean.EmpDTO"	/>
<jsp:setProperty name="empDTO" property="*"				/>

<%
// 	JDBC 드라이버 로딩
	Class.forName("oracle.jdbc.driver.OracleDriver");
	String url="jdbc:oracle:thin:@localhost:1521:orcl";
	String user="scott";
	String password="tiger";
	
// 	conn 객체 생성
	Connection conn = DriverManager.getConnection(url, user, password);
	
// 	쿼리문 작성
	String sql = "select * from emp where empno =?";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, empDTO.getEmpno());
	
	ResultSet rs = pstmt.executeQuery();
	
	if (rs.next()) {
		int empno = rs.getInt("empno");
		String ename = rs.getString("ename");
		String job = rs.getString("job");
		int mgr = rs.getInt("mgr");
		Timestamp hiredate = rs.getTimestamp("hiredate");
		int sal = rs.getInt("sal");
		int comm = rs.getInt("comm");
		int deptno = rs.getInt("deptno");
%>
	<h2>사원번호 :	<%= empno	 %></h2>
	<h2>사원이름 :	<%= ename  	 %></h2>
	<h2>사원업무 :	<%= job		 %></h2>
	<h2>사원상사 :	<%= mgr 	 %></h2>
	<h2>입사일자 :	<%= hiredate %></h2>
	<h2>사원급여 :	<%= sal 	 %></h2>
	<h2>보너스 :	<%= comm 	 %></h2>
	<h2>부서번호 :	<%= deptno 	 %></h2>
<%	}else {
		out.println("<h2>사원번호를 확인해주세요.</h2>");
	}

// 	연결 끊기
	conn.close();
	pstmt.close();
	rs.close();
%>

  • empUpdateForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>/0401/empUpdateForm.jsp</h1>

<form action="empUpdate.jsp" method="post">
	사원 번호	: <input type="text" name="empno"	/>	<br />
	업무 변경	: <input type="text" name="job"		/>	<br />
	급여 변경	: <input type="text" name="sal"		/>	<br />
	보너스 변경	: <input type="text" name="comm"	/>	<br />
				  <input type="submit" value="정보 변경"	/>
	
</form>

  • empUpdate.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<h1>/0401/empUpdate.jsp</h1>
<jsp:useBean id="empDTO" class="web.test.bean.EmpDTO"	/>
<jsp:setProperty name="empDTO" property="*"				/>

<%
	Class.forName("oracle.jdbc.driver.OracleDriver");
	String url="jdbc:oracle:thin:@localhost:1521:orcl";
	String user="scott";
	String password="tiger";
	
// 	객체 생성
	Connection conn = DriverManager.getConnection(url, user, password);

// 	쿼리문 작성	
	String sql = "UPDATE emp SET job = ?, sal = ?, comm = ? WHERE empno = ?";

	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setString(1, empDTO.getJob());
	pstmt.setInt(2, empDTO.getSal());
	pstmt.setInt(3, empDTO.getComm());
	pstmt.setInt(4, empDTO.getEmpno());
	
// 	쿼리문 실행
	int result = pstmt.executeUpdate();
	
// 	연결 끊기
	conn.close();
	pstmt.close();
%>
<h2><%= result %>행이 수정되었습니다.</h2>

  • empDeleteForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>/0401/empDeleteForm.jsp</h1>

<form action="empDelete.jsp" method="post">
	사원 번호 :	<input type="text" name="empno" 		/>	<br />
				<input type="submit" value="정보 삭제"	/>
</form>

  • empDelete.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<h1>/0401/empDelete.jsp</h1>
<jsp:useBean id="empDTO" class="web.test.bean.EmpDTO"	/>
<jsp:setProperty name="empDTO" property="*"				/>

<%
	Class.forName("oracle.jdbc.driver.OracleDriver");
	String url="jdbc:oracle:thin:@localhost:1521:orcl";
	String user="scott";
	String password="tiger";
	
	Connection conn = DriverManager.getConnection(url, user, password);
	
	String sql = "DELETE FROM emp WHERE empno = ?";
	
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, empDTO.getEmpno());
	
	int result = pstmt.executeUpdate();
	
	conn.close();
	pstmt.close();
	
%>
<h2><%= result %>행이 삭제되었습니다.</h2>

2024-05-21

Spring Boot

- 스프링 부트란?

  • 웹 프로그램(웹 애플리케이션)을 쉽고 빠르게 만들 수 있도록 도와주는 자바의 웹 프레임워크

  • 스프링 부트는 스프링(Spring) 프레임워크에 톰캣(Tomcat)이라는 서버를 내장하고, 여러 편의 기능들을 추가하여 개발자들 사이에서 꾸준히 인기를 누리고 있음

- 웹 프레임워크란?

  • 웹 프로그램을 완성하려면 쿠키나 세션 처리, 로그인/로그아웃 처리, 권한 처리, 데이터베이스 처리 등 만들어야 할 기능이 많음

  • 하지만 웹 프레임워크를 사용하면 이런 기능을 일일이 만들 필요가 없음. 웹 프레임워크에는 그런 기능들이 이미 만들어져 있기 때문

  • 쉽게 말해 웹 프레임워크는 웹 프로그램을 만들기 위한 스타터 키트

  • 자바로 만든 웹 프레임워크 중 하나가 바로 스프링 부트

  • 스프링 부트의 몇 가지 규칙만 익히면 기존에 자바로 웹 프로그램을 작성하는 방식보다 빠르게 웹 프로그램을 만들 수 있음

  • 크롬이나 사파리와 같은 웹 브라우저에 ‘Hello World’를 출력하려면 다음과 같은 클래스 하나만 작성하면 됨

@Controller
public class HelloController {
	@GetMapping("/")
    @ResponseBody
    public String hello(){
    	return "Hello World"
    }
}

- 스프링 부트를 배워야 하는 이유

스프링 부트는 튼튼한 웹 프레임워크이다

  • 개발자가 웹 프로그램을 만들 때 어렵게 느끼는 기능 중 하나는 바로 보안 기능
  • 웹 사이트를 괴롭히는 사람의 공격에 개발자 홀로 신속하게 대응하기는 무척 어려운 일
  • 스프링 부트가 이런 보안 공격을 기본으로 아주 잘 막아줌
  • 예를 들어 SQL 인젝션, XSS(cross-site scripting), CSRF(Cross-Site Request Forgery),
    클릭재킹(clickjacking)과 같은 보안 공격을 막음
  • 스프링 부트를 사용하면 이런 보안 공격을 막아 주는 코드를 짤 필요가 없음

스프링 부트에는 여러 기능이 준비되어 있다

  • 스프링 부트는 2012년에 등장하여 10년 이상의 세월을 감내한 ‘베테랑’ 웹 프레임워크
  • 무수히 많은 기능이 추가되고 또 다듬어져
    스프링 부트에는 웹 프로그램을 개발하는 데 필요한 도구와 기능이 대부분 준비됨

스프링 부트는 WAS가 필요없다

  • 스프링 부트 대신 스프링만 사용하여 웹 애플리케이션을 개발한다면
    실행할 수 있는 톰캣과 같은 WAS(Web Application Server)가 필요함
  • WAS의 종류는 매우 다양하며 설정 방식도 제각각이어서 공부해야 할 내용도 상당함
  • 하지만 스프링 부트에는 톰캣 서버가 내장되어 있고 설정도 자동 적용되기 때문에
    WAS에 대해서 전혀 신경 쓸 필요가 없음
  • 심지어 배포되는 jar 파일에도 톰캣 서버가 내장되어 실행되므로
    서로 다른 WAS들로 인해 발생되는 문제들도 사라짐

- 스프링 부트 장점

스프링 부트는 설정이 쉽다

  • 스프링 부트가 등장하기 전 개발자들은 스프링을 사용하여 웹 애플리케이션을 개발했는데
    스프링의 복잡한 설정 때문에 개발자들은 많은 어려움을 겪음
    스프링 부트는 스프링의 복잡한 설정을 자동화·단순화함

Junit

- JUnit 이란?

  • 자바 개발자가 가장 많이 사용하는 테스팅 기반 프레임워크
  • 현재 가장 많이 사용되고 있는 버전은 JUnit 5
  • 기본 Junit 4에 비해 JUnit 5는 3가지 모듈로 구성
  • JUnit5는 테스트 작성자를 위한 API 모듈과 테스트 실행을 위한 API가 분리되어 있음
  • 자바 8 이상을 필요로 함
  • Java 에서 독립된 단위테스트를 지원해주는 프레임워크

- 단위 테스트(Unit Test)란?

  • 테스트 대상 단위가 엄격하게 정해져있지는 않지만, 일반적으로 클래스 또는 메소드 수준으로 정해짐
  • 단위의 크기가 작을수록 단위의 복잡성이 낮아지고, 실행하려는 기능을 표현하기 더 쉬워짐
  • 특정 소스코드의 모듈이 의도한 대로 잘 작동하는지 검증하는 테스트입니다.
  • 함수 및 메소드에 대한 테스트를 하는 작업으로써, Spring에서 단위테스트는 Spring Container에 올라와 있는 Bean 을 테스트 하는 것

- 단위 테스트의 장점

  • 단위 테스트는 해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 파악할 수 있음
  • 개발자는 작성한 테스트 코드를 수시로 빠르게 돌리면서 문제점을 파악할 수 있음
  • 단위 테스트를 진행하는 동안에는 실제 어플리케이션을 동작시킬 때와 마찬가지로 Bean을 테스트 할 수 있음

- Test (Question, Answer)

  • build.gradle
  • 프로젝트 의존성 추가
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'		// 저장하면 java 파일 서버 재실행 하지 않아도 됨
	compileOnly 'org.projectlombok:lombok'								// 컴파일 시에만 필요한 라이브러리를 지정할 때 사용됨
	annotationProcessor 'org.projectlombok:lombok'						// 언노테이션 프로세싱을 위한 라이브러리를 지정할 때 사용됨
	runtimeOnly 'com.h2database:h2'										// runtime 시에만 라이브러리를 실행하겠다
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'	// JAP
	testImplementation 'org.junit.jupiter:junit-jupiter'					// Junit
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

  • application.properties
  • H2 Database 연결
spring.application.name=sbp
# DATABASE
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:tcp://localhost/~/local
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=uc
spring.datasource.password=
# JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

  • Question 클래스 생성
package com.example.sbp.question;

import java.time.LocalDateTime;
import java.util.List;

import com.example.sbp.answer.Answer;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Question {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)	// 별도로 고유번호를 정해줌. 1씩 자동 증가함
	private Integer id;

	@Column(length=200)						// 200자 length : 열의 '길이'
	private String subject;

	@Column(columnDefinition="TEXT")		// 문자열 데이터 저장. 글자 제한이 없음
	private String content;
   
	private LocalDateTime createDate;
	
	@OneToMany(mappedBy="question", cascade=CascadeType.REMOVE)
	private List<Answer> answerList;		// 답변 리스트
	
}

  • QuestionRepository 인터페이스 생성
package com.example.sbp.question;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer> {
	
//	Junit 테스트
	Question findBySubject(String subject);
	
//	subject 와 content 두 개의 엔티티 속성(컬럼) 조회하기 위한 메서드
	Question findBySubjectAndContent(String subject, String content);
	
//	like 포함하는게 하나가 아닐수도있으므로 List 로 받음
	List<Question> findBySubjectLike(String subject);
}

  • Answer 클래스 생성
package com.example.sbp.answer;

import java.time.LocalDateTime;

import com.example.sbp.question.Question;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Answer {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer id;
	
	@Column(columnDefinition="TEXT")
	private String content;
	
	private LocalDateTime createDate;		// 카멜표기법 -> DB 에서는 스네이크 표기법으로 바뀜 create_date
	
//	질문 엔티티를 참조하기 위해 question 속성을 추가함
	@ManyToOne
	private Question question;
}
/*
	여러 답변이(Many) 하나의(One) 질문에 달림
		N	:	1
	@ManyToOne
	-----------------------------------
	질문(One)	답변(Many)
		1	:	N
	@OnetoMany
	
*/

  • AnswerRepository 인터페이스 생성
package com.example.sbp.answer;

import org.springframework.data.jpa.repository.JpaRepository;

public interface AnswerRepository extends JpaRepository<Answer, Integer> {
	
}

  • Test 시작
package com.example.sbp;

import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.sbp.question.Question;
import com.example.sbp.question.QuestionRepository;


@SpringBootTest		// 테스트 클래스라는걸 알려줌
class SbpApplicationTests {
	
	@Autowired		// 자동 연결 / 의존성 주입(DI - Dependency Injection) / 스프링이 객체를 대신 생성하여 주입하는 방식
	private QuestionRepository questionRepository;
    
    @Autowired
	private AnswerRepository answerRepository;
    
    @Test			// 메서드는 여러개 생성 가능하지만, 어노테이션은 1개만 사용가능하다.
	void testJpa() {							// 첫 번째 행. 첫 번째 레코드
		Question q1 = new Question();
		q1.setSubject("sbp가 무엇인가요?");
		q1.setContent("sbp에 대해서 알고싶습니다.");
		q1.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q1);
		
		Question q2 = new Question();			// 두 번째 행. 두 번째 레코드
		q2.setSubject("스프링부트 모델 질문입니다!");
		q2.setContent("id는 자동으로 생성되나요?");
		q2.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q2);
	}

//	----------
//	@Test
	void testJpa() {
		List<Question> all = this.questionRepository.findAll();		// 모든 데이터 조회하는 메서드
		assertEquals(2, all.size());								// 전체 개수, 리스트 사이즈 같아야함
		
		Question q = all.get(0);
		assertEquals("sbp가 무엇인가요?", q.getSubject());
//		assertEquals(기댓값, 실제값);
//		테스트에서 예상한 결과와 실제 데이터가 동일한지 확인하는 목적
//		JPA 또는 데이터베이스에서 데이터를 올바르게 가져오는지 확인
	}

//	----------
//	@Test
	void testJpa() {
		Optional<Question> oq = this.questionRepository.findById(1);	// 리턴타입 Optional
		if (oq.isPresent()) {											// Optional : null 값이면 유연하게 처리하기 위해 사용
			Question q = oq.get();
			assertEquals("sbp가 무엇인가요?", q.getSubject());
		}
	}
    
//	----------
//	@Test
	void testJpa() {
		Question q = this.questionRepository.findBySubject("sbp가 무엇인가요?");	// 리포지토리 에 선언한 메서드를 사용할 수 있다. JPA의 기능
		assertEquals(1, q.getId());
	}
    
//	----------
//	@Test
    void testJpa() {
		Question q = this.questionRepository.findBySubjectAndContent("sbp가 무엇인가요?", "sbp에 대해서 알고싶습니다.");
		assertEquals(1, q.getId());
	}
    
//	----------
//	@Test
	void testJpa() {
		List<Question> qList = this.questionRepository.findBySubjectLike("sbp%");
		Question q = qList.get(0);
		assertEquals("sbp가 무엇인가요?", q.getSubject());
//		aaa% : 시작 / %aaa : 끝 / %aaa% 사이
//		조건절 LIKE를 사용하여 데이터에 '%'가 포함된 데이터를 찾으려고 한다.
//		하지만 LIKE문에서 '%'는 실제문자 '%'가 아닌, 조건검색 기능에 해당하는 문자로 쓰이게된다.
	}
    
//	----------
//	@Test
	void testJpa() {			// 수정
		Optional<Question> oq = this.questionRepository.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		q.setSubject("수정된 제목");
		this.questionRepository.save(q);
	}
    
//	----------
//	@Test
	void testJpa() {			// 데이터 삭제
		assertEquals(2, this.questionRepository.count());
		Optional<Question> oq = this.questionRepository.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		this.questionRepository.delete(q);
		assertEquals(1, this.questionRepository.count());		// 삭제 후 레코드 개수 1개됨 -> 1로 비교
	}

//	----------
//	@Test
	void testJpa() {
		Optional<Question> oq = this.questionRepository.findById(2);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		Answer a = new Answer();
		a.setContent("네, 자동으로 생성됩니다!");
		a.setQuestion(q);
		a.setCreateDate(LocalDateTime.now());
		this.answerRepository.save(a);
	}
    
//	----------
//	@Test
	void testJpa() {
		Optional<Answer> oa = this.answerRepository.findById(1);
		assertTrue(oa.isPresent());
		Answer a = oa.get();
		assertEquals(2, a.getQuestion().getId());
	}

//	----------
//	@Transactional	// 임포트 -> 스프링 프레임워크
//	@Test
	void testJpa() {
		Optional<Question> oq = this.questionRepository.findById(2);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		
//		지연(Lazy) 방식 <-> 즉시(Eager) 방식
		List<Answer> answerList = q.getAnswerList();
		
		assertEquals(1, answerList.size());
		assertEquals("네, 자동으로 생성됩니다!", answerList.get(0).getContent());
	}
}

2024-05-22

Thymeleaf(타임리프)

- Thymeleaf(타임리프)란?

  • 타임리프는 자바 라이브러리이며, 텍스트, HTML, XML, Javascript, CSS, 그리고 텍스트를 생성할 수 있는 템플릿 엔진
  • 스프링 MVC와의 통합 모듈을 제공하며, 애플리케이션에서 JSP로 만든 기능들을 완전히 대체할 수 있음
  • Spring Boot에서는 JSP가 아닌 Thymeleaf 사용을 권장
  • 스프링과 통합되어 있어, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원해줌

- 타임리프 태그 속성

  • 타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작
  • th:* 로 속성을 적용하면 기존 THML 속성을 대체하고, 기존 속성이 없으면 새로 만듬
<div th:text="th:내용">그냥 내용</div>
// th:내용 출력

Test

- messages.properties 생성

# messages.properties : fileName
content.id=spring boot
content.name=java
version=1.0
date=2000.01.01

- Member 클래스 생성

package com.example.ex;

import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Member {
	private String userName;
	private String pw;
	private String email;
	private String gender;
	private String country;
	private List<String> hobbies;
}

- ThymeleafController 생성

package com.example.ex;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import jakarta.servlet.http.HttpSession;

@Controller
@RequestMapping("/th/*")	// 기본적인(공통적인) URL을 지정해줌. http://localhost:8080/th/* -> 루트 주소
public class ThymeleafController {
	
	@GetMapping("main")
	public String main() {
		return "main";
	}
	
	@GetMapping("ex01")		// * 부분의 주소가 됨. http://localhost:8080/th/ex01
	public String ex01(Model model) {	// import->springframework.ui (2번째)
		model.addAttribute("message", "Hello, Thymeleaf");
		return "ex01";
	}
	
	@GetMapping("ex02")
	public String ex02(Model model) {
		model.addAttribute("message", "<h1>Hello Thymeleaf</h1>");
		model.addAttribute("htmlContent", "<h1>Hello Thymeleaf</h1>");
		return "ex02";
	}
	
	@GetMapping("ex03")
	public String ex03(Model model) {
		return "ex03";
	}
	
	@GetMapping("ex04")
	public String ex04(Model model) {
		model.addAttribute("date", new Date());		// java.util 패키지
		model.addAttribute("num", 100000);
		model.addAttribute("num2", 0.12345);
		model.addAttribute("message", "Hello Thymeleaf");
		
		List<String> list = new ArrayList<String>();
		list.add("sql");
		list.add("jsp");
		list.add("java");
		list.add("spring");
		model.addAttribute("list", list);
		
		return "ex04";
	}
	
	@GetMapping("ex05")
	public String ex05(Model model) {
		model.addAttribute("data", "main");
		return "ex05";
	}
	
	@GetMapping("ex06")
	public String ex06(Model model, HttpSession session) {
		User user = new User();
		user.setAge(20);
		user.setName("김자바");
		session.setAttribute("user", user);
		return "ex06";
	}
	
	@GetMapping("ex07")
	public String ex07() {
		
		return "ex07";
	}
	
	@GetMapping("ex08")
	public String ex08() {
		return "ex08";
	}
	
	@GetMapping("ex09")
	public String ex09(HttpSession session) {
		session.setAttribute("sid", "springboot");
		return "ex09";
/*
		스프링에서 session 사용 시
		메서드의 매개변수로 HttpSession 선언 해두면,
		스프링MVC 컨테이너가 세션을 자동으로 주입해준다.
		
		스프링 MVC에서 자동으로 주입해주는 객체
		- HttpServletRequest	: jsp에서 사용한 request
		- HttpServletResponse	: jsp에서 사용한 response
		- HttpSession			: 세션
		- Principal				: 시큐리티(security)에서 사용되는 인증된 사용자 정보
		- Model					: 컨트롤러에서 뷰로 데이터를 전달할 때 사용
		- ModelMap				: Model과 비슷한 역할
								  Map 인터페이스를 구현한 객체
		- MultipartFile			: 파일 업로드 처리
*/
	}
	
	@GetMapping("ex10")		// if문
	public String ex10(Model model) {
		model.addAttribute("age", 20);
		return "ex10";
	}
	
	@GetMapping("ex11")		// switch문
	public String ex11(Model model) {
		model.addAttribute("age", "20");
		return "ex11";
	}
	
	@GetMapping("ex12")
	public String ex12(Model model) {
		List<String> list = Arrays.asList("java", "jsp", "spring", "sql");	// 배열을 리스트로 받는 메서드
		model.addAttribute("list", list);
		
		List<User> userList = new ArrayList<User>();
		userList.add(new User("java", 20));
		userList.add(new User("jsp", 30));
		userList.add(new User("spring", 35));
		userList.add(new User("sql", 41));
		
		model.addAttribute("userList", userList);
		
		return "ex12";
	}
	
//	th:value 속성 활용
	@GetMapping("ex13")
	public String ex13(Model model) {
		model.addAttribute("age", "20");
		return "ex13";
	}
	
//	th:src 속성 활용
	@GetMapping("ex14")
	public String ex14(Model model) {
		model.addAttribute("image", "default.jpg");
		return "ex14";
	}
	
//	th:attr 속성 활용
	@GetMapping("ex15")
	public String ex15(Model model) {
		
		return "ex15";
	}
	
	@GetMapping("memberForm")
	public String memberForm() {
		return "memberForm";
	}
	
	@PostMapping("memberPro")
	public String memberPro(Member member, Model model) {
		model.addAttribute("message", "회원가입이 완료되었습니다.");
		model.addAttribute("member", member);
		return "memberPro";
	}
}

- templates/ html 파일 생성

  • HTML 파일로, 동적 웹 페이지를 생성하기 위해 사용됨
  • ex01
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<p>내용</p>
	<p th:text="${message}">태그의 내용은 작성해도 적용되지 않는다.</p>
	<p th:text="${message}"></p>
</body>
</html>

  • ex02
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div th:text="${message}"></div>
	<div th:utext="${htmlContent}"></div>		<!-- <h1>태그 적용됨 -->
<!-- 	th:text : 전달받은 message 속성에 html 태그를 포함해도 그대로 출력됨	-->
<!-- 	th:utext : 전달받은 htmlContent 속성에 html 태그가 포함되면 적용됨 	-->

	타임리프의 표현식
		- #{} : 메세지 표현식
			messages.properties 파일에 정의된 속성을 사용, 다국어 처리
			-> JSP에서의 I18N : JSTML에서 fmt 태그와 같다.
		- ${} : 변수 표현식
			모델 속성에 접근하는데 사용
			-> JSP에서 사용하는 표현언어 ${}와 동일하다.
		- ${#} : 기본 객체 표현식
			ex) ${#list.size()} 타임리프에 내장된 기본객체 list 를 사용한 것
		- @{} : 링크 표현식
			URL을 생성하는데에 사용한다.
			ex) <a th:href="@{/main}"></a>
				<form th:action="@{/main/image/list}"></form>
		- *{} : 선택변수 표현식
			현재 객체의 내부 변수(속성)에 접근하는데 사용. 주로 반복문에서 사용된다.
		- ~{} : 프래그먼트 표현식
			다른 html을 포함할 때 사용한다.
			-> JSP에서 include와 같은 기능 (부분, 전체 가능)
</body>
</html>

  • ex03
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	1. 메세지 표현식
	<div th:text="#{content.id}"></div>
	<div th:text="#{content.name}"></div>
	<div th:text="#{version}"></div>
	<div th:text="#{date}"></div>
	- 파일 이름은 messages 로 기본값이 정해져 있다.
</body>
</html>

  • ex04
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	- 적용된 언어 확인
	<div th:text="${#locale}"></div>
	- messages.properties 파일의 속성을 사용
	<div th:text="${#messages.msg('version')}"></div>
	- Date 클래스 활용
	<div th:text="${#dates.format(date)}"></div>
	<div th:text="${#dates.format(date, 'dd/MMM/yyy HH:mm')}"></div>
	- Calendar 클래스를 활용
	<div th:text="${#calendars.day(date)}"></div>
	- 숫자 형식 적용 ( ,3) 최소 3자릿수 표현. 3자릿수 이하이면 0으로 표시. ex) 12 -> 012 표현
	<div th:text="${#numbers.formatInteger(num, 3)}"></div>
	- ( , 3, 2) : 정수 최소 3자리, 소수점 2자리 표현 (반올림 적용)
	<div th:text="${#numbers.formatPercent(num2, 3, 2)}"></div>
	- 자바 string 클래스 (0포함 5미만)
	<div th:text="${#strings.substring(message, 0, 5)}"></div>
	- util 패키지의 List, Set, Map 존재
	<div th:text="${#lists.size(list)}"></div>
	- 오름차순 정렬
	<div th:text="${#lists.sort(list)}"></div>
</body>
</html>

  • ex05
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	링크 표현식
	<a href="http://naver.com">naver</a>
	- 다른 사이트로 이동 시 사용
	<p><a th:href="@{http://naver.com}" th:text="naver"></a></p>
	-기본 URL 설정 방법
	<p><a th:href="@{/th/main}" th:text="main"></a></p>
	- URL에 변수 표현식 사용 시에 반드시 앞,뒤로 파이프바(|)로 감싸줘야 한다. / 변수 앞,뒤x 링크표현식 앞,뒤o
	<p><a th:href="@{|/th/${data}|}" th:text="main"></a></p>
	- URL에 파라미터를 설정하는 기본 방식
	<p><a th:href="@{main?id=java&pw=1234}" th:text="main"></a></p>
	- URL의 {kkk} 부분에 대입 할 변수를 처리하는 방식
	<p><a th:href="@{/th/{kkk}(kkk=${data})}" th:text="main"></a></p>
	- URL의 {kkk} 부분이 (kkk=${data})으로 값이 대입되고, id=${'java'} 부분이 파라미터로 적용된다.
	<p><a th:href="@{/th/{kkk}(kkk=${data}, id=${'java'})}" th:text="main"></a></p>
	<p><a th:href="@{/{k1}/{k2}(k1=${'th'}, k2=${data}, id=${'java'})}" th:text="main"></a></p>
</body>
</html>

  • ex06
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	- th:object
		타임리프의 변수 선언 속성
		적용된 태그 내부에서만 사용가능
	- th:object="${session.user}"
		div 태그 내에서만 사용 가능한 지역변수로 session에 저장된 user를 지역변수로 선언함
	- th:text="*{name}"
		변수로 저장된 user의 getName() 메서드 사용
	- th:text="*{age}"
		변수로 저장된 user의 getAge() 메서드 사용
	
	<h2>선택 변수 표현식</h2>
	<div th:object="${session.user}">
		<p>Name : <span th:text="*{name}"></span></p>
		<p>Age : <span th:text="*{age}"></span></p>
	</div>
	
	<h2>변수 표현식</h2>
	<div>
		<p>Name : <span th:text="${session.user.name}"></span></p>
		<p>Age : <span th:text="${session.user.age}"></span></p>
	</div>
</body>
</html>

  • ex07
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>프래그먼트 표현식</h2>
	<div th:insert="~{main.html}"></div> 
	<div th:replace="~{main.html}"></div>
	
	<div th:insert="~{sub.html :: title}"></div>
	<div th:insert="~{sub.html :: content}"></div>
	<div th:insert="~{sub.html :: info('김자바', '010-1111-2222')}"></div>
	<div th:insert="~{sub.html :: info('이자바', '010-3333-4444')}"></div>
	
	- th:insert
		태그 내부에 삽입 시킨다.
		<div>
			main.html
		</div>
		
	- th:replace
		div 태그 대신 main.html로 대체된다.
			<h3>Hello Thymeleaf</h3>
			
	- th:insert="~{sub.html :: title}"
		sub.html의 th:fragment="title"에 설정된
		내용(제목입니다.)을 합친다.
		
	- th:insert="~{sub.html :: info('김자바', '010-1111-2222')}"
		sub.html의
		th:fragment="info(name, tel)" 에
		설정된 메서드의 매개변수로 보내고 합친다.
</body>
</html>

  • ex08
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>파라미터 사용</h1>
<!--	http://localhost:8080/th/ex08?name=java&age=20 주소창에 표시될 내용	-->
	<h1 th:text="${param.name}"></h1>
	<h1 th:text="${param.age}"></h1>
</body>
</html>

  • ex09
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>세션 사용</h1>
	<h1 th:text="${session.sid}"></h1> 
	<h1 th:text="'세션 = ' + ${session.sid}"></h1>
	
	세션 sid 값 확인	= [[${session.sid}]]		<br />
	세션 저장된 개수	= [[${session.size()}]]		<br />
	세션 비어있는지 확인	= [[${session.isEmpty()}]]	<br />
	특정 세션키 sid 있는지 확인	= [[${session.containsKey('sid')}]]	<br />
	
	- 변수 표현식을 th:text 와 같은 타임리프의 속성 이외에
	  단순 사용 시 [[ ${} ]] 대괄호로 묶어줘야 한다.
</body>
</html>

  • ex10
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>조건문 - if, unless를 같은 조건식으로 사용</h3>
	<h3>자바의 조건문 처럼 사용</h3>
	<span th:if="${age} > 10">크다</span>
	<span th:unless="${age} > 0">작다</span>
	
	<h3>조건문 if만 사용 가능</h3>
	<span th:if="${age} > 10">크다</span>
	
	<h3>조건문 unless만 사용 가능</h3>
	<span th:unless="${age} > 10">작다</span>
	
	
</body>
</html>

  • ex11
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>switch 문 - 자바의 switch문과 같다.</h3>
	<div th:switch="${age}">
		<span th:case="30">30대</span>
		<span th:case="20">20대</span>
		<span th:case="10">10대</span>
        <span th:case="*">기타</span>
	</div>
</body>
</html>

  • ex12
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>반복문</h3>
	<div th:each="str : ${list}">
		<span th:text="${str}"></span>
	</div>
	
	- th:each="str : ${list}"
		forEach문 구조,
		"변수 : 반복대상" -> 기본 구조
	
	<hr color="gray">
	
	<table border="1">
		<tr th:each="str, loop : ${list}">
			<td th:text="${loop.count}"></td>
			<td th:text="${str}"></td>
		</tr>
	</table>
	
	- th:each="str, loop : ${list}"
		loop는 반복문의 속성을 가진 변수
		loop의 이름은 아무거나 사용해도 된다.
		
	- 반복문의 속성
		loop.index	: 루프(반복문)의 순서 	(루프의 반복 순서, 0부터 1씩 증가)
		loop.count	: 루프의 순서 			(루프의 반복 순서, 1부터 1씩 증가)
		loop.size	: 반복 객체의 요소 개수	(ex. list 요소의 개수)
		loop.first	: 루프의 첫번째 순서인 경우 true
		loop.last	: 루프의 마지막 순서인 경우 true
		loop.odd	: 루프의 홀수번째 순서인 경우 true
		loop.even	: 루프의 짝수번째 순서인 경우 true
		loop.current: 현재 대입된 객체
	
	<hr color="darkgray">
	
	<table border="1">
		<tr>
			<th>번호</th>
			<th>이름</th>
			<th>나이</th>
		</tr>
		<tr th:each="user, i : ${userList}">
			<td th:text="${i.count}"></td>
			<td th:text="${user.name}"></td>
			<td th:text="${user.age}"></td>
		</tr>
	</table>
</body>
</html>

  • ex13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>th:value 속성 활용</h3>
	- input 태그에 사용되는 value 속성에 변수표현식을 사용할 경우
	  표현식이 적용되지 않는다.
	- 타임리프는 기본 HTML에 적용하였기 때문에 HTML 기능이 우선 적용된다.
	- HTML은 표현언어를 처리하지 못한다.
	
	- $, @, *, ~ 다양한 표현식은 반드시 타임리프 속성에 사용해야 한다.
	
	<h3>input 태그의 value 속성에 ${}변수표현식 사용 시 th:value 속성 활용</h3>
	<input type="text" value="${age}"		/>	<br />
	<input type="text" th:value="${age}"	/>	<br />
</body>
</html>

  • ex14
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>th:src 속성 활용</h3>
	- 이미지 태그 src 속성 활용					<br />
	<img th:src="@{/img/java.png}"		/>	<br />
	<img th:src="@{|/img/${image}|}"	/>	<br />
	
	- @{} 안에서 ${} 사용 시 ※반드시 |(파이프 바) 로 감싸주어야 한다.
</body>
</html>

  • ex15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>th:attr 속성 활용</h3>
	- 여러 속성을 한 번에 설정
	
	<a th:href="@{/th/main}" th:title="${'my page'}">Main</a>	<!-- href 안에 변수표현식이 있으면 || 기호, th:title="" 안에 변수표현식 썼으므로 || 없어도됨 -->
	<br />
	<a th:attr="href=@{/th/main}, title=${'my page'}">Main</a>	<!-- th:attr 속성 내부에서 여러 속성을 지정할 때 작은따옴표가 사용되지 않는다. -->
	<!-- @{/th/main}는 URL /th/main을 의미한다. 이 구문은 URL을 지정할 때 사용되며, 이 경우 작은따옴표가 필요하지 않는다. -->
</body>
</html>

  • main
<!DOCTYPE html>
<html xmlns:th="">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>Hello Thymeleaf</h3>
</body>
</html>

  • sub
<h2>프래그먼트 표현식</h2>
- th:fragment="title"
	fragment 속성을 사용하여 title 이름으로 설정
	포함시킬 페이지에서 title 이름으로 호출한다.
<div th:fragment="title">제목입니다.</div>
<div th:fragment="content">내용입니다.</div>

- th:fragment="info()"
	자바의 메서드처럼 사용 가능
	각각 ${name}, ${tel} 에 적용된다.
<div th:fragment="info(name, tel)">
	이름		: <span th:text="${name}"></span>,
	연락처	: <span th:text="${tel}"></span>
</div>

  • memberForm
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
	<h1>회원가입</h1>
	<form action="#" th:action="@{/th/memberPro}" method="post">
	<!-- 폼의 실제 목적지를 아직 설정하지 않은 경우, 임시로 #를 사용 -->
    <!-- th:action만 사용해도 충분함 -->
    <!-- 변수표현식쓸때 th:action 사용 -->
		<label for="userName">사용자 : </label>
		<input type="text" id="userName" name="userName"  required	/>	<br /><br />
		
		<label for="pw">비밀번호 : </label>
		<input type="password" id="pw" name="pw" required			/>	<br /><br />
		
		<label for="email">이메일 : </label>
		<input type="text" id="email" name="email" required			/>	<br /><br />
		
		<label>성별 : </label>
		<input type="radio" id="male" name="gender" value="male" required		/>
		<label for="male">남성</label>								&nbsp;
		<input type="radio" id="female" name="gender" value="female" required	/>
		<label for="female">여성</label>								<br /><br />
		
		<label for="country">국가</label>
		<select id="country" name="country">
			<option value="korea">대한민국</option>
			<option value="usa">미국</option>
			<option value="canada">캐나다</option>
		</select>													<br /><br />
		
		<label>취미 : </label>
		<input type="checkbox" id="hobby1" name="hobbies" value="sports"	/>
		<label for="hobby1">운동</label>								&nbsp;
		<input type="checkbox" id="hobby2" name="hobbies" value="reading"	/>
		<label for="hobby2">독서</label>								&nbsp;
		<input type="checkbox" id="hobby3" name="hobbies" value="travling"	/>
		<label for="hobby3">여행</label>								&nbsp;
		<input type="checkbox" id="hobby4" name="hobbies" value="game"		/>
		<label for="hobby4">게임</label>								<br /><br />
		
		<button type="submit">가입하기</button>						<br /><br />
		<a th:attr="href=@{/th/main}, title=${'Main'}">메인으로</a>
	</form>
</body>
</html>

  • memberPro
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원가입 결과</title>
</head>
<body>
	<h1>회원가입 결과</h1>
	<p th:text="${message}"></p>
	
	<h2>회원 정보</h2>
	<p>사용자 : <span th:text="${member.userName}"></span></p>
	<p>비밀번호 : <span th:text="${member.pw}"></span></p>
	<p>이메일 : <span th:text="${member.email}"></span></p>
	<p>성별 : <span th:text="${member.gender}"></span></p>
	<p>국가 : <span th:text="${member.country}"></span></p>
	<p>취미 : <span th:each="hobby : ${member.hobbies}" th:text="${hobby} + ' '"></span></p>
	<a th:href="@{/th/memberForm}">다시 가입하기</a>
</body>
</html>

2024-05-23

Lombok

- Lombok 이란?

어노테이션 기반으로 코드 자동완성 기능을 제공하는 라이브러리

  • 반복되는 코드가 자주 등장하며 가독성을 떨어뜨림

  • ex) Getter, Setter, ToString, Constructor(생성자)

- Lombok의 장점

  • 어노테이션을 통한 코드 자동 생성을 통한 생산성, 편의성 증가
  • Code의 길이가 줄어듬으로 가독성, 유지보수성 향상
  • Builder 패턴의 적용, Log 생성 등등 편의성

- lombok Annotaion(어노테이션)

@Getter

  • code가 컴파일 될 때 getter 메서드들을 생성
  • 속성 @Getter(lazy = true) 사용시
    최초 한번만 Getter 호출. 이후 캐시된 값을 사용

@Setter

  • code가 컴파일 될 때 setter 메서드들을 생성

@ToString

  • toString() 메서드를 생성

@EqualsAndHashCode

  • 사용 객체에 대해서 equals(), hashCode() 메서드를 생성

@Data

  • @Getter(모든속성), @Setter(final이 붙지 않은), @ToString,
    @EqualsAndHashCode, @RequiredArgsConstructor
    위의 어노테이션들을 합쳐둔 어노테이션

@NoArgsConstructor

  • 파라미터(매개변수)가 없는 생성자를 생성

@RequiredArgsConstructor

  • final, @NonNull이 있는 필드를 포함하여 생성자를 생성

@AllArgsConstructor

  • 모든 필드를 파라미터(매개변수)로 갖는 생성자를 생성

@Builder

  • 해당 클래스에 빌더 패턴을 사용할 수 있도록 해줌

@Log

  • log라는 변수를 이용하여 로그 기능을 사용할 수 있음
  • 컴파일시 : private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(this.class.getName());
    -> 해당 코드 생성

@Log4j, @Slf4J

  • Log4J(Slf4J) 설정을 이용하여 로그 기능 사용할 수 있음
  • 마찬가지로 log 변수를 통해 사용

@SneakyThrows

  • 예외 발생시 Throwable 타입으로 반환해줌
    JVM(.class) 에서 검사 여부 관계없이 모든 예외에 대해 throw동작
  • 롬복 공식 홈페이지에서는 신중하게 사용하라는 권고 있음

@Synchronized

  • Method 에서 동기화 설정.
  • 동기화 관련 문제 발생을 해당 어노테이션을 통해 가상의 필드 레벨에서 조금이나마 안전하게 락을 걸어줌

@NonNull

  • 필드의 값이 null이 될 수 없음을 명시해줌

@Value

  • 불변 클래스를(Immutable Class) 생성해줌

  • 모든 필드를 Private, Final 로 설정하고, Setter를 생성하지 않음(상수로 만들어줌)

  • FInal 이 붙기 때문에 Setter는 존재할 수 없음

Test

- Lombok 클래스 생성

package com.example.ex;

import lombok.Getter;
import lombok.Setter;
import lombok.NonNull;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Lombok {
	private String name;
	private int age;
	
	@NonNull
	private String test;
}

- LombokController 생성

package com.example.ex;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/lombok")
@Controller
public class LombokController {

	@GetMapping("/lom01")
	public String lom01(Model model) {
		Lombok lombok = new Lombok();
		lombok.setName("김자바");
		lombok.setAge(20);
		lombok.setTest("자바공부");
		
		model.addAttribute("lombok", lombok);
		return "lom01";
	}
	
	@GetMapping("/lom02")
	public String lom02(Model model) {
//		@NoArgsConstructor 설정에 의해 기본생성자 사용 가능
		Lombok lombok = new Lombok();
		lombok.setName("이자바");
		lombok.setAge(30);
		
//		@NonNull 설정에 의해 null 대입 시 소스코드에는 오류가 없지만, 실행 시 NullPointException 예외 발생
//		user.setTest(null);
		
		lombok.setTest("lombok");
		model.addAttribute("lombok", lombok);
		return "lom02";
	}
	
	@GetMapping("/lom03")
	public String lom03(Model model) {
//		@AllArgsConstructor 설정에 의해 모든 변수를 매개변수로 받는 생성자
		Lombok lombok = new Lombok("박자바", 10, "hello");
		
		model.addAttribute("lombok", lombok);
		return "lom03";
	}
	
	@GetMapping("/lom04")
	public String lom04(Model model) {
//		@Builder 생성자를 사용하지 않고, 빌더 패턴을 자동으로 생성했다.
		Lombok lombok = Lombok.builder().name("최자바").age(25).test("hi").build();
		
		model.addAttribute("lombok", lombok);
		return "lom04";
	}
}

- templates/* 생성

  • lom01 ~ lom04
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div th:text="${lombok.name}"></div>
	<div th:text="${lombok.age}"></div>
	<div th:text="${lombok.test}"></div>
</body>
</html>

2024-05-24

로깅(Logging)

- 로깅(Logging)이란?

  • 소프트웨어 개발 및 유지보수 과정에서 중요한 역할을 하는데,
    이는 오류 디버깅, 시스템 동작 확인, 보안 이슈 추적 등 다양한 목적으로 사용됨

- Log4j와 Log4j2의 차이점?

  • Log4j에 비해 Log4j2는 멀티스레드 환경에서의 성능 향상, 로깅 구성 변경 시 애플리케이션 재시작 없이 동적 로딩, API 개선 등을 제공함

- @Log4j2

  • Log4j2 로깅 프레임워크의 비동기 로깅 기능을 제공
  • 로그 이벤트를 즉시 처리하지 않고 백그라운드에서 처리하여 응용 프로그램의 성능을 향상시킴
  • XML, JSON, YAML, Properties 등 다양한 형식으로 로깅 설정을 할 수 있음

- 로그 레벨

(TRACE < DEBUG < INFO(기본) < WARN < ERROR < FATAL)

  • TRACE - 일반적으로 사용하지 않음 / 모든것을 기록
  • DEBUG - 일반적으로 사용하지 않음 / 문제를 진단할 때 (디버깅)
  • INFO - 기본값 WARN - 경고 / 어플리케이션이 동작하는데에 문제는 없음
  • ERROR - 오류 / 해결해야 함
  • FATAL - 치명적인 오류 / 문제가 큼

Test

- build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'			// 개발도구
	compileOnly 'org.projectlombok:lombok'									// 롬복
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'	// 타임리프
}

- 루트 레벨(전체 레벨) 전체 로깅 레벨 지정

  • application.properties
spring.application.name=param
# K=V
# log level
logging.level.root=info

- Member 클래스 생성

package com.example.param;

import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Member {
	private String id;
	private String pw;
	private String email;
	private String gender;
	private String country;
	private List<String> hobbies;
}

- ParamController 생성

package com.example.param;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.log4j.Log4j2;

@Controller
@RequestMapping("/param/*") // http://localhost:8080/param/~
@Log4j2
public class ParamController {

	@GetMapping("form")
	public String form() {
		log.trace("[trace] url-/param/form");
		log.debug("[debug] url-/param/form");
		log.info("[info] url-/param/form");
		log.warn("[warn] url-/param/form");
		log.error("[error] url-/param/form");
		log.fatal("[fatal] url-/param/form");

		return "form";
	}

	@PostMapping("ex01")
	public String ex01(Member member) { // ${member.~} (대문자x, 소문자o)
		log.info("ex01 -- parameter id : " + member.getId());
		log.info("ex01 -- parameter pw : " + member.getPw());
		return "ex01";
	}

	@PostMapping("ex02")
	public String ex02(Model model, @RequestParam("id") String id, @RequestParam("pw") String pw) {
//		@RequestParam - 지정해서 보냄. 한두개 보낼때 유용하게 쓰임
//		request.getParameter("id") 와 같은 역할

		log.info("ex02 -- parameter id : " + id);
		log.info("ex02 -- parameter pw : " + pw);

		model.addAttribute("id", id);
		model.addAttribute("pw", pw);

		return "ex02";
	}

	@PostMapping("ex03")
	public String ex03(Model model, @RequestParam("hobbies") List<String> hobbies,
			@RequestParam("hobbies") String[] arr) {
		log.info("ex03 -- parameter  hobbies" + hobbies);
		log.info("ex03 -- parameter  hobbies" + Arrays.asList(arr));

		model.addAttribute("hobbies", hobbies);
		model.addAttribute("arr", arr);

		return "ex03";
	}

	@PostMapping("ex04")
	public String ex04(Member member, @ModelAttribute("id") String id) {
//		@ModelAttribute - parameter 를 view 까지 전달가능	/ @RequestParam 은 못보냄
//		view 에서 ${id} 사용 가능
//		@RequestParam 과 Model 이 합쳐진 기능

		log.info("ex04 -- parameter id : " + id);

		return "ex04";
	}

	@PostMapping("ex05")
	public String ex05(@RequestParam("id") String id, RedirectAttributes rttr) {
//		RedirectAttributes - redirect 는 페이지 이동만, 이것은 속성들 까지 보내줌
//		일회성 데이터를 전달 - 브라우저에 한 번만 사용된다. (새로고침 하면 id 값이 출력되지 않음)

		log.info("ex05 -- parameter id : " + id);
		rttr.addFlashAttribute("id", id);
		rttr.addFlashAttribute("pw", "1234");

//		redirect 로 이동 시 뷰로 가는것이 아니라, 해당 매핑으로 GET 방식으로 이동한다.
		return "redirect:ex05"; // 경로 아님. @Mapping 의 URI 로 보낸다는 의미
								// GetMapping 의 ex05 로 이동한다.
	}

	@GetMapping("ex05")
	public String ex05() {

		return "ex05";
	}

	@GetMapping("ex06")
	public @ResponseBody String ex06() {
//		@ResponseBody - view 를 거치지 않고 바로 보여줌
//		http://localhost:8080/param/ex06

		return "Hello Param";
	}

	@GetMapping("ex07")
	public @ResponseBody Member ex07() {
//		@ResponseBody 객체도 전달 가능하다.
		Member dto = new Member();
		dto.setId("java");
		dto.setPw("boot");

		return dto;
	}

	@GetMapping("ex08")
	public ResponseEntity<String> ex08() {
//		ResponseEntity - HTTP 의 응답을 표현하는 클래스
//		상태 코드, 헤더 를 포함하고있음		상태코드 200 - 성공 (정상실행)

		String msg = "{\"name\": \"김자바\"}";
//				"{ \" name \"  : \" 김자바 \"  }"
//				"{	" name  "  :  " 김자바  "  }"
//		Json 방식에서는 "" / 이스케이프 방식으로 문자를 표현해야함

		HttpHeaders header = new HttpHeaders();
		header.add("Content-Type", "application/json;charset=UTF-8");

		return new ResponseEntity<String>(msg, header, HttpStatus.OK);
	}

	@PostMapping("ex09")
	public String ex09(@RequestParam("save") MultipartFile mf, @ModelAttribute("id") String id, Model model) {
//		파일 업로드 시 MultipartFile mf 객체 사용 -> 업로드 직접 해줘야한다.
//		파일 이름 중복처리 기능이 없다.
//		c 드라이브에 upload 폴더 생성

		String fileName = "";
		String uploadDir = "C:/upload/";
		String orgName = mf.getOriginalFilename();
		if (orgName != null) {
			fileName = Paths.get(orgName).getFileName().toString();
			File copy = new File(uploadDir + fileName);
			
			try {
				mf.transferTo(copy);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		model.addAttribute("contentType", mf.getContentType());
		model.addAttribute("fileSize", mf.getSize());
		model.addAttribute("fileName", fileName);
		
		return "ex09";
	}

	@PostMapping("ex10")
	public String ex10(@RequestParam("save") List<MultipartFile> files, @ModelAttribute("id") String id, Model model) {
//		다중 업로드 시 List 로 받아서 처리할 수 있다.
		for (MultipartFile mf : files) {
			String uploadDir = "C:/upload/";
			String orgName = mf.getOriginalFilename();
			if (orgName != null) {
				String fileName = Paths.get(orgName).getFileName().toString();
				File copy = new File(uploadDir + fileName);
				
				try {
					if (mf.getContentType().split("/")[0].equals("image")) {
						mf.transferTo(copy);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
		model.addAttribute("fileCount", files.size());
		
		return "ex10";
	}
	
	@PostMapping("ex11")
	public String ex11(@RequestParam("save") MultipartFile mf, Model model) {
		
//		날짜와 랜덤 메서드를 활용하여 파일이름 중복 방지
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
		
		Random random = new Random();
		String rn = Integer.toString(random.nextInt(Integer.MAX_VALUE));
//		.nextInt() - 랜덤값이 int로 나오게 하는 메서드. int 최대 범위 사이의 랜덤감
		
		String ts = df.format(new Date());
//		현재 시간을 df(포맷) 적용
		
		String uploadFileName = ts + rn + mf.getOriginalFilename();
		File copy = new File("c:/upload/" + uploadFileName);
		
		try {
			mf.transferTo(copy);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		model.addAttribute("orgName", mf.getOriginalFilename());
		model.addAttribute("uploadFileName", uploadFileName);
		
		return "ex11";
	}
	
	@PostMapping("ex12")
	public String ex12(@RequestParam("save") MultipartFile mf, Model model, HttpServletRequest request) {
//		네트워크 UUID 를 활용한 파일 이름 설정
//		UUID(Universally Unique Identifier) 네트워크에서 고유한 식별자를 생성하는 표준 방식
//		ex) 5550e8400-e29d0343djdk-294935kjl
//		파일명에 - 있으면 읽지 못하는 경우가 있으니 처리
		String sysName = UUID.randomUUID().toString().replace("-", "") + mf.getOriginalFilename();
		String uploadPath = new File("").getAbsolutePath() + "\\src\\main\\resources\\static\\upload\\";
//									  ^ 비어있다면 현재 작업중인 폴더를 의미함
		log.info(uploadPath);
		
		File copy = new File(uploadPath + sysName);
		
		try {
			mf.transferTo(copy);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		model.addAttribute("sysName", sysName);
		return "ex12";
	}
	
	@GetMapping("display")
	public ResponseEntity<Resource> display(@RequestParam("fileName") String fileName) {
		String path = new File("").getAbsolutePath() + "\\src\\main\\resources\\static\\upload\\";
		
//		내 현재 파일을 읽어오기
		Resource re = new FileSystemResource(path + fileName);
		if (!re.exists()) {		// 존재하지 않는다면
			return new ResponseEntity<Resource>(HttpStatus.NOT_FOUND);
		}
		
//		응답 이미지 정보를 설정하기 위한 객체
		HttpHeaders head = new HttpHeaders();
		Path filePath = null;
		
//		파일의 MIME 타입을 결정하기 위해 Path 클래스로 변환
//		웹에서 사용하는 모든 파일 타입을 통틀어서 MIME 타입 이라고 한다.
		try {
			filePath = Paths.get(path + fileName);
			head.add("Content-type", Files.probeContentType(filePath));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return new ResponseEntity<Resource>(re, head, HttpStatus.OK);
	}
}

- templates/* 생성

  • ex01
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex01</title>
</head>
<body>
	<p>아이디 : <span th:text="${member.id}"></span></p>
	<p>비밀번호 : <span th:text="${member.pw}"></span></p>
</body>
</html>

  • ex02
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex02</title>
</head>
<body>
	<p>아이디 : <span th:text="${id}"></span></p>
	<p>비밀번호 : <span th:text="${pw}"></span></p>
</body>
</html>

  • ex03
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex03</title>
</head>
<body>
	<h3>리스트</h3>
	<p>취미 : <span th:each="hobby : ${hobbies}" th:text="${hobby} + ', '"></span></p>
	
	<h3>배열</h3>
	
	<p>취미 : <span th:each="arr : ${arr}" th:text="${arr} + ', '"></span></p>
</body>
</html>

  • ex04
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex04</title>
</head>
<body>
	<p>아이디 : <span style="color: red;" th:text="${id}"></span></p>
</body>
</html>

  • ex05
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex05</title>
</head>
<body>
	<p>아이디 : <span th:text="${id}"></span></p>
	<p>비밀번호 : <span th:text="${pw}"></span></p>
</body>
</html>

  • ex09
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex09</title>
</head>
<body>
	<p>아이디 : <span th:text="${id}"></span></p>
	<p>파일 타입 : <span th:text="${contentType}"></span></p>
	<p>파일 이름 : <span th:text="${orgName}"></span></p>
	<p>파일 크기 : <span th:text="${fileSize}"></span></p>
</body>
</html>

  • ex10
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex10</title>
</head>
<body>
	<p>아이디 : <span th:text="${id}"></span></p>
	<p>업로드 수 : <span th:text="${fileCount}"></span></p>
</body>
</html>

  • ex11
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex11</title>
</head>
<body>
	<p>원본 파일 이름 : <span th:text="${orgName}"></span></p>
	<p>바뀐 파일 이름 : <span th:text="${uploadFileName}"></span></p>
</body>
</html>

  • ex12
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ex12</title>
</head>
<body>
	<img th:src="'/param/display?fileName='+${sysName}"	/>
</body>
</html>

  • form
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>form</title>
</head>
<body>
	<h3>form --> ex01</h3>
	<form th:action="@{/param/ex01}" method="post">
		<label for="id">아이디 : </label>
		<input type="text" name="id" id="id" required		/>	<br />
		<label for="pw"> 비밀번호 : </label>
		<input type="password" name="pw" id="pw" required	/>	<br />
		<button type="submit">전송</button>
	</form>
	
	<hr color="gray"	/>
	
	<h3>form --> ex02</h3>
	<form th:action="@{/param/ex02}"	method="post">
		<label for="id2">아이디 : </label>
		<input type="text" name="id" id="id2" style="font-size: 30px;" required		/>	<br />
		<label for="pw2">비밀번호 : </label>
		<input type="password" name="pw" id="pw2" style="font-size: 30px;" required	/>	<br />
		<button type="submit">전송</button>
	</form>
	
	<hr color="darkgray"	/>
	
	<h3>form --> ex03</h3>
	<form th:action="@{/param/ex03}" method="post">
		<label>취미 : </label>
		<input type="checkbox" name="hobbies" id="hobby1" value="sports"	/>
		<label for="hobby1">운동</label>	&nbsp;
		<input type="checkbox" name="hobbies" id="hobby2" value="reading"	/>
		<label for="hobby2">독서</label>	&nbsp;
		<input type="checkbox" name="hobbies" id="hobby3" value="traveling"	/>
		<label for="hobby3">여행</label>	&nbsp;
		<input type="checkbox" name="hobbies" id="hobby4" value="game"		/>
		<label for="hobby4">게임</label>	<br	/>
		
		<button type="submit">전송</button>
	</form>
	
	<hr color="lightgray"	/>
	
	<h3>form --> ex04</h3>
	<form th:action="@{/param/ex04}" method="post">
		<label for="id">아이디 : </label>
		<input type="text" name="id" id="id" style="font-size: 30px;" required			/>	<br />
		
		<button type="submit">전송</button>
	</form>
	
	<hr color="lightgray"	/>
	
	<h3>form --> ex05</h3>
	<form th:action="@{/param/ex05}" method="post">
		<label for="id">아이디 : </label>
		<input type="text" name="id" id="id" style="font-size: 30px;" required			/>	<br />
		
		<button type="submit">전송</button>
	</form>
	
	<hr color="silver"	/>
	
	<h3>form --> ex09</h3>
	<form th:action="@{/param/ex09}" method="post" enctype="multipart/form-data">
		<label for="id">아이디 : </label>
		<input type="text" name="id" id="id" required		/>	<br />
		
		<label for="save">파일 : </label>
		<input type="file" name="save" id="save" required	/>	<br />
		
		<button type="submit" style="cursor: pointer">전송</button>
	</form>

	<hr color="silver"	/>
	
	<h3>form --> ex10</h3>
	<form th:action="@{/param/ex10}" method="post" enctype="multipart/form-data">
		<label for="id">아이디 : </label>
		<input type="text" name="id" id="id" required		/>	<br />
		
		<label for="save1">파일1 : </label>
		<input type="file" name="save" id="save1" required	/>	<br />
		<label for="save2">파일2 : </label>
		<input type="file" name="save" id="save2" required	/>	<br />
		<label for="save3">파일3 : </label>
		<input type="file" name="save" id="save3" required	/>	<br /> 
		
		<button type="submit">전송</button>
	</form>
	
	<hr color="gold"	/>
	
	<h3>form --> ex11</h3>
	<form th:action="@{/param/ex11}" method="post" enctype="multipart/form-data">
		<label for="save">파일 : </label>
		<input type="file" name="save" id="save" required		/>	<br />
		
		<button type="submit" style="cursor: pointer;">전송</button>
	</form>
	
	<hr color="red"	/>
	
	<h3>form --> ex12</h3>
	<form th:action="@{/param/ex12}" method="post" enctype="multipart/form-data">
		<label for="save">파일 : </label>
		<input type="file" name="save" id="save" required		/>	<br />
		
		<button type="submit">전송</button>
	</form>
</body>
</html>

2024-05-27

Spring Boot Service

- Spring Boot Service란?

  • Spring Boot로 개발된 웹 애플리케이션의 주요 구성 요소
  • 애플리케이션의 비즈니스 로직과 데이터 처리를 담당하며, 일반적으로 컨트롤러(Controller)와 데이터베이스(Database) 사이에서 중개자 역할을 수행함

- 비즈니스 로직 처리

  • 서비스는 비즈니스 로직을 담당하며, 데이터 처리와 관련된 작업들을 수행함
  • ex) 사용자 등록, 주문 처리, 데이터 분석

- 트랜잭션 관리

  • 서비스는 트랜잭션을 관리하여 데이터의 이관성과 무결성을 보장함
  • 하나의 비즈니스 로직이 여러 개의 데이터 조작 작업으로 이루어져 있을 때, 이를 하나의 논리적인 단위로 묶어서 처리함

- 재사용성과 모듈화

  • 서비스는 독립적으로 설계되기 때문에, 다른 부분과의 결합도가 낮아져 재사용성과 모듈화가 높아짐
  • 유지보수와 확장성을 향상시키는데 도움이 됨

- 유닛 테스트

  • Spring Boot의 특성상 서비스를 유닛 테스트하기 쉬움
  • 비즈니스 로직을 담당하는 서비스를 모킹(mocking)하여 다양한 시나리오를 테스트할 수 있음

Spring Boot Service 구현

- 인터페이스 정의

  • 비즈니스 로직을 담당할 서비스의 인터페이스를 정의함
  • 인터페이스를 통해 추상적인 선언을 하고, 실제 구현은 이를 구현하는 클래스에서 담당함

- 서비스 구현

  • 인터페이스를 상속받아 비즈니스 로직을 구현하는 클래스를 작성함
  • @Service 어노테이션을 사용하여 Spring에 해당 클래스가 서비스임을 알려줌

- 의존성 주입(Dependency Injection)

  • 서비스는 컨트롤러 등과 함께 동작해야함
  • Spring의 의존성 주입(DI)을 활용하여 서비스를 컨트롤러에 주입함

- 트랜잭션 설정

  • @Transactional 어노테이션을 제공하여 트랜잭션을 간편하게 설정할 수 있음
  • 서비스 메서드에 @Transactional 어노테이션을 추가하여 트랜잭션 범위를 설정함

Test #1

- 세팅

  • 2024-05-21 참고
  • sbp 패키지
  • Question, QuestionRepository, Answer, AnswerRepository 생성
  • 의존성 주입 추가
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
}

- QuestionController

package com.example.sbp.question;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
@RequestMapping("/question/*")
public class QuestionController {
	
	private final QuestionService questionService;
	
	@GetMapping("/")			// http://localhost:8080 '/' <- 이 부분
	public String root() {		// Question 의 메인페이지 - list
		
		return "redirect:/question/list";
	}
	
	@GetMapping("list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList", questionList);
		
		return "question_list";
	}
	
	@GetMapping("detail/{id}")	// http://localhost:8080/question/list/2 번호 바뀜
	public String detail(Model model, @PathVariable("id") Integer id) {	
											// @PathVariable("id") id와 매핑된 id 이름이 동일해야함
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		
		return "question_detail";
	}
}

QuestionService 생성

package com.example.sbp.question;

import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public List<Question> getList() {
		
		return this.questionRepository.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if (question.isPresent()) {
			
			return question.get();
		}else {
			throw new DataNotFoundException("아이디가 없음");
		}
	}
}

- 예외처리 클래스 생성

package com.example.sbp;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "entity not found")
public class DataNotFoundException extends RuntimeException {
	
	private static final long serialVersionUID = 1L;
	
	public DataNotFoundException(String message) {
		super(message);
	}
}

- AnswerController

package com.example.sbp.answer;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.sbp.question.Question;
import com.example.sbp.question.QuestionService;

import lombok.RequiredArgsConstructor;

@Controller
@RequestMapping("/answer/*")
@RequiredArgsConstructor
public class AnswerController {

	private final QuestionService questionService;
	private final AnswerService answerService;
	
	@PostMapping("create/{id}")
	public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam("content") String content) {
		Question question = this.questionService.getQuestion(id);
		this.answerService.create(question, content);
		
		return String.format("redirect:/question/detail/%s", id);
	}
}

- AnswerService 생성

package com.example.sbp.answer;

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;

import com.example.sbp.question.Question;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class AnswerService {
	
	private final AnswerRepository answerRepository;
	
	public void create(Question question, String content) {
		Answer answer = new Answer();
		answer.setContent(content);
		answer.setCreateDate(LocalDateTime.now());
		answer.setQuestion(question);
		
		this.answerRepository.save(answer);
	}
}

- templates/* 생성

  • question_list
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" text="text/css" th:href="@{/bootstrap.min.css}"	/> -->

</head>

<body>
	<h2>Hello Question_list</h2>
	
	<div layout:fragment="content" class="container my-3">
		<table class="table">
			<thead class="table-dark">
				<tr>
					<th>글번호</th>
					<th>제목</th>
					<th>작성일시</th>
				</tr>
			</thead>
			<tbody>
				<tr th:each="question, loop : ${questionList}">
					<td th:text="${loop.count}"></td>
					<td>
						<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
					</td>
					<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
				</tr>
			</tbody>
		</table>
	</div>
</body>
</html>

  • question_detail
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/> -->
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<!-- 질문 -->
		<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
		<div class="card my-3">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
			</div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
		
		<!-- 답변 개수 -->
		<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
		
		<!-- 답변 반복 -->
		<div class="card my-3" th:each="answer : ${question.answerList}">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
				<div class="d-flex justify-content-end">
					<div class="badge bg-light text-dark p-2 text-start">
						<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
			</div>
		</div>
		
		<!-- 답변 작성 -->
		<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-3">
			<label for="content">답변 : </label>
			<textarea name="content" id="content" rows="15" style="width: 100%;"></textarea>	<br />
			<button type="submit" class="btn btn-primary my-2">답변 등록</button>
		</form>
	</div>
</body>
</html>

- 레이아웃(layout)

  • 레이아웃 적용할 페이지 < HTML >에 공통양식 경로 적기

  • layout:decorate="~{layout}"

  • 부트스트랩(Bootstrap) 홈페이지 : https://getbootstrap.kr/

  • 경로 : /src/main/resources/static/

  • CSS/JS 다운로드 - bootstrap.min 경로에 넣기

  • /templates/layout.html

<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<!-- Bootstrap CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/>
	<!-- sbp CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/style.css}"			/>
<title>Hello SBP !!</title>
</head>
<body>
	<!-- 기본 템플릿 안에 들어갈 내용 시작 -->
	<th:block layout:fragment="content"></th:block>
	<!-- 기본 템플릿 안에 들어갈 내용 끝 -->
</body>
</html>

2024-05-28

Validation

- Validation 이란?

  • 클라이언트에서 서버로 값을 전달하고자 할 때 (@RequestBody, @RequestParam, @PathVariable)
    전달되는 데이터에 대해 유효성 검증을 수행하며, 유효하지 않을 경우 에러(MethodArgumentNotValidException)를 발생하도록 처리하는 기능을 수행하는 라이브러리
  • 예외를 방지하기 위해 미리 검증하는 과정
    (방어적 코드를 대신해서 사용할 수 있음)
  • 검증해야 할 값이 많은 경우 코드의 길이가 길어짐
  • 흩어져 있는 경우 어디에서 검증하는지 알기 어려움
  • 검증 로직이 변경되는 경우, 참조하는 클래스에서 로직이 변경되어야 하는 부분이 발생할 수 있음
  • 메서드 호출 전/후 에 필터링 및 검증 수행

- 의존성 주입

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

- 관련 어노테이션

@Valid 또는 @Validated

  • 해당 객체에 대해 유효성 검사 실행

@Size(max=최대길이, min=최소길이)

  • 문자 길이에 대한 제한 지정

@NotNull

-null 불가

@NotEmpty

  • null, "" 불가

@NotBlank

  • null, "", " " 불가

@Max(최댓값)

  • 최댓값 지정

@Min(최솟값)

  • 최솟값 지정

@Email

  • @가 포함되어야 함

@Past

  • 과거 날짜

@PastOrPresent

  • 오늘 or 과거 날짜

@Future

  • 미래 날짜

@FutureOrPresent

  • 오늘 or 미래 날짜

@Pattern

  • 정규식 적용

@AssertTrue / False

  • 별도 로직 적용

- BindingResult 란?

  • 스프링이 제공하는 검증 오류 처리 방법의 핵심
  • 검증 오류를 보관하는 객체
  • ex) ModelAttribute에 데이터 바인딩 오류가 발생했을 때, BindingResult에 오류 정보가 담기게 되고 Controller가 정상 호출됨
  • BindingResult가 없으면 400오류가 발생하게되고 Controller가 호출되지 않으며, Error Page로 이동하게 됨

Test #2

- 폼 페이지 생성

  • QuestionForm
package com.example.sbp.question;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class QuestionForm {
	
	@NotEmpty(message="제목은 필수 항목입니다.")
	@Size(max = 200)	// byte
	private String subject;
	
	@NotEmpty(message="내용은 필수 항목입니다.")
	private String content;
}

  • AnswerForm
package com.example.sbp.answer;

import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class AnswerForm {

	@NotEmpty(message = "내용은 필수 항목 입니다.")
	private String content;
}

- 회원가입 Form 생성

<!DOCTYPE html>
<html 	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
	<div layout:fragment="content" class="container">
		<h5 class="my-3 border-bottom pd-2">질문 등록</h5>
		<form th:action="@{/question/create}" th:object="${questionForm}" method="post">
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<!-- 			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}"> -->
<!-- 				<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div> -->
<!-- 			</div> -->
			<div class="mb-3">
				<label for="subject" class="form-label">제목</label>
				<input type="text" th:field="*{subject}" id="subject" class="form-control"	/>	<!-- 이름 대신 사용해야하므로 name 지움 (th:field) -->
			</div>
			<div class="mb-3">
				<label for="content">내용</label>
				<textarea th:field="*{content}" id="content" class="form-control" rows="10"></textarea>
			</div>
			<button type="submit" class="btn btn-primary" style="float: right;">저장하기</button>
		</form>
	</div>
</body>
</html>

- Form 에러 페이지 생성

  • th:replace 속성으로 보여줄 에러 페이지
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div th:fragment="formErrorsFragment" class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
		<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div>
	</div>
</body>
</html>

- Repository 수정

QuestionRepository

package com.example.sbp.question;

import java.util.List;

import org.springframework.data.domain.Page;			// 페이지를 위한 클래스
import org.springframework.data.domain.Pageable;		// 페이징 처리를 하는 인터페이스
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer> {
	
//	Junit 테스트
	Question findBySubject(String subject);
	
//	subject 와 content 두 개의 엔티티 속성(컬럼) 조회하기 위한 메서드
	Question findBySubjectAndContent(String subject, String content);
	
//	like 포함하는게 하나가 아닐수도있으므로 List 로 받음
	List<Question> findBySubjectLike(String subject);
	
	Page<Question> findAll(Pageable pageable);
//	+PageRequest : 현재 페이지와 한 페이지에 보여줄 게시물 수 등을 설정하여 페이징을 요청하는 클래스
}

- Controller 수정

QuestionController

  • RequestMapping 등 매핑 방식 변경
  • 폼 페이지 객체 반영
  • question_list 에서 사용할 페이징 처리 추가
package com.example.sbp.question;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.sbp.answer.AnswerForm;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
@RequestMapping("/question")
public class QuestionController {
	
	private final QuestionService questionService;
	
//	@GetMapping("/")			// http://localhost:8080 '/' <- 이 부분
//	public String root() {		// Question 의 메인페이지 - list
//		
//		return "redirect:/question/list";
//	}
	
	@GetMapping("/list")	// /list?page=0		default값 0으로 지정
	public String list(Model model, @RequestParam(value = "page", defaultValue = "0") int page) {
		Page<Question> paging = this.questionService.getList(page);
		model.addAttribute("paging", paging);
		
		return "question_list";
	}
	
	@GetMapping("/detail/{id}")	// http://localhost:8080/question/list/2 번호 바뀜
	public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm) {		// AnswerForm 추가
											// @PathVariable("id") id와 매핑된 id 이름이 동일해야함
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		
		return "question_detail";
	}
	
	@GetMapping("/create")
	public String questionCreate(QuestionForm questionForm) {
		
		return "question_form";
	}
	
	@PostMapping("/create")
	public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult) {
//		@valid : 이 객체가 유효한지 검증하는 어노테이션
		if (bindingResult.hasErrors()) {	// 에러가 있는 경우에 다시 form 으로 돌려보냄
			return "question_form";
		}
		
		this.questionService.create(questionForm.getSubject(), questionForm.getContent());
		
		return "redirect:/question/list";
	}
	
}

AnswerController

  • 답글 작성 메서드 추가
package com.example.sbp.answer;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.sbp.question.Question;
import com.example.sbp.question.QuestionService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequestMapping("/answer/*")
@RequiredArgsConstructor
public class AnswerController {

	private final QuestionService questionService;
	private final AnswerService answerService;
	
	@PostMapping("create/{id}")
	public String createAnswer(Model model, @PathVariable("id") Integer id, @Valid AnswerForm answerForm, BindingResult bindResult) {
		Question question = this.questionService.getQuestion(id);
		
		if (bindResult.hasErrors()) {
			model.addAttribute("question", question);
			
			return "question_detail";
		}
		
		this.answerService.create(question, answerForm.getContent());
		
		return String.format("redirect:/question/detail/%s", id);
	}
}

- Service

QuestionService

  • 페이징처리 메서드 추가
  • 질문 등록 메서드 추가
package com.example.sbp.question;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public Page<Question> getList(int page){
		List<Sort.Order> sorts = new ArrayList<Sort.Order>();
		sorts.add(Sort.Order.desc("createDate"));						// 등록일자 기준 내림차순 정렬
		
		Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));	// 조회 할 페이지 번호, 한 페이지에서 보여질 개수
		
		return this.questionRepository.findAll(pageable);
	}
	
	public List<Question> getList() {
		
		return this.questionRepository.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if (question.isPresent()) {
			
			return question.get();
		}else {
			throw new DataNotFoundException("아이디가 없음");
		}
	}
	
	public void create(String subject, String content) {
		Question question = new Question();
		question.setSubject(subject);
		question.setContent(content);
		question.setCreateDate(LocalDateTime.now());
		
		this.questionRepository.save(question);
	}
}

- List

question_list

  • 페이징 처리 추가
  • 반복문으로 꺼낼 때 반복할 객체 paging 으로 변경
  • 반복대상 변경으로 인한 반복문 내부 변수표현식 내용 변경
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" text="text/css" th:href="@{/bootstrap.min.css}"	/> -->

</head>

<body>
	<h2>Hello Question_list</h2>
	
	<div layout:fragment="content" class="container my-3">
		<table class="table">
			<thead class="table-dark">
				<tr>
					<th>번호</th>
					<th>제목</th>
					<th>작성일시</th>
				</tr>
			</thead>
			<tbody>
				<tr th:each="question, loop : ${paging}">
					<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
					<td>
						<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
						<span class="text-danger small ms-2" th:if="${#lists.size(question.answerList) > 0}" th:text="${#lists.size(question.answerList)}"></span>
					</td>
					<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
				</tr>
			</tbody>
		</table>
		
		<!-- 페이징 처리 시작 -->
		<div th:if="${!paging.isEmpty()}">
			<ul class="pagination justify-content-center">
				<li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
					<a class="page-link" th:href="@{|?page=${paging.number-1}|}">
						<span>이전</span>
					</a>
				</li>								<!-- 0부터 시작하기 때문에 0 ~ 총 페이지 - 1을 함 -->
				<li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}" 	
				th:if="${page >= paging.number-5 and page <= paging.number+5}" 
				th:classappend="${page == paging.number} ? 'active'" class="page-item">
										<!-- ^ 조건식이 참인 경우 ^ 활성 -->
					<a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
				</li>
				<li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
					<a class="page-link" th:href="@{|?page=${paging.number+1}|}">
						<span>다음</span>
					</a>
				</li>
			</ul>
		</div>
		<!-- 페이징 처리 끝 -->
		
		<a th:href="@{/question/create}" class="btn btn-primary" style="float: right;">질문 등록</a>
	</div>
</body>
</html>

- Detail

question_detail

  • AnswerForm 폼 페이지 객체 사용
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/> -->
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<!-- 질문 -->
		<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
		<div class="card my-3">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
			</div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
		
		<!-- 답변 개수 -->
		<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
		
		<!-- 답변 반복 -->
		<div class="card my-3" th:each="answer : ${question.answerList}">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
				<div class="d-flex justify-content-end">
					<div class="badge bg-light text-dark p-2 text-start">
						<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
			</div>
		</div>
		
		<!-- 답변 작성 -->
		<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<!-- 			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}"> -->
<!-- 				<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div> -->
<!-- 			</div> -->
			<label for="content">답변 : </label>
			<textarea th:field="*{content}" id="content" rows="15" style="width: 100%;"></textarea>	<br />
			<button type="submit" class="btn btn-primary my-2">답변 등록</button>
		</form>
	</div>
</body>
</html>

- 네비게이션 바(navbar)

  • Header 역할
  • 화면 줄어들 시 오른쪽 상단에 3단 줄(메뉴) 생성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
		<div class="container-fluid">
			<a class="navbar-brand" href="/question/list">SBP</a>
			<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
				data-bs-target="#navbarSupportedContent"
				aria-controls="navbarSupportedContent" aria-expended="false"
				aria-label="Toggle navigation">
				<span class="navbar-toggler-icon"></span>
			</button>
			<div class="collapse navbar-collapse" id="navbarSupportedContent">
				<ul class="navbar-nav me-auto mb-2 mb-lg-0">
					<li class="nav-item">
						<a class="nav-link" href="#">로그인</a>
					</li>
				</ul>
			</div>
		</div>
	</nav>
</body>
</html>

- 레이아웃(layout)

layout.html

  • navbar 추가
  • Bootstrap 스크립트 추가
  • /static/ 경로에 bootstrap.min.js 추가
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<!-- Bootstrap CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/>
	<!-- sbp CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/style.css}"			/>
<title>Hello SBP !!</title>
</head>
<body>
	<!-- 네비게이션 바 -->
	<nav th:replace="~{navbar :: navbarFragment}"></nav>	<!-- 파일명 :: 프래그먼트 이름 -->

	<!-- 기본 템플릿 안에 들어갈 내용 시작 -->
	<th:block layout:fragment="content"></th:block>
	<!-- 기본 템플릿 안에 들어갈 내용 끝 -->
	
	<!-- Bootstrap JS -->
	<script th:src="@{/bootstrap.min.js}"></script>
</body>
</html>

2024-05-29

스프링 시큐리티(Spring Security)

- 스프링 시큐리티란?

  • 스프링 웹 어플리케이션의 인증, 권한을 담당하는 하위 프레임워크
  • 인증(authentication)
    로그인과 같은 사용자의 신원을 확인하는 프로세스
  • 권한(authorize)은 인증된 사용자가 어떤일을 할 수 있는지(어떤 접근 권한이 있는지) 관리하는 것을 의미

- 의존성 추가

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
}

- SecurityConfig 클래스 생성

package com.example.sbp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration			// 이 파일이 스프링 환경 설정 파일이라는 것을 의미
@EnableWebSecurity		// 모든 요청 URL이 시큐리티의 제어를 받도록 만드는 어노테이션
public class SecurityConfig {
	
	@Bean				// SecurityFilterChain 생성하지 않았지만, 객체를 생성해준다.
	SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
//			요청을 허용할지 결정하는 설정
			.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
//				모든 요청을 허용한다는 의미
				.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
		.csrf((csrf) -> csrf
				.ignoringRequestMatchers(new AntPathRequestMatcher
						("/h2-console/**")))
		.headers((headers) -> headers
				.addHeaderWriter(new XFrameOptionsHeaderWriter(
						XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
		;
	return http.build();
	}
    
    @Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

람다식

- 람다식이란?

  • 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어
  • 람다 대수는 간단히 말하자면 수학에서 사용하는 함수를 보다 단순하게 표현하는 방법

- 익명함수?

  • 이름이 없는 함수
  • 일급객체(First Class citizen) 라는 특징을 가지고있음
  • 일급 객체란 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체를 말함
  • 함수를 값으로 사용 할 수도 있으며 파라미터로 전달 및 변수에 대입 하기와 같은 연산들 가능

- 람다식 장점

  • 코드의 간결성 - 람다를 사용하면 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현 가능
  • 지연연산 수행 - 람다는 지연연상을 수행 함으로써 불필요한 연산을 최소화 가능
  • 병렬처리 가능 - 멀티쓰레디를 활용하여 병렬처리를 사용 가능

- 람다식 단점

  • 람다식의 호출이 까다로움
  • 람다 stream 사용 시 단순 for문 혹은 while문 사용 시 성능이 떨어짐
  • 불필요하게 너무 사용하게 되면 오히려 가독성을 떨어 뜨릴 수 있음

- 람다식 예제

  • ex01
package com.example.lamda;
/*
	람다식 (익명함수)
	(타입 매개변수) -> {실행문;}
	(int a) -> {System.out.println(a);}
	() -> System.out.println(a);	매개변수가 없는 경우 비워도됨 / 타입 생략 가능
	(x, y) -> {return x+y;}			
	(x, y) -> return x+y;
*/

import groovyjarjarantlr4.v4.parse.ANTLRParser.block_return;

interface Test1 {
//	매개변수가 없는 추상 메서드
	public abstract void name();
}
interface Test2 {
//	매개변수가 하나인 추상 메서드
	public abstract void name(String name);
}
interface Test3 {
//	두 개의 매개변수를 가진 추상 메서드
	public abstract void name(String name, int age);
}
interface Test4 {
//	정수를 리턴하는 추상 메서드
	public abstract int sum(int a, int b);
}

public class Ex01 {
	public static void main(String[] args) {
		
//		익명클래스로 Test1 인터페이스 구현
		Test1 t = new Test1() {
//		name 메서드 구현
			public void name() {
				System.out.println("김자바");
			}
		};
		t.name();
		
//		람다식으로 Test1 인터페이스 구현
		Test1 t1 = () -> {
			System.out.println("이자바");
		};
		t1.name();
		
//		람다식으로 Test2 인터페이스 구현 - 매개변수 1개
		Test2 t2 = n -> {
			System.out.println(n);
		};
		t2.name("박자바");
		
//		람다식으로 Test3 인터페이스 구현 - 타입이 다른 2개
		Test3 t3 = (n, a) -> {
			for(int i=0; i<=a; i++) {
				System.out.println(n);
			}
		};
		t3.name("최자바", 3);
		
//		람다식으로 Test4 인터페이스 구현 - 리턴타입 있는 메서드
		Test4 t4 = (a, b) -> {
			return a+b;
		};
		
//		메서드 호출
		System.out.println(t4.sum(2, 3));
		
//		람다식으로 Test4 인터페이스 구현 (간결한 버전)
		Test4 t5 = (a, b) -> a + b;
//		메서드 호출
		System.out.println(t5.sum(2, 3));
	}
}

  • ex02
package com.example.lamda;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

public class Ex02 {
	public static void main(String[] args) {
		List<String> list = Arrays.asList("aaa", "bbb", "ccc");
		
//		Iterator 외부 반복자
		Iterator<String> iter = list.iterator();
		while(iter.hasNext()) {
			String abc = iter.next();
			System.out.println(abc);
		}
		
//		또다른 List
		List<String> list2 = Arrays.asList("100", "200", "300");
//		java.util.stream 내부 반복자
		Stream<String> stream = list2.stream();
//		stream 사용하여 값을 꺼내줌
		stream.forEach(ott -> System.out.println(ott));
	}
}

  • ex 03
package com.example.lamda;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Ex03 {
	public static void main(String[] args) {
		List<String> list = Arrays.asList("java", "jsp", "html", "css", "spring");
//		리스트로부터 스트림 생성
		Stream<String> st = list.stream();
		st = st.distinct();		// 중복된 요소 제거
//		반복으로 출력
		st.forEach(n -> System.out.println(n));
		
		System.out.println("==========");
		
		list.stream()
			.distinct()
			.forEach(n -> System.out.println(n));
		
		System.out.println("==========");
		
		list.stream()
			.filter(n -> n.startsWith("j"))
			.forEach(n -> System.out.println(n));
	}
}

- Stream이란?

  • Stream(스트림)은 자바 8버전부터 추가된 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식(함수적 스타일)으로 처리할 수 있도록 도와주는 반복자

  • 자바 7버전까지는 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator반복자를 사용하였는데, 자바 8버전부터는 Stream이 등장함

  • Stream은 Iterator와 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하기에 병렬 처리가 쉽다는 점, 그리고 중간 처리와 최종 처리의 파이프라인 작업을 수행한다는 차이가 있음

- Stream의 특징

  • 람다식으로 요소 처리 코드 제공
    Stream이 제공하는 대부분의 요소 처리 메서드는 함수적 인터페이스 매개 타입을 가지기 때문에 람다식 또는 메서드 참조를 이용해서 요소 처리 내용을 매개 값으로 전달할 수 있음

- 내부 반복자

  • 내부 반복자는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴

  • 외부 반복자는 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴
    index를 이용하여 for문과 Iterator를 이용하는 While문이 대표적인 외부 반복자

  • 내부 반복자의 장점은 요소를 반복시키는 방식에 대한 것은 컬렉션에 맡기고, 개발자는 요소 처리 코드에 만 집중할 수 있음

  • 내부 반복자는 요소들의 반복 순서를 변경하거나, 멀티 코어 CPU를 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기 때문에, 하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있음

- 병렬 처리

  • 한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것

  • 병렬 처리 스트림을 이용하면 런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고, 서브 작업의 결과를 자동으로 결합해서 최종 결과물을 생성함

Enum

- Enum이란?

  • enumerated type의 줄임말로 열거형이라고 부르기도 함
  • 열거형(enumerated type, enumeration)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형임
  • 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자

- Enum의 장점

  • 코드가 단순해지며, 가독성이 좋아짐
  • 인스턴스 생성과 상속을 방지하여 상수값의 타입안정성 보장
  • enum class를 사용해 새로운 상수들의 타입을 정의함으로 정의한 타입이외의 타입을 가진 데이터값을 컴파일시 체크
  • 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 알 수 있음

Validation

- Validation이란?

  • 데이터의 값이 유효한지 검사하는 것
  • 유효성 검사를 통해 올바르지 않은 데이터가 서버로 전송되거나, DB에 저장되지 않도록 함

- BindingResult란?

  • 스프링이 제공하는 검증 오류 처리 방법의 핵심
  • 객체에서 오류가 발생 시 코드를 담아주는 역할을 하는 클래스
  • BindingResult가 없으면 400에러 발생 - 컨트롤러 호출 안됨
  • BindingResult 사용 시 컨트롤러가 호출되는 이유
    - HandlerAdapter -> ArgumentResolver -> Handler
    ArgumentResolver가 호출되면서 파라미터가 바인딩 됨
    이 때, Validation이 적용되며,
    바인딩 실패 시 BindingResult에 마치 try-catch가 작동되는 것 처럼
    오류 정보가 담기게 됨
    그러므로 ArgumentResolver가 무사히 적용되고 컨트롤러가 호출됨

- rejectValue, reject

  • BindingResult 가 제공하는 rejectValue() , reject() 를 사용하면 FieldError, ObjectError를 직접 생성하지 않고, 깔끔하게 검증 오류를 다룰 수 있음

rejectValue()

field : 오류 필드명
errorCode : 오류 코드(이 오류 코드는 메시지에 등록된 코드가 아님 messageResolver를 위한 오류 코드)
errorArgs : 오류 메시지에서 {0} 을 치환하기 위한 값
defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지

  • rejectValue() 메서드는 특정 필드에 대한 검증 실패를 나타내는 오류를 등록할 때 사용됨

  • 이 메서드는 필드 수준의 검증에서, 필드 값이 특정 조건을 만족하지 않을 경우 오류를 등록하는 데 적합함

reject()

  • reject() 메서드는 객체 수준의 검증 실패를 나타내는 오류를 등록할 때 사용됨

  • 이는 특정 필드에 국한되지 않고, 전체 객체 또는 복합적인 조건에 대한 검증에서 사용됨

BindingResult 는 어떤 객체를 대상으로 검증하는지 target을 이미 알고 있음
따라서 target(item)에 대한 정보는 없어도 됨
rejectValue() 를 사용하고 부터는 오류 코드를 간단하게 입력할 수 있음

Test #3

- 의존성 추가

  • build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-security'
}

- SiteUser 클래스 생성

package com.example.sbp.user;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class SiteUser {
		
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(unique = true)
	private String username;
	
	
	private String pw;
	
	@Column(unique = true)
	private String email;
}

- Repository 생성

package com.example.sbp.user;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<SiteUser, Long> {
	Optional<SiteUser> findByUsername(String username);
	
}

- UserRole Enum파일 생성

  • UserRole.java
  • Enum 파일 생성
package com.example.sbp.user;

import lombok.Getter;

@Getter
public enum UserRole {
	ADMIN("ROLE_ADMIN"),
	USER("ROLE_USER");
	
	UserRole(String value) {
		this.value = value;
	}
	
	private String value;
}

- User 서비스 생성

  • UserService
package com.example.sbp.user;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UserService {

	private final UserRepository userRepository;
	private final PasswordEncoder passwordEncoder;
	
	public SiteUser create(String username, String pw, String email) {
		SiteUser user = new SiteUser();
		user.setUsername(username);
		user.setEmail(email);
		user.setPw(passwordEncoder.encode(pw));		// 암호화 처리 기능
		
		this.userRepository.save(user);
		
		return user;
	}
}

- User 폼 클래스 생성

  • UserCreateForm
package com.example.sbp.user;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserCreateForm {

	@Size(min = 3, max = 25)
	@NotEmpty(message = "사용자 이름은 필수 항목입니다.")
	private String username;
	
	@NotEmpty(message = "비밀번호는 필수 항목입니다.")
	private String pw1;
	
	@NotEmpty(message = "비밀번호 확인은 필수 항목입니다.")
	private String pw2;
	
	@Email
	@NotEmpty(message = "이메일은 필수 항목입니다.")
	private String email;
	
}

- user 컨트롤러 생성

  • UserController
package com.example.sbp.user;

import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {

	private final UserService userService;
	
	@GetMapping("/signup")
	public String signup(UserCreateForm userCreateForm) {
		
		
		return "signup_form";
	}
	
	@PostMapping("/signup")
	public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
		
		if (bindingResult.hasErrors()) {
			return "signup_form";
		}
		if (!userCreateForm.getPw1().equals(userCreateForm.getPw2())) {
			bindingResult.rejectValue("pw2", "passwordInCorrect", "2개의 비밀번호가 일치하지 않습니다.");
			
			return "signup_form";
		}
		
		try {
			userService.create(userCreateForm.getUsername(), userCreateForm.getPw1(), userCreateForm.getEmail());
		} catch(DataIntegrityViolationException e) {
			e.printStackTrace();
			bindingResult.reject("signupFailed", "이미 등록된 사용자 입니다.");
			return "signup_form";
		} catch(Exception e) {
			e.printStackTrace();
			bindingResult.reject("signupFailed", e.getMessage());
			return "signup_form";
		}
		
		return "redirect:/question/list";
	}
	
	@GetMapping("/login")
	public String login() {
		
		return "login_form";
	}
}

- SecurityConfig 생성

  • 시큐리티에서 default 값이 정해져있음
  • username, password 등..
  • 이미 pw 라고 정해놓았기 때문에 pw 이름으로 시큐리티에서 set해줌
package com.example.sbp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration			// 이 파일이 스프링 환경 설정 파일이라는 것을 의미
@EnableWebSecurity		// 모든 요청 URL이 시큐리티의 제어를 받도록 만드는 어노테이션
public class SecurityConfig {
	
	@Bean				// SecurityFilterChain 생성하지 않았지만, 객체를 생성해준다.
	SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
//			요청을 허용할지 결정하는 설정
			.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
//				모든 요청을 허용한다는 의미
				.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
		.csrf((csrf) -> csrf
				.ignoringRequestMatchers(new AntPathRequestMatcher
						("/h2-console/**")))
		.headers((headers) -> headers
				.addHeaderWriter(new XFrameOptionsHeaderWriter(
						XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
		.formLogin((formLogin) -> formLogin
			.loginPage("/user/login")
            .passwordParameter("pw")			// default =Password
			.defaultSuccessUrl("/question/list"))
		;
	return http.build();
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean
	AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}

- UserSecurityService

package com.example.sbp.user;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UserSecurityService implements UserDetailsService {
	
	private final UserRepository userRepository;
	
	@Override				// v 인터페이스 (강제)
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		Optional<SiteUser> _siteUser = this.userRepository.findByUsername(username);
		
		if (_siteUser.isEmpty()) {
			throw new UsernameNotFoundException("사용자를 찾을 수 없습니다.");
		}
		SiteUser siteUser = _siteUser.get();
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		if ("admin".equals(username)) {
			authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
		}else {
			authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
		}
		
		return new User(siteUser.getUsername(), siteUser.getPw(), authorities);
	}
}

- 로그인 폼

  • login_form.html
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<form th:action="@{/user/login}" method="post">
			<div th:if="${param.error}">		 <!-- URL 파라미터로 'error'가 전달되면 이 요소가 렌더링됩니다 -->
				<div class="alert alert-danger">
					사용자 이름 또는 비밀번호를 확인해주세요.
				</div>
			</div>
			<div class="mb-3">
				<label for="username" class="form-label">사용자 이름</label>
				<input type="text" name="username" id="username" class="form-control"	/>
			</div>
			<div class="mb-3">
				<label for="pw" class="form-label">비밀번호</label>
				<input type="password" name="pw" id="pw" class="form-control"			/>
			</div>
			<button type="submit" class="btn btn-primary">로그인</button>
		</form>
	</div>
</body>
</html>

- 회원가입 폼

  • signup_form.html
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<div class="my-3 border-bottom">
			<div>
				<h4>회원가입</h4>
			</div>
		</div>
		<form th:action="@{/user/signup}" th:object="${userCreateForm}" method="post">
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
			<div class="mb-3">
				<label for="username" class="form-label">사용자 이름</label>
				<input type="text" th:field="*{username}" class="form-control"	/>
			</div>
			<div class="mb-3">
				<label for="pw1" class="form-label">비밀번호</label>
				<input type="password" th:field="*{pw1}" class="form-control"	/>
			</div>
			<div class="mb-3">
				<label for="pw2" class="form-label">비밀번호 확인</label>
				<input type="password" th:field="*{pw2}" class="form-control"	/>
			</div>
			<div class="mb-3">
				<label for="email" class="form-label">이메일</label>
				<input type="email" th:field="*{email}" class="form-control"	/>
			</div>
			<button type="submit" class="btn btn-primary">회원가입</button>
		</form>
	</div>
</body>
</html>

- 네비게이션 바 수정

  • 로그인 로그아웃 수정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
		<div class="container-fluid">
			<a class="navbar-brand" href="/question/list">SBP</a>
			<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
				data-bs-target="#navbarSupportedContent"
				aria-controls="navbarSupportedContent" aria-expended="false"
				aria-label="Toggle navigation">
				<span class="navbar-toggler-icon"></span>
			</button>
			<div class="collapse navbar-collapse" id="navbarSupportedContent">
				<ul class="navbar-nav me-auto mb-2 mb-lg-0">
					<li class="nav-item">
						<a class="nav-link" sec:authorize="isAnonymous()" th:href="@{/user/login}">로그인</a>
						<a class="nav-link" sec:authorize="isAuthenticated()" th:href="@{/user/logout}">로그아웃</a>
					</li>
					<li class="nav-item">
						<a class="nav-link" th:href="@{/user/signup}">회원가입</a>
					</li>
				</ul>
			</div>
		</div>
	</nav>
</body>
</html>

2024-05-30

Spring Security

- @PreAuthorize

  • @PreAuthorize: 메서드가 실행되기 전에 인증을 거침

- @PostAuthorize

  • @PostAuthorize: 메서드가 실행되고 나서 응답을 보내기 전에 인증을 거침

- 어노테이션 안에서 사용 가능한 함수들

hasRole([role])

  • 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true

hasAnyRole([role1,role2 ...])

  • 현재 사용자의 권한 파라미터의 권한 중 일치하는 것이 있는 경우 true

principal

  • 사용자를 증명하는 주요객체(User)를 직접 접근할 수 있다.

authentication

  • SecurityContext에 있는 authentication 객체에 접근 할 수 있다.

permitAll

  • 모든 접근 허용

denyAll

  • 모든 접근 비허용

isAnonymous()

  • 현재 사용자가 익명(비로그인)인 상태인 경우 true

isRememberMe()

  • 현재 사용자가 RememberMe 사용자라면 true

isAuthenticated()

  • 현재 사용자가 익명이 아니라면 (로그인 상태라면) true

isFullyAuthenticated()

  • 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true

Test #4

로그아웃, 글 수정, 삭제, 추천 기능 추가

- Entity 수정

  • 유효성 검사 기능 추가

  • Set타입 voter 컬럼 추가

  • Question.java

package com.example.sbp.question;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

import com.example.sbp.answer.Answer;
import com.example.sbp.user.SiteUser;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Question {
	
	@ManyToMany						// 추천
	Set<SiteUser> voter;			// 중복 안되게 하기 위해 Set 사용
	
	@ManyToOne
	private SiteUser author;		// 5.30)
	
	private LocalDateTime modifyDate;
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)	// 별도로 고유번호를 정해줌. 1씩 자동 증가함
	private Integer id;

	@Column(length=200)						// 200자 length : 열의 '길이'
	private String subject;

	@Column(columnDefinition="TEXT")		// 문자열 데이터 저장. 글자 제한이 없음
	private String content;
   
	private LocalDateTime createDate;
	
	@OneToMany(mappedBy="question", cascade=CascadeType.REMOVE)
	private List<Answer> answerList;		// 답변 리스트
	
}

  • Answer.java
package com.example.sbp.answer;

import java.time.LocalDateTime;
import java.util.Set;

import com.example.sbp.question.Question;
import com.example.sbp.user.SiteUser;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Answer {
	
	@ManyToMany
	Set<SiteUser> voter;		// 추천
	
	@ManyToOne
	private SiteUser author;	// 05.30) 글쓴이
	
	private LocalDateTime modifyDate;
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer id;
	
	@Column(columnDefinition="TEXT")
	private String content;
	
	private LocalDateTime createDate;		// 카멜표기법 -> DB 에서는 스네이크 표기법으로 바뀜 create_date
	
//	질문 엔티티를 참조하기 위해 question 속성을 추가함
	@ManyToOne
	private Question question;
}
/*
	여러 답변이(Many) 하나의(One) 질문에 달림
		N	:	1
	@ManyToOne
	-----------------------------------
	질문(One)	답변(Many)
		1	:	N
	@OnetoMany
	
*/

- User 서비스 수정

package com.example.sbp.user;

import java.util.Optional;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UserService {

	private final UserRepository userRepository;
	private final PasswordEncoder passwordEncoder;
	
	public SiteUser create(String username, String pw, String email) {
		SiteUser user = new SiteUser();
		user.setUsername(username);
		user.setEmail(email);
		user.setPw(passwordEncoder.encode(pw));		// 암호화 처리 기능
		
		this.userRepository.save(user);
		
		return user;
	}
	
	public SiteUser getUser(String username) {		// 05/30 메서드 추가
		Optional<SiteUser> siteUser = this.userRepository.findByUsername(username);
		if (siteUser.isPresent()) {
			return siteUser.get();
		}else {
			throw new DataNotFoundException("siteUser not found");
		}
	}
}

- SecurityConfig 수정

package com.example.sbp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration			// 이 파일이 스프링 환경 설정 파일이라는 것을 의미
@EnableWebSecurity		// 모든 요청 URL이 시큐리티의 제어를 받도록 만드는 어노테이션
@EnableMethodSecurity(prePostEnabled = true)	// 5.30) @PreAuthorize 로그인 되었는지 판별해주는 어노테이션 default값 : false
public class SecurityConfig {
	
	@Bean				// SecurityFilterChain 생성하지 않았지만, 객체를 생성해준다.
	SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
//			요청을 허용할지 결정하는 설정
			.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
//				모든 요청을 허용한다는 의미
				.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
		.csrf((csrf) -> csrf
				.ignoringRequestMatchers(new AntPathRequestMatcher
						("/h2-console/**")))
		.headers((headers) -> headers
				.addHeaderWriter(new XFrameOptionsHeaderWriter(
						XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
		.formLogin((formLogin) -> formLogin
			.loginPage("/user/login")
            .passwordParameter("pw")			// default = Password
			.defaultSuccessUrl("/question/list"))
		.logout((logout) -> logout
			.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
			.logoutSuccessUrl("/question/list")	//로그아웃 성공시 이동할 url
			.invalidateHttpSession(true))		//로그아웃시 생성된 세션 삭제 활성화
		;
	return http.build();
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean
	AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}

- Question 서비스 수정

package com.example.sbp.question;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;
import com.example.sbp.user.SiteUser;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public Page<Question> getList(int page){
		List<Sort.Order> sorts = new ArrayList<Sort.Order>();
		sorts.add(Sort.Order.desc("createDate"));
		
		Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));	// 조회 할 페이지 번호, 한 페이지에서 보여질 개수
		
		return this.questionRepository.findAll(pageable);
	}
	
	public List<Question> getList() {
		
		return this.questionRepository.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if (question.isPresent()) {
			
			return question.get();
		}else {
			throw new DataNotFoundException("아이디가 없음");
		}
	}
	
	public void create(String subject, String content, SiteUser user) {		// 05.30)
		Question question = new Question();
		question.setSubject(subject);
		question.setContent(content);
		question.setCreateDate(LocalDateTime.now());
		question.setAuthor(user);
		
		this.questionRepository.save(question);
	}
	
	public void modify(Question question, String subject, String content) {
		question.setSubject(subject);
		question.setContent(content);
		question.setModifyDate(LocalDateTime.now());
		
		this.questionRepository.save(question);
	}
	
	public void delete(Question question) {
		this.questionRepository.delete(question);
	}
	
	public void vote(Question question, SiteUser siteUser) {
		question.getVoter().add(siteUser);
		this.questionRepository.save(question);
	}
}

- Question 컨트롤러 수정

package com.example.sbp.question;

import java.security.Principal;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;

import com.example.sbp.answer.AnswerForm;
import com.example.sbp.user.SiteUser;
import com.example.sbp.user.UserService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
@RequestMapping("/question")
public class QuestionController {
	
	private final QuestionService questionService;
	private final UserService userService;
	
//	@GetMapping("/")			// http://localhost:8080 '/' <- 이 부분
//	public String root() {		// Question 의 메인페이지 - list
//		
//		return "redirect:/question/list";
//	}
	
	@GetMapping("/list")	// /list?page=0		default값 0으로 지정
	public String list(Model model, @RequestParam(value = "page", defaultValue = "0") int page) {
		Page<Question> paging = this.questionService.getList(page);
		model.addAttribute("paging", paging);
		
		return "question_list";
	}
	
	@GetMapping("/detail/{id}")	// http://localhost:8080/question/list/2 번호 바뀜
	public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm) {	
											// @PathVariable("id") id와 매핑된 id 이름이 동일해야함
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		
		return "question_detail";
	}
	
	@PreAuthorize("isAuthenticated()")		// 5.30)
	@GetMapping("/create")
	public String questionCreate(QuestionForm questionForm) {
		
		return "question_form";
	}
	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/create")
	public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal) {
//		@valid : 이 객체가 유효한지 검증하는 어노테이션
		if (bindingResult.hasErrors()) {	// 에러가 있는 경우에 다시 form 으로 돌려보냄
			return "question_form";
		}
		SiteUser siteUser = this.userService.getUser(principal.getName());
		this.questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser);
		
		return "redirect:/question/list";
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/modify/{id}")
	public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
		Question question = this.questionService.getQuestion(id);
		if(!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		questionForm.setSubject(question.getSubject());
		questionForm.setContent(question.getContent());
		
		
		return "question_form";
	}
	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/modify/{id}")
	public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult, @PathVariable("id") Integer id, Principal principal) {
		if (bindingResult.hasErrors()) {
			return "question_form";
		}
		
		Question question = this.questionService.getQuestion(id);
		if(!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		this.questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
		return String.format("redirect:/question/detail/%s", id);
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/delete/{id}")
	public String questionDelete(Principal principal, @PathVariable("id") Integer id) {
		Question question = this.questionService.getQuestion(id);
		if (!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제 권한이 없습니다.");
		}
		this.questionService.delete(question);
		
		return "redirect:/question/list";
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/vote/{id}")
	public String questionVote(@PathVariable("id") Integer id, Principal principal) {
		Question question = this.questionService.getQuestion(id);
		SiteUser siteUser = this.userService.getUser(principal.getName());
		this.questionService.vote(question, siteUser);
		
		return String.format("redirect:/question/detail/%s", id);
	}
}

- Answer 서비스 수정

package com.example.sbp.answer;

import java.time.LocalDateTime;
import java.util.Optional;

import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;
import com.example.sbp.question.Question;
import com.example.sbp.user.SiteUser;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class AnswerService {
	
	private final AnswerRepository answerRepository;
	
	public Answer create(Question question, String content, SiteUser author) {	// 5.30) 타입 변경
		Answer answer = new Answer();
		answer.setContent(content);
		answer.setCreateDate(LocalDateTime.now());
		answer.setQuestion(question);
		answer.setAuthor(author);			// 5.30) 작성자 추가
		
		this.answerRepository.save(answer);
		
		return answer;
	}
	
	public Answer getAnswer(Integer id) {
		Optional<Answer> answer = this.answerRepository.findById(id);
		if (answer.isPresent()) {
			return answer.get();
		}else {
			throw new DataNotFoundException("answer not found");
		}
	}
	
//	수정
	public void modify(Answer answer, String content) {
		answer.setContent(content);
		answer.setModifyDate(LocalDateTime.now());
		
		this.answerRepository.save(answer);
	}
	
//	삭제
	public void delete(Answer answer) {
		answerRepository.delete(answer);
	}
	
//	추천
	public void vote(Answer answer, SiteUser siteUser) {
		answer.getVoter().add(siteUser);
		answerRepository.save(answer);
	}
}

- Answer 컨트롤러 수정

  • AnswerController
package com.example.sbp.answer;

import java.security.Principal;

import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ResponseStatusException;

import com.example.sbp.question.Question;
import com.example.sbp.question.QuestionService;
import com.example.sbp.user.SiteUser;
import com.example.sbp.user.UserService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequestMapping("/answer/*")
@RequiredArgsConstructor
public class AnswerController {

	private final QuestionService questionService;
	private final AnswerService answerService;
	private final UserService userService;
	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("create/{id}")															// 05/30 추가) Principal - 로그인이 되었는지 처리를 하는 객체를 가지고있음 
	public String createAnswer(Model model, @PathVariable("id") Integer id, @Valid AnswerForm answerForm, BindingResult bindResult, Principal principal) {
		Question question = this.questionService.getQuestion(id);
		SiteUser siteUser = this.userService.getUser(principal.getName());		
		if (bindResult.hasErrors()) {
			model.addAttribute("question", question);
			
			return "question_detail";
		}
		
		Answer answer = this.answerService.create(question, answerForm.getContent(), siteUser);
		
		return String.format("redirect:/question/detail/%s#answer_%s", answer.getQuestion().getId(), answer.getId());
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/modify/{id}")
	public String answerModify(AnswerForm answerForm, @PathVariable("id") Integer id, Principal principal) {
		Answer answer = this.answerService.getAnswer(id);
		if (!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		answerForm.setContent(answer.getContent());
		
		return "answer_form";
	}
	
//	수정
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/modify/{id}")
	public String answerModify(@Valid AnswerForm answerForm, BindingResult bindingResult, @PathVariable("id") Integer id, Principal principal) {
		if (bindingResult.hasErrors()) {
			return "answer_form";
		}
		Answer answer = this.answerService.getAnswer(id);
		if (!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		this.answerService.modify(answer, answerForm.getContent());
		return String.format("redirect:/question/detail/%s#answer_%s", answer.getQuestion().getId(), answer.getId());
	}												//   ^ d 로 해도됨. String 이라 s도 가능
	
//	삭제
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/delete/{id}")
	public String answerDelete(@PathVariable("id") Integer id, Principal principal) {
		Answer answer = this.answerService.getAnswer(id);
		if (!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제 권한이 없습니다.");
		}
		this.answerService.delete(answer);
		
		return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/vote/{id}")
	public String answerVote(@PathVariable("id") Integer id, Principal principal) {
		Answer answer = answerService.getAnswer(id);
		SiteUser siteUser = userService.getUser(principal.getName());
		answerService.vote(answer, siteUser);
		
		return String.format("redirect:/question/detail/%s#answer_%s", answer.getQuestion().getId(), answer.getId());
	}
}

- sbpApplicationTests 수정

package com.example.sbp;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.sbp.question.QuestionService;


@SpringBootTest		// 테스트 클래스라는걸 알려줌
class SbpApplicationTests {
	
	@Autowired
	private QuestionService questionService;
	
	@Test
	void testJpa() {
		for (int i=1; i<=300; i++) {
			String subject = String.format("테스트 데이터 입니다:[%03d]", i);
			String content = "내용";
			this.questionService.create(subject, content, null);
			// 5.30) create 메서드 siteUser 매개변수 추가 에 따른 null 추가
		}
	}
}

- templates/* 수정

  • question_list
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" text="text/css" th:href="@{/bootstrap.min.css}"	/> -->

</head>

<body>
	<h2>Hello Question_list</h2>
	
	<div layout:fragment="content" class="container my-3">
		<table class="table">
			<thead class="table-dark">
				<tr class="text-center">
					<th>번호</th>
					<th style="width: 50%;">제목</th>
					<th>글쓴이</th>
					<th>작성일시</th>
				</tr>
			</thead>
			<tbody>
				<tr class="text-center" th:each="question, loop : ${paging}">
					<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
					<td class="text-start">
						<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
						<span class="text-danger small ms-2" th:if="${#lists.size(question.answerList) > 0}" th:text="${#lists.size(question.answerList)}"></span>
					</td>
					<td>
						<span th:if="${question.author != null}" th:text="${question.author.username}"></span>
					</td>
					<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
				</tr>
			</tbody>
		</table>
		
		<!-- 페이징 처리 시작 -->
		<div th:if="${!paging.isEmpty()}">
			<ul class="pagination justify-content-center">
				<li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
					<a class="page-link" th:href="@{|?page=${paging.number-1}|}">
						<span>이전</span>
					</a>
				</li>
				<li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}" 
				th:if="${page >= paging.number-5 and page <= paging.number+5}" 
				th:classappend="${page == paging.number} ? 'active'" class="page-item">
										<!-- ^ 조건식이 참인 경우 ^ 활성 -->
					<a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
				</li>
				<li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
					<a class="page-link" th:href="@{|?page=${paging.number+1}|}">
						<span>다음</span>
					</a>
				</li>
			</ul>
		</div>
		<!-- 페이징 처리 끝 -->
		
		<a th:href="@{/question/create}" class="btn btn-primary" style="float: right;">질문 등록</a>
	</div>
</body>
</html>

  • question_detail

수정, 삭제, 추천 기능 추가

<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/> -->
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<!-- 질문 -->
		<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
		<div class="card my-3">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
				<div class="d-flex justify-content-end">
					<div th:if="${question.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
						<div class="mb-2">modified at</div>
						<div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<div class="mb-2">
							<span th:if="${question.author != null}" th:text="${question.author.username}"></span>
						</div>
						<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
				<div class="my-3">
					<!-- 추천 -->
					<a href="javascript:void(0);" class="recomend btn btn-sm btn-outline-secondary" th:data-uri="@{|/question/vote/${question.id}|}">
						추천
						<span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></span>
					</a>
					<!-- 수정 -->
					<a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
					th:if="${question.author != null and #authentication.getPrincipal().getUsername == question.author.username}"
					th:text="수정"></a>
					<!-- 삭제 -->
					<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}" class="delete btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
					th:if="${question.author != null and #authentication.getPrincipal().getUsername == question.author.username}"
					th:text="삭제"></a>	<!-- javascript:void(0); : 링크를 클릭해도 페이지를 이동하지 않도록 함 -->
				</div>
			</div>
		</div>
		
		<!-- 답변 개수 -->
		<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
		
		<!-- 답변 반복 -->
		<div class="card my-3" th:each="answer : ${question.answerList}">
			<div class="card-body">
				<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
				<div class="d-flex justify-content-end">
					<div th:if="${answer.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
						<div class="mb-2">modified at</div>
						<div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<div class="mb-2">
							<span th:if="${answer.author != null}" th:text="${answer.author.username}"></span>
						</div>
						<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
				<!-- 추천 -->
				<a href="javascript:void(0);" class="recomend btn btn-sm btn-outline-secondary" th:data-uri="@{|/answer/vote/${answer.id}|}">
					추천
					<span class="badge rounded-pill bg-success" th:text="${#lists.size(answer.voter)}"></span>
				</a>
				<!-- 수정 -->
				<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
				sec:authorize="isAuthenticated()"
				th:if="${answer.author != null and #authentication.getPrincipal().getUsername == answer.author.username}"
				th:text="수정"></a>
				<!-- 삭제 -->
				<a href="javascript:void(0);" th:data-uri="@{|/answer/delete/${answer.id}|}" class="delete btn btn-sm btn-outline-secondary"
				sec:authorize="isAuthenticated()"
				th:if="${answer.author != null and #authentication.getPrincipal().getUsername == answer.author.username}"
				th:text="삭제"></a>
			</div>
		</div>
		
		<!-- 답변 작성 -->
		<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<!-- 			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}"> -->
<!-- 				<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div> -->
<!-- 			</div> -->
			<label for="content">답변 : </label>
			<textarea sec:authorize="isAnonymous()" disabled
			 th:field="*{content}" id="content" rows="15" style="width: 100%;"></textarea>	<br />	<!-- 05.30) -->
			<textarea sec:authorize="isAuthenticated()"							
			th:field="*{content}" id="content" rows="15" style="width: 100%;"></textarea><!-- 로그인상태인 경우, 아닌 경우 두 가지 적용해야 함 -->
			<button type="submit" class="btn btn-primary my-2">답변 등록</button>
		</form>
	</div>
	<script layout:fragment="script" type="text/javascript">
		const delete_elements = document.getElementsByClassName("delete");
		Array.from(delete_elements).forEach(function(element) {
			element.addEventListener('click', function() {
				if (confirm("정말로 삭제하시겠습니까?")) {
					location.href = this.dataset.uri;
				};
			});
		});
		
		const recomend_elements = document.getElementsByClassName("recomend");
		Array.from(recomend_elements).forEach(function(element) {
			element.addEventListener('click', function() {
				if (confirm("정말로 추천하시겠습니까?")) {
					location.href = this.dataset.uri;
				};
			});
		});
	</script>
</body>
</html>

  • question_form
<!DOCTYPE html>
<html 	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
	<div layout:fragment="content" class="container">
		<h5 class="my-3 border-bottom pd-2">질문 등록</h5>
		<form th:object="${questionForm}" method="post">				<!-- 5.30) th:action 속성 없앰. url 기준으로 전송되는 규칙이 있음 -->
			<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"	/>	<!-- 수정 -->
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<!-- 			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}"> -->
<!-- 				<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div> -->
<!-- 			</div> -->
			<div class="mb-3">
				<label for="subject" class="form-label">제목</label>
				<input type="text" th:field="*{subject}" id="subject" class="form-control"	/>	<!-- 이름 대신 사용해야하므로 name 지움 (th:field) -->
			</div>
			<div class="mb-3">
				<label for="content">내용</label>
				<textarea th:field="*{content}" id="content" class="form-control" rows="10"></textarea>
			</div>
			<button type="submit" class="btn btn-primary" style="float: right;">저장하기</button>
		</form>
	</div>
</body>
</html>

  • answer_form
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div layout:fragment="content" class="container">
		<h5 class="my-3 border-bottom pd-2">답변 수정</h5>
		<form th:object="${answerForm}" method="post">
			<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"	/>
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
			<div class="mb-3">
				<label for="content" class="form-label">내용</label>
				<textarea th:field="*{content}" class="form-control" rows="10"></textarea>
				<button type="submit" class="btn btn-primary my-2">저장하기</button>
			</div>
		</form>
	</div>
</body>
</html>

  • layout.html
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<!-- Bootstrap CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/>
	<!-- sbp CSS -->
	<link rel="stylesheet" type="text/css" th:href="@{/style.css}"			/>
<title>Hello SBP !!</title>
</head>
<body>
	<!-- 네비게이션 바 -->
	<nav th:replace="~{navbar :: navbarFragment}"></nav>	<!-- 파일명 :: 프래그먼트 이름 -->

	<!-- 기본 템플릿 안에 들어갈 내용 시작 -->
	<th:block layout:fragment="content"></th:block>
	<!-- 기본 템플릿 안에 들어갈 내용 끝 -->
	
	<!-- Bootstrap JS -->
	<script th:src="@{/bootstrap.min.js}"></script>

	<!-- 자바스크립트 시작 -->
	<th:block layout:fragment="script"></th:block>
	<!-- 자바스크립트 끝 -->
</body>
</html>

2024-05-31

마크다운(MarkDown)

- 마크다운 이란?

  • 마크다운(Markdown)은 텍스트 기반의 마크업 언어
  • 문서 작성 시 간단한 문법을 사용하여 HTML로 변환할 수 있음

- 마크다운의 장점

  • 문법이 쉽고 간결함
    - 관리가 쉬움
    - 별도의 도구없이 작성 가능
    - 다양한 형태로 변환 가능
    - 지원 가능한 플랫폼과 프로그램이 다양함
    - 텍스트(Text)로 저장되기 때문에 용량이 적어 보관이 용이함

- 의존성 추가

  • build.gradle
dependencies {
	implementation 'org.commonmark:commonmark:0.21.0'
}

- CommonUtil 클래스 생성

package com.example.sbp;

import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.stereotype.Component;

@Component
public class CommonUtil {

	public String markdown(String markdown) {		// commonMark~ 임포트
		Parser parser = Parser.builder().build();
		Node document = parser.parse(markdown);		// commonMark~ 임포트
		HtmlRenderer renderer = HtmlRenderer.builder().build();
		
		return renderer.render(document);
	}
}

- question 디테일 수정

  • 마크다운 추가
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}"	/> -->
</head>
<body>
	<div layout:fragment="content" class="container my-3">
		<!-- 질문 -->
		<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
		<div class="card my-3">
			<div class="card-body">
				<!-- 5/30) 마크다운 적용 -->
            <div class="card-text" th:utext="${@commonUtil.markdown(question.content)}"></div>	<!-- utext 링크로 인식하도록 -->
				<div class="d-flex justify-content-end">
					<div th:if="${question.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
						<div class="mb-2">modified at</div>
						<div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<div class="mb-2">
							<span th:if="${question.author != null}" th:text="${question.author.username}"></span>
						</div>
						<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
				<div class="my-3">
					<!-- 추천 -->
					<a href="javascript:void(0);" class="recomend btn btn-sm btn-outline-secondary" th:data-uri="@{|/question/vote/${question.id}|}">
						추천
						<span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></span>
					</a>
					<!-- 수정 -->
					<a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
					th:if="${question.author != null and #authentication.getPrincipal().getUsername == question.author.username}"
					th:text="수정"></a>
					<!-- 삭제 -->
					<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}" class="delete btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
					th:if="${question.author != null and #authentication.getPrincipal().getUsername == question.author.username}"
					th:text="삭제"></a>	<!-- javascript:void(0); : 링크를 클릭해도 페이지를 이동하지 않도록 함 -->
				</div>
			</div>
		</div>
		
		<!-- 답변 개수 -->
		<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
		
		<!-- 답변 반복 -->
		<div class="card my-3" th:each="answer : ${question.answerList}">
			<a th:id="|answer_${answer.id}|"></a>
			<div class="card-body">
				<!--마크다운 적용-->
           		<div class="card-text" th:utext="${@commonUtil.markdown(answer.content)}"></div>
           		<!-- <div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>-->
				<div class="d-flex justify-content-end">
					<div th:if="${answer.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
						<div class="mb-2">modified at</div>
						<div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<div class="mb-2">
							<span th:if="${answer.author != null}" th:text="${answer.author.username}"></span>
						</div>
						<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
					</div>
				</div>
				<!-- 추천 -->
				<a href="javascript:void(0);" class="recomend btn btn-sm btn-outline-secondary" th:data-uri="@{|/answer/vote/${answer.id}|}">
					추천
					<span class="badge rounded-pill bg-success" th:text="${#lists.size(answer.voter)}"></span>
				</a>
				<!-- 수정 -->
				<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
				sec:authorize="isAuthenticated()"
				th:if="${answer.author != null and #authentication.getPrincipal().getUsername == answer.author.username}"
				th:text="수정"></a>
				<!-- 삭제 -->
				<a href="javascript:void(0);" th:data-uri="@{|/answer/delete/${answer.id}|}" class="delete btn btn-sm btn-outline-secondary"
				sec:authorize="isAuthenticated()"
				th:if="${answer.author != null and #authentication.getPrincipal().getUsername == answer.author.username}"
				th:text="삭제"></a>
			</div>
		</div>
		
		<!-- 답변 작성 -->
		<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
			<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<!-- 			<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}"> -->
<!-- 				<div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div> -->
<!-- 			</div> -->
			<label for="content">답변 : </label>
			<textarea sec:authorize="isAnonymous()" disabled
			 th:field="*{content}" id="content" rows="15" style="width: 100%;"></textarea>	<br />	<!-- 05.30) -->
			<textarea sec:authorize="isAuthenticated()"							
			th:field="*{content}" id="content" rows="15" style="width: 100%;"></textarea><!-- 로그인상태인 경우, 아닌 경우 두 가지 적용해야 함 -->
			<button type="submit" class="btn btn-primary my-2">답변 등록</button>
		</form>
	</div>
	<script layout:fragment="script" type="text/javascript">
		const delete_elements = document.getElementsByClassName("delete");
		Array.from(delete_elements).forEach(function(element) {
			element.addEventListener('click', function() {
				if (confirm("정말로 삭제하시겠습니까?")) {
					location.href = this.dataset.uri;
				};
			});
		});
		
		const recomend_elements = document.getElementsByClassName("recomend");
		Array.from(recomend_elements).forEach(function(element) {
			element.addEventListener('click', function() {
				if (confirm("정말로 추천하시겠습니까?")) {
					location.href = this.dataset.uri;
				};
			});
		});
	</script>
</body>
</html>

- 마크다운 확인

  • /question/list 질문 추가
**마크다운 문법 적용**

### 목록1
## 목록2
# 목록3

네이버 홈페이지 [http://naver.com](http://naver.com)입니다

```
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.stereotype.Component;

@Component
public class CommonUtil {

	public String markdown(String markdown) {		// commonMark~ 임포트
		Parser parser = Parser.builder().build();
		Node document = parser.parse(markdown);		// commonMark~ 임포트
		HtmlRenderer renderer = HtmlRenderer.builder().build();
		
		return renderer.render(document);
	}
}
```


키워드 (검색 기능)

- 쿼리문

  • 참고용
  • query.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	select
		distinct q.id,		<!-- 중복 제거 outer 조인이라 중복된값 나옴 -->
		q.author_id,
		q.content,
		q.create_date,
		q.modify_date,
		q.subject
	from question q
		left outer join site_user u1 on q.author_id = u1.id
		left outer join answer a on q.id = a.question_id
		left outer join site_user u2 on a.author_id = u2.id
	where
		q.subject like '%스프링%'
		or q.content like '%스프링%'
		or u1.username like '%스프링%'
		or a.content list'%스프링%'
		or u2.username like '%스프링%'
		
	Specification
</body>
</html>

  • QuestionRepository 수정
package com.example.sbp.question;

import java.util.List;

import org.springframework.data.domain.Page;			// 페이지를 위한 클래스
import org.springframework.data.domain.Pageable;		// 페이징 처리를 하는 인터페이스
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer> {
	
//	Junit 테스트
	Question findBySubject(String subject);
	
//	subject 와 content 두 개의 엔티티 속성(컬럼) 조회하기 위한 메서드
	Question findBySubjectAndContent(String subject, String content);
	
//	like 포함하는게 하나가 아닐수도있으므로 List 로 받음
	List<Question> findBySubjectLike(String subject);
	
	Page<Question> findAll(Pageable pageable);
//	+PageRequest : 현재 페이지와 한 페이지에 보여줄 게시물 수 등을 설정하여 페이징을 요청하는 클래스
	
	Page<Question> findAll(Specification<Question> spec, Pageable pageable);		// 5/31) 추가
}

- Question 서비스 수정

  • QuestionService
package com.example.sbp.question;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import com.example.sbp.DataNotFoundException;
import com.example.sbp.answer.Answer;
import com.example.sbp.user.SiteUser;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	private Specification<Question> search(String kw) {			// 5/31) 추가
		return new Specification<>() {
			private static final long serialVersionUID = 1L;
			
			@Override								// query.html 참조
			public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb) {	// Root 자료형 Question 객체를 기준으로 잡음 
				query.distinct(true);				// v  foreign key
				Join<Question, SiteUser>u1 = q.join("author", JoinType.LEFT);
				Join<Question, Answer> 	a  = q.join("answerList", JoinType.LEFT);
				Join<Answer, SiteUser> 	u2 = a.join("author", JoinType.LEFT);
				return cb.or(cb.like(q.get("subject"), 	"%" + kw + "%"),		// 제목			// where 절
							 cb.like(q.get("content"), 	"%" + kw + "%"),		// 내용
							 cb.like(u1.get("username"),"%" + kw + "%"),		// 질문 작성자
							 cb.like(a.get("content"), 	"%" + kw + "%"),		// 답변 내용
							 cb.like(u2.get("username"),"%" + kw + "%"));		// 답변 작성자
			}
		};
	}
	
	public Page<Question> getList(int page, String kw){			// 5/31)
		List<Sort.Order> sorts = new ArrayList<Sort.Order>();
		sorts.add(Sort.Order.desc("createDate"));
		
		Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));	// 조회 할 페이지 번호, 한 페이지에서 보여질 개수
		Specification<Question> spec = search(kw);
		
		return this.questionRepository.findAll(spec, pageable);
	}
	
	public List<Question> getList() {
		
		return this.questionRepository.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if (question.isPresent()) {
			
			return question.get();
		}else {
			throw new DataNotFoundException("아이디가 없음");
		}
	}
	
	public void create(String subject, String content, SiteUser user) {		// 05.30)
		Question question = new Question();
		question.setSubject(subject);
		question.setContent(content);
		question.setCreateDate(LocalDateTime.now());
		question.setAuthor(user);
		
		this.questionRepository.save(question);
	}
	
	public void modify(Question question, String subject, String content) {
		question.setSubject(subject);
		question.setContent(content);
		question.setModifyDate(LocalDateTime.now());
		
		this.questionRepository.save(question);
	}
	
	public void delete(Question question) {
		this.questionRepository.delete(question);
	}
	
	public void vote(Question question, SiteUser siteUser) {
		question.getVoter().add(siteUser);
		this.questionRepository.save(question);
	}
}

- Question 컨트롤러 수정

  • QuestionController
package com.example.sbp.question;

import java.security.Principal;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;

import com.example.sbp.answer.AnswerForm;
import com.example.sbp.user.SiteUser;
import com.example.sbp.user.UserService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
@RequestMapping("/question")
public class QuestionController {
	
	private final QuestionService questionService;
	private final UserService userService;
	
//	@GetMapping("/")			// http://localhost:8080 '/' <- 이 부분
//	public String root() {		// Question 의 메인페이지 - list
//		
//		return "redirect:/question/list";
//	}
	
	@GetMapping("/list")	// /list?page=0		default값 0으로 지정		검색 키워드가 Null 일때를 대비하여 디폴트값 공백 넣어줌
	public String list(Model model, @RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value="kw", defaultValue = "") String kw) {
		Page<Question> paging = this.questionService.getList(page, kw);
		model.addAttribute("paging", paging);
		model.addAttribute("kw", kw);			// 검색결과 화면에 보여주기위해 넘김
		
		return "question_list";
	}
	
	@GetMapping("/detail/{id}")	// http://localhost:8080/question/list/2 번호 바뀜
	public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm) {	
											// @PathVariable("id") id와 매핑된 id 이름이 동일해야함
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		
		return "question_detail";
	}
	
	@PreAuthorize("isAuthenticated()")		// 5.30)
	@GetMapping("/create")
	public String questionCreate(QuestionForm questionForm) {
		
		return "question_form";
	}
	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/create")
	public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal) {
//		@valid : 이 객체가 유효한지 검증하는 어노테이션
		if (bindingResult.hasErrors()) {	// 에러가 있는 경우에 다시 form 으로 돌려보냄
			return "question_form";
		}
		SiteUser siteUser = this.userService.getUser(principal.getName());
		this.questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser);
		
		return "redirect:/question/list";
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/modify/{id}")
	public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
		Question question = this.questionService.getQuestion(id);
		if(!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		questionForm.setSubject(question.getSubject());
		questionForm.setContent(question.getContent());
		
		
		return "question_form";
	}
	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/modify/{id}")
	public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult, @PathVariable("id") Integer id, Principal principal) {
		if (bindingResult.hasErrors()) {
			return "question_form";
		}
		
		Question question = this.questionService.getQuestion(id);
		if(!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
		}
		this.questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
		return String.format("redirect:/question/detail/%s", id);
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/delete/{id}")
	public String questionDelete(Principal principal, @PathVariable("id") Integer id) {
		Question question = this.questionService.getQuestion(id);
		if (!question.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제 권한이 없습니다.");
		}
		this.questionService.delete(question);
		
		return "redirect:/question/list";
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/vote/{id}")
	public String questionVote(@PathVariable("id") Integer id, Principal principal) {
		Question question = this.questionService.getQuestion(id);
		SiteUser siteUser = this.userService.getUser(principal.getName());
		this.questionService.vote(question, siteUser);
		
		return String.format("redirect:/question/detail/%s", id);
	}
}

- 리스트 수정

  • question_list.html
<!DOCTYPE html>
<html	xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- <link rel="stylesheet" text="text/css" th:href="@{/bootstrap.min.css}"	/> -->

</head>

<body>
	<h2>Hello Question_list</h2>
	
	<div layout:fragment="content" class="container my-3">
		<div class="row my-3">
			<div class="col-6">
				<div class="input-group">
					<input type="text" id="search_kw" class="form-control" th:value="${kw}"	/>
					<button class="btn btn-outline-secondary" type="button" id="btn_search">검색</button>
				</div>
			</div>
			<div class="col-6">
				<a th:href="@{/question/create}" class="btn btn-primary" style="float: right;">질문 등록</a>
			</div>
		</div>
		<table class="table">
			<thead class="table-dark">
				<tr class="text-center">
					<th>번호</th>
					<th style="width: 50%;">제목</th>
					<th>글쓴이</th>
					<th>작성일시</th>
				</tr>
			</thead>
			<tbody>
				<tr class="text-center" th:each="question, loop : ${paging}">
					<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
					<td class="text-start">
						<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
						<span class="text-danger small ms-2" th:if="${#lists.size(question.answerList) > 0}" th:text="${#lists.size(question.answerList)}"></span>
					</td>
					<td>
						<span th:if="${question.author != null}" th:text="${question.author.username}"></span>
					</td>
					<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
				</tr>
			</tbody>
		</table>
		
		<!-- 페이징 처리 시작 -->
		<div th:if="${!paging.isEmpty()}">
			<ul class="pagination justify-content-center">
				<li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
					<a class="page-link" href="javascript:void(0)" th:data-page="${paging.number-1}">	<!-- 5/31) 수정/ 검색기능 추가 -->
						<span>이전</span>
					</a>
				</li>
				<li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}" 
				th:if="${page >= paging.number-5 and page <= paging.number+5}" 
				th:classappend="${page == paging.number} ? 'active'" class="page-item">
										<!-- ^ 조건식이 참인 경우 ^ 활성 -->
					<a th:text="${page}" class="page-link" href="javascript:void(0)" th:data-page="${page}"></a>
				</li>
				<li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
					<a class="page-link" href="javascript:void(0)" th:data-page="${paging.number+1}">
						<span>다음</span>
					</a>
				</li>
			</ul>
		</div>
		<!-- 페이징 처리 끝 -->
		<form th:action="@{/question/list}" method="get" id="searchForm">
			<input type="hidden" id="kw" name="kw" th:value="${kw}"	/>
			<input type="hidden" id="page" name="page" th:value="${paging.number}"	/>
		</form>
	</div>
	<script layout:fragment="script" type="text/javascript">
		const page_elements = document.getElementsByClassName("page-link");
		Array.from(page_elements).forEach(function(element) {
			element.addEventListener('click', function() {
				document.getElementById('page').value = this.dataset.page;
				document.getElementById('searchForm').submit();
			});
		});
		const btn_search = document.getElementById("btn_search");
		btn_search.addEventListener('click', function(){
			document.getElementById('kw').value = document.getElementById('search_kw').value;
			document.getElementById('page').value = 0;
			document.getElementById('searchForm').submit();
		});
	</script>
</body>
</html>
profile
velini

0개의 댓글