'java'에 해당되는 글 28건

  1. 2011.08.29 mybatis interceptor 를 이용해서 쿼리로그를 디비에다 저장하기 2
  2. 2011.08.23 jQuery 와 Servlet을 이용한 간단한 Captcha 샘플~ 5
  3. 2011.08.23 Apache HttpComponents를 이용한 GET, POST 요청하기 7
  4. 2011.08.22 Oracle JDBC Driver maven 디펜던시에 추가하기 3
  5. 2011.08.22 Oracle JDBC : "스트림이 이미 종료되었습니다" 혹은 "java.sql.SQLException: Stream has already been closed" 익셉션

mybatis interceptor 를 이용해서 쿼리로그를 디비에다 저장하기

시스템에서 실행되는 update, insert 등의 쿼리에 대해서 실행된 쿼리를 로그쌓기 전용 데이터베이스에 넣어 주세요~ 라는 요구사항을 받았다.

 뭐 Log4j 같은 것으로 DB로 로깅하는걸 문득 떠올려 보긴 했지만 고건 쫌 안될것 같고..

생각하다 보니 문득 mybatis 설정파일 설명에서 어렴풋이 보았던 plugin 태그가 생각났다. 고걸 이용하는 방법이다. 

plugin 이란게 보니까 spring 의 interceptor 랑 비스무리한것 같다. 여러가지 경우가 미리 정의되 있고 특정 경우를 처리할 Interceptor 를 구현하면 되는것 같다.

이것저것 설명하는 것 보다 일단 실제 설정파일의 설정부분과 소스부분을 보자.

mybatis 설정 xml 중 plugin 설정 부분
<plugins>
	<plugin interceptor="com.tistory.stove99.UpdateInterceptor"/>
</plugins>


인터셉터(update 성 쿼리가 실행될때 인터셉트!)
package com.tistory.stove99;

import java.sql.Statement;
import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

@Intercepts({@Signature(type=StatementHandler.class, method="update", args={Statement.class})})
public class UpdateInterceptor implements Interceptor{
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler handler = (StatementHandler)invocation.getTarget();
		
		// 쿼리
		String sql = handler.getBoundSql().getSql();
		
		// 쿼리실행시 맵핑되는 파라메터들
		String param = handler.getParameterHandler().getParameterObject()!=null ? 
                             handler.getParameterHandler().getParameterObject().toString() : "";
		
		// DB에다 로그 insert
		/////////////////
		////////////////
		
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
	}
}

org.apache.ibatis.plugin.Interceptor 인터페이스를 implements 해서 intercept, plugin, setProperties 메소드를 구현해 주면 된다.
그리고 상단에 @Signature 어노테이션은 아마도 인터셉트할 시점에 대한 설정인것 같다. 


@Signature 설정시 type으로 설정할수 있는것은

Executor.class, ParameterHandler.class, ResultSetHandler.class, StatementHandler.class 요렇게

4가지가 있고 어떤것인가는 각자의 필에 따라~~ 대충 이해하면 된다.




method 에 설정할 수 있는것은 예제소스에서 type으로 설정한 StatementHandler.class 의 소스를 보면
public interface StatementHandler {

  Statement prepare(Connection connection)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  List query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

요런 인터페스이다. 짐작할 수 있겠지만 @Signature 어노테이션에서 method와 args 에 설정한 것은 요 인터페이스에 있는 메소드중 하나에 대해서
정의를 한것이다.


대충 눈치로 이해를 해 보자면

@Signature(type=StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})

요래 설정을 하면 실행되는 쿼리들중 select 쿼리가 실행됬을때 고것을 인터셉트 할것이다.




다른 type 들에 어떤 메소드들이 있는지는 소스를 보면 된다 -_-

mybatis API 문서를 봐도 될것 같은데 나의 검색범위 내에서는 API 문서를 못 찾겠다.
분명 어딘가에 있을건데 이상하게 잘 검색이 안되네 -_-;;

공식 문서는 아닌것 같고 아쉬운데로 요기라도~~



※ 첨부파일로 첨부된 User Guide 17 페이지를 보면 자세한 설명은 없고 쥐똥만큼은 설명이 되 있다~ 참고할 사람은 참고하길~~~

MyBatis-3-User-Guide.pdf

 

jQuery 와 Servlet을 이용한 간단한 Captcha 샘플~

Captcha 란 간단히 아래쪽 그림과 같은 기능을 하는것을 말한다.


 

제작해본 샘플은 

서버측에서 이미지를 생성해 주고, 생성된 문자열을 세션에 저장시켜주는 기능을 수행하는 Servlet 하나와

고 서블릿을 사용하는 클라이언트 쪽 html 파일로 구성된다. 

사용한 라이브러리는 랜덤 스트링 생성을 편하게 하기 위해 apache commons lang 의 RandomStringUtils 를 사용했다.


CaptchaGenServlet.java
package servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.RandomStringUtils;

public class CaptchaGenServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	public void init() throws ServletException {
		super.init();
		
		// 폰트 등록
		try {
			Font font = Font.createFont(Font.TRUETYPE_FONT,
                                    this.getClass().getResourceAsStream("/servlet/HelveticaMedCd.ttf"));
			GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
    public CaptchaGenServlet() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                      throws ServletException, IOException {
		generateCaptcha(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                       throws ServletException, IOException {
		generateCaptcha(request, response);
	}
	
	private void generateCaptcha(HttpServletRequest request, HttpServletResponse response){
		try {
			// 알파벳 숫자섞인 5자리 문자열 생성
			String randomString = RandomStringUtils.randomAlphanumeric(5).toUpperCase();
			
			// 세션에 저장
			request.getSession().setAttribute("CAPTCHA", randomString);
			
			Font font = new Font("Helvetica 67 Medium Condensed", Font.PLAIN, 50);
			FontRenderContext frc = new FontRenderContext(null, true, true);
			Rectangle2D bounds = font.getStringBounds(randomString, frc);
			int w = (int) bounds.getWidth();
			int h = (int) bounds.getHeight();
			
			// 이미지 생성
			BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			Graphics2D g = image.createGraphics();
			g.setColor(Color.WHITE);
			g.fillRect(0, 0, w, h);
			g.setColor(new Color(113, 193, 217));
			g.setFont(font);
			g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                               RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, 
                               RenderingHints.VALUE_FRACTIONALMETRICS_ON);
			g.drawString(randomString, (float) bounds.getX(), (float) -bounds.getY());
			g.dispose();

			ImageIO.write(image, "png",  response.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


이미지 생성시 문자열에 쓸 폰트를 클래스패스에 위치시킨 다음에 30번째 라인처럼 불러와서 폰트를 등록시킬 수 있다.
폰트 등록후 57번째 라인처럼 등록된 폰트를 불러와 쓸 수 있는데 시스템에 등록된 폰트들 이름을 확인하기 위해서는 다음과 같은 코드로 확인하면 된다.
Font[] fontList = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
			
for( Font f : fontList){
	System.out.println(f.getName());
}



web.xml 서블릿 설정

<servlet>
	<display-name>CaptchaGenServlet</display-name>
	<servlet-name>CaptchaGenServlet</servlet-name>
	<servlet-class>servlet.CaptchaGenServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>CaptchaGenServlet</servlet-name>
	<url-pattern>/captcha</url-pattern>
</servlet-mapping>



Captcha UI
<html>
<head>
	<title>Captcha Test</title>
	
	<script type="text/javascript" src="/jquery-1.6.1.min.js"></script>
	
	<script>
		$(document).ready(function(){
			create();
			
			$("#refreshBtn").click(function(e){
				e.preventDefault();
				create();
			});
			
			$("#confirmBtn").click(function(e){
				e.preventDefault();
				$("#frm").submit();
			});
			
			function create(){
				$("#captcha img").attr("src", "/captcha?"+Math.random());
			}
		});
	</script>
	
	<style>
		#captcha {
			width : 230px;
			height : 70px;
			border: 3px dotted #A3C552;
			text-align: center;
			padding: 5px;
		}
	</style>
</head>

<body>
	Catpcha Test!!!
	<form id="frm" action="result.jsp" method="post">
		<div id="captcha"><img/></div>
		<br/>
		<input type="text" name="captchaInput"/>
		<button id="confirmBtn">확인</button>
		<button id="refreshBtn">리푸레쉬</button>
	</form>
</body>
</html>



쪽바로 입력했는지 체크하기(result.jsp)
SESSION : ${sessionScope['CAPTCHA']}
PARAM : ${param.captchaInput}

통과 : ${ sessionScope['CAPTCHA'] eq param.captchaInput ? 'O' : 'X'}

CaptchaSample.war

Apache HttpComponents를 이용한 GET, POST 요청하기

예전에는 Jakarta Commons HttpClient 프로젝트로 commons 프로젝트들이랑 옹기종기 같이 모여 있었는데 문득 사이트에 가보니 

프로젝트명이 Apache HttpComponents 로 바껴있고  라이브러리 사용하는 것도 훅 달라진것 같았다.

테스트겸 겸사겸사 GET, POST 요청을 하는 클래스를 맹글어 보았다.



소스코드
package proxy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public class HttpProxy {
	/**
	 * POST 요청
	 * @param url       요청할 url
	 * @param params    파라메터
	 * @param encoding  파라메터 Encoding
	 * @return 서버 응답결과 문자열
	 */
	public String post(String url, Map params, String encoding){
		HttpClient client = new DefaultHttpClient();
		
		try{
			HttpPost post = new HttpPost(url);
			System.out.println("POST : " + post.getURI());
			
			List<NameValuePair> paramList = convertParam(params);
			post.setEntity(new UrlEncodedFormEntity(paramList, encoding));
			
			ResponseHandler<String> rh = new BasicResponseHandler();

			return client.execute(post, rh);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			client.getConnectionManager().shutdown();
		}
		
		return "error";
	}
	
	public String post(String url, Map params){
		return post(url, params, "UTF-8");
	}

	
	
	
	/**
	 * GET 요청
	 * POST 와 동일
	 */
	public String get(String url, Map params, String encoding){
		HttpClient client = new DefaultHttpClient();

		try{
			List<NameValuePair> paramList = convertParam(params);
			HttpGet get = new HttpGet(url+"?"+URLEncodedUtils.format(paramList, encoding));
			System.out.println("GET : " + get.getURI());
			
			ResponseHandler<String> rh = new BasicResponseHandler();
			
			return client.execute(get, rh);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			client.getConnectionManager().shutdown();
		}
		
		return "error";
	}
	
	public String get(String url, Map params){
		return get(url, params, "UTF-8");
	}
	
	
	
	private List<NameValuePair> convertParam(Map params){
		List<NameValuePair> paramList = new ArrayList<NameValuePair>();
		Iterator<String> keys = params.keySet().iterator();
		while(keys.hasNext()){
			String key = keys.next();
			paramList.add(new BasicNameValuePair(key, params.get(key).toString()));
		}
		
		return paramList;
	}
	
	

	// Test
	public static void main(String[] args) {
		HttpProxy p = new HttpProxy();
		
		Map params = new HashMap();
		params.put("w", "tot");
		params.put("q", "한예슬");
		
		System.out.println(p.get("http://m.search.daum.net/search", params));
	}
}

HttpProxy.java






라이브러리 다운로드는  http://hc.apache.org/downloads.cgi 요기에 가서 다운로드 받던지

Maven을 사용하고 있다면 디펜던시에
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.1.2</version>
</dependency>
 요걸 추가해 주면 된다.




그리고~ http://hc.apache.org/downloads.cgi 요기에서 다운받아 보면 압축파일안에  examples, tutorial, api 문서도 같이 들어 있어서 참고하기 좋다~

※ 위 예제를 실행하기 위해서 꼭 필요한 jar 파일은 httpclient-4.1.2.jar, httpcore-4.1.2.jar, commons-codec-1.4.jar, commons-logging-1.1.1.jar
요것들 4개고 모두 lib 디렉토리 안에 들어 있음.

Oracle JDBC Driver maven 디펜던시에 추가하기

Oracle JDBC 드라이버는 메이븐 센트럴 레파지토리에 없다. 그래서 따로 레파지토리를 추가해 줘야 하는데

검색을 해보니 예전에는 오라클에서 제공하는 메이븐 레파지토리(http://download.oracle.com/maven)가 있었는데

요즘 오라클 사이트가 전체적으로 삐꾸가 되서 이제 잘 되지 않는것 같다.



다음은 JDBC 드라이버가 오라클 사이트에 올라오는 최신 버전은 아니지만  그래도 거의 준 최신 버전을 받을수 있는 레파지토리이다.

http://maven.jahia.org/maven2

pom.xml 파일  repositories 에 요걸 추가하고
<repository>
	<id>oracle</id>
	<name>ORACLE JDBC Repository</name>
	<url>http://maven.jahia.org/maven2</url>
</repository>


dependencies 에 요걸 추가해 주면 된다.
<dependency>
	<groupId>com.oracle</groupId>
	<artifactId>ojdbc6</artifactId>
	<version>11.1.0.7.0</version>
</dependency>
ojdbc5 가 필요한 사람은 ojdbc6 대신에 ojdbc5 로 바꿔서 적어주면 됨.



나는 꼭 최신버전 드라이버를 maven으로 가져와서 써야 겠다는 사람들은 자기 로컬 레파지토리를 만들면 된다 -_-;

Oracle JDBC : "스트림이 이미 종료되었습니다" 혹은 "java.sql.SQLException: Stream has already been closed" 익셉션

Oracle JDBC 로 CLOB 나 LONG 같은, 값을 Stream 형태로 읽어야 하는 타입들을 Select 할때 발생하는 Exception 이다.

나같은 경우는 mybatis로 select 할때 쿼리문과 쿼리결과를 Log4j 로그로 찍었는데 시쭈구리하게도  logger 에서 ResultSet을 로그로 찍었기 때문에 나는 에러였다.

찍혀 있는 로그를 가만히 살펴보면 쿼리문이랑 ResultSet 결과가 먼저 쭉 찍힌뒤 그 뒤로 익셉션이 쭉 발생하고 있었다.
고말인 즉슨, 로거에서 찍을때는 정상적으로 값을 가져왔다는 말이된다.



가만 입다물고 생각해보면, 로거에서 ResultSet을 찍을려고 값을 가져오면서 Stream이 닫혀 버렸기 때문에 나는 에러 같았다.

기존 log4j.xml 설정은
<logger name="java.sql">
	<level value="debug" />
</logger>
 요렇게 되 있어서 java.sql 패키지에 포함된 모든클래스에서 발생하는 로그들을 다 찍게 되 있었다.




고것을
<logger name="java.sql.Connection">
	<level value="debug" />
</logger>

<logger name="java.sql.PreparedStatement">
	<level value="debug" />
</logger>
요래 바꿔서 ResultSet 에 대한 로그를 안찍게 바꾸니 해결되었다. 로그가 아예 필요없다면 다 지워도 되고~
나는 어떤 쿼리가 실행됬는가 까지는 꼭 알고 싶어서..



이것도 왠지 버그인것 같은 느낌이 문득 들지만 언젠가는 해결될것이라고 믿는다. 아니면 내가 잘못 쓰고 있는건가???? 

테스트 라이브러리 버전
mybatis :  3.0.6-SNAPSHOT.jar
oracle jdbc driver : ojdbc6.jar (11.2.0.2.0) 
prev 1 2 3 4 5 6 next