HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🚗
⭐ 현대 소프티어 부트캠프 2조 - 카북 ⭐
/
👥
개발 일지
/
😍
백엔드
😍

백엔드

2.1 피어 세션 피드백
  • HttpSession을 사용하여 로그인을 구현할 때, 흐름이 웹 브라우저 → 프론트(웹 서버) → 백엔드(WAS) 이렇게 되는데, 배포할 때 쿠키 처리와 세션에 문제가 있을까?
  • 특장점
    • 차 사진 등록할 때 차 종류, 모델명에 옵션도 추가하면 좋겠다
    • 완성도를 높이자
 
  • 협업 방식(역할 분담)
    • 이슈 별로? API 별로?
 
  • 배포 환경
    • 깃허브 액션
 
  • ec2에 mysql 설치해서 사용 (RDS x)
 
  • 도메인 사용?
    • https://www.gabia.com/
    • ec2 주소를 gabia에 등록
2.10 피어 세션 메모
쿠키 - 세션 allowCredentials

 
❗️유저로부터 이름이 같은 다른 이미지파일이 들어올 수도 있어서 이미지 파일 이름을 어떻게 저장할지 고민해봐야할듯. eg) postId/이미지이름.jpg 로 저장해도 괜찮을듯
 
좋아요 동시성 처리
문제점: 글을 불러올 때마다 좋아요 테이블 전체조회 > 성능 하락
notion image
notion image
999,353 테이블 조회 시간: 92ms(0.092s)
notion image
해결방안
https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/
  • 반정규화
    • 게시글 테이블에 좋아요 개수 칼럼 추가
    • 문제점
    • 데이터 정합성 문제
    • 데이터 정합성을 위해 스케줄러 사용 → post가 많을 경우, 모든 post마다 너무 자주하면 오히려 성능 저하 가능성
    • 성능 저하 생각? 기본적으로 디스크의 read보다 write가 오래걸림
    • + index → 쓰기 성능 저하..? 상관없을듯
    • 고려할 점: 유저가 게시글을 클릭하는 경우마다 join vs 매 n초마다 모든 post 데이터의 like 수 update.
      • 좋아요 수가 많으면 join 때문에 조회 성능 저하 VS 스케줄러 사용 시 전체 post 수가 많으면, 모든 post의 좋아요 수를 한 번에 update하므로 속도 저하
    • 내가 생각하는 타협점: 유저의 팔로워수든, 게시글의 인기수든 판단을 해서 인기많은 것들은 스케줄러, 일반적인 글은 join 사용 (글 조회 횟수가 적으니 join도 적음)
 
좋아요 버튼 광클
좋아요 여부 판단 > 좋아요 true > 좋아요 취소 진행
좋아요 여부 판단 > 좋아요 true > 좋아요 취소 진행 > commit
1번 요청
2번 요청
좋아요
좋아요 여부 판단
ㅤ
true
ㅤ
좋아요 여부 판단
true
좋아요 true
ㅤ
true
ㅤ
좋아요 true
true
좋아요 취소 진행 > isDeleted = 1
ㅤ
false
ㅤ
좋아요 취소 진행 > isDeleted = 1
false
ㅤ
ㅤ
false
ㅤ
ㅤ
false
UnLike Success
UnLike Success
ㅤ
1번 요청
2번 요청
좋아요
좋아요 여부 판단
ㅤ
false
ㅤ
좋아요 여부 판단
false
좋아요 false
ㅤ
false
ㅤ
좋아요 false
false
좋아요 진행 새로 만들거나 isDeleted = 0
ㅤ
true
ㅤ
좋아요 진행 새로 만들거나 isDeleted = 0으로 변경 인데 이미 있다고 판단 > 같은 값으로 덮어씀
true
ㅤ
ㅤ
true
ㅤ
ㅤ
true
Like Success
Like Success
ㅤ
 
hard delete인 경우
1번 요청
2번 요청
좋아요
좋아요 여부 판단
ㅤ
false
ㅤ
좋아요 여부 판단
false
좋아요 false
ㅤ
false
ㅤ
좋아요 false
false
좋아요 진행 새로 만들기
ㅤ
true
ㅤ
좋아요 진행 새로 만들기
true
ㅤ
ㅤ
true
ㅤ
ㅤ
true
Like Success
Like Success
ㅤ
➡️ 좋아요가 2개 삽입됨.
soft delete를 통해 동시성 이슈 해결
 
  • https://velog.io/@korea3611/Spring-boot-좋아요수-증가-분산락을-이용하여-동시성-제어하기-redis활용하기
문제: 좋아요 스케쥴링을 모든 post마다 너무 자주해주면 오히려 성능이 저하될 수 있다…
결과적 일관성: NoSQL에서는 응답시간, 일관성, 지속성의 균형을 위해 결과적 일관성을 구현한다.
 
무한 스크롤
문제점: 게시글 추가할 경우 게시글 중복, 게시글 삭제할 경우 게시글을 보지 못함.
게시글 추가 시
100번 게시글 추가 시 104번 게시글이 중복되어 사용자에게 출력됨.
101
102
103
104
105
106
107
108
100
101
102
103
104
105
106
107
게시글 삭제 시
101번 게시글 삭제 시 105번 게시글이 사용자에게 출력되지 않음.
101
102
103
104
105
106
107
108
102
103
104
105
106
107
108
109
 
해결방안
 
 
인덱스를 게시글 수로 잡지 않고 게시글 id로 잡으면 해결!
게시글 추가 시
104번을 요청으로 보내주면 105번부터 출력해주니 해결
단, 새로운 게시물은 새로고침을 통해 확인
101
102
103
104
105
106
107
108
109
100
101
102
103
104
105
106
107
108
게시글 삭제 시
104번을 요청으로 보내주면 105번부터 출력해주니 해결
단, 사용자가 101번 게시글로 접근 했을 때 예외처리 필요
101
102
103
104
105
106
107
108
102
103
104
105
106
107
108
109
 
비로그인
기존 쿼리
SELECT img.post_id, img.image_url FROM POST AS p INNER JOIN IMAGE AS img ON p.id = img.post_id WHERE p.is_deleted = false ORDER BY p.create_date DESC LIMIT ?, ?"
 
변경 방안
SELECT img.post_id, img.image_url FROM POST AS p INNER JOIN IMAGE AS img ON p.id = img.post_id WHERE p.is_deleted = false and p.id < ?(postId) ORDER BY p.create_date DESC LIMIT ? (size)
 
로그인
기존 쿼리
FROM POST AS p, IMAGE AS img, FOLLOW AS f where f.is_deleted = false and p.is_deleted = false and f.follower_id = ? and f.following_id = p.user_id and p.id = img.post_id ORDER BY p.create_date DESC LIMIT ?, ?
 
변경 방안
FROM POST AS p, IMAGE AS img, FOLLOW AS f where f.is_deleted = false and p.is_deleted = false and p.id < ?(postId) and f.follower_id = ? and f.following_id = p.user_id and p.id = img.post_id ORDER BY p.create_date DESC LIMIT ? (size)
 
검색
PostService의 searchByTags()에서 호출하는 findImagesOfPostsStartsWithIndex() 메서드 로직 수정
 
🎉 2/8(수) 백엔드 첫 배포
백엔드 3명이서 깃헙 액션 설정하고 쉘 스크립트 짜고 자바 깔고 난관 하나씩 해결
ssh -i CarBook.pem ubuntu@ec2-3-39-1-92.ap-northeast-2.compute.amazonaws.com

너무 어려웠던 issues

aws Permission denied 오류 >> 재훈 해결중,, >> 해결완료
macbook16@macbook16ui-noteubug ~ % chmod 400 Carbook-key.pem macbook16@macbook16ui-noteubug ~ % ssh -i CarBook-key.pem ubuntu@ec2-3-37-102-17.ap-northeast-2.compute.amazonaws.comubuntu@ec2-3-37-102-17.ap-northeast-2.compute.amazonaws.com: Permission denied (publickey).
우분투 노드 버전 문제!!!
프론트 - 백엔드 요청 경로 문제 해결!!
https://ohgyun.com/621
dto 필드가 1개면 json 파싱이 안돼요,,,,
https://thalals.tistory.com/380
CSV 저장했는데 글씨가 깨짐…..
스크린샷
notion image
users.to_csv('users.csv', index = False, encoding='cp949')로 해결!
sample data python code
https://colab.research.google.com/drive/1SN3SuVvQHl4MTzi8Gzn-vxDqVuBrQ4b-?usp=sharing
mysql 시간 문제
https://velog.io/@taelee/mysql에서-9시간-차이날때GCP
Repository test in-memory testdb(h2)
@JdbcTest 어노테이션 추가하고 진행 > datasource 찾지 못하는 오류
@AutoConfigureTestDatabase(replace = Replace.NONE) 추가 > mysql로 데이터소스 가져옴
h2 데이터베이스 build.gradle에 추가, application-test.properties로 h2 설정
테스트 클래스에서 datasource를 @Autowired로 받아와서 해결!
h2와 mysql 문법이 달라서 계속 오류 > datasource url에 mode = mysql;추가하면 해결된다
>해결안됨 > create_table.sql에 SET MODE MYSQL 추가해서 해결!
 
mysql, h2 keyholder
mysql: biginteger
h2: int
생성된 id 값의 이름도 다름
mysql: GENERATED_KEY
h2: ID
 
 

개발 메모

주형 개발 메모
2/6 월 TODO LIST
@ExceptionHandler 공부 및 적용
Controller에서 ResponseEntity 생성
isLoginSuccess > login 함수명 변경
@methodargumentnotvalidexception >> @Valid 예외 처리
클래스명 SignupNicknameDuplicateException보다 NicknameDuplicateException
email, nickname 중복 메세지 api 문서대로
Test 코드 작성
DB 연결
methodargumentnotvalidexception handling 안돼서 bindexception으로 처리
 
2/7 화 로그아웃 메모
https://joalog.tistory.com/81 로그아웃 메모
2/8 수
엔진x origin null?
2/10 금 aws bulk insert
load data local infile "/home/ubuntu/sample_data/users.csv" into table USER fields terminated by "," ignore 1 lines (email, password, nickname);
load data local infile "/home/ubuntu/sample_data/posts.csv" into table POST fields terminated by "," ignore 1 lines (user_id, create_date, update_date, content, model_id);
load data local infile "/home/ubuntu/sample_data/images.csv" into table IMAGE fields terminated by "," ignore 1 lines (post_id, image_url);
load data local infile "/home/ubuntu/sample_data/follows.csv" into table FOLLOW fields terminated by "," ignore 1 lines (follower_id, following_id);
load data local infile "/home/ubuntu/sample_data/models.csv" into table MODEL fields terminated by "," ignore 1 lines (type_id, tag);
load data local infile "/home/ubuntu/sample_data/post_hashtags.csv" into table POST_HASHTAG fields terminated by "," ignore 1 lines (post_id, tag_id);
load data local infile "/home/ubuntu/sample_data/post_likes.csv" into table POST_LIKE fields terminated by "," ignore 1 lines (user_id, post_id);
load data local infile "/home/ubuntu/sample_data/tags.csv" into table HASHTAG fields terminated by "," ignore 1 lines (tag);
load data local infile "/home/ubuntu/sample_data/types.csv" into table TYPE fields terminated by "," ignore 1 lines (tag);
sudo mysql --local-infile=1 -u root -p
set global local_infile = 1;
 
2/20 월 auto_increment 된 pk값 변경
SET @count = 0;
update POST set id=@count:=@count+1;
update POST set id=@count:=@count+1 order by create_date;
2/21 화
 
조회 성능
 
notion image
반정규화 전: 118.9 ms
notion image
반정규화 후: 103.85 ms
 
삽입 성능
 
notion image
반정규화 전: 72.25 ms
notion image
반정규화 후: 82.44 ms
 
explain select count(id) from POST_LIKE where post_id = 18 and is_deleted = false;
notion image
 
explain select like_count from POST where id = 18 and is_deleted = false;
notion image
 
  • ref : 인덱스로 지정된 컬럼끼리의 '=' , '<=>' 와 같은 연산자를 통한 비교로 수행되는 조인이다
  • const: 하나의 매치되는 행만 존재하는 경우. 하나의 행이기 때문에 상수로 간주되며, 한번만 읽어들이기 때문에 무척 빠르다.
  • rows: 이 값은 쿼리 수행에서 MySQL이 찾아야하는 데이터행 수의 예상값을 나타낸다. 추정 수치이며 항상 정확하지 않다.
    •  
update POST set like_count = (select count(POST_LIKE.id) from POST_LIKE where POST_LIKE.post_id = POST.id)
notion image
notion image
notion image
notion image
notion image
select convert(argument using utf8) as query from mysql.general_log where convert(argument using utf8) like 'SELECT id, user_id, create_date, update_date, content, model_id FROM POST%';
select convert(argument using utf8) as query from mysql.general_log where convert(argument using utf8) like 'SELECT id, user_id, create_date, update_date, content, model_id, like_count FROM POST%';
select convert(argument using utf8) as query from mysql.general_log where convert(argument using utf8) like 'insert into POST_LIKE(user_id, post_id) values %';
 
select count(convert(argument using utf8)) as selectQueryCnt from mysql.general_log where convert(argument using utf8) like 'SELECT id, user_id, create_date, update_date, content, model_id, like_count FROM POST%';
 
select count(convert(argument using utf8)) as insertQueryCnt from mysql.general_log where convert(argument using utf8) like 'insert into POST_LIKE(user_id, post_id) values %';
 
set global general_log='on';
set global log_output='table';
 
 
 
수민 개발 메모
2/6 (월)
  • 키워드(해시태그)를 통한 게시물 검색 SQL
    • public List<Post> searchByHashtags(List<Integer> hashtagIds, int size, int index) { String whereStatement = createWhereStatement(hashtagIds); String query = "SELECT p.id, p.user_id, p.create_date, p.update_date, p.content " + "FROM POST AS p " + "INNER JOIN POST_HASHTAG AS ph " + "ON p.id = ph.post_id WHERE " + whereStatement + " ORDER BY p.create_date DESC LIMIT ?, ?"; return jdbcTemplate.query(query, postRowMapper(), index, size); }
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
SELECT 쿼리문에서의 문법 순서와 실행 순서는 서로 다릅니다. 쿼리문의 실행 순서를 알고 쿼리를 작성하면 보다 효율적인 쿼리를 작성할 수 있습니다.
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
https://soo-vely-dev.tistory.com/220
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
ON vs WHERE] ON : JOIN 을 하기 전 필터링을 한다 (=ON 조건으로 필터링이 된 레코들간 JOIN이 이뤄진다) WHERE : JOIN 을 한 후 필터링을 한다 (=JOIN을 한 결과에서 WHERE 조건절로 필터링이 이뤄진다) [INNER JOIN 에서의 ON vs WHERE] 1.
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
https://developyo.tistory.com/121
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
2/7 (화)
  • 여러 개의 repostory를 사용하여 쿼리를 여러 개 보내기 VS 여러 개의 테이블을 다중 조인하여 하나의 sql을 보내기
    • 여러 repository를 만들어 객체의 책임을 분리하였는데, 쿼리를 여러 개 보내면 성능 저하의 문제가 있지 않을까? 테이블을 다중 조인하여 하나의 sql을 보내는 게 낫지 않을까?
  • 여러 개의 테이블을 다중 조인하여 하나의 sql을 보내기 로 결정!
    • 쿼리를 여러 개 보내면 네트워크로 통신하기 때문에 속도가 심각하게 저하될 수 있다.
2/8 (수) - AWS 배포
  • Docker + Github Action + Spring Boot 자동배포환경 만들기
  • Github Actions CD: AWS EC2 에 Spring Boot 배포하기
  • [SpringBoot] Github Action으로 AWS EC2 자동 빌드/배포하기(CI/CD)

스크립트로 배포하는 방법

GitHub Actions yml 작성 방법 1
GitHub Actions yml 작성 방법 2
main.yml
name: Deploy to Amazon EC2 on: push: branches: [ "main" ] jobs: deploy: name: Deploy runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Run Script on EC2 Server uses: appleboy/ssh-action@master with: key: ${{ secrets.SSH_KEY }} host: ${{ secrets.HOST }} username: ubuntu script: | /home/ubuntu/run.sh
Github의 Settings → secrets and variables → actions → New repository secret
  • HOST: ec2-3-37-102-17.ap-northeast-2.compute.amazonaws.com
SSH_KEY
-----BEGIN RSA PRIVATE KEY-----
….
----END RSA PRIVATE KEY-----
 
⭐[AWS] 스프링 부트 배포 스크립트 생성
run.sh
#!/bin/bash REPOSITORY=/home/ubuntu PROJECT_NAME=Team2-CarBook cd $REPOSITORY/$PROJECT_NAME/ echo "> Git Pull" git pull cd backend/carbook echo "> 프로젝트 Build 시작" ./gradlew build echo "> step1 디렉토리로 이동" cd $REPOSITORY echo "> Build 파일 복사" cp $REPOSITORY/$PROJECT_NAME/backend/carbook/build/libs/*.jar $REPOSITORY/ echo "> 현재 구동중인 애플리케이션 pid 확인" CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID" if [ -z "$CURRENT_PID" ]; then echo "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다." else echo "> kill -15 $CURRENT_PID" kill -15 $CURRENT_PID sleep 5 fi echo "> 새 애플리케이션 배포" JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1) echo "> JAR Name: $JAR_NAME" nohup java -jar \ -Dspring.config.location=/home/ubuntu/Team2-CarBook/backend/carbook/src/main/resources/application.properties \ -Dspring.profiles.active=real \ $REPOSITORY/$JAR_NAME 2>&1 &
 
⭐우분투에 자바 11 설치하기
JAVA_HOME의 시스템 변수 설정이 필요하다면 다음과 같이 ~/.bashrc 파일에 아래 내용을 추가합니다.
  • vim ~/.bashrc 마지막에 다음을 추가
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH="$PATH:$JAVA_HOME/bin"
  • java가 설치 경로 확인: ls -l /usr/lib/jvm
  • source ~/.bashrc
  • echo JAVA_HOME
 
📌
~/Team2-CarBook/backend/carbook/src/main/resources/application.properites 파일 직접 넣어주기!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://3.39.1.92:3306/carbook?serverTimezone=UTC&characterEncoding=UTF-8 spring.datasource.username=carbook spring.datasource.password=carbook1234

문제

  • 정상적인 경우에 foreground에서 동작되어서 github action이 끝나지 않음

해결방법

background에서 실행되도록 run.sh 변경
nohup java -jar \ -Dspring.config.location=/home/ubuntu/Team2-CarBook/backend/carbook/src/main/resources/application.properties \ -Dspring.profiles.active=real \ $REPOSITORY/$JAR_NAME > back.log 2> back.err < /dev/null &
run.sh
#!/bin/bash REPOSITORY=/home/ubuntu PROJECT_NAME=Team2-CarBook cd $REPOSITORY/$PROJECT_NAME/ echo "> Git Pull" git pull cd backend/carbook echo "> 프로젝트 Build 시작" ./gradlew build echo "> step1 디렉토리로 이동" cd $REPOSITORY echo "> Build 파일 복사" cp $REPOSITORY/$PROJECT_NAME/backend/carbook/build/libs/*.jar $REPOSITORY/ echo "> 현재 구동중인 애플리케이션 pid 확인" CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID" if [ -z "$CURRENT_PID" ]; then echo "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다." else echo "> kill -15 $CURRENT_PID" kill -15 $CURRENT_PID sleep 5 fi echo "> 새 애플리케이션 배포" JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1) echo "> JAR Name: $JAR_NAME" nohup java -jar \ -Dspring.config.location=/home/ubuntu/Team2-CarBook/backend/carbook/src/main/resources/application.properties \ -Dspring.profiles.active=real \ $REPOSITORY/$JAR_NAME > back.log 2> back.err < /dev/null & echo "backend server start"
2/9 (목) - AWS 배포(+프론트)

문제

  • AWS 우분투 서버 접속이 안됨
ubuntu@ec2-3-37-102-17.ap-northeast-2.compute.amazonaws.com: Permission denied (publickey).

해결방법

새로운 EC2 인스턴스를 파기로 결정함 ㅠㅠ
 
  • vim ~/.bashrc 마지막에 JAVA_HOME 추가 X
프론트엔드 코드도 같이 빌드
run.sh
#!/bin/bash REPOSITORY=/home/ubuntu PROJECT_NAME=Team2-CarBook cd $REPOSITORY/$PROJECT_NAME/ echo "> Git Pull" git pull cd backend/carbook echo "> 백엔드 프로젝트 Build 시작" ./gradlew build echo "> step1 디렉토리로 이동" cd $REPOSITORY/$PROJECT_NAME echo "> Build 파일 복사" cp $REPOSITORY/$PROJECT_NAME/backend/carbook/build/libs/*.jar $REPOSITORY/ cd frontend/carbook echo "> 프론트엔드 프로젝트 Build 시작" npm install npm run build echo "> 현재 구동중인 애플리케이션 pid 확인" CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID" if [ -z "$CURRENT_PID" ]; then echo "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다." else echo "> kill -15 $CURRENT_PID" kill -15 $CURRENT_PID sleep 5 fi echo "> 새 애플리케이션 배포" JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1) echo "> JAR Name: $JAR_NAME" nohup java -jar \ -Dspring.config.location=/home/ubuntu/Team2-CarBook/backend/carbook/src/main/resources/application.properties \ -Dspring.profiles.active=real \ $REPOSITORY/$JAR_NAME > backend.log 2>&1 &
프론트엔드는 nginx 웹 서버 사용
vim /etc/nginx/sites-available/default
프론트랑 백엔드 같은 EC2 서버에 있음
→ 프론트에서 백엔드로 요청 보낼때 로컬호스트로 접근가능하고, 외부 요청은 막을 수 있다.
server { listen 80; location / { root /home/ubuntu/Team2-CarBook/frontend/carbook/dist; index index.html index.htm; try_files $uri /index.html; }
2/10 (금)
📌
게시글 좋아요 개수는 필드로 저장하면 안된다!
→ 동시성 문제
→ 따로 테이블로 빼서 저장함(POST_LIKE 테이블)
2/11 (토)
  • 예외 테스트 하는 방법
 
ORM 안 쓰고 jdbcTemplate 쓰니까 뭔가 객체지향프로그래밍과 데이터베이스 설계와 충돌
  • 기능이 추가되거나 변경되면 DB 수정이 불가피함
  • 또는 DB 컬럼 이름이 바뀌거나 하면 쿼리문 찾아서 다 수정해야 함
  • 객체지향프로그래밍이 힘듦
2/13 (월) - AWS S3
  • [AWS] Spring Boot에서 AWS S3와 연계한 파일 업로드처리
2/14 (화) - 좋아요 개수 최적화하기 (soft delete 구현)
"insert into POST_LIKE(user_id, post_id) values (?, ?) " + "on DUPLICATE KEY update is_deleted = false", userId, postId);
좋아요 최적화
좋아요 개수 조회 최적화하기
속닥속닥 프로젝트(https://github.com/woowacourse-teams/2022-sokdak…
https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/
좋아요 개수 조회 최적화하기
게시글에 대한 ‘좋아요’ 기능인데요, 현재 구조에서는 게시글을 조회할 때, ‘좋아요’ 테이블과 함께 조인을 해서 가져오고 있습니다. 단순히 좋아요 개수만 저장하지 않는 이유는, 한번 좋아요 버튼을 누른 사람은 두 번째 누를 때 취소할 수 있어야 하기 때문입니다.
하지만 이런 방식이라면 만약 게시글 하나에 좋아요가 100만개가 된다면 게시글을 조회 한번 할때마다 100만개를 조인하게되어 오버헤드가 커지게 됩니다.
그래서 저희는 반정규화를 선택했습니다. post 테이블에 like_count 컬럼을 추가함으로서 likes 테이블을 조인하지 않고도 해당 게시글의 좋아요 개수를 알 수 있습니다. 이렇게 해서 해당 게시글의 좋아요 개수를 가져오는데 시간을 0.0013으로 줄일 수 있었습니다.
하지만 이렇게 되면 좋아요 개수에 대한 정보가 두 테이블에 나타나게 되어 데이터 정합성을 맞추는 것이 중요한 문제가 됩니다.

Sync Schedule

특정 주기마다 좋아요 개수를 맞추어 주는 것
처음에 문제였던 것이 동시에 사용자 요청이 들어올 때 같은 likecount 를 얻어 좋아요 개수가 맞지 않는다는 것인데, 이렇게 맞지 않았던 데이터를 주기적으로 한번씩 맞추어 주는 것입니다.
Sync Schedule 방법은 업데이트 주기가 오기 전에는 데이터의 정합성이 맞지 않아 사용자에게 좋아요 개수가 부정확하게 표시될 수 있습니다. 하지만, 속닥속닥은 좋아요 개수가 일시적으로 부정확하게 표기 되더라도 큰 문제가 되지 않는다고 생각했습니다.
[Spring boot] 좋아요수 증가 분산락을 이용하여 동시성 제어하기 (redis활용하기)
기존 코드에서 좋아요수를 증가하는 로직은 하나의 서버에 대해서 들어오는 요청에 대해서는 트랜잭션을 보장하지만 다중 서버인 경우에는 트랜잭션을 보장하지 않았습니다. 이 글은 이 문제를 겪고 해결하는 과정을 작성한 글입니다. 기존 코드는 아래와 같았습니다. 되게 단순한 로직입니다. 1. 게시글을 가져온다. 2. 카운트를 1 더해준다. 입니다.
[Spring boot] 좋아요수 증가 분산락을 이용하여 동시성 제어하기 (redis활용하기)
https://velog.io/@korea3611/Spring-boot-%EC%A2%8B%EC%95%84%EC%9A%94%EC%88%98-%EC%A6%9D%EA%B0%80-%EB%B6%84%EC%82%B0%EB%9D%BD%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0-redis%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0
[Spring boot] 좋아요수 증가 분산락을 이용하여 동시성 제어하기 (redis활용하기)
실제 현업에서는 서버를 하나만 두고 서비스를 운영한다는 것은 상당히 리스크가 있는 운영이기 때문에 하나가 죽더라도 문제 없이 서비스 하기 위해 다중서버를 구성합니다.
redis를 활용하는 방안을 생각했습니다. 기존에 글로벌 캐시로 활용하던 redis서버가 있었고 redis는 싱글쓰레드이기 때문에 여러 요청에 대해서 트랜잭션을 보장해줄 수 있을 것이라고 생각했습니다. (이 생각이 잘못되었다는 것은 밑에서 나옵니다.)
Redis 캐시 전략 중 write-through전략을 사용

왜 안됐을까?(redis의 특징)

redis의 특징을 다시 살펴보았습니다.우선 대표적인 특징으로 redis는 싱글스레드 입니다.
레디스는 Event Loop를 이용하여 요청을 수행합니다.즉 실제 명령에 대한 작업은 커널 I/O 레벨에서 멀티플렉싱을 통해 처리하여 동시성을 보장합니다.따라서 유저 레벨에서는 싱글스레드로 동작하지만, 커널 I/O 레벨에서는 스레드 풀을 이용합니다.
하지만 이 동시성을 보장한다는 의미 자체가 동시성으로 인한 문제가 발생할 수 있다는 뜻을 의미했습니다. 따라서 동시성 문제에 대한 처리가 필요했습니다.
→ 락을 사용하기 위한 Redisson 사용
트랜잭션 격리 수준
[데이터베이스] 트랜잭션
들어가며 SOPT에서 프로젝트를 진행하면서, Sequelize ORM을 활용해서 프로젝트를 관리했습니다. 하지만 Sequelize를 활용하다 보니, SQL의 원리에 대해서 제대로 알 수 없었습니다. Sequelize에 문제가 생기면, SQL 쿼리를 읽고 해석할 줄 알아야 하는데, SQL을 제대로 할 줄 몰라서 어떤 에러가 뜨는 건지 도저히 알 수 없었습니다. 데이터베이스에 대한 기초지식도 없이 바로 프로젝트를 진행했다는 사실을 깨닫고, 부족함을 채워보기 위해 공부를 시작합니다. 기본이 있는 개발자가 되고 싶습니다. 트랜잭션(Transaction) 트랜잭션은 하나의 작업을 수행하는 데 필요한 데이터베이스의 연산들을 모아놓은 것입니다. 데이터베이스에서 논리적인 작업의 단위이며 데이터베이스에서 장애가 발생했을..
[데이터베이스] 트랜잭션
https://overcome-the-limits.tistory.com/528
[데이터베이스] 트랜잭션
2/15 (수) - 무한 스크롤 구현, null 비교
무한 스크롤
무한 스크롤 그거 그렇게 구현하는거 아닌데
무한 스크롤에서 페이지를 쓰면 안되는 이유
무한 스크롤 그거 그렇게 구현하는거 아닌데
https://velog.io/@dramatic/%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B7%B8%EA%B1%B0-%EA%B7%B8%EB%A0%87%EA%B2%8C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94%EA%B1%B0-%EC%95%84%EB%8B%8C%EB%8D%B0
무한 스크롤 그거 그렇게 구현하는거 아닌데
  • 인덱스 대신(페이지가 없이) 응답 body에 next_max_id 값을 넘김
    • sections/
    • 요청 form data에 max_id
    • 응답 body에 next_max_id
  • 피드를 요청한 API의 응답 값인데요, next_max_id라는 값이 존재합니다. 이 값이 현재 응답받은 피드의 기준이 되는 값입니다.
  • 그래서 다음 피드를 불러오는 API를 요청할 때 max_id에 그 값을 담아 요청을 보내게 됩니다.
[Spring] 무한스크롤 구현 및 성능 개선 하기 - No Offset 페이지네이션
안녕하세요 오늘은 페이지네이션의 성능을 개선 시키는 방법 중 하나인 no-offset 페이지네이션에 대해 기록을 남겨보고자 합니다! 예전에 스프링 페이지네이션에 대해 포시팅한적이 있는데 이번에 실무에서 무한스크롤을 적용해야해서 직접! 사용해볼 기회가 생겨 더 자세한 내용을 기록으로 남기고자 합니다!! 👏🏻 이전 포스팅해서는, 아래와 같은 방법을 커서 페이지네이션이라고 칭했는데, 여기서 말하는 No Offset 과 같은 의미의 단어로 말했습니다.
[Spring] 무한스크롤 구현 및 성능 개선 하기 - No Offset 페이지네이션
https://thalals.tistory.com/350
[Spring] 무한스크롤 구현 및 성능 개선 하기 - No Offset 페이지네이션
select * from pont from limit 20 offset 700000
select * from pont from where name > 70000 limit 20
  • where 조건절을 타고 들어가는 건, 해당 컬럼에 인덱스가 걸려있어야합니다.
  • 그래야만 index range scan으로 조회가 되기 때문에, 유의미한 결과를 낼 수 있습니다.
No-Offset은 정렬되고 중복이 존재하지 않는 인덱스 값을 가지는, 무한 스크롤 방식에 유용하다고, 생각됩니다.
Offset 기반 페이지네이션은 우리가 원하는 데이터가 ‘몇 번째’에 있다는 데에 집중하고 있다면, 커서 기반 페이지네이션(여기서는 No Offset 이라 칭함) 은 우리가 원하는 데이터가 '어떤 데이터의 다음'에 있다는데에 집중합니다.
객체 == null VS Object.isNull()
리팩토링 - 객체의 null 체크시 == 연산자를 직접 사용하는게 좋은가?
== 연산자를 이용한 방법과, Objects.isNull() 을 이용한 방법 두 가지를 혼용해서 사용하고 있었다.둘이 어떤 차이가 있고 어떤 방법이 베스트 프랙티스일지 생각해보자.String은 아래에서 따로 정리한다.설명할게 없다. 해당 객체가 null인지 판단한다.내
리팩토링 - 객체의 null 체크시 == 연산자를 직접 사용하는게 좋은가?
https://velog.io/@znftm97/%EA%B0%9D%EC%B2%B4%EC%9D%98-null-%EC%B2%B4%ED%81%AC%EC%8B%9C-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A5%BC-%EC%A7%81%EC%A0%91-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B2%8C-%EC%A2%8B%EC%9D%80%EA%B0%80
리팩토링 - 객체의 null 체크시 == 연산자를 직접 사용하는게 좋은가?
  • Object의isNull(), isEmpty() 모두 내부적으로 == 연산자를 사용한다.isNull()은 null 체크를 위해 만든 메서드가 아니며 Stream의 filter 메서드처럼 인자를 Predicate 인터페이스로 받는 메서드에 사용하라고 있는 메서드인 것이다. isEmpty()는 null을 검사하는 것인지 명확하지 않다.
  • String
    • str ≠ null
      • null인지 아닌지 판단
    • !str.isEmpty()
      • 빈 문자열인지 즉 “” 형태 값인지 아닌지 판단
    • containsText(strt)
      • 문자열의 길이가 0보다크고, 공백으로만 이루어져 있어도 false를 리턴한다.
      • 즉 null이 아니고, 빈문자열도 아니며, 공백으로만 이루어져 있는 문자열도 아닌 문자열인 경우에만 true를 retrun해준다. 단순 null체크를 넘어서 유효한 문자열인지 검사해주는 메서드이다.
따라서 null 체크할 때 == 연산자를 직접 사용하는 것을 지향하려고 한다. String 타입인 경우에는 StringUtils.hasText() 사용을 지향하고자 한다.
 
Objects.isNull과 Validation
javable에서 댓글을 보면서 배운 내용 중에 기억이 흐려져서 자꾸 다시 찾아보게 하는 댓글들이 있다. 다시 찾는 게 번거로워서 확실히 기억하기 위해.. 혹은 다시 찾을 때 편하게 찾기 위해 글로 정리해놓고자 한다. Objects.isNull, Objects.nonNull javable에 아래와 비슷한 예제를 작성한 글이 있었다. public void validateEmpty(List names) { if (Objects.isNull(names)) { throw new IllegalArgumentException(ERROR_MESSAGE); } ... } 위 코드는 메서드로 넘어온 List names를 null 체크하는 평범한 코드로 보일 수 있지만 좋은 코드는 아니다. Objects.isNull 메서..
Objects.isNull과 Validation
https://dundung.tistory.com/271
Objects.isNull과 Validation
Objects.isNull은 if문에 사용하라고 있는 메서드가 아니다. javaDoc을 보면 Objects.isNull의 설명은 아래와 같이 쓰여 있다.
ApiNoteThis method exists to be used as a Predicate, filter(Objects::isNull)
Objects.isNull 메서드는 Stream의 filter 메서드처럼 인자를 Predicate 인터페이스로 받는 메서드에 사용하라고 있는 메서드인 것이다. 아래의 예제와 같이 사용하는 것이 적절하다.
즉, Objects.isNull, Objects.nonNull을 if문에서 사용하면 가독성을 오히려 해칠 수 있고 용도에 맞지 않는다. Predicate 인터페이스를 인자로 받는 메서드에 사용하자.
📌
Redis 서버가 잘 죽는다고 함
인 메모리 DB
  • 꺼지면 죽는게 아니라 디스크를 따로 쓴다고 함 → 영속성
2/16 (목) - 인메모리 DB
S3 CORS 헤더 관련 이슈
S3 CORS 헤더 관련 이슈 해결방법 (html2canvas, lottie)
AWS S3는 Simple Storage Service를 줄여 S3라고 부른다.이 S3는 여러 용도로 쓰이는데 대표적으로 아래 세가지 용도로 주로 사용된다.정적 리소스 저장용 (웹 하드 느낌?)정적 웹 페이지 및 콘텐츠 호스팅정적 리소스 서버로 활용이번에 해결한 이슈는
S3 CORS 헤더 관련 이슈 해결방법 (html2canvas, lottie)
https://velog.io/@kimsehwan96/S3-CORS-%ED%97%A4%EB%8D%94-%EA%B4%80%EB%A0%A8-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95-html2canvas-lottie
S3 CORS 헤더 관련 이슈 해결방법 (html2canvas, lottie)
인메모리 DB
[Spring DB] 7. DataBase Test, 임베디드 DB
* 김영한님의 스프링 DB 2편 강좌를 수강하며 정리한 글입니다. * 스프링 DB 2편 - 데이터 접근 활용 기술 - 인프런 | 강의 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의 소개 | 인 www.inflearn.com 이전 글에서는 JdbcTemplate의 활용법에 대해서 정리하였다. 테스트 데이터베이스의 분리 @SpringBootTest는 상위 패키지에서 @SpringBootApplication을 찾아서 자동으로 설정 정보를 사용한다. 데이터베이스를 사용해 테스트를 하다 보면 DB에 남은 데이터로 인해 영향을 받을 수 있다. 테스트에는 외부의 변수가 개입하..
[Spring DB] 7. DataBase Test, 임베디드 DB
https://everenew.tistory.com/269
[Spring DB] 7. DataBase Test, 임베디드 DB
[Spring Boot] JdbcSQLSyntaxErrorException: Syntax error in SQL statement ... expected "identifier" 해결
[Spring Boot] @DataJpaTest 잘못된 사용 오류 해결
[Spring Boot] JdbcSQLSyntaxErrorException: Syntax error in SQL statement ... expected "identifier" 해결
https://velog.io/@jwkim/spring-boot-datajpatest-error
[Spring Boot] JdbcSQLSyntaxErrorException: Syntax error in SQL statement ... expected "identifier" 해결
[우아한 테크코스 3기] LEVEL 2 회고 (95일차)
안녕하세요? 제이온입니다. 오늘은 오전에 브라운의 스프링 테스트 수업을 듣고, 오후에는 중간곰과 페어 프로그래밍을 이어서 했습니다. 브라운의 스프링 테스트 수업 수업은 단위 테스트, 통합 테스트, E2E 테스트 등에 대한 소개와 다양한 테스트 관련 어노테이션을 알려주는 수업이었습니다. 그 외에는 크루들의 Q&A가 대부분이었는데, 몇 가지 새롭게 알게 된 점이 있었습니다. 그리고 그것은 안그래도 어제부터 고민해왔던 주제였습니다. 바로, '중복된 이름에 관한 예외 처리를 어디서 할 것인가?'입니다. 저는 이것을 커스텀 익셉션을 만든 다음 서비스에서 검증을 하였습니다. 물론, DB 테이블 속성으로 UNIQUE를 주면 되는데 왜 굳이 커스텀 익셉션을 만들고 그것을 서비스에서 검증하냐고 반론을 제기할 수 있습니다..
[우아한 테크코스 3기] LEVEL 2 회고 (95일차)
https://steady-coding.tistory.com/429
[우아한 테크코스 3기] LEVEL 2 회고 (95일차)
❗검색 기능 오류 → 2/16에 해결 완료
  • 키워드를 통한 게시물 검색
    • 키워드가 무조건 있다고 가정하고 들어오는 것이기 때문에… 수정할 필요가 없을 수도 있음
 
  • 한글 입력 → 1번째 요청은 잘 되면서, 2번째 요청부터 모든 태그가 응답에 담김
    • 2번째 요청부터 쿼리 파라미터로 빈 문자열이 들어옴
    • → Postman 문제였던 것 같다. 프론트와 통신할 때 잘만 돌아감
  • 쿼리 파라미터에 + 문자는 “ “(공백)으로 들어옴
    • Spring 컨트롤러는 쿼리 파라미터의 “+” 문자를 공백으로 대체함
  • &, # → 빈 문자열
    • URL에 사용하는 거라서 그냥 빈 문자열이 들어오는듯 하다
  • ^, {, }, [, ], \, | → 400 bad request 이상한 html문을 응답으로 보냄 → controller로 아예 안들어오는 듯
  • % → null 값으로 들어옴
    • 해시태그/모든 태그 검색 시 % → 400 bad request (null값)
      • WARN 3314 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'hashtag' for method parameter type String is not present]
    • 문자를 인코딩할 때 %를 쓰기 때문에 ‘%’만 들어오면 오류 생성

→ 인코딩하지 않고 보내 발생한 문제

 
접두어로 검색할 때 쿼리문 수정
  • LIKE 절의 와일드 문자는 '%'와 '_'이므로 이 두 문자가 검색어로 들어올 경우를 처리하기 위해 쿼리문을 수정함.
  • 와일드 문자가 들어가는 부분은 ESCAPE 옵션을 사용해야 한다.
MSSQL LIKE 특수문자(와일드카드) ESCAPE 검색 방법 ([, %, _)
MSSQL LIKE 특수문자(와일드카드) ESCAPE 검색 방법 ([, %, _) MS SQL에서 와일드카드 문자 또는 특수문자를 LIKE 조건으로 검색해야 할 때가 있는데요. 그럴 경우 ESCAPE 옵션을 사용하시면 됩니다. 보통 설명 같은 필드에 사용자가 특수문자 [], %, _를 입력하는 경우가 있는데 ESCAPE 옵션을 사용하지 않고 LIKE로 검색을 하면 조회가 되지 않습니다. 그럼 LIKE ESCAPE 옵션을 사용하여 특수문자를 검색하는 방법에 대해서 알아보죠. ◎ 포스트 기준 - Microsoft SQL Server 2019, SSMS 18.11 1. LIKE 구문 (Microsoft 기술 문서) match_expression [ NOT ] LIKE pattern [ ESCAPE escape..
MSSQL LIKE 특수문자(와일드카드) ESCAPE 검색 방법 ([, %, _)
https://blueshare.tistory.com/224
MSSQL LIKE 특수문자(와일드카드) ESCAPE 검색 방법 ([, %, _)
public List<Hashtag> searchHashtagByPrefix(String keyword) { return jdbcTemplate.query("SELECT h.id, h.tag FROM HASHTAG h WHERE tag LIKE '" + convertWildCharToRealChar(keyword), hashtagRowMapper()); } public String convertWildCharToRealChar(String oldStr) { String newStr = oldStr.replaceAll("%", "!%"); newStr = newStr.replaceAll("_", "!_"); newStr += "%' ESCAPE '!'"; return newStr; }
 
 
메인페이지 로드 시에 여러 서비스가 필요한데 어떻게 해야할까
// cookie를 통해 로그인 유저인지 확인 > userservice
// 팔로잉 중인 유저 리스트 불러오기 > followservice
// 리스트 별로 게시물 불러오기 > postservice
 
 
git 은 빈 폴더는 추적하지 않는다!!
.keep 파일을 추가해서 add commit 해주면 된다.
git clean -nd | sed s/'^Would remove '// | xargs -I{} touch "{}.keep"
하위폴더 한방에 추가도 가능
Git : 빈 디렉토리 추가하기
기존에는 SVN을 주로 사용하다가 최근 Git를 사용하기 시작했다. 맛보기를 시작한지는 조금 됐는데 본격적으로 사용하려고하니 아직 많이 미숙하다. 오늘은 작업을 하다가 빈 디렉토리를 추가하려고 하니 동작을 하지 않았다. SVN에서는 당연히 추가가 가능했는데 Git에서는 add로 추가해도 new file로 나타나지 않았다. 그래서 찾아보니 원래 Git는 빈 디렉토리는 Tracking 하지 않는다는 글들을 보게되었다. 이런...
Git : 빈 디렉토리 추가하기
https://blog.asamaru.net/2015/09/25/git-tracking-empty-directories/
Git : 빈 디렉토리 추가하기
 
 
비밀번호 해싱 BCrypt 사용?
 
Post vs Delete?
별개로 굳이 DELETE 메서드를 고집할 필요가 없다면, Request body에 필요한 정보를 담고, POST 메서드 요청 메세지를 보내는 것도 나쁘지 않은 방법입니다.  DELETE 메서드 자체를 허용하지 않는 서버, 방화벽이 많아서 편의성 측면에서 더 좋을 수도 있습니다. 실제로 TOSS의 결제 시스템의 경우, PG 연동 개발자들의 편의를 위해 POST 메서드로 DELETE 요청에 해당하는 API를 디자인한 사례가 있습니다. 관련 내용은 이 링크에 요약해뒀어요 :)
https://humblego.tistory.com/18
 
무한스크롤 구현 관련 자료글
https://thalals.tistory.com/350
 
aws elastic ip
 
jwt 토큰 io. 으로 import 해서 사용하면 어떨까?
 
이미지 저장 방식에 대한 고민
이미지 저장 방식을 프론트에서 firebase 사용하면 구조, 성능 면에서 좋다는 의견
아마존 서버 성능 <<<<< 프론트 성능
프론트에서 firebase에 저장하고 url을 백에 넘겨 주고 백은 url을 db에 저장
 
Spring Data JDBC 관련 자료
Log in or sign up to view
See posts, photos and more on Facebook.
Log in or sign up to view
https://www.facebook.com/groups/springkorea/posts/2655056721272607/
Video preview
 
 
인텔리제이 Warning:(1, 14) Unsupported characters for the charset 'ISO-8859-1’ 경고가 뜰 시 처리하는 방법
 
valid 예외처리 MethodArgumentNotValidException 썼는데 ExceptionHandler가 못잡음 ㅡㅡBindException 써서 해결
https://devoong2.tistory.com/entry/Spring-Valid-Annotation을-이용한-유효성-검증과-예외처리
 
REST API 관점에서 바라보는 HTTP 상태 코드
 
 
ResponseEntity 관련
 
amazon s3 에 이미지 저장
antdev.tistory.com
https://antdev.tistory.com/93
 
 
User의 필드를 final로 선언하는 것에 대해서 어떻게 생각?
비밀번호, 닉네임 변경 시 DTO로 받아서 바로 dao(repository)로 넘기면 되지 않나?
 
생성자 vs setter vs builder
Setter
setter의 무분별한 사용은 코드의 의도를 가지기 어렵다.
객체의 일관성이 깨진다.
Initializer & Builder
대안으로 생성자나 빌더를 사용할 수 있다.
하지만 생성자의 경우 채워야할 필드가 무엇인지 명확하게 지정할 수 없다.
따라서 builder의 사용을 권장한다.
 
 
git fetch —all —prune
 

SQL

[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
SELECT 쿼리문에서의 문법 순서와 실행 순서는 서로 다릅니다. 쿼리문의 실행 순서를 알고 쿼리를 작성하면 보다 효율적인 쿼리를 작성할 수 있습니다.
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
https://soo-vely-dev.tistory.com/220
[Database] SQL SELECT 쿼리문의 문법 순서와 실행 순서
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
ON vs WHERE] ON : JOIN 을 하기 전 필터링을 한다 (=ON 조건으로 필터링이 된 레코들간 JOIN이 이뤄진다) WHERE : JOIN 을 한 후 필터링을 한다 (=JOIN을 한 결과에서 WHERE 조건절로 필터링이 이뤄진다) [INNER JOIN 에서의 ON vs WHERE] 1.
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
https://developyo.tistory.com/121
JOIN 에서 WHERE 와 ON 의 차이, 그리고 OUTER JOIN
 
mysql 데이터 존재 여부 쿼리
 
 
 

CI/CD

  • Github Actions CD: AWS EC2 에 Spring Boot 배포하기
Docker + Github Action + Spring Boot 자동배포환경 만들기
[CICD] Docker + Github Action + Spring Boot 자동배포환경 만들기
Docker + Github Action + Spring Boot 자동배포환경 최근에 github action을 이용해서 spring boot서버의 자동배포환경을 구축했습니다. docker-compose와 nginx도 함께사용하니 비슷한 설정을 원하시는 분들께 도움이 될 것같네요👍 어렵고 성가신(?) 과정이지만 초반에 잘 세팅해놓으면 서버개발이 진짜 편해집니다..(어려운만큼 만들어 놓으면 뿌듯👻) 배포환경자동화에는 여러가지 방법이 있습니다. 그중에서 저는 Docker Hub를 활용해서 코드를 옮겨야하는 불편함을 제거하려고 했습니다.
[CICD] Docker + Github Action + Spring Boot 자동배포환경 만들기
https://velog.io/@rmswjdtn/Spring-Docker-Github-Action-Spring-Boot-%EC%9E%90%EB%8F%99%EB%B0%B0%ED%8F%AC%ED%99%98%EA%B2%BD-%EB%A7%8C%EB%93%A4%EA%B8%B0
[CICD] Docker + Github Action + Spring Boot 자동배포환경 만들기
 
 

Filter/Interceptor/AOP

https://goddaehee.tistory.com/154
[Spring] Filter, Interceptor, AOP 차이 및 AOP를 사용하여 Logging을 구현한 이유
업무를 하다 개인정보를 처리할 수 있는 비즈니스 로직에 접근하는 API에 대한 Logging이 필요해져 구현하기 위해 공부하고 정리한 포스트 입니다.
[Spring] Filter, Interceptor, AOP 차이 및 AOP를 사용하여 Logging을 구현한 이유
https://velog.io/@miot2j/Spring-Filter-Interceptor-AOP-차이-및-AOP를-사용하여-Logging을-구현한-이유
[Spring] Filter, Interceptor, AOP 차이 및 AOP를 사용하여 Logging을 구현한 이유
  • 현재 이미지 저장소에 url decode를 하고 있는데 이거를 filter로 적용해서 모든 request 들어올 때 바로 decode 해버리기?
로그인 체크 → 인터셉터
인터셉터 구현 관련(JWT)
https://velog.io/@gillog/Spring-InterceptorHandlerInterceptor-HandlerInterceptorAdapter
>>>PreHandle: 컨트롤러 직전에 실행, true false로 진입 여부 판단
PostHandle: view 렌더링 전에 실행, 화면 data 조작 가능
afterComplete: view 렌더링 이후 실행
afterConcurrentHandlingStarted: 비동기 요청 시 사용
로그인 체크를 하고 있는 api 정리
/profile/logout 로그아웃
/profile/modify/{nickname} 닉네임 변경
/profile/modify/password 비번 변경
/profile 사용자 프로필 페이지
/profile/follow 팔로우/팔로우취소
/profile/follower 팔로워 삭제
 
/posts/m getPosts >> 좀 애매함
 
/post 글 상세 페이지 조회
/post/{postId} 글 삭제
/post 글 생성
/post 글 수정
/post/like 글 좋아요/좋아요취소
 
 
Dispatcher-Servlet의 정적 자원 처리

애플리케이션 요청을 탐색하고 없으면 정적 자원 요청으로 처리

두번째 방법은 Dispatcher Servlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource) 경로를 탐색하여 자원을 탐색하는 것입니다. 이렇게 영역을 분리하면 효율적인 리소스 관리를 지원할 뿐 아니라 추후에 확장을 용이하게 해준다는 장점이 있습니다.