Java/JDBC2014. 11. 3. 01:06
반응형

 JDBC 3.0 사양부터 세이브포인트(저장점, 트랜잭션 내부 경계)가 지원되기 시작했습니다. 트랜잭션 내부의 세이브포인트를 지정함으로서 포인트 이후에 실행한 작업을 전부 롤백할 수 있다. 트랜잭션이라 함은 일반적으로 원자성을 일컫는 것으로 전부 혹은 아무것도 아님(All or Nothing)을 나타낸다. 이러한 원자적 특성을 세이브포인트를 지정함으로서 완화시킬 수 있다. 주의할 점은 세이브포인트는 로컬 트랜잭션에서만 지원된다는 점이다.


 먼저 JDBC 세이브포인트에 대해서 알아보기 전에 간단하게 SQL을 이용해서 세이브포인트 사용예를 보도록 하자.


SQL예(오라클)

INSERT INTO TEST_USER (NAME) VALUES ('JOHN');

SAVEPOINT MARK;
INSERT INTO TEST_USER (NAME) VALUES ('MARK');

SAVEPOINT JACK;
INSERT INTO TEST_USER (NAME) VALUES ('JACK');

SAVEPOINT PAUL;
INSERT INTO TEST_USER (NAME) VALUES ('PAUL');

ROLLBACK TO JACK;

INSERT INTO TEST_USER (NAME) VALUES ('JANE');

COMMIT;


실행 결과



 SQL문은 실행한 결과는 위와 같은데 SAVEPOINT로 특정 세이브포인트까지 롤백을 하게되면 롤백실행한 부분부터 지정한 세이브포인트까지 롤백 되는 것을 알수있다. 구조적으로 나중에 지정한 세이브포인트는 직전 세이브 포인트에 종속적이라고 보면 되겠다. 중첩된 트랜잭션이라고 보면 되겠다. 그리고 지정한 세이브포인트 이전과 롤백을 실행한 이후의 SQL에는 영향을 미치지 않았다는 것을 알 수 있다.



 다음으로 jdbc를 이용해서 세이브포인트를 설정하는 소스코드를 살펴보자.


		Connection conn = null;
		Statement stmt = null;
		try {
			conn = DriverManager.getConnection(url, id, pw);
			conn.setAutoCommit(false);

			stmt = conn.createStatement();

			stmt.executeUpdate("INSERT INTO TEST_USER (NAME) VALUES ('JOHN')");


			Savepoint savepoint = conn.setSavepoint();
			try {
				stmt.executeUpdate("INSERT INTO TEST_USER (NAME) VALUES ('MARK')");
			} catch (Exception e) {
				conn.rollback(savepoint);
			}

			conn.commit();
		} catch (Exception e) {
			try {
				conn.rollback();
			} catch (SQLException e1) {
			}
		} finally {
			try {
				stmt.close();
			} catch (SQLException e) {
			}
			try {
				conn.close();
			} catch (SQLException e) {
			}
		}



  일반적인 JDBC를 이용한 소스와 크게 다르지 않다. 소스코드 12라인에 있는 커넥션 인스턴스의 setSavepoint 메소드 호출한 부분을 보면 될 듯 하다. 롤백할 경우는 기존의 rollback메소드 호출시 세이브포인트 인스턴스를 전달해 주면 된다. JDBC스펙에 정의되기 이전부터 각 DBMS별로 세이브포인트 기능이 제공되는 경우도 있었으므로 이경우 표준 JDBC 인터페이스 이외에 구현을 직접 호출하여 실행하는 것도 가능하다. 오라클의 경우를 예를 들어보면 OracleConnection.oracleSetSavepoint을 이용하여 세이브 포인트를 설정할 수 있다.



 세이브포인트에 관련된 JDBC인터페이스를 전체적으로 살펴보도록 하자.



public Savepoint setSavepoint() throws SQLException;

public Savepoint setSavepoint(String name) throws SQLException;


 세이브포인트를 설정하는 인터페이스는 세이브포인트명을 명시적으로 지정하느냐의 차이에 따라 두가지로 나뉘는데 명시적으로 지정하지 않았을경우 내부적으로는 정수 ID가 할당되는 듯 하다.




public int getSavepointId() throws SQLException;

public String getSavepointName() throws SQLException;


 세이브포인트ID혹은 세이브포인트 명을 취득하는 인터페이스이다. 명시적으로 세이브포인트명을 지정하였을 경우 ID취득 인터페이스를 호출하면 SQLException이 발생한다. 역으로 지정하지 않았을 경우 세이브포인트명 취득 인터페이스 호출이 예외를 발생시킨다.




public void rollback(Savepoint savepoint) throws SQLException;


 롤백인터페이스는 현재 트랜잭션에서 지정한 세이브포인트까지 롤백한다. 일단 실행한 후에 세이브포인트를 참조할려고 하면 SQLException이 발생한다.




public void releaseSavepoint(Savepoint savepoint) throws SQLException;


 지정한 세이브포인트를 제거한다. 일단 제거한 세이브포인트를 참조하려하면 예외가 발생한다.



 위의 인터페이스들은 공통적으로 DB에러 발생후나 자동커밋모드, 글로벌 트랜잭션과 관계있는 경우에도 예외가 발생하므로 사용시에 주의하도록 하자.



 세이브포인트를 이용하여 트랜잭션 내부에서 실행은 해야하지만 실행 경과가 전체 트랜잭션에 영향을 미치지 않아야 할 경우 등에 사용할 수 있다. 예를 들어 쇼핑몰의 주문 프로세스상에서 주문결과에 영향을 미치지 않는 내부 프로세스에 의한 에러시에도 전체 트랜잭션이 실패로 처리된다면 결과적으로 기회비용의 손실이 발생하게 될수 있다. 이 경우 세이브포인트등을 이용 트랜잭션을 분할하여  별도의 리커버리 프로세스를 진행하면 손실을 줄일수 있을 것이다. 트랜잭션의 경계를 설정하는 일은 개발을 진행함에 있어서 중요한 부분이므로 항상 명확히 하여 구현하는 것이 좋을 듯 합니다.




Posted by Reiphiel
Java2014. 10. 26. 16:28
반응형

 최근 서버/클라이언트 어플리케이션 개발을 진행하다 보면 스마트폰, 태블릿 등 다양한 클라이언트가 생김으로서 이기종간의 호환성 유지를 위해 클라이언트에서 서버로의 요청을 json을 이용하여 개발하는 경우가 있다. 이 경우 서버 시스템 테스트를 위해서 클라이언트측 요청을 별도로 구현해야 하는 경우가 있다. 이때 apache재단의 오픈소스를 이용하여 간단하게 구현할 수 있다. 


 먼저 필요한 라이브러리를 추가할 필요가 있다.(현 시점의 안정화 최신버전 리스트)

  • Apache HttpClient (httpclient-4.3.5.jar)
  • HttpComponents Core (httpcore-4.3.2.jar)
  • Apache HttpClient Mime (httpmime-4.3.5.jar)


위의 라이브러리를 추가할 필요가 있으며 의존관계로 commons-logging, commons-codec 라이브러리도 필요하다. MAVEN을 이용하여 간단하게 의존라이브러리를 추가하는게 편할 듯하다.


httpclient 추가

    
        org.apache.httpcomponents
        httpclient
        4.3.5
    

httpmime 추가

    
        org.apache.httpcomponents
        httpmime
        4.3.5
    

위의 두개의 의존 라이브러리를 추가하면 나머지는 의존 관계에 따라서 자동으로 추가된다.


먼저 application/json 타입의 요청을 하기 위한 간단한 소스코드는 아래와 같다. 요청을 생성하고 JSON문자열을 문자열 엔티티로 추가한후 컨텐츠 타입 헤더를 application/json으로 지정했다.

		try {
			RequestConfig.Builder requestBuilder = RequestConfig.custom();
			HttpClientBuilder builder = HttpClientBuilder.create();
			builder.setDefaultRequestConfig(requestBuilder.build());
			HttpClient client = builder.build();

			String requestJson = "{\"test\":\"test\"}";
			StringEntity stringEntity = new StringEntity(requestJson);

			HttpPost httpost = new HttpPost(new URI("요청할URL"));
			httpost.addHeader("Content-Type", "application/json");

			httpost.setEntity(stringEntity);

			client.execute(httpost);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

  다음으로 json데이터와 함께 파일도 업로드 하여야 할 경우 multipart/mixed 타입으로 컨텐츠 타입을 지정해서 요청하면 된다. 아래 소스코드를 살펴보자.

	
		try {
			RequestConfig.Builder requestBuilder = RequestConfig.custom();
			HttpClientBuilder builder = HttpClientBuilder.create();
			builder.setDefaultRequestConfig(requestBuilder.build());
			HttpClient client = builder.build();

			String requestJson = "요청할 json데이터 문자열";

			HttpPost httpost = new HttpPost(new URI("요청할 URI"));
			httpost.addHeader("Content-Type", "multipart/mixed; boundary=바운더리 문자열");
			MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();

			entityBuilder.addPart("jsonpart", new StringBody(requestJson, ContentType.APPLICATION_JSON));
			entityBuilder.setBoundary("위의 헤더에 지정한 바운더리 문자열");
			entityBuilder.addPart("file1", new FileBody(new File("파일경로"), ContentType.create("파일타입"), "파일명"));
			httpost.setEntity(entityBuilder.build());
			HttpResponse response = client.execute(httpost);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}


 Java계열의 서블릿 컨테이너를 이용중이라면 multipart요청은 서블릿 스펙3.0(톰캣의 경우 7이상)을 지원하는 컨테이너에서 처리 가능하다. 위의 소스코드상에서 바운더리 문자열을 지정했는데 Content-Type헤더를 위처럼 별도 지정하지 않았을 경우 multipart/form-data로 지정되면 바운더리도 자동으로 지정되므로 크게 신경스지 않아도 될듯하다. 톰캣이나 스프링의 멀티파트 리졸버 상에는 multipart/로 시작되는 Content-Type헤더를 가진 요청은 동일하게 처리하는듯하니 상황에 따라 지정해서 구현하면 될듯하고 별도로 지정하는 경우는 org.apache.http.entity.mime.MultipartEntityBuilder#generateBoundary의 구현을 참조하여 바운더리를 지정하면 되겠다.



※바운더리란 간단히 요약하면 여러 조각(Part)의 요청을 보낼경우 경계를 구분하기 위한 문자열로 '--바운더리 문자열'로 각 조각을 경계지으면 최종적으로 '--바운더리 문자열--'로 최종경계가 표현된다.


'Java' 카테고리의 다른 글

apache httpclient 요청 디버깅  (0) 2014.11.27
Java8 Base64 Encoder/Decoder  (0) 2014.11.19
Apache Commons Codec Base64 Encoding/Decoding  (0) 2014.11.17
Eclipse java Access restriction rt.jar  (0) 2013.12.04
Java7 zip 파일 압축  (0) 2013.11.26
Posted by Reiphiel
Java2013. 12. 4. 14:56
반응형

 이클립스에서 자바 개발을 수행하다 보면 아래와 같은 경고 메시지를 접할때가 있다.

Access restriction: The constructor BASE64Decoder() is not accessible due to restriction on required library C:\Program Files\Java\jdk1.6.0_45\jre\lib\rt.jar


※실제 경고 메시지 스크린 샷


 위의 경고를 배제하는 방법은 크게 두가지로 나뉘는데 이클립스의 경고레벨을 낮춰서 에러를 감추는 방법으로 문제를 일시적으로 감추는 방법과 실제적으로 경고를 해결하는 방법이다.


- 이클립스의 경고레벨을 낮추는 방법


 빨갛게 표시된 부분의 경고 레벨을 Error에서 Warning이나 Ignore로 낮추면 일단 빨간 경고 메시지는 사라지게 된다. Warning으로 변경했을 경우엔 대신 노란 Warning을 보게된다.


 위의 경고의 의미는 특정 JVM벤더에 종속적인 라이브러리(즉 각 벤더별JVM내부에서만 사용하도록 작성된 라이브러리)를 참조하고 있다는 것을 경고하는 것이다. 따라서 위와같은 일시적인 해결방법으로는 Write Once, run anywhere라는 자바 모토와는 다르게 특정 JVM벤더에 종속되는 결과를 가져오게 되어 다양한 환경에 적용할 수 없게 되버리는 결과를 가져오게 된다. 


 예로든 메시지 상의 문제를 야기한 클래스인 BASE64Decoder의 경우 패키지가 sun.misc 즉 선마이크로시스템의 패키지이다. 따라서 다른 JVM의 런타임 라이브러리엔 존재하지 않을 가능성이 매우 높아 보인다. BASE64인코더/디코더의 경우 apache-commons-codec라이브러리에 다른 구현체가 존재하므로 참조를 변경해서 개발을 진행하면 깔끔하게 문제를 해결할 수 있다. 물론 다른 클래스 이용의 경우엔 그에 맞는 구현체를 찾아야 하고 최악의 경우엔 운용환경의 JVM벤더를 제한하는 방법을 택할 수밖에 없는 경우도 있으므로 상황에 맞추어 잘 대처하도록 하면 되겠습니다.




Posted by Reiphiel