(스프링) RequestContextHolder

RequestContextHolder 는 요청당 발생되는 스레드에 바인딩되어 RequestAttributes 를 key/value 쌍으로 가지고 있다. 개발적인 측면에선 레이어에 구분없이 스태틱하게 접근할 수 있다. 하지만 메인스레드를 벗어난 별도 스레드풀에서 꺼낸 다른 스레드에서 RequestContextHolder 에 접근하여 값을 꺼내려고 하면 null 이 나온다. 이를 방지하기 위해선 인터넷에 여러 방법들이 있는 걸로 보인다. 다만 직관적이면서 영향도를 가장 최소화하는 방법은 전달되는 Param 객체에 RequsetAttributes 내용을 인자로 같이 넘겨서 수신받는 코드쪽에 아래와 같이 작성하면 해소가 된다.

fun asyncMethod(param: Param) {
	// 할당
    RequestContextHolder.setRequestAttributes(param.requestAttributes)
    
    // .. logic ..
    
    // 명시적 해제
    RequestContextHolder.resetRequestAttributes()
}

 

 

(스프링) @TransactionalEventListener

서비스에서 객체의 상태의 변경 혹은 비즈니스 로직으로 인하여 콜백이나 혹은 이벤트 전파가 필요한 경우가 있다. 이 때 레이어간 도메인을 침범하지 않고 싶을 때 쓰는 EventListener 의 트랜잭션 버전이다. 트랜잭션이 제공되진 않고 트랜잭션의 전/후에 따라서 이벤트 호출 시점을 조정할 수 있다. 이번에 처음 사용해본건 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 이다. 이걸 사용하면 트랜잭션의 동작이 끝나고 이후에 EventListener 가 동작한다. 하지만 해당 이벤트 영역에선 트랜잭션 수행을 시킬 수 없다. 억지로 수행시키면 아래와 같은 에러를 만난다.

ERROR 54966 --- [io-48081-exec-1] o.s.t.s.TransactionSynchronizationUtils  
: TransactionSynchronization.afterCompletion threw exception
org.springframework.dao.InvalidDataAccessApiUsageException
: no transaction is in progress

 

트랜잭션을 동작하고 싶으면 @TransactionalEventListener 가 붙은 메소드에 다른 애노테이션을 붙여주어야 동작한다. 방법은 두가지다.

(1) @Transactional(propagation = Propagation.REQUIRES_NEW) 를 같이 붙여준다.

(2) @Async + @Transactional 를 같이 붙여준다.

 

(1) 은 신규 트랜잭션을 만들어 처리하는 것이고 (2) 는 비동기 스레드풀에서 다른 스레드에서 트랜잭션을 처리하는 것이다. 필요에 따라 사용하도록 한다. (+ 익셉션 핸들링은 당연하게 하자.)

 

 

(kotlin) 확장함수 로그작성

확장함수에서 로그를 써야할 경우가 있을 수 있음. 클래스의 경우는 필드값으로 선언처리할 수 있지만 확장함수는 클래스내 필드로 존재할 수 없다. 따라서 그냥 static 하게 올려놓고 써야한다. 스프링이랑 같이 운용할 때 사실 로깅 객체도 빈 안에 선언된 형태로 쓰이기 때문에 그것또한 결국 static 하다고 생각한다. (아래 코드를 디컴파일하면 logger 객체는 static {} 블럭안에 처리되어있다.)

// kotlin 기준.
class CustomExtension

val logger: Logger  = LoggerFactory.getLogger(CustomExtension::class.java)

fun Person.toBatMan(): Person {
    logger.info("person -> batman")
    return this.copy(
        name = "batman",
        age = 99999
    )
}

// 로그는 아래와 같이 출력된다.
// 00:41:47.496 [main] INFO com.example.springbootbasis.practice.logger.CustomExtension -- person -> batman

 

 

(이펙티브 코틀린) 안정성 있는 코드를 만들기

require/check/assert 를 이용하여 예외를 일으키고 코드에 제한을 두자. 여기서 assert 는 제외하고 require/check 만 보려고 한다.

 

require : 아규먼트에 제한을 두기

- age 라는 필드값은 음수가 될 수 없다고 제한을 두고 음수가 되는 케이스의 경우 IllegalArgumentException 에러를 발생시킨다.

fun main() {
     Person(age = -1, "홍길동")
}

data class Person(
    val age: Int,
    val name: String
) {
    init {
        require(age >= 0) {
            "나이는 음수가 될 수 없습니다. age=$age"
        }
    }
}

Exception in thread "main" java.lang.IllegalArgumentException: 나이는 음수가 될 수 없습니다. age=-1

 

check : 상태값 조건을 확인

- isOrdered 상태값이 true 인 경우에 IllegalStateException 에러를 발생시킨다.

fun main() {
    val order = Order().apply { this.isOrdered = true }
    order.ordered()
}


class Order {
    var isOrdered: Boolean = true

    fun ordered() {
        check(isOrdered.not()) {
            "주문된 상태입니다. ordered=$isOrdered"
        }
    }
}

 

 

(이펙티브 코틀린) 초기화를 지연시킨다면 명시적으로 알린다.

기본타입에는 Dellgates.notNull() 을 사용한다. 그 외 레퍼런스에는 lateinit var 을 사용한다. !! 연산자를 지양하라고 책에서 한다. 정적분석도구인 https://github.com/detekt/detekt 이라는 오픈소스를 추천해줌

'Interest > 개발' 카테고리의 다른 글

2024-04 개발 : 자투리기록  (1) 2024.04.10
2024-03 개발 : 자투리 기록  (0) 2024.03.09
2024-01 개발 : 자투리 기록  (0) 2024.01.21
2023-11-25 개발 : 자투리 기록  (0) 2023.11.25
2023-11-19 개발 : 자투리 기록  (0) 2023.11.19
Posted by doubler
,