쿼리로 시간차 계산시 날짜를 문자로 바꾼뒤 마이너스 연산한 값을 사용한 경우가 가끔 보이던데, 

의외로 이같은 실수를 저지르는 개발자들이 상당히 있는데다가 

오류가 쉽게 드러나지 않아 모르고 지나쳐버리는 경우도 꽤 많습니다.

하지만 이런 코드는 심각한 오류를 가지고 있으므로 반드시 수정되야합니다.



두 경우 모두 결과 값이 1로 나오길 기대하겠지만 두번째 경우는 41이라는 결과가 나오게 됩니다.

문자간 마이너스 연산시 오라클이 문자를 숫자로 취급하는데, 

이때 숫자를 10진수로 처리하기 때문에 의도와는 달리 완전히 다른 계산 결과가 나와버린 것 입니다.


오라클에서 시간차를 구하려면 날짜간 마이너스 연산 후 단위를 변환해야 올바른 결과를 얻을 수 있습니다.



(날짜차이, 달수 차이등은 이전 포스팅 참조하세요.)






iOS7 베타를 깔기위해 오랜만에 iTunes를 켰다가 들뜬맘에 얼떨결에 퀵타임을 설치해버렸다.

이미 설치한거 지우기도 귀찮아서 내버려뒀는데, PC 재부팅후 이클립스를 실행하니

멀쩡하던 프로젝트가 에러를 뿜어내고 있는 것 아닌가?

"QTJava? 뭐 이런 퀵타임스런 이름이 jre 에 있지???"했는데 진짜 퀵타임이 설치한 라이브러리임.. OMG!

근데 에러가 난 이유는 실제론 QTJava.zip 이 저기 나온 경로엔 없고 퀵타임 설치경로에 있기 때문이다.

다만, 퀵타임이 클래스패스까지 손을 대는 바람에... 결국 저런 참사가 벌어진 것이다.

맘대로 클래스패스 수정 할거면 제대로나 하지!


아무튼 구글링 좀 해봤더니...

아니 내가 초짜라니!!(←초짜 맞음)


아무튼 난 동영상 다룰일 없으니 QTJava 를 클래스패스에서 싹 지워버린 후

블로그에 글까지 남기게 한 이 사태에 대한 책임을 물어 퀵타임을 내 PC에서도 삭제 조치 하였다.훗.

메이븐 리소스 필터링?

Filtering -> 흔히 '걸러내기'라고 이해하고 있기 때문에

jar 패키징시 .properties 파일등을 빼내는 건 줄 알았지만 그것은 함정!


핸..핸드드립!!! 출처: http://blog.naver.com/PostView.nhn?blogId=adsl4860&logNo=70110339277


필터링의 진실을 다음 sample pom.xml 을 보면서 간단히 설명하겠습니다.

<filter>태그에서 db 접속정보가 들어있는 속성파일을 필터로 지정하면

<resource>태그 아래서 filtering=true 를 통해

<directory>태그와 <include>태그로 지정한 (텍스트로 된)리소스안에 

${...}형식으로 되어 있는 표기된 부분이 필터로 지정한 속성파일안에 지정한 값으로 치환됩니다. 

또, pom.xml의 properties 태그로 지정한 속성이나 maven의 기본 속성변수인 ${basedir}, ${project.build.finalName}등은 필터지정 없이도 사용 할 수 있어요.


단, filtering 기본값은 false 이기 때문에 filtering=true 를 지정하지 않으면 당연히 바뀌지 않는점, 텍스트파일이 아닌 바이너리 등은 바뀌지 않는것에 유의해주세요.


활용

우리는 흔히 myBatis 의 환경설정파일(db.config.xml)에 db접속 정보를 그대로 기록합니다.

하지만, DB접속 정보는 개발자마다 달라질 수 있어서 이런 파일은 저장소에 커밋하지 않습니다.

그러나 이 파일안에는 접속정보외에도 어플리케이션의 안정적 운영을 위한 설정도 함께 들어있어 커밋에서 제외하기도 힘듭니다.

궁여지책으로 '템플릿'역할을 하는 파일을 만들어 이를 커밋한 후 각 개발자가 이 파일이름을 수정한 후 자신의 접속 정보를 설정해 사용하지만 종종 실수한 커밋이 올라오는 경우도 많아서 혼란스러울 때가 많습니다.


위 문제는 필터링을 이용하면 좀 더 안전하게? 해결 할 수 있습니다.


다시 한번 위 예시를 봅시다. 

필터로 지정된 db.config.properties 에는 DB접속 설정만을 기록합니다.

그리고 myBatis 가 사용하는 db.config.xml은 리소스로 지정한 src/main/resources/ 에 위치시킵니다.

마지막으로 db.config.xml 안에 DB접속 정보가 들어간 부분을 db.config.properties의 속성으로 바꿔주고 이클립스에서 실행하면

maven 에 의해 빌드타임에 db.config.xml 안에 ${...}로 표기된 부분이 db.config.properties 에 지정된 값으로 치환됩니다.


이제 db.config.xml 파일을 맘놓고 커밋 할 수 있겠군요!


또 다른 활용사례로 저는 jar 실행을 위해 만든 배치파일인 run.bat 에 다음과 같이 적용했습니다.


src/main/resources/run.bat (필터링안된 소스)

java -jar *${project.build.finalName}*.jar


target/jar/run.bat (필터링됨)

java -jar *imonLope-1.0-SNAPSHOT*.jar

참고: http://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html

'IT > Maven' 카테고리의 다른 글

maven-resource-plugin: Filtering  (0) 2013.06.07

리플렉션을 이용하면 다른 클래스의 메서드 불러(invoke) 볼 수 있습니다. iBatis 로 데이터베이스에서 조회한 결과를 객체에 맵핑 할 때, 담을 객체만 정의해줘도 같은 필드명을 가진 속성에 들어가는 것도 이 방법을 이용한 것 입니다.(속도 문제로 현재는 다른 방법을 쓴다네요.) 

일반적인 자바 프로그래밍이 할 수 없는 것을 할 수 있게 해주는 리플렉션이지만 흔히 성능이 나쁘다고 알려져있어 도대체 얼마나 느린지 테스트를 해봤습니다. 추가로 리플렉션 성능을 향상시키기 위해 동적으로 생성한 바이트코드를 사용하는 ReflectASM 와도 비교해 봤습니다.

1. 결과

chartgo (4)

리플렉션 함부로 쓰면 큰일나겠네요! (이미지를 클릭하면 500M 까지의 테스트 결과를 더 볼 수 있습니다.)

 

2. 테스트 코드

이클립스는 Syntax Coloring 을 백업/복원하기 위한 도구를 별도 지원하지 않습니다. 게다가 워크스페이스가 변경되면 Syntax Coloring 설정도 초기화되버려, 워크스페이스를 새로 만들때 마다 새로 설정 해야 하는 번거로움이 생깁니다.

앞서 소개한 이클립스의 컬러 셋팅 가져오기(http://yjacket.tistory.com/40)으로는 Java와 XML 에디터의 컬러 셋팅만을 백업/복원 할 수 있어, 이번에는 Flash Builder의 Editor 에 대한 Syntax Coloring 을 백업/복원하는 방법을 설명하려합니다.


역시 간단히 얘기하자면, 아래 위치에 있는 파일에 컬러셋팅이 저장됩니다. 이 파일을 백업하고, 새로만든 워크스페이스의 경로에 덮어 써주면 됩니다.

Flex Builder

[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\com.adobe.flexbuilder.editors.mxml.prefs
[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\com.adobe.flexbuilder.editors.actionscript.prefs

Flash Builder

[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\com.adobe.flexide.mxml.core.prefs
[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\com.adobe.flexide.as.core.prefs


단, 이 파일은 이클립스에서 이 설정을 지정 한 후 이클립스를 정상 종료해야 생성/저장됩니다. 그러니까 백업을 할 때는 원하는 설정을 저장하고, 이클립스를 종료 한 후 해당 파일을 백업해야 한다는 얘기죠.

자바에서 엑셀파일을 읽는데 POI와 JXL중 어느것이 더 성능이 나을까?

테스트를 위해 Excel 2010에서 sheet 1에 A열부터 Z열까지 각 셀에 다양한 언어로 10~30글자씩 입력하고 65,536행을 모두 같은 내용으로 채워 test.xls 로 저장했다.

테스트코드 및 결과는 다음과 같다.

테스트코드


* POI, JXL 을 java build path 에 추가해야 한다. 구글링하면 다운로드 받을 수 있는 경로가 나오므로 셋팅방법을 별도로 설명하지는 않는다.

결과

 

elapsed(sec)

JXL

3.48

POI(xls)

4.43

POI(xlsx)

28.43

 
* JXL 이 가장 빠른 속도로 내용을 읽어 왔다.
* 같은 문서를 xlsx 형식으로 저장했을때, POI의 읽기 속도는 JXL보다 
8.18배 느리다.
  1. 초보개발자 2012.02.28 12:03

    안녕하세요 질문하나 해도 될까요?
    poi 로 작업 중인데
    20만건이 넘는 엑셀 파일을 읽으려고 하고 있습니다.
    그런데 위에 선언한것 처럼 65536건 밖에 못 읽더군요
    이걸 해결 할 방법이 어떤게 있는지 알수 있을까요?

    • yjacket 2012.03.05 15:29 신고

      안녕하세요?
      xls 포맷으로는 한 시트에 65,536건밖에 입력 못합니다.
      이를 초과 할 데이터를 저장해야 할 때 여러 시트에 분리해 넣는 방법 등을 사용하긴 합니다. (불편 할 수 있지만 속도면에서 유리합니다.)

      반면 POI를 이용하면 xlsx포맷을 이용 할 수 있기 때문에 65,536건 이상의 데이터도 저장 할 수 있습니다. 정확하게 얼마나 많은 건을 저장 할 수 있는지 확인해보진 않았지만 백만건정도는 된다고 하는것 같네요.

자바에서 엑셀파일을 쓰는데 POI와 JXL중 어느것이 더 성능이 나을까?

테스트코드 및 결과는 다음과 같다.

테스트코드


* POI, JXL 을 java build path 에 추가해야 한다. 구글링하면 다운로드 받을 수 있는 경로가 나오므로 셋팅방법을 별도로 설명하지는 않는다.
* StopWatch 클래스는 성능측정을 위해 임의로 작성한 클래스이므로 해당 클래스가 없다면 이 코드는 컴파일 되지 않는다. 해당 클래스를 작성하던지 아니면 해당 구문을 제거하고 컴파일 할 것

결과

 

10,000 rows

65,536 rows

elapsed(sec)

filesize(KB)

elapsed(sec)

filesize(KB)

JXL

                   0.28

                  942

                  1.39

             6,113

POI(xls)

                   0.98

              2,043

               27.07

          13,371

POI(xlsx)

                   6.68

                  276

             257.20

            1,775


* 속도는 JXL 이 가장 빠르고, 파일 크기는 POI(xlsx)가 가장 작다.
* 10,000 행 테스트와 65,536 행 테스트간 처리속도시간는 처리량이 6.55배 증가한 것에 비해 JXL은 4.96배 밖에 증가 하지 않았지만, POI는 xls가 27배, xlsx가 38배나 증가했다.
* POI 는 xls 형식으로 대량의 행을 저장하기에는 속도 및 용량효율측면에서 모두 JXL에 상당히 뒤쳐지지만, xlsx 형식으로 저장시 파일 용량을 JXL로 작성된 xls의 30% 수준으로 줄일 수 있다.
테스트 목적
Java 의 Connection 과 Oracle 의 session 간의 관계 확인

가정
IBATIS 설정 MaximumActiveConnections 까지 점차적으로 Connection 수를 증가시키면
매 증가시 마다 오라클 session 수가 늘어날 것이다.

실험

1. 연결 유지를 위한 무한루프 프로시저

CREATE OR REPLACE PROCEDURE SP_LOOP
is
begin    
    loop 
        null;
    end loop;
end;



2. SQLMap 

<sqlMap>
    <procedure id="loop">
        {call sp_loop}
    </procedure>
</sqlMap>

 
3. 5초마다 하나씩 커넥션을 늘리고, 연결정보를 출력하는 자바 어플리케이션

import java.sql.SQLException;

import com.ibatis.common.jdbc.SimpleDataSource;
import com.ibatis.sqlmap.client.SqlMapClient;

public class ConnectionTest
{
public static void main(String[] args) throws InterruptedException
{
init();
int i = 0;
while (true)
{
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
sqlMapClient.queryForList("loop");
}
catch (SQLException e)
{
e.printStackTrace();
}
}
});
System.out.println("take " + i++);
t.start();

System.out.println(((SimpleDataSource) sqlMapClient.getDataSource()).getStatus());

Thread.sleep(5000);
}
}
private static SqlMapClient sqlMapClient = null;
private static void init()
{
// IBATIS 초기화(생략)
}
}


4. 결과

테스트 어플리케이션이 실행되면 매 5초 마다 새로운 커넥션이 생성되므로
오라클에서 다음 쿼리를 이용해 session 수의 증가를 확인

SELECT status, count(*) 
FROM v$session where username='test' AND program='JDBC Thin Client' 
GROUP BY status

 
그리고 자바 어플리케이션을 실행하면 다음과 같은 로그가 나타난다.


take 0

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              0
 idleConnections                0
 requestCount                   0
 averageRequestTime             0
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
DEBUG [00:08:40.544] {Thread-1} (Log4jImpl.java:26) Created connection 2040402337.
DEBUG [00:08:40.550] {Thread-1} (Log4jImpl.java:26) {conn-100000} Connection
DEBUG [00:08:40.554] {Thread-1} (Log4jImpl.java:26) {conn-100000} Preparing Call: {call sp_loop}
DEBUG [00:08:40.679] {Thread-1} (Log4jImpl.java:26) {pstm-100001} Executing Statement: {call sp_loop}
DEBUG [00:08:40.680] {Thread-1} (Log4jImpl.java:26) {pstm-100001} Parameters: []
DEBUG [00:08:40.680] {Thread-1} (Log4jImpl.java:26) {pstm-100001} Types: []

take 1

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              1
 idleConnections                0
 requestCount                   2
 averageRequestTime             919
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
DEBUG [00:08:45.637] {Thread-2} (Log4jImpl.java:26) Created connection 435456241.
DEBUG [00:08:45.637] {Thread-2} (Log4jImpl.java:26) {conn-100002} Connection
DEBUG [00:08:45.638] {Thread-2} (Log4jImpl.java:26) {conn-100002} Preparing Call: {call sp_loop}
DEBUG [00:08:45.638] {Thread-2} (Log4jImpl.java:26) {pstm-100003} Executing Statement: {call sp_loop}
DEBUG [00:08:45.639] {Thread-2} (Log4jImpl.java:26) {pstm-100003} Parameters: []
DEBUG [00:08:45.639] {Thread-2} (Log4jImpl.java:26) {pstm-100003} Types: []

take 2

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              2
 idleConnections                0
 requestCount                   2
 averageRequestTime             919
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
DEBUG [00:08:50.861] {Thread-3} (Log4jImpl.java:26) Created connection 39288954.
DEBUG [00:08:50.861] {Thread-3} (Log4jImpl.java:26) {conn-100004} Connection
DEBUG [00:08:50.862] {Thread-3} (Log4jImpl.java:26) {conn-100004} Preparing Call: {call sp_loop}
DEBUG [00:08:50.862] {Thread-3} (Log4jImpl.java:26) {pstm-100005} Executing Statement: {call sp_loop}
DEBUG [00:08:50.862] {Thread-3} (Log4jImpl.java:26) {pstm-100005} Parameters: []
DEBUG [00:08:50.863] {Thread-3} (Log4jImpl.java:26) {pstm-100005} Types: []

take 3

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              3
 idleConnections                0
 requestCount                   3
 averageRequestTime             686
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
DEBUG [00:08:57.057] {Thread-4} (Log4jImpl.java:26) Created connection 2008400431.
DEBUG [00:08:57.058] {Thread-4} (Log4jImpl.java:26) {conn-100006} Connection
DEBUG [00:08:57.058] {Thread-4} (Log4jImpl.java:26) {conn-100006} Preparing Call: {call sp_loop}
DEBUG [00:08:57.058] {Thread-4} (Log4jImpl.java:26) {pstm-100007} Executing Statement: {call sp_loop}
DEBUG [00:08:57.059] {Thread-4} (Log4jImpl.java:26) {pstm-100007} Parameters: []
DEBUG [00:08:57.059] {Thread-4} (Log4jImpl.java:26) {pstm-100007} Types: []

.
.
.
(중략)
.
.
.


take 49

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              49
 idleConnections                0
 requestCount                   49
 averageRequestTime             1653
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
DEBUG [00:12:59.662] {Thread-50} (Log4jImpl.java:26) Created connection 630515574.
DEBUG [00:12:59.662] {Thread-50} (Log4jImpl.java:26) {conn-100098} Connection
DEBUG [00:12:59.663] {Thread-50} (Log4jImpl.java:26) {conn-100098} Preparing Call: {call sp_loop}
DEBUG [00:12:59.663] {Thread-50} (Log4jImpl.java:26) {pstm-100099} Executing Statement: {call sp_loop}
DEBUG [00:12:59.664] {Thread-50} (Log4jImpl.java:26) {pstm-100099} Parameters: []
DEBUG [00:12:59.664] {Thread-50} (Log4jImpl.java:26) {pstm-100099} Types: []

take 50

===============================================================
 jdbcDriver                     oracle.jdbc.OracleDriver
 jdbcUrl                        jdbc:oracle:thin:test/************@127.0.0.1:1521:xe
 jdbcUsername                   test
 jdbcPassword                   ************
 poolMaxActiveConnections       50
 poolMaxIdleConnections         10
 poolMaxCheckoutTime            120000
 poolTimeToWait                 300
 poolPingEnabled                false
 poolPingQuery                  select 1 from dual
 poolPingConnectionsOlderThan   1
 poolPingConnectionsNotUsedFor  1
 --------------------------------------------------------------
 activeConnections              50
 idleConnections                0
 requestCount                   50
 averageRequestTime             1687
 averageCheckoutTime            0
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================
 
take 51
 


activeConnections 는 MaximumActiveConnections의 제한 수치인 50에서 더이상 증가하지 않고 
take 51 에서 멈춘다. 내부적으로 take51 는 다음과 같은 Stack trace 를  나타내고 있다.

Name: Thread-51
State: BLOCKED on oracle.jdbc.driver.T4CConnection@6c63a721 owned by: Thread-1
Total blocked: 1  Total waited: 0

Stack trace: 
 oracle.jdbc.driver.PhysicalConnection.rollback(PhysicalConnection.java:3689)
com.ibatis.common.jdbc.SimpleDataSource.popConnection(SimpleDataSource.java:600)
   - locked java.lang.Object@7d7082d8
com.ibatis.common.jdbc.SimpleDataSource.getConnection(SimpleDataSource.java:222)
...

 
오라클의 세션수는 take n 의 n 수와 일치하며 take 51 시점의 세션수는 이미 한계치에 도달했으므로 더 이상 증가하지 않는다.

STATUS COUNT(*)
--------    ----------
ACTIVE 50



 
  1. 단테 2014.03.12 18:02

    비슷하게 따라 했는데 왜 activeConnections 가 스레드 하나당 하나씩 증가 하지 않을까요???

    ===============================================================
    jdbcDriver com.p6spy.engine.spy.P6DataSource
    jdbcUrl jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1522:myDB
    jdbcUsername myName
    jdbcPassword ************
    poolMaxActiveConnections 80
    poolMaxIdleConnections 40
    poolMaxCheckoutTime 120000
    poolTimeToWait 500
    poolPingEnabled true
    poolPingQuery select 0 from dual
    poolPingConnectionsOlderThan 1
    poolPingConnectionsNotUsedFor 1
    --------------------------------------------------------------
    activeConnections 53
    idleConnections 0
    requestCount 79
    averageRequestTime 50100
    averageCheckoutTime 0
    claimedOverdue 0
    averageOverdueCheckoutTime 0
    hadToWait 0
    averageWaitTime 0
    badConnectionCount 0
    ===============================================================

    ===============================================================
    jdbcDriver com.p6spy.engine.spy.P6DataSource
    jdbcUrl jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1522:myDB
    jdbcUsername myName
    jdbcPassword ************
    poolMaxActiveConnections 80
    poolMaxIdleConnections 40
    poolMaxCheckoutTime 120000
    poolTimeToWait 500
    poolPingEnabled true
    poolPingQuery select 0 from dual
    poolPingConnectionsOlderThan 1
    poolPingConnectionsNotUsedFor 1
    --------------------------------------------------------------
    activeConnections 53
    idleConnections 0
    requestCount 79
    averageRequestTime 50100
    averageCheckoutTime 0
    claimedOverdue 0
    averageOverdueCheckoutTime 0
    hadToWait 0
    averageWaitTime 0
    badConnectionCount 0
    ===============================================================
    [Thread Count] : 880
    [Thread Count] : 881
    [Thread Count] : 882

  2. 단테 2014.03.12 19:23

    현재 현상은 위의 에러와 같이 popConnection() 에서 lock이 걸리는데 재현이 안되네요. 혹시 도움좀 받을 수 있을까 해서요.^^

MaximumIdleConnections 
IBATIS 에서는 MaximumActiveConnections 설정으로 connection pool 의 최대 크기를 설정한다.
그런데 이 설정에 대비되는 설정인 MaximumIdleConnections 을 얼핏(본인이 그랬음-_-;)
connection pool 의 최소 크기 설정이라 생각하기 쉬운데 사실은 그렇지 않다.

Ibatis 는 처음부터 최소치만큼 커넥션을 생성해두지 않고 필요시마다 증가시키므로 (이름부터 Max인) MaximumIdleConnections 은 최소값이 아니고, 단지 ArrayList인 idleConnections 의 최대 길이일 뿐이다.

Connection pooling logic
SimpleDataSource 는 내부적으로 connection pool 을 관리하기 위해 
ArrayList 타입 멤버인 idleConnections 와 activeConnections 에 커넥션을 저장한다.

그리고 처음엔 커넥션을 하나도 안만들고 있다가, 요청이 들어올때 다음 순서로 동작한다.

1. idleConnections 에 대기중인 커넥션이 있으면 이를 반환
2. idleConnections 가 없으면 MaximumActiveConnections 초과하지 않을 경우, 새 커넥션을 만들어 반환
3. MaximumActiveConnections을 초과하면, activeConnections 에서 가장 오래된 커넥션을 가져와 체크아웃타임이 MaximumCheckoutTime을 초과할 경우 이 커넥션을 롤백하고 반환
4. MaximumCheckoutTime을 초과하지 않으면 TimeToWait 만큼 기다렸다가 다시 커넥션 획득 시도


자, 이제 본론이다. 다시 MaximumIdleConnections 의 의미를 돌이켜보자.

MaximumIdleConnections 은 최대 유휴 연결(보관) 수,
즉, idle 상태 연결을 최대 얼마나 보관할지 결정하는 설정인데,
주의해야 할 점은 이 설정을 잘못하면 성능에 심각한 문제가 발생 할 수 있다는 것이다.

다음 상황을 가정해보자

MaximumActiveConnections = 50
MaximumIdleConnections = 0

connection pool size 는 최대 50까지 증가하므로 activeConnections.size() 는 50 이하다. 그런데, activeConnections 에 있던 연결이 다 사용되고 나서 pushConnection() 으로 반환될때 해당 연결은 유휴 상태이므로 idleConnections 로 들어가야하지만, idleConnections 는 위 설정에 의해 길이가 0 이상 될 수 없다. 따라서 유휴 상태로 접어드는 연결은 무조건 소멸되며, idleConnections 에 어떤 연결도 없으므로, ibatis 는 매번 새로운 연결을 만들어내게 되므로 connection pool 을 사용하는 의미가 없게 된다.

이러한 성능 저하는 MaximumActiveConnections, MaximumIdleConnections의 차가 크면 클수록 더 심각하게 되므로, 어플리케이션의 성격에 따라 적절하게 설정 되어야 한다.



참조 : com.ibatis.common.jdbc.SimpleDataSource.popConnection(), pushConnection()

Java 에서 long 은 primitive type, 즉, 원시형 데이터로 클래스가 아니다.
long 은 64bit 정수값을 표현하는 데이터 타입으로 8byte 의 메모리 공간을 사용한다.
Long 은 long 과 마찬가지로 64bit 정수 값을 표현하지만 이것은 클래스다.
그렇다면 Long 은 몇 byte 의 메모리 공간을 사용할까?
테스트를 위해 다음과 같은 코드를 작성해보자.

public class LongTest1
{
public static void main(String[] args)
{
int length = 10000000;
long[] ls = new long[length];
long l = 0;
for (int i = 0; i < length; i++)
ls[i] = l++;
System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
}
}

위 테스트 클래스는 primitive type인 long을 사용해 배열을 작성하고, 메모리 사용량을 출력한다.

public class LongTest2
{
public static void main(String[] args)
{
int c = 10000000;
Long[] ls = new Long[length];
long l = 0;
for (int i = 0; i < length; i++)
ls[i] = new Long(l++);
System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
}
}


위 테스트 클래스는 Long 클래스의 배열을 작성하고, 메모리 사용량을 출력한다.
 
아래는 heapsize를 최소 100MB, 최대 2GB로 지정한 VM에서 위 두 클래스의 c 값을 바꿔가며 실행한 결과다.


측정한 결과치는 32bit / 64bit VM 에서 각각 다르게 나왔는데, 배열 자체의 사용량을 측정한 것이 아니고 전체 VM의 메모리 사용량을 본 것이므로 long배열의 경우 차이가 없다고 해석 가능하지만, Long배열은 눈에 띄는 차이를 보였다.

아무튼 위 표의 측정치를 통해 long 은 8byte 를 사용하고, Long 은 32bit에선 약 20byte, 64bit 에선 약 28byte 를 사용한다고 볼 수 있다.

측정시 약간의 실수가 있었던것 같아 정정하자면 위 측정 결과는 아주 정확하다고 볼 수 없지만 heapdump 를 확인한 결과 측정결과와 거의 일치하므로 어느정도 신뢰 해도 될 듯 하다. (heapdump 에선 Long 의 메모리 사용량이 64bit 에서 24byte로 나왔다.)
 
  1. conrg 2013.08.19 10:58

    글 잘읽었습니다. 저도 객체 사이즈가 궁금해서 이것저것 찾아봤는데, 위에서 제시한 접근방법이 맞지만, 측정하는 부분이 잘못되었습니다. http://www.javaworld.com/javaworld/javaqa/2003-12/02-qa-1226-sizeof.html?page=1, http://www.javaworld.com/javaworld/javatips/jw-javatip130.html?page=1 두사이트 읽어보세요. 많은 도움이 될것입니다.

JAI (Java Advanced Imaging)
지원 : BMP, GIF (read only), FlashPix (read only), JPEG, PNG, PNM, TIFF, and WBMP
http://java.sun.com/javase/technologies/desktop/media/
http://java.net/projects/jai/
http://java.sun.com/products/java-media/jai/whatis.html
이미지 변환 방법 http://www.javanuri.com/devforum/boardView.jsp?menuId=9&Id=261407&gb=qna
 

JIMI (Java Image Management Interface)
지원 : GIF, JPEG, TIFF, PNG, PICT, Photoshop, BMP, Targa, ICO, CUR, Sunraster, XBM, XPM, and PCX
다운로드 http://java.sun.com/products/jimi/
JAI 와 성능면에서 뒤쳐지는 결과? http://www.hanhuy.com/pfn/java-image-thumbnail-comparison 
난 JIMI가 좋은거 같은데, 왜 모든 사람들은 JAI 만 언급하는가? https://forums.oracle.com/forums/thread.jspa?threadID=1270326
JIMI가 Java3D 로 흡수? 되었다는 얘기 http://stackoverflow.com/questions/967557/java-api-to-convert-jpeg-to-tiff


im4java (imageMagick)

제작사 http://www.imagemagick.org/script/api.php
다운로드 http://im4java.sourceforge.net/
JAI 와 품질,성능에 대한 코멘트 http://stackoverflow.com/questions/2291358/how-do-i-convert-a-tif-to-png-in-java


bmp 를 변환할 일이 있어서, 검색해봤는데 자바 초창기에 JIMI가 많이 쓰이다가 JAI 가 나오고나서는 JAI 를 더 많이 쓰는듯. 하지만  JAI가 너무 크다고 생각하는 사람들은 아직도 JIMI가 좋다고 생각함... 또 어떤사람은 im4java 를 JAI 보다 더 좋고 깔끔하다고 생각하지만 im4java 는 JAI나 JIMI 와 달리 sun(oracle)의 프로젝트가 아니기 때문인지..아니면 실제로 버그가 좀 있는건지 지적당하는 경우도 있음.

JAI는 좀 거대해보이고.. 난 변환만 하면 되니까 jar 크기 젤 작은 JIMI 쓸 생각!

이었으나

JDK 1.4.2 이후 버전에서는 이미지 변환같은 건 imageio로 가능해서 다음과 같이 구현함

File input = new File("D:\image.bmp");
File output = new File("D:\image.png");
BufferedImage bufferedImage = ImageIO.read(input);
ImageIO.write(bufferedImage, "png", output); // jpg, gif 도 가능

자바 어플리케이션에 문제가 있어 아무 말 없이 종료되버리면 개발자 입장에선 난감하기 그지없다.
오류라도 뿜어주면 고맙겠지만, 아무도 오류메시지를 보지 못했다면 어디서 그의 죽음에 관한 정보를 얻을 수 있을것이며, 그 오류는 어떻게 찾아 고쳐야 할까?

다행히 JVM은 자바 어플리케이션에서 심각한 오류나, 예상하지 못한 오류를 발견 했을때는 자동적으로 STDOUT에 dying message 를 남기고, 어플리케이션의 실행위치에 error report 파일을 작성한다는데...

진짜 그런지 한번 보자.
아래는 강제로 에러를 발생시키는 자바 어플리케이션이다.

public class ForceDump {
public static void main( String[] args ) throws Exception
{
java.lang.reflect.Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        sun.misc.Unsafe $ = (sun.misc.Unsafe) field.get(null);
        $.putAddress(0, 0);
    }
}


이걸 실행해보면 다음과 같은 결과가 출력된다. 빨간색 글자로 범인은 누구라고 알려준다. 뿐만 아니라 어플리케이션 종료 당시의 해당 어플리케이션의 실행정보를 가지고 있는 에러파일을 저장했다고 한다.

D:\dev\lab\Test\bin>java ForceDump

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006d9b9565, pid=4568, tid=6248
#
# JRE version: 6.0_25-b06
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.0-b11 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# V  [jvm.dll+0x1c9565]
#
# An error report file with more information is saved as:
# D:\dev\lab\Test\bin\hs_err_pid4568.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#


자세한 에러 파일 내용을 보기 전에 잠깐,

에러파일이 hs_err_pid<PID>.log 라는 이름으로 java 어플리케이션이 실행된 경로에 생겨있다. 파일이름과 생기는 위치는 이와 같은 형태가 기본값이지만, 이 파일의 이름과 위치를 바꾸려면 다음과 같이 옵션을 주면 된다.
 

D:\dev\lab\Test\bin>java -XX:ErrorFile=../err_file_pid_%p.log ForceDump
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006d9b9565, pid=3088, tid=1340
#
# JRE version: 6.0_25-b06
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.0-b11 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# V  [jvm.dll+0x1c9565]
#
# An error report file with more information is saved as:
# ../err_file_pid_3088.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#


-XX:ErrorFile=<filename> 형식이며 filename에 %p 를 쓰면 해당 어플리케이션의 pid 값으로 치환된다.
filename 은 상대 또는 절대경로를 포함해 지정 할 수 있다. (위 예시에서는 상대경로를 지정해보았다.)
만일 유효하지 않은 경로라면 이 옵션을 지정하지 않은 것과 동일하게 동작한다.



아래는 범인에 대한 더 많은 정보를 가지고 있다는 에러파일의 내용이며, 왜 프로그램이 종료 되었는지에 대해 분석하는데 도움이 된다.


JVM 이 java 어플리케이션에서 unexpected error (e.g. out of memory error) 를 발견했을때는 에러파일내용이 아래와 같이 나오기도 한다.(헤더부분만 첨부함)


 
참조
http://www.java.com/en/download/help/error_hotspot.xml 
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html#DebuggingOptions 

http://forums.oracle.com/forums/thread.jspa?threadID=1237584 (-XX:OnError 옵션을 이용해 에러파일 내용을 메일로 보내는 예제까지)
오라클에서 길이가 없는 문자열(aka empty string, white space, blank)은 null 로 취급된다.
즉, 필드에 들어있는 값이나 '' 문자 또는 함수가 반환하는 값이 길이가 없는 문자열이라면 null 로 취급된다는 것이다.
이러한 특징은 여타 DBMS 와 다른 특징이므로 매우 주의해야 할 점이다.

몇가지 예를 통해 오라클에서 길이가 없는 문자열을 null 로 취급하고 있음을 확인해보자.

1. NVL
NVL(expr1, expr2) 함수는 expr1이 null 이 아니면 expr1을 그대로 반환하고, null 이면 expr2을 반환한다.
NVL 을 이용해 null 이면 길이가 없는 문자열을 반환하도록 다음과 같이 쿼리를 작성하고 테스트해보면

SELECT NVL(null, '') FROM DUAL


의도와는 다르게 아래와 같이 null 이 반환되버린다.

 NVL(NULL,'')    
 --------------- 
 (null)          

2. INSERT
not null 필드에 길이가 없는 문자열을 삽입하기위해 임시테이블을 만들고, 레코드를 추가해보자.

CREATE TABLE TestTable (field1 varchar(50) not null)
GO
INSERT INTO TestTable VALUES ('')


그러면 다음과 같이 에러가 발생한다.

>[에러] 스크립트 라인: 1-1 ----------------------------------
 ORA-01400: NULL을 ("USER1"."TestTable"."FIELD1") 안에 삽입할 수 없습니다
 스크립트 라인 1. 문장 라인 1, 컬럼 26

3. 비교
'' 은 무엇과 비교하더라도 true 가 될 수 없다. 따라서 다음은 모두 true 가 아니다.

* '' = ''
* '' = NULL
* AnyExpression = ''


그리고 다음은 null 을 반환한다.

* trim(' ')
* '' 을 저장한 필드


단, 다음은 참이다.

* '' IS NULL


 
왜 오라클은 empty string 을 null 로 취급할까?
http://stackoverflow.com/questions/203493/why-does-oracle-9i-treat-an-empty-string-as-null 
SQL standard 가 만들어지기 전 empty string 으로 null 을 식별하도록 디자인 했었는데, SQL Standard 에서 empty string 과 null 을 구별하기로 정해진 이후에도 하위 호환성을 위해 열어둔 것 같다고 함.
한번 하고 나면 수년간 차량 관리가 간편해진다는 유리막 코팅! 하지만 방송을 통해 이미 과대광고와 일부 업체의 소비자 기만 행태를 보아왔던 터라 신차를 구매 했음에도 별로 관심을 가지고 있지 않았지만, 몇몇 커뮤니티에서 알게된 정보에 의하면 일본 제품인 코랄에이스라는 유리막코팅제는 가격도 저렴하며 혼자서도 쉽게 시공 가능하다고 하네요. 또 제품소개나 평판들이 좋은편이라 백만년만에 DIY를 시도해봤고 유리막코팅이 좋던 나쁘던 제가 직접 해보고 알게된 DIY 노하우를 공유하고자 포스팅합니다.

1. 구매
옥션, 11번가등에서 이 제품을 구매 할 수 있습니다. 특이하게도 일본 본사의 직판구조로 판매되고 있으며, 이로 인해 유통마진을 줄일 수 있어 구매자에게는 좋은 조건이겠네요.

그런데, 직판이라서 그런지 판매자도 일본인인듯. 제품 소개 페이지 최상단에 이런 문구가 나타납니다.



지진으로 인해 배송이 늦어지는 것이 미안해 9,800원짜리 카샴푸 2개와 천 2개를 그냥 준답니다. 음, 서비스 물품가가 1~2만원 이상 되는건데.. 이 정도면 흔한 서비스는 아니죠... 다만 제가 구매한 날은 4월 20일인데, 서비스 날짜가 지났음에도 불구하고 상관 없는 문구가 계속 올라와 있는 것은 혼란을 야기 할 수 있으며 약간의 배아픔도 줄 수 있으므로 얼른 내리시기 바라겠습니다...ㅎㅎㅎ

본품 1세트와 옵션상품인 코랄카샴푸 하나를 장바구니에 담고 결제한게 20일 오후 3시쯤인데, 다음날에 물건이 도착하더군요. 

구성품은 먼저 유리막코팅제(60ml)와 이 것을 덜어 쓸 수 있는 30ml 짜리 빈병(사진은 다 쓰고 난 후 촬영 한 것이라 빈병에 무언가가 조금 남아 있지만 처음엔 깨끗합니다.)



그리고 스펀지 두개. 파란부분은 손잡이고 그 아래 작은 구멍이 숑숑난 검은색 스펀지가 붙어 있습니다. 스펀지가 들어간 비닐백 표면에 레이블지로 안내문이 꼼꼼하게 붙어 있네요.



노란천-극세사입니다- 4장이 들어있습니다. 크기는 동일하고 이걸로 스펀지를 감은 후 유리막코팅제를 콕콕 발라서 차에 바라는 겁니다.



아래는 제가 처음 봤을때 편지 봉투에 무언가 잔뜩 들어가 있길래 무슨 계산서가 이렇게 많을까 했던 것들입니다. 구성품 안내, 구성품 변경 안내, 시공방법 안내, 제품 홍보 안내, 성능 시험결과 보고서 일문, 국문까지 총 A4지 8장입니다. 아, 정말 꼼꼼합니다.



이 제품의 유리막 성분이 10년을 간다는 분석 결과랍니다. 10년동안 유리막코팅의 주성분 Si(규소)의 mass % 가 유지 된다는 얘긴데... 5년차에 팍 떨어졌다가 10년차엔 다시 올라가는데 어떻게 해석해야 하는지 이해 불가! 아무튼 규소성분이 도장면에 오랜시간 유지 된다는 것을 기관의 실험 결과를 통해 검증 받았다는 내용이네요.



요건 경화제랍니다. 유리막코팅을 다하고 나서 유리코팅면을 보호하기 위해 사용한다고 합니다. 경화제라고 한다면 유리막을 더욱 단단히 만든다는 느낌이 있긴 한데 실제론 발수제가 아닌가 싶습니다. 2~3 개월에 한번씩 다시 사용해주는게 좋다고 하네요. 


이외에 사진은 없지만 보증서가 2장 들어있습니다. 유리막 코팅제 한병으로 두대이상 작업이 가능하기 때문에 2개를 넣었다고 합니다.


2. 시공준비

드디어 22일 토요일, 봄비가 내린 다음날, 몇일간 비가 오지 않는다는 예보를 확인하고 아침일찍 셀프세차장으로 가서 차를 구석 구석 깨끗이 닦았습니다. 광택은 내지 말라고 해서 안했고, 물기를 완전히 말리기 위해서 에어건으로 틈새 물기까지 완전히 제거한 후 혹시 몰라 두시간쯤 더 말렸습니다. 

단, 만일 세차만으로 없어지지 않는 흠집이 있으면, 컴파운드 같은거로 없에주시는게 좋을 것 같습니다. 저는 3M 스월마크 리무버를 사용해 눈에 띄는 흠집을 다 제거했습니다.

손세차 하고, 흡집 잡는데 대략 2시간쯤 걸린 것 같고, 물기 말리는데 2시간 더 걸렸으니 총 4시간 걸렸네요. 사실 물기 말리는 2시간은 회사 앞에 있는 글라스틴트 송파점 사장님이랑 점심먹고 얘기한 시간이라 실제로는 2시간 정도 걸린다고 보면 되겠네요.


3. 유리막코팅 시공

본격적으로 유리막코팅을 시작하기 위해 차에 물기가 다 말랐는지 확인하고, 물때가 남은게 있으면 깨끗이 닦아 줬습니다. 

유리막코팅제를 30ml 빈병에 덜은 후 남은 병은 얼른 뚜껑 닫아서 비닐에 담은 후 냉장고에 보관했습니다. (냉장고에 보관하면 오래동안 변하지 않고 나중에 다시 재사용 할 수 있다고 적혀있더군요.)



스펀지를 준비합니다. 저는 코팅제가 너무 많이 사용되지 않을까 하여 스펀지 하나는 반으로 잘라 봤습니반으로 쓰면 좀 더 손이 가지만 더 꼼꼼하게 작업 할 수 있더군요.


설명서에 의하면 스펀지는 그냥 쓰는게 아니고 노란 극세사 천으로 이렇게 감싸서 (위에 옷핀을 꽂아주면 더 좋다고 합니다.) 쓰라고 합니다.


그리고 30ml 병에 든 유리막 코팅제를 아래 사진처럼 잠깐 뒤집었다가 돌리면,


요렇게 유리막코팅제가 묻어나오는데요, 이때 차에 잽싸게 발라줍니다. 단, 노란천을 처음 감쌌을때는 4-5회 이상해서 골고루 적셔줘야 됩니다.



막 바르다 보면 아래 그림처럼 지저분해 지기도 합니다.(세차 한거 맞나 --??) 그럴땐 깨끗한 부분이 밑을 향하도록 다시 감싸주고 위 작업을 반복하면 되겠죠.



스펀지에 적신 유리막 코팅제를 바를때는 다음 요령을 숙지해야 합니다. 

유리막코팅제는 바른 후 20초 전후로 닦아내야 합니다.
유리막코팅제는 공기중에서 자연경화됩니다. 설명서에 의하면 20초 전후가 제일 좋고 아무리 늦어도 1분 이내에 닦아내야 하는데 그렇지 못하면 얼룩이진다고 합니다. 얼룩이란 유리막코팅제가 발린 모양이나 흘러내린 모양이 그대로 굳어버린 것으로 자동차 도장면이 울퉁불퉁 해지는 걸 원하지 않는 분이시라면 꼭 이점을 유의해야 합니다.

아래 사진을 잘 보시면 병의 바닦 부분이 약간 비스듬히 경사져 있는게 보이실겁니다. 유리막코팅제가 조금 남아있는 병을 바닥에 눞혀놨더니 코팅제가 흘러내리며 그대로 경화되어 버렸습니다. 만일 자동차 도장면에 저렇게 경화된게 있다면 정말 큰일이겠죠?



바를 범위를 정합니다.
우선 한판씩 발라야 합니다. 예를들어 본넷->휀다->문짝->트렁크->뒷범퍼->앞범퍼 순으로 한판씩 진행합니다. 

그리고 바를 크기를 일정하게 잡아야 합니다. 본넷처럼 큰 부위는 4등분, 문짝은 2등분, 휀다는 한번에 바르는 식으로 해야 유리막 코팅제를 바르고 닦아내는 시간이 너무 길어지는 걸 예방 할 수 있습니다.



바를때는 일자로, 닦아낼때는 원형으로
유리막코팅제를 바를때는 아래 처럼 한방향으로, 일자로 주욱 펴 바릅니다. 그리고 딱 맞게 바르려다 보면 빈틈이 생길 수 있기 때문에 반씩 겹쳐서 발라줍니다.


닦아낼때는 역시 바른지 20초 전후 시간에 맞추도록 하고 먼저 바른곳 부터 원을 그리며 골고루 닦아냅니다. 너무 힘줄 필요는 없으며 얼룩이 생기지 않도록 한다는 생각으로 가볍게 닦아냅니다.



도장면이 아니어도 유리막코팅은 유효합니다.
플라스틱으로 된 라이트, 금속인 휠, 크롬도금면 부분에도 유리막 코팅은 유효하다고 합니다. 여유가 있으면 빠짐없이 발라줍니다. 다만 휠에는 침투 속도가 느리기 때문에 4-5분 정도 후 닦아내라고 하네요. 단, 유리에는 바르지 마라고 하네요. 유리에 유리막코팅을 하는건 상식적으로도 필요 없는 행동일 것 같습니다.



제일 중요한 사항!!!
작업에 매우 큰 영향을 주는 굉장히 중요한 사항이 있습니다.
햇빛이 너무 강하지 않은 시간에 그늘진 곳에서 작업하는 것이 그것인데요. 왜냐하면 너무 밝은 햇볓 아래서는 유리막코팅제를 발라도, 발렸는지 안발렸는지 잘 안보입니다. 안보이면 바른데를 또 바르게 되거나, 안바른 곳이 생기게 되어 제대로 시공되기 어렵습니다. 특히 밝은 색상 차량이면 더욱 주의 하셔야 할 부분으로 저도 이 사항을 잘 모르고 3시쯤 밝은 곳에서 작업 시작했다가 유리막코팅제가 너무 많이 들어가 유리막코팅제 한통을 다 써버렸습니다.ㅠ_ㅠ

제 경우 유리막코팅하는데 걸린 시간은 약 한시간정도 됩니다. 그런데 체감 시간은 더 걸린 것 같습니다.한병에 10만원이 넘는 액체를 차에 막 바르고 닦아내고 그러다 보니 긴장이 되었던지 시간이 길게 느껴진 것 같습니다.



4. 경화제 시공
유리막코팅제를 다 바르고 나서 30분 경과 후에는 경화제를 뿌려줍니다. 경화제는 유리막코팅을 더욱 오래 갈 수 있도록 보호해준다고 하는데요. 비나 오물로 유리막 성분이 떨어져나가는 걸 막기 위한 발수제가 아닌가 싶습니다. 아무튼 경화제를 유리막코팅했던 부분에 골고루 발라주고, 또 유리에 써도 됩니다. (사실 되는 정도가 아니라 유리에 뿌리면 더 좋습니다. 발수력이 있기 때문에 우천시에 매우 편리하니 전면유리와 사이드 미러에는 꼭꼭 잘 뿌려주세요.)


경화제는 2-3개월 마다 다시 사용해주면 좋다고 합니다. 반정도 남은 것 같으니 3개월 후에 다시 사용해보고 괜찮으면 2년정도 사용 가능한 대용량 제품을 추가로 구매할 생각입니다.

경화제를 바르는 요령은 따로 없습니다. 너무 많이 뿌리지만 말고 물왁스 뿌리고 닦듯이 살살 하면 됩니다. 다 바르는데 걸리는 시간은 대략 20분정도 소요 됩니다.



5. 작업결과 
여기까지 작업 한 결과사진들입니다. 밝은 색상이라 광이 막 나고 그런건 아니지만, 앞으로 차 외장 관리가 용이해질 것을 생각하니 많이 뿌듯하네요~ ^^



예전엔 그저 루마 젤 좋은거가 짱인줄 알았는데... 

요즘엔 루마뿐만 아니라 글라스틴트, 후퍼옵틱 같은 업체도 많이 유명하더군요.

 

그럼에도 불구하고 구매전 확인 할 수 있는 객관적 비교자료가 없어서 

다른 사용자의 경험담에 의지해 구매결정을 내리려니

뭔가 빼먹은듯 쉬이 결정을 못하고 여기저기 기웃기웃하다가 

결국 홈페이지에 공시된 퍼포먼스 데이터를 직접 정리해봤습니다.

 

물론 각 회사의 홈페이지에 올라온 자료를(시험성적 포함) 기준으로 했기에

객관적이라고 보기는 어렵지만 비교할때 참고는 되실 것 같습니다.

 

https://spreadsheets.google.com/ccc?key=0AhkruTPZ8WTEdEFaaWxlbGgyODFqVW9aUEhvWDk5M0E&hl=ko&authkey=CJOJ28EK

 

조사 불가능한건 비워놨고 시공가격도 승용만 했고, 부분시공은 파노라마썬루프만 조사했습니다. 

제가 계속 업데이트하기 어려울 것 같아서 아무나 업데이트 할 수 있게 오픈해뒀으니 잘못된게 있으면 고쳐주세요~^^

 

아무나 업데이트 할 수 있게 했더니 
어느분이 자꾸 지우시는건지 데이터가 남아나질 않네요;;

그냥 열람만 가능하게 했구요. 대신 자유롭게 활용하시기 바랍니다.

 

아 그리고 보실때 참고하실 만한 정보 덧붙힙니다.

 

가시광선투과율 : 숫자가 낮아지면 프라이버시 보호 수준이 올라갑니다..어두워진다는 뜻이죠.

가시광선반사율 : 정확히 모르겠습니다만, 안에서 밖을 볼때 창에 대시보드나 내가 비춰보이는 정도가 아닐까 싶네요.

자외선차단율 : UV는 피부암을 발생시키고 기타 등등 무조건 막는게 좋으니 이건 높을수록 좋습니다.

적외선차단율 : 적외선은 열선이라고도 하죠. 이게 높으면 열차단이 잘 되는 겁니다.

총태양투과율 : 낮을수록 열차단 잘 됩니다고 볼 수 있습니다만, 이보단 총태영에너지차단율이 열차단 성능을 직접적으로 보여준다고 생각됩니다.

총태양에너지차단율 : 루마에서는 열차단의 가장 정확한 근거로 이 수치를 내세우네요. 높을수록 열차단성능이 높답니다.(관련글)

일사취득계수차폐계수 : 용어정의는 인터넷에 잘 나와있구요. 낮을수록 좋은거고 열차단의 가장 중요한 지표라고 합니다.


 

뜻이 정반대인 "I love you" 라는 문자열과 "I hate you" 라는 문자열의 유사도는 어떻게 구할까?

스펠링검사, 표절검사 같은 분야에 사용되어지는 Levenshtein distance algorithm (A.K.A edit distance)을 이용하여 문자열의 유사도를 측정해보자.

1. 원리는 간단하다.
비교 대상이 되는 두 문자열을 각 a, b 라 할 때, 
a를 b로 수정하는데 필요한 문자의 추가, 삭제, 수정 횟수를 덧셈하면 그것이 곧 유사도가 된다..

즉, "I love you"가 "I hate you"가 되기 위해선

I love you 
 ↓↓   
I hate you 

"lov" 세글자가 "hat" 로 수정되야 한다. 
문자바뀜횟수는 3회이고 따라서 레벤시테인 거리 알고리즘에 의한 유사도는 3 이라 볼 수 있다.

유사도는 0이면 완전히 일치하고, 0에서 멀어질수록 유사하지 않다고 볼 수 있다.
단, 의미 상의 유사도가 아님에 유의해야 한다.


2. 알고리즘 구현에 대해선 다른 좋은 포스팅이 있어서 링크로 대신한다.

그리고 내가 필요해서 찾은 T-SQL 구현은 아래와 같으며 저자는 Arnold Fribble, 출처는 sqlteam.com 이다.
CREATE FUNCTION edit_distance(@s1 nvarchar(3999), @s2 nvarchar(3999)) RETURNS int AS BEGIN DECLARE @s1_len int, @s2_len int, @i int, @j int, @s1_char nchar, @c int, @c_temp int, @cv0 varbinary(8000), @cv1 varbinary(8000) SELECT @s1_len = LEN(@s1), @s2_len = LEN(@s2), @cv1 = 0x0000, @j = 1, @i = 1, @c = 0 WHILE @j <= @s2_len SELECT @cv1 = @cv1 + CAST(@j AS binary(2)), @j = @j + 1 WHILE @i <= @s1_len BEGIN SELECT @s1_char = SUBSTRING(@s1, @i, 1), @c = @i, @cv0 = CAST(@i AS binary(2)), @j = 1 WHILE @j <= @s2_len BEGIN SET @c = @c + 1 SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j-1, 2) AS int) + CASE WHEN @s1_char = SUBSTRING(@s2, @j, 1) THEN 0 ELSE 1 END IF @c > @c_temp SET @c = @c_temp SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j+1, 2) AS int)+1 IF @c > @c_temp SET @c = @c_temp SELECT @cv0 = @cv0 + CAST(@c AS binary(2)), @j = @j + 1 END SELECT @cv1 = @cv0, @i = @i + 1 END RETURN @c END

'IT > 데이터베이스' 카테고리의 다른 글

두 문자열의 유사도 측정방법  (0) 2011.03.09

음파칫솔이라 불리우는 전동칫솔.

칫솔모가 초고속으로 진동하면서 발생시키는 음파?가 치아 깊숙한 곳까지 침투해
플라그와 찌꺼기를 깨끗이 제거해준다는데...


작년말부터 한 3,4개월 써본 결과 이건 진리!
☞ 빨리 살수록 좋다는 결론에 도달하였고 지름에 조금이나마 도움이 되고자 포스팅 합니다.



아, 사용소감 간단하게 하자면

-일반 칫솔에 비해 100배 더 깨끗하게 닦이는 것 같음
-회사에서 일반칫솔 쓰는데 도저히 못쓰겠어서 회사용으로 하나 더 살 예정
-처음엔 양치하고나서 치아에 입술이 붙었버렸음
-혀끝에 항상 걸리던 아래 앞니 치석이 지금 대어보니 없어져 있음


정도로 요약 할 수 있겠습니다~

그럼 구매 가이드 아래 나갑니다.

[브랜드]
필립스, 브라운오랄비 두개가 각각 1, 2위(에누리기준)
필립스는 음파가 좀 쎄고 브라운은 부드럽다고들 함(클리앙 의견)
개인적으론 필립스만 써봐서 무슨 차이인지 모르나 처음 썼을때 필립스는 턱뼈가 간지러울 정도로 강력하게 느껴지긴 했습니다.

[필립스(소닉케어) 모델 추천]
HX-67??, HX-69?? 시리즈 추천. 본품 성능은 다 같지만 구성차이에 따라 가격이 막 다르니 자기 환경에 맞게 쓰면 됩니다.
저는 가족이 둘이고 와이프가 소독기 필요하데서 HX-6972 골랐습니다.
혼자였다면 HX-6730 썼을듯

2011.9 추가
한가지 단점이 있습니다. 바로 내구성. 4월쯤에 밤에 이상한 소리가 들려서 욕실에 가보니 이 칫솔이 혼자 진동하고 있더군요. 물에 빠뜨리거나 떨어뜨린 적도 없는데 밤새 혼자 징징 거려서 다음날 필립스 A/S 센터가서 리퍼받아 왔습니다. 10만원도 넘는제품이 1년도 안돼서 이러니 별로 기분이 안좋더군요! 무상A/S 기간 끝나서 또 이러면 어쩌냐고 했더니 그땐 어쩔 수 없다고 그러네요...쩝... 물에 너무 안닿도록 조심하랍니다. 어처구니 없었지만 어쩝니까? 살살써봐야죠. 9월 현재 아직까진 이상 없습니다.



[브라운오랄비(소닉컴플리트, 바이탈리티소닉) 모델 추천]
안써봐서 모르지만 링크 가보면 제품이 몇개 없습니다. 음파칫솔은 원래 필립스가 먼저 했고 잘나가서 브라운오랄비는 라인업이 좀 부실한듯
그래도 싸고 소문도 괜찮아서 이번에 회사에서 쓰려고 S12.513 사려하고 있습니다.

2011.9 추가
이건 비추합니다. 회사에서 쓰려고 샀는데 부드럽다는 평과는 달리 그냥 진동이 매우 약합니다. 필립스와 비교하면 음파칫솔이라하기 민망한 수준으로 전에 마트에서 6,000원 주고 샀던 펄사와 비슷합니다. 샀으니 할수 없이 지금까지 한 6개월 쓰고 있는데, 필립스는 이에 대고만 있는 반면 이거는 진동이 약해 일반칫솔처럼 제 팔을 움직여서 닦습니다. 장점이라곤 아직까지 잔고장 없다는 정도.


[칫솔모]
필립스는 최저가 기준 개당 5000원 이상(링크)
브라운오랄비는 최저가 기준 개당 4000원 이하(링크)

[구매팁]

HX-6972 같은건 아무래도 가격이 조금 부담스럽습니다. 그런데 에누리에서 볼수 있는 가격추세를 보면 어쩌다 한번씩 잠깐 뚝 떨어질때가 있습니다. 가격방어하는 본사 사람들의 눈을 피해 판매자들이 일시적으로 가격을 떨어뜨리는데, 주로 본사사람들의 모니터링이 없는 연휴때나 저녁시간에 잠깐씩 하는 것 같습니다.(클리앙 정보) 저도 실제로 워터픽 20만원짜리 모델을 연말 저녁시간에 한곳에서만 15만원에 파는걸 구매해서 이득 봤습니다.(인증)

  1. 나그네 2011.09.26 12:20

    구경 잘했습니다... 일반칫솔보다 백배 나은 정도라면, 하나 구입하는게 낫겠네요. 흐흐...
    이가 고른 편인데도 치과가니까 칫솔질하기 쉽지 않은 구조라고 하고. 지금 최소한 어금니 하나가 맛이 간것 같은데. 역시 미리 예방하는게 최선이네요. ㅠㅠ

    • yjacket 2011.09.26 16:13 신고

      도움이 되셨는지 모르겠습니다. ^^
      칫솔질 어려운 구조?시면 워터픽도 한번 써보세요.
      어떤 사람은 삼겹살 먹은 후에 양치했는데도
      워터픽했더니 고기조각이랑 상추조각이 후두둑 떨어지더라고 하더군요.

넘어가도 되는 이야기

웹 서핑을 하다 보면 시간 가는 줄 모르기 십상입니다. 매일 같이 새롭게 쏟아지는 정보를 탐닉하다 보면 해놓은 일 없이 어느새 시간이 훌쩍 지나가 버리기 때문에 잔여 업무 처리를 위해 매일같이 야근하기도 하구요. 이제 그만 봐야지 하고 용기 내어 브라우저를 닫아보기도 하지만 자료 찾으러 검색 좀 하다 보면 어느새 또 삼천포로 빠져버리곤 합니다.

저도 한번 호기심이 발동하면 편집증적으로 관련 정보를 섭렵하는 습관 때문에 업무시간에 집중하지 못하고 시간을 낭비한 적이 많습니다. 습관이 쉬이 고쳐질리 만무하다 싶어, 자구책으로 제 컴퓨터에 localhost 파일을 수정해 시간낭비의 주범인 몇 개 사이트들을 등록해 놓고 아에 접속 하지 못하게 만들어 놓고 써보니 제법 통제가 되는듯 했습니다만, 곧 새로운 사이트에서 시간을 낭비하고 있더군요…ㅠ_ㅠ localhost 를 수정하는게 좀 귀찮은 일이라 몇개 사이트까지는 더 추가했지만 계속 그 방식을 통해 사이트를 차단관리하기는 생각보다 힘들었습니다.

그래서 주로 사용하는 브라우저인 크롬 확장 중 제가 원하는 기능이 있는지 검색해 왔었는데, 한동안 발견 못하다가 드디어 오늘 마음에 쏙드는 확장을 찾았습니다.

 

StayFocusd 소개

StayFocusd, 지정한 사이트들에 머무른 시간이 총 ?분이 넘을 때 다음날까지 해당 사이트들에 접근을 차단하는 확장입니다. 설치 할 수 있는 주소는 아래 있습니다.

https://chrome.google.com/extensions/detail/laankejkbhbdhmipfmgcngdelahlfoji?hl=ko

 

주요기능으로는

  • 최대 허용시간 지정
  • 차단 요일 지정
  • 차단 시간대 지정
  • 차단할 사이트 지정
  • 허용할 사이트 지정
  • 머무른 시간 타이머 초기화 시각 지정
  • 핵폭탄 기능!
  • 설정 변경을 어렵게 하는 기능!
  • 허용시간을 다 쓰면 타이머 초기화 될때까지 절대로 차단을 풀어주지 않기!

등이 있고, 다음으로 기능별 상세 화면을 보여드리겠습니다.

 

1. 최대 허용 시간

image

차단할 사이트로 등록한 사이트들에 접근한 시간의 합이 여기서 지정한 시간을 초과하면 해당 사이트들은 모두 접근이 불가능합니다.

 

2. 차단 요일지정

image

저는 회사에서만 이 차단 기능을 이용할 것이기 때문에 월~금요일로 설정했습니다.

 

3. 차단 시간대 지정

image

역시 근무시간대에만 차단을 실행하게 했습니다.

 

4. 타이머 초기화 시각

image

0시가 젤 무난하겠죠?

 

5. 차단할 사이트

image

차단하고 싶은 사이트를 씁니다. 엔터로 구분하구요, 맨 아래 버튼을 누르면 추가됩니다.

 

6. 허용할 사이트

 image

차단 주소 아래 있거나, 뒤에 설명할 핵폭탄 타임에도 특별히 허용하고 싶은 사이트가 있으면 여기 써주세요.

 

7. 핵폭탄 기능

image

재미있는 기능인데요, 지정한 시간 동안 아무것도 안 열리게 하는 겁니다. 완전 집중모드로 일해야 할 때 좋겠네요. 허용한 사이트만 열리게 하거나, 완전히 아무것도 안 열리게 하는 옵션이 있습니다.

 

7. 설정 변경을 어렵게 하는 기능

image

네, 요것도 참 재미있는 기능입니다. 사람 마음이 간사해 차단해 놓고서도 설정을 살짝 풀고 싶을때가 있죠. 이 옵션을 켜두면 설정변경하기 전 장문의 텍스트를 입력하도록해 설정변경을 힘들게 합니다. 바꾸려고 하다가도 “아 귀찮아 그냥 안봐!” 하고 포기하도록 유도하는 기능이죠. 이 기능을 활성화하면 설정 변경하려 할때 다음 화면이 뜹니다.

image

정말 귀찮겠죠?ㅎㅎ

 

마지막으로 실제 사용 모습을 보여드릴게요.

image

매뉴 버튼이 나오는 자리 엎에 아이콘이 StayFocusd 아이콘이 나오고, 그걸 클릭하면 위 화면이 나옵니다. 현재 사이트 정보와 남아있는 허용시간, 사이트 차단 여부, 설정등의 링크가 나옵니다. 저는 현재 허용시간을 다 쓴 상태인데 남아있는 허용시간이 0으로 나오지 않고 좀 이해하기 어려운 숫자가 나오네요. 버그인지는 아직 모르겠습니다만 작동에는 지장 없습니다.

 

마지막으로 차단화면입니다. 일 안하냐는군요…ㅋㅋ

image

그리고 허용시간을 다 쓴 상태에서 차단 사이트에 접속하면 다음날이 되기 전까지는 계속 이 화면만 나옵니다. 설정도 변경 할 수 없고 그냥 꼼짝 없이 일해야 합니다.ㅎㅎ

  1. jhc 2010.12.16 16:12

    클리앙에서 보고 왔습니다. 저에게 많이 필요한 팁이었어요. 감사합니다 :)

  2. adea 2011.04.04 03:47

    그런데 크롬 쓰다가 시간 다되면 IE쓰게 됩니다 ㅠㅠ

  3. lisnt 2011.10.27 15:14

    여기서 소개받고 이거 아~주 잘 쓰고 있는 인턴입니다...ㅎㅎㅎ이렇게 훌륭한데다가 꽁짜인데 왜 쓰는 사람이 적은지 모르겠네요!!

  4. 굿굿 2011.11.07 01:11

    좋은글엔 리플을^^

  5. 정말좋네요 2011.11.19 13:24

    ㄳㄳ!

  6. 감사합니다 2012.01.21 13:31

    이제 웃대랑 디씨좀 접고 공부좀해야지;;

  7. 이웅채 2012.02.10 01:51

    잘 읽었습니다. 보기 좋게 잘 작성하신듯 하네요...

    제 블로그에 좀 퍼가겠습니다. 굽굽

  8. 이웅채 2012.02.10 02:00

    아.. 근데 이거 쓰다보니 느낀점이 있습니다.

    1. 설정 변경시에 나오는 문구 입력칸의 기준이 너무너무 엄격합니다. 오타 하나만 쳐도 다시 처음부터 시작하네요. 한 5~6번 시도하다가 개 짱나서 그냥 포기했습니다.

    2. 문제는 이런 경우에 그냥 플러그인 자체를 삭제하게 된다는 거죠. 삭제 후 재설치가 훨씬 시간이 조금 걸리니 말입니다. 그런데, 이렇게 하다보면 플러그인 삭제한 뒤에 깜빡하고 재설치를 안할 수도 있는데, 이는 플러그인의 기본 취지와 위배됩니다.

    3. 설정 변경과정을 복잡하게 한 것은 이해합니다만, 1.정도로 빡시게 해놓으면 사람들이 2.처럼 꼼수를 부리게 마련이겠죠. 굉장히 매력적인 플러그인은 맞습니다만, 개선의 여지가 있는 듯합니다. 오타정도는 봐주는게 맞죠.. 3글자 남기고 처음부터 다시쓸때의 그기분 아십니까... 참고로 저는 제한사이트 하나 추가하려고 설정 바꾸려는 거였는데...;;

  9. ㄹㅇ 2012.03.07 13:22

    크롬 사이트 차단 검색하다 왔어요
    아예 접근 자체를 못하게 하니 암호 거는것보다 더 효과적이네요
    고맙습니다 ㅋㅋㅋㅋㅋ

  10. 민재 2012.06.30 03:04

    이거 아무짝에도 쓸모없지않나요 그냥 도구-확장프로그램 가서 사용안함해버리면 전혀 소용이 없잖아요 그걸 못해야 의미가 있을텐데

    • yjacket 2012.07.02 16:02 신고

      오해하신거 같아요!
      이건 회사에서 업무외 사이트를 통제할려구 쓰는게 아니고
      자기 스스로 절제하기 위해 사용하는거거든요.
      그런식으로 옵션을 꺼버릴게 걱정된다면 아에 깔지를 않으면 더 쉽겠죠. ㅎㅎ

  11. 토마쓰 2012.08.25 16:49

    유용한 정보입니다 감사합니다 !

  12. 우드 2013.04.07 21:14

    좋은정보 감사합니다!

  13. 2013.04.09 17:44

    검색해서 찾아 왔습니다. 이거 좀 웃기네요 ^^;; 역시 사람들이 하는 고민은 다 비슷한가봅니다.

  14. hagnod 2013.05.24 17:12 신고

    좋은 정보네요 ^^
    깔아서 써 볼려구요! ㅎㅎ

  15. Aristo 2013.10.02 06:47

    깔았는데 정말 좋네요. 감사합니다.

  16. goolbi 2013.12.31 14:17

    좋은 정보네요. 감사합니다. ^^

  17. 자몽주스 2014.04.10 00:47

    항상 크롬쓰는 유저인데, 뉴스랑 웹툰에 시간을 하도 많이써서,, 뉴크, 핵폭탄모드로 쓰는데, 꽤 좋네요. 좋은 포스팅 감사드립니다.

  18. chromeuser 2015.01.07 03:28

    유용한 정보 정말 감사합니다!

1. insert 전에는 추가할 레코드의 기본키가 무엇이 될 지 모르는 경우 selectKey 를 이용해 추가된 레코드의 기본키를 반환 받아 확인한다.

iBATIS 2.x 의 insert 메서드는 selectKey 와 함께 쓰일 경우 기본키 객체에 추가된 레코드의 기본키를 넣어 반환한다.

ProductDao.java

public class ProductDao
{
    SqlMapClient sqlMapClient;

    public boolean insertProduct(Product product)
    {
        Integer id = (Integer) sqlMapClient.insert(“insertProduct”, product);
        return id > 0; // product.productId 에도 같은 값이 저장됨
    }
}

Product.xml

<insert id="insertProduct">
    <selectKey keyProperty="productId" resultClass="int" type="pre">
        SELECT S_Products.NEXTVAL FROM DUAL
    </selectKey>

    INSERT INTO Products VALUES (#productId#, #productName#, #productCode#)
</insert>

 

2. insert 전에 추가할 레코드의 기본키를 알고 있을때는?

본키를 이미 알고 있다면, 기본키를 이용한 성공여부 판단이 불가능하기 때문에 insert 구문을 update 메서드로 실행하여 영향 받은 행수를 평가한다.

ProductDao.java

public class ProductDao
{
    SqlMapClient sqlMapClient;

    public boolean insertProduct(Product product)
    { 
        int rows = sqlMapClient.update(“insertProduct”, product);
        return rows > 0;
    }
}

Product.xml

<insert id="insertProduct">
    INSERT INTO Products VALUES (#productId#, #productName#, #productCode#)
</insert>

 

3. 신뢰할 만하다고들 하는 또 다른 방법

예외를 잡는 것 만으로도 상당히 신뢰할만한 성공여부 판단을 할 수 있다고들 한다. 단, 개인적으로는 왠지 찝찝하다. 가급적이면 1, 2 방법으로 판단하고 도저히 방법이 없을 경우에 시도해보도록 하자. 또, 프로시저, 함수를 이용해 DBMS 에게 성공여부를 판단하도록 하는것도 한 방법이 될 수 있다.

A, B 두개의 작업이 있다.

두 작업은 같은 SqlMapClient 객체를 통해 DB에 엑세스 한다.

A 는 10000개의 insert를 일괄처리하고, B 는 1개의 insert를 처리하는데,

만일 A가 트랜잭션중일때 B가 실행되어 예외가 발생한다면 A 작업이 롤백 될까?

 

다음은 테스트코드다.

 

 

이 테스트 코드의 실행결과, 데이터베이스에는 success() 메서드의 결과는 insert 되어 있었지만 error1, error2 메서드의 실행결과는 빠져있었다.

즉, A메서드가 실행중일때 B메서드에서 예외가 발생하여도 A 작업은 문제없이 실행된다.

iBATIS 2.x 는 대량의 insert, update, delete 작업을 효율적으로 처리 할 수 있는 방법으로 startBatch(), executeBatch() 메서드를 제공한다.

개발자 가이드에 의하면 네트워크 트래픽 최소화, JDBC 드라이버의 추가적인 최적화(압축같은) 같은 혜택을 통해 성능향상을 볼 수 있다고 한다.

startBatch(), executeBatch() 메서드는 문서상으로 명시적 transaction 과 함께 사용해야 한다고 나오지는 않았으나
테스트시에는 명시적 transaction 을 사용하지 않았을때는 일반작업과 동일하거나 나쁜 결과를 보였다.

1. 특징

UPDATE 문 1000번 반복 처리 속도 테스트 결과
1) 배치+트랜잭션 : 77ms
2) 트랜잭션 : 1104ms
3) 일반작업 : 4274ms
4) 배치 : 4604ms

다음은 "UPDATE Computers SET ..." 문을 배치로 10번 실행하는 부분의 디버그 로그다.



그리고 다음은 같은 UPDATE 문을 배치하지 않고 트랜잭션만 걸린 상태로 10번 실행한 부분의 디버그 로그다.



배치 적용 유무에 따른 속도 차이는 상황에 따라 많이 다르지만 위 테스트에선 대략 5~7배 정도 차이를 보였다.

위 테스트에서 트랜잭션까지 빼면 디버그 로그는 다음과 같이 나온다.



위 테스트는 트랜잭션만 건 테스트에 비해 3-5배정도 느리게 나왔다.
트랜잭션없이 배치만 걸면 위 테스트와 비슷하거나 더 느린 결과를 보였다.

2. 장점
동일유형의 작업을 빠르게 처리 한다. 테스트시 최대 50배이상 빠르게 실행되는 경우도 있었다.

3. 단점
작업실패시 모든 배치가 롤백된다.
- 롤백하지 않고 실패한건만 무시하고 계속 진행하고 싶은 경우엔 해결 방법이 없다.
문제가 발생했을때 어디서 발생했는지 알기 어렵다.
- executeBatch() 메서드는 성공한 업데이트 수를 반환한다. 단, insert, update, delete 에 한한다.queryForObject 의 수행결과는 알 수 없다
- executeBatchDetail() 메서드는 더욱 상세한 모든 배치작업결과를 반환한다. 하지만 역시 insert, update, delete 에 한한다.
- 또 작업중 예외가 발생하면 배치예외를 던지는데, 어떤 문장(statement)에서 예외가 발생했는지는 보이지만 동일한 문장을 배치작업으로 여러개 수행하는 경우라면 어느 부분에 문제가 발생했는지 알 수 없다.

위와같은 한계로 배치 중 오류발생시 무시하고 다음으로 진행하고 싶을땐 배치할 작업을 저장해두고 있다가 예외를 잡아 일반작업으로 넘겨 처리해줘야 하는 불편이 있다.
※ 배치예외, 배치결과객체 분석이 충분하지 않아 단점으로 지적한 부분에 문제가 있을수도 있다.

*4. 결론*
동일작업을 대량 반복할때 배치, 트랜잭션은 성능을 대폭 향상시킨다.
배치예외가 빈번히 발생하는게 아니라면, 예외시 일반작업 처리를 위한 대비 코드를 사용하는 비용을 치르더라도 배치를 쓰는게 좋다.


결과
ArrayList - 넣기 : 38ms
ArrayList - 빼기 : 36851ms
HashMap - 넣기 : 170ms
HashMap - 빼기 : 17ms
ConcurrentLinkedQueue - 넣기 : 156ms
ConcurrentLinkedQueue - 빼기 : 17ms
LinkedList - 넣기 : 41ms
LinkedList - 빼기 : 9ms
ArrayBlockingQueue - 넣기 : 32ms
ArrayBlockingQueue - 빼기 : 22ms
ArrayDeque - 넣기 : 17ms
ArrayDeque - 빼기 : 5ms
LinkedBlockingQueue - 넣기 : 47ms
LinkedBlockingQueue - 빼기 : 32ms
LinkedBlockingDeque - 넣기 : 100ms
LinkedBlockingDeque - 빼기 : 24ms
PriorityBlockingQueue - 넣기 : 68ms
PriorityBlockingQueue - 빼기 : 188ms
SynchronousQueue - 넣기 : 7ms
SynchronousQueue - 빼기 : 4ms
PriorityQueue - 넣기 : 16ms
PriorityQueue - 빼기 : 164ms

테스트코드

  1. 공학코드 2015.08.14 09:59 신고

    성능 테스트 정보 감사합니다!!!

최근 프로젝트에 Log4J 를 Logback 으로 교체하고, 코드 전반적으로 로그를 적용하게 되면서 코드를 대량으로 수정 할 일이 생겼는다. 보통은 Refactoring이나 File Search 의 replace 기능을 이용하는데 이 작업을 하기엔 불편한 점이 많아서 다이나믹 임포트를 이용해 logger 선언을 편리하게 해주는 이클립스 템플릿을 만들어봤다. 

이 템플릿을 이용하면 logger까지 치고 CTRL+SPACE 키를 누를때 로거 선언과 클래스 임포트가 한번에 된다.

Window > Show View > Templetes 으로 Templetes view 를 연 후 다음과 같이 템플릿을 하나 추가하자

타이핑 하기 귀찮은 분은 다음 텍스트를 copy & paste!

private static final Logger logger = LoggerFactory.getLogger(${enclosing_type}.class);
${:import(org.slf4j.Logger,org.slf4j.LoggerFactory)}${cursor}

이제 이클립스 자바 에디터에서 logger, CTRL+SPACE, ENTER 로 보다 손쉽게 Logger를 선언 할 수 있다.

웹서버를 포함한 솔루션 배포시에는 데이터베이스 연결 설정 파일을 포함시키기 어렵다. 사용자의 DB접속 환경이 어떨지 알 수 없기 때문이다. 그렇다고 서버 셋업시에 사람이 일일이 설정파일을 만지는게 하는것도 불안하다.

보통은 어플리케이션 설치시에 정보를 입력받아 속성파일로 써주거나, 웹사이트가 구동되고 나서 DB설정을 먼저 하도록 한다. 그런데 문제는 Spring 이 dataSource 객체를 생성하는 방식이다.

 

applicationContext.xml

   1: <bean id="propertyConfigurer"
   2:     class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   3:     <property name="locations" value="WEB-INF/jdbc.properties" />
   4: </bean>
   5:  
   6: <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   7:     <property name="driverClassName" value="${jdbc.driverClassName}" />
   8:     <property name="url" value="${jdbc.url}" />
   9:     <property name="username" value="${jdbc.username}" />
  10:     <property name="password" value="${jdbc.password}" />
  11: </bean>

Spring 은 이처럼 applicationContext.xml 파일로 properties 파일의 위치를 정의하고 dataSource bean에서 이를 참조하도록 해서 웹서버 시작시에 로딩한다. 그런데 만일 웹서버 시작시에 이 파일이 없거나 속성을 읽어오지 못했다면 서버가 시작조차 되지 않는다.

데이터베이스 연결 속성 파일이 없거나 올바르지 않아도 웹서버가 정상적으로 시작되게 하기 위해서는 PropertyPlaceholderConfigurer 클래스의 ignoreUnresolvablePlaceholders 속성을 true 로 만들어주면 되는데, 이렇게 설정하기 위해서 위 코드의 location 속성 정의 아래 다음 내용을 추가 한다.

   1: <property name="ignoreUnresolvablePlaceholders" value="true" />

Spring – iBATIS 사용시 보통 WEB-INF/jdbc.properties 파일에 데이터베이스에 접속하기 위한 설정을 저장해 둔다.

데이터베이스 접속 환경이 변경되어 이 파일을 수정한후에는 웹서버를 재시작해줘야 변경된 설정이 반영된다만, 웹서버를 재시작하지 않고, 또 jsp 를 쓰지 않는 – flex & blazeds 만으로 구동되는 웹 – 환경에서 변경된 설정을 바로 반영 해줄 수 있는 방법이 필요해졌다.

방법을 요약하면, 스프링으로부터 WebApplicationContext를 받아와 dataSource bean 객체를 받아온 후 값을 변경해주면 된다.

예제는 아래..

applicationContext.xml

   1: <bean id="webConfigService" class="webConfig.service.WebConfigService" autowire="byType"/>

동적로딩을 구현 할 클래스의 bean 설정에 autowire 를 true 로 설정

 

WebConfigService.java

   1: public class WebConfigService
   2: {
   3:     /**
   4:      * 웹 애플리케이션이 실행중인 경로를 가져오기 위해 WebApplicationContext 를 스프링으로부터 가져와 저장한다.  
   5:      */
   6:     private WebApplicationContext webApplicationContext;
   7:     
   8:     /**
   9:      * 스프링 프레임웍에서 이 쓰기 전용 메서드에 WebApplicationContext을 셋팅한다.
  10:      * @param w
  11:      */
  12:     @Autowired
  13:     public void setWebApplicationContext(WebApplicationContext w)
  14:     {
  15:         webApplicationContext = w;
  16:     }
  17:     
  18:     public void reloadDataSource(String driverClassName, String url, String username, String password)
  19:     {
  20:         BasicDataSource bds = (BasicDataSource) webApplicationContext.getBean("dataSource");
  21:         bds.setDriverClassName(driverClassName);
  22:         bds.setUrl(url);
  23:         bds.setUsername(username);
  24:         bds.setPassword(password);
  25:     }
  26: }

스프링에 의해 WebConfigService 클래스 setWebApplicationContext 메서드가 호출되며 WebApplicationContext 가 셋팅된다.

dataSource 의 값을 변경해주고 싶으면 reloadDataSource 메서드를 쓰면 된다. 설정내용은 즉시 반영 되는듯.

  1. 신용호 2010.08.09 16:24

    너무너무 감사합니다.
    이글 덕분에 해결했습니다.
    Tomcat의 경우 글처럼 하면 되는데
    이상하게 Jeus에선 bds.close()를 먼저 해야 되네요.

    • yjacket 2010.08.17 18:09 신고

      도움되셨다니 다행입니다! JEUS 는 안써봐서 모르겠지만. 톰캣도 bds.close() 먼저 해주는게 좋겠네요~~ ^^

하려는것

1) Flex 에서 RemoteObject 를 이용해 blazeds 서버의 메서드를 호출하고,
2) 서버는 RemoteClass 로 등록되어있는 객체를 반환하면
3) Flex 가 이를 받아 View 에서 볼 수 있도록 바인딩 한다.

예제1) 간단한 모델

Java

   1: public class Config
   2: {
   3:     private String text;
   4:  
   5:     public String getText()
   6:     {
   7:         return text;
   8:     }
   9:  
  10:     public void setText(s:String)
  11:     {
  12:         text = s;
  13:     }
  14: }

Flex
   1: package com.myapp.model.config
   2: {
   3:     [RemoteClass(alias="com.myapp.model.Config")]
   4:     
   5:     [Bindable]
   6:     public class Config
   7:     {
   8:         private var _text:String;
   9:  
  10:         public function get text():String
  11:         {
  12:             return _text;
  13:         }
  14:  
  15:         public function set text(s:String):void
  16:         {
  17:             _text = s;
  18:         }
  19:     }
  20: }

예제1은 특별히 설명할게 없다. 저렇게 하면 알아서 리모트 클래스로 처리되어 속성을 참조 할 수 있다.

플렉스에서 리모트 클래스로 등록한 Config객체는 text 가 속성으로 되어 있는데

RemoteObject 로 가져올때 자바객체의 getText(), setText() 메서드를 알아서 불러와 text에 집어 넣어준다.

 

예제2) 복잡한 모델

Java

   1: package com.myapp.model;
   2:  
   3: import com.myapp.model.BaseDataModel;
   4:  
   5: public class Config extends BaseDataModel
   6: {
   7:     private int seq;
   8:  
   9:     public int getSeq()
  10:     {
  11:         return seq;
  12:     }
  13:  
  14:     public void setSeq(int i)
  15:     {
  16:         this.seq = i;
  17:     }
  18:  
  19:     public int getInterval()
  20:     {
  21:         return getInt("interval", 60);
  22:     }
  23:  
  24:     public void setInterval(int i)
  25:     {
  26:         setInt("interval", i);
  27:     }
  28: }
   1: import java.util.HashMap;
   2:  
   3: public abstract class BaseDataModel
   4: {
   5:     /**
   6:      * "key1=value1&key2=value2& ... " 와 같은 문자열을 key&value 쌍으로 저장하고 있는 멤버  
   7:      */
   8:     private HashMap<String, String> dataHashMap = new HashMap<String, String>();
   9:     
  10:     /**
  11:      * @return
  12:      * dataHashMap의 요소를 "key=value& ... " 형식 문자열로 만들어 반환한다.
  13:      * 이 메서드는 오버라이드 할 수 없다. 
  14:      */
  15:     public final String getData()
  16:     {
  17:         return DataUtil.getDataString(dataHashMap);
  18:     }
  19:     
  20:     /**
  21:      * "key=value& ... " 형식 문자열을 파싱하여 dataHashMap 에 저장한다.
  22:      * 문자열 전달인자는 파싱되기 전 멤버변수인 data 에 저장된다.
  23:      * 이 메서드는 오버라이드 할 수 없다.
  24:      * @param data
  25:      */
  26:     public final void setData(String data)
  27:     {
  28:         dataHashMap = DataUtil.getDataHashMap(data);
  29:     }
  30:     
  31:     /**
  32:      * int 타입의 값을 문자열로 변환해 dataHashMap 에 저장한다.
  33:      * @param key
  34:      * @param value
  35:      */
  36:     protected void setInt(String key, int value)
  37:     {
  38:         dataHashMap.put(key, Integer.toString(value));
  39:     }
  40:  
  41:     /**
  42:      * dataHashMap 에서 key 에 해당하는 값을 참조해 int 형으로 반환한다.
  43:      * 참조한 값이 null 이면 0을 반환한다.
  44:      * @param key
  45:      * @return
  46:      */
  47:     protected int getInt(String key)
  48:     {
  49:         return Integer.getInteger(dataHashMap.get(key), 0);
  50:     }    
  51: }

 

Flex

   1: package com.myapp.model
   2: {    
   3:     [RemoteClass(alias="com.myapp.model.Config")]
   4:     
   5:     [Bindable]
   6:     public class Config
   7:     {    
   8:         private var _seq:int;
   9:         private var _interval:int;
  10:         private var _data:String;
  11:         
  12:         // ---------------------------------------------------
  13:         // Properties
  14:         // ---------------------------------------------------
  15:         public function get seq():int
  16:         {
  17:             return _seq;
  18:         }
  19:         
  20:         public function set seq(i:int):void
  21:         {
  22:             _seq = i;
  23:         }
  24:         
  25:         public function get interval():int
  26:         {
  27:             return _interval;
  28:         }
  29:         
  30:         public function set interval(i:int):void
  31:         {
  32:             _interval = i;
  33:         }
  34:         
  35:         public function get data():String
  36:         {
  37:             return _data;
  38:         }
  39:         
  40:         public function set data(s:String):void
  41:         {
  42:             _data = s;
  43:         }
  44:     }
  45: }

예제2의 경우를 동작시키기 위해 한 12시간은 쓴거 같다. 플렉스의 객체는 예제1과 동일한 형식이지만 속성이 2개 더 있다. 새로 생긴 2개의 속성은 자바단에서 구현이 예제 1과 다르다.

자바 객체는 다음 특징을 가진다.

1) BaseDataModel을 상속
2) seq는 예제1의 경우와 동일하며 public getter & setter 메서드가 seq 라는 private 변수를 단순 참조하는 전형적인 형태.
3) data 는 부모객체의 구현이며 public getter & setter 메서드가 dataHashMap 이라는 private 변수에 복잡한 연산을 통해 참조하는 형태
4) interval 은 public getter & setter 메서드가 부모객체의 메서드를 이용해 dataHashMap 을 참조하는 형태

이러한 상황을 구현하면서 매핑할 자바 클래스가 부모가 있는 클래스라고 해서 Flex 의 클래스 상속구조가 동일 할 필요는 없다는 사실을 확인했고, interval 과 data 와 같이 전형적인 구조가 아닌 속성을 매핑할때도 어렵게 생각 할 필요가 없었다.

시간을 12시간이나 소모시킨 주범은 다름아닌 데이터형이었다. 자바단에서 getInterval 의 리턴타입이 처음엔 Integer 로 되어 있었는데, 자바단에선 틀림없이 올바른 값이 저장되어 있었지만 플렉스로 넘어오면 무조건 0으로 바뀌는 것이다. 처음엔 리모트 클래스가 이런 유형, 즉, 상속된 객체라던지, getter & setter 에 해당하는 private 변수가 없는 경우를 지원하지 않는 줄 알고 별 방법을 다 동원해보았지만 해결되지 않았다. 모든 방법을 다 시도해봤다고 생각했을 무렵 설마 하고 Interger 로 정의되어 있던 리턴타입을 int 로 바꿨더니 거짓말 처럼 값이 넘어오더라--

새벽까지 이 코드 붙들고 하도 고생을 해서 나중에 잊어 먹고 성질 내지 않도록 남겨 놓는다.ㅎㅎ

자바 프로그램을 작성중 웹으로부터 전달받은 임시파일을 데몬이 읽어 처리 하도록 해야 할 일이 생겼다. 임시파일의 적당한 위치를 찾기 위해 자바 어플리케이션상에서의 현재 경로가 어떻게 되는지 궁금해 다음 코드를 짜서 확인해보았다.

   1: // Java Application
   2: // d:\workspace\daemon\src\test.java
   3: File file = new File(“.”);
   4: System.out.println(file.getCanonicalPath()); // d:\workspace\daemon\
   5:  
   6: // Dynamic Web Project
   7: // d:\workspace\web\webContent\test.jsp
   8: // d:\apache-tomcat-6.0.14
   9: <%
  10: java.io.File file = new java.io.File(".");
  11: %>
  12: <%=file.getCanonicalPath()%> // d:\apache-tomcat-6.0.14\bin\
  13:  
  14:  
  15:  

요약하면,

자바 어플리케이션은 프로젝트 홈이 현재 경로가 된다.
다이나믹 웹 프로젝트는 톰캣 실행파일 경로가 현재 경로가 된다.(단, 이건 직접 아파치 톰캣을 실행했을떄 얘기고, 이클립스에서 실행 했을 경우는 이클립스 홈이 현재 경로가 된다.)

But, 위 실험 결과와는 상관없이 업로드 경로는 System.getProperty(“java.io.tmpdir”) 로 해 볼 예정. 근데, 윈도에서는 System.getProperty(“java.io.tmpdir”) 뒤에 \ 가 붙는데, 리눅스에선 안붙어서 검사하는 코드도 추가해야되겠다…쩝… 크로스플랫폼 지원하기가 쉽지 않네.

이클립스(3.5기준)는 컬러셋팅 가져오기를 지원하지 않습니다. 저 처럼 블랙테마를 즐겨쓰는 사람들은 워크스페이스를 바꿀때 마다 매번 컬러 셋팅을 다시해줘야 한다는게(그것도 에디터별로!) 상당히 번거로운 일인데요, 수작업으로 이를 편리하게 하는 방법이 있어서 링크합니다.

http://srand2.blogspot.com/2009/08/eclipse-color-themes.html

간단히 요약하면 다음 두 위치에 있는 파일을 바꿔주면 된다는 얘기구요, 자기가 원래 쓰던 파일을 가져다 덮어쓰면 됩니다.

[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.jdt.ui.prefs
[workspace]\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.ui.editors.prefs

원래 쓰던 파일이 없으면(처음 셋팅하는거면) 위 링크에서 몇가지 자바용 테마의 다운로드도 제공하니 받아서 써보세요. 저는 Sula가 맘에 들더군요. 다만 xml 에디터의 색상이 잘 안보여서 그건 직접 수정했습니다. (Preferences > XML > XML Files > Editor > Syntax Coloring)

단, xml 에디터 컬러셋팅은 위 두 파일에 저장 안되고 같은 경로에 있는 org.eclipse.wst.xml.ui.prefs 파일에 저장되니 참고하시기 바랍니다.

http://ibatis.apache.org 의 5/21 공지에 따르면, iBATIS 프로젝트 팀이 구글 코드로 옮기겠답니다.

이유는 구글코드가 지원이 더 좋고 더 잘 할 수 있을것 같기 때문에라는 것 같구요.

창시자인 Clinton Begin이 Apache 재단에 이름과 코드를 기부했기 때문에, 이름도 MyBATIS 로 바꾼다네요.

iBATIS 3.0 GA 까지 나왔던 상황에 그 코드를 그대로 들고 MyBATIS 로 가기 때문에

이름만 바뀌고 호환성은 계속 유지 된다고 합니다.

젤 중요한 라이선스도 변화가 없을거구요. 소스코드도 완전히 호환되어질거라고 합니다.

패키지 이름과 네임스페이스의 변화도 당분간 없을거라고 하지만 앞으론 어찌 될지 모르겠군요.

MyBATIS 는 현재 3.0.1 GA가 릴리즈 되어 있고, 조만간 2.3.5 GA 도 릴리즈 할거랍니다.

참고 : http://mybatis.org

프로그래밍을 하다보면 어떤 로직을 실행하는데 실행시간이 얼마나 걸리는지 궁금 할 때가 있는데 다음 코드로 간단히 doSomething() 함수를 실행하는데 걸리는 시간을 화면에 출력 할 수 있다. 같은 기능을 하지만 서로 다르게 구현 한 각 로직의 성능을 시험해보고 싶을때 활용해보자. 실행하는 동안은 시스템을 건드리지 말아야 정확한 측정 결과를 구할 수 있다.

   1: long l = System.currentTimeMillis();   
   2: for (int i = 0; i < 10000; i++)  
   3: {  
   4:     doSomething();  
   5: }  
   6: System.out.println(System.currentTimeMillis() - l);  

+ Recent posts