2022.12.13 [TUE]
- Day 91-
수업 주요 목차
- Transaction
- Lombok
Spring Handler Interceptor : DispatcherServlet이 해당 컨트롤러를 호출하기 전,후에 요청과 응답을 제어하는 역할을 한다. 컨트롤러 영역( presentation layer )의 공통 업무인 인증 체크 로직을 담당
- 컨트롤러 실행전 preHandle(request,response,handler)
- 컨트롤러 실행후 postHandle(request,response,handler)
- 응답완료 afterCompletion(request,response,handler)
Spring에서 제공하는 HandlerInterceptor 를 implements하여 위와 같은 메서드를 오버라이딩해서 사용한다
DispatcherServlet -- HandlerInterceptor -- Handler(Controller)
컨트롤러 영역의 공통관심사항을 일괄처리
Lombok : 어노테이션 기반으로 자바 코드를 자동 생성하는 라이브러리
👾 자주 사용하는 롬복 어노테이션 👾
@Getter | get method 생성 |
@Setter | set method 생성 |
@NoArgsConstructor | 파라미터가 없는 기본 생성자를 생성 |
@AllArgsConstructor | 모든 필드 값을 파라미터로 받는 생성자 |
@RequiredArgsConstructor | final field 를 파라미터로 받는 생성자 |
@ToString | toString method 생성 |
@Data | @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode 를 한번에 코드로 생성 |
@Slf4j | logging 선언부 코드를 자동생성 |
@Builder | class에 builder pattern 을 적용해 객체 생성할 수 있게 함 ex ) TestVO vo=TestVO.builder().id("java").name("아이유").money(100).build(); |
lombok.jar 다운로드 확인
터미널 실행 후 cd /jar파일이 있는 위치로 이동/
java -jar lombok.jar 실행하면 설치 exe가 실행되는데
mac은 eclipse ini 파일을 경로설정(?)해주면 된다.
Spring AOP Transaction
@Transactional : AOP 기반 선언적 방식의 트랜잭션 처리
@Transactional 어노테이션을 적용하면 AOP Proxy(대리인) 객체가 Transaction 제어를 한다
해당 서비스 메서드에서 Error 와 RuntimeException(unchecked exception) 계열 Exception 발생하면 자동 rollback
Spring Data 관련 Exception 은 모두 java.lang.RuntimeException 의 하위 Exception 이다
모든 작업이 문제없이 실행되면 commit
소프트웨어 아키텍쳐 계층( layer )
- Presentation Layer ( Controller ) : 어플리케이션의 기능을 웹서비스로 제공
- Service Layer ( Service ) : 비즈니스 로직을 정의 , 트랜잭션 제어
- Persistence Layer ( Repository ) : 데이터 액세스
👾 장점 :
- 계층별로 분리 -> 응집도가 높아짐 -> 중복 감소 , 재사용성 증가
- 계층 즉 layer 별로 추상화를 적용 ( 캡슐화 or 계층구조화 ) 하면 결합도가 낮아져 유지보수성이 향상
- service layer를 분리하면 presentation layer의 기술 변경에 유연하게 대처할 수 있음
- 예를 들면 web page 응답 방식에서 ajax response(rest api) 방식으로 컨트롤러 메커니즘이 변경될 때 service layer 없이 controller 즉 presentation layer에 business logic 이 정의되어 있다면 많은 부분의 작업이 필요하겠지만 비즈니스 로직이 service layer 에 분리되어 응집되어 있다면 단순히 응답 방식을 변경하는 소량의 작업이 하면 됨
- 또한 service layer로 구분되어 있으면 비즈니스 로직 하나의 다양한 방식의 컨트롤러가 연결되어 재사용될 수 있음
DB Transaction : 비즈니스 로직에서 더 이상 나눌 수 없는 하나의 작업단위 ( 여러 세부 작업을 하나로 묶은 실행 단위 )
- 데이터베이스의 상태를 변경하고자 할 때 한번에 수행되어야 하는 연산을 의미 ( 예 - 계좌이체는 출금과 입금의 세부 작업을 하나의 단위로 처리해야 한다 )
트랜잭션 성질 ACID : 트랜잭션의 안전성을 보장하기 위한 성질
- 원자성(Atomicity) : 한 트랜잭션의 세부 연산들이 모두 성공하거나 실패하는 성질(예:이체 작업 중 문제발생시 출금,입금연산 모두 롤백되어야 함)
- 일관성(Consistency) : 트랜잭션이 성공하면 일관적인 db 상태를 유지하는 성질(예: 무결성 제약이 모든 계좌는 잔고가 있어야 한다면 이를 위반하면 트랜잭션은 중단된다.)
- 격리성(Isolation) : 트랜잭션으로 처리되는 중간에 외부 간섭은 없어야 한다.
- 영속성 (Durability) : 성공적으로 완료된(commit) 트랜잭션은 데이터베이스에 영구 보존되어야 하는 성질
상세한 설명은 https://ko.wikipedia.org/wiki/ACID
🧚♂️ 트랜잭션 처리 예: 회원 가입시 포인트 지급이 약속된 업무 business
- 회원 정보와 포인트 정보가 모두 정상적으로 등록될 때 commit
- 세부 작업 진행 중 문제가 발생( Error 와 RuntimeException계열 Exception )하면 진행되었던 모든 작업은 rollback( 취소하고 원상태로 복귀 ) 되어야 한다.
🔎 Eclipse 실습 내용
1. SQL(Self Join) + 예제문제
/*
Join SQL : 여러개의 (or 하나 이상의) 테이블의 정보를 결합하기 위한 SQL
1. INNER JOIN : 테이블 간의 상응하는 컬럼의 정보가 존재할때 조회 :
ex) 부서와 사원테이블에서 부서번호에 해당하는 사원의 사원정보와 부서정보를 조회할때 사용
일반적인 Join 을 의미
여러 테이블을 결합할 때 , 지정한 컬럼의 데이터가 존재할 때 사용하는 join
예 ) 부서 테이블의 부서번호와 사원 테이블의 부서번호가 일치할 때 부서와 사원정보를 join 해서 조회
2. OUTER JOIN : 테이블 간의 상응하는 컬럼의 정보가 존재하지 않을 때에도 조회 :
ex) 부서와 사원테이블 결합시 사원이 존재하지 않는 부서정보까지 모두 조회할때 사용
여러 테이블 중 한 쪽에서는 데이터가 있고 , 다른 한쪽에는 데이터가 없는 경우
데이터가 있는 쪽 테이블의 내용을 모두 출력
예 ) 부서 테이블에는 40번 부서정보가 존재하는 데
사원 테이블에는 40번 부서의 사원정보가 존재하지 않을 경우
사원이 없는 부서 테이블의 부서정보도 모두 조회하기 위해 Outer Join 을 이용
3. SELF JOIN : 동일한 테이블 상에서의 조인
예 ) 사원 테이블의 매니저 컬럼 정보는 또 다른 사원의 사원번호이다.
이를 이용해 사원 정보와 그 사원의 관리자인 매니저 정보를 함께 조회할 때 self join 을 사용
*/
/*
1. Inner Join : 테이블 간의 상응하는 컬럼의 정보가 존재하면 조회
*/
-- 사원은 10,20,30번 부서에만 속해 있음
SELECT * FROM emp;
SELECT distinct deptno FROM emp;
-- 10,20,30,40 번 부서가 있음
SELECT* FROM dept;
-- INNER JOIN : 부서 테이블(dept) 과 사원 테이블(emp) 의 정보를 결합
-- 사원 정보와 그 사원이 속한 부서 정보를 조회 Inner Join : 일치하는 정보가 없는 부서 테이블의 40번 부서 정보는 조회되지 않는다
--oracle join sql
SELECT e.empno, e.ename, e.job, d.deptno, d.dname, d.loc
FROM emp e, dept d
WHERE e.deptno=d.deptno
-- ANSI SQL ( American National Standards Institute : 미국표준협회 ) : 데이터베이스 표준 SQL -> 특정 벤더에 종속적이지 않는 SQL
SELECT e.empno, e.ename, e.job, d.deptno, d.dname, d.loc
FROM emp e
INNER JOIN dept d ON e.deptno=d.deptno
-- INNER JOIN : 사원 테이블(emp) 과 월급등급 테이블(salgrade) 의 정보를 결합
-- sal losal , hisal , grade
-- 사원의 월급 최저액이상 최고액이하 그에 따른 등급
-- SMITH 사원은 sal 이 800 이므로 월급등급은 1이다
SELECT * FROM salgrade;
SELECT * FROM emp WHERE ename='SMITH';
-- SMITH 사원의 empno,ename,job,sal,grade 를 INNER JOIN을 이용해 조회
SELECT e.empno, e.ename, e.job, e.sal, s.grade
FROM emp e, salgrade s
WHERE e.ename='SMITH' AND e.sal>=s.losal
--WHERE e.ename='SMITH' AND s.grade=(SELECT grade FROM salgrade WHERE losal<=800)
-- ANSI SQL 로 Inner JOIN 표현
SELECT e.empno, e.ename, e.job, e.sal, s.grade
FROM emp e
INNER JOIN salgrade s ON e.sal>=s.losal
WHERE e.ename='SMITH'
-- SQL 연습 : emp 의 empno가 7521 인 사원의 ename과 dept 의 deptno,dname, salgrade의 grade를 조회해본다
--oracle
SELECT e.ename, d.deptno, d.dname, s.grade
FROM emp e, salgrade s, dept d
WHERE e.empno=7521 AND e.sal>=s.losal AND s.hisal>=e.sal
AND e.deptno = d.deptno
-- ANSI SQL
SELECT e.ename, d.deptno, d.dname, s.grade
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
INNER JOIN salgrade s ON e.sal>=s.losal AND s.hisal>=e.sal
WHERE e.empno=7521
/*
2. OUTER JOIN : 여러 테이블 중 한 쪽에서는 데이터가 있고 , 다른 한쪽에는 데이터가 없는 경우
데이터가 있는 쪽 테이블의 내용을 모두 출력
예 ) 부서 테이블에는 40번 부서정보가 존재하는 데
사원 테이블에는 40번 부서의 사원정보가 존재하지 않을 경우
사원이 없는 부서 테이블의 부서정보도 모두 조회하기 위해 Outer Join 을 이용
*/
-- 부서테이블에는 10,20,30,40번 부서정보가 존재
SELECT DISTINCT deptno FROM dept;
-- 사원테이블에는 10,20,30번 부서 사원들이 존재
SELECT DISTINCT deptno FROM emp;
-- inner join 에서는 결과행이 14 : 40번 부서에 해당하는 사원이 존재하지 않으므로 부서테이블의 40번 정보는 조회되지 않는다
SELECT e.empno, e.ename, d.deptno, d.dname, d.loc
FROM emp e
INNER JOIN dept d ON e.deptno=d.deptno
--사원이 존재하지 않는 부서 (40번 부서) 정보까지 모두 조회하고자 한다면 Outer Join 이 필요하다.
-- outer join : 조인 즉 결합할 정보가 없는 측 조건에 (+) 기호를 명시 , 부서테이블의 부서정보 40 이 존재 , 사원테이블의 40번 사원정보가 존재 x
-- outer join 에서는 결과행이 15 : 40번 부서에 해당하는 사원이 존재하지 않아도 부서테이블의 40번 부서정보를 조회한다
--oracle sql
SELECT e.empno, e.ename, d.deptno, d.dname, d.loc
FROM emp e, dept d
WHERE e.deptno(+)=d.deptno
-- ANSI SQL : LEFT Outer Join -> 왼쪽 테이블을 기준으로 해서 오른쪽 테이블의 정보를 결합
SELECT e.empno, e.ename, d.deptno, d.dname, d.loc
FROM dept d
LEFT OUTER JOIN emp e ON e.deptno=d.deptno
-- RIGHT OUTER JOIN -> 오른쪽 테이블을 기준으로 결합
SELECT e.empno, e.ename, d.deptno, d.dname, d.loc
FROM emp e
RIGHT OUTER JOIN dept d ON e.deptno=d.deptno
/*
3. SELF JOIN : 동일한 테이블 상에서의 조인
동일한 테이블이지만 개념적으로 다른 정보를 결합
예 ) 사원 테이블의 매니저 컬럼 정보는 또 다른 사원의 사원번호이다.
이를 이용해 사원 정보와 그 사원의 관리자인 매니저 정보를 함께 조회할 때 self join 을 사용
*/
SELECT * FROM emp;
-- 7902 사원번호의 매니저 번호 7566 이다 , 7566 은 또 다른 사원의 사원번호이다 -> 사원의 매니저 번호란 관리자의 사원번호를 의미한다
SELECT empno,ename,mgr FROM emp WHERE empno=7902;
SELECT empno,ename,mgr FROM emp WHERE empno=7566;
-- 7902 empno 의 ename과 mgr , 매니저명(또 다른 사원의 ename) 을 조회하고자 한다 : 이 때 self join을 이용
--oracle sql
SELECT e.ename, m.ename AS mgrEname
FROM emp e, emp m
WHERE e.empNo=7902 AND e.mgr=m.empNo
--ANSI
SELECT e.ename, m.ename AS mgrEname
FROM emp e
JOIN emp m ON e.mgr=m.empNo
WHERE e.empNo=7902
-- 전체 사원을 대상으로 SELF JOIN 해서 사원의 e.empno,e.ename,e.mgr,m.ename as 매니저명을 모두 조회해본다
--oracle sql
SELECT e.ename, m.ename AS mgrEname
FROM emp e, emp m
WHERE e.mgr=m.empno
--ANSI
SELECT e.ename, m.ename AS mgrEname
FROM emp e
INNER JOIN emp m ON e.mgr=m.empno
-- 위 self join sql을 실행하면 13명의 사원이 조회된다
-- 총사원수는 14명 , 이유는 mgr이 없는 king 사원이 제외되었기 때문에 13명이 조회된다 -> self join 이 inner join 으로 적용되었기 때문
-- mgr 즉 매니저가 존재하지 않는 사원까지 모두 조회하고자 한다면 Outer Join을 적용하면 된다
-- Outer Join 연산기호 (+) 는 존재하지 않는 정보의 조건에 명시 : king 사원정보는 존재하고 매니저 정보가 존재하지 않으므로 매니저 측에 (+) 을 명시
SELECT e.ename, m.ename AS mgrEname
FROM emp e, emp m
WHERE e.mgr=m.empno(+)
-- ANSI SQL로 표현 : table1 LEFT OUTER JOIN table2 : 왼쪽의 table1을 기준으로 table2 정보를 결합 , table 1의 모든 정보가 조회
SELECT e.ename, m.ename AS mgrEname
FROM emp e
LEFT OUTER JOIN emp m ON e.mgr=m.empno
-- JOIN SQL 연습
-- empno 가 7369인 사원의 사원명ename,부서명dname,월급등급grade,매니저명mgr의 ename 을 조회
-- table : emp , dept , salgrade
SELECT e.ename, d.dname, s.grade , m.ename
FROM emp e, dept d, emp m, salgrade s
WHERE e.deptno=d.deptno
AND e.mgr=m.empno
AND e.sal>=s.losal
AND s.hisal>=e.sal
AND e.empno=7369
-- ANSI SQL
SELECT e.ename, d.dname, s.grade , m.ename
FROM emp e
INNER JOIN dept d ON e.deptno=d.deptno
INNER JOIN emp m ON e.mgr=m.empno
INNER JOIN salgrade s ON e.sal>=s.losal AND s.hisal>=e.sal
WHERE e.empno=7369
-- 답 : SMITH , RESERCH , 1, FORD
-- JOIN SQL 연습
-- 전체 사원의 사원명ename,부서명dname,월급등급grade,매니저명mgr의 ename 을 조회하되 매니저가 없는 사원까지 모두 조회한다
-- table : emp , dept , salgrade
-- outer join 연산기호 (+) 는 데이터가 없는 측의 조인조건에 기술
SELECT e.ename,d.dname,s.grade,m.ename
FROM emp e, dept d, salgrade s, emp m
WHERE e.mgr=m.empno(+)
AND e.sal>=s.losal
AND s.hisal>=e.sal
AND e.deptno=d.deptno
-- ANSI SQL
SELECT e.ename,d.dname,s.grade,m.ename
FROM emp e
JOIN dept d ON e.deptno=d.deptno
LEFT OUTER JOIN emp m ON e.mgr=m.empno
INNER JOIN salgrade s ON e.sal>=s.losal AND s.hisal>=e.sal
/******************************************/
-- join 연습을 위한 테이블 SQL
DROP TABLE DEPT;
-- 부서 테이블
CREATE TABLE DEPT(
DEPTNO NUMBER PRIMARY KEY,
DNAME VARCHAR2(14) ,
LOC VARCHAR2(13)
) ;
DROP TABLE EMP;
-- 사원 테이블
-- foreign key ( 부서 정보를 참조)
CREATE TABLE EMP(
EMPNO NUMBER PRIMARY KEY, -- 사원번호
ENAME VARCHAR2(10),--사원명
JOB VARCHAR2(9),-- 직종
MGR NUMBER, -- 매니저 사원 번호
HIREDATE DATE, -- 입사일
SAL NUMBER, -- 월급
COMM NUMBER, -- 커미션
DEPTNO NUMBER,
CONSTRAINT fk_deptno2 FOREIGN KEY(deptno) REFERENCES DEPT(deptno)
);
INSERT INTO DEPT VALUES
(10,'ACCOUNTING','NEW YORK');
INSERT INTO DEPT VALUES (20,'RESEARCH','DALLAS');
INSERT INTO DEPT VALUES
(30,'SALES','CHICAGO');
INSERT INTO DEPT VALUES
(40,'OPERATIONS','BOSTON');
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
(7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);
INSERT INTO EMP VALUES
(7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);
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
(7788,'SCOTT','ANALYST',7566,to_date('13-7-1987','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO EMP VALUES
(7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);
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-7-1987','dd-mm-yyyy'),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
(7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO EMP VALUES
(7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);
COMMIT
SELECT count(*) FROM dept; -- 4
SELECT count(*) FROM emp; -- 14
SELECT * FROM emp;
DROP TABLE salgrade;
CREATE TABLE salgrade(
grade number PRIMARY KEY,
losal number,
hisal number
)
INSERT INTO salgrade(grade,losal,hisal) VALUES(1,700,1200);
INSERT INTO salgrade(grade,losal,hisal) VALUES(2,1201,1400);
INSERT INTO salgrade(grade,losal,hisal) VALUES(3,1401,2000);
INSERT INTO salgrade(grade,losal,hisal) VALUES(4,2001,3000);
INSERT INTO salgrade(grade,losal,hisal) VALUES(5,3001,9999);
COMMIT
SELECT COUNT(*) FROM salgrade;
SELECT * FROM salgrade;
SELECT * FROM emp;
SELECT count(*) FROM emp WHERE deptno=30;
SELECT ename FROM emp;
2. Lombok Test (프로젝트 새로 만들 때 Lombok dependencies 추가)
- VO1
package org.kosta.myproject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class MyLombokVO {
private String id;
private String name;
private int price;
}
- VO2
package org.kosta.myproject;
import lombok.Data;
@Data
//@Data : @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode 를 한번에 코드로 생성
public class MyLombokVO2 {
private String id;
private String name;
private int price;
}
- VO3
package org.kosta.myproject;
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class MyLombokVO3 {
private String id;
private String name;
private int price;
private String maker;
private int point;
}
- Test
package org.kosta.myproject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MyLombokTest {
@Test
void contextLoads() {
MyLombokVO myLombokVO=new MyLombokVO();
myLombokVO.setId("java");
System.out.println(myLombokVO.getId());
System.out.println(myLombokVO);
}
@Test
void myLombok2Test() {
MyLombokVO2 myLombokVO=new MyLombokVO2();
myLombokVO.setId("java");
System.out.println(myLombokVO.getId());
System.out.println(myLombokVO);
}
@Test
void builderTest() {
MyLombokVO3 mlb=MyLombokVO3.builder().id("java").name("아이유").price(100).maker("edam").point(500).build(); // 변수가 아주 많을 때
System.out.println(mlb);
}
}
↓
java
MyLombokVO2(id=java, name=null, price=0)
java
MyLombokVO2(id=java, name=null, price=0)
MyLombokVO3(id=java, name=아이유, price=100, maker=edam, point=500)
//오늘의 숙제
layout복습+면접준비 따로 하기