admin write
blogblogblogbloglocation loglocation logtag listtag listguest bookguest book
rss feed

Error:Error:line (13)cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element  ...


프로젝트 완료후 실서비스를 위해 테스트 하던중 위와같은 오류를 만났다

오류내용을 자세히 뜯어보니 Spring프레임워크의 xml 네임스페이스와 schemaLocation 설정에

문제가 있는듯 했다 

그런데 생각해보니 실서비스를 하는 WAS서버가 내부망으로 묶여 내부망으로의 서비스만을 위한

서버로 인터넷을 연결하지 않는 서버였다.

반나절을 구글링을 해보니 spring-flex integration 라이브러리가 dtd관련한 이슈가 많이 검색되어 

보였다


일반적인 schemaLocation 예시

xsi:schemaLocation="

        http://www.springframework.org/schema/beans 

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"


스키마의 해당 xsd 주소를 찾아들어가 브라우져의 "다른 이름으로 저장" 기능등을 통해 

xsd 를 다운받는다.


절대경로로 설정한 schemaLocation 예시 

(참조가 필요할때 해당 URL이 아닌 내부 파일시스템에서 참조)

xsi:schemaLocation=" 

        http://www.springframework.org/schema/flex             

        file:///C:/Workspace/LTEB/WebContent/WEB-INF/lib/spring-flex-1.5.xsd"


※ schemaLocation 설정시 함께 설정하는 xml 네임스페이스(xmlns)와 URL이 한쌍이다. 






tomcat-jdbc로 MySQL에 연결한 경우 "java.net.SocketException: Broken pipe" 에러가 발생하는 경우를 가끔 볼 수 있다. 대부분의 경우 connection idle time이 MySQL에 설정된 wait_timeout을 지나서 DB에서 연결을 끊은 것이다. 이를 방지하려면 connection이 일정 기간동안 사용되지 않으면 close되도록 설정하거나 connection 대여시에 connection을 체크하도록 하면 된다.


설정 1 - 일정 기간동안 사용되지 않으면 connection을 테스트

validationQuery="SELECT 1"

testWhileIdle="true"

minEvictableIdleTimeMillis="3600000"

timeBetweenEvictionRunsMillis="60000"


(*) DB에 쿼리를 하기 때문에 이 때마다 DB의 session idle time이 갱신된다. 만약 connection이 이미 끊겼다면 에러가 발생할테고 해당 connection은 pool에서 제거된다.


(*) timeBetweenEvictionRunsMillis의 기본값은 5000 (5초)이고, minEvictableIdleTimeMillis의 기본값은 60000 (60초)이다. minEvictableIdleTimeMillis 값은 MySQL에 설정된 wait_timeout이나 방화벽에 설정된 session timeout 값보다 작아야 한다.


설정 2 - connection 대여 시 테스트

validationQuery="SELECT 1"

testOnBorrow="true"


(*) 매번 connection 대여 시 마다 체크하는 것은 아니다. 체크 한 connection은 validationInterval 기간 안에는 다시 체크하지 않는다. validationInterval의 기본값은 30000 (30초).


설정 3 - 일정 기간동안 사용되지 않으면 connection을 close

minIdle="0"


설정 1을 사용한 JDBC Resource 설정 예

 context.xml

 <?xml version="1.0" encoding="UTF-8"?>

<Context>

<Resource name="jdbc/testDB" auth="Container" type="javax.sql.DataSource"

factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"

driverClassName="com.mysql.jdbc.Driver"

url="jdbc:mysql://localhost/smartconnect?useUnicode=true&amp;characterEncoding=utf8"

username="username" 

password="password" 

initialSize="10" 

minIdle="10"

maxIdle="50" 

maxActive="50" 

maxWait="5000"

validationQuery="SELECT 1"

validationInterval="30000"

testWhileIdle="true" />

</Context>


tomcat-jdbc PoolCleaner 동작

PoolCleaner thread는 timeBetweenEvictionRunsMillis 만큼 sleep하다 idle/abandoned connection을 체크한다.

  • removeAbandoned="true"이면 abandoned connection 체크
    • connection이 대여된 후 removeAbandonedTimeout (초, 기본값 60)안에 반납되지 않으면 connection을 pool에서 제거하고 close한다.
  • pool의 idle connection 개수가 minIdle보다 크면 체크
    • idle connection 개수가 minIdle이 될 때 까지 마지막 사용시간이 minEvictableIdleTimeMillis (기본값 60000)을 지난 idle connection을 찾아 pool에서 제거하고 close한다.
  • testWhileIdle="true"이면 idle connection들에 대해 validation 테스트
    • 전체 idle connection에 대해 validationQuery에 설정된 SQL을 수행해서 Exception이 발생하면 pool에서 제거하고 close한다.


MySQL wait_time 설정값 확인 방법 (단위: 초)

mysql>select @@global.wait_timeout;

+-----------------------+

| @@global.wait_timeout |

+-----------------------+

|                 28800         |

+-----------------------+

1 row in set (0.00 sec)



[출처] Tomcat7 tomcat-jdbc 설정 - Broken pipe 에러 회피|작성자 소프  

http://blog.naver.com/clotho95?Redirect=Log&logNo=140142861915






maxActive - 연결할 커넥션의 최대 갯수

maxIdle - 풀에 대기시킬 커넥션의 최대 갯수

minIdle - 풀에 대기시킬 커넥션의 최소 갯수 

  (0은 놀고있는 커넥션을 풀에 간직하지 않고 모두 close함을 뜻한다)


당연히 maxActive 값보다 maxIdle 값은 적어야 하며

maxIdle 보다 minIdle 값은 또 적어야 한다.


minIdle 을 0이상 잡을 경우

DB의 일방적인 세션 끊김을 방지하는 차원으로

testWhileIdle="true" 의 validation 속성을 추가로 설정하는것이 좋다.


ParameterDefaultDescription
validationQueryThe SQL query that will be used to validate connections from this pool before returning them to the caller. If specified, this query MUST be an SQL SELECT statement that returns at least one row.





좀 느려서 그렇지 그래도 암호/복호화가 필요하면 많이 쓰이는 기법이지요

어서 줏어듣기로는 키를 모르면 일일이 푸는데 계산상 100년이걸린다고 합니다;

-----------------------------------------------------------------------------------------
package AES;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AES {    
    // 128 비트는 키가 16글자, 192, 256비트는 각각 24, 32글자임
    public static String key = "wtfrudoinganpang";

    /**
     * hex to byte[] : 16진수 문자열을 바이트 배열로 변환한다.
     * 
     * @param hex    hex string
     * @return
     */
    public static byte[] hexToByteArray(String hex) {
        if (hex == null || hex.length() == 0) {
            return null;
        }

        byte[] ba = new byte[hex.length() / 2];
        for (int i = 0; i < ba.length; i++) {
            ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return ba;
    }

    /**
     * byte[] to hex : unsigned byte(바이트) 배열을 16진수 문자열로 바꾼다.
     * 
     * @param ba        byte[]
     * @return
     */
    public static String byteArrayToHex(byte[] ba) {
        if (ba == null || ba.length == 0) {
            return null;
        }

        StringBuffer sb = new StringBuffer(ba.length * 2);
        String hexNumber;
        for (int x = 0; x < ba.length; x++) {
            hexNumber = "0" + Integer.toHexString(0xff & ba[x]);

            sb.append(hexNumber.substring(hexNumber.length() - 2));
        }
        return sb.toString();
    } 

    /**
     * AES 방식의 암호화
     * 
     * @param message
     * @return
     * @throws Exception
     */
    public static String encrypt(String message) throws Exception {

        // use key coss2
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");

        // Instantiate the cipher
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

        byte[] encrypted = cipher.doFinal(message.getBytes());
        return byteArrayToHex(encrypted);
    }

    /**
     * AES 방식의 복호화
     * 
     * @param message
     * @return
     * @throws Exception
     */
    public static String decrypt(String encrypted) throws Exception {

        // use key coss2
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");

        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] original = cipher.doFinal(hexToByteArray(encrypted));
        String originalString = new String(original);
        return originalString;
    }
    
    public static void main(String[] args)
    {
        try {
            String encrypt = encrypt("test1234");
            System.out.println("origin str = "+"test1234");
            System.out.println("encrypt str = "+encrypt);
            
            String decrypt = decrypt(encrypt);
            System.out.println("decrypt str = "+decrypt);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}


 

<sqlMap namespace="board">
<typeAlias alias="Board" type="board.BoardVO"/>

<sql id="board.where">
<!-- searchValue값이 비어있지 않다면 -->
<isNotEmpty property="searchValue">
<!-- parameterClass의 searchKey값이 "all" 과 같다면 -->
<isEqual property="searchKey" compareValue="all">
WHERE title LIKE #searchValue#
OR content LIKE #searchValue#
</isEqual>
<isEqual property="searchKey" compareValue="title">
WHERE title LIKE #searchValue#
</isEqual>
<isEqual property="searchKey" compareValue="content">
WHERE content LIKE #searchValue#
</isEqual>
</isNotEmpty>
</sql>

<select id="board.getList" parameterClass="Board" resultClass="Board">
SELECT *
FROM board
<include refid="board.where" /> <!-- board.where 를 여기에 삽입한다 -->
</select>

</sqlMap>


위에 보면 #searchValue# (검색내용) 와 #searchKey# (검색항목) 가 있죠?

로직부분에서 두 값을 담아 올 수 있게
BoardVO에 searchValuesearchKey 속성을 추가하고 Get/Set메소드를 만들어 줍니다
만약 정렬(orderValue)이나 페이징(firstRow, lastRow ) 이 필요할때도 VO에 추가해주면 됩니다

DAO 메소드

public List getList(BoardVO vo) throws SQLException {
return sqlMapper.queryForList("board.getList", vo);
}



JSP페이지에 검색부분은 이런식일테고
<select name="searchKey" >
<option value="all">제목+내용</option>
<option value="title">제목</option>
<option value="content">내용</option>
</select>
<input type="text" name="searchValue" />
<input type="submit" value="검색" />

자바 소스에 추가되는 내용은 이정도겠죠?
vo.setSearchKey(request.getParameter("searchKey") );
vo.setSearchValue(request.getParameter("searchValue") );
List list = dao.getList(vo);

[출처] http://slog2.egloos.com/3572364



PS. iBatis와 Spring 으로 구성을 하여 프로젝트를 하면서

DB의 테이블 디자인이 UI를 고려하지 않은 너무 데이터 수집위주로 디자인된경우

이런 동적쿼리를 많이 쓰게 되었는데

개발당시에야 익숙해지면 어느정도 개발이 되지만

추후 문제가 생겨 수정하려하니 하나 고치면 다른 한곳에서  오류나고 ;;  그러다보니

include 를 빼고 수동으로 다 입력..

또 그러다보니 어느순간에 이건 모두가 다 동적쿼리가 아니고.. 아...

동적쿼리를 쓰게 만드는 구조나 패턴은 최대한 배제해야 하고

만일, 정말 동적쿼리가 많이 쓰이게되는경우

최고의 방법은 iBatis에서 include의 사용을 완전히 배재하고

아주 스탠다드하게 isEqual, isNotEqual 로 1차적인 if 들을 걸러 동적쿼리를 어느정도 구현하고

나머지는 java 에서 짜집기 하는것이었음

그게 더 짧은 소스를 생성해 원하는것을 얻을수 있고

더 가독성이 뛰어나고,

더 유지보수가 용이함

이클립스에서 VisualEditor 플러그인을 설치하여 사용해보았습니다

(http://www.ehecht.com/eclipse_ve/ve.html)

설치는 간단한.. 그저 자신의 이클립스버젼에 맞는 링크를 다운받아

이클립스 설치 폴더에 덮어쓰기로 추가하시면됩니다.

그런후 이클립스에서 새로운 프로젝트를 보면 Visual Class 가 표시됩니다.

간단한 Swing 윈도우 어플을 만들어보았는데

VE가 상당히 깨집니다 ㅠ 영문은 모르겠지만;

콘트롤을 추가할때 AWT, Swing 콘트롤 클래스를 입맛에 맞게 골라 사용할수 있습니다.

AWT 콘트롤로만 구성을 하면 안깨질지도 모르겠지만;;

사용자 삽입 이미지
Visual Class의 편집화면 상단에 윈도우폼이 처음엔 안깨졌는데 ㅠ 깨졌다 안깨졌다를 반복하는;;

뭐 흉하지만 크게 불편하진 않습니다. 어차피 VE가 없을때는 일일이 머릿속에서 상상하며 그렸으니..



일반 자바클래스의 편집화면.. 뭐 항상보는..



자바로 윈도우 어플을 개발하면 참 이로운점중 하나가

JDBC를 이용한 오라클DB연결입니다.

이때는 여타 응용프로그램 개발언어들과는 틀리게 오라클클라이언트가 필요없으니

바로 2-Tier의 DB연결이 가능합니다.

하지만 VE의 이런 노력에도 불구하고

GUI 프로그램은 왠만하면 자바 Swing 프로젝트로 안하시는게 정신건강에 이롭습니다.

그저 집에서도 쓸만한 급할때 오라클클라이언트 없이 DB에 붙기위해 간단한 어플제작할 일이 있어

오랫만에 사용을 해보았지만..

정말 이걸로 프로젝트는 하기싫은..;;

VSmooth 로 이클립스에서 Jar로 Export후 EXE컴파일을 할수 있습니다.

말이 컴파일이지 그저 배포하기 좋은형태로 구성하는정도..

EXE 생성은 별문제가 없습니다.

JDK 의 최소버젼 설정.. 어플의 분류 설정(콘솔인지, 윈도우용인지 서비스인지등)

그리고 저처럼 JDBC등을 사용한다면 별도의 Jar을 Embed 할수도 있습니다.