0%

들어가기에 앞서

글을 정리하다 보니 너무 깊게 파고 정리한 거 같아 글이 너무 길어져서 아무도 읽지 않을 것 같아 정리부터 해보겠습니다.

  1. 엔티티 매니저의 persist 메서드는 리턴값이 없기 때문에 원본 객체를 수정하고, merge 메서드는 리턴값이 있기 때문에 새로운 객체를 반환합니다.
  2. JpaRepository.save 호출 시 엔티티의 식별자(@Id, @EmbeddedId 어노테이션이 붙은 컬럼 등등)가 붙은 필드의 타입이 primitive type이 아닐 때는 null이거나 숫자형일 때는 0이면 새로운 엔티티라고 판단하면서 persist 메서드가 호출되고, 그게 아니면 merge 메서드가 호출됩니다.
  3. JPQL 호출 시 FlushMode가 AUTO(하이버네이트 기본 FlushMode)라 하더라도 쿼리 지연 저장소에 JPQL에서 사용하는 테이블과 관련있는 쿼리가 저장돼있지 않다면 flush를 호출하지 않습니다.
  4. JPQL 호출 시 AutoFlushEvent가 발생하면서 flush 이전에 cascade가 먼저 이뤄지는데 이 때는 PersistEvent가 발생하면서 원본 엔티티를 변경합니다.
  5. JpaRepository.save 호출 시 엔티티가 새로운 엔티티가 아니면 MergeEvent가 발생하고, cascade가 발생하는데 이 때 해당 엔티티에 대해 MergeEvent가 또 발생하면서 Transient 상태인 경우에는 원본 엔티티를 카피하고 카피한 객체의 값을 수정하고 연관관계가 맺어진 엔티티에서는 레퍼런스도 카피 객체로 바꿔치기 하고 있습니다.
  6. JpaRepository.save 호출 시 엔티티가 새로운 엔티티가 아니면 MergeEvent가 발생하는데 cascade 이후에 DirtyChecking이나 Flush가 호출되지 않습니다.
  7. 모든 트랜잭션이 끝난 이후에 커밋 이전에 FlushMode가 MANUAL이 아니고, Managed Entity가 존재하면 FlushEvent를 발생시켜서 DirtyChecking 및 Flush를 하게 됩니다.

제목은 엔티티 매니저의 persist와 merge에 대해 개념을 설명할 것처럼 적어놨지만 이해를 돕기 위해, 흥미 유발을 위해 사내에서 겪었던 문제 과정을 서술하겠습니다.

문제 상황

더 읽어보기 »

3줄 요약

  1. OSIV가 꺼져있으면 트랜잭션이 시작될 때 엔티티 매니저가 생성되고, 트랜잭션이 끝날 때 엔티티 매니저를 종료한다.
  2. OSIV가 꺼져있고, 다른 트랜잭션이라면 엔티티 매니저가 공유되지 않기 때문에 엔티티 매니저의 1차 캐시도 서로 공유되지 않는다.
  3. OSIV가 켜져있으면 요청 당 엔티티 매니저는 한 번 생성되고, 뷰 렌더링이 끝날 때까지 엔티티 매니저는 종료되지 않고 트랜잭션이 다르더라도 1차 캐시가 공유된다.

들어가기에 앞서

엔티티 매니저 팩토리는 생성 비용이 비싸서 대부분 어플리케이션 당 하나를 생성하는 편이고, 엔티티 매니저는 생성 비용이 비싸지 않아서 어플리케이션에서 여러 번 생성된다.
하지만 엔티티 매니저는 쓰레드 세이프 하지 않기 때문에, 쓰레드 당 하나를 생성해야할 것 같고 Spring MVC는 리퀘스트 당 하나의 쓰레드가 할당되기 때문에 리퀘스트 당 하나의 엔티티 매니저가 생성될 것만 같은 기분이 든다.
나 또한 그렇게 알고 있었는데 아래 코드를 통해 뭔가 의문이 생겼다.

1
2
3
4
5
6
7
8
9
10
11
interface SomeRepository : JpaRepository<SomeEntity, Long>

@Service
class SomeService(
private val repository: SomeRepository
) {
fun some() {
val someEntity = repository.findById(1L)
val someEntity2 = repository.findById(1L)
}
}
더 읽어보기 »

3줄 요약

  1. @Transaction(readOnly = true)로 설정해도 트랜잭션은 시작된다. (transaction isolation level 보장)
  2. readOnly 트랜잭션도 시작한 트랜잭션을 종료시켜야하기 때문에 커밋도 한다.
  3. readOnly 트랜잭션의 Hibernate Session의 FlushMode는 Manual로 강제하기 때문에 트랜잭션을 커밋하기 전에 flush를 하지 않는다. (readOnly 보장)

@Transaction(readOnly = true)로 설정해도 트랜잭션은 시작된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface SomeEntityRepository extends JpaRepository<Parent, Long> {
@Transactional(readOnly = true)
List<Parent> findByName(String name);
}

@Service
public SomeService {
private final SomeEntityRepository repository;

public SomeService(final SomeEntityRepository repository) {
this.repository = repository;
}

public void test() {
repository.findByName("qwer");
}
}


repository의 구현체는 프록시 객체로써 인터페이스이기 때문에 jdk dynamic 프록시 객체가 생성이 된다.
또한 TransactionInterceptor라는 Advisor를 가지고 있으며

더 읽어보기 »

Netty를 사용하다보면 채널 파이프라인에 여러 이벤트 핸들러를 추가하기 마련이다.
그러다보니 순서가 중요할 때가 있다.

  1. 클라에서 보낸 데이터 중에 헤더를 파싱하고,
  2. 헤더에 따라 바디를 파싱하고,
  3. 바디를 토대로 뭔가를 또 처리해야하고…

이런 식으로 N 개의 이벤트 핸들러를 붙여야하고, 순서가 중요하다보니 어떤 순서대로 실행되는지가 궁금해졌다.

Inbound Event Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ExampleHandler1 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("1")
ctx.fireChannelRead(msg)
}
}

class ExampleHandler2 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("2")
ctx.fireChannelRead(msg)
}
}

class ExampleHandler3 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("3")
ctx.fireChannelRead(msg)
}
}
더 읽어보기 »

Netty는 왜 자바 표준인 NIO의 ByteBuffer를 사용하지 않는 걸까 이유를 몰랐는데 자바 네트워크 소녀 네티를 보고 이유를 알게되어 정리해봄.
ByteBuffer와 ByteBuf의 세부사항 보다는 ByteBuffer는 어떤 문제점을 가지고 있고, ByteBuf는 그 문제점을 어떻게 해결했는지에 초점을 맞추어 정리함.

ByteBuffer의 문제점

Netty의 ByteBuf는 자바의 ByteBuffer가 가진 문제점들을 해결하기 위해 나왔다.

데이터 쓰기/읽기 인덱스가 분리돼있지 않다

1
2
3
4
5
6
7
val byteBuffer = ByteBuffer.allocate(3) // 3바이트를 담을 수 있는 힙버퍼, 전부 0으로 초기화된다.
println(byteBuffer) // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]

byteBuffer.put(1)
println(byteBuffer) // java.nio.HeapByteBuffer[pos=1 lim=3 cap=3]

println(byteBuffer.get()) // 0
더 읽어보기 »

이벤트 루프의 개념이 명확하지 않아 자바 네트워크 소녀 네티를 보고 정리해봄.

통상적으로 이벤트 기반 어플리케이션이 이벤트를 처리하는 방식은 아래 두 가지가 존재한다고 함.

이벤트 리스너와 이벤트 처리 쓰레드 방식

브라우저에서 DOM에 클릭 이벤트를 어떻게 핸들링하는지 생각해보면 된다.

1
document.querySelector('body').onclick = e => console.dir(e) 
더 읽어보기 »

Netty의 개념이 하도 익숙하지 않아 자바 네트워크 소녀 네티를 보고 용어를 정리해봄.

Netty

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

네티는 비동기 이벤트 기반 네트워크 어플리케이션 프레임워크로써 유지보수를 고려한 고성능 프로토콜 서버와 클라이언트를 빠르게 개발할 수 있다.
즉, TCP 통신을 위해 무조건 Netty를 써야하는 건 아니지만 유지보수하기도 쉽고, 비동기 이벤트 기반이기 때문에 고성능도 보장하게 된다.
Spring Integration 또한 TCP 통신을 지원한다.

Spring Integration provides channel adapters for receiving and sending messages over internet protocols.
Both UDP (User Datagram Protocol) and TCP (Transmission Control Protocol) adapters are provided.

더 읽어보기 »

N줄 요약

SimpleClientHttpRequestFactory(RestTemplate을 기본생성자로 만들었을 때 사용하는)를 사용하더라도 내부에서 KeepAliveCache를 사용하여 커넥션 풀을 관리한다.
기본적으로 KeepAliveKey(protocol, host, port) 당 5개의 풀을 가지며 시스템 프로퍼티 http.maxConnections를 할당해주면 늘릴 수 있다.
커넥션 풀을 초과하면 커넥션은 바로 종료되며, 커넥션 풀 내의 커넥션은 매번 연결을 맺고 끊는 게 아니라 재사용 된다.
당연하게도 서버에서 Keep-Alive를 사용하지 않으면 매번 커넥션이 종료된다.
그럼에도 불구하고 SimpleClientHttpRequestFactory는 다음의 단점이 있기 때문에 토이 프로젝트가 아닌 이상 HttpComponentsClientHttpRequestFactory 같은 다른 구현체를 사용해야할 것 같다.

  • http.maxConnections라는 시스템 프로퍼티를 설정해야하는데 설정을 위해 자주 사용하던 properties(yml)에는 설정할 수 없다보니 다른 방법으로 설정을 해줘야하고, 그러다 보면 설정을 파악하려면 한 군데(properties 또는 yml)만 집중해서는 파악할 수 없는 내용도 있다보니 실수할 여지가 발생할 수 있다.
  • KeepAliveCache가 static 변수이다보니 서로 다른 SimpleClientHttpRequestFactory여도 동일한 커넥션 풀을 참조한다.
  • route(프로토콜, 호스트, 포트) 별 커넥션 풀은 설정할 수 있지만 토탈 커넥션 풀은 제한이 없다.

SimpleClientHttpRequestFactory가 뭐지??

RestTemplate의 기본 생성자를 사용하면 ClientHttpRequestFactory를 별도로 초기화하지 않으므로 기본값인 SimpleClientHttpRequestFactory를 사용한다.

더 읽어보기 »

Spring Boot Reference의 Testing - Detecting Test Configuration 파트를 보면 다음과 같은 내용이 나온다.

If you are familiar with the Spring Test Framework, you may be used to using @ContextConfiguration(classes=…​) in order to specify which Spring @Configuration to load. Alternatively, you might have often used nested @Configuration classes within your test.
When testing Spring Boot applications, this is often not required. Spring Boot’s @*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.
The search algorithm works up from the package that contains the test until it finds a class annotated with @SpringBootApplication or @SpringBootConfiguration.

Detecting Test Configuration을 위해서 스프링에 친숙하다면 @ContextConfiguration이나 Nested @Configuration이 필요하다고 하고,
Spring Boot를 사용하면 @*Test(@SpringBootTest, @WebMvcTest, @DataJpaTest, etc.)에서 별다른 설정을 하지 않았다면 primary configuration을 찾아나간다고 한다.

N줄 요약

글이 길어지다보니 아무도 안 볼 거 같고, 집중을 하고 소스코드를 따라가면서 읽어야해서 우선 먼저 요약을 적어놓는다.

더 읽어보기 »

1
2
3
dependencies {
compileOnly("org.springframework.boot:spring-boot-starter-web:2.4.0")
}

compileOnly는 compile classpath에만 추가된다.
runtime classpath에는 추가되지 않아서 원래는 java.lang.ClassNotFoundException이 나야 정상이다.
하지만 IntelliJ의 Spring Boot Configuration으로 실행하면 실행이 잘만 된다.


실제로 classpath를 찍어보면 아래와 같이 spring-boot-starter-web을 포함하고 있다.

1
2
3
4
5
fun main(args: Array<String>) {
val property = System.getProperty("java.class.path")
println(property)
runApplication<DemoApplication>(*args)
}
1
2
3
4
5
6
7
/Users/perfectacle/IdeaProjects/demo/build/classes/kotlin/main:
/Users/perfectacle/IdeaProjects/demo/build/resources/main:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.4.10/998caa30623f73223194a8b657abd2baec4880ea/kotlin-stdlib-jdk8-1.4.10.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.4.0/4bdd422c370d1d66ffc12ecafdecc70cad406367/spring-boot-starter-web-2.4.0.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.10/30e46450b0bb3dbf43898d2f461be4a942784780/kotlin-stdlib-jdk7-1.4.10.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.10/ea29e063d2bbe695be13e9d044dcfb0c7add398e/kotlin-stdlib-1.4.10.jar:
...
더 읽어보기 »