java.lang.ClassNotFoundException: org.apache.taglibs.standard.tlv.JstlCoreTLV
Exception 이 발생되는 이유는 standard.jar 파일과 jstl.jar 파일이 없어서 나는 것.

pom.xml 파일에 다음을 추가하면 된다.




	<!-- standard.jar --> 
	<dependency>
		<groupId>taglibs</groupId>
		<artifactId>standard</artifactId>
		<version>1.1.2</version>
	</dependency>
 
	<!-- JSTL --> 
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>jstl</artifactId>
		<version>1.1.2</version>
	</dependency>



java.lang.ClassNotFoundException: org.apache.taglibs.standard.tlv.JstlCoreTLV
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1678)
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1523)
 at org.apache.jasper.compiler.TagLibraryInfoImpl.createValidator(TagLibraryInfoImpl.java:658)
 at org.apache.jasper.compiler.TagLibraryInfoImpl.parseTLD(TagLibraryInfoImpl.java:244)
 at org.apache.jasper.compiler.TagLibraryInfoImpl.<init>(TagLibraryInfoImpl.java:169)
 at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:410)
 at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:475)
 at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1425)
 at org.apache.jasper.compiler.Parser.parse(Parser.java:138)
 at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:242)
 at org.apache.jasper.compiler.ParserController.parse(ParserController.java:102)
 at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:198)
 at org.apache.jasper.compiler.Compiler.compile(Compiler.java:373)
 at org.apache.jasper.compiler.Compiler.compile(Compiler.java:353)
 at org.apache.jasper.compiler.Compiler.compile(Compiler.java:340)
 at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:644)
 at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:358)
 at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:389)
 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:333)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:684)
 at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:471)
 at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:402)
 at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:329)
 at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:238)
 at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
 at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1047)
 at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:817)
 at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
 at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
 at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
 at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:851)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:278)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515)
 at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
 at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
 at java.lang.Thread.run(Thread.java:662)

'general java' 카테고리의 다른 글

OSCache 3가지 주의할 점  (0) 2011.09.07
Spring MVC 3 Restful 맛보기  (2) 2011.08.10
Jersey Framework 맛보기  (0) 2011.08.05
구글 프로토콜 버퍼 (Google Protocl Buffer)  (0) 2011.08.03
Hiberate 4  (0) 2011.07.15
Posted by '김용환'
,

오랜만에 쉽고 재미있는 Framework을 만나서 즐길 수 있어서 참 좋았다.
이렇게 프레임웍이 쉽고 간단하면 얼마나 좋을까, 어린 친구들도 쉽게 배울 수 있는 프레임웤이 나왔으면 좋겠다.

Jersey Frameworks는 JSR 311 표준을 바탕으로 만들어졌다. 그리고, JSON/XML/HTML mime type으로 결과를 REST를 통해 전달할 수 있다. 따라서, 무척 간단하다. OPEN API 서버로 쓰기에 적당해 보인다.

PPT 훓어보면, 5분이면 이해할 수 있을 것이다.


 





 

Posted by '김용환'
,


PPT 작성 자료








* 배경
RPC와 쉽게 연동, Not RPC
하나의 개념으로 다양한 언어에서 쓸 수 있게 구글에서 다양한 언어별로 개발 하다 보니. 통신을 위한 단일 표준이 필요

  proto 파일 -> proto 컴파일러 -> 여러 언어로 변환 (java, python, c++)

Not socket
구글 에서 2008년 7월 발표
이슈 제시
- XML 문제
- Parsing, serialization  (debugging)
- Portable : IDL처럼 사용
- Heavy Optimization
- Language 지원
짧은 데이터의 송수신 용도/긴 데이터 송수신이 목표가 아님

* XML 보다 좋은 점
Simple
3~10배 작음
20~100배 속도 빠름
모호하지 않음
바로 프로그램에 사용하기 쉬움

* 레퍼런스
Protocol Buffers: A new open source release
    http://www.youtube.com/watch?v=K-e8DDRwVUg

Home Page
   http://code.google.com/p/protobuf/

개발자 가이드
   http://code.google.com/intl/ko-KR/apis/protocolbuffers/docs/overview.html

Protocol Buffer Language Guide
    http://code.google.com/intl/ko-KR/apis/protocolbuffers/docs/proto.html

API
    http://code.google.com/intl/ko-KR/apis/protocolbuffers/docs/reference/java/index.html

PB 포맷
    http://wiki.openstreetmap.org/wiki/PBF_Format

비슷한 솔루션과 비교
    https://github.com/eishay/jvm-serializers/wiki


* 활용
구글
- 원래 index server request/response protocol로 사용했었음
- 48,162 different message types
- 12,183 .proto files
다양한 회사
국내/외 게임 회사의 통신


* 장점
쓰기 편함
Stub 코드 자동 생성
- 통신에서 가져야 할 보편적 특성을 다 추가
- Serializing, Parsing 지원
코드 일치
- 클라이언트/서버 코드 동일
IDL 형태로 정의가 단순
- Portable
- 클래스 또는 struct 디자인
언어 지원
java, c++, python
3rd party lib (많은 언어 지원. http://code.google.com/p/protobuf/wiki/ThirdPartyAddOns#RPC_Implementations)
배우기 쉬움
이클립스 플러그인 존재
Lite 버전 개발 가능
Good Document
BSD license
언어마다 특화되고 쓰기 편한 특징을 제공

* 단점
Output이 binary만 존재
- PB의 Reflection을 이용해서 json으로 전달 가능
Map, set 지원 없음


* 개발환경 구성
Download proto compiler in google code
- http://code.google.com/p/protobuf/downloads/list

(Option) eclipse plugin
- http://protoclipse.googlecode.com/svn/trunk/site/


* proto 파일


// text.txt
package test;

message Request {
  required string command = 1;
  optional string param = 2;
}


 



/cygdrive/c/protocolbuffer
$ ./protoc.exe test.txt --java_out=.



Test폴더 생기고 TestTxt.java 생성됨
(17KB, Protocol buffer 내부 클래스 사용)

// c++ 로 하면
test.txt.pb.cc (12K)
test.txt.pb.h (8K)

// python으로 하면,
test/txt_pb2.py (2K)

* 생성된 자바 코드
Descriptor 지원
- internal_static_test_Request_descriptor
Reflection 지원
- Message / Message.Builder interfaces.
- Json 처럼 프로토콜로 변경 가능 (ajax 가능)
메시지 수정시 하위 호환 보장, 새로운 메시지로 변경되면 기존 코드에 대한 필드만 처리
파일이름을 디폴트로 해서 소스를 생성하지만, 내가 원하는 클래지 이름과 클래스 이름의 개별 지정이 가능
- option java_package = "com.example.foo.bar";
- option java_outer_classname = “ProtocolData";
PB의 Enum은 java의 enum으로 변경


* 크기 제한
디폴트로 크기 제한 : 64 MB
속도를 최적화 또는 악의를 가진 사용자로부터 보호하기 위해서 크기를 제한할 수 있음
- CodedInputStream/CodedOutputStream (ZeroCopyInputStream/ZeroCopyOutputStream ) 
   SetTotalBytesLimit 메소드

* proto 파일 생성시 유의할 점
package 선언
클래스 파일 이름
운영을 위한 파일명 .proto
message 등록
- Protocol buffer language guide
Protoc.exe(컴파일러)에 의해 만들어진 java, python, c++ 코드는 고치지 말아야 한다.(immutable)
protoc에서 컴파일 되면, 자동으로 accessor가 붙는다.
- get/set/has…

* message 설명
package
Type
- Bool, int32, uint32, float, double, string, bytes,  ….
- Enum
Nested type
Default value
importing
Modifier
- required : 반드시 사용해야 할 필드. 미초기화된 상태 미초기화된 메시지를 빌드하면 RuntimeException, 초기화되지 않은 메시지를 파싱하면서 에러나면 IOException발생
- optional : option의 개념
     hasXXX() 로 확인
- repeated : 0을 포함하는 개수를 계속 넣을 수 있음
     자바에서는 List객체로 구현됨


* 필드에 번호를 반드시 주는 이유
번호를 주지 않으면 protoc 에러 발생
Write/Read 할 때, serialization 순서를 주기 위함
필드 정보가 set되었는지 쉽게 알기 위함(내부적으로 bit 연산함)

* Versioning 정보가 없는 이유
새로운 필드를 언제든지 추가 될 수 있음
모든 정보를 볼 필요 없이 필요한 정보만 파싱할 수 있도록 함
But, java는 기본으로 존재하지 않지만, c++ 은 존재한다. 링킹 이슈. (GOOGLE_PROTOBUF_VERIFY_VERSION 매크로)
Incompatible한 버전 때문에 문제가 없도록 해야 함


* 데모 1

// text.txt
package test;

message Request {
  required string command = 1;
  optional string param = 2;
}


 



generated 자바 코드 로 컴파일


<Writer.java>

package com.example.test;

import java.io.FileOutputStream;
import test.Test.Request;

public class Writer {
    public static void main(String[] args) throws Exception {
        Request request = Request.newBuilder()
                                       .setCommand("commit")
                                       .setParam("every files")
                                       .build();
        FileOutputStream output = new FileOutputStream("r.os");
        request.writeTo(output);
        output.close();
    }
}




<Reader.java>
package com.example.test;

import java.io.FileInputStream;
import test.Test.Request;

public class Reader {
    public static void main(String[] args) throws Exception {
        Request request = Request.parseFrom(new FileInputStream("r.os"));
        System.out.println("command : " + request.getCommand());
        if (request.hasParam()) {
            System.out.println("params : " + request.getParam());
        }
    }
}



 

<실행결과>
command : commit
params : every files


* 데모 2


<writer.cpp>

#include <iostream>
#include <fstream>
#include <string>
#include "test.pb.h"

using namespace std;

int main(int argc, char* argv[]) {
 Request request;
   request.set_command("init");
 request.set_param("0");

           fstream out("streams", ios::out | ios::binary | ios::trunc);
 request.SerializeToOstream(&out);
 out.close();

 return 1;
}



 



<reader.cpp>
#include <iostream>
#include <fstream>
#include <string>
#include "test.pb.h"

using namespace std;

int main(int argc, char* argv[]) {
    Request request;
    fstream in(“streams", ios::in | ios::binary); 
    if (!request.ParseFromIstream(&in)) { 
          cerr << "Failed to parse streams." << endl; 
          exit(1); 
    } 
    cout << “command: " << request.command() << endl; 
    if (request.has_param()) { 
         cout << “param: " << request.param() << endl; 
    }
}

 

* 데모 3


package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}


* 속도
용량이 작다고 가정했을 때, 상당히 빠르게 동작하는 솔루션임
추후 Thrift와 Avro와 비교 테스트 예정

Posted by '김용환'
,

Hiberate 4

general java 2011. 7. 15. 14:59
JBoss진영에서 미는 Hibernate.
조만간에 Hibernate Core 4가 정식으로 릴리즈될 예상이다. 2011년 7월 현재 beta 버전이다.
Hibernate에 좋은 기능이 생겼다.

- Multi-tenancy 
DB를 사용할 때, 서로 다른 고객들이 다른 고객의 정보를 보지 않도록 하는 것을 의미한다. 쓰지 않을 수도 있는 DB장비를 매번 구성한다면 비용이 드니, 같은 DB를 쓰지만, 마치 client쪽은 모르고 쓰게 하는 기술.
클라우드 서비스에서는 이 부분이 중요하다.
3가지 방법으로 나눌 수 있다.
1. DB 나누기
2. 스키마 나누기
3. 파티셔닝

파티셔닝 방법

CUSTOMER (
  ID BIGINT,
  NAME VARCHAR,
  ...
  TENANT_ID VARCHAR
)

데이터를 분할

CUSTOMER (
  ID BIGINT,
  NAME VARCHAR,
  ...
)

하이버네이트 코드
public class MyTenantAwareConnectionProvider implements ConnectionProvider {
    public static final String BASE_JNDI_NAME_PARAM = "MyTenantAwareConnectionProvider.baseJndiName";

    private String baseJndiName;

    public void configure(Properties props) {
        baseJndiName = props.getProperty( BASE_JNDI_NAME_PARAM );
    }

    public Connection getConnection() throws SQLException {
        final String tenantId = TenantContext.getTenantId()
        final String tenantDataSourceName = baseJndiName + '/' + tenantId;
        DataSource tenantDataSource = JndiHelper.lookupDataSource( tenantDataSourceName );
        return tenantDataSource.getConnection();
    }

    public void closeConnection(Connection conn) throws SQLException {
        conn.close();
    }

    public boolean supportsAggressiveRelease() {
        // so long as the tenant identifier remains available in TL throughout, we can
        return true;
    }

    public close() {
        // currently nothing to do here
    }
}





- 새로운 클래스로더 서비스

http://in.relation.to/Bloggers/JBossAS7AndHibernateWhatsUp
http://in.relation.to/Bloggers/MultitenancyInHibernate
Posted by '김용환'
,

자바(java)에서 특정 로직이 얼마나 소요되는지 체크를 하는 방법을 소개한다.








1. jdk에서 제공하는 System 클래스의 currentTimeMillis() 메소드를 이용해서 확인할 수 있다.

예제
long startTime = System.currentTimeMillis();
Thread.sleep(1000);
long estimatedTime = System.currentTimeMillis() - startTime;
System.out.println("took " + estimatedTime + " ms");

결과값은 밀리세컨드(ms)로 다음과 같다.
took 1000 ms


2. jdk에서 제공하는 System 클래스의 nanoTime() 메소드를 이용해서 확인할 수 있다. 아주 정확한 시간을 측정할 때 많이 활용한다.

예제
long time1 = System.nanoTime();
Thread.sleep(1000);
long time2 = System.nanoTime();
long timeSpent = time2 - time1;
System.out.println("took " + timeSpent + " ns");

결과값은 nano 값으로 나온다.
took 1000232514 ns

3. Common-lang의 StopWatch 클래스를 이용한다. (commons-lang 2.3)

예제
import org.apache.commons.lang.time.StopWatch;

StopWatch stopWatch = new StopWatch();
stopWatch.reset();
stopWatch.start();
Thread.sleep(2000);
stopWatch.stop();
System.out.println(stopWatch.toString());
  
stopWatch.reset();
stopWatch.start();
Thread.sleep(5000);
stopWatch.stop();
System.out.println(stopWatch.toString());
  
stopWatch.reset();
stopWatch.start();
Thread.sleep(3000);
stopWatch.stop();
System.out.println(stopWatch.toString());


start를 하기전에 반드시 reset()를 해야 한다. 그 이유는 Common-lang의 StopWatch 클래스는 내부적으로 status를 가지고 있다. 이 부분에 대해서 약간 신경써야 한다. 자세한 것은 뒤에서 다시 설명한다.

결과값은 다음과 같다.

0:00:02.000
0:00:05.000
0:00:03.000


4. Spring core lib에 있는 util성 StopWatch 클래스를 이용한다. (Spring 3.0.x)

예제
import org.springframework.util.StopWatch;

...
StopWatch stopWatch = new StopWatch("Stop Watch");
stopWatch.start("initializing");
Thread.sleep(2000);
stopWatch.stop();
System.out.println("took " + stopWatch.getLastTaskTimeMillis() + " ms");
stopWatch.start("processing");
Thread.sleep(5000);
stopWatch.stop();
System.out.println("took " + stopWatch.getLastTaskTimeMillis() + " ms");
  
stopWatch.start("finalizing");
Thread.sleep(3000);
stopWatch.stop();
System.out.println("took " + stopWatch.getLastTaskTimeMillis() + " ms");
  
System.out.println(stopWatch.toString());
System.out.println();
System.out.println(stopWatch.prettyPrint());

결과 화면
took 2000 ms
took 5000 ms
took 3001 ms
StopWatch 'Stop Watch': running time (millis) = 10001; [initializing] took 2000 = 20%; [processing] took 5000 = 50%; [finalizing] took 3001 = 30%
StopWatch 'Stop Watch': running time (millis) = 10001
-----------------------------------------
ms     %     Task name
-----------------------------------------
02000  020%  initializing
05000  050%  processing
03001  030%  finalizing


얼마나 소요되었는지, 그동안의 관련정보를 계속 모아서, Task 별로 모아 정보를 출력한다.

5. 그냥 인터넷에서 남들이 만든 StopWatch를 찾아서 copy&paste한다.

http://www.devdaily.com/blog/post/java/stopwatch-class-that-can-be-used-for-timings-benchmarks



* Commons-lang의 StopWatch와 Spring Util의 StopWatch를 간단히 비교하였다.

 

Commons-lang StopWatch 클래스

Spring util StopWatch 클래스

FQN

org.apache.commons.lang.time.StopWatch

org.springframework.util.StopWatch

메모리

경량화

Commons-lang Stopwatch보다는 메모리 조금 더 소요 (Task로 관리)

사용성

O

<OO (통계를 내주니까..)

기본 메소드

Start(), stop(), reset(), suspend(), resume()

Start(),stop()

이름(id)

X

O

Running status

Unstarted, running, stopped, suspended

 

상태가 있기 때문에 규칙이 있음

 

you cannot now call stop before start, resume before suspend or unsplit before split.

 

1. split(), suspend(), or stop() cannot be invoked twice

2. unsplit() may only be called if the watch has been split()

3. resume() may only be called if the watch has been suspend()

4. start() cannot be called twice without calling reset()

Running, not running

Split

기능 :시계에서처럼 split 기능

 

Status : Unsplit, split

 

Split(), Unsplit(), getSplitTime(), toSplitString()

X

Start task name

X

O

Print

O

toString()

O

toString()

shortSummary()

prettyPrint

누적시간을 기준으로 출력

Keeping track

X

O

setKeepTaskList(boolean keepTaskList)

디폴트값은 true

isRunning

X

O

isRunning()

Last time

X

O

getLastTaskTimeMillis()

Total time

X

O

getTotalTimeSeconds()

getTotalTimeMillis()

Task count

X

O

getTaskCount()






추가 - guava stopwatch 


예제

import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.google.common.base.Stopwatch;

public class GuavaTest {

@Test
public void test() {

Stopwatch 
stopwatch = Stopwatch.createStarted();

try {
Thread.sleep(1000);
catch (InterruptedException e) {
e.printStackTrace();
}

System.
out.println("Elapsed time : " + stopwatch);
System.
out.println("Elapsed time (milliseconds) : " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
System.
out.println("Elapsed time (microseconds) : " + stopwatch.elapsed(TimeUnit.MICROSECONDS));
System.
out.println("Elapsed time (nanoseconds) : " + stopwatch.elapsed(TimeUnit.NANOSECONDS));
                
                                stopwatch.stop();
}



결과

Elapsed time : 1.001 s

Elapsed time (nanoseconds) : 1004995000

Elapsed time (microseconds) : 1005043

Elapsed time (Milliseconds) : 1005


'general java' 카테고리의 다른 글

구글 프로토콜 버퍼 (Google Protocl Buffer)  (0) 2011.08.03
Hiberate 4  (0) 2011.07.15
DBCP connection 정리  (0) 2011.07.05
Spring Batch - MSSQL 채번  (0) 2011.04.07
Spring Batch retry 예제들  (0) 2011.04.06
Posted by '김용환'
,



DBCP단에서 connection을 정리하는 방법

1. testOnBorrow
    Pool에서 connection 가져올 때마다   validationQuery를 날림
   특정기간 마다 connection의 연결성을 보장하기 위해서 아무쿼리 날림

2. testWhileIdle 
   특정기간(testBetweenEvictioRunMillis)마다 특정개수(numTestsPerEvictionRun)만큼 idle한 connection 가 잘 연결되어 있는지 validataionQuery로  체크 
  
3. 자체 처리
    DBCP 내부에서 Connection에 문제가 생겼다면, pool로 반납하지 않는다.
    Connection의 isClosed()의 결과값이 true이면 pool로 반환, false이면 connection을 반환하지 않는다. (정리된다.)
   

오라클 Driver 단에서 connection을 정리
1. Connection을 Pool에 꺼내서 사용할때, 만약 끊어져 있으면 closed로 변경
(오라클 DB는 10을 쓰지만, 이 부분에 대해서 버그가 있어서 클라이언트 드라이버는 11을 사용한 부분 있음)


TODO. 나중에 DBCP 관련해서 PPT 만들어보기

Posted by '김용환'
,

Spring Batch는 mssql 채번을 하기 위한 방법으로 spring에 있는 org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer를 사용한다.

채번 방식은 다음과 같다.

insert ..
select @@identity
delete .. where  id < value


하나의 Job Repository를 쓰고, 동시에 여러 jvm에서 사용하는 경우 문제가 발생된다. 바로 delete 때문이다.
delete 를 범위로 지정하게 되면 여러 jvm에서 접근할 때, deadlock이 발생된다.

즉, A jvm에서 delete .. where id < 3
   B jvm에서 delete .. where id < 4

이렇게 하면서 3번이 db에 저장되어 있다고 하면 , deadlock이 걸리게 된다.

이런 문제를 해결 하기위해서 session을 새로 열어서 테스트해도 deadlock이 걸렸다.

따로 패치해서 테스트를 하였다.
commit
insert ..
rollback
select @@identity

이렇게 하니 deadlock은 걸리지 않았다. 그러나, 범용성에 문제가 있다. session을 재활용하면서 commit를 쓰는 것은 바람직 하지 않았다.

autocommit(false)
insert..
select max(id)_ from .. with (nolock)
delete .. where id = value
commit

transaction을 걸어서 해결해보려고 했지만, delete 하는 곳에서 또 다시 deadlock이 발생했다.

그래서 table에 index에 pk로 인덱스 생성하니 deadlock은 발생하지 않았다. (table scan이 아닌 index seek방식)

그러나....
채번은 넘어갔다 하더라도.. spring batch 내부에서 transaction이 진행되면서 상태 정보를 저장 하는 transaction에서 문제가 발생했다. (select/insert 또는 select/update)

FK로 constraint 연결되어 있기 때문에 값이 조금이나마 이상해지면 data integrity violation exception이 뜨면서 문제가 발생하게 된다.
mssql 뿐 아니라, 모든 db에서 발생할 수 밖에 없는 문제가 생긴다.

단순히 retry로 해결할 수 없는 수준이다...

결국 내가 내린 결론은 이렇다..
방법은 Spring Batch는 job 당 job repository를 하나로 가지고 있으니, 그 철학을 따라야 한다.
여러 JVM에서 동작하는 Spring Batch job들이 하나의 job repository를 가지게 하면 안된다!!


이번 기회에 Spring batch에 대해서 많이 공부하게 된 계기가 된거 같다..




Posted by '김용환'
,
Posted by '김용환'
,

CommandlineJobRunner 을 이용해서 동작하는 클래스가 여러개의 쓰레드에서 동작했을 때 문제 없는지 확인하는 TestCase이다.
ExecutorService 를 이용해서 개발된 코드이다. 동시에 몇개의 쓰레드를 동시에 시작시키고 이런 iteration을 몇개나 할 수 있는지, 예상해서 개발이 가능하다.

/*
 * Copyright 2006-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.sample.batch.example;
import static org.junit.Assert.assertNotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
 * @author Dave Syer
 *
 */
@ContextConfiguration(locations = { "/launch-context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleJobConfigurationTests {
 @Autowired
 private JobLauncher jobLauncher;
 @Autowired
 private Job job;
 private int seq = new Random().nextInt();
 @Test
 public void testSimpleProperties() throws Exception {
  assertNotNull(jobLauncher);
 }
 @Test
 public void testLaunchJob() throws Exception {
  ExecutorService service = Executors.newFixedThreadPool(5);
  for (int i = 1; i < 100; i++) {
   service.execute(new SimpleJob());
  }
  service.shutdown();
  try {
   while (!service.awaitTermination(2000, TimeUnit.MILLISECONDS)) {
   }
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
 public String getSeq() {
  return String.valueOf(seq++);
 }
 class SimpleJob implements Runnable {
  @Override
  public void run() {
   try {
    Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
    parameters.put("job_mssql", new JobParameter("job_mssql"));
    System.out.println("## seq: " + getSeq());
    parameters.put("seq", new JobParameter(getSeq()));
    JobParameters jobParameters = new JobParameters(parameters);
    jobLauncher.run(job, jobParameters);
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
}

Posted by '김용환'
,


spring batch 2.1.6을 기준으로 설명한다.


 <description>Example job to get you started. It provides a
  skeleton for a typical batch application.</description>
  <job id="job_mssql" xmlns="http://www.springframework.org/schema/batch">
  <step id="step1" parent="simpleStep">
   <tasklet>
    <chunk reader="reader" writer="writer" retry-policy="batchRetryPolicy" > 
    </chunk>
   </tasklet>
  </step>
 </job>

 <bean id="reader" class="org.springframework.sample.batch.example.ExampleItemReader"/>
 <bean id="writer" class="org.springframework.sample.batch.example.ExampleItemWriter" />
 <bean id="retryListener" class="org.springframework.sample.batch.example.RetryListenerSupport" />
 
 <bean id="simpleStep"
  class="org.springframework.batch.core.step.item.SimpleStepFactoryBean"
  abstract="true">
  <property name="transactionManager" ref="transactionManager" />
  <property name="jobRepository" ref="jobRepository" />
  <property name="startLimit" value="100" />
  <property name="commitInterval" value="1" />
 </bean>
  
 <bean id="batchRetryPolicy" class="org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy">
  <property name="policyMap"> 
   <map>
    <entry key="org.springframework.dao.DataAccessResourceFailureException">
     <bean class="org.springframework.batch.retry.policy.SimpleRetryPolicy">
      <property name="maxAttempts" value="20" /> 
     </bean>
    </entry>
    <entry key="org.springframework.dao.DeadlockLoserDataAccessException">
     <bean class="org.springframework.batch.retry.policy.SimpleRetryPolicy">
      <property name="maxAttempts" value="20" />
     </bean>
    </entry>
   </map>
  </property>
 </bean> 
     <bean id="batchRetryTemplate" class="org.springframework.batch.retry.support.RetryTemplate">
      <property name="retryPolicy" ref="batchRetryPolicy"></property>         
     </bean> 
        
    <bean id="batchRetryAdvice" class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor">
     <property name="retryOperations" ref="batchRetryTemplate"></property>
    </bean> 
             
   <aop:config>                 
     <aop:pointcut id="launching" expression="execution(* org.springframework.batch.core.repository.JobRepository.createJobExecution(..))"/>
     <aop:advisor pointcut-ref="launching" advice-ref="batchRetryAdvice" order="-1"/>
   </aop:config>

Posted by '김용환'
,