
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); // 콘솔에 출력하는 메소드 호출
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에 저장
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
//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 f = 3.14F; // 식별자 F , f 를 붙여준다
double d = 3.14;
float f = 3.14F;
System.out.println(d); // 3.14
System.out.println(f); // 3.14
float < double
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 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
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
// 서로 다른 타입끼리의 변환 생략하지 마라
// 변환(대입)하려는 쪽의 타입 반드시 적어라
int a =10;
double b = 5.5;
double result = a + b; // 15.5
^ double타입으로 자동 변환
Ex) int type으로 계산 결과를 얻고 싶다면?
Double type 변수를 먼저 int로 변환 후 계산
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
// "" 앞에 숫자 : 산술 연산으로 더해짐
int num1 = 5;
int num2 = 3;
boolean value = (num1 > num2);
System.out.println(value); // true
value = (num1 < num2);
System.out.println(value); // false
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
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
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
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
// 16을 몫이 0이 될 때 까지 8로 나누어 나머지를 끝에서부터 기입함
// 16/8 = 2 나머지 0
// 2/8 = 나눌 수 없음 2 (자기자신)
// 2 0
int num8 = 020;
System.out.println(num8); // 16
// 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
int octal = 020;
// 각 자리의 수를 8의 거듭제곱으로 곱한 후 합산
// 2*8^1 + 0*8^0 = 2*8 + 0*1 = 16
System.out.println(octal); // 16
// 16을 몫이 0이 될 때 까지 16으로 나누어 나머지를 끝에서부터 기입함
// 16/16 = 1 나머지 0
// 1/16 = 나눌 수 없음 1
// 1 0
int num16 = 0x10;
System.out.println(num16); // 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
int hex = 0x10;
// 각 자리의 수를 16의 거듭제곱으로 곱한 후 합산
// 1*16^1 + 0*16^0 = 1*16 + 0*1 = 16
System.out.println(hex); // 16
비트 값을 기반으로 하는 연산자
비트 단위로 연산 이루어짐
// 두개의 비트 값이 모두 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
// 비트 값이 하나라도 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
// 같으면 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
int age = 8;
if(age >= 8){
// 조건식의 결과가 true일 때 수행
System.out.println("학교에 다닙니다.");
}
// 학교에 다닙니다.
int age = 7;
if(age >= 8){
System.out.println("학교에 다닙니다.");
}else{ // 필수 아님. 선택적 사용
System.out.println("학교에 다니지 않습니다.");
}
// 학교에 다니지 않습니다.
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문
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+
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만원
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일까지 있습니다.
int i = 1; // 초기식
while(i <= 10){ // 조건식
System.out.println(i); // 실행문
i++; // 증감식
}
// 1 2 3 4 ~ 10
int i = 1;
while(i <= 100){
System.out.println(i);
i += 2;
}
// 1 3 5 7 ~ 99
// 두가지 필요 : 합이 될 변수 , 반복할 변수
int sum = 0;
int num = 1;
while(num<=10){
sum += num;
num++;
}
System.out.println("1~10까지의 합 = "+sum);
// 1~10까지의 합 = 55
// 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 실행 되었기 때문
int a;
for(a=1; a<=5; a++){
System.out.println(a); // 1 ~ 5 출력
} // for문 끝
/* int d = 1;
for(; ; d++){
System.out.println(d); // 문법상 되지만 쓰지 않음
*/
// 홀수
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 짝수 출력
}
// 홀수 합
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(int a=1; a<=3; a++){
for(int b=1; b<=3; b++){
System.out.println(a+"=="+b);
}
}
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 출력
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
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는 출력되지 않음
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문으로 되돌아감
int a;
for(a=1; a<=3; a++){
if(a == 3){ // a가 3일 경우
continue; // 이후 코드 진행하지 않고 증감식으로
}
System.out.println(a);
}
// 1 , 2 출력
int a;
for(a=1; a<=100; a++){
if((a % 2) == 0){ // 짝수인 경우
continue; // 이후 코드 진행하지 않고 증감식으로
}
System.out.println(a);
}
int num;
int sum = 0;
for(num=1; num<=100; num++){
if(num%2 == 1){ // 홀수
continue; // 이후 코드 진행x
}
sum += num;
}
System.out.println(sum); // 2550
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의 배수
ex) 로그인, 주문, 생산, 관리 등 어떤 행동을 나타내는 것도 가능
: Student
: 이름, 학년, 나이, 학번, 연락처, 주소 등등
: 이러한 클래스의 속성은 클래스 내부에 변수로 선언
: 클래스 속성의 변수를 '멤버 변수'라고 함
class 클래스이름{
멤버 변수;
}
타입 변수이름 = 값;
클래스 변수이름 = new 클래스();
new 클래스(); - 객체를 생성하고 주소를 변수에 대입(리턴)함
// 변수
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
클래스(매개변수, ..){객체의 초기화 코드}
생성자 오버로딩이 많아지면, 중복되는 코드들이 발생
매개변수의 갯수만 다르고, 초기화 내용이 비슷한 경우
이 때, 클래스 변수의 초기화 코드를 가지고있는 생성자를 호출해서 사용
하나의 생성자에 초기화 코드를 집중적으로 작성
나머지 생성자는 초기화 코드를 가지고있는 생성자를 호출해서 사용
생성자에서 다른 생성자를 호출 할 때 사용
클래스(매개변수, ..){
this(매개변수, .. 값);
실행문;
}
this. / this()
반드시 생성자 내부의 첫줄에 사용
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();
}
}
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클래스로 진입 가능
}
}
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; // 다시 대입 불가능. 변경 불가
}
}
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.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 , 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); // 에러 - 본인 클래스 내에서만 가능
읽기 전용 필드가 있을 수 있음 (Getter의 필요성)
외부에서 엉뚱한 값으로 변경할 수 없도록 (Setter의 필요성)
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); // 같은 경로 해시코드 나옴
}
}
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(변수선언 : 반복대상)
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 출력
// 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 사용
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();
}
}
}
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로부터 상속 받음
}
}
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
}
}
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();
}
}
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 참조
}
}
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 를 사용하여 보다 정확한 값 출력
}
}
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;
}
*/
package day0224;
public final class FinalSuper {
}
package day0224;
// public class FinalSub extends FinalSuper {}
// final 붙이면 상속할 수 없음
public class FinalSub {
}
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(); // 가능
}
}
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); // 예외발생
}
// 패키지에 추상 클래스 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();
}
}
interface 인터페이스명{
// 상수 변수
[public static final] 타입 변수명 = 값;
// 추상 메서드
[public abstract] 리턴타입 메서드명(매개변수, ..);
// 8버전 이상
// 디폴트 메서드
[public] default 리턴타입 메서드명(매개변수, ..){구현부}
// 정적 메서드
[public] static 리턴타입 메서드명(매개변수, ..){구현부}
}
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("건전지를 교환합니다.");
}
}
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의 추상 메서드 구현
}
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("건전지를 교환합니다.");
}
}
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();
}
}
기존 인터페이스를 확장해서 새로운 기능을 추가하기 위함
기존 인터페이스 이름과 추상 메서드의 변경 없이 디폴트 메서드의 추가만으로 이미 구현된 객체는 수정 없이 그대로 사용 가능
새로 구현할 객체는 추가된 기능 활용 가능
인터페이스
public interface MyInterface {
// 추상 메서드
public void method1();
/*
기능 추가 필요
public void method2();
추상 메서드는 하위 클래스에서 구현이 필수
구현을 하지 못할 경우 디폴트 메서드를 사용
*/
// 디폴트 메서드
public default void method2() {
System.out.println("MyInterface - method2() 실행");
}
}
// MyInterface 를 구현할 클래스
public class MyClassA implements MyInterface {
@Override
public void method1() {
System.out.println("MyClassA - method1() 실행");
}
}
// 완성 프로그램
// 기능의 추가가 필요하다면
// MyInterface에 기능을 추가해야함
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 {} // 지역 클래스 - 사용 : 해당 지역 내에서만
}
}
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 라는 내부 클래스 속 인스턴스 멤버
}
}
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);
}
}
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();
}
}
// 많이 쓰이진 않지만 필요하다면 쓰이는 방법
public static void main(String[] args) {
String data = null; // String 객체를 참조하고있지 않음
System.out.println(data.toString());
// .toString() : String 객체가 가지고있는 메서드
}
// NullPointerException 예외 발생
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");
}
} // 매개값이 없어서 예외 발생
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 (예외클래스 e (Exception 앞글자 e)) {
예외처리
}finally {
항상 실행하는 코드;
}
public static void main(String[] args) {
try {
// .forName() : 매개값으로 주어진 class 가 존재하면,
// 이 클래스 객체를 리턴함
// 존재하지 않으면 ClassNotFoundException 예외 발생
Class c = Class.forName("java.lang.String2");
// 일반예외 이므로, 컴파일러가 알려줌
} catch (ClassNotFoundException e) {
System.out.println("클래스가 존재하지 않습니다.");
}
}
리턴타입 메서드명(매개변수, ..) throws Exception {
// 반드시 try-catch 블럭 내에서 호출되어야 함
// catch 블럭에서 떠넘겨 받은 예외를 처리해야 함
}
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");
}
클래스 이름 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);
}
힙 메모리에 저장된 객체의 주소 값
.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() 메서드도 동일한 해시코드 값 리턴
}
원본 객체를 보호할 때 사용
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);
}
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
}
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]);
}
}
ex) String str = "hello";
str += " world";
"hello world"
한 개의 String 객체가 사용되었다고 생각하지만
새로운 String 객체가 생성됨
그리고 str 변수는 새로운 객체를 참조함
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));
}
}
<Ε> : 제네릭 으로 타입 지정 가능
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);
}
}
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));
}
}
}
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);
}
}
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);
}
}
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); // 같은타입 같은값 중복안됨
}
}
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);
}
}
값만 저장 : List, Set ..
값에 이름을 붙여 저장 : Map ..
구현 클래스
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);
}
}
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;
}
}
}
}
}
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());
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
}
ex) 크롬 2개 이상 사용
서로 독립적
ex) 카톡 - 채팅, 파일 업로드
멀티 스레드를 생성해서 멀티 태스킹을 수행함
스레드 객체 생성시 반드시 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("메인 종료");
}
}
class 작업스레드 extends Thread {
코드들 ..
public void 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("메인 종료");
}
}
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());
}
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();
}
}
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 자료형으로 형변환 해줘야 함
}
}
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();
}
}
}
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) {}
}
}
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 타입으로 받음
// 인트타입이지만, 인트범위를 넘긴 파일이있을 수 있음
}
}
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 으로 읽어야 함
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();
}
}
} // 타입 순서에 맞게 읽어야함
<head>
제목을 포함하는 HTML 문서의 머리(HEAD) 부분임을 알려줌
모든 HTML 문서에는 HEAD 가 필요함
일반적으로 HTML에서는 대소문자를 구별하지 않음 < HEAD > 와 < head > 는 같음
<title>Document</title>
문서의 제목(TITLE)을 나타내므로, 전체적인 문서의 내용을 대표할 수 있는 주제어를 담게함
문서를 표시하는 영역에는 나타나지 않으나, 웹 브라우저 대체적으로 맨 위에 표시됨
문서를 대표하는 목록, 북마크, 창 등으로 표시되고 검색에서도 사용되기 때문에
생략하지 않는것이 좋음
</head>
<body>
HTML 문서에서 문서의 본체(BODY)를 말하며, 문서의 내용이 들어가는 주된 부분임
거의 모든 부분의 명령 엘리먼트(= 요소)들이 이곳에 들어감
</body>
BR 태그 <br />
01, 02 파일을 실행해 보면 <BR>
줄바꿈을 했음에도 적용되지 않음 <BR />
주로 사용하는 줄바꿈 태그는 <br /> 태그임
<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>
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>
<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 />
<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>
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 />
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. <HEAD> 표시하면 < HEAD >로 표현됨 <br />
왼쪽 꺽쇠( < )는 태그의 시작, 오른쪽 꺽쇠( > )는 태그의 종료를 의미함
앤드기호( & )는 특수문자의 시작을 알림
HTML 문서에서 특수한 의미를 지니고 있으므로,
일반 문서 내부에서는 사용할 수 없음
2. 일반적으로 문서 편집기에 없는 문자를 표현하기 위해 사용됨 <br />
<br />
이중 따옴표( " )는 " 로 사용해도 되고,
고급의 복잡한 문서에서는 반드시 " 로 사용해야 할 경우도 있음
<br />
문자들을 HTML 에서 사용하려면, 그 문자들의 문자기호(escape 기능)를 사용해야 함
<br />
< 혹은 < : ( < )의 문자 기호 <br />
> 혹은 > : ( > )의 문자 기호 <br />
혹은   : ( ) 공간의 문자 기호 <br />
© 혹은 © : 저작권의 문자 기호 <br />
<br />
∫ 혹은 ∫ : 인테그랄 <br />
∑ 혹은 ∑ : 합계
<CENTER><H4>중앙에 위치</H4></CENTER>
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="#ffffdd" text="#00a0a0">
<CENTER>
<br />
<HR size=2 width=60% color=red>
</CENTER>
</BODY>
<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>
<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>
<DL>
<DT> 목록 제목1
<DD>목록 내용 1-1
<DD>목록 내용 1-2
<DT> 목록 제목2
<DD>목록 내용 2-1
<DD>목록 내용 2-2
</DL>
<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 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>
브라우저에 따라 표현이 차이가 있음
브라우저에 따라 표현이 차이가 있음
<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
<FORM>
기본 버튼 : <INPUT type="button" name="buttonname" value="확인" /> <br />
이미지 : <INPUT type="image" name="imagename"
src="이미지 주소" onClick="alert('클릭하면 나오는 팝업 내용')" />
</FORM>
<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>
<FORM>
입력하세요. <br />
<TEXTAREA name="text" rows="5" cols="50"></TEXTAREA>
<INPUT type="submit" value="입력완료" />
<INPUT type="reset" value="다시입력" />
</FORM>
내용과 디자인이 구분되어 있으면 사이트의 내용을 수정할 때
디자인에 전혀 영향을 미치지 않아 편리하고,
반대로 디자인을 수정할 때도 내용을 건드리지 않고 수정 가능함
기본형 선택자 { 속성1:속성값1; 속성2:속성값2; }
P { text-align:center; color:blue; }
P {
text-align:center; /* 텍스트 정렬 - 중앙 */
color:blue; /* 글자색 - 파랑 */
}
: 두가지 모양 모두 같은 것임
두 번째 것이 가독성이 좋고, 주석을 쓸 수도 있음
/* 주석 */
<P>안녕하세요</P>
<body>
<h1>레드향</h1>
<p style="color:blue;">껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
<p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
</body>
<style>
h1 {
padding:10px;
background-color:#222;
color:#fff;
}
</style>
<body>
<h1>레드향</h1>
<p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<p>레드향은 한라봉과 귤을 교배한 것으로 일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
<p>비타민 C와 비타민 P가 풍부해 혈액순환, 감기예방 등에 좋은 것으로 알려져 있다.</p>
</body>
<!-- 외부 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>
클래스명은 임의로 지정함
기본형
.클래스명 {스타일 규칙}
클래스 스타일은 여러곳에 적용 가능함
또한, 요소 하나에 클래스 스타일을 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>
기본형
#아이디명 {스타일 규칙}
<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>
2가지 방법이 있음
<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>
HTML과 CSS와 달리
자바스크립트는 대소문자 구별한다.
변수 이름이나 함수를 지정할 때에는 대소문자를 정확하게 구별해야 한다.
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>
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>
기본형 alert(메시지)
: 괄호 안에 따옴표( "" 또는 '' )와 함께 메시지를 넣어주면 된다.
<body>
<script>
alert("안녕하세요.");
</script>
</body>
기본형 confirm(메시지)
<body>
<script>
var reply = confirm("창을 닫으시겠습니까?");
</script>
</body>
기본형 prompt(메시지) 또는 prompt( 메시지,기본값 )
: 기본값을 지정하거나 지정하지 않을 수 있다.
기본값을 지정하면 텍스트 부분 안에 기본값이 표시된다.
기본값을 지정하지 않으면 빈 텍스트 부분으로 표시된다.
<body>
<script>
var neme = prompt("이름을 입력해주세요" , "김자바");
</script>
</body>
기본형 document.write()
<body>
<script>
document.write("<h1>어서오세요.</h1>");
</script>
</body>
<body>
<script>
var name = prompt("이름을 입력해주세요.");
console.log(name + "님, 환영합니다.");
</script>
</body>
<!--
웹 브라우저 화면에는 아무런 변화 없다.
ctrl + shift + J 를 눌러 콜솔창을 열 수 있다.
실행 결과가 콘솔창에 표시된다.
-->
<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>
<form>
<input type="button" value="클릭해 보세요." onClick="alert('안녕하세요!')" />
</form>
웹 사이트나 애플리케이션의 유지보수 시간, 비용 절감 효과
// 한줄주석
/* 여러줄 주석 */
// 기본형 var 변수명;
var currentYear; <!-- 올해 연도 -->
var birthYear; <!-- 태어난 연도 -->
var age; <!-- 나이 -->
* 값 저장/할당
= 기호 사용
예)
var currentYear;
currentYear = 2024;
var currentYear = 2024;
var birthYear = 1984;
var age = 10;
<body>
<script>
var userNumber = prompt("숫자를 입력하세요.");
<!--
입력창 prompt();
- 아무것도 쓰지 않더라도 입력을 누르면 값이 넘어감
- 공백도 값임
- 즉, 기본값 null 아님
- 취소를 눌러야만 null
-->
// prompt(); 입력창 - 기본값이 null 아님
// 따라서, 취소를 눌러야 null
if (userNumber % 3 === 0){
alert("3의 배수 입니다.");
}else {
alert("3의 배수가 아닙니다."); // 입력값이 null
}
</script>
</body>
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>
<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>
<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>
<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>
<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>
<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>
<body>
<script>
function addNumber(){
var num1 = 2;
var num2 = 3;
var sum = num1+num2;
alert("결과값 : "+sum);
}
addNumber();
addNumber();
</script>
</body>
<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>
그러나, 예약어 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>
즉, 변하지 않는 값을 변수로 선언할 때 사용함
<body>
<script>
const currentYear = 2024;
console.log(currentYear);
// const currentYear; // 재선언 불가 - 오류
// currentYear = 2023; // 재할당 불가 - 오류
console.log(currentYear);
</script>
</body>
<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>
<body>
<script>
function addNumber (num1, num2){
var sum = num1 + num2;
return sum; // 반환하는 값 지정
}
var result = addNumber(2, 3); // 결과를 받을 변수 선언 = 함수 호출(2, 3 : 인수);
document.write("두 수를 더한 값 : "+result);
</script>
</body>
<body>
<script>
var sum =
function (a, b){
return a+b;
}
document.write("함수 실행 결과 : "+sum(10, 20));
</script>
<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>
키보드 이벤트
키보드에서 특정 키를 조작할 때 발생
문서 로딩 이벤트
서버에서 웹 문서를 가져오거나
문서를 위/아래 로 스크롤 하는 등
웹 문서를 브라우저 창에 보여주는 것과 관련
폼 이벤트
폼은 로그인, 검색, 게시판, 설문조사 처럼
사용자가 입력하는 모든 요소를 가리킴
폼 요소에 내용을 입력하면서 발생하는 이벤트
<body>
<%!
// 선언문 - 클래스 영역
static String name="java";
int number = 10;
public String getName () {
return name;
}
%>
</body>
<%!
// 선언문 - 클래스 영역
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 />");
%>
<body>
<%!
// 선언문 - 클래스 영역
static String name="java";
int number = 10;
public String getName () {
return name;
}
%>
<h1><%=name %></h1>
<%=number %>
<%=getName() %>
</body>
<body>
<h2>A Test of Comments</h2>
<%-- This comment will not be visible in the page source --%>
</body>
<body>
<%for (int dan=2; dan<=9; dan++) { %>
<%for (int num=1; num<=9; num++) { %>
<%=dan%> x <%=num%> = <%=dan*num %><br/>
<%} %>
<%} %>
</body>
<%@ 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>
<%
// 인스턴스 메서드 영역
int z = 500;
web.test.jsp.Tv t = new web.test.jsp.Tv();
%>
표현문 (jsp에서의 출력문) <br />
<%= a %> <br />
<%= x %> <br />
<%= z %> <br />
<%= t %>
buffer의 양을 늘리겠느냐 = "true" : 자동으로 늘리겠다는 의미
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<h1>top</h1>
<hr color="skyblue" />
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<%@ include file="top.jsp" %>
<form action="pro.jsp" method="post">
이름 : <input type="text" name="name" /> <br />
나이 : <input type="text" name="age" /> <br />
<input type="submit" value="입력완료" />
</form>
<% 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>");
}
%>
<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>
<% 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+" 입니다.");
}
%>
<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>
<% 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+" 권역입니다.");
%>
<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" />
2학년 : <input type="radio" name="grade" value="2" />
3학년 : <input type="radio" name="grade" value="3" />
<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>
<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>
<%@ 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 />");
}
%>
<H3>현재 페이지는 ex03(responseRedirect).jsp 입니다.</H3>
<%
response.sendRedirect("ex04.jsp");
%>
<H3>현재 페이지는 ex04(Redirect된 페이지).jsp 입니다.</H3>
ex03.jsp -> ex04.jsp 이동
<H2>out 내장객체 - out.println() 사용</H2>
<%
String name = "HTML";
out.println("출력되는 내용 <B>"+name+"</B> 입니다.");
%>
<H2>out 내장객체 - 표현식 사용</H2>
<%
String name2 = "JSP";
%>
출력되는 내용 <B><%=name2 %></B> 입니다.
<%
String info = application.getServerInfo();
String path = application.getRealPath("/");
application.log("로그 기록 : ");
%>
웹 컨테이너의 이름과 버전 : <%= info %> <br />
웹 어플리케이션 폴더의 로컬 시스템 경로 : <%= path %>
<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>
<% 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 의 나머지 부분
이 과정에서 클라이언트는 전달된 서블릿이 실제로 요청을 처리한 서블릿인지 알 수 없음
<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>
<% 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 포워드 -완- <%-- 내용 출력되지 않음 --%>
Cookie coo = new Cookie("coo", "test");
// 쿠키 유효기간 : 초 단위
coo.setMaxAge(30); // 유효기간 30초 설정 - 생성 후 30초 지나면 자동 삭제
// .setMaxAge(0); // 로그아웃
// 클라이언트(브라우저) 전달
response.addCookie(coo);
%>
<h1>쿠키 생성 -완-</h1>
<%
// 쿠키 생성
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>
<%
Cookie[] cookies = request.getCookies();
for (Cookie c : cookies) {
out.println(c.getName()+" : "+c.getValue()); // key : value
out.println("<br />");
}
// 60초 후 새로고침으로 다시 확인
%>
<%--
404 error
- 문서를 찾을 수 없음
- 주로 사용자가 잘못된 페이지를 요청했을 경우
- URL 확인. 올바르게 입력했는지 확인
500 error
- 서버 내부 오류
- 웹 서버가 요청 사항을 수행할 수 없을 경우에 발생
- 프로그램 코딩 확인
실제 현재 에러처리 방법
- web.xml 페이지에 작성
- 전체 파일 설정
- 프로젝트이름.xml 생성
.xml
- 태그 사용하는 것은 HTML 과 같음
- 그러나 지정된 태그는 없음
- DTD 스키마 파일의 맨 위에 있음
--%>
<%
// 서버 실행(켜저있는) 상태에서 어디서든 사용 가능
// 서버에 저장되기 때문에 어디서든(페이지가 달라도) 꺼낼 수 있음
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>
<%
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>
<%--
서버측에 생성되기 때문에 브라우저에서 출력하여 확인
--%>
<%--
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>
<%@ 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>
// 계정 생성
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 컬럼명
// FROM 테이블명;
SELECT empno
FROM emp;
SELECT empno, ename, sal
FROM emp;
// 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;
// SELECT 컬럼명
// FROM 테이블명 ORDER BY 컬럼명;
SELECT *
FROM emp ORDER BY sal;
SELECT *
FROM emp ORDER BY deptno, sal DESC, hiredate;
// 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'; // 숫자는 >초과, <미만
// 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';
// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 > 값 OR 컬럼명 = 값;
SELECT *
FROM emp
WHERE deptno = 10 OR deptno = 20;
SELECT ename, job
FROM emp
WHERE job = 'MANAGER' OR job = 'CLERK';
// SELECT 컬럼명
// FROM 테이블명
// WHERE NOT 컬럼명 > 값;
SELECT *
FROM emp
WHERE NOT sal = 3000; // NOT 은 컬럼명 앞에 붙음
SELECT *
FROM emp
WHERE sal != 3000;
// 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');
// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 NOT IN(값, 값, 값, ..);
SELECT *
FROM emp
WHERE deptno NOT IN(10, 30, 50);
// 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';
// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 NOT BETWEEN 값 AND 값;
SELECT *
FROM emp
WHERE sal NOT BETWEEN 1500 AND 3000;
// SELECT 컬럼명
// FROM 테이블명
// WHERE 컬럼명 IS NULL;
SELECT *
FROM emp
WHERE comm IS NULL;
SELECT *
FROM emp
WHERE comm IS NOT NULL;
// 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%';
// 비교하는 대상의 컬럼수와 타입이 같아야함
// 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;
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%');
SELECT ename, LENGTH(ename)
FROM emp
ORDER BY LENGTH(ename) DESC;
SELECT ename, sal, deptno
FROM emp
WHERE LENGTH(ename) = 4;
SELECT LENGTH('한글'), LENGTHB('한글')
FROM DUAL;
// 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; // 공백, 대문자 구분
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의 인덱스의 위치를 검색
SELECT REPLACE('010-1234-5678','-',' ')
FROM DUAL; // - 를 띄어쓰기 로 변경
SELECT job, REPLACE(job, 'M', '-')
FROM emp; // M을 - 로 변경
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;
SELECT CONCAT(ename, job)
FROM emp;
SELECT CONCAT(empno, ename)
FROM emp;
SELECT ename||'-'||job
FROM emp; // 이름, 직업 사이에 - 기호 추가
SELECT TRIM(' oracle ')
FROM DUAL; // 양쪽 공백 지움
SELECT LTRIM('<oracle>', '<')
FROM DUAL; // 왼쪽 < 기호 지움
SELECT RTRIM('<oracle>', 'le>')
FROM DUAL; // 오른쪽 le> 문자와 기호까지 지움
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; // 음수는 올림 하지 않음
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
// 주어진 숫자보다 작거나 같은 가장 큰 정수를 반환함 즉, 내림하는 역할을 함
SELECT MOD(15, 6)
FROM DUAL; // 앞자리 수를 뒷자리 수로 나눈 나머지 값 %
SELECT MOD(15,0)
FROM DUAL; // 0 나누면 자기자신
SELECT SYSDATE
FROM DUAL; // 오늘 날짜 출력
SELECT SYSDATE, SYSDATE-1, SYSDATE+8
FROM DUAL; // 오늘날짜-1일, 오늘날짜+8일
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;
SELECT MONTHS_BETWEEN(SYSDATE, hiredate)
FROM emp; // 날짜 차이가 소수점자리 까지 나옴
SELECT MONTHS_BETWEEN(SYSDATE, hiredate), ROUND(MONTHS_BETWEEN(SYSDATE, hiredate)) AS MONTH
FROM emp; // 소수점 반올림
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;
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;
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;
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;
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; // 기준 컬럼 없이 비교 가능
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 값은 제외
SELECT SUM(sal) AS 총급여,
MAX(sal) AS 최대급여,
MIN(sal) AS 최소급여,
TRUNC(AVG(sal)) AS 평균급여,
COUNT(*) AS 사원수
FROM emp
WHERE deptno = 10;
SELECT deptno AS 부서번호,
COUNT(*) AS 사원수,
SUM(sal) AS 총급여
FROM emp
GROUP BY deptno;
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;
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전체,
// 전체 부서의 총 급여정보 출력
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;
SELECT *
FROM emp, dept
WHERE emp.deptno(+) = dept.deptno;
SELECT a.ename, b.ename AS MANAGER
FROM emp a, emp b
WHERE a.empno = b.mgr;
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');
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');
create table 테이블(
컬럼명 타입 옵션,
컬럼명 타입 옵션,
컬럼명 타입 옵션,
......
컬럼명 타입 옵션
);
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 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 INTO member(id,pw) VALUES('spring','1111');
INSERT INTO member(id, pw, name) VALUES('jsp', '2222', 'aaaa');
INSERT INTO member(id, pw, birth) VALUES('css', '3333', '92/03/03');
COMMIT;
UPDATE member SET pw = '5555';
UPDATE member SET pw = '1234', name = 'guest', age = 20;
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 FROM member;
DELETE FROM member WHERE id = 'java';
SELECT * FROM member;
COMMIT;
DROP TABLE member;
COMMIT;
CREATE TABLE test (
num NUMBER PRIMARY KEY,
name VARCHAR2(100),
reg DATE DEFAULT SYSDATE
);
COMMIT;
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 test RENAME COLUMN name TO nick;
SELECT *
FROM test;
ALTER TABLE test DROP COLUMN nick;
SELECT TABLE_NAME
FROM USER_TABLES;
SELECT OWNER, TABLE_NAME
FROM ALL_TABLES;
SELECT *
FROM DBA_TABLES;
SELECT *
FROM DBA_USERS;
SELECT *
FROM DBA_USERS
WHERE USERNAME = 'SCOTT';
SELECT *
FROM USER_INDEXES;
SELECT *
FROM USER_IND_COLUMNS; -- INDEX 줄임말
CREATE INDEX IND_EMP_SAL ON emp(sal);
DROP INDEX IND_EMP_SAL;
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;
SELECT empno, ename, sal
FROM emp;
SELECT empno, ename, sal, ROWNUM
FROM emp;
SELECT empno, ename, sal, ROWNUM
FROM emp
ORDER BY sal;
SELECT e.*, ROWNUM
FROM (
SELECT empno, ename, sal
FROM emp
ORDER BY sal
) e;
SELECT *
FROM (
SELECT e.*, ROWNUM AS r
FROM (
SELECT empno, ename, sal
FROM emp
ORDER BY sal
) e)
WHERE r >= 1 AND r <= 5;
CREATE TABLE test (
num NUMBER PRIMARY KEY,
name VARCHAR2(100)
);
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;
CREATE SEQUENCE test1_seq
START WITH 10
INCREMENT BY 2
MAXVALUE 100
MINVALUE 1
CYCLE
NOCACHE;
<%@ 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();
%>
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;
}
}
<%@ 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>
<%@ 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>
<%@ 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>
<%@ 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();
%>
<%@ 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>
<%@ 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>
<%@ 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>
<%@ 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>
웹 프로그램(웹 애플리케이션)을 쉽고 빠르게 만들 수 있도록 도와주는 자바의 웹 프레임워크
스프링 부트는 스프링(Spring) 프레임워크에 톰캣(Tomcat)이라는 서버를 내장하고, 여러 편의 기능들을 추가하여 개발자들 사이에서 꾸준히 인기를 누리고 있음
웹 프로그램을 완성하려면 쿠키나 세션 처리, 로그인/로그아웃 처리, 권한 처리, 데이터베이스 처리 등 만들어야 할 기능이 많음
하지만 웹 프레임워크를 사용하면 이런 기능을 일일이 만들 필요가 없음. 웹 프레임워크에는 그런 기능들이 이미 만들어져 있기 때문
쉽게 말해 웹 프레임워크는 웹 프로그램을 만들기 위한 스타터 키트
자바로 만든 웹 프레임워크 중 하나가 바로 스프링 부트
스프링 부트의 몇 가지 규칙만 익히면 기존에 자바로 웹 프로그램을 작성하는 방식보다 빠르게 웹 프로그램을 만들 수 있음
크롬이나 사파리와 같은 웹 브라우저에 ‘Hello World’를 출력하려면 다음과 같은 클래스 하나만 작성하면 됨
@Controller
public class HelloController {
@GetMapping("/")
@ResponseBody
public String hello(){
return "Hello World"
}
}
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'
}
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
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; // 답변 리스트
}
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);
}
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
*/
package com.example.sbp.answer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Integer> {
}
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());
}
}
<div th:text="th:내용">그냥 내용</div>
// th:내용 출력
# messages.properties : fileName
content.id=spring boot
content.name=java
version=1.0
date=2000.01.01
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;
}
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";
}
}
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!DOCTYPE html>
<html xmlns:th="">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>Hello Thymeleaf</h3>
</body>
</html>
<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>
<!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>
<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>
<input type="checkbox" id="hobby2" name="hobbies" value="reading" />
<label for="hobby2">독서</label>
<input type="checkbox" id="hobby3" name="hobbies" value="travling" />
<label for="hobby3">여행</label>
<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>
<!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>
반복되는 코드가 자주 등장하며 가독성을 떨어뜨림
ex) Getter, Setter, ToString, Constructor(생성자)
불변 클래스를(Immutable Class) 생성해줌
모든 필드를 Private, Final 로 설정하고, Setter를 생성하지 않음(상수로 만들어줌)
FInal 이 붙기 때문에 Setter는 존재할 수 없음
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;
}
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";
}
}
<!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>
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' // 타임리프
}
spring.application.name=param
# K=V
# log level
logging.level.root=info
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;
}
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);
}
}
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<input type="checkbox" name="hobbies" id="hobby2" value="reading" />
<label for="hobby2">독서</label>
<input type="checkbox" name="hobbies" id="hobby3" value="traveling" />
<label for="hobby3">여행</label>
<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>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
}
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";
}
}
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);
}
}
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);
}
}
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);
}
}
<!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>
<!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>
레이아웃 적용할 페이지 < 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>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
-null 불가
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;
}
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;
}
<!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>
<!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>
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 : 현재 페이지와 한 페이지에 보여줄 게시물 수 등을 설정하여 페이징을 요청하는 클래스
}
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";
}
}
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);
}
}
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);
}
}
<!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>
<!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>
<!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>
<!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>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
}
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();
}
}
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));
}
}
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));
}
}
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(스트림)은 자바 8버전부터 추가된 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식(함수적 스타일)으로 처리할 수 있도록 도와주는 반복자
자바 7버전까지는 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator반복자를 사용하였는데, 자바 8버전부터는 Stream이 등장함
Stream은 Iterator와 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하기에 병렬 처리가 쉽다는 점, 그리고 중간 처리와 최종 처리의 파이프라인 작업을 수행한다는 차이가 있음
내부 반복자는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴
외부 반복자는 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴
index를 이용하여 for문과 Iterator를 이용하는 While문이 대표적인 외부 반복자
내부 반복자의 장점은 요소를 반복시키는 방식에 대한 것은 컬렉션에 맡기고, 개발자는 요소 처리 코드에 만 집중할 수 있음
내부 반복자는 요소들의 반복 순서를 변경하거나, 멀티 코어 CPU를 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기 때문에, 하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있음

한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것
병렬 처리 스트림을 이용하면 런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고, 서브 작업의 결과를 자동으로 결합해서 최종 결과물을 생성함
field : 오류 필드명
errorCode : 오류 코드(이 오류 코드는 메시지에 등록된 코드가 아님 messageResolver를 위한 오류 코드)
errorArgs : 오류 메시지에서 {0} 을 치환하기 위한 값
defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지
rejectValue() 메서드는 특정 필드에 대한 검증 실패를 나타내는 오류를 등록할 때 사용됨
이 메서드는 필드 수준의 검증에서, 필드 값이 특정 조건을 만족하지 않을 경우 오류를 등록하는 데 적합함
reject() 메서드는 객체 수준의 검증 실패를 나타내는 오류를 등록할 때 사용됨
이는 특정 필드에 국한되지 않고, 전체 객체 또는 복합적인 조건에 대한 검증에서 사용됨
BindingResult 는 어떤 객체를 대상으로 검증하는지 target을 이미 알고 있음
따라서 target(item)에 대한 정보는 없어도 됨
rejectValue() 를 사용하고 부터는 오류 코드를 간단하게 입력할 수 있음
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-security'
}
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;
}
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);
}
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;
}
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;
}
}
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;
}
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";
}
}
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();
}
}
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);
}
}
<!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>
<!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>
유효성 검사 기능 추가
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; // 답변 리스트
}
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
*/
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");
}
}
}
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();
}
}
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);
}
}
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);
}
}
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);
}
}
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());
}
}
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 추가
}
}
}
<!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>
수정, 삭제, 추천 기능 추가
<!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>
<!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>
<!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>
<!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>
dependencies {
implementation 'org.commonmark:commonmark:0.21.0'
}
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);
}
}
<!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>
**마크다운 문법 적용**
### 목록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);
}
}
```
<!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>
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) 추가
}
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);
}
}
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);
}
}
<!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>