Category: Spring

0

[Spring Boot] 애플리케이션 Warm Up 가이드

참고 - IF(kakao) 2022 https://www.youtube.com/watch?v=CQi3SS2YspY 🤔 Warm Up이 필요한 주요 이유Spring Boot 와 같은 JAVA 애플리케이션을 최초 실행하거나 재배포할 때, 첫 번째 요청이 다른 요청들에 비해 현저히 느린 현상을 경험한 적이 있을 것입니다. 이러한 현상을 Cold Start 문제라고 하며, 이를 해결하기 위해 Warm Up이 필요합니다. 1. JVM 의 JIT (Just-In-Time) 컴파일Java 애플리케이션은 바이트코드 로 컴파일되어 실행되며, JVM은 런타임에 자주 사용되는 코드를 네이티브 코드 로 최적화합니다. 초기에는 인터프리터 모드 로 실행되어 바이트 코드를 한줄 한줄 해석하기 때문에 성능이 상대적으로 낮습니다. JIT 컴파일러가 자주 실행되는 코드 핫스팟(Hot Spot) 을 식별하고 최적화된 네이티브 코드로 전환됩니다. 때문에 애플리케이션 구동 후 JIT 컴파일러가 핫스팟(Hot Spot) 을 식별하고 최적화하는 전까지는 상대적으로 느릴 수 있습니다.

0

[QueryDSL] Transform 사용 시 HikariCP Connection Leak 문제 해결

QueryDSL Transform 사용 시 HikariCP Connection Leak 문제QueryDSL의 transform() 메서드는 쿼리 결과를 그룹화하고 Map으로 변환할 때 매우 유용한 기능입니다. 하지만 부적절하게 사용할 경우 심각한 데이터베이스 커넥션 누수(Connection Leak)를 발생시킬 수 있습니다. 문제 상황운영 중인 서비스에서 다음과 같은 에러가 발생했습니다: HikariPool-1 - Connection is not available, request timed out after 30000ms. DB 커넥션 수는 POD 당 20개(max-connection-pool) 로 충분한 상태였고, 트랜잭션이 길게 잡히는 슬로우 쿼리도 없는 상황이라 아무래도 Connection Leak 이 발생하는 상황이라 생각 했습니다. 원인 분석QueryDSL의 transform() 메서드 특징문제의 원인은 QueryDSL의 transform() 메서드의 내부 동작 방식에 있었습니다. // 문제가 되는 코드 예시public Map<Long, List<OrderItem>> getOrderItemsByOrderId(List<Long> orderIds) { return queryFactory .selectFrom(orderItem) .where(orderItem.orderId.in(orderIds)) .transform( groupBy(orderItem.orderId) .as(list(orderItem)) );}

0

[Spring] - 트랜잭션 동기화 (TransactionSynchronizationManager)

리소스 동기화 - TransactionSynchronizationManager트랜잭션을 유지하기 위해서는 트랜잭션 시작부터 끝까지 같은 데이터 베이스 Connection 을 유지해야 합니다. 스프링에서는 트랜잭션 동기화를 위해 트랜잭션 동기화 매니저를 제공합니다. 트랜잭션 매니저는 DataSource 를 통해 데이터 베이스 Connection 을 생성하고 트랜잭션을 시작합니다. 트랜잭션 매니저는 트랜잭션이 시작된 Connection 을 트랜잭션 동기화 매니저에 저장합니다. Repository 는 트랜잭션 동기화 매니저에 저장된 Connection 을 사용합니다. 트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 저장된 Connection 을 통해 트랜잭션을 종료하고 Connection 을 닫습니다. TransactionSynchronizationManagerTransactionSynchronizationManager 는 ThreadLocal 을 사용해서 각 쓰레드별 데이터 베이스 Connection 을 저장하고 사용할 수 있도록 있습니다. TransactionSynchronizationManager 사용하기TransactionSynchronizationManager 에 저장된 Connection 을 가져오기 위해 DataSourceUtils 를 사용합니다. Connection getConnection(DataSource dataSource) DataSource 를 이용해 Database Connection 을 가져옵니다. Connection 이 없을 경우 생성하고 이미 있을 경우 ThreadLocal 에 저장된 Connection 을 가져옵니다. void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) DataSource 에 연결된 Database Connection 을 닫습니다. 만약 해당 Connection 을 사용중인 프로세스가 있을 경우 닫지 않고 반환합니다.

0

[Spring Boot] - 스프링 프로퍼티 암호화

스프링부트 민감정보 암호화 하기운영 중인 프로그램에서 DB 암호나 API 키와 같은 민감 정보를 설정 파일에 평문으로 저장할 경우, 프로그램이 유출되면 그대로 외부에 노출되는 심각한 보안 문제가 발생할 수 있습니다. 이를 방지하기 위해 프로퍼티 암호화를 적용하면, 설정값이 유출되더라도 평문이 아닌 암호화된 형태로만 노출되므로 보안성을 크게 강화할 수 있습니다. 자바 환경에서는 이러한 암·복호화를 손쉽게 지원하는 Jasypt 모듈을 활용할 수 있어, 운영 환경에서 안전하게 민감 정보를 관리할 수 있습니다. 이번 포스팅에서는 Spring boot 에 Jaspyt 적용해 프로퍼티를 암복호화 하는 내용을 작성했습니다 암복호화를 위한 라이브러리 추가// Jasypt for property encryptionimplementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5' 값 암호화 하기java -cp "build/libs/app.jar" com.ulisesbocchio.jasyptspringboot.cli.JasyptEncryptorCLI \ input="my-db-pass" \ password="${JASYPT_ENCRYPTOR_PASSWORD}" \ algorithm="PBEWITHHMACSHA512ANDAES_256" \ ivGeneratorClassName="org.jasypt.iv.RandomIvGenerator" Configuration 추가

0

[Spring Boot] - Github Package 에 Spring 프로젝트를 배포하기

참고 GitHub Packages - Gradle 레지스트리 작업 🧑‍💻 무료로 Artifact 저장소를 사용하고 싶다!회사가 아닌 개인 프로젝트를 할때, 라이브러리를 개발해서 다른 프로젝트에서 사용하고 싶은 경우, 라이브러리 저장을 위한 Nexus 저장소를 별도로 구축하기가 쉽지 않습니다. 지속적이고 무료로 패키지 저장이 가능한 서비스로 JitPack 과 Github 패키지 저장소를 찾아봤습니다. 처음에 알아본 JitPack 은 패키지를 무료로 올릴 수 있는 서비스지만, Private 저장소 대상으로는 유료입니다. 만약 내가 Private 저장소를 이용해 패키지를 만든다고 하면 적합한 대상이 아닙니다. 추가적으로 알아본게 Github 인데, Github 가 코드 저장 뿐만 아니라 Docker 이미지나 Artifact 들을 올릴 수 있는 기능 또한 제공을 하고 있었습니다. 코드와 함께 이번에는 Github 를 패키지 저장소로 사용해보려고 합니다. 🌱 Spring Boot 프로젝트를 Github 에 배포하기✅ Gradle 플러그인 추가 설정 추가build.gradle 에 maven-publish 플러그인을 추가합니다

0

[Spring Boot] - Spring Interceptor 에서 Response 조작이 가능한가?

참고 Spring interceptor에서 Response 수정하기 “HttpServletResponse is committed” mean? 📝 후처리에서 메뉴 데이터를 Response 객체에 데이터를 넣어주세요개발 요구사항 중 페이지 접근시 전처리에서 접근 권한을 확인하고 후처리로 Response 객체에 사용자가 접근할 수 있는 메뉴를 같이 내려달라는 요구를 받았습니다. 해당 요구사항을 Spring 에서 제공하는 Interceptor 로 가능한지 검토해 달라는 요청을 받았습니다. 🤔 Interceptor 에서 데이터 변경이 가능할까?스프링에서 제공하는 Interceptor 는 아래 코드와 같습니다. 요청을 처리하는 Controller 전에 preHandler 를 거치기 때문에 사용자 접근을 제어해달라는 요구사항은 어렵지 않았습니다. 마찬가지로 요청이 끝난 후에는 postHandler 를 거쳐 나가는데 그때 응답 객체를 수정할 수 있지 않을까? 마침, HttpServletRequest 객체를 매게변수로 받아서 조작이 가능할 것 같은 그런 기분이 들지만, 결론은 안됩니다. (두둥탁 🥁) HandlerInterceptorpublic interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }} 🧐 그럼 왜 인터셉터 에서 응답 객체 조작이 불가능한 것인가?

0

[Spring Boot] - Cache

목차 Post not found: spring-boot/spring-framework/springboot-actuator Post not found: spring-boot/spring-framework/springboot-messageresource Post not found: spring-boot/spring-framework/configuration/springboot-WebMvcConfigurer Post not found: spring-boot/spring-framework/configuration/springboot-autoconfiguration 참고 https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-caching.html https://www.baeldung.com/spring-cache-tutorial Spring Boot 캐시 추상화Spring Boot 에서는 캐시를 쉽게 사용할 수 있도록 캐싱 기능들을 추상화해 제공합니다. 캐싱 추상화 가능으로 개발자는 특정 캐시 저장소에 종속적이지 않고 일관된 방식으로 캐싱기능을 사용할 수 있습니다. ✅ 의존성 추가implementation 'org.springframework.boot:spring-boot-starter-cache' ✅ Cache 활성화

0

[Spring] - M1 맥북에서 Embeded Redis 사용하기

목차 [Spring] - M1 맥북에서 Embeded Redis 사용하기 [Spring] - Embeded Redis 사용하기 참고 https://github.com/ozimov/embedded-redis https://github.com/redis/redis-hashes 🚫 M1 Mac 에서 Embedded Redis 사용시 에러M1 맥북에서 Embedded Redis 를 그냥 사용하려고 하면 다음과 같이 redis server 를 시작할 수 없다는 에러가 발생합니다. Caused by: java.lang.RuntimeException: Can't start redis server. Check logs for details. Redis process log: at redis.embedded.AbstractRedisInstance.awaitRedisServerReady(AbstractRedisInstance.java:70) ~[embedded-redis-0.7.3.jar:na] at redis.embedded.AbstractRedisInstance.start(AbstractRedisInstance.java:42) ~[embedded-redis-0.7.3.jar:na] 🔎 Embedded Redis 라이브러리 확인Embedded Redis 라이브러리에서 제공해주는 Redis 아키택처 정보들을 확인하면 M1 맥북에 해당하는 aarch64 용 Redis 를 지원하지 않습니다. 따라서, M1 에서 Embedded Redis 를 사용하기 위해서는 M1 용 아키택처에 맞게 Redis 를 새롭게 빌드해서 Embedded Redis 라이브러리가 띄울 수 있게 제공해야 합니다.

0

[Spring Core] - 컴포넌트 스캔과 의존성 주입

목차 [Spring Core] - 의존성 주입 방식 [Spring Core] - 컴포넌트 스캔과 의존성 주입 [Spring Core] - BeanDefinition [Spring Core] - 스프링 빈 [Spring Core] - 스프링 컨테이너 생성과 Bean 등록, 의존성 주입 [Spring Core] - 스프링 컨테이너 컴포넌트 스캔과 의존성 주입스프링에서는 설정 정보 없이도 스프링 Bean 을 자동으로 등록하는 기능을 제공합니다. 또한, 등록된 Bean 들의 의존 관계도 자동으로 주입하는 기능도 제공합니다. 스프링은 @ComponentScan 을 이용해 스프링 Bean 을 자동으로 등록하고, @Autowired와 @Inject 를 사용해 의존 관계를 자동으로 주입해 줍니다. 1. 컴포넌트 스캔@ComponentScan 은 @Component 가 붙은 모든 클래스들을 스프링 Bean 으로 자동 등록합니다. 스프링 Bean 등록시 기본적으로 클래스명과 동일하며 맨 앞글자만 소문자로 등록됩니다. 만일, 별도의 이름을 지정하고 싶으면 @Component("Bean 이름") 으로 등록하면 됩니다.

0

[Spring Core] - 의존성 주입 방식

목차 [Spring Core] - 의존성 주입 방식 [Spring Core] - 컴포넌트 스캔과 의존성 주입 [Spring Core] - BeanDefinition [Spring Core] - 스프링 빈 [Spring Core] - 스프링 컨테이너 생성과 Bean 등록, 의존성 주입 [Spring Core] - 스프링 컨테이너 🔎 의존성 주입 (Dependency Injection) 의존성 주입은 객체간의 의존성을 관리하기 위한 디자인 패턴 한 객체가 직접 의존성하는 다른 객체를 생성하거나 관리하지 않고, 프레임워크와 같은 외부로부터 필요한 의존성을 주입받는 방식입니다. 이를 통해 객체간 의존성을 수정하기 위해 직접 코드를 수정할 필요가 없어 객체간 결합도 를 낮춰줍니다. 📌 스프링에서의 의존성 주입스프링에서는 스프링 컨테이너 가 객체의 의존성을 관리하고 주입해줍니다. 이때, 스프링 컨테이너가 의존성을 관리 및 주입을 위해 관리하는 객체들을 스프링 Bean 이라 부릅니다. 스프링에서 의존성 주입을 위해서는 우선, 스프링 컨테이너에 객체들을 스프링 Bean 으로 등록해야 합니다. 의존성 주입은 스프링 Bean 으로 등록된 객체들을 기준으로 주입이 이뤄집니다. 스프링에서 의존성 주입을 위해 @Autowired와 @Inject 를 사용하고 생성자, Setter, 필드, 메소드 에 붙일 수 있습니다.

0

[Spring Security] - SecurityContextRepository

목차 [Spring Security] - SecurityContextRepository [Spring Security] - SecurityContextPersistenceFilter 와 SecurityContextHolderFilter 🔎 SecurityContextRepository SecurityContextRepository 는 SecurityContext 를 저장하고 검색하기 위한 저장소입니다. Spring Security 에서는 인증 후 생성된 SecurityContext 를 저장하고 관리하기 위해 SecurityContextRepository 인터페이스를 제공합니다. SecurityContextRepository 인터페이스 구현을 통해 인증 객체(SecurityContext) 를 Request 객체나 Session 에 저장하거나 아니면 Redis 와 같은 별도의 저장소에 저장할 수 있습니다. SecurityContextRepository.javapublic interface SecurityContextRepository { @Deprecated SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder); default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) { Supplier<SecurityContext> supplier = () -> loadContext(new HttpRequestResponseHolder(request, null)); return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier), SecurityContextHolder.getContextHolderStrategy()); } // 변경된 보안 컨텍스트를 저장합니다. 주로 사용자가 인증되면 새로운 보안 컨텍스트를 저장하는 데 사용됩니다. void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response); // 현재 요청에서 보안 컨텍스트가 존재하는지 확인합니다. boolean containsContext(HttpServletRequest request);} 1. Session 에 저장 - HttpSessionSecurityContextRepository HttpSessionSecurityContextRepository 는 Session 에 SecurityContext 정보를 저장합니다.

0

[Spring Security] - SecurityContextPersistenceFilter 와 SecurityContextHolderFilter

목차 [Spring Security] - SecurityContextRepository [Spring Security] - SecurityContextPersistenceFilter 와 SecurityContextHolderFilter 기존에 인증을 하고 SecurityContext 를 생성하면 로그인 Session 이 유지가 됐는데, 새 프로젝트를 개발하면서 Reqeust 가 끝나면 인증이 풀리는 것을 알게 됐습니다. Spring Security 5.7 버전 이상부터는 SecurityContextPersistenceFilter 가 Deprecated 되고 SecurityContextHolderFilter 로 대체돼면서 이런 문제가 생긴거 같아 내용을 정리했습니다. 🔎 SecurityContextPersistenceFilterSecurityContextPersistenceFilter 에서는 인증을 위해 SecurityContextRepository 에 저장된 SecurityContext 정보를 가져와 인증 처리를 하고 요청이 종료되면 SecurityContext 정보를 SecurityContextRepository 에 저장합니다. SecurityContextPersistenceFilter 에서는 saveContext 를 이용해 SecurityContext 를 저장합니다. SecurityContextPersistenceFilter.javaprivate void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ... // SecurityContext 를 가져옵니다. SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder); try { this.securityContextHolderStrategy.setContext(contextBeforeChainExecution); if (contextBeforeChainExecution.getAuthentication() == null) { logger.debug("Set SecurityContextHolder to empty SecurityContext"); } else { if (this.logger.isDebugEnabled()) { this.logger .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution)); } } chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = this.securityContextHolderStrategy.getContext(); this.securityContextHolderStrategy.clearContext(); // SecurityContext 를 저장합니다. this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); this.logger.debug("Cleared SecurityContextHolder to complete request"); }} 🔎 SecurityContextHolderFilterSecurityContextHolderFilter 에서는 인증을 위해 SecurityContextRepository 에 저장된 SecurityContext 정보를 가져와 인증 처리를 진행합니다. 하지만 SecurityContextPersistenceFilter 와는 다르게 SecurityContextRepository 에 저장하지는 않습니다.

0

[Spring Cloud] - Eureka 에 서비스 등록하기

목차 [Spring Cloud] - Eureka 에 서비스 등록하기 [Spring Cloud] - Eureka (Service Discovery Server) 사용하기 참고 https://cloud.spring.io/spring-cloud-netflix/reference/html/ https://coe.gitbook.io/guide/service-discovery/eureka ✅ Eureka 서버에 서비스 등록하기앞에서 설정을 마치고 실행중인 Eureka 서버에 서비스를 등록하기 위해서는 몇가지 작업이 필요합니다. 1. 라이브러리 추가Eureka 에 서비스를 등록하기 위해서는 client 라이브러리가 필요합니다. implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 2. @EnableDiscoveryClient 추가

0

[Spring Cloud] - Eureka (Service Discovery Server) 사용하기

목차 [Spring Cloud] - Eureka 에 서비스 등록하기 [Spring Cloud] - Eureka (Service Discovery Server) 사용하기 참고 https://cloud.spring.io/spring-cloud-netflix/reference/html/ https://coe.gitbook.io/guide/service-discovery/eureka 🔎 Service Discovery Server 로써의 EurekaMSA (Micro Service Architecture) 환경에서는 서비스들이 하나의 서버에서 실행하는 환경이 아닌 각기 다른 서버에서 실행합니다. 특정 요청이 들어 왔을때 해당하는 서비스로 전달해주기 위해서는 각 서비스들이 어떤 서버에서 작동하고 있는지 알 필요가 있습니다. Spring Cloud 에서는 Eureka 서버를 이용해 각 서비스들이 어떤 서버에서 작동중인지를 등록하고 중앙 집중식으로 관리할 수 있고, 실행중인 모든 서비스들을 한번에 찾아 통신할 수 있습니다. 보통 이런 역할을 하는 서버를 Service Discovery Server 라 부릅니다. ✅ Eureka 서버 사용하기Eureka 를 Service Discovery Server 로 사용하기 위해서는 라이브러리 추가와 몇가지 설정이 필요합니다. 1. Eureka 사용을 위한 의존성 추가하기

0

[Spring Batch] - 배치 설정 (application.properties)

✅ 배치 잡의 실행 여부를 설정# Spring Boot 가 자동으로 실행시키는 Batch Job 을 실행시키도록 한다.spring.batch.job.enabled: true # default# Spring Boot 가 자동으로 실행시키는 Batch Job 을 실행하지 않도록 한다.spring.batch.job.enabled: false ✅ 배치 잡의 재시작 여부를 설정# 기본이 true, 재시작을 허용한다.spring.batch.job.restartable: true# 재시작을 허용하지 않는다spring.batch.job.restartable: false ✅ 실행할 배치 잡의 이름을 지정스프링 배치는 실행시 기본적으로 모든 Job 을 실행시킵니다. spring.batch.job.names 를 이용해 지정 Job 만 실행하도록 설정할 수 있습니다. 프로그램 실행시 특정 Job 을 전달받아 실행시키고 싶을 경우 spring.batch.job.names: ${job.name:NONE} 로 설정하면 외부에서 주입받은 Job 이름을 이용해 실행시킬 수 있습니다. 만일 전달받은 값이 없으면 아무 Job 도 실행시키지 않습니다. :NONE 은 프로퍼티 표현 중 하나로 전달받은 값이 없을 경우 NONE 으로 대체한다는 의미이다. # Hard Coding 방식spring.batch.job.names: springJob1# Binding 을 사용한 방식spring.batch.job.names: ${job.name:NONE}