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

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

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

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



두 경우 모두 결과 값이 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 을 구별하기로 정해진 이후에도 하위 호환성을 위해 열어둔 것 같다고 함.
뜻이 정반대인 "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

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 파일에 저장되니 참고하시기 바랍니다.

프로그래밍을 하다보면 어떤 로직을 실행하는데 실행시간이 얼마나 걸리는지 궁금 할 때가 있는데 다음 코드로 간단히 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);  

오래전부터 데이터베이스 스키마 변경(버전)을 관리하기 위해 간단한 툴을 만들어 보고 싶었지만,
만들 시간이 별로 없기에.. 오늘은 관련 툴을 찾아봤다.

역시 시간이 없는 관계로 공부는 나중에..ㅎ

테이블에 랜덤한 dummy 데이터를 여러개 insert 하고 싶을때

SELECT a.cnt, trunc(a.cnt / 10), DBMS_RANDOM.value()
FROM (SELECT LEVEL cnt FROM DUAL CONNECT BY LEVEL < 10000) a

1. 날짜 더하기 빼기

오라클은 SQLServer의 dateAdd 함수가 없다. 대신 + / – 연산자를 이용해 일자를 더하고 뺀다.

select to_date('2009-10-10') - 60 from dual
---------------------------
2009. 8. 11 오전 12:00:00 

또는 add_months(date, n) 함수를 이용해 달을 더하거나 뺀다.

select add_months('2009-10-10', -1) from dual
------------------------------
2009. 9. 10 오전 12:00:00

시간은 ‘날짜 + 숫자/24’ 와 같은 방식으로 더하고 뺀다.

select to_date('2010-03-01') + 1/24 from dual
-----------------------------
2010. 3. 1 오전 1:00:00        

 

2. 날짜간 차이 구하기

오라클은 SQLServer의 dateDiff 함수도 없다. 대신 + / – 연산자를 이용해 일수의 차를 구할 수 있다.

select to_date('2010-03-02') - to_date('2010-03-01') from dual
----------------------------------------------
1                                             

달수 차는 months_between(date, date) 함수를 통해 구한다.

select months_between('2010-03-02', '2009-10-01') from dual
--------------------------------------------
5.03225806451612903225806451612903225806    

정수부분이 달 수의 차이. 비정수부분은 달수 계산하고 남은 나머지 부분이다.

 

3. 기타

LAST_DAY('2010-02-01')   
-------------------------
2010. 2. 28 오전 12:00:00  

ROUND(TO_DATE('2010-02-0111:32:40','YYYY-MM-DDHH24:MI:SS'),'DAY')   
--------------------------------------------------------------------
2010. 1. 31 오전 12:00:00                                

ROUND(TO_DATE('2010-02-0111:32:40','YYYY-MM-DDHH24:MI:SS'),'Q')   
------------------------------------------------------------------
2010. 1. 1 오전 12:00:00

TRUNC(SYSDATE,'MI')    
-----------------------
2010. 3. 18 오후 12:38:00

회사 메일을 google apps 로 변경하면서, redmine 의 메일 알림기능 설정을 변경해야 되었다.
원래 gmail  이 smtp 메일 발송을 지원하기 때문에 별 문제 없이 끝날 줄 알았는데 예상치 못한 문제가 있었다.
그것은 gmail 을 통한 smtp 메일 발송시 TLS 암호화가 필요하다는 것.
설정파일에서 그런 옵션이 없기 때문에 안되는 줄 알았지만, 구글에서 손쉽게 답을 찾을 수 있었다.

참고 한 링크는 아래 두개..

http://redmineblog.com/articles/setup-redmine-to-send-email-using-gmail/
http://kingori.egloos.com/4236006

0.8.7.stable 기준 설정 방법은 다음과 같다.

1. TLS 플러그인 설치

1-1. 레드마인 서버에서 인터넷에 접속 할 수 있는 경우, 레드마인 설치 경로에서 다음 명령을 실행 하면 끝.

ruby script/plugin install git://github.com/collectiveidea/action_mailer_optional_tls.git

1-2. 레드마인 서버에서 인터넷에 접속 할 수 없는 경우, 다음 URL 에서 소스를 다운로드 받아, 레드마인 설치 경로/vendor/plugins/action_mailer_optional_tls 에 압축을 풀어 넣으면 끝.

http://github.com/collectiveidea/action_mailer_optional_tls

image

 

2. email.yml 설정

redmine_mail

박스 친 부분이 포인트… 구글 앱일 경우 도메인은 자신의 도메인을 써도 무방하지만, smtp.gmail.com을 써도 관계 없다.
authentication 은 참고한 기사에 따르면 plain 을 쓰라고 하는데, login 이라고 놔둬도 아무 문제 없었다.

 

3. 테스트

설정을 다 마쳤으면 (레드마인 최상단) 관리자 > 설정 > 메일 알림으로 이동.
그리고 우하단 테스트 메일 보내기 링크를 클릭하면 즉시 테스트 결과가 나타난다.
만일, 설정이 잘못 되었다면 상단 테스트 결과에 에러메시지가 나와서 문제 해결에 참고 할 수 있다.

image

  1. 김기원 2010.02.19 09:08

    주인장님의 글을 참고해서 Redmine을 적용 했습니다.
    GMail 세팅시 위와 같이 했는데 저는 적용이 안되더라구요.
    그래서 제가 성공한 방법을 엮어 놓고자 합니다.
    제 테스트 환경은 아래와 같고
    Windows Server 2003 Standard R2 SP2 Kor(Windows Server 2008 Standard R1 SP2)
    Redmine : 0.9.2
    MySQL : 5.1.43
    Ruby : 1.8.7-p249
    Rails : 2.3.5
    Ruby Gems : 1.3.5
    W/S : Apache 2.2.13(in VisualSVN Server), mongrel

    해당 포스트 주소입니다.
    http://blog.naver.com/xyz37/50083113439

Windows 7 을 설치한 이후 내 컴퓨터에서 직접 배포본을 컴파일 하지 않고 있다가, 오늘 컴파일 해야 할 일이 있어서 ant 로 플렉스 컴파일을 시도했으나 “Could not find a jvm” 이라는 에러 메시지를 만나게 되었다.

Error: could not find a JVM.

Error: could not find a JVM.

%JAVA_HOME% 설정이 잘못된 듯 하여 환경변수를 수정해보았다.

image

C:\Program Files (x86)\Java\jre6\bin –> C:\Program Files\Java\jdk1.6.0_17로 변경.
하지만 이번엔 Error: could not find a JVM. 에러 대신 Error loading: c:\Program Files\Java\jdk1.6.0_17\jre\bin\server\jvm.dll 에러가 발생했다.


처음 보는 메시지라 일단 구글 검색을 시도 했더니 두 가지 정도로 해결 방법이 제시 되어 있었다.

  • jvm.config 파일을 수정
  • %JAVA_HOME% 에서 역슬래시(\) 문자 대신 슬래시(/)를 쓰고, 긴 파일이름(LFN) 대신 도스 이름(C:\PROGRA~1)을 쓸 것

두가지 방법을 다 사용해봤지만 별 소용이 없었다.

원인 재분석

처음 만난 에러메시지(Error: could not find a JVM.)는 eclipse 에서 ant 를 이용해 mxmlc를 실행하는 상황에서 mxmlc가 토해내는 메시지다. 근데 왜 오류가 났을까?

1. eclipse는 java 를 이용해 ant 를 실행하고 ant task에서 mxmlc 를 실행한다.
2. mxmlc 는 시스템 환경변수 %JAVA_HOME% 을 참조하여 java 를 실행하고 소스코드를 컴파일 한다.

의문점은 “1번의 java 는 문제 없는데 2번의 java 만 문제가 있을까?” 라는 부분.

하지만 다시 생각하니 문제는 간단했다.
eclipse 에서 ant를 실행하는 java 환경은 eclipse가 제공하는 환경이고
mxmlc가 실행되는 java 환경은 윈도우가 제공하는 환경이다.

즉, 2번에서 문제가 발생하는 것으로 봐서 윈도우의 환경변수 %JAVA_HOME% 가 잘못 설정 된 것이 원인이다.

오류메시지에 대해 생각해보자..

Error loading: c:\Program Files\Java\jdk1.6.0_17\jre\bin\server\jvm.dll 에러 메시지는 왜 나타났을까.
왜 로딩에 실패했다고 나올까… 혹시 mxmlc 가 32bit응용프로그램이라 64bit 자바를 못 쓰는건가? 그럼 자바를 다시 설치해보자.

http://java.sun.com을 방문해 32bit JDK 를 내려 받고 설치.

환경변수 %JAVA_HOME%을 다시 설정하고, mxmlc 를 실행하니 헉~ 잘된다.

결론

mxmlc를 사용하기 위해선 32bit JDK가 필요하다.

+ Recent posts