본문 바로가기

전체 글461

[Spring Boot/JAVA] 익명 채팅 웹 사이트 만들기 (1/3) 옛날부터 웹 소켓을 직접 구현해보고 싶었고 웹 소켓의 대표적인 예시인 채팅 웹 어플리케이션을 하나 만들어보고 싶었다. 채팅 어플리케이션과 웹 소켓이 무슨 상관인가?많이 사용되는 HTTP의 경우는 사용자가 Request를 보내야만 서버에서 Response를 보내는 방식이다.그에 반해 채팅은 서버가 Request가 없어도 사용자에게 데이터를 보내는데,한 번 성립된 연결을 누군가 끊기 전까지 계속 유지하는 웹 소켓의 특성상 이러한 채팅을 구현하기에 적합하다.그래서 해당 프로그램을 만드는 과정을 기재할 게시판도 만들어놓았으나 솔직히 귀찮아서 미루고 또 미루고 있었다.그런데 요즘 주말만 되면 그냥 낮잠을 자거나 유튜브를 보는 것 외에 하는 것도 없는 내 모습을 보며 이 이상 게을러지면 안되겠다는 생각이 들었고, .. 2025. 3. 19.
[Spring Boot] Docker 컨테이너로 띄우기 프로그램을 만들면 그 프로그램이 항상 동일한 환경에서만 돌아갈까? 물론 특정 환경에 최적화되어 그 환경에서만 돌아가는 프로그램이 요구사항이었다면 충분히 그럴 수 있다. 그러면 어느 환경에서도 돌아가야하는 솔루션은 어떨까? 물론 환경이 아무리 제각각이어도 마음먹고 돌리려면 방법이야 많다.하지만 환경이 변할 때마다 그 환경에 맞춰 프로그램을 실행시키는 것은 여간 귀찮은 일이 아니다. 그러면 "어느 환경에서도 동일하게 프로그램을 실행시키고 싶을 때"는 어떻게 하면 될까?  방법이야 여러 가지가 있겠지만, 오늘은 그 방법 중 하나로 Docker 컨테이너로 만드는 방법을 소개하고 싶다.* 실제로 우리 회사는 이렇게 여러 환경에서 돌아가는 솔루션을 만들 때 일단 Docker 컨테이너하 한다.스프링 어플리케이션을 D.. 2025. 3. 11.
[Spring Boot] Quartz Scheduler 아래와 같은 요구사항이 있다고 가정해보자."5분에 한 번씩 A 로직이 실행됐으면 좋겠어요."그러면 간단하게 @Scheduled를 사용하면 구현이 가능할 것이다. @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행public void ALogic() { // A 로직 log.info("@@ A Login Run");}위의 요구사항은 쉽다. "x분에 한 번" 이라는 요구사항이 명확하게 있으니까. 하지만 아래와 같은 요구사항이 있다고 가정하면 어떻게 될까?"A 로직이 동적으로 실행됐으면 좋겠어요. 사용자가 1분에 한 번으로 다섯 개를 실행시킬 수도 있고, 10분에 한 개만 실행시킬 수도 있게요."이러면 @Scheduled 만으로 구현이 까다로워질 것이다. 기본적으로 스케줄링을.. 2025. 2. 20.
[JS] 로컬 스토리지와 세션 스토리지 쇼핑몰에 들어가 로그인을 하지 않은 상태로 장바구니에 담아보자. 그리고 다른 페이지를 전전하다, 다시 장바구니에 들어가보자. 그러면 장바구니는 텅 비어있는가? 아니다. 장바구니에는 아까 담아놓은 상품이 그대로 들어가있다. 서버에서 로그인하지 않은 사용자의 데이터도 관리하는 걸까? 물론 하려면 할 수는 있다. 하지만 굳이 그래야 할까? 로그인하지 않은 사용자의 데이터마저도 저장하면 그만큼 서버 자원과 네트워크 트래픽이 추가로 발생할 것이기에, 시스템에 부담을 줄 수 있다. 그러면 어떻게 해야할까? 방법은 서버가 아니라 클라이언트 측 저장소를 활용하는 것이다. 이를 통해 서버와의 통신을 최소화 하면서도, 사용자가 웹 사이트를 탐색하는 동안 장바구니에 담긴 상품을 유지할 수 있다. 이처럼 클라이언트 측 저장소.. 2025. 2. 6.
[JAVA] Record란 무엇인가? 학교나 학원에서 Java를 배울 때 멤버변수는 private으로 선언하며 getter, setter 등으로 접근 가능하게 하라고 배운다. 이는 객체의 캡슐화를 위해 당연히 해야할 일이다.캡슐화 (encapsulation)객체의 속성과 메서드를 하나로 묶고, 외부에서 직접 접근하지 못하도록 제한한다. setter 함수를 통해서만 데이터를 수정하도록 해서 유효성 방지를 통해 잘못된 데이터 수정을 방지하고,내부 필드가 바뀌어도 getter/setter만 유지하면 외부 코드에는 영향이 없기에 유지보수와 확장성이 좋으며, 객체의 독립성이 유지된다. 또한 getter만 제공함으로써 읽기 전용으로 활용도 가능하다. 객체를 만들 때 getter, setter는 당연히 기재하고, 상황과 필요에 toString, equa.. 2025. 2. 4.
[JAVA] 동시성있는 변수를 사용합시다. 옛날부터 포스팅하고 싶은 내용이 있었는데, 조금 귀찮기도 하고 너무 당연한 거라서 굳이 하지 않고 있었다. 그런데 바로 직전에 API에 여러 개의 요청을 동시에 보내는 테스트 코드도 작성했고 하니이어서 계속해서 포스팅하고 싶었던 내용을 작성해볼까 한다. 동시성을 제어하는 변수 주제는 제목에서 알 수 있듯 "동시성" 이다. 당연한 말이지만 우리가 만드는 어플리케이션은 단일 사용자를 위한 것이 아니다. 다수의 사용자를 위한 것이고, 그러니 다수의 사용자가 오차가 거의 없이 동시에 어플리케이션에 접근하는 것을 유의해서 개발을 수행해야 한다. 그런 것에 개념이 없던 시절이 있었다. 그 때 아래와 같은 요구사항이 있었다. 대충 설명해보자면 아래와 같다.A 서비스에는 B, C 메소드가 있다.누군가 B 메소드를 호출.. 2024. 11. 6.
JUnit Test로 컨트롤러 API 테스트 (RestTemplate, MockMVC) 테스트를 할 때는 서비스단을 호출하는 경우도 있지만, 종종 컨트롤러에 있는 API를 호출하는 경우도 있다. 이는 너무 당연한 거라서 굳이 따로 포스팅을 하지 않았었다. 그런데 오늘도 PM 부장님의 요청으로 한 가지 테스트를 하기로 했다. 테스트는 별 건 아니고, 특정 API를 여러 번 찔러서 평균적으로 속도가 얼마나 걸리는지 확인해보는 일이었다. 굳이 JMeter까지 사용할 건 아닌 것 같아서, 간단하게 Test 코드를 작성하고 돌리려고 했다. 우선 테스트 대상으로 아래와 같은 간단한 API가 있다고 해보자.private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");@GetMapping("/date")public String date().. 2024. 11. 6.
SSE를 사용해 알람 구현 비슷하지만 다른 웹 어플리케이션 두 개를 우리 팀과 다른 팀에서 각자 구현중이다. 정말 상세하게 요구사항을 하나 하나 짚어보면 엄연히 다른 웹 어플리케이션이지만, 어느정도 비슷한 어플리케이션 이니만큼 공통적인 부분은 존재한다.그리고 다른 팀은 우리 팀에 비해 인원이 월등히 많기 때문에 개발 속도가 현저히 빠르고, 이로 인해 공통적인 부분은 해당 팀의 소스코드를 참고해서 개발을 수행중이다. 어쨌든 공통적인 부분 중에는 알람 기능도 존재한다. 별 건 아니고 배치 모듈에서 이상사항을 발견하고 알람을 DB에 Insert하면, 이를 로그인한 사용자들의 화면에 띄워주는 것이다.알람 기능 역시 다른 팀이 먼저 개발을 해놓았기에 이를 참고해서 개발을 했다. 그리고 그 과정에서 SSE라는 새로운 개념을 알게 되었다. 오.. 2024. 10. 29.
Redis도 네트워크 딜레이를 고려해야 한다 이번에 받은 요구사항이다.리스트 페이지에서 특정 데이터 목록을 조회한 후 상세 조회 페이지로 이동한다.상세 조회 페이지에는 "이전", "다음" 게시물 조회 기능이 존재한다.리스트 페이지에서 조회된 순서에 따라 "이전", "다음" 게시물로 이동해야 한다.위 요구사항은 조회를 할 때 사용한 파라미터를 쿼리 파라미터 등으로 넘겨 조회 조건을 유지함으로써 쉽게 구현이 가능하다. 하지만 문제가 하나 있는데, 그것은 바로 상세조회 페이지에서 "상태 변경이 가능"하고, 이 상태가 리스트 페이지에서 "조회 조건"에 포함된다는 것이었다. 예를 들어보자."댓글이 0개"인 게시글만을 필터링해서 목록을 조회했다.가장 첫번째에 있는 A 게시글에 들어간다.댓글을 달았다."다음 게시물로 이동" 버튼을 눌러 다음 게시물인 B 게시글.. 2024. 10. 28.
[Mybatis] Insert 후 자동으로 생성된 PK 가져오기 게시판을 예로 들어보자.아래와 같은 테이블이 존재한다고 가정한다. DROP TABLE IF EXISTS file_info;DROP TABLE IF EXISTS board_info;CREATE TABLE board_info ( `board_id` INT NOT NULL AUTO_INCREMENT, `board_title` VARCHAR(100) NULL, `board_content` VARCHAR(2000) NULL, `writer` VARCHAR(20) NULL, `crtr_dtm` TIMESTAMP NULL, PRIMARY KEY (`board_id`));CREATE TABLE file_info ( `file_id` INT NOT NULL AUTO_INCREMENT, `board_id` INT NOT .. 2024. 9. 14.
[Thymeleaf] 데이터를 동적으로 바꾸기 - replaceWith 재직중인 회사에서는 웹 UI 개발 시 거의 전부 Thymeleaf\ 사용을 선호한다. 그리고 DOM 관리에는 JQuery를, 데이터 로딩에는 Ajax를 사용하는 것을 선호한다. 사실 처음부터 웹 UI 개발을 주로 할 생각은 없었다. 원래는 백엔드 개발자가 되고 싶었고, 현재 회사도 원래는 백엔드 개발자로 들어오게 되었다. 하지만 만성 인력난에 허덕이는 중소기업의 특성상 백엔드 개발만 할 수는 없고, 제안서 작성부터 요구사항 식별, 설계, 개발, 테스트, 배포 등 전과정을 해야만 했다. 웹 UI 개발도 별로 원하지는 않았지만 어쩔 수 없이 해야하는 부분 중 하나였다. 그동안 웹 UI를 개발할 때마다 나는 한 가지 말을 입에 달고 살았다.저는 원래 웹 개발자가 아니에요. 여러 가지 뜻이 함축된 말이었다. 백.. 2024. 9. 14.
Mybatis 중복 쿼리 공통화 (<sql>과 <include>) + Interceptor 이번 프로젝트에서는 4중 조인 등 복잡한 쿼리가 많은 탓에 JPA 대신 MyBatis를 채택해서 사용했다. 그리고 지금까지 MyBatis를 사용하면서 뭣도 모르고 사용하고 있었다는 사실을 눈치챘다. 그것은 바로 지금까지 를 사용한 적이 없다는 것이었다. 어찌됐던 지금이라도 알았으니 다행이라고 생각하고, 연습 및 재활용을 위해 블로그에 기록을 해놓을까 한다. 코드에서 여러 메소드에 중복된 코드가 존재하면 어떻게 할까? 당연한 말이지만 중복된 코드가 여러 군데 존재하면 유지 보수가 어렵다. 만약 해당 코드에서 문제가 발생했을 경우 동일한 모든 코드를 수정해야 하기 때문이다. 게다가 쓸데없이 코드가 길어져 가독성도 저하된다. 이를 위해 중복된 코드가 발견될 경우 메소드로 만드는 "메소드화' 리팩토링을 하곤 .. 2024. 9. 7.