<!--페이징을 위한 카운트 쿼리 수정전-->
<select id="selectTotalProductCount" parameterType="map" resultType="int">
SELECT COUNT(*)
FROM PRODUCT P
LEFT JOIN
CATEGORY C ON P.category_seq = C.category_seq
WHERE 1=1
<!-- 카테고리 필터 -->
<if test="category_seq != 0">
AND P.category_seq = #{category_seq}
</if>
<!-- 검색어 필터 -->
<if test="query != null and query != ''">
AND (P.product_serial ILIKE '%' || #{query} || '%')
OR (P.product_name ILIKE '%' || #{query} || '%')
OR (P.product_brand ILIKE '%' || #{query} || '%')
OR (P.product_height ILIKE '%' || #{query} || '%')
OR (P.product_weight ILIKE '%' || #{query} || '%')
OR (P.product_wh ILIKE '%' || #{query} || '%')
OR (P.product_color ILIKE '%' || #{query} || '%')
OR (P.product_features ILIKE '%' || #{query} || '%')
OR (C.category_name ILIKE '%' || #{query} || '%')
</if>
<!-- 브랜드 필터 -->
<if test="product_brand != null and product_brand != ''">
AND P.product_brand = #{product_brand}
</if>
<!-- 가격대 필터 -->
<if test="priceRange != null and priceRange != ''">
AND (
#{priceRange} LIKE '%below10k%' AND P.product_pay < 10000
OR #{priceRange} LIKE '%10kTo20k%' AND P.product_pay BETWEEN 10001 AND 20000
OR #{priceRange} LIKE '%20kTo30k%' AND P.product_pay BETWEEN 20001 AND 30000
OR #{priceRange} LIKE '%30kTo40k%' AND P.product_pay BETWEEN 30001 AND 40000
OR #{priceRange} LIKE '%40kTo50k%' AND P.product_pay BETWEEN 40001 AND 50000
OR #{priceRange} LIKE '%50kTo70k%' AND P.product_pay BETWEEN 50001 AND 70000
OR #{priceRange} LIKE '%80kTo100k%' AND P.product_pay BETWEEN 80001 AND 100000
OR #{priceRange} LIKE '%above100k%' AND P.product_pay > 100000
)
</if>
</select>
쿼리 수정후
<if test="query != null and query != ''">
AND ( (P.product_serial ILIKE '%' || #{query} || '%')
OR (P.product_name ILIKE '%' || #{query} || '%')
OR (P.product_brand ILIKE '%' || #{query} || '%')
OR (P.product_height ILIKE '%' || #{query} || '%')
OR (P.product_weight ILIKE '%' || #{query} || '%')
OR (P.product_wh ILIKE '%' || #{query} || '%')
OR (P.product_color ILIKE '%' || #{query} || '%')
OR (P.product_features ILIKE '%' || #{query} || '%')
OR (C.category_name ILIKE '%' || #{query} || '%')
)
</if>
<!-- 가격대 필터 -->
<if test="priceRange != null">
AND (
#{priceRange} LIKE '%below10k%' AND P.product_pay < 10000
OR #{priceRange} LIKE '%10kTo20k%' AND P.product_pay BETWEEN 10001 AND 20000
OR #{priceRange} LIKE '%20kTo30k%' AND P.product_pay BETWEEN 20001 AND 30000
OR #{priceRange} LIKE '%30kTo40k%' AND P.product_pay BETWEEN 30001 AND 40000
OR #{priceRange} LIKE '%40kTo50k%' AND P.product_pay BETWEEN 40001 AND 50000
OR #{priceRange} LIKE '%50kTo70k%' AND P.product_pay BETWEEN 50001 AND 70000
OR #{priceRange} LIKE '%80kTo100k%' AND P.product_pay BETWEEN 80001 AND 100000
OR #{priceRange} LIKE '%above100k%' AND P.product_pay > 100000
)
</if>
<select id="selectTotalProductCount" parameterType="map" resultType="int">
SELECT COUNT(*)
FROM PRODUCT P
LEFT JOIN
CATEGORY C ON P.category_seq = C.category_seq
WHERE 1=1
<!-- 카테고리 필터 -->
<if test="category_seq != 0 and category_seq != null">
AND P.category_seq = #{category_seq}
</if>
<!-- 검색어 필터 -->
<if test="query != null and query != ''">
AND ( (P.product_serial ILIKE '%' || #{query} || '%')
OR (P.product_name ILIKE '%' || #{query} || '%')
OR (P.product_brand ILIKE '%' || #{query} || '%')
OR (P.product_height ILIKE '%' || #{query} || '%')
OR (P.product_weight ILIKE '%' || #{query} || '%')
OR (P.product_wh ILIKE '%' || #{query} || '%')
OR (P.product_color ILIKE '%' || #{query} || '%')
OR (P.product_features ILIKE '%' || #{query} || '%')
OR (C.category_name ILIKE '%' || #{query} || '%')
)</if>
<!-- 브랜드 필터 -->
<if test="product_brand != null and product_brand != ''">
AND P.product_brand = #{product_brand}
</if>
<!-- 가격대 필터 -->
<if test="priceRange != null">
AND (
#{priceRange} LIKE '%below10k%' AND P.product_pay < 10000
OR #{priceRange} LIKE '%10kTo20k%' AND P.product_pay BETWEEN 10001 AND 20000
OR #{priceRange} LIKE '%20kTo30k%' AND P.product_pay BETWEEN 20001 AND 30000
OR #{priceRange} LIKE '%30kTo40k%' AND P.product_pay BETWEEN 30001 AND 40000
OR #{priceRange} LIKE '%40kTo50k%' AND P.product_pay BETWEEN 40001 AND 50000
OR #{priceRange} LIKE '%50kTo70k%' AND P.product_pay BETWEEN 50001 AND 70000
OR #{priceRange} LIKE '%80kTo100k%' AND P.product_pay BETWEEN 80001 AND 100000
OR #{priceRange} LIKE '%above100k%' AND P.product_pay > 100000
)
</if>
</select>
</mapper>
최초 기능 설계시
검색기능과 필터/정렬 기능을 각각 분리하여 SQL 문을설계 진행 -> 기능을 구현후 테스트 진행시 결과조건은 동일하고
category_seq 와 query(검색어) 유무에 따른 결과 분리
currentPage:
설명: param.page가 비어 있으면(예: 첫 진입 시), 기본값으로 1을 설정합니다.
개인, 기업, 정부가 자금을 모으고 사용하며, 투자, 대출, 보험, 자산 관리 등의 활동을 포함
금융 공학
금융 문제를 해결하기 위해 수학, 통계, 경제학, 컴퓨터 과학 등의 방법을 사용하는 학문으로, 파생상품 가격 설정, 리스크 관리, 투자 전략 개발 등에 활용
금융기관
금융기관은 자금의 중개 역할
은행, 보험사, 증권사 등
예금, 대출, 투자, 보험 등의 서비스
2. 주요 금융 자산
주식
기업의 소유권을 나타내는 증권
주식을 보유한 투자자는 해당 기업의 지분을 갖게 됨
주식 시장에서 매매, 배당금과 자본 이익을 통해 수익
효율적 시장 가설(EMH): 전체 투자자에게 공개된 정보가 주가에 즉시 반영되어 주식 시장이 항상 공정하고 효율적으로 작동. 주가는 이미 모든 정보를 반영하며, 지속적 추가 수익은 어렵다고 가정.
채권
정부나 기업이 자금을 조달하기 위해 발행하는 부채 증서
투자자는 이자 수익, 만기 시 발행자는 원금을 상환
안전한 투자 수단, 이자율, 신용 등급, 만기 기간에 따른 가치
신용평가: 차입자의 채무 상환 능력을 평가, 신용 등급 부여. 신용 등급에 따라 대출 조건 등이 영향
파생상품
기초 자산의 가치에 기반한 금융 계약
선물, 옵션, 스왑 등이 포함
파생상품을 통해 리스크를 관리하거나 투기
가격은 기초 자산의 가격 변동에 따라 결정
ABS: 자산유동화증권으로 담보 자산에서 발생하는 현금 흐름을 기초로 발행된 증권. 주택담보대출, 자동차대출, 신용카드 채권 등이 있으며, 이 증권을 통해 다양한 자산의 현금 흐름에 투자하며, 발행자는 자금을 조달할 수 있음
리스크 관리: 위험을 식별, 평가한 후, 최소화하기 위한 전략을 수립. 예를 들어 분산 투자, 헤징, 보험, 내부 통제 등
3. 수익율
수익율
수익률: 투자 자산의 초기 가치 대비 변화율, 퍼센트로 표현
로그수익률: 자연 로그를 이용한 수익률, 시간의 흐름에 따른 연속 복리 효과를 반영, log(p(t)/p(t-1))로 표현하며 정규분포를 가정한 금융 모델에 유용, 로그수익률은 서로 더할 수 있어, 여러 기간 수익률 계산에 활용, 상승과 하락 변동이 대칭적으로 나타나서 비대칭성이 줄고 분석이 용이함
복리와 현재 가치
원금에 이자가 붙고 다시 그 이자에 이자가 붙는 방식
시간에 따라 자산이 기하급수적으로 증가하는 효과
현재 가치: 미래의 금액을 현재 시점에서 평가한 가치. 투자 결정과 재무 계획에서 중요한 개념. 할인율을 사용
포트폴리오
다양한 자산의 집합, 분산 투자와 리스크 관리를 통해 수익 극대화 및 손실 최소화 목표
주식, 채권, 현금, 부동산, 파생상품 등 다양한 자산으로 구성
투자 목표, 리스크 허용도, 투자 기간에 따라 구성비율 변화
자산 배분, 리밸런싱, 시장 변화에 대한 대응 등
CAPM
CAPM(자본자산가격결정모형)은 자산의 기대수익률을 시장 위험과 무위험 수익률을 기반으로 계산하는 모델
@Controller
public class ChatController {
@GetMapping("/chat.do")
public String chat() {
return "chat/chatting";
}
}
WebSocketConfig
@Component
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketServer
//@Service ...필수아님
//@Service 어노테이션은 해당 클래스를 관리하며,웹 소켓 연결과 관련된 라이프사이클 이벤트를 처리하도록 하는데 필수적인 역할
//웹소켓을 통한 실시간 양방향 통신을 가능하게 하는 서버 사이드의 연결점
@ServerEndpoint("/chatserver")
public class WebSocketServer {
// 현재 채팅 서버에 접속한 클라이언트(WebSocket Session) 목록
private static List<Session> list = new ArrayList<Session>();
@OnOpen //웹 소켓이 연결되면 호출되는 이벤트
public void handleOpen(Session session) {
list.add(session); // 접속자 관리
}
@OnClose
// 웹 소켓이 닫히면 호출되는 이벤트
public void handleClose(Session session) {
list.remove(session);
}
@OnError
//웹 소켓 에러가 나면 발생하는 이벤트
public void handleError(Throwable t) {
}
@OnMessage //클라이언트에서 서버 측으로 메시지를 보내면 호출되는 이벤트
public void handleMessage(String msg, Session session) {
// 로그인할 때: 1#유저명
// 대화 할 때: 2유저명#메세지
int index = msg.indexOf("#", 2);
String no = msg.substring(0, 1);
String user = msg.substring(2, index);
String txt = msg.substring(index + 1);
if (no.equals("1")) {
// 누군가 접속 > 1#아무개
for (Session s : list) {
if (s != session) { // 현재 접속자가 아닌 나머지 사람들
try {
s.getBasicRemote().sendText("1#" + user + "#" +txt);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else if (no.equals("2")) {
// 누군가 메세지를 전송
for (Session s : list) {
if (s != session) { // 현재 접속자가 아닌 나머지 사람들
try {
s.getBasicRemote().sendText("2#" + user + ":" + txt);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
WebSocket은 HTML5에서 제공하는 양방향 통신 프로토콜 클라이언트와 서버 간에 **지속적인 연결(persistent connection)**을 유지하면서, 양방향으로 데이터를 실시간으로 주고받을 수 있도록 설계되었습니다. WebSocket은 HTTP와 함께 사용되지만, HTTP와는 다른 독립적인 프로토콜
주요 특징
양방향 통신 (Full-Duplex):
클라이언트와 서버가 서로 동시에 데이터를 주고받을 수 있습니다.
요청-응답 패턴이 아니라, 필요할 때 자유롭게 메시지를 주고받음.
지속적인 연결 유지:
클라이언트와 서버가 연결을 맺은 이후에는 지속적으로 연결을 유지합니다.
새로 요청을 보내거나 연결을 다시 설정하지 않아도 실시간 데이터를 주고받을 수 있습니다.
낮은 오버헤드:
HTTP는 요청마다 헤더를 전송해야 하지만, WebSocket은 초기 핸드셰이크 이후 추가적인 헤더 없이 데이터를 주고받습니다.
이를 통해 대역폭 절약과 지연 시간 감소가 가능합니다.
이벤트 기반 통신:
데이터가 발생할 때마다 실시간으로 전달 가능.
특히 채팅 애플리케이션, 실시간 알림, 주식 데이터와 같은 실시간성이 중요한 애플리케이션에서 유용합니다.
동작 원리
핸드셰이크(Handshake):
WebSocket은 처음에는 HTTP 프로토콜을 사용하여 연결을 시작합니다.
클라이언트가 서버에 WebSocket 연결을 요청하며, 서버가 이를 승인하면 WebSocket 연결이 성립됩니다.
지속적인 데이터 통신:
핸드셰이크 이후에는 WebSocket 프로토콜로 전환되어 양방향 데이터 통신이 이루어집니다.
데이터는 프레임(Frame) 단위로 주고받습니다.
연결 종료:
클라이언트나 서버 중 하나가 명시적으로 연결을 종료하거나 네트워크 문제로 연결이 끊어질 수 있습니다.
WebSocket의 구조
Opcode: 메시지 타입 (텍스트, 바이너리 등)
Payload Length: 메시지 길이
Masking Key: 데이터를 암호화하기 위한 키 (클라이언트->서버 데이터에만 적용)
Payload Data: 실제 데이터
WebSocket 메시지는 다음과 같은 구조로 프레임화됩니다:
WebSocket과 HTTP의 비교특징HTTPWebSocket
통신 방식
요청-응답 (Request-Response)
양방향 (Full-Duplex)
연결 방식
요청마다 새 연결
연결을 유지 (Persistent Connection)
오버헤드
요청마다 헤더를 포함
초기 핸드셰이크 이후 헤더 없음
실시간성
낮음 (폴링 또는 롱 폴링 필요)
높음
주요 활용 사례
정적인 웹 페이지, API
실시간 채팅, 게임, 알림, IoT 데이터 스트리밍
WebSocket의 주요 활용 사례
실시간 채팅:
예: Slack, WhatsApp Web, Discord
실시간 알림 시스템:
예: 이메일 알림, 쇼핑몰의 실시간 주문 상태 업데이트
온라인 게임:
실시간 멀티플레이어 게임에서 유용
금융 데이터 스트리밍:
주식 시장 데이터, 환율 정보 등
IoT(사물 인터넷):
장치 간 실시간 데이터 교환
화상 회의 및 스트리밍:
예: WebRTC와의 통합
// WebSocket 서버에 연결
const socket = new WebSocket('ws://example.com/socket');
// 연결이 열렸을 때 실행
socket.onopen = () => {
console.log('WebSocket 연결 성공!');
socket.send('Hello, Server!');
};
// 메시지를 수신했을 때 실행
socket.onmessage = (event) => {
console.log('서버로부터 메시지:', event.data);
};
// 연결이 닫혔을 때 실행
socket.onclose = () => {
console.log('WebSocket 연결 종료');
};
// 오류가 발생했을 때 실행
socket.onerror = (error) => {
console.error('WebSocket 오류:', error);
};
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (socket) => {
console.log('클라이언트 연결됨');
// 클라이언트로부터 메시지 수신
socket.on('message', (message) => {
console.log('클라이언트 메시지:', message);
socket.send('Hello, Client!');
});
// 연결 종료
socket.on('close', () => {
console.log('클라이언트 연결 종료');
});
});
console.log('WebSocket 서버가 8080 포트에서 실행 중');
JUnit은 Java 프로그래밍 언어를 위한 단위 테스트(Unit Testing) 프레임워크, 단위 테스트는 소프트웨어의 작은 단위(주로 클래스나 메서드)를 독립적으로 테스트하여 올바르게 동작하는지 확인하는 과정, JUnit은 테스트 자동화를 지원하며, 소프트웨어 개발 과정에서 테스트를 쉽고 체계적으로 실행할 수 있도록 도와줍니다.
주요 특징
애너테이션 기반 테스트:
JUnit은 애너테이션을 활용하여 테스트 메서드를 정의
@Test: 테스트 메서드를 나타냅니다.
@BeforeEach / @AfterEach: 각 테스트 전에/후에 실행되는 메서드.
@BeforeAll / @AfterAll: 모든 테스트 전에/후에 한 번 실행되는 메서드.
자동화된 테스트:
개발자가 명시한 테스트를 자동으로 실행하고 결과를 확인함
Assertions 지원:
assertEquals, assertTrue, assertFalse, assertThrows 등 다양한 메서드로 기대값과 실제값을 비교하여 테스트의 성공 여부를 판단
통합 도구와의 호환성:
JUnit은 Gradle, Maven과 같은 빌드 도구 및 Jenkins와 같은 CI/CD 도구와 통합되어 효과적인 테스트 및 배포 환경을 구축
import org.apache.log4j.Logger;
public class Log4jExample {
private static final Logger logger = Logger.getLogger(Log4jExample.class);
public static void main(String[] args) {
logger.trace("This is a TRACE message");
logger.debug("This is a DEBUG message");
logger.info("This is an INFO message");
logger.warn("This is a WARN message");
logger.error("This is an ERROR message");
logger.fatal("This is a FATAL message");
}
}
2. 주요 메서드
메서드설명
logger.trace(String msg)
TRACE 레벨의 로그 메시지를 출력합니다.
logger.debug(String msg)
DEBUG 레벨의 로그 메시지를 출력합니다.
logger.info(String msg)
INFO 레벨의 로그 메시지를 출력합니다.
logger.warn(String msg)
WARN 레벨의 로그 메시지를 출력합니다.
logger.error(String msg)
ERROR 레벨의 로그 메시지를 출력합니다.
logger.fatal(String msg)
FATAL 레벨의 로그 메시지를 출력합니다.
logger.isDebugEnabled()
DEBUG 레벨이 활성화되어 있는지 확인합니다.
logger.isInfoEnabled()
INFO 레벨이 활성화되어 있는지 확인합니다.
Log4j와 Log4j 2
Log4j 1.x: 위에서 설명한 버전으로 오래된 시스템에서 여전히 사용됩니다.
Log4j 2.x: Log4j 1.x의 성능 및 보안 문제를 개선한 최신 버전으로, 비동기 로깅과 동적 로깅 구성이 가능합니다. Log4j 1.x는 더 이상 유지보수되지 않으므로 가능하면 Log4j 2를 사용하는 것이 권장됩니다.
Log4j 관련 보안 이슈
Log4j 2에서 Log4Shell(CVE-2021-44228) 취약점이 발견되었습니다. 공격자가 악의적인 데이터를 이용해 원격 코드 실행(RCE)을 할 수 있는 문제입니다. 해결 방법: