cache의, cache를 위한, cache에 의한

cache의 3단 변신..

 

Vegeta로 하염없는 Request를 보내보니 알겠더군요..

* [대기열 시스템] HTTP load testing tool, Vegeta 설치(Window)

Cache가 중요하다는 것을요..

 

메모리에 저장해두고 누구보다 빠르게 남들과는 다르게 데이터를 전달해주는 Redis..

 

 

다양한 방법, 3.5단 변신을 정리해보고자 합니다..🤖

 

1단 변신 준비. RedisConfig - userRedisTemplate

Redis에 데이터를 저장하고 가져올 때, User 객체를 쉽게 직렬화/역직렬화하여 처리할 수 있도록 RedisTemplate을 설정

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // RedisConnectionFactory: Redis 서버와의 연결을 관리하는 클래스
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // ObjectMapper: JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 데이터로 변환
    // DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false: JSON에 Java 클래스에 정의되지 않은 필드가 있어도 에러를 발생시키지 않도록 설정
    // JavaTimeModule: java.time 패키지의 날짜/시간 클래스(LocalDate, LocalDateTime 등)를 처리할 수 있는 모듈을 추가
    // SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS
    // : 날짜를 타임스탬프가 아닌 ISO-8601 포맷(예: 2023-01-18T10:15:30)으로 직렬화하도록 설정

    // Object Mapper 설정 이유
    // Java 객체의 구조에 맞지 않는 JSON 필드가 있을 경우 에러가 발생할 수 있음
    // Java의 날짜/시간 클래스(LocalDate, LocalDateTime 등)는 기본적으로 직렬화/역직렬화가 제대로 지원되지 않음
    // 기본적으로 날짜와 시간을 타임스탬프 형식(예: 1674042600000, 밀리초 단위)으로 직렬화
    // 날짜를 사람이 읽을 수 있는 ISO 형식(예: 2025-01-18T15:30:00)으로 변환

    RedisTemplate<String, User> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setKeySerializer(new StringRedisSerializer()); // 키를 문자열(String)로 직렬화
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, User.class)); // JSON 포맷으로 데이터를 저장

      return template;
  	}
  }

 

 

1단 변신. RedisConfig - objectRedisTemplate

RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();
    // PolymorphicTypeValidator: allowIfSubType을 통해 역직렬화 허용 클래스 타입을 정의
    
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // activateDefaultTyping: Jackson에서 직렬화/역직렬화 시 객체 타입 정보를 포함하도록 설정하는 메서드
    // DefaultTyping: 비최종 클래스(Non-final class)에 대해서만 타입 정보를 포함

    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
    // RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장
    // GenericJackson2JsonRedisSerializer
    // : JSON 데이터를 역직렬화할 때, 대상 객체의 타입 정보를 명시적으로 제공하지 않으면 기본적으로 Map 타입(즉, LinkedHashMap)으로 역직렬화

    return template;
  }
}

 

 

2단 변신. RedisHashUser

@Redishash를 사용해 객체를 Redis의 Hash 데이터 구조로 저장

RedisHashUser.java
@RedisHash(value = "redishash-user", timeToLive = 30L)
// @RedisHash를 사용해 Redis에 저장할 객체를 정의
public class RedisHashUser {

  @Id
  private Long id;
  // HSET redishash-user:3 id 3
  private String name;
  // HSET redishash-user:3 name "hi3"
  @Indexed
  private String email;
  // HSET redishash-user:3 email "hi3@email.com"
  // "SADD" "redishash-user:email:hi3@email.com" 3
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;
}

 

RedisHashUserRepository.java
public interface RedisHashUserRepository extends CrudRepository<RedisHashUser, Long> {
	// ...
}

 

JpaRepository는 Redis가 아닌 RDBMS(예: MySQL, PostgreSQL 등)와 상호작용하기 위한 Repository 

CrudRepository / RedisRepository를 상속하는 Repository 이용 필수

기능 JpaRepository CrudRepository
저장소 대상 관계형 데이터베이스 (RDBMS) Redis
데이터 모델 테이블과 레코드 Key-Value 또는 Hash 등
내부 구현 JPA(EntityManage, Hibernate 등) 기반 RedisTemplate 기반
동작 방식 SQL 쿼리 생성 및 실행 Redis 명령어(SET, GET 등) 실행
@RedisHash 지원 여부 지원하지 않음 지원함

 

 

3단 변신(최종 변신). @Cacheable

캐시에 데이터가 없을 경우, 캐시에 데이터를 추가하고, 있을 경우, 캐시의 데이터 반환

CacheConfig.java
@EnableCaching
// Spring의 캐싱 기능을 활성화
// 캐싱 관련 애너테이션(@Cacheable, @CacheEvict, @CachePut)이 동작하도록 설정
// 애플리케이션 컨텍스트에 한 번만 추가되면 전체 프로젝트에서 동작
@Configuration
public class CacheConfig {

  public static final String CACHE1 = "cache1";
  public static final String CACHE2 = "cache2";

  @AllArgsConstructor
  @Getter
  public static class CacheProperty{
    private String name;
    private Integer ttl;
  }

  @Bean
  public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(){
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();

    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

    List<CacheProperty> properties = List.of(
        new CacheProperty(CACHE1, 300),
        new CacheProperty(CACHE2, 30)
    );

    return (builder -> {
      properties.forEach(i ->
          builder.withCacheConfiguration(i.getName(),
              RedisCacheConfiguration.defaultCacheConfig()
              .disableCachingNullValues()
              .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
              .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)))
              .entryTtl(Duration.ofSeconds(i.getTtl()))));
    });
    // RedisCacheConfiguration.defaultCacheConfig(): 기본 Redis 캐시 설정을 가져옴
    // disableCachingNullValues(): null 값을 캐싱하지 않도록 설정
    // serializeKeysWith: Redis의 키를 직렬화하는 방법을 설정, 키를 문자열로 직렬화
    // serializeValuesWith: Redis의 값을 직렬화하는 방법을 설정, GenericJackson2JsonRedisSerializer를 사용하여 값을 JSON으로 직렬화
    // entryTtl: 캐시의 만료 시간을 설정
  }
}

 

UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
  @Cacheable(cacheNames = CACHE1, key = "'users:' + #id")
  // cacheNames: 캐시를 구분하고 각각의 캐시에 대해 커스터마이징된 설정을 적용하기 위해 사용
  public User getUser3(final Long id) {
    return userRepository.findById(id).orElseThrow();
  }
}

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

 

선생님은 Mac, 나는 Windows 시리즈입니다.

 

즉, 알아서 따라가야하는 시간이라는 뜻입니다.

 

지금은 Redis를 활용한 caching을 배우고 있는데, 정말 효과가 있나를 테스트 해보기 위해서 발빠르게 Postman을 연속해서 클릭할 수도 있지만, 부하테스트 툴이 따로 존재한다고 하여 해당 툴에서 맡겨보고자 합니다,,

 

Vegeta

https://github.com/tsenart/vegeta

 

GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!

HTTP load testing tool and library. It's over 9000! - tsenart/vegeta

github.com

 

눈 씻고 찾아보아도 Windows는 없습니다.

 

그럼 윈도우에서는 설치를 못하냐😲?

우리에게는 윈분투,, WSL(Windows Subsystem for Linux),,  윈도우 환경에 우분투 설치해서 Vegeta를 쓸 수 있습니다🎉.

 

No brew..🙅‍♀️🙅‍♂️ Yes sudo..🙆‍♀️🙆‍♂️

 

1. 우분투 설치

wsl --install

 

공용 네트워크에서는 잘 안받아질 수 있습니다.

저는 도서관 와이파이로 install을 진행하려고 하니, '목록 배포를 가져오지 못했습니다. 작업 시간을 초과했습니다.' 라는 오류가 났었습니다.

집에 와서 다시 설치하니 잘 됐습니다.

 

설치가 끝나고 재부팅 후 자동으로 터미널 창에서 우분투가 실행되며, 이 때 UNIX 유저명과 비밀번호를 설정해주면 됩니다.

 

 

2. root 계정 로그인

sudo su

이후 비밀번호를 입력하면 우분투 세계의 근원이 될 수 있습니다.

 

 

3. IPv4 주소 확인

ipconfig

Vegeta를 통해 부하 테스트를 진행하기 위해 어느 IP 주소로 요청을 보낼 것인지 적어줘야 합니다. 

따라서, 미리 확인해둡니다.

 

 

4. Vegeta 설치

sudo install vegeta

Vegeta 설치를 완료하면, 재빠른 포스트맨 클릭 대신 Vegeta가 요청을 보내줄 것입니다.

 

 

(번외) Vegeta 명령어

  • vegeta attack : 부하 테스트(공격)를 실행
  • -timeout : 각 요청의 최대 대기 시간, 요청이 지정한 시간(예: 20초) 안에 응답되지 않으면 실패로 간주
  • -duration : 실행 시간
  • -rate : 초당 요청 수, 트래픽 양을 설정
  • -targets : 테스트할 대상 URL과 HTTP 메서드 정보가 담긴 파일
  • -workers : 동시에 동작하는 작업 단위(worker)의 수, 병렬 요청 처리에 사용
  • | : 파이프라인, 앞에 있는 vegeta attack에서 만든 결과를 뒤에 있는 vegeta report로 바로 넘기는 것
  • vegeta report : 결과를 요약 보고서 형식으로 출력, 평균 응답 시간, 성공률, 요청/초, 분산 등의 통계가 포함
vegeta attack -timeout=20s -duration=10s -rate=3000/1s -targets=request.txt -workers=100 | vegeta report

# request.txt
GET http://192.168.0.1:8080/users/1
GET http://192.168.0.1:8080/users/2
GET http://192.168.0.1:8080/users/3

최대 20초의 타임아웃으로 100개의 워커가 10초동안 초당 3천번의 요청을 보냄

 

 

 

📑

참고 자료

Chat GPT

https://devstriker.tistory.com/14

 

[Linux] Windows11에 WSL(Ubuntu) 설치

개발환경 세팅을 위해 WSL (Windows Subsystem for Linux)로 내 Windows11에 Linux(Ubuntu)를 설치하고자 한다.  Windows 10 버전 2004 이상(빌드 19041 이상) 또는 Windows 11이라면 이하의 과정을 통해 별도의 번거로운

devstriker.tistory.com

https://lsdiary.tistory.com/86

 

Spring cache abstraction, Vegeta 오픈소스 사용해보기

2024.05.08 - [Spring/대용량 트래픽] - Spring Boot Cache Spring Boot Cache2024.05.08 - [Spring/대용량 트래픽] - Redis Cache로 실습하기 Redis Cache로 실습하기2024.05.07 - [Spring/대용량 트래픽] - Redis Cache 이론 Redis Cache

lsdiary.tistory.com

 

 

 

객체 한 번 입력 안했다고 Exception에 멱살 잡힌 이야기를 정리해 봅니다..

 

농담입니다.

 

RedisTemplate을 사용할 때, Class Type을 명시할 경우, 재사용성이 떨어져서 Object로 Value값을 지정할 경우, 나타나는 오류에 대해 간단히 정리해 보고자 합니다.

 

 

 

1. 상황

RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장

@Bean
RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
	ObjectMapper objectMapper = new ObjectMapper()
		.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
		.registerModule(new JavaTimeModule())
		.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

	RedisTemplate<String, Object> template = new RedisTemplate<>();
	template.setConnectionFactory(connectionFactory);
	template.setKeySerializer(new StringRedisSerializer());
	template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));

	return template;
}

 

 

 

2. 문제

LinkedHashMap을 User 타입으로 Casting하지 못해 ClassCastException 발생

java.lang.ClassCastException: 
class java.util.LinkedHashMap cannot be cast to class com.example.spring_boot_cache.domain.entity.User (java.util.LinkedHashMap is in module java.base of loader 'bootstrap';

GenericJackson2JsonRedisSerializer는 JSON 데이터를 역직렬화할 때, 대상 객체의 타입 정보를 명시적으로 제공하지 않으면 기본적으로 Map 타입(즉, LinkedHashMap)으로 역직렬화

→ LinkedHashMap을 User 객체로 강제 캐스팅할 수 없어 오류 발생

 

 

3. 해결 방법

JSON 데이터를 저장할 때 타입 정보를 포함

@Bean
RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
	PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
		.builder()
		.allowIfSubType(Object.class)
		.build();

	ObjectMapper objectMapper = new ObjectMapper()
		.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
		.registerModule(new JavaTimeModule())
		.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
		.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

	RedisTemplate<String, Object> template = new RedisTemplate<>();
	template.setConnectionFactory(connectionFactory);
	template.setKeySerializer(new StringRedisSerializer());
	template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));

	return template;
}

Redis에 저장되는 데이터의 타입은 여러 종류(Object)로 다양
Jackson(직렬화 라이브러리)은 기본적으로 타입 정보를 추가하지 않음
데이터를 읽어올 때 원래 타입이 무엇인지 알 수 없는 문제가 발생

PolymorphicTypeValidator: allowIfSubType을 통해 변환할 수 있는 데이터 타입을 정의

activateDefaultTyping: Jackson에서 직렬화/역직렬화 시 객체 타입 정보를 포함하도록 설정하는 메서드
  만일, allowIfSubType에서 허용하지 않은 class type이 redis에 저장되어있는 채로 역직렬화 시도 시, InvalidTypeIdException 발생
PolymorphicTypeValidator: 역직렬화 시 허용된 클래스 타입인지 검증하는 데 사용
DefaultTyping: 비최종 클래스(Non-final class)에 대해서만 타입 정보를 포함
예를 들어, Object나 서브클래스에는 타입 정보가 포함되지만, String, Integer처럼 final로 선언된 클래스는 제외

 

 

+ TTL 설정은 안해서 Redis 값이 Class 정보를 포함하지 않은 채로 남아있게 되면 만날 수 있는 오류..🍂

# 오류
com.fasterxml.jackson.databind.exc.MismatchedInputException: 
Unexpected token (START_OBJECT), expected START_ARRAY: 
need Array value to contain As.WRAPPER_ARRAY type information for class java.lang.Object

# Redis 패키지가 저장되지 않았던 데이터
"{\"id\":3,\"name\":\"hi3\",\"email\":\"hi3@email.com\",\"createdAt\":[2025,1,18,18,25,53,321769000],\"updatedAt\":[2025,1,18,18,25,53,321769000]}"

# Redis 패키지가 저장된 데이터
"[\"com.example.spring_boot_cache.domain.entity.User\",{\"id\":3,\"name\":\"hi3\",\"email\":\"hi3@email.com\",\"createdAt\":[2025,1,18,20,33,16,59381000],\"updatedAt\":[2025,1,18,20,33,16,59381000]}]"

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

 

여전히 저는 환경 설정에 머물러 있습니다..

 

Redis와 달리 유난히 연결되지 않았던 MySQL, YourSQL이 될 뻔하다 다시 찾은 MySQL..

 

Docker에서 MySQL 설치 후, 포트가 사용 중이라 오류가 났던 부분과 SpringBoot에 연결하면서 이 비밀번호는 맞는데 자꾸 틀리다고 오류가 났던 부분까지의 과정을 남겨봅니다.

 

 

1. MySQL 이미지 다운로드

docker pull mysql:8

 

 

2. MySQL 컨테이너 생성

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql:8

은 실패

Error response from daemon: 
Ports are not available: 
exposing port TCP 0.0.0.0:3306 -> 0.0.0.0:0: 
listen tcp 0.0.0.0:3306: 
bind: 
Only one usage of each socket address (protocol/network address/port) is normally permitted

기존 Window에 설치된 MySQL이 3306 포트를 사용하고 있기에 컨테이너에 3306 호스트 포트를 할당할 수 없다는 오류가 발생하였습니다.

 

netstat -ano | findstr 3306
#  TCP    0.0.0.0:3306           0.0.0.0:0              LISTENING       6628
# 0.0.0.0:3306: IPv4에서 모든 네트워크 인터페이스에 대해 이 포트를 열어 놓고 있음
# 6628: 모든 관련 포트를 점유하고 있는 프로세스의 PID
#  TCP    0.0.0.0:3306           0.0.0.0:0              LISTENING       6628
#  TCP    [::]:3306              [::]:0                 LISTENING       6628
# IPv6에서 모든 네트워크 인터페이스에 대해 이 포트를 열어 놓고 있음
#  TCP    [::]:33060             [::]:0                 LISTENING       6628
# 33060: MySQL에서 제공하는 X Protocol(엑스 프로토콜)을 사용하는 통신에 사용
# 기존의 SQL 기반 통신 방식과는 다른, JSON 기반 데이터 처리를 지원하기 위해 만들어짐
# JSON 데이터를 쉽게 저장하고 다룰 수 있는 NoSQL 기능을 추가한 것

호스트 운영체제(윈도우)에서 3306 포트를 사용하는지 확인하면 3306 포트가 열심히 듣고있는 걸 확인할 수 있습니다.

 

프로세스 제거해서 도커가 3306 포트를 사용할 수 있게 해줍니다.

askkill /pid 6628 /f

 

오류: 프로세스(PID 6628)를 종료할 수 없습니다.
원인: 액세스가 거부되었습니다.

은 실패

→ cmd를 관리자 권한으로 실행해서 해당 프로세스를 kill해주면 됩니다.

 

하지만, 저는 컨테이너에  다른 포트르 할당하여 생성해볼 예정입니다.

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3307:3306 mysql:8
# docker run Docker 컨테이너를 실행하는 기본 명령
# -e MYSQL_ROOT_PASSWORD=password: MySQL의 루트 계정(root user) 비밀번호를 지정하는 환경 변수
# -d: Detached mode로 컨테이너를 실행, 터미널에서 실행 로그를 출력하지 않고, 백그라운드에서 컨테이너를 실행
# -p 3307:3306: 포트 포워딩을 설정
# 호스트의 3307 포트를 컨테이너 내부의 3306 포트로 연결
# 로컬 머신에서 localhost:3307로 docker 컨테이너의 MySQL 서버에 접근할 수 있음

처음 명령어와 달리진 점은 포트 포워딩 부분이 달라졌다는 것인데, 호스트의 3307 포트를 컨테이너 내부의 3306 포트로 연결했습니다.

 

 

3. SpringBoot 연결

그 다음은 Springboot에서 properties 파일에 MySQL 설정을 해야합니다.

spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/fastsns"
    username: root
    password: password
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

 

위의 설정 파일로는 MySQL을 만날 수 없습니다.

심지어 틀린 부분이 2개나 있습니다.

 

오류 메시지는 단 하나 뿐😲

하지만, 원인은 자그마치 2개😲!

java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

 

🚨 1. 비밀번호 설정 규칙

대문자, 소문자, 숫자, 특수문자를 포함한 암호 길이 8자 이상으로 설정해야 연결이 제대로 됨

기존에는 비밀번호가 password였는데, Springboot에 MySQL을 연결하고 싶다하면 password123! 정도는 설정해줘야 합니다..🍂

 

🚨 2. 포트번호 확인

로컬 컴퓨터가 쓰고 있던 포트를 빼앗고 싶지 않았던 전 로컬 3307포트를 사용했었습니다.즉, datasource의 포트번호는 3307이 되야 합니다.

 

 

⭐ 최종

spring:
  datasource:
    url: "jdbc:mysql://localhost:3307/fastsns"
    username: root
    password: password123!
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

 

 

+ 사실은 하나 더 있던 오류😲

Caused by: org.hibernate.HibernateException: 
Unable to determine Dialect without JDBC metadata 
(please set 'jakarta.persistence.jdbc.url' for common cases 
or 'hibernate.dialect' when a custom Dialect implementation must be provided)

원인: Spring Boot 컨테이너가 MySQL Dialect 설정을 못 찾음

해결: dialect 설정 추가

spring:
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

 

 

 

📑

참고 자료

Chat GPT

https://yonghwankim-dev.tistory.com/570

 

[docker] mysql conatiner 생성시 포트포워딩 문제

배경 docker 엔진을 사용하여 mysql container를 생성하기 위해 다음과 같은 명령어를 실행하였습니다. $ docker run -p 3306:3306 —name mysql_boot -e MYSQL_ROOT_PASSWORD=1 -e MYSQL_DATABASE=springboot -e MYSQL_USER=yonghwan -e MY

yonghwankim-dev.tistory.com

https://m.blog.naver.com/gingsero/222350441743

 

taskkill 액세스가 거부되었습니다.

어라? 80포트로 뭔가 떠있는데 해당 프로세스가 taskkill로 죽여보니 액세스가 거부된다. 기억은 안나지만 ...

blog.naver.com

https://velog.io/@saintho/Error-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1%EC%8B%9C-%ED%8F%AC%ED%8A%B8-%EC%B6%A9%EB%8F%8C

 

[Error] 도커 컨테이너 생성시 포트 충돌

docker Error invoking remote method 'docker-start-container': Error: (HTTP code 500) server error - Ports are not available: exposing port TCP 0.0.0.0

velog.io

https://velog.io/@saintho/Error-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1%EC%8B%9C-%ED%8F%AC%ED%8A%B8-%EC%B6%A9%EB%8F%8C

 

[Error] 도커 컨테이너 생성시 포트 충돌

docker Error invoking remote method 'docker-start-container': Error: (HTTP code 500) server error - Ports are not available: exposing port TCP 0.0.0.0

velog.io

https://velog.io/@jiwonblue/Spring-Boot%EC%99%80-MySQL-%EC%97%B0%EB%8F%99-%EC%98%A4%EB%A5%98

 

Spring Boot와 MySQL 연동 오류

Spring Boot와 MySQL연동 시 발생한 오류Caused by: org.hibernate.HibernateException: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persi

velog.io

https://luna-archive.tistory.com/17

 

Caused by: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 해결하기

이번 글에서는 spring boot에서 RDB 세팅을 하다가 겪었던 에러 사항 및 해결 방법에 대해 남기려고 합니다. 최근 spring batch작업을 위해 RDB 연동을 하는데, 아래와 같은 에러를 마주쳤다. Caused by: java.

luna-archive.tistory.com

https://kjy1ho.tistory.com/18

 

[mysql] java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

이클립스와 mySQL을 연동하는데 java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 이런 에러가 떴다. 에러 이유를 알아보니 root 계정에 접속할 비밀번호가 틀려서 나온 에러라고

kjy1ho.tistory.com

 

 

 

 

 

 

미니미 프로젝트, 커피 월드컵 시리즈 마지막, 4탄..

 

1탄: [커피 월드컵] Netlify React 배포

2탄: [커피 월드컵] React 카카오톡 공유하기

3탄: [커피 월드컵] React에서 사용한 기술 정리

4탄: 현재 글!

 

사실 커피 월드컵이 돌아가는 것 자체는 오래 걸리지 않았습니다.

문제는 Netlify에서 배포한 사이트를 모바일로 확인했을 때부터 시작되었죠..

 

PC에선 참 깔끔하게 나온 화면이 모바일에서는 중구난방..

처음 미디어 쿼리를 사용해 봤는데, 아직도 미지의 세계지만 짧게나마 알게 된 걸 정리해 봅니다..

 

 

1. 미디어 쿼리란..🍂(요새 바람이 많이 불어서 바람에 날리는 낙엽 이모지를 넣어봤습니다,,)

잘 모르는 걸 배울 땐 모르는 단어의 정의부터 확인합니다..

 

CSS Media Query
예를 들어 "뷰포트가 480 픽셀보다 넓다."라고 지정한 규칙에 브라우저 및 장치 환경이 일치하는 경우에만 CSS를 적용할 수 있는 방법을 제공합니다. 미디어 쿼리는 뷰포트의 크기에 따라 서로 다른 레이아웃을 생성할 수 있기 때문에 반응형 웹 디자인의 중요한 부분입니다.

 

 

2. 미디어 유형.. 🍂

* all

* print: 페이지가 인쇄된 경우에 적용

* screen: 페이지가 화면에 보이는 경우에 적용

  * screen은 주로 min-width, max-width와 함께 사용하여 반응형 디자인을 만들 때 사용

      * 뷰포트가 특정 너비 이상 또는 이하인 경우 CSS를 적용하는 방식으로 사용

@media screen and (max-width: 768px){ ... }
/* 뷰포트의 width가 768 픽셀보다 좁은 경우 적용적용 */
/*@media (width <= 768px) { ... }과 동일*/

 

 

3. 미디어 쿼리와 연산.. 🍂

and
/*landscape(가로 방향화면) 방향으로 제한시키고 최소폭을 30 ems로 지정*/
@media (min-width: 30em) and (orientation: landscape) {
  /* … */
}

+ em, rem: font-size 속성값에 비례해서 결정되는 상대 단위

  ⭐ rem을 사용하는 것이 유지보수 측면에서 더 좋음

    (em은 해당 요소의 font-size가 없을 경우, 부모의 font-size를 찾을 때까지 계속 거슬러 올라가는데, em계산 시 어떤 요소의 font-size가 영향을 주는지 한 번에 파악하기 어려울 수 있으므로)

<html>
  <div>
  </div>
</html>
html {
  font-size: 16px;
}
div {
  font-size: 20px;
  width: 10em; 
  /* 200px, 해당 단위가 사용되고 있는 요소의 font-size 속성값이 기준
  해당 요소에 font-size가 없을 경우, 상위 요소의 font-size 적용*/
}

html {
  font-size: 16px;
}
div {
  font-size: 20px;
  width: 10rem; /* 160px, 최상위 요소의 font-size 속성값이 기준 */
}

 

or(,)
/*뷰포트의 높이가 680px 이상이거나, screen 모드에서 세로 모드(뷰포트의 높이가 너비보다 큰 경우)일 때 적용*/
@media (min-height: 680px), screen and (orientation: portrait) {
  /* … */
}

+ 뷰포트: 웹 브라우저에서 웹 콘텐츠가 화면에 표시되는 영역(스크롤을 하지 않고 한눈에 보이는 영역만 포함)

  * 브라우저가 웹사이트를 렌더링(화면에 보여주는)하는 공간

  * 스마트폰이나 컴퓨터에서 웹사이트를 볼 때, 그 화면에서 실제 콘텐츠가 표시되는 부분

  * 해상도

    * 디스플레이(모니터, 스마트폰 등)에서 실제로 보이는 부분의 픽셀 수

    * 스마트폰 화면 해상도 1080x1920: 화면이 가로로 1080픽셀, 세로로 1920픽셀로 구성

<meta name="viewport" content="width=device-width, initial-scale=1.0">
</meta>

  * width=device-width: 기기의 화면 너비에 맞게 뷰포트를 설정

    * height=device-height: 콘텐츠가 기기의 높이를 넘는 경우 스크롤이 생길 수 있고, 동적인 뷰포트 변화가 있는 브라우저(iOS Safari 등)에서는 동작이 예상과 다를 수 있음 → 잘 사용되지 않음

  * initial-scale=1.0: 초기 확대/축소 비율을 1로 설정

 

not
/*미디어 쿼리 전체의 의미를 반전*/
@media not print {
  /* … */
}

/*print에만 not 적용*/
@media (hover) and not print {
  /* … */
}

/*@media (not (screen and (color))), print and (color)와 동일*/
@media not screen and (color), print and (color) {
  /* … */
}

+ (hover): hover가 가능한 지 여부를 조건으로 걸 때 사용

+ (color): 색상을 표현할 수 있는 디스플레이 장치를 조건으로 걸 때 사용

  * 모노크롬 디스플레이(흑백만 지원)에서는 (color) 조건이 거짓(false)

 

 

 

📑

참고 자료

Chat GPT

https://developer.mozilla.org/ko/docs/Learn_web_development/Core/CSS_layout/Media_queries

 

미디어 쿼리 초보자 안내서 - Web 개발 학습하기 | MDN

CSS Media Query는 예를 들어 "뷰포트가 480 픽셀보다 넓다."라고 여러분이 지정한 규칙에 브라우저 및 장치 환경이 일치하는 경우에만 CSS를 적용할 수 있는 방법을 제공합니다. 미디어 쿼리는 뷰포트

developer.mozilla.org

https://www.daleseo.com/css-em-rem/

 

CSS 상대 단위 - em과 rem

Engineering Blog by Dale Seo

www.daleseo.com