slf4j + logback 환경에서 이니지스 결재 모듈(INIpay50.jar) 사용 시 이니시스 로깅에 문제가 발생한다.
이니시스측에서도 log4j 를 사용하라고 권고한 상황..
하지만 쓰고 싶다면...
'org.slf4j:log4j-over-slf4j' 부분을 exclude 하면 된다.
※ 'org.slf4j:log4j-over-slf4j' 는 기존 라이브러리가 log4j를 사용하고 있을때, slf4j로 넘겨주는 역할은 하는거란다.
slf4j + logback 환경에서 이니지스 결재 모듈(INIpay50.jar) 사용 시 이니시스 로깅에 문제가 발생한다.
이니시스측에서도 log4j 를 사용하라고 권고한 상황..
하지만 쓰고 싶다면...
'org.slf4j:log4j-over-slf4j' 부분을 exclude 하면 된다.
※ 'org.slf4j:log4j-over-slf4j' 는 기존 라이브러리가 log4j를 사용하고 있을때, slf4j로 넘겨주는 역할은 하는거란다.
현재 겉모습은 제니퍼의 X-View 와 비슷하게 따라 하려는중이다.ㅎㅎ
1차적으로 요청 URI, 요청 시간, 종료 시간, 처리 시간, HttpStatus 값 정도만 차트에 표현하였다.
구조는 Tomat에 Agent를 띄운 후 Agent에서 모니터링툴 WAS에 WebSocket 로 보낸 후 데이터를 받은 모니터링툴에서 모니터링에 접속한 Client Browser 에 WebSocket로 데이터를 보내주는 방식이다.
모니터링툴에 붙은 Client가 많았을 경우 어떻게 될지 아직 잘 모르겠다.
상황봐서 Queue에 담아서 한다던가 고려해볼 생각이고 현재에는 비슷하게 구현만 하는게 목적이다.
인터넷의 자료를 통해서 javassist 를 활용하여 Byte Code Instrumentation 시도해보았지만, 일반적인 Java Main Program은 잘 되는데 Tomcat 의 특정 Class를 제어 하려고 했더니 잘 되지 않았다.
많은 삽질 끝에 ClassPool을 얻어 오는 부분을 조금 수정 하니 잘 되었다.
(이 부분때문에 몇일을 삽질 하다.. 포기도 할까 하고, Application Level Servlet Filter를 이용하는 방법으로 선회 할까도 했음...)
검색을 하두 해서 어디서 이 정보를 얻었는지 기억이 안나네요.
[해결 부분]
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new LoaderClassPath(classLoader));
[2015. 7. 6.]
WAS Application 요청시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 부분을 잡아 처리 하였다.
(만들어 가며 바뀔 수도 있는 부분임..)
이렇게 함으로 인해서 어떠한 문제가 발생할지는 미지수임...
HttpStatus=302 케이스에서 문제 발생
[2015. 8. 10.]
javax.servlet.http.HttpServlet.service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse 으로 변경
이러면.. JSP Direct 호출이 안 잡히는군.. 쉽지 않구만...
다시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 원복!!
Spring Security LOGOUT HttpStatus 302일때문 문제가 발생!!
문제 발생 부분 request.getSession().getId()
이 부분을 Before 일때만 하고, After 일때는 주석
Before, After 요청 정보 맵핑을 Thread.currentThread().getId() 로 처리
우선 잘 된다...
[MonitorAgent.java]
package pe.kr.ddakker.monitor.agent; import java.lang.instrument.Instrumentation; import pe.kr.ddakker.monitor.agent.transformer.TomcatTransformer; public class MonitorAgent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new TomcatTransformer()); } public static void agentmain(String args, Instrumentation inst) throws Exception { premain(args, inst); } }
[TomcatTransformer.java]
package pe.kr.ddakker.monitor.agent.transformer; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.LoaderClassPath; import javassist.NotFoundException; /** * Tomcat 요청 가로채서 수정!! * @author ddakker 2015. 6. 14. */ public class TomcatTransformer implements ClassFileTransformer { ClassPool pool = null; public TomcatTransformer() { this.pool = ClassPool.getDefault(); } public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain, byte[] bytes) throws IllegalClassFormatException { if (className.contains("StandardEngineValve")) { System.out.println("className: " + className); return transformClass(redefiningClass, bytes); } else { return bytes; } } private byte[] transformClass(Class classToTransform, byte[] b) { CtClass cl = null; try { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new LoaderClassPath(classLoader)); if (pool != null) { cl = pool.makeClass(new java.io.ByteArrayInputStream(b)); if (cl.isInterface() == false) { CtBehavior[] methods = cl.getDeclaredBehaviors(); for (int i = 0; i < methods.length; i++) { System.out.println("methods[" + i + "]: " + methods[i]); if (methods[i].isEmpty() == false) { doTransform(methods[i]); } } } b = cl.toBytecode(); } } catch (Exception e) { System.err.println("e111: " + e); } finally { if (cl != null) { cl.detach(); } } return b; } private void doTransform(CtBehavior method) throws NotFoundException, CannotCompileException { if (method.getName().equals("invoke")) { System.out.println("0"); try { System.out.println("1"); method.insertBefore("" + "String jvmRoute = System.getProperty(\"jvmRoute\");" + "String sessionId = $1.getSession().getId();" + "String uri = $1.getRequestURI();" + "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{" + "server: '\" + jvmRoute + \"' " + ", sessionId: '\" + sessionId + \"' " + ", uri: '\" + uri + \"' " + ", stTime: '\" + System.currentTimeMillis() + \"' " + "}\");"); System.out.println("2"); method.insertAfter("" + "String jvmRoute = System.getProperty(\"jvmRoute\");" + "String sessionId = $1.getSession().getId();" + "String uri = $1.getRequestURI();" + "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{" + "server: '\" + jvmRoute + \"' " + ", sessionId: '\" + sessionId + \"' " + ", uri: '\" + uri + \"' " + ", edTime: '\" + System.currentTimeMillis() + \"' " + ", status: '\" + $2.getStatus() + \"' " + "}\");"); System.out.println("3"); } catch (Exception e) { System.err.println("Aa e: " + e); } System.out.println("4"); } } }
[build.gradle]
jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version, 'Premain-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent', 'Agent-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent', 'Can-Redefine-Classes': true } }
instrument 시 필요한 외부 Library들은 Agent jar에 포함 시켰다.
[실행]
-javaagent:D:\..\tomcat-monitor-agent-1.0.jar
[context-cache.xml]
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> <!-- EHCache Local 형 --> <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager"> <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:config/cache/ehcache.xml"></property> </bean> </property> </bean> <!-- Infinispan Server 형 --> <util:properties id="hotrod_data" location="classpath:properties/hotrod_data.properties" /> <bean id="ispnCacheManager" class="org.infinispan.spring.provider.SpringRemoteCacheManagerFactoryBean"> <property name="configurationProperties" ref="hotrod_data" /> </bean> <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager"> <property name="cacheManagers"> <list> <ref bean="ehCacheManager" /> <ref bean="ispnCacheManager" /> </list> </property> </bean> </beans>
[ehcache.xml]
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="java.io.tmpdir" /> <defaultCache maxElementsInMemory="50000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <cache name="EHCACHE_MENU" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="600" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </cache> </ehcache>
[Service.java]
@Cacheable("ISPN_EVENT") public Map getEvent(String eventKey) { return query... } @Cacheable("EHCACHE_MENU") public Map getMenu(String menuKey) { return query... }
최근 fastjson 이란 알리바바에서 공개한 JSON Parser 을 보게되어 현재 회사에서 사용중인 Library 들을 비교해 보았다.
jackson-databind-2.1.3.jar
json-lib-2.2.1-jdk15.jar
fastjson-1.2.5.jar
[테스트 상황]
Vo 안에 2개의 Vo가 있는 객체 활용하여, to JSON String, to JAVA Object 로 변환 및 역변환을 10,000 실행하였다.
서버 모니터링 시스템 구축 중 Tomcat Comet으로 했던걸 WebSocket로 변경 하였다.
단순함!
WebSocket Servlet 에서는 Client Open/Close 만 관리하는 public static Set 객체에 Client 정보를 관리하고, Queue Receive 에서 해당 Set 객체에 접근 하여 각각의 Client 에 데이터를 Push 한다.
Thread 간 통신에 public static 변수를 사용하는게 좋은건지 모르겠으나.. Thread 관련 지식이 부족한 관계로 현재는 우선 패스한다.
import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ServerEndpoint(value = "/ws/was") public class WasWebSocket { private static Logger log = LoggerFactory.getLogger(WasWebSocket.class); public static Setconnections = new CopyOnWriteArraySet<>(); private Session session; public WasWebSocket() { log.info("----------- thread start"); } public Session getSession() { return this.session; } @OnOpen public void onOpen(Session session) { log.info("onOpen this: {}, this.sessson: {}", this.getClass(), session); this.session = session; synchronized (connections){ connections.add(this); } } @OnClose public void onClose() { log.info("onClose"); synchronized (connections){ connections.remove(this); } } @OnMessage public void onMessage(String message) { log.info("@OnMessage"); log.info("websocket message: " + message); } @OnError public void onError(Throwable t) throws Throwable { log.error("e: ", t.toString()); synchronized (connections){ connections.remove(this); } } }
function wasStatusListen() { var oSocket = new WebSocket(WS_URL + "/ws/was"); oSocket.onmessage = function (e) { var json = eval("(" + e.data + ")"); if (json.result && json.result == "0000") { var wasNode = json.data.name; var heapUsedPercent = json.data.heapUsedPercent; .. .. 처리 } }; oSocket.onopen = function (e) { console.log("open"); }; oSocket.onclose = function (e) { console.log("close"); wasStatusListen(); }; window.unload = function() { if (oSocket.readyState != 3) oSocket.disconnect(); } } wasStatusListen();
Authentication Basic 로 인증되는 서버에서 제공하는 API를 이용 하는 중 Spring RestTemplate 활용 방법
[환경]
Spring-4.1.6
HttpClient-4.3.5
1. 호출 URL 에서 인증 하는 방법
Map<String, Object> resultMap = ezRestTemplate.get("http://admin:1234@localhost:12345/api/overview", HashMap.class);
2. 설정 부분에서 인증 하는 방법
[설정]
import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.SSLContext; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import pe.kr.ddakker.framework.support.wrapper.http.EzRestTemplate; @ImportResource("classpath:config/spring/context-*.xml") @Configuration public class ApplicationContext { @Bean(name = "ezRestTemplate") public EzRestTemplate getEzRestTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build(); SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier()); BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("admin", "1234")); HttpClient httpClient = HttpClientBuilder.create() .setSSLSocketFactory(connectionFactory) .setDefaultCredentialsProvider(credentialsProvider) .build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); return new EzRestTemplate(new RestTemplate(requestFactory)); } }
[사용]
// EzRestTemplate 는 RestTemplate 감쌓놓은 Wrapper Class임. @Resource EzRestTemplate ezRestTemplate; Map<String, Object> resultMap = ezRestTemplate.get("http://localhost:12345/api/overview", HashMap.class); System.out.println("resultMap: " + resultMap);
서버 모니터링 시스템을 만들고 있다.
구조는 서버의 MBean 및 Background 테스팅 도구가 실행한 정보를 각각의 N대의 서버에서 Message Qeue에 전달 하고, 모니터링 WAS에서 Queue 를 Receive 하여 Tomcat Long Pooling 을 활용하여 N대의 Client Browser 에 전송 해 준다.
public static List connections = new ArrayList<>(); 부분이 접속된 Client Browser 정보이므로, Queue Receive Event 시점에 connections Client 갯수만큼 PrintWriter 활용 해서 write 하고, connections 의 CometEvent 객체를 Close 하고, connections에서 Remove() 시킨다.
접속된 Client 정보를 public static 변수로 다른 Thread 에서 제어 하는게 잘 하는건지는 모르겠지만. 잘 된다...
사실 처음에 Tomcat Comet 방식을 이용했지만 지금은 WebSocket 방식으로 동일 하게 활용중이다.
다음 글은 동일한 상황에서 Comet 를 Websocket로 변환.. 거의 동일함..
import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.comet.CometEvent; import org.apache.catalina.comet.CometProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @WebServlet("/comet/testCase") public class TestCaseServlet extends HttpServlet implements CometProcessor { private static Logger log = LoggerFactory.getLogger(TestCaseServlet.class); public static Listconnections = new ArrayList<>(); public void init() throws ServletException { log.info("----------- thread start"); } public void destroy() { log.info("---------- thread stop"); synchronized (connections) { connections.clear(); } } @Override public void event(CometEvent event) throws IOException, ServletException { log.info("testCase event: " + event.getEventType()); HttpServletRequest request = event.getHttpServletRequest(); HttpServletResponse response = event.getHttpServletResponse(); response.setCharacterEncoding("UTF-8"); event.setTimeout(1000*60*60); if (event.getEventType() == CometEvent.EventType.BEGIN) { log.info("Begin for request: {}, response: {}, session: {}", request, response, request.getSession(true).getId()); synchronized(connections) { connections.add(event); } } else if (event.getEventType() == CometEvent.EventType.ERROR) { log.info("Error for session: {}", request.getSession(true).getId()); synchronized(connections) { connections.remove(event); } PrintWriter writer = response.getWriter(); writer.println("{result: '9999', msg: 'timeout'}"); event.close(); } else if (event.getEventType() == CometEvent.EventType.END) { log.info("End for session: " + request.getSession(true).getId()); synchronized(connections) { connections.remove(event); } PrintWriter writer = response.getWriter(); writer.println("{result: '0000', msg: 'end'}"); event.close(); } else if (event.getEventType() == CometEvent.EventType.READ) { log.info("read"); while (true) { ; } } } }
BindResult 삽질기!!!
회사 동료가 Spring Validation 기능을 활용해보자고 해서 해보는데 엄청난 시간 소모가...
결론은 BindResult 파라미터 선언을 @Valid 바로 뒤에 해야 한다는...
[TestWeb.java]
import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("test") public class TestWeb { private static Logger log = LoggerFactory.getLogger(TestWeb.class); @RequestMapping(value="case1", method=RequestMethod.GET) public String test(@Valid TestVo testVo, BindingResult bindResult, Model model) { log.debug("bindResult.hasErrors(): {}", bindResult.hasErrors()); if (bindResult.hasErrors()) { throw new RuntimeException("파라미터 설정이 잘 못 되었습니다.."); //return "coupon/mainError"; // 공통 Exception 처리 Throws 하거나 에러 뷰 return } return "test/case1"; } }
[TestVo.java]
import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter public class TestVo { @Size(min=0, max=5) private String test1; @NotNull private String test2; }
[build.gradle]
dependencies { compile 'spring.... 4.1.x...' compile 'org.hibernate:hibernate-validator:5.1.3.Final' }
최근 인터넷 곳곳에서 ORM 관련 내용들이 많이 쏟아져나오고 있다.
예전에도 Spring 과 Hibernate를 이용해서 내부 프로젝트를 진행 한적이 있었는데, 그때는 단순한것은 Criteria를 사용하고, 조금이라도 복잡한것은 HQL을 사용했던 기억인데 개념적으로 잘 이해되지 않아 초반에 힘들었던거 같은데...
이번에도 역시 많은 삽질을 해야 했다.. 개념 이해 뿐만 아니라 이래저래 많은 변화가 있었던것 같다.
Hibernate에서 JPA, Spring Data JPA, QueryDSL, Spring Data JPA & QueryDSL 등 표준이니 뭐니...
단순 셈플링이다.
아직 알지도 못하는 많은 방법들이 존재해서 뭐가 어떻게 되는건지 심도 있는 학습이 필요할 것 같다.
작은 내부 프로젝트를 진행하려고 하는데 이번 기회에 사용해봐야겠다.
[build.gradle]
apply plugin: 'java' apply plugin: 'idea' ext { javaVversion = '1.7' servletVersion = '3.0.1' springframeworkVersion = '4.1.6.RELEASE' hibernateJpaVersion = '1.0.1.Final' hibernateEntitymanagerVersion = '4.1.9.Final' hibernateCommonsAnnotationsVersion = '4.0.1.Final' hibernateValidatorVersion = '4.3.1.Final' aspectjVersion = '1.6.8' queryDslVersion = '3.2.0' springDataJpaVersion = '1.8.0.RELEASE' } List loggerSlf4jAndLogback = [ "ch.qos.logback:logback-classic:1.0.13", "org.slf4j:jcl-over-slf4j:1.7.5" ] sourceCompatibility = javaVversion version = '1.0' task wrapper(type: Wrapper) { gradleVersion = '2.3' distributionUrl = 'http://services.gradle.org/distributions/gradle-2.3-bin.zip' } repositories { mavenCentral() } dependencies { compile loggerSlf4jAndLogback compile "org.springframework:spring-webmvc:" + springframeworkVersion compile "org.springframework:spring-orm:" + springframeworkVersion compile "org.springframework:spring-aspects:" + springframeworkVersion compile "org.springframework.data:spring-data-jpa:" + springDataJpaVersion compile 'com.h2database:h2:+' //compile 'mysql:mysql-connector-java:5.1.23' compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:" + hibernateJpaVersion compile "org.hibernate:hibernate-entitymanager:" + hibernateEntitymanagerVersion compile "org.hibernate.common:hibernate-commons-annotations:" + hibernateCommonsAnnotationsVersion compile "org.hibernate:hibernate-validator:" + hibernateValidatorVersion compile "com.mysema.querydsl:querydsl-core:" + queryDslVersion compile "com.mysema.querydsl:querydsl-apt:" + queryDslVersion compile "com.mysema.querydsl:querydsl-jpa:" + queryDslVersion compile "com.mysema.querydsl:querydsl-sql:" + queryDslVersion compile "org.aspectj:aspectjrt:" + aspectjVersion compile "org.aspectj:aspectjweaver:" + aspectjVersion compile 'javax:javaee-api:7.0' //providedCompile 'javax.servlet:javax.servlet-api:3.1.0' testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "org.springframework:spring-test:" + springframeworkVersion } sourceSets { generated { java { srcDirs = ['src/main/generated'] } } } task generateQueryDSL(type: JavaCompile, group: 'build') { source = sourceSets.main.java classpath = configurations.compile options.compilerArgs = [ "-proc:only", "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor" ] destinationDir = sourceSets.generated.java.srcDirs.iterator().next() } compileJava { dependsOn generateQueryDSL source generateQueryDSL.destinationDir } compileGeneratedJava { dependsOn generateQueryDSL options.warnings = false classpath += sourceSets.main.runtimeClasspath } clean { delete sourceSets.generated.java.srcDirs } idea { module { sourceDirs += file('src/main/generated') } }
[Config AppConfig.java]
package pe.kr.ddakker.jpa; import org.h2.tools.Server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.aspectj.EnableSpringConfigured; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.beans.PropertyVetoException; import java.sql.SQLException; import java.util.Properties; /** * Created by ddakker on 2015-04-09. */ @Configuration @ComponentScan("pe.kr.ddakker.jpa") @EnableTransactionManagement @EnableSpringConfigured @EnableJpaRepositories("pe.kr.ddakker.jpa") public class AppConfig { @Autowired Environment env; @Bean public DataSource testDataSource() { return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build(); } @Bean public HibernateJpaVendorAdapter jpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws SQLException, PropertyVetoException{ LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(testDataSource()); emf.setJpaVendorAdapter(jpaVendorAdapter()); emf.setPersistenceUnitName("rss"); emf.setPackagesToScan("pe.kr.ddakker.jpa.domain"); Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); properties.put("hibernate.hbm2ddl.auto", "create-drop"); // properties.put("hibernate.show_sql","true"); // properties.put("hibernate.format_sql", "true"); emf.setJpaProperties(properties); return emf; } @Bean public JpaTransactionManager transactionManager() throws PropertyVetoException, SQLException{ JpaTransactionManager trans = new JpaTransactionManager(); trans.setEntityManagerFactory(entityManagerFactory().getObject()); return trans; } }
[Domain User.java]
package pe.kr.ddakker.jpa.domain; import javax.persistence.*; import java.io.Serializable; import java.util.Date; @Entity public class User implements Serializable { @Id @Column(name = "USER_ID") private Long id; @Column(name = "USER_NM") private String name; @Column(name = "USER_AGE") private Integer age; @Temporal(TemporalType.DATE) @Column(name = "REG_DT") private Date regDt; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getRegDt() { return regDt; } public void setRegDt(Date regDt) { this.regDt = regDt; } }
[Repository UserRepository.java]
package pe.kr.ddakker.jpa.repository; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.CrudRepository; import pe.kr.ddakker.jpa.domain.User; import java.util.List; /** * Created by ddakker on 2015-04-09. */ public interface UserRepository extends CrudRepository, QueryDslPredicateExecutor { public List findByName(String name); }
[Test UserRepositoryTest.java]
package pe.kr.ddakker.jpa.repository; import com.mysema.query.jpa.impl.JPAQuery; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; import pe.kr.ddakker.jpa.AppConfig; import pe.kr.ddakker.jpa.domain.QUser; import pe.kr.ddakker.jpa.domain.User; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.Date; import java.util.List; import static org.junit.Assert.assertEquals; /** * Created by ddakker on 2015-04-09. */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {AppConfig.class}) @Transactional @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class UserRepositoryTest { @Autowired private UserRepository userRepository; @PersistenceContext private EntityManager entityManager; /** * Spring JPA CRUD 기능을 이용한다면 */ @Before public void create() { User inputUser = new User(); inputUser.setId(System.currentTimeMillis()); inputUser.setName("ddakker"); inputUser.setAge(33); inputUser.setRegDt(new Date()); userRepository.save(inputUser); inputUser = new User(); inputUser.setId(System.currentTimeMillis()); inputUser.setName("petitjjang"); inputUser.setAge(33); inputUser.setRegDt(new Date()); userRepository.save(inputUser); inputUser = new User(); inputUser.setId(System.currentTimeMillis()); inputUser.setName("sisigi"); inputUser.setAge(1); inputUser.setRegDt(new Date()); userRepository.save(inputUser); assertEquals("전체 사이즈", 3, userRepository.count()); } /** * Method Name Query를 사용한다면 */ @Test public void testRepository_methodNameQuery() { ListuserList = userRepository.findByName("ddakker"); assertEquals("갯수는", 1, userList.size()); } /** * Spring Data JPA & QueryDSL Predicate 사용한다면 */ @Test public void testRepository_Predicate() { String name = "dda%"; int age = 33; QUser user = QUser.user; Page page = userRepository.findAll(user.name.like(name).and(user.age.eq(age)), new PageRequest(0,10)); assertEquals("검색 결과", 1, page.getNumberOfElements()); Iterable users = userRepository.findAll(user.age.eq(age)); for (User u : users) { System.out.println("iterable user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt()); } } /** * Spring Data JPA & QueryDSL 확장 기능을 이용한다면 * @throws Exception */ @Test public void testRepository_support() throws Exception { UserRepositorySupport userRepositorySupport = new UserRepositorySupport(User.class); userRepositorySupport.setEntityManager(entityManager); assertEquals("큰 나이", 33, userRepositorySupport.getMaxAge()); assertEquals("작은 나이", 1, userRepositorySupport.getMinAge()); } /** * 직접 QueryDSL을 써본다면.. * - 이것을 어느 영역에 둬야 할까... */ @Test public void test_Dsl() { JPAQuery query = new JPAQuery(entityManager); QUser qUser = QUser.user; List userList = query.from(qUser).where(qUser.name.eq("ddakker")).list(qUser); for (User u : userList) { System.out.println("list user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt()); } } }
[Repository UserRepositorySupport.java]
package pe.kr.ddakker.jpa.repository; import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport; import pe.kr.ddakker.jpa.domain.QUser; import pe.kr.ddakker.jpa.domain.User; /** * Created by ddakker on 2015-04-09. */ public class UserRepositorySupport extends QueryDslRepositorySupport { public UserRepositorySupport(Classuser) { super(user); } public int getMinAge() { QUser qUser = QUser.user; return from(qUser).uniqueResult(qUser.age.min()); } public int getMaxAge() { QUser qUser = QUser.user; return from(qUser).uniqueResult(qUser.age.max()); } }
[참고] https://www.youtube.com/watch?v=ho0fQt8v_HA
로그성 DB INSERT로 인해서 전체적인 DB성능 이슈가 발생하면서 어플리케이션까지 서능에 영향이 미치는 상황이 발생하였다.
처음에는 Logging 라이브러리를 활용하여 Text형태로 쌓아놓고 한가한 시간에 배치로 DB에 INSERT하려고 하다가 공부 겸 JMS(java message service)를 적용해보기로 하였다.
Message Queue 관련 방법은 여러가지가 있던데 선택은 Apache ActiveMQ 를 선택 하였다.
관련 장단점에 대해서는 인터넷 참고!!
우선 Apache ActiveMQ 설치
context-jms.xml
MemberMessageConverter.java
package pe.kr.ddakker.jms; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.Message; import javax.jms.Session; import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.jms.support.converter.MessageConverter; public class MemberMessageConverter implements MessageConverter { public MemberMessageConverter(){} @Override public Object fromMessage(Message message) throws JMSException, MessageConversionException { if(!(message instanceof MapMessage)){ throw new MessageConversionException("Messaeg isn't a MapMessage"); } MapMessage mapMessage = (MapMessage) message; Member member = new Member(); member.setName(mapMessage.getString("name")); member.setEmail(mapMessage.getString("email")); return member; } @Override public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException { if(!(object instanceof Member)){ throw new MessageConversionException("Messaeg isn't a Member"); } Member member = (Member) object; MapMessage message = session.createMapMessage(); message.setString("name", member.getName()); message.setString("email", member.getEmail()); return message; } }
ReceiverMessageListenerImpl.java
package pe.kr.ddakker.jms; import javax.annotation.Resource; import javax.jms.Message; import javax.jms.MessageListener; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.stereotype.Component; @Component public class ReceiverMessageListenerImpl implements MessageListener { @Resource private MessageConverter memberConverter; @Override public void onMessage(Message message) { Member member = null; try { member = (Member) memberConverter.fromMessage(message); if (member != null) { System.out.println("member.getEmail(): " + member.getEmail()); } } catch (Exception e) { System.err.println(e); } } }
JmsTest .java
package pe.kr.ddakker.jms; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.jms.core.JmsTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath*:config/spring/context-jms.xml"}) public class JmsTest { @Resource private JmsTemplate jmsTemplate; @Test public void test() { for (int i = 0; i < 5; i++) { Member member = new Member(); member.setName("ddakker" + i); member.setEmail("ddakker" + i + "@gmail.com"); jmsTemplate.convertAndSend(member); } } }