개발/JAVA (29)
히카리 풀 모니터링

히카리 풀을 사용하게 되면 housekeeper란 놈이 기본으로 30초마다 풀 상태를 debug모드로 찍고있는데

커스터마이징이 잘 안되서 별도로 찍기로 함

 

@SpringBootApplication
@EnableScheduling
class ApiApplication(
        val dataSource: DataSource) {

    @Scheduled(fixedDelay = 10000)
    fun hikariPoolMonitor() {
        (DirectFieldAccessor(dataSource).getPropertyValue("pool") as HikariPool?)?.let { hikariPool ->
            logger().info("Pool stats (total=${hikariPool.totalConnections}, active=${hikariPool.activeConnections}, idle=${hikariPool.idleConnections}, waiting=${hikariPool.threadsAwaitingConnection})")
        }
    }
}

 

2020-04-28 11:22:58,868 INFO  [scheduling-1] [||] [n.d.api.ApiApplication:23] : Pool stats (total=50, active=0, idle=50, waiting=0)
2020-04-28 11:23:08,868 INFO  [scheduling-1] [||] [n.d.api.ApiApplication:23] : Pool stats (total=50, active=0, idle=50, waiting=0)
2020-04-28 11:23:18,869 INFO  [scheduling-1] [||] [n.d.api.ApiApplication:23] : Pool stats (total=50, active=0, idle=50, waiting=0)
  Comments,   0  Trackbacks
댓글 쓰기
동기(Synchronous)/비동기(Asynchronous) 처리

다음과 같이 여러개의 작업을 동시에 처리할때 작업1,2가 메인작업과 별개의 작업일때

비동기(@Async)로 던져놓고 메인 작업만 완료 후 응답을 주기도 합니다.

(응답시간 0.5초, 작업1,2의 결과 받을 수 없음)

 

메인작업 : 0.5초

작업1 : 1초

작업2 : 2초

 

하지만 3가지 작업의 결과가 모두 필요할 경우 동기 처리를 하면 3.5초가 걸리지만

작업1, 2를 비동기로 처리하면 일찍 처리가 끝난 작업은 모든 작업이 완료될때까지 대기 후 응답을 줍니다.

(응답시간 2초, 3개의 작업 결과 확인 가능)

 

Kotlin

@RestController
@EnableAsync
class ThreadTest(
        val taskService: TaskService,
        val mainService: MainService
) {
    @GetMapping("task")
    fun testMapping(): String {
        val start = System.currentTimeMillis()

        val task1 = taskService.task1()
        val task2 = taskService.task2()
        val main = mainService.task()

        return "main[$main], task1[${task1.get()}], task2[${task2.get()}], total[${System.currentTimeMillis()-start}]"
    }
}

@Service
class MainService {
    fun task(): Long {
        val start = System.currentTimeMillis()
        Thread.sleep(500)
        return (System.currentTimeMillis()-start)
    }
}

@Service
class TaskService {
    @Async
    fun task1(): CompletableFuture<Long> {
        val start = System.currentTimeMillis()
        Thread.sleep(1000)
        return CompletableFuture.completedFuture(System.currentTimeMillis()-start)
    }

    @Async
    fun task2(): CompletableFuture<Long> {
        val start = System.currentTimeMillis()
        Thread.sleep(2000)
        return CompletableFuture.completedFuture(System.currentTimeMillis()-start)
    }
}

 

테스트1

http://localhost:8080/task

main[502], task1[1001], task2[2001], total[2007]


메인 작업은 처리 완료 후 비동기 작업들이 완료될때까지 대기 후 응답을 주고 있습니다.

 

테스트2

반대로 동기로 처리하는 메인작업이 오래걸릴 경우도 테스트 해보겠습니다.

(MainService의 작업시간을 3초로 늘려주세요)

 

메인작업 : 3초

작업1 : 1초

작업2 : 2초

 

http://localhost:8080/task

main[3001], task1[1001], task2[2001], total[3004]


당연히 메인작업이 처리되는동안 1,2번 작업은 비동기로 작업을 완료하고 응답을 같이 주고 있습니다.

 

 

* 동시에 여러 곳을 연동해서 결과를 취합한다거나

  자원이 남아 별도의 스레드에서 처리하고자 할 때 사용하시면 됩니다.

 

 

 

 

 

 

  Comments,   0  Trackbacks
댓글 쓰기
RestTemplate 한글 깨짐

FCM 연동 중 한글 깨짐 현상 발생 시 다음과 같이 메세지 컨버터의 인코딩을 변경한다.

 

Kotlin

        val restTemplate = RestTemplate().apply {
            messageConverters.forEach {
                if(it is StringHttpMessageConverter) {
                    it.defaultCharset = Charset.forName("UTF-8")
                }
            }
        }

 

  Comments,   0  Trackbacks
댓글 쓰기
Kotlin toIntOrNull

코틀린에서 숫자 변환시에 자주 쓰이는 함수입니다.

다른 용도로 숫자인지 아닌지를 판단할때도 자주 쓰는데 주의할 점이 있습니다

fun main(args:Array<String>)
{
    "1234".run {
        println(this.toIntOrNull() ?: "$this is not number")
   }
    "349505749735".run {
        println(this.toIntOrNull() ?: "$this is not number")
        println(this.toBigIntegerOrNull() ?: "$this is not number")
    }
}

//1234
//349505749735 is not number
//349505749735

위 예제를 보시면 문자열이 숫자인지 아닌지 판단하기 위해 toIntOrNull을 사용하였지만

중간에 숫자가 아니라고 나오는 경우가 발생합니다.

 

이미 눈치채셨겠지만 숫자가 너무 커서 toInt를 하지 못한 경우입니다.

toBigIntegerOrNull로 대체해서 해결되었지만 무심코 쓰다가 실수할 수 있는 부분이니 신경 써주면 좋습니다

  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트+마이바티스에서 트랜잭션이 안먹을때

@Transactional(rollbackFor = Exception.class)


1. rollbackFor를 명시해준다

2. 메소드는 public으로 선언해준다


다른 패키지에서 안쓴다고 public으로 안했다가 삽질 경험

그 외에 다른 DataSourceTransactionManager니 @EnableTransactionManagement니

이런거 안넣어도 잘 동작함(블로그 글 기준)


[개발/JAVA] - 스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기


참고

https://stackoverflow.com/questions/7085271/how-to-set-up-transaction-with-mybatis-and-spring



  Comments,   0  Trackbacks
댓글 쓰기
코틀린으로 스프링 사용 시 ClassNotFoundException: kotlin.reflect.full.KClasses

Caused by: java.lang.ClassNotFoundException: kotlin.reflect.full.KClasses

at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_151]

at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_151]

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) ~[na:1.8.0_151]

at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_151]

... 26 common frames omitted


코틀린으로 스프링을 사용 시 위와 같이 오류가 난다

이 경우 kotlin-reflect을 추가해주면 간단히 해결


<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect -->

<dependency>

    <groupId>org.jetbrains.kotlin</groupId>

    <artifactId>kotlin-reflect</artifactId>

    <version>1.2.41</version>

</dependency>



  Comments,   0  Trackbacks
댓글 쓰기
H2 DB 사용 중 file is locked 발생

H2 DB를 사용 시 다음과 같은 오류 메세지를 만날때가 있습니다


The file is locked


내용 보면 알겠지만 여러 프로세스에서 동시에 접근할때 발행하는 오류입니다

제 경우 API 서버를 띄워놓고 배치를 돌릴때 이미 DB를 사용중이기 때문에 발생한 경우였습니다.

그럴 경우 설정을 다음과 같이 변경해 주시면 두개의 프로세스에서 동시 접근이 가능합니다.


datasource:
# url: jdbc:h2:file:~/test
url: jdbc:h2:~/test;AUTO_SERVER=true
username: sa
driver-class-name: org.h2.Driver


  Comments,   0  Trackbacks
댓글 쓰기
spring profile 사용시 주의점

최근 삽질


application.yml에 값을 설정 해두고

spring:
profiles: local
sleep:
min: 100
max: 500
---

spring:
profiles: dev

sleep:
min: 500
max: 1000


application.properties에 아래와 같이 프로파일을 설정해주었다

spring.profiles.active=dev


어플리케이션 구동했으나 계속 발생하는 오류.  심지어 다른 서버에서는 정상 동작


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sleepAspect': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'sleep.min' invalue "${sleep.min}"


오류를 유심히 보니 프로파일이 다른것으로 적용이 되고 있었음
INFO(6347)[main] [n.d.TestApplication:656] The following profiles are active: dev1


원인은 서버 환변경수에 다음과 같이 프로파일이 적용되고 있었음

$ env | grep PROFILE

SPRING_PROFILES_ACTIVE=dev1


결론은 프로퍼티보다 서버 환경변수의 값이 먼저 적용하기 때문에 쉘 스크립트에서 환변경수를 초기화

프로그램안에만 적용되기 때문에 다른 프로그램에는 영향 없음

SPRING_PROFILES_ACTIVE=

nohup java -cp application.properties -jar donnert.jar > console.log 2>&1 &



  Comments,   0  Trackbacks
댓글 쓰기
@Scope 어노테이션 사용하기(request+ProxyMode)

[개발/JAVA] - Threadlocal을 이용하여 사용자별 요청 처리하기


ThreadLocal과 같이 보시면 좋습니다.

스프링에서 빈 Scope 타입은 여러가지 방식이 있으며 아래를 참고해 주세요.

https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html


별도의 옵션이 없다면 빈은 싱글톤으로 관리되며 부모의 속성을 따라갑니다.

즉 controller는 따로 설정을 하지 않았기 때문에 singleton으로 동작하게 되며 그 안에서 주입받은 빈 역시 scope를 request로 줘도 singleton으로 동작하게 됩니다.

빈을 생성해 주고 위에 보이는것처럼 scope를 request로 선언해줍니다. 


그리고 위처럼 컨트롤러를 구현합니다.

위 경우 Cart가 매 요청마다 생성될것 같지만 이미 Controller(부모)가 싱글톤으로 잡혀있고 빈을 주입받은 상태이기 때문에 Cart 역시 싱글톤으로 동작합니다.  결국 Cart와 Mart는 동작 상 다른 점이 없습니다.


이럴땐 아래처럼 ProxyMode를 지정하게 되면 싱글톤 객체를 중간 프록시에서 새로운 오브젝트로 전달을 해주게 됩니다.

이전 글에서 Threadlocal을 이용했지만 스프링의 빈 만으로도 쉽게 개발이 가능합니다.


https://github.com/donnert/spring-boot/tree/master/scope-proxy

  Comments,   0  Trackbacks
댓글 쓰기
Threadlocal을 이용하여 사용자별 요청 처리하기





개발시 소스 전체에서 사용할 수 있는 전역변수처럼 데이터를 할당하고 싶을떄가 있습니다.

Threadlocal을 이용하여 이를 구현해 봅니다.


ShoppingController.java

@RestController
public class ShoppingController {

	private int melon;

	@RequestMapping("/")
	private String test() {
		this.addMelon();
		
		return String.valueOf(melon);
	}
	
	private void addMelon() {
		melon++;
	}
}

사용자별로 요청 시 카트에 멜론을 담는 기능을 구현하려고 합니다.  이 떄 카트 수량은 여러 메소드에서 사용할 수 있기 때문에 멤버변수로 선언을 합니다.


이렇게 작성 후 실행을 해보면 카운트가 초기화가 되지 않고 계속 증가하는 것을 볼 수 있습니다.  스프링의 경우 빈 관리를 싱글톤으로 하기 때문에 ShoppingController 객체는 요청 시 재사용 됩니다.  멤버변수 역시 초기화가 되지 않습니다.




아래는 Threadlocal을 이용해 사용자별 카트를 구현한 소스입니다.


ThreadRepository.java

public class ThreadRepository {
	private static ThreadLocal<Cart> threadLocal = new ThreadLocal<Cart>() {
		@Override
		protected Cart initialValue() {
			return new Cart();
		};
	};
	
	public static Cart getCart() {
		return threadLocal.get();
	}
	
	public static void remove() {
		threadLocal.remove();
	}
}

카트를 담을 저장소를 만들어 줍니다.  Thradlocal을 이용하여 Thread별로 저장소가 생성이 됩니다.


Cart.java

public class Cart {
	private int apple = 0;
	private int orange = 0;
	private int melon = 0;
	
	public void addApple() {
		this.apple++;
	}

	public void addOrange() {
		this.orange++;
	}

	public void addMelon() {
		this.melon++;
	}

	@Override
	public String toString() {
		return "Cart [apple=" + apple + ", orange=" + orange + ", melon="
				+ melon + "]";
	}
}

카트를 생성해줍니다.


ShoppingController.java

@RestController
public class ShoppingController {

	@RequestMapping("/")
	private String test() {
		ThreadRepository.remove();
		
		this.addMelon();
		
		return ThreadRepository.getCart().toString();
	}
	
	private void addMelon() {
		ThreadRepository.getCart().addMelon();
	}
}

컨트롤러가 위처럼 수정되었습니다.  ThreadRepository에서 getCart를 했을때 해당 요청 쓰래드에 대한 Cart가 나오기 때문에 소스 어떤부분에서든 해당 카트의 데이터를 넣고 빼는 것이 가능합니다.

이때 ThreadLocal을 이용하였기 때문에 요청 시 새로운 스래드가 생성되며 그 스래드에서만 사용할 수 있게 관리됩니다.


* 일반으로 WAS는 쓰래드풀을 쓰기 때문에 요청이나 응답 시 해당 Threadlocal을 초기화 하지 않을 경우 이전에 사용한 정보가 남아 있습니다.  쓰래드 재사용시 반드시 remove를 통해 초기화 시켜주어야 합니다.



참고소스

https://github.com/donnert/spring-boot/tree/master/thread-local




  Comments,   0  Trackbacks
댓글 쓰기