반응형

Javascript 기술은 점점 발전하여 단순 JS만으로도 많은 기능을 사용할 수 있는데, 이번엔 화면을 캡처하는 방법을 알아보겠습니다.

 

요청사항 중 특정 요소를 캡처하여 이미지 데이터를 서버에 전송하고 저장을 원하였습니다.

화면 캡처 후 다운로드 기능은 사실 이미 많이 공유되고 있는데, 제가 겪었던 문제점에 대한 대응책이나 이런부분이 잘 보이질 않아 추가적으로 적어볼까합니다.

 

일단 html2canvas를 사용하여 동작시킬 예정이므로 해당 라이브러리를 추가합니다.

 

 

html2canvas를 통한 캡처 후 전송하기

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

아래는 공식사이트입니다.

https://html2canvas.hertzen.com/

 

html2canvas - Screenshots with JavaScript

Try out html2canvas Test out html2canvas by rendering the viewport from the current page. Capture

html2canvas.hertzen.com

 

이제 전송을 위해 ajax를 활용하여 전송 예제를 만들어보겠습니다.

-HTML

<section id="chart_box">
    <div class="chart">
     ...//차트에 해당하는 요소들
    </div>
</section>

 

 

-JS

$("#btn").on("click", function() {
	sreenShot($("#chart_box"));
});

function sreenShot(target) {
	if (target != null && target.length > 0) {
		var t = target[0];
		html2canvas(t).then(function(canvas) {
			var myImg = canvas.toDataURL("image/png");
			myImg = myImg.replace("data:image/png;base64,", "");

			$.ajax({
				type : "POST",
				data : {
					"imgSrc" : myImg
				},
				dataType : "text",
				url : contextPath + "/public/ImgSaveTest.do",
				success : function(data) {
					console.log(data);
				},
				error : function(a, b, c) {
					alert("error");
				}
			});
		});
	}
}

넣고 싶은 요소 $("#chart_box")를 매개변수로 집어넣고 0번째 데이터를 html2canvas 첫번째 파라미터에 처리합니다.

html2canvas는 Promise를 사용하는것으로 보이는데, 요소 처리가 완료되면 다음 이행동작을 정의합니다.

 

매개변수로 나온 canvas의 toDataURL을 통해 이미지 바이너리 데이터를 받을 수 있습니다.

거기서 앞에 붙는 데이터타입과 인코딩타입을 제거하고 서버로 전송하면 js부분은 끝입니다.

전송은 ajax를 통해 처리하였습니다.

 

 

-Server(java)

@ResponseBody
@RequestMapping(value = { "ImgSaveTest" }, method = RequestMethod.POST)
public ModelMap ImgSaveTest(@RequestParam HashMap<Object, Object> param, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
	ModelMap map = new ModelMap();
	
	String binaryData = request.getParameter("imgSrc");
	FileOutputStream stream = null;
	try{
		System.out.println("binary file   "  + binaryData);
		if(binaryData == null || binaryData.trim().equals("")) {
		    throw new Exception();
		}
		binaryData = binaryData.replaceAll("data:image/png;base64,", "");
		byte[] file = Base64.decodeBase64(binaryData);
		String fileName=  UUID.randomUUID().toString();
		
		stream = new FileOutputStream("E:/test2/"+fileName+".png");
		stream.write(file);
		stream.close();
		System.out.println("캡처 저장");
	    
	}catch(Exception e){
		e.printStackTrace();
		System.out.println("에러 발생");
	}finally{
		if(stream != null) {
			stream.close();
		}
	}
	
	map.addAttribute("resultMap", "");
	return map;
}

파라미터로 넘긴 데이터를 문자열로 전달받고 바이너리 데이터를 스트림을 통해 읽어서 파일쓰기를 통해 파일을 남깁니다.

 

 

사용예시

html Page

전송버튼을 누르고 지정한 test2 디렉토리에 저장되는지 확인해보겠습니다.

 

 

정상적으로 파일이 써진 모습을 볼 수 있습니다.

 

 

개발시 주의사항

*아래 기재한 주의사항은 개인적으로 적용과 테스트를 해보면서 겪은 문제들입니다.

 

1. html2canvas에 넣는 첫번째 요소가 jQuery 선택자인 경우 id값일지라도 배열의 인덱스가 필요합니다.

ex) $("#test")[0]

 

2. 다운받으려는 특정요소는 width, height등 요소의 영역이 정확하게 지정되어 있어야합니다.

지정되지 않은 요소를 받게되면 브라우저상에서는 잘 보일지라도 전송된 데이터는 비어있거나 잘렸거나 등등 여러가지 문제가 발생할 수 있습니다.

 

3. 다운받으려는 특정요소의 위치가 position등을 통해 억지로 맞춰진 경우 정상출력이 되지 않을 수 있습니다.

 

4. div태그 내부의 img태그등 정보를 받아오는 데이터가 외부인 경우 정상적으로 출력되지 않습니다.

다른 예시 블로그 글들을 보면 http~를 통해 이미지를 가져오고 다운로드 받는다고 되어있는데, 막상 따라해보면 흰색 화면만 받아졌습니다. 서버 내부에 존재하는 이미지를 넣고 다운로드시 정상 출력되었습니다.

 

5. 인터셉터 관련 서버 코드 문제

저같은 경우는 XSS악성 스크립트 방지를 위해 서버코드로 넘어가는 모든 파라미터를 검사하는 로직을 추가해놨습니다.

html2canvas로 읽는 데이터가 얼마 안되는 경우에는 큰 문제가 없지만 복잡한 이미지를 만들게되면 그만큼 바이너리 데이터 또한 매우 커집니다. 캡처의 바이너리만 해도 문자열 길이가 약 7만5천개정도가 되었습니다. 여기서 로직을 체크하는 함수와 문자열이 겹치면서 서버로 전송이 안되는 이슈가 있어서, 해당 파라미터만 검사를 안하거나 url에 따라 예외처리등 약간의 극복(?)이 필요했습니다.

 

6. 바이너리 데이터를 넘기다보니 더욱 복잡한 이미지를 이미지화하여 전송하게되면 데이터는 더욱 커지게 되고 tomcat의 maxPortSize를 강제로 키우거나 -1처리를 통해 무제한으로 변경해야 할 수도 있습니다.

 

7. 높이가 body보다 큰 영역을 처리할 때 잘릴 수가 있습니다. 처리하는동안 body태그의 값을 제어하여 영역과 동일하게 또는 그 보다 크게 처리하였다가 원래 사이즈로 처리하는 로직이 필요합니다. - 20201126 추가

반응형
반응형

new Date객체의 getDate()메소드를 통해 마지막 일자를 구할 수 있다.

var date = new Date(년, 월, 0);
date.getDate(); //마지막 날짜


//사용예
new Date(2020, 1, 0).getDate(); //31
new Date(2020, 2, 0).getDate(); //29
new Date(2020, 3, 0).getDate(); //31
new Date(2020, 4, 0).getDate(); //30
new Date(2020, 5, 0).getDate(); //31
new Date(2020, 6, 0).getDate(); //30

 

반응형
반응형

기존 5버전대 mysql을 쓰다가 해당 구조만 가져올 일이 있어서 그대로 백업한 .sql파일을 복원 하는 과정에서 아래와같은 에러가 발생하였다.

 

 

ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

 

 

 

함수 생성시 옵션 에러라고 하는데 버전이 올라가면서 기본값이 변경된 것 같다.

발생하는 에러로그를 토대로 검색해보니 함수 생성시 생성 제약처리를 할 수 있었고, 생성할 수 있는 부분이 OFF처리 되어있는걸 볼 수 있었다.

 

log_bin_trust_function_creators 옵션 확인하기

show global variables like 'log_bin_trust_function_creators';

 

해결방법
설치한 Mysql은 8버전대였고 아래의 쿼리를 실행 후 동일한 .sql파일 실행시 정상적으로 동작하였다.

SET GLOBAL log_bin_trust_function_creators = 1; //ON
#SET GLOBAL log_bin_trust_function_creators = 0; //OFF

1은 ON처리로 함수 생성을 시킬수 있다.

 

0은 OFF처리로 정상적으로 처리 후 다시 원복시 사용하거나 다른 사용자가 함수를 추가하지 못하도록 막을 수 있을것이다.

반응형
반응형

자바로 소켓 프로그램 연동 프로젝트를 구성하고 테스트를 하면서 이상한 버그를 발견하였다.

 

소켓 서버에서 클라이언트 연결을 하고 전송되는 데이터를 기다리는데 연결 클라이언트 정보 이력은 확인이 되지만, 클라이언트에서 전송한 문자열 데이터를 받지 못하고 무한 대기하는 현상이였다.

 

기존 소스

public void run(){
	BufferedReader br = null;
	PrintWriter pw = null;
	
	try{
		String connIp = socket.getInetAddress().getHostAddress();
		logger.info(StringUtils.logStr(connIp + "에서 연결 시도."));
		int BUF_SIZE = 1024*7;
		br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf8"), BUF_SIZE);
		pw = new PrintWriter(socket.getOutputStream());
		
		String receiveStr = ""; 
		String tmp = "";
		while((tmp = br.readLine()) != null) {
			receiveStr += tmp;
		}
		logger.info(StringUtils.logStr("receive msg : " + receiveStr));

위 와 같은 형태로 BufferedReader를 통해 readLine형태로 메시지를 전달받고 null일때까지 데이터를 임시 변수에 받아서 저장하는 형태였다.

 

하지만 전송하는 클라이언트에서 문자열 끝에 개행문자 "\n" 를 처리해주거나 BufferedWriter를 사용해 newLine() 처리를 해주지 않으면 영원히 머물러 있는 무한 대기 현상을 겪었고, 클라이언트측에서는 개행을 보장해주지 않는 상황이라 받는 부분을 개선해야 했다.

 

BufferedReader의  read()를 사용하여 개선하였다.

 

개선 소스

public void run(){
	BufferedReader br = null;
	PrintWriter pw = null;
	
	try{
		String connIp = socket.getInetAddress().getHostAddress();
		logger.info(StringUtils.logStr(connIp + "에서 연결 시도."));
		int BUF_SIZE = 1024*7;
		br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf8"), BUF_SIZE);
		pw = new PrintWriter(socket.getOutputStream());
		
		// 클라이언트에서 보낸 문자열 출력
		StringBuffer buffer = new StringBuffer();
		while(true) {
			int ch = br.read();
			if((ch<0) || (ch == '\n')) {
				break;
			}
			buffer.append((char) ch);
		}
		String receiveStr = buffer.toString();
		logger.info(StringUtils.logStr("receive msg : " + receiveStr));
반응형
반응형

이미 작업이 끝난 페이지가 오류가 발생하거나 분석을 위해 찾다보면 사용한 함수 -> 함수 형태로 찾을일이 많은데,

웹에서 디버깅은 관리자도구만 잘 사용하면 빠르게 디버깅이 가능하고 위치를 찾아서 분석하고 수정을 할 수 있습니다.

 

예를 들어 함수가 JSP에도 정의되어 있고 따로 빼놓은 js에도 있고, include처리된 jsp에도 있고 그 jsp를 물고 있는 js에도 정의 되어있고... 직접 짠사람이라면 금방 알고 수정하겠지만, 처음보거나 다른사람이 보면 정의된 내용을 찾는게 여간 고역이 아닙니다. 여러사람이 개발하면서 제대로 된 개발 약속이 없다면 이런식으로 중구난방으로 막 들어가 있을텐데 관리자도구를 사용하면 해당 함수가 어디 있는지 바로바로 찾을 수 있습니다.

 

관리자 도구를 통해 함수가 적용된 페이지 찾기

1. F12버튼을 누르고 관리자도구를 열어줍니다.

 

2. 관리자도구 Console창에 해당하는 함수명을 입력합니다.

function test() {
    console.log("저를 찾았나요?");
}

라는 내용의 test함수를 찾을 경우 "test"만 입력합니다.

 

함수명만 입력

 

3. 그럼 관리자도구는 해당 함수가 존재할 경우 타입이 무엇인지 정의된 내용이 무엇인지 간단하게 알려줍니다.

이때 출력된 함수 내용을 클릭해주면 해당하는 페이지로 관리자 도구가 이동을 시켜줍니다.

 

반응형
반응형

처음 대학에 들어가 c언어를 배우면서 일상에서 쉽게 사용하던 문자열들이 굉장히 어렵게 처리했던것들이 기억난다. char형의 배열로 문자열형태를 만들고 제어했는데, 자바는 String 클래스 하나면 문자열을 저장하고 합치고 필요한 메소드를 사용해 제어가 가능하다.

 

불과 작년까지 String을 자바의 기본 자료형(primitive type)으로 착각하고 문자열처리는 String 클래스로만 가능할 것이라고 생각했던 것에 반성하며 String, StringBuffer, StringBuilder클래스에 대해 정리해본다.

 

String

String 클래스는 일단 변경 불가능한 클래스(immutable)이다.

쉽게 String클래스에 문자열을 넣어 사용하지만 실제로는 다른 언어처럼 char[] 배열 변수를 인스턴스 변수로 받아서 저장이 된다.

final 상수로 String을 처리하였다.(불변 객체 String)

"Test"+ "Sum" = "TestSum" 이런식으로 String으로 쉽게 문자열들을 합쳐왔을텐데, 인스턴스 내의 문자열이 바뀌는게 아닌 TestSum이라는 새로운 문자열이 생성되는 것이다.

 

String의 문자열 비교는 equals()

여기서 우리가 자주 쓰던 조건문의 eqauls() 사용하는 이유가 나오는데, 문자열은 생성할때 새로운 인스턴스를 생성하고 메모리에 저장한다.

 

public class Test {

	public static void main(String[] args) {
		String a = "test1";
		String b = "test";
		String c = "1";
		String d = b + c;
		
		if(a == d) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
		
		if(a.equals(d)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
	}
}

같지 않아요 // 같아요

 

당연히 equals로 비교하지 않았기 때문에 else를 탄다는것을 알지만 왜? 가 여기서 이해가 될 것이다.

test라는 문자열을 생성하고 1을 생성 후 둘을 합치면서 새로운 test1이라는 문자열을 만들었기때문에 둘의 생성된 메모리의 주소값은 다르다. 그래서 두 변수의 문자열의 값만 비교하는 eqauls()를 통해 boolean값을 얻는다.

 

계속해서 말하는 내용이지만 결합이나 추출 등의 작업이 나오면 새로운 문자열이 생성된다. 즉, 문자열을 합칠때마다 새로운 문자열을 만들기 위해 인스턴스를 생성하고 메모리를 잡아먹는다.

문자열의 결합, 추출 등의 작업이 필요하다면 String이 아닌 StringBuffer를 통해 처리하는것이 시스템상 좋을 것이다. 왜? 인스턴스를 매번 생성하고 메모(간단하게 합치고 추출정도는 현대의 컴퓨터 연산과 메모리에 큰 영향이 없으므로 상관 없을것같다.)

 

 

 

StringBuffer

StringBuffer는 불변객체가 아니다. 내부적으로 buffer를 가지고 있는데,

StringBuffer 인스턴스를 생성할때는 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집할때 사용하는 buffer가 된다.

 

기본 사이즈는 16

여기서 적절한 길이를 지정해야 하는 이유는, 수정하는 문자열이 버퍼의 길이를 넘어가면 버퍼의 길이를 늘려주는 작업이 추가되기 때문이다. 배열은 길이를 바꿀수 없기때문에 이전에 작업하던 배열의 값을 복사해서 넣어준다.

 

StringBuffer의 문자열 비교

StringBuffer의 equals는 String의 equals와는 다르게 (==) 비교 연산이라 문자열 비교시 원하는 결과를 얻을 수 없다.

toString()을 통해 String인스턴스화를 거친 후 비교를 한다.

public class Test {

	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer("test1");
		StringBuffer sb2 = new StringBuffer("test1");
		if(sb.equals(sb2)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
		
		String str1 = sb.toString();
		String str2 = sb.toString();
		if(str1.equals(str2)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
	}
}

같지 않아요 // 같아요

 

StringBuffer의 문자열 합치기, 추출, 제거하기

문자열 합치기

StringBuffer에는 append()메소드가 존재한다. 해당 메소드를 통해 문자열을 추가할 수 있다.

StringBuffer sb = new StringBuffer("a");
sb.append("b"); //ab
sb.append("c").append("d").append("e"); //abcde
System.out.println(sb); //abcde

"abcde"

 

위 캡처처럼 계속 연결해서 사용할 수도 있다.

 

 

문자열 제거

delete(), deleteCharAt() 메소드를 통해 문자열을 지우거나 특정 문자를 제거할 수도 있다.

StringBuffer sb = new StringBuffer("abcde");
sb.delete(0, 2);
System.out.println(sb); //cde

"cde"
"abd"

 

 

문자열 추출

String의 substring의 StringBuffer에도 존재한다. 사용법은 동일하다.

StringBuffer sb = new StringBuffer("hello world!");
String str1 = sb.substring(6); //world!
String str2 = sb.substring(0, 5); //hello

"hello"

 

 

문자열 역순처리

reverse()메소드는 문자열을 거꾸로 나열할 수있다.

StringBuffer sb = new StringBuffer("!dlrow olleh");
sb.reverse(); //reverse
System.out.println(sb); //hello world!

문자열 역순처리로 hello world! 출력

 

 

 

StringBuilder

StringBuilder는 기본적으로 사용하는 메소드나 사용법은 동일하여 서로 호환이 됩니다.(append(), delete(), reverse() 등)

StringBuffer와의 차이점은 동기화를 하는지 않하는지의 차이인데

StringBuffer - 동기화O // StringBuilder - 동기화X

싱글쓰레드 환경에서의 개발이라면 StringBuilder를 멀티쓰레드 환경이라면 동기화처리가된 StringBuffer를 사용하면 됩니다.

즉, StringBuffer의 동기화 처리를 빼고 만들어진 클래스가 StringBuilder입니다.

 

그럼 무시하고 무조건 StringBuffer만 쓰면 되는것이 아니냐? 라고 물어볼 수도 있지만...

동기화처리는 시스템의 불필요하게 성능만 느리게 하므로 차이를 알고 사용하는게 시스템 성능향상에 도움이 될 것입니다.

 

 

반응형
반응형

log4j.properties를 사용하여 log4j 설정을 하고 Mybatis의 동작 로그를 출력하도록 설정을 해보겠습니다.

 

mybatis-configs.xml

<configuration>

	<settings>
		<setting name="cacheEnabled" value="false"/>
		<setting name="useGeneratedKeys" value="ture"/>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="defaultStatementTimeout" value="3000"/>
		<setting name="logImpl" value="LOG4J"/>  <!-- log4j log setting  -->
	</settings>

mybatis 설정의 settings 부분에 <setting name="logImpl" value="LOG4J"/> 를 추가합니다.

 

 

log4j.properties

log4j.rootLogger=DEBUG, stdout, logfile

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p (%C{2}:%L) - %m%n

# Direct log message to log file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
log4j.appender.logfile.File=C:/javaProcess/dailyLog.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %5p (%C{2} - %M:%L) - %m%n
log4j.appender.logfile.Append=true
log4j.appender.logfile.encoding=UTF-8

 

rootLogger 부분에 DEBUG를 추가해주면 정상적으로 mybatis 동작에 대해서 출력하는 모습을 볼 수 있습니다.

반응형
반응형

JAVA 서버를 작성해야 할 일이 생겨서 Maven 프로젝트로 생성하여 진행을 하고 있었다. DB에 insert와 select처리가 필요해서 Mybatis 설정작업까지 완료 후, select는 정상동작하는 걸 봤는데 insert처리를 아무리해도 에러가 발생하였다.

 

설정에 문제가 있나???... 점점 당황스러운 시점에 동작하는 insert 쿼리 자체를 넣어도 동작하지 않았다.

 

insert된 결과값을 찍어보니 -2147482646 라는 값이 찍히고 있었다.

 

<setting name="defaultExecutorType" value="BATCH" /> //제거

Mybatis 설정을 하면서 setting부분한 BATCH부분이 문제라고 해서 해당부분을 제거하고 재구동을 해봤지만, 결과값은 정상적으로 1로 나오지만 DB에 반영은 안되고 있었다.

 

 

점점 멘탈이 나가려고 하는 순간 최초에 Mybatis 설정을 하던 부분에서 주석부분이 생각났다.

 

SqlSessionFactory와 SqlSession을 static변수로 처리하고 싱글톤 형태로 특정 객체에서 관리하도록 하였는데,

SqlSession session = sqlSessionFactory.openSession(false);	 //false = Not autoCommit

openSession(false);처리는 autoCommit을 끄는 옵션이였다.

 

해답은 commit이 동작하지 않아서 반영이 안되었기 때문이었다...

순간적으로 부끄러움과 아직 멀었구나가 느껴지는 순간이었다~

 

항상 스프링에서 Mybatis를 사용하다보면 Spring이 알아서 전부 관리해주다보니 까맣게 잊고 있었던 것 같다.

 

Maven프로젝트에서 Mybatis insert처리 후 commit하기

이번일을 계기로 다시한번 공부를 하게되었고, 처리한 방법을 올려볼까 합니다.

 

SqlsessionFactorys.java

package kr.or.kisa.ktoaInterlock.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;

public class SqlSessionFactorys {
	
	private static final Logger logger = Logger.getLogger(SqlSessionFactorys.class);
	
	private SqlSessionFactorys() {	}
	
	static SqlSessionFactory sqlSessionFactory;
	
	private static SqlSessionFactory setSqlSessionFactory() {
		SqlSessionFactory sqlSessionFactory = null;
		String resource = "프로젝트내 xml 위치/mybatis-config.xml";
		
		PropertyReader pr = PropertyReader.getInstance();
		Properties props = new Properties();
		props.put("driver"      , pr.get("mybatis.driver"));
		props.put("url"         , pr.get("mybatis.url"));
		props.put("username"    , pr.get("mybatis.user"));		
		props.put("password"    , pr.get("mybatis.password"));	
		
		InputStream inputStream = null;
		try {
		    inputStream = Resources.getResourceAsStream(resource);
		    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, props);
		    props.clear();
		} catch (IOException e) { 	
			logger.error(e);
		} finally {
			if(inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return sqlSessionFactory;
	}
	
	public static SqlSession setSqlSession() {
		if(sqlSessionFactory == null) {
			sqlSessionFactory = setSqlSessionFactory();
		}
		SqlSession session = sqlSessionFactory.openSession(false);	 //false = Not autoCommit
		return session;
	}
}

SqlSessionFactory는 싱글톤 형태로 유지하고 쿼리를 진행할때마다 열고 닫아가면서 사용할 SqlSessionFactory를 싱글톤 형태로 관리하는 부분이다.

 

 

SqlSessionUtils.java

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

import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;

public class SqlSessionUtils {
	private static final Logger logger = Logger.getLogger(SqlSessionUtils.class);
	
	private SqlSessionUtils() {}
	public static SqlSessionUtils instance;
	public static SqlSessionUtils getInstance() {
		if(instance == null) {
			instance = new SqlSessionUtils();
		}
		return instance;
	}
	
	public int insert(String statement, Object params) {
		int res = 0;
		SqlSession session = null;
		try {
			session = SqlSessionFactorys.setSqlSession();
			res = session.insert(statement, params);
			if(res > 0) {
				session.commit();
			}else {
				session.rollback();
			}
		}catch(Exception e) {
			logger.error(e);
			session.rollback();
		}
		session.close();
		return res;
	}
	
	public List<Map<Object, Object>> selectList(String statement, Object params) {
		List<Map<Object, Object>> res = new ArrayList<>();
		SqlSession session = null;
		try {
			session = SqlSessionFactorys.setSqlSession();
			res = session.selectList(statement, params);
		}catch(Exception e) {
			logger.error(e);
		}
		session.close();
		return res;
	}
}

SqlSession의 메소드와 일부러 이름을 맞추고 파라미터 또한 통일시켰다. 다만 리턴값은 좀 다르게 설정하였다.

예제에서는 insert, select만 존재하며, 추가적으로 update, delete, selectOne 등은 추가적으로 작성이 필요하다.

 

매번 동작할때마다 SqlSession에 SqlSessionFactorys에서 처리한 싱글톤을 통해 SqlSession을 열어주고

쿼리 동작이 끝나면 정상적으로 처리된 경우 commit()을 한다.

정상 동작이 아니거나 예외처리가 발생하면 rollback()처리를 해주고, 동작이 끝나면 해당 SqlSession을 닫아준다.

 

 

사용한 Dao 예제 소스)

import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import kr.or.kisa.ktoaInterlock.utils.SqlSessionUtils;

public class InterlockDao {
	private static final Logger logger = Logger.getLogger(InterlockDao.class);
	
	private InterlockDao() {}
	private static InterlockDao instance;
	static SqlSessionUtils session = SqlSessionUtils.getInstance();
	
	public static InterlockDao getInstance() {
		if(instance == null) {
			instance = new InterlockDao();
		}
		return instance;
	}
	
	//테이블 데이터 저장
	public int insertRequestTrack(Map<Object, Object> params) throws Exception{
		return session.insert("track.insertTest", params);
	}
}

로그를 확인해보면 오토커밋은 꺼진상태로 정상적으로 JDBC 커넥션이 열리는 모습을 볼 수 있고, insert 동작이 끝나면 commit을 처리하고 JDBC연결을 닫아준다.

 

추가적으로 commit을 한번 처리하면 autoCommit이 켜지는것 같은데, SqlSession을 닫아버리고 다시 꺼진 옵션의 SqlSession을 가져오기 때문에 큰 문제는 없이 계속해서 해당 로직을 사용하여 insert 처리를 할 수 있었다.

반응형