일을 하다보면 다른 소유자(계정)간에 협업을 할 때 데이터 제공 등의 목적으로 View를 생성, 제공해 주는 경우가 종종있습니다. 그리고 애써 View를 구성해서 제공해 주었는데 부득이하게 등의 이유로 제공받은 쪽에서 데이터를 업데이트를 해야하는 경우가 있습니다. 이때 원본 테이블을 제공해 줄 수 없는 경우 또는 원본 테이블이 여러개인 경우(대부분 View를 생성하는 경우가 원본이 여러개인 경우일 듯 합니다...) 등의 이유로 난감한 경우가 발생하기도 합니다. (제 경우에는 늘어나는 업무량에 '그냥 확 원본 테이블을 제공하자!'가 항상 머릿속을 맴돌지만, 원본 테이블들을 제공하고 업데이트 시 데이터/업무 흐름이나 컬럼 플래그 케이스 등을 설명하고 문서를 작성하는게 업무량은 훨씬 더 많이 늘어나기에... 포기했습니다.)


이런 난감한 경우에 View에 Trigger를 연결해서 원본 테이블의 데이터를 업데이트 하도록 할 수 있습니다. 즉, 데이터/업무 흐름등에 맞춰 Trigger를 생성하고 View에 연결하면, View를 제공받는 쪽에 원본 테이블을 알려 줄 필요없이 View에 Update를 실행하는 것만으로도 원본 테이블에 대한 데이터를 수정할 수 있게 됩니다. 실은 저도 View에 업데이트 구문이 실행된다는 사실은 이번에 알게 됬습니다. Trigger 없이 실행한 경우 실행만 되고 실제 원본 데이터가 업데이트 되진 않더군요.


'업데이트 요청 및 백업 용'같은 성격의 테이블을 만들어 이력을 남기도록 Trigger에 함께 추가 해 놓으니 협업할 때 검증 용으로 괜찮았습니다.


아래는 간단하게 테스트 해 볼 수 있도록 한 예제입니다.
아참, 제공받는 소유자(계정)에 대해서 View를 Update할 수 있는 권한을 부여해 주어야 한다는 것을 잊지 마세요.

1. 원본 테이블(Origin Table) 및 View의 생성

View를 업데이트 할 때 수정 될 원본 테이블(Origin Table)과 View를 생성하고, 원본 테이블(Origin Table)의 데이터를 넣어 줍니다.

-- Origin Table을 생성합니다.
CREATE TABLE ORIGIN_TABLE (
  SEQ NUMBER
, FLAG CHAR(1)
);

-- Origin Table을 기준으로 하는 View Table을 생성합니다.
CREATE VIEW ORIGIN_DATA_VIEW (
  SEQ
, FLAG
) AS
    SELECT SEQ, FLAG FROM ORIGIN_TABLE;

-- 이 테이블은 원본에 대한 백업과 요청 이력을 남겨 보려 생성하는 테이블 입니다.
CREATE TABLE REQ_HISTORY (
  SEQ NUMBER
, FLAG CHAR(1)
, REQ_DATE DATE
);

-- 원본 테이블(Origin Table)에 데이터를 넣어 줍니다. 
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 1, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 2, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 3, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 4, 'S' );

2. View에 연결되어 작동하는 Trigger 생성

이제 View에 연결되어 작동하는 Trigger를 생성합니다. 일반 Trigger 생성과 같이 Insert, Update, Delete에 대한 처리를 해 줄 수 있습니다만 여기서는 Update만 하도록 하겠습니다.

-- Update시 작동하는 트리거를 작성합니다.
CREATE OR REPLACE TRIGGER TRI_UPDATE_VIEW
    INSTEAD OF UPDATE --(OR INSERT OR DELETE) 여기 INSTEAD가 중요합니다.
    ON ORIGIN_DATA_VIEW
    REFERENCING NEW AS NEW OLD AS OLD
    FOR EACH ROW

BEGIN
    -- 원본 테이블을 업데이트 합니다.
    UPDATE ORIGIN_TABLE SET FLAG = :NEW.flag WHERE SEQ = :NEW.seq;

    -- 원본 백업 및 이력을 남겨 봅니다.
    INSERT INTO REQ_HISTORY(SEQ, FLAG, REQ_DATE) VALUES (:NEW.seq, :OLD.flag, SYSDATE);

EXCEPTION
    WHEN OTHERS THEN RAISE;

END TRI_UPDATE_VIEW;

3. 작동에 대한 테스트

이제 실제로 View에 업데이트를 실행 해서 원본 테이블(Origin Table)의 데이터가 변경 되었는지 확인해 봅니다.

-- VIEW를 UPDATE 해 봅니다.
UPDATE ORIGIN_DATA_VIEW SET FLAG = 'R' WHERE SEQ = 1;
UPDATE ORIGIN_DATA_VIEW SET FLAG = 'X' WHERE SEQ = 1;


-- 갱신이 되었는지 원본 테이블(Origin Table)과 VIEW를 조회 해 봅니다.
SELECT * FROM ORIGIN_TABLE;
SELECT * FROM ORIGIN_DATA_VIEW;


.Net framework v4.0, Visual Studio 2010, asp.net MVC3 프로젝트 기준으로 진행된 내역입니다.

Asp.Net MVC 중에 _Layout.cshtml에서 각 페이지 렌더링 될 때 사용될 기본적인 룰을 정리하던 도중 RenderSection처리하는 부분이 너무 번거로운 것 같아 '좀 내 입맛대로 해보자!'하는 취지에서 찾아보고 진행했던 내용입니다.

WebViewPage클래스를 상속받는 클래스 생성

RenderSection은 MSDN을 찾아보면 System.Web.WebPages.WebPageBase클래스에 구현되어 있으니 WebPageBase클래스를 상속받은 클래스를 생성해도 되지만, WebViewPage클래스에 구현되어 View 페이지(*.cshtml)에서 사용되는 유용한 속성, 메서드들은 사용할 수 없거나, 다시 구현해야 하기 때문에 WebViewPage클래스를 상속받는 클래스를 생성합니다.

클래스를 생성하는 위치는 적절하게 선택하면 되며, 여기서는 WebViewPageHandler 클래스로 진행하도록 하겠습니다.
제가 원하는 RenderSection의 기능이 <script ...></script>를 필요에 따라서 삭제하고, 추가로 jquery에서 사용되는 $(document).ready...로 시작되는 부분을 필요에 따라서 삭제할 수 있도록 하는 부분이어서 아래와 같이 구현하였습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Web.Mvc;
using System.Web.WebPages;
using System.Text.RegularExpressions;

namespace 네임스페이스 명
{
    public abstract class WebViewPageHandler<T> : WebViewPage<T>
    {
        public override void InitHelpers()
        {
            base.InitHelpers();
        }

        /// <summary>
        /// 레이아웃 페이지에서 명명된 섹션의 콘텐츠를 렌더링하고 섹션이 필수인지를 지정합니다. 
        /// <para>또한 스크립트 블럭 테그와  Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제할지 지정합니다.</para>
        /// </summary>
        /// <param name="name">렌더링할 섹션입니다.</param>
        /// <param name="required">섹션을 필수로지정하려면 true이고, 그렇지 않으면 false입니다.</param>
        /// <param name="isRemoveScriptBlock">상단에 스크립트 블럭 시작Tag 와 끝Tag를 삭제하려면 true, 그렇지 않으면 false입니다.</param>
        /// <param name="isRemoveDocReady">Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제하려면 true, 그렇지 않으면 false입니다.</param>
        /// <returns>렌더링할 HTML 콘텐츠입니다.</returns>
        public HelperResult RenderSection(string name, bool required, bool isRemoveScriptBlock, bool isRemoveDocReady)
        {
            String renderString = NoNull.ToString(base.RenderSection(name, required), string.Empty);

            #region 객체에 대한 유효성 검사를 실시합니다. 
            if (string.IsNullOrEmpty(renderString) && !required)
            {
                // 필수지정이 되어 있지않고, 구현 된 Section 내용이 없다면 null을 리턴합니다.
                // cshtml에서는 아무런 내용도 작성되지 않습니다.
                return null;
            }
            else if (string.IsNullOrEmpty(renderString) && !required)
            {
                // 필수로 지정되어있으나 구현 된 Section 내용이 없다면 오류로 생각해야 합니다.
                // 때문에 MSDN 오류 처리에 맞춰 오류를 발생시킵니다.
                throw new HttpException("렌더링할 섹션을 찾을 수 없습니다.");
            }
            #endregion

            // 상단의 <script ...> 와</script> 항목을 삭제합니다.
            // 내용물은 삭제하지 않습니다.
            if (isRemoveScriptBlock)
            {
                renderString = Regex.Replace(renderString, @"<script[^>]*>", "");
                renderString = Regex.Replace(renderString, @"<\/script>", "");
            }

            // Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제합니다.
            if (isRemoveDocReady)
            {
                renderString = renderString.Replace(@"$(document).ready(function () {", "");
                int index = renderString.LastIndexOf(@"});");
                renderString = renderString.Substring(0, index);
            }

            HelperResult result = new HelperResult(__razor_helper_writer =>
            {
                // 아래 WriteLiteralTo 메서드를 써야 "나 '문자가 변환되지 않습니다.
                WriteLiteralTo(__razor_helper_writer, renderString);
            });

            return result;
        }
    }
}

Web.config 수정

Web.config 파일은 프로젝트 내에 2개가 존재하는데 여기서 수정하는 파일은 View폴더 밑에 있는 파일입니다. 내용 중에 <system.web.webPages.razor>노드 하위에 <pages>노드를 찾아 다음과 같이 수정해 줍니다.

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

<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>
  
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.1, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <!--<pages pageBaseType="System.Web.Mvc.WebViewPage">-->
    <pages pageBaseType="네임스페이스 명.WebViewPageHandler">  <!-- 이 부분을 상황에 맞게 수정합니다. -->
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

_layout.cshtml 파일의 수정

_layout.cshtml에서 위에 overload된 RenderSection을 사용하는 부분입니다.

<!DOCTYPE html>
<html>
<head>
    <title>홈페이지에 오신걸 환영합니다.</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" lang="ko" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />

    <link href="@Url.Content("~/Content/layout.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/jquery-ui/themes/flick/jquery-ui-1.10.4.flick.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/menu_top.css")" rel="stylesheet" type="text/css" />
    @if (IsSectionDefined("PageDefineCssBlock")) { @RenderSection("PageDefineCssBlock") }
                                                                                        
    <script src="@Url.Content("~/Scripts/jquery-1.10.2.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery-ui-1.10.4.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/menu_top.js")" type="text/javascript"></script>
    @if (IsSectionDefined("PageDefineScriptBlock")) { @RenderSection("PageDefineScriptBlock") }
       
    <script type="text/javascript">
        $(document).ready(function () {
            $('#logOut').button().css('font-size', '0.8em');
            @RenderSection("PageDefineDocReadyBlock", false, true, true)   // overload된 RenderSection
        });

        @RenderSection("PageDefineFunctionBlock", false, true, false)    // overload된 RenderSection
    </script>
</head>

<body>

....

아래는 View 페이지 내역으로 PageDefineDocReadyBlock블럭에서는 위에서 구현한 부분과 같이 <script ...>부터 $(document).ready(function () {, </script> 부분이 제거된 상태로 _layout.cshtml파일에 삽입됩니다. 또한 PageDefineFunctionBlock블럭에서는 <script ...>, </script> 부분이 제거되어 삽입 됩니다.

....
@{ 
    Layout = "~/Views/Shared/_Layout.cshtml"; 
}

@*@section PageDefineScriptBlock { }*@

@section PageDefineCssBlock {
    <!-- 페이지 선언 CSS Block -->
    <link href="@Url.Content("~/Content/login.css")" rel="stylesheet" type="text/css" /> 
}

@section PageDefineDocReadyBlock {
    <script type="text/javascript">
        $(document).ready(function () {
            $("#Id_user").focus();
            $("#login_form").submit(function () {
                var inputId = $("#Id_user").val();
                var inputPw = $("#Pw_user").val();

                if (inputId.length === 0) {
                    alert("ID를 입력해주세요.");
                    $("#Id_user").focus();
                    return false;
                }

                if (inputPw.length === 0) {
                    alert("비밀번호를 입력해주세요.");
                    $("#Pw_user").focus();
                    return false;
                }
            });
        });
    </script>
}

@section PageDefineFunctionBlock {
    <script type="text/javascript">
        function test() {
            alert('test function입니다.');
        }
    </script>
}
....

마지막으로 실제 실행 된 HTML의 소스 부분입니다.

<!DOCTYPE html>
<html>
<head>
    <title>홈페이지에 오신걸 환영합니다.</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" lang="ko" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />

    <link href="/Content/jquery-ui/themes/flick/jquery-ui-1.10.4.flick.css" rel="stylesheet" type="text/css" />

    <!-- 페이지 선언 CSS Block -->
    <link href="/Content/login.css" rel="stylesheet" type="text/css" /> 
                                                                                        
    <script src="/Scripts/jquery-1.10.2.js" type="text/javascript"></script>
    <script src="/Scripts/jquery-ui-1.10.4.js" type="text/javascript"></script>
                                                                                              
    <script type="text/javascript">
        $(document).ready(function () {
            
    
        
            $("#Id_user").focus();
            $("#login_form").submit(function () {
                var inputId = $("#Id_user").val();
                var inputPw = $("#Pw_user").val();

                if (inputId.length === 0) {
                    alert("ID를 입력해주세요.");
                    $("#Id_user").focus();
                    return false;
                }

                if (inputPw.length === 0) {
                    alert("비밀번호를 입력해주세요.");
                    $("#Pw_user").focus();
                    return false;
                }
            });
        
        });

         
    
        function test() {
            alert('test function입니다.');
        }
    

    </script>
</head>

<body>
    <!-- Content Start -->

...

참고 페이지

아래 작성한 내용은 Windows에 Tomcat을 서비스로 등록하여 구동하는 것을 전제로 구성된 내용입니다.

1.Apache Tomcat 내려받기

우선 아래 그림 처럼 아파치 톰캣 홈 페이지에서 Binary 형태(.zip)의 Tomcat을 내려받습니다.


Windows 환경에서는 '32-bit Windows zip'(또는 64-bit Windows zip)항목을 클릭해서 내려받기 합니다.
(그 외의 항목에서는 Windows Service에 등록하는 배치파일이 없을 수 있습니다.)

2.Apache Tomcat 설치

거창하게 설치라고 했습니다만 ... Binary 형태(.zip)로 내려 받았기 때문에 설치라는 행위를 할 필요는 없습니다. 내려받은 파일을 적절한 위치(원하는 위치)에 압축을 푼 뒤, 압축을 풀어 나온 파일(폴더)를 복사하고, 이름을 변경하여 붙여넣기 합니다.

폴더 명은 용도에 따라서 적절하게 변경하면 되나 가급적 한글은 사용하지 않는 것이 좋습니다. 글에서는 'tomcat1', 'tomcat2'로 구분해서 사용합니다.

3.설정 변경

3.1.tomcat2항목의 conf디렉터리 파일 중 server.xml파일을 열어 port항목이 존재하는 부분의 내용을 다르게 분리해 줍니다.

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
 
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
 
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"  redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>
 
      <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
 
    </Engine>
  </Service>
</Server>

위 코드에 기본적으로 설정 된 prot, redirectPort 부분은 tomcat1을 구동하면 사용되는 포트이므로 수정 없이 tomcat2를 실행하면 포트 충돌이 발생하거나 tomcat2 종료 시 tomcat1까지 함께 종료되는 등의 문제가 발생할 수 있습니다. 때문에 강조된 부분을 모두 변경해 주어야 합니다. 예를 들어 port="8005"port="8100"와 같이 변경 해 주어야 하며, 'Connector' 항목에 설정 된 포트의 경우 방화벽을 사용하고 있다면 방화벽 예외 항목에 포함되어야 합니다. (필요하다면 tomcat1, tomcat2모두 변경해서 사용해도 상관없습니다.)


추가로 위 내용 중 protocol="AJP/1.3" 항목의 redirectPort="8443" 항목의 포트는 바로 윗 줄의 redirectPort="8443" 항목의 포트 번호와 같아야 합니다.
(통상적으로 tomcat만 사용하여 웹서버를 구성하는 것이라면 주석으로 처리해 두어도 무방합니다.)

3.2.tomcat2폴더의 bin디렉터리의 파일 중 service.bat파일을 찾아 메모장 등 에디터로 열러 수정합니다.

...
rem Set Default Service name
set SERVICE_NAME=TOMCAT ...
set PR_DISPLAYNAME=Apache Tomcat ...
...

파일의 내용 중에 SERVICE_NAME, PR_DISPLAYNAME항목을 적절하게 변경합니다. 변경 시 주의할 부분은 SERVICE_NAME항목에 띄어쓰기나 '_'(언더바)가 들어가면 서비스 등록 시 오류가 발생할 수도 있고, 서비스 명이 정상적으로 표시되지 않을 수도 있습니다.

4.서비스 등록 및 확인

4.1.서비스 등록

3번 항목까지 모두 완료되면 커멘드(CMD)창을 열어 tomcat2bin디렉터리로 이동합니다. 해당 디렉터리에서 아래와 같이 명령을 실행하여 서비스에 등록합니다.

$prompt tomcat2\bin>service.bat install


4.2.서비스 등록 확인

Windows의 관리도구 > 서비스창을 띄워서 3.2에서 설정한 SERVICE_NAME에 맞는 항목이 등록되었는지 확인하고, 등록되었다면 상황에 맞게 설정 및 시작을 진행합니다.


5.실행 확인

브라우저를 띄워 3.1항목에서 설정한 포트에 맞게 호출하여 정상적으로 실행되고 있는지 확인합니다. 정상적으로 실행되고 있다면 아래와 같은 화면을 확인할 수 있습니다.




IIS 7.0 이상에서 aspx를 구동 시 다음과 같은 에러가 나는 경우가 있습니다.

HTTP 오류 500.23 - Internal Server Error
관리되는 통합 파이프라인 모드에 적용되지 않는 ASP.NET 설정이 있습니다.
이 경우에는 해당 페이지에 해결방법이 자세히 나와 있는데
간단히 정리해서
 
1. [ 시작 >  제어판 > 프로그램 및 기능 > Windows 기능 사용/사용 안 함 ]
   항목을 선택한 후 웹관리 도구 중 IIS 6 관리 호환성을 모두 선택합니다. ( 또는 서버관리자에서 웹서버 역할부분에서 확인, 추가 할 수 있습니다. )

2. 그래도 계속 아래 메시지가 난다면

An ASP.NET setting has been detected that does not apply in the Integrated managed pipeline mode

cmd 창에서 아래 명령을 실행하면 해결됩니다.
%SystemRoot%\system32\inetsrv\appcmd migrate config "Default Web Site/"


보통 Windows Server 2008의 경우엔 Oracle에서 제공하는 설치파일을 다운받아 설치하면 문제가 되지 않습니다. 다만 Windows Server 2008 R2 및 Windows 7의 경우에는 설치가 되지 않습니다. 물론 호환성 적용 등을 해도 설치되지 않습니다.
10g Oracle Installer가 인식하는 OS의 버전은 4.0, 5.0, 6.0까지인데, Windows Server 2008 R2 및 Windows 7은 6.1로 인식하기 때문입니다. 이런 경우엔 11g Client를 설치하면 문제가 없습니다만, 부득이하게 10g Client를 설치해야하는 경우를 위한 설치 방법을 적어둡니다.

이 글에서 설명하는 Oracle Client의 버전은 10.2.0.4기준입니다.
10.2.0.4 다운로드 경로
http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html

1. 위에 존재하는 링크를 따라 Oracle Client를 다운받아 압축을 적절히 풀어놓습니다.
2. 다음과 같은 경로에 3개의 파일을 수정해주면 설치가 가능합니다.

..\client\install\oraparam.ini
..\client\stage\prereq\client\refhost.xml
..\client\stage\prereq\client_prereqs\client\refhost.xml

3. oraparam.ini의 경우 중간에
[Certified Versions]
#You can customise error message shown for failure, provide value for CERTIFIED_VERSION_FAILURE_MESSAGE
Windows=5.0,5.1,5.2,6.0

와 같은 구문을 찾을 수 있는데 Windows=5.0,5.1,5.2,6.0,6.1와 같이 6.1을 추가해 줍니다.

4. 2개의 refhost.xml파일의 경우
<OPERATING_SYSTEM>
<VERSION VALUE="6.0"/>
</OPERATING_SYSTEM>

이란 구문 아래 동일한 구성으로 <VERSION VALUE="6.1"/>을 추가해 줍니다. 위와 같이 처리한 후 setup.exe를 실행하면 유효성 검사항목을 통과하며 설치가 됩니다.

원격 제어 환경이나 MS 서버 환경에서 메일 체크할 땐 필요한 방법인데... 자주 사용하지 않는 내용이다보니 금방 잊어버리네요...

아래 내용은 MS SMTP를 사용/기준으로 작성한 내용입니다.

커멘드 창에 아래처럼 입력하면

$\> telnet Mail\_Server\_IP\_Address 25(PORT)

화면이 전화면 되면서 SMTP 환영 메시지를 확인할 수 있습니다. 그리고 아래와 같이 메일을 발송합니다.

220 xxxxx Microsoft ESMTP MAIL Service, Version: x.x.x ready as 요일, 날짜, 월 년도, 시간 

helo me                      -> 정상적으로 접속이 되었는지 확인
mail from:                   -> 발송자 메일주소
rcpt to:                        -> 수신자 메일주소
data
subject:                       -> 제목

-> 내용적기

.                                  -> 마지막 종료 문자

1. 묶고 풀기 ( tar )

☞ tar 명령은 파일을 묶는 명령이다. 

원래는 백업을 위해 테이프백업 장치인 DAT  Device 에 백업을 할 때 쓰는 명령이었지만, 요즘은 파일의 압축이나 묶기에 더 많이 쓰이는 편이다. 

압축명령에는 compress 나 gzip 이 따로 있지만 tar 명령으로 통합해서 쓰면 편리하고 좋다. 우선 파일을 묶는 방법에 대해 알아 본다.


- tar 명령으로 파일을 묶을 때는 주의해야 할 것이 있는데 바로 디렉토리 단위로 묶는 것이다. 공개 자료실등에서 tar 파일을 받아서 풀 경우 디렉토리 단위로 묶지 않은 파일이면 수십개의 파일이 풀려 나와 디렉토리가 엉망이 되니 신경을 써야 된다. 

- tar는 아카이브 안의 파일에 대한 소유권과 허가권을 그대로 가지고 있으며, 심볼릭 링크나 하드링크, 디렉토리 구조를 유지한다. 뒤에 나오지만 이런점 때문에 tar를 이용해서 시스템 내에서의 데이터의 이동이나 복사에 자주 사용된다. 


  • 묶기
    $ tar cvf 생성_파일명.tar 대상 파일명(또는 대상 디렉토리)
  • 풀기
    $ tar xvf 생성_파일명.tar 


ex) 

$ tar cvf doc.tar doc/

$ tar xvf doc.tar


☞ tar 파일의 종류는 다음과 같다.

  • *.tar       <-- 일반적으로 tar로만 묶었을 때
  • *.tar.Z     <-- compress 로 압축했을 때
  • *.tar.z     <-- gzip 으로 압축(gunzip으로 푼다)
  • *.tar.gz    <-- gzip 으로 압축(gunzip으로 푼다)
  • *.tgz       <-- gzip 으로 압축(gunzip으로 푼다) 


2. 압축, 압축풀기

☞ 압축은 compress 나 gzip 등의 명령을 쓰면 된다. 

  • compress를 이용한 압축(*.tar.Z)
    $ compress 대상파일명.tar
  • uncompress를 이용한 압출풀기
    $ uncompress 대상파일명.tar.Z 


  • gzip을 이용한 압출(*.tar.gz)
    $ gzip 대상파일명.tar 
  • gunzip을 이용한 압축풀기
    $ gunzip 대상파일명.tar.gz


- 각각의 경우에서 뒤의 확장자 .Z .gz가 붙고 떨어지는 것을 주의 깊게 봐야 한다. 

- 파일을 풀기 전에 tar 파일의 내용을 알고 싶을 때 직접 풀지 않고 내용을 보려면 -t 옵션을 주어 풀기를 test 할 수 있다.

ex)  $ tar tvf 대상 파일명.tar


3. 동시수행

☞ tar 명령으로 묶기와 압축을 동시에 수행할 수 있다. 

아래와 같이 tar 명령에서 z 옵션을 주면 된다. 하지만 사용되는 OS에 따라서 z옵션이 지원되지 않는 경우도 있다.


  • 동시 수행
    $ tar cvfz 생성파일명.tgz  대상파일/
    $ tar xvfz 대상파일명.tgz
    $ tar tvfz 대상파일명.tgz


  • 파이프를 이용한 압축 (*.tgz)
    $ tar cvf - 대상파일/ | gzip > 생성파일명.tgz 
  • 파이프를 이용한 압축풀기
    $ gunzip -c 대상파일명.tgz | tar xvf - 


  • 묶어서 디렉토리 옮긴 후 풀기 
    $ tar cvf - 대상파일명 | ( cd 디렉토리명 ; tar xvf - ) 
리눅스 환경 작업을 하다가 보면 vi 에디터를 사용해야 하고,
익숙하지 않은 경우에는 정말 불편한게 vi 에디터다...
물론 익숙해지면 정말 쓰기 편하지만.^^;;;

억지로라도 익숙해질 수 밖에 없기에
과감히 vi 에디터 윈도우용을 설치해서 사용하고 있다.

초기에 설정을 하는 부분이 조금 많아서 필자도 이래저래 찾기바빴다.
하긴 설정파일을 복사해서 가지고 다녀도 되지만 혹 필자처럼 찾는 사람에게 도움이 됐으면 하는 마음과
혹시나 나중을 위해서라도 설정부분을 올려놓으려 한다.

설정파일 위치는 설치폴더에 가면 _vimrc 이란 확장자가 없는 파일이 존재한다.(포터블 버전 역시 마찬가지~)
이부분에 추가해 주면 된다. 필자도 이제 막 사용하는 터라 모자란 부분과 설명은 조금씩 추가해 갈 예정이다.

""""""""""""""""""""""""""""""""""""""""""""""""
" 추가 설정
""""""""""""""""""""""""""""""""""""""""""""""""
set guifont=Bitstream_Vera_Sans_Mono:h9    " 표시 폰트 설정
set fencs=ucs-bom,utf-8,cp949                    " 파일 읽을 때 인코딩 설정
set ai                    " 자동 들여쓰기를 설정
"set autoindent       " 자동 들여쓰기 - 이렇게 써도 된다.
"set smartindent     " 역시 자동 들여쓰기
set shiftwidth=4      " 들여쓰기 폭을 설정
set tabstop=4         " 탭의 폭을 정합니다.
set et                    " 탭을 스페이스로 대체합니다.
set visualbell         " 경고 소리를 화면 깜빡임으로 대체
"set novisualbell    " 비주얼벨 기능을 사용하지 않음
set nu!                 " LineNumber 표시
syntax on             " 구문강조 표시
color desert          " 컬러스킴 설정
set nobackup        " 백업파일을 만들지 않음
set nocompatible   " Vim 디폴트 기능들을 사용함
set backspace=2   " 삽입 모드에서 백스페이스를 계속 허용
set textwidth=90     " 86번째 칸을 넘어가면 자동으로 줄 바꿈
set nowrapscan    " 찾기에서 파일의 맨 끝에 이르면 계속하여 찾지 않음
set nojoinspaces   " J 명령어로 줄을 붙일 때 마침표 뒤에 한칸만 띔
set ruler               " 상태표시줄에 커서 위치를 보여줌
set showcmd       " (부분적인) 명령어를 상태라인에 보여줌
set showmatch    " 매치되는 괄호의 반대쪽을 보여줌
set ignorecase     " 찾기에서 대/소문자를 구별하지 않음
set incsearch       " 점진적으로 찾기
set autowrite        " :next 나 :make 같은 명령를 입력하면 자동으로 저장
set linespace=3    " 줄간격
set title                " 타이틀바에 현재 편집중인 파일을 표시 "

1. library 추가

- version 1.1 이상 필요

- commons-fileupload.jar

- commons-io.jar


2. Bean 주입 ( applicationContext.xml )

-[bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /]

- 이부분에 대한 추가적인 설정이나 설명은 각자 찾아보길 바란다.


3. Source part

// 파일 첨부를 위해 추가 import 구문

import org.springframework.util.FileCopyUtils;

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.multipart.MultipartHttpServletRequest;

import org.springframework.web.multipart.commons.CommonsMultipartResolver;


public class ActionClassName extends MappingDispatchActionSupport {


       private CommonsMultipartResolver cResolver;

       // Multipartrequest를 컨트롤하는 객체를 주입받는다.

       cResolver = (CommonsMultipartResolver) getWebApplicationContext().getBean("multipartResolver");    

       

       public ActionForward adminGoodStoreSave(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {


        MultipartHttpServletRequest mrequest = cResolver.resolveMultipart(request);

        String realPath      = request.getSession().getServletContext().getRealPath("/");    // 실제 서버의 주소는 이런식으로 가져올 수도 있다.

        String uploadPath    = realPath + customPath;


          // Multipartrequest 에서 파일 객체부분을 가저온다.

        Map fileMap = mrequest.getFileMap();


        for (Iterator it=fileMap.entrySet().iterator(); it.hasNext();) {

            Entry entry      = (Entry)it.next();

            MultipartFile mf = (MultipartFile)entry.getValue();


            if (!mf.isEmpty()) {

                if(mf.getSize() > 0){

       

                    // 원본 파일명

                    String originalFilename = mf.getOriginalFilename();

                    // 새로운 파일명은 시간(밀리세컨드까지)으로 변경하고 

                    // 확장자만을 갖는다.

                    String newFilename =  System.currentTimeMillis() + originalFilename.substring(originalFilename.length() -4, originalFilename.length());;


                    File fOri = new File(uploadPath, newFilename);


                    // 실제 파일을 작성한다.

                    InputStream is = mf.getInputStream();

                    OutputStream os = new FileOutputStream(fOri);

                    FileCopyUtils.copy(is, os);


                    /* 

                     * 여기에서 파일 필드를 구분하여 처리를 한다.

                     * mf.getName() 또는 entry.getKey().toString() 으로 

                     * input Tag의 name을 확인할 수 있다.

                     */

                    if"file1".equals(mf.getName()) ) {

                            /* 파일 필드1에 대한 처리 구역 */

                    } else if"file2".equals(mf.getName()) ) {

                            /* 파일 필드2에 대한 처리 구역 */

                    } else if"file3".equals(mf.getName()) ) {

                            /* 파일 필드3에 대한 처리 구역 */

                    }


                    os.close();

                    is.close();


                }

            }

        }

        return mapping.findForward(Constants.SUCCESS_KEY);

    }

       

}

       

       

4. JSP Tag part

<html>

<head>

</head>

<body>

....

<tr>

<td align="center" width="100">첨부1</td>

<td colspan="3"><input type="file" name="file1" id="file1"></td>

</tr>

<tr>

<td align="center" width="100">첨부2</td>

<td colspan="3"><input type="file" name="file2" id="file2"></td>

</tr>

<tr>

<td class="rowtitle" align="center" width="100">첨부3</td>

<td colspan="3"><input type="file" name="file3" id="file3"></td>

</tr>

...

</body>

</html>


Editer 창에서 마우스 우측 클릭 후 > [ Prompt For Substitution Variables ] 항목의 체크 해제



**** F5 Key로 전체 스크립트 실행시에는 해당기능이 적용되지 않았다.....*****

Character-set이 UTF-8인 환경에서 EUC-KR환경으로 submit을 했을 때 한글이 깨져서 전송이 되는 문제로
JAVA 환경인 경우에는 통상적으로 전송 받는 쪽에서

request.setCharacterEncoding("euc-kr");   
또는 
String Variable_Name = new String( Input_Variable_Name.getBytes("UTF-8"),"EUC-KR") ); 

위와 같은 형식으로 한글에 대한 처리를 해주고는 합니다. 허나 이 방식은 JAVA에서 브라우저에 Data를 바인딩 하는 경우에는 원할하게 처리가 되었습니다만, 브라우저를 이용하는 사용자가 직접 입력한 값이나 Hidden 필드에 Script로 값을 넣어 전송 등, 위와 같은 처리를 해주어도 올바른 값을 전송받지 못하는 경우가 발생 할 수 있습니다. 특히 한글은 항상 깨지더군요.

확인한 방법으로는 Javascript로 Submit을 제어하는 경우 전송이 이루어지는 submit();구문 직전에 아래와 같은 구문을 추가해 주면 문제가 해결되었습니다. submit 직전에 브라우저 document의 설정을 변경하는 방법입니다.

// EUC-KR로 변경했던 Character-set을 폼 전송 후 원래의 Character-set으로 설정하기 위해
// 원래의 Character-set 정보를 변수에 담아 둡니다.
var defCharset = document.charset;    

if (/MSIE/.test(navigator.userAgent)) {
    document.charset = 'EUC-KR';
} else {
    form_name.acceptCharset = 'EUC-KR';
}

form_name.submit();
document.charset = defCharset;
요즘은 cvs보다 svn쪽을 더 선호하는 추세이기는 하지만...
아직 회사에서도 cvs를 사용하는 부분이 있다....

새로운 계정을 추가하고 비밀 번호를 생성해서 passwd파일에 넣어주는 일이 어렵지는 않지만...
어느정도 번거로운 것도 있고... 특히 자주 사용하지 않아서 쉽게 잊어 버린다...
웹을 검색하면서 apache나 perl을 이용해서 암호화된 비밀 번호를 생성하는 것을 공부하다 보니
Windows에서 perl을 이용해서 쉽게 계정을 생성하는 것을 해보려한다.

우선 http://strawberryperl.com/에 가서 원하는 버전을 받는다.
5.10.x.x버전은 아직 버그가 많이 존재한다고 해서 strawberry-perl-5.8.9.2버전의 바이너리 타입을 받았다.
(설치하는 걸 그다지 좋아하지 않기도 하지만... 대부분 바이너리로 실행이 가능하다면 포터블처럼 들고다닐 수 있기에 선호하는 편이다. )

적당한(원하는) 위치에 압축을 풀고,
[strawberry-perl-5.8.9.2\perl\bin\]을 살펴보면 perl.exe 파일을 확인할 수 있다.
perl.exe는 cmd 창에서 실행이 가능하다.
(전역으로 perl.exe를 사용하고 싶다면 환경변수[Path]에 해당 경로를 넣어주면 된다. 아래는 버전확인 부분)

perl의 설치는 이것으로 완료되었다. 이제 cvs에서 사용할 비밀번호 생성 스크립트를 작성해 보자.
이부분은 잘 모르는 부분이라서 아래 출처에서 참고하였으며, 자세한 설명이 되어있는 곳이니 한번쯤은 읽어보기 바란다.

출처 : http://wiki.kldp.org/wiki.php/DocbookSgml/CVS_Tutorial-KLDP  (+ 항목 3.2.2. )

파일명 : cvspasswdgen.pl

#!D:\04.Programs\strawberry-perl-5.8.9.2\perl\bin                           <- perl이 설치된 bin 디렉터리

($u, $p)=@ARGV;
@d=(A..Z,a..z);
$s=$d[rand(52)].$d[rand52];
print $u.":".crypt($p, $s).":cvs\n";


위의 내용을 작성하여 [\strawberry-perl-5.8.9.2\perl] 디렉터리에 저장한다.
( 다른 디렉터리에 저장을 해도 무관하지만 굳이 perl 폴더에 저장하는 이유는 아래의 .bat파일에서 확인할 수 있다. )

위의 cvspasswdgen.pl 만으로도 실행을 할 수는 있지만... perl 의 실행경로를 환경변수에 넣어주지 않았다면 아래 화면과 같이 매번 perl.exe파일의 실행경로를 적어주어야 하는 번거로움이 있다...

필자는 환경변수에 추가해서 사용하는 걸 별로 좋아하지 않다보니 너무 번거로웠다. 때문에 번거로움을 해결하기 위해서 cvsgen.bat라는 파일을 만들어봤다.
(많은 삽질을 했지만.. 덕분에 번거로움이 많이 줄어든 것 만으로도 뿌듯하다.^^;;;;)

파일명 : cvsgen.bat

@echo off
rem ========================================================
rem 아이디 값 입력
rem ========================================================
:select_id
@echo Enter your id
@echo off
set id=
set /p id=

rem 빈 값에 대해 다시 입력처리
if "%id%" == "" goto select_id

rem 한줄 띄우기
@echo.

rem ========================================================
rem 암호 값 입력
rem ========================================================
:select_pw
@echo Enter your passward : 
@echo off
set pw=
set /p pw=

rem 빈 값에 대해 다시 입력처리
if "%pw%" == "" goto select_pw

rem 한줄 띄우기
@echo.

rem ========================================================
rem 이부분의 %cd%값은 현재 실행되고 있는 bat파일의
rem 위치가 된다. 만약 실행 위치를 변경하려면 이곳을 수정한다.
rem ========================================================
%cd%/bin/perl.exe cvspasswdgen.pl %id% %pw%

rem 아래 명령어를 사용하면 실행이 완료되어도 창이 닫히지 않고 그대로 유지된다.
pause > nul

:end


cvsgen.bat 파일은 [\strawberry-perl-5.8.9.2\perl\]위치에 저장해두고 파일을 실행해 보면 아래와 같은 화면을 볼 수 있다.

해당부분을 복사해서 passwd파일에 넣으면 끝~!!
개발을 하다보면 프로그램이 종료되지 않아서.. 작업관리자에서 강제로 프로세스를 종료해야하는 일이 종종 생기는 것 같다... 거기다 노트북인지라... 프로세스 갯수는 거의 80여개에 가까운 숫자가 된다....

프로세스를 보다보니 대체 무엇에 쓰이는 항목들인지..
이래저래 찾아보고 몇가지만 정리해 본다.

-----------------------------------------------------------------------------------------------------
프로세스명 : System 
 -> 쓰레드의 시작점 프로세스

프로세스명 : system idle process 
 -> 작업관리자에서 system idle process은 cpu의 여유로운 상태를 말해준다. 
    현재 사용할 수 있는 남은 자원을 %로 말한 것으로 아무작업을 안하고 있을때는 99%가 됩니다.

프로세스명 : taskmgr.exe 
 -> Task Manager 즉, 작업 관리자를 띄우면 생성된다.
 -> 실행파일경로 : C:\WINDOWS\system32\taskmgr.exe

프로세스명 :  wuauclt.exe
 -> Windows Update AutoUpdate Client는 마이크로소프트 윈도우 자동업데이트를 도와주는 백그라운드 프로그램
 -> 실행파일경로 : C:\WINDOWS\system32\wuauclt.exe or \system32\dllcache\wuauclt.exe 

프로세스명 : conime.exe
 -> Consol IME 명령 프롬프트에서 다국어를 지원하도록 하기 위해 사용하는 프로그램.
    영어만 사용하지 않는 이상 반드시 필요한 프로그램. 단, conimekr.exe일경우 conimekr.exe는 광고 프로그램.
 -> 실행파일경로 : C:\WINDOWS\system32\conime.exe

프로세스명 : mmc.exe
 -> Microsoft Management Console application으로 컴퓨터 관리창을 실행했을 경우 나타난다.
 -> 실행파일경로 : C:\WINDOWS\system32\mmc.exe

프로세스명 : wmiprvse.exe
 -> 윈도우 미디어 플레이어 10을 설치하면 생성되는 파일이며, 단 wmiprvsw.exe는 Sasser worm이다. wmiprvsw.exe이라면 제거해야한다. 
 -> 실행파일경로 : C:\WINDOWS\system32\dllcache\wmiprvse.exe or \wbem\wmiprvse.exe

작업이름 : Winlogon.exe
 -> 사용자 로그인/로그오프를 담당하는 프로세스, 윈도우의 시작/종료시, Ctrl+Alt+Del
 -> 실행파일경로 : C:\WINDOWS\system32\Winlogon.exe

프로세스명 : smss.exe
 -> Session Manager SubSystem, 사용자 세션을 시작하는 기능을 담당. Winlogon, Win32(Csrss.exe)을 구동시키고, 시스템 변수를 설정. 
    Winlogon이나 Csrss가 끝날때, 정상적인 Winlogon/Csrss 종료시 시스템을 종료시키며, 비정상적인 Winlogon/Csrss 종료시, 시스템이 멎는 상태가 된다
 -> 실행파일경로 : C:\WINDOWS\system32\smss.exe

프로세스명 : csrss.exe
 -> Client/Server Runtime SubSystem, 윈도우 콘솔을 관장, 쓰레드를 생성/삭제하며, 16bit 가상 MS-DOS 모드를 지원. 
    이 프로세서는 작업관리자로 제거 할 수 없다.
 -> 실행파일경로 : C:\WINDOWS\system32\csrss.exe

프로세스명 : Lsass.exe
 -> Local Security Authentication Server, Winlogon 서비스에 필요한 인증 프로세스를 담당
 -> 실행파일경로 : C:\WINDOWS\system32\Lsass.exe

프로세스명 : winmgmt.exe
 -> 클라이언트 관리 요소, 윈도우를 정상적으로 운영하는데 있어 필수적인 프로세스, 종료시 시스템 다운및 다른 부작용유발됨
 -> 실행파일경로 : C:\WINDOWS\system32\wbem\winmgmt.exe

프로세스명 : MDM.EXE
 -> MDM.EXE(Machine Debug Manager) Office 2000과 Windows NT Option Pack / Developer Studio등에 의해서 설치됨 - 일반적으로 컴퓨터성능에 영향을 미침
 -> 실행파일경로 : C:\WINDOWS\system32\MDM.EXE

라디오 버튼을 동적으로 생성하기 위해 만들었던 jQuery인데...
DWR과 같이 사용을 한터라.... 범용으로 사용하기에는 무리가 있을 수 있다.
음...데이터 형태만 맞추면 가능할지도....^^;;

/************************************************************************************
' function명: addRadioMap
' 내용      : 라디오버튼 동적으로 생성.
'             jQuery.js(core)에 삽입
'             strGrpName = 라디오버튼 그룹명.
'             data = [{key:'string', value:'string'},{key:'string', value:'string'},{key:'string', value:'string'}] 형식
'             strKey = Map데이터의 key
'             strValueKey = Map데이터의 value key
*************************************************************************************/
addRadioMap: function ( strGrpName, data, strKey, strValueKey )  {
    var arrText = [];
    for( var iCnt in data ){
      arrText[iCnt] = "&nbsp;<input type='radio' name='"+strGrpName
                    +"' id='"+data[iCnt][strKey]+"'value='"+data[iCnt][strValueKey]+"'>"+data[iCnt][strValueKey];
    }

    // 해당 테그의 압쪽에 넣는다.
    this.prepend(arrText.join(' '));    //or this.html(arrText.join(''));
프로젝트를 진행하면서 새로운 사실을 발견했다~

java에서 String 타입의 null 값 더하기 String 타입의 null은 String 타입 nullnull이 된다....
찾아본 결과 아래의 글을 찾아볼 수 있었는데...
String 타입의 편의성을 위해 그렇게 된다고 하는데....

자바를 오랜 기간 다루었던 사람들은 알지도 모르는 것이지만 
다룬 기간이 짧은 나로서는 새로우면서도 조금은 쏘킹했다.

오오 널널~

===========================================================================================

[ null String value in Java ] null String problem


자바에서 String 을 다룰때 조심해야할 사항들이 있다.

junit으로 테스트 하면 다음의 테스트 클래스는 성공적으로 실행된다.

public class Test_NullString extends TestCase {
    
    public void testNullString () {
        String nullString = null;
        String nullString2 = null;
        assertNull(nullString); // null 이다.
        nullString += "?";
        assertEquals("null?", nullString);
        assertEquals("nullnull", nullString + nullString );   
}

    
    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }
    public void testSameObject() {
        assertFalse(new TestObj() == new TestObj());
    }
    private String getJane(){ return "Hi";}
    private String getTom() { return "Hi";}
    
    private class TestObj{
        public boolean equals(Object obj) {
            if ( obj instanceof TestObj ) return true;
            else return false;
        }
    }
}

testNullString() 에서 볼 수 있듯이 null 로 초기화된 string의 경우 이어지는 string 값을 더할 경우 + 연산에 의해서 "null" 이 붙게 된다. null 로 초기화된 두개의 string 을 더할때에도 마찬가지로 그 결과는 "nullnull" 이 된다.

이런 결과는 null 에 대한 보통의 상식과 전혀 다른 결과이다. null 인 스트링과 null 이 아닌 스트링을 더했을때 당연히 null 이 아닌 스트링만 나오기를 기대했었는데 의외의 결과...

왜 일반의 통념과 다르게 
null에 대한 + 연산을 이렇게 구현해야 했을까?

우선 + 연산이 스트링에 대해서 아주 빈번하게 사용된다는 점을 들 수 있는데, + 연산이 구체적으로 어떻게 실행되는지 api 의 코드에는 나와있지 않아서 알 수 없지만 내부적으로 StringBuffer 와 동일한 규칙이 적용되고 있다고 짐작할 따름이다.(StringBuffer의 경우도 append( null ); 이 들이오면 "null" 을 붙이도록 구현되어 있다. 뭐냐 이거.. -_-;;)

만일 일반의 생각대로 ((String)null) + "a" 의 결과가 "a" 라고 하자.

그러면 결과는 "a"가 나올 수 없는데, null 이기 때문에 NullPointerException 이 던져져야 한다. null 로 초기화된 객체는 아무런 연산 행위도 갖지 못한다. 무의식 중에 스트링에 대해서 + 연산을 실행하지만 이런 편의를 위해서 위와같이 null 스트링을 처리하는 것이 아닌가...짐작할 뿐이다.

예를 들어 ((Object)null) + ((Object)null) 을 하면 컴파일 에러가 던져진다. Object 형에 대해서는 + 연산이 정의되어있지 않기 때문... 결국 String 객체에 + 연산을 적용하기 위해서 null에 대해 특별한 처리를 한 것 같다.

두번째 예제도 나름 흥미롭다.

    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }

보통 == 와 equals(Object o) 에 대한 닳고 닳은 설명들과 달리 위 테스트도 모두 성공한다. 첫번째와 달리 두번째, 세번째 테스트가 false로 나오는 것에 주목할 필요가 있다.

자바소스 코드 내에서 "...." 로 둘러싸인 부분들은 모두 ConstantPool에 저장된다. ConstantPool이란 자바 클래스 파일에 상수값들만 따로 뽑아놓은 값들인데 동일한 문자열들은 모두 하나의 스트링 값으로 참조되므로 첫번째 "home" == "home"은 true가 된다.(동일한 물리 주소를 참조)

두번째 세번째 assert 에서 new 로 새로운 스트링객체를 만드는데 이것들은 ConstantPool에 있는 스트링을 통해서 새로 만들어진 객체라는게 중요하다.(서로 다른 물리주소를 참조하므로 false이다.)

상식과 어긋나 보이는 이런 규칙들은 대부분의 경우 편리하고 좋지만 안좋을 때도 있다. 일례로 이 글을 쓰게 된 것도 Servlet 에서 getQueryString() 이 반환하는 null 값이 예상치 못한 행동을 하고(위에 설명한...) 그 이유를 알아보다 위와같은 테스트까지 하게 된 것이다.

어찌보면 null의 문제라기보다는 null을 반환하도록 코딩된 서블릿의 구현이 문제가 있는게 아닌가 싶다. 사실 모든 메소드가 인스턴스를 반환할때 null을 피하는 것이 좋다.특히 + 연산이 허용된 java.lang.String 의 경우는 더욱 그러하다. 하지만 스펙에는 아주 용감하게 "쿼리 스트링이 없을 경우 null 을 반환한다" 라고 쓰여져 있다.

덕분에 두시간 넘게 헤매고 다녔다. -_-;;

웬만하면 스트링을 초기화할때는 null 이 아니라 "" 로 초기화해야한다.

           String query = "":

물론 String을 반환하는 메소드의 경우 ""과 null 이 서로 다른 의미를 갖는게 아니라면 무조건!!! "" 을 반환하도록 하는게 옳다고 생각한다. 
직업도 인터넷에 연관되어 있지만....
엄청나게 무지했던 저작권...관련 문제들....

요즘 주변에서 '저작권..저작권...'하며 하도 안좋은 사례들만을 들었기에...
자세히 알아보고자 찾아봤다...

프로그램도 그렇지만... 어느정도는 알아야 자신이 어디에 서 있는지 알 수 있기 때문이다.
=====================================================================================================
출처 : http://www.creativecommons.or.kr/info/about

개념 및 여러가지 다른 내용도 있기 때문에 
위의 링크를 한번쯤은 확인하는 편이 좋을 것 같다. ^^;;;;



Creative Commons License 구성요소

CCL의 구성요소 즉, 이용자에게 부과하고 있는 "이용방법 및 조건"의 구체적 내용은 다음과 같은 4가지입니다.

  저작자표시
저작권법 상 저작인격권의 하나로서, 저작물의 원작품이나 그 복제물에 또는 저작물의 공표에 있어서 그의 실명 또는 이명을 표시할 권리인 성명표시권(right of paternity, 저작권법 제12조 제1항)을 행사한다는 의미입니다. 따라서 이용자는 저작물을 이용하려면 반드시 저작자를 표시하여야 합니다.

  비영리
저작물의 이용을 영리를 목적으로 하지 않는 이용에 한한다는 의미입니다. 물론 저작권자가 자신의 저작물에 이러한 비영리 조건을 붙였어도 저작권자는 이와는 별개로 이 저작물을 이용하여 영리행위를 할 수 있습니다. 또한 영리 목적의 이용을 원하는 이용자에게는 별개의 계약으로 대가를 받고 이용을 허락할 수 있습니다.

  변경금지
저작물을 이용하여 새로운 2차적 저작물을 작성하는 것뿐만 아니라 저작물의 내용, 형식 등의 단순한 변경도 금지한다는 의미입니다.

  동일조건변경허락
저작물을 이용한 2차적 저작물의 작성을 허용하되 그 2차적 저작물에 대하여는 원저작물과 동일한 내용의 라이선스를 적용하여야 한다는 의미입니다. 예를 들어 저작자표시-비영리 조건이 붙은 원저작물을 이용하여 새로운 2차적 저작물을 작성한 경우 그 2차적 저작물도 역시 저작자표시-비영리 조건을 붙여 이용허락 하여야 합니다. 
예전에 테이블 컬럼 정보 조회 쿼리를 올린적이 있었는데....
이리저리 도움을 받아서 다시 정리했다.

음.. 이번거는 목적이 '테이블 정의서'라서.... 
조금 애매하기는 하다.

아래의 쿼리문을 그대로 돌리면 해당 유저의 스키마 테이블을 모두 조회한다.
아참.. 오라클 실행계획을 보면 LOOP가 굉장히 많고 시간도 조금 걸리는 문장이란걸 유념하자.
그리고 좀 길다...쩝...

SELECT (CASE WHEN MAIN.COLUMN_ID = 1 THEN MAIN.TABLE_COMMENTS 
                       ELSE '' END)                                                                             table_kor_name
           , (CASE WHEN MAIN.COLUMN_ID = 1 THEN MAIN.TABLE_NAME 
                        ELSE '' END )                                                                           table_eng_name
           , MAIN.COLUMN_ID                                                                                column_id
           , MAIN.COLUMN_NAME                                                                          column_name
           , MAIN.COLUMN_COMMENTS                                                                 column_comments
           , (CASE SUB.CONSTRAINT_TYPE WHEN 'P' 
                                                             THEN 'PK' 
                                                             WHEN 'R' 
                                                             THEN 'FK ('||SUB.FK_COLUNM||')' END) key_flag
           , (CASE NVL(MAIN.NULL_FLAG, 'N') WHEN 'N' 
                                                                 THEN 'NOT NULL' 
                                                                 ELSE '' END)                                    null_flag
           , MAIN.DATA_TYPE                                                                                 data_type
           , (CASE MAIN.DATA_TYPE WHEN 'NUMBER' 
                                                   THEN (CASE WHEN MAIN.DATA_PRECISION IS NOT NULL 
                                                                       THEN TO_CHAR(MAIN.DATA_LENGTH)||' ('
                                                                                ||TO_CHAR(MAIN.DATA_PRECISION)||')'                                                                                     ELSE TO_CHAR(MAIN.DATA_LENGTH) END)
                                                   WHEN 'DATE'   
                                                   THEN '' 
                                                   ELSE TO_CHAR(MAIN.DATA_LENGTH) END)      data_type_length
   FROM (SELECT T_COMT.COMMENTS                                              table_comments
                         , T_COLS.TABLE_NAME                                             table_name
                         , C_COMT.COMMENTS                                             column_comments
                         , T_COLS.COLUMN_NAME                                         column_name
                         , (CASE T_COLS.NULLABLE WHEN 'Y' THEN 'Y' END) null_flag
                         , T_COLS.DATA_TYPE                                               data_type
                         , T_COLS.DATA_LENGTH                                           data_length
                         , T_COLS.COLUMN_ID                                               column_id
                         , T_COLS.DATA_PRECISION                                       data_precision
                 FROM USER_TAB_COLUMNS T_COLS
                         , USER_TAB_COMMENTS T_COMT
                         , USER_COL_COMMENTS C_COMT
                WHERE T_COLS.TABLE_NAME  = T_COMT.TABLE_NAME
                     AND T_COLS.TABLE_NAME  = C_COMT.TABLE_NAME
                     AND T_COLS.COLUMN_NAME = C_COMT.COLUMN_NAME
                     AND T_COMT.TABLE_TYPE  = 'TABLE'
             ) MAIN
           , (SELECT CONST.CONSTRAINT_TYPE    constraint_type
                         , CONST.TABLE_NAME             table_name
                         , COLS.COLUMN_NAME            column_name
                         , R_COLS.COLUMN_NAME         fk_colunm
                 FROM USER_CONSTRAINTS CONST
                         , USER_CONS_COLUMNS COLS
                         , USER_CONS_COLUMNS R_COLS
                WHERE CONST.CONSTRAINT_NAME   = COLS.CONSTRAINT_NAME
                    AND CONST.CONSTRAINT_TYPE IN ('P', 'R')
                    AND CONST.R_CONSTRAINT_NAME = R_COLS.CONSTRAINT_NAME(+)
             ) SUB
 WHERE MAIN.TABLE_NAME    = SUB.TABLE_NAME(+)
     AND MAIN.COLUMN_NAME   = SUB.COLUMN_NAME(+)
     --AND MAIN.TABLE_NAME  = UPPER('TABLE_NAMES')  /*특정 테이블 조회일 경우 사용.*/
 ORDER BY MAIN.TABLE_NAME, MAIN.COLUMN_ID
;


헉...이거 신경써서 오와 열을 맞췄더니만.... 
미리보기 하니 다 깨지네...쩝.. 몰라....
요즘은 웹 에디터를 사용하여 DB에 저장을 할 때 
그냥 통째로 HTML 태그와 함께 저장을 하는 경우가 많아지는 것 같다...

그 내용을 대충 요약해서 보여줘야 하는데...
아무생각없이 조회된 내용을 몇 글자 내로 잘라서 보여 줬더니
그 속에 같이 저장된 태그들 덕에 욕먹을 뻔했던 기억이....

지금 적어놓는 건 JAVA 프로퍼티에서 걸러 줬을 때 사용하던 정규식이다..
(내가 이렇게 사용했다는 것뿐...)

참고로,
정규식은 javascript에서도 사용할 수 있다는거.....^^;;

==============================================================================================

// HTML 태그제거 정규식 적용
var_name = var_name.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>","").replaceAll("\r|\n|&nbsp;","");

// 위에 구문이 넘 길어서 쪼개 놓으면 요렇게...
//var_name = var_name.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>","");
//var_name = var_name.replaceAll("("\r|\n|&nbsp;","");

return var_name;
예전에 올렸던건데....
글 삭제되면서.... 같이 삭제했던 글....
크흑....

====================================================================================================
확인할 때는 당연히! DBA 계정으로 확인


SELECT U.TABLESPACE_NAME                                                  "테이블 스페이스"
           , U.BYTES / 1048576                                                         "크기(mb)"
          , (U.BYTES - SUM(NVL(F.BYTES,0))) / 1048576                   "사용됨(mb)"
          , (SUM(NVL(F.BYTES,0))) / 1048576                                    "남음(mb)"
          , TRUNC((SUM(NVL(F.BYTES,0)) / U.BYTES) * 100,2)          "남은 %"
          , U.FILE_NAME                                                                  "저장위치"
 
FROM DBA_FREE_SPACE F
          , DBA_DATA_FILES U
WHERE F.FILE_ID(+) = U.FILE_ID
GROUP BY U.TABLESPACE_NAME
               , U.FILE_NAME
               , U.BYTES
 
ORDER BY U.TABLESPACE_NAME

;


플렉스 스터디를 위해.. 용어정리 및 기본정보 수집을 하려고 했는데....
문득 이런 생각이 들었다.

' 가만... 그러고 보니 RIA란게 뭐지??'

웹쪽 프로그램을 만지다 보니 그냥 전문가처럼 용어만을 입에서 밷어 댔었는데....
정작 RIA란 용어의 개념도 몰랐고... 

더 나아가 생각해보니... 웹 2.0의 개념도 몰랐..다...쩝...

용어가 먼저 생겨나서 정확한 개념이란게 없다고는 하지만...
그 흐름이라도 알고자 황급히 검색창과 눈팅을 한다....

트랙백이 안걸리는군....
쩝.. 잘됐다.. 차라리 조금씩 찾아보면서 링크를 추가하는 편이 효율적이겠다...


출처 : http://blog.naver.com/hdlee91/15001264663

F1

Toad 도움말 파일의 SQL Editor 부분이 표시됩니다.

F2

전체 화면 Editor Editor/Results 패널 표시 장치 사이를 전환합니다.

<SHIFT>F2

전체 화면 그리드를 전환합니다.

F3

다음으로 일치하는 것을 찾습니다.

<SHIFT>F3

이전에 일치하는 것을 찾습니다.

F4

팝업 창의 테이블, , 프로시저, 함수, 또는 패키지를 설명합니다.

F5

스크립트로 실행합니다.

F6

커서를 Editor Results 패널 사이로 전환합니다.

F7

모든 텍스트를 지웁니다.

F8

이전 SQL 문을 재호출합니다(SQL Statement Recall 창을 불러옵니다).

F9

실행문을 실행합니다.

<CTRL>F9

실행(구문 분석) 없이 실행문을 검사합니다.

<SHIFT>F9

커서 위치에서 현재 실행문을 실행합니다.

F10

오른쪽 클릭 메뉴를 표시합니다.

F11

Script 같은 실행(=F5)

F12

편집기 내용을 지정된 외부 편집기로 전달합니다.

<CTRL>A

모든 텍스트를 선택합니다.

<CTRL>C

복사

<CTRL>D

프로시저 인수를 표시합니다.

<CTRL>E

현재 실행문에서 Explain Plan 실행합니다.

<CTRL>F

텍스트를 찾습니다(Find Text 창을 불러옵니다).

<CTRL>G

라인으로 이동합니다(Goto Line 창을 불러옵니다).

<CTRL>L

텍스트를 소문자로 변환합니다.

<CTRL>M

Make Code Statement

<CTRL>N

이름이 지정된 SQL 문을 재호출합니다(SQL Statement Recall 창을 불러옵니다).

<CTRL>O

텍스트 파일을 엽니다.

<CTRL>P

Strip Code Statement

<CTRL>R

검색 바꾸기(Find and Replace Text 창을 불러옵니다)

<CTRL>S

파일을 저장합니다.

<SHIFT><CTRL>S

파일을 다른 이름으로 저장합니다.

<CTRL>T

드롭다운을 표시합니다.

<CTRL>U

텍스트를 대문자로 변환합니다.

<CTRL>V

붙여넣기

<CTRL>X

잘라내기

<SHIFT><CTRL>Z

마지막으로 취소한 작업을 재실행합니다.

<ALT><UP>

이전 실행문을 표시합니다.

<ALT><DOWN>

다음 실행문을 표시합니다(<ALT><UP> 사용한 사용)

<ALT><PgUp>

이전 탭으로 이동

<ALT><PgDn>

다음 탭으로 이동

<CTRL><ALT><PgUp>

이전 결과 패널 탭으로 이동

<CTRL><ALT><PgDn>

다음 결과 패널 탭으로 이동

<CTRL><HOME>

데이터 그리드에서는 위의 레코드셋으로 이동하며, 결과 그리드에서는 커서가 위치한 행의 번째 열로 이동하고, 편집기에서는 텍스트의 번째 열과 번째 행으로 이동합니다.

<CTRL><END>

데이터 그리드에서는 레코드셋의 끝으로 이동하며, 편집기에서는 텍스트의 마지막 열과 마지막 행으로 이동합니다. 단원의 "주의" 참조하십시오.

<CTRL><SPACE>

코드 완성 템플릿을 활성화합니다.

<CTRL><TAB>

MDI Child 창의 콜렉션을 순환합니다.

<CTRL><ENTER>

커서 이치에서 현재 SQL 문을 실행합니다.

<CTRL>. (마침표)

테이블 이름을 자동으로 완성합니다.

종료 버튼이 없는 우리회사 '전자식(?) 출퇴근 프로그램' 덕에
자주 작업관리자의 프로세스 항목을 들여다 보는 편이다..

가끔 부팅하고 나서 잽싸게 출근을 찍고~
(지각을 면하기 위해선 정말 잽싸야 한다!!)
프로세스 목록을 멍~하니 보고 있다보면
wuauclt란 넘이 무쟈게 메모리를 먹고있는 것을 발견하곤한다....

윈도우 업데이트에 관련 된 프로세스란 건 어디서 주워듣기 했다만....
괜히 궁굼해서 구글어빠~ 한테 '뭐에 쓰는 물건인고?' 하고 물어봤다.

다소 불친절한 구글어빠~ 라선지... 너무 많은 글들을 제시해 주더군...

검색한 글들을 읽어보다 sp3를 설치하고 나서 저넘이랑 다른한넘이랑 짜구
부팅시 다른 넘들의 로딩을 딜레이 시킨다는 것이었다.
(나의 지각을 이넘이 좌우하는 건가....)

그분이 주신 해결책으로는 자동 업데이트를 사용안함으로 하면 저넘이 실행이
안돼서 현상이 줄어든다는 것.

해보니 정말 그런거 같았다.
근데... 문제는 보안센터에서... 자꾸 풍선말을 뱉어 댄다는....

신경이 거슬려서 그냥 살기루 했도다~
일찍일찍 출근하는 착한(?) 사원이 되련다~


1. *.jsp 파일에서 엑셀로 열거나 저장하기 위해서 <head>부분에 삽입 필요.
2. 열기를 하면 브라우저 창에서 열림.
3. 각 컬럼 색상이나 모양등은 엑셀에서 제공하는 색상만이 지원되며 (웹 색상은 지원되지 않는듯....) 컬럼모양 역시 엑셀 서식을 기준으로 적용된다.
4. *.css파일을 참조할 수 있으며, 엑셀로 내려받는 것에는 문제가 없으나 엑셀파일을 열었을 경우 해당 파일이 없다며 오류가 발생.

=== 추가 부분 ===
<head>
<!-- 엑셀 파일이라는 걸 브라우저에 알려주는 부분 -->
<%@ page contentType="application/vnd.ms-excel; name='My_excel'; text/html; charset=euc-kr"%> 
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr" />
<title>Untitled Document</title>
<%
//해당 파일에 추가 설정
//저장을 눌렀을 때 저장 파일명이 myexcel 이 된다.
//지정해주지 않으면 기본적으로 *.jsp파일 명이 되는 듯.
response.setHeader("Content-Disposition", "attachment; filename=myexcel");
response.setHeader("Content-Description", "JSP Generated Data");   
%> 
</head>
전부터 테이블 정의서가 없어 고생한 적이 많았는데....
능통한 분의 도움을 받아 작성해놓은 쿼리문.
요즘은 이 쿼리를 사용하지 않길 바랄뿐이다.
하나하나 조회해서 정리하는 귀찬음이란....

=== v1.0 ===
select column_id
        , table_name
        , column_name
        , data_type ||'( '||data_precision||', '||data_scale||' )'
        , data_length
        , data_precision
        , data_scale
        , decode( nullable, 'Y', '', 'N', 'NOT NULL',  '')
   from USER_TAB_COLUMNS
where table_name = upper( 'TABLE_NAME' )
  order by column_id

=== v1.1 ===
select decode( a.column_id, '1' , a.table_name, '' ) as TableName
       , a.column_id as ID
       , a.column_name as ColumnName
       , b.comments as Comments
       , a.data_type ||'( '||a.data_precision||', '||a.data_scale||' )' as Types
       , a.data_length as Length
       , a.data_precision as Precision
       , a.data_scale as Scale
       , decode( a.nullable, 'Y', 'NULL', 'N', 'NOT NULL',  '') as NullYN
  from USER_TAB_COLUMNS a,
         ALL_COL_COMMENTS b
where a.table_name = upper( 'TABLE_NAME' )
 --and a.data_type = upper( 'Data_Type' )
    and a.table_name = b.table_name(+)
    and a.column_name = b.column_name(+)
  order by column_id

예전에 소수점 처리 때문에 꽤나 애먹었던 기억이 있다.
이리저리 검색도 해보고, 도움말도 보고....

<script language= "JavaScript">
// strValue : 처리하려는 값
// strType :  '* 소수점 계산. 0:절사, 1:반올림, 2:올림
function fnFixNum( strValue, strType)
{
    var iType =  parseInt( strType );
    switch ( type )
    {
        case 0 :  // 절사
                return( parseInt( strValue ) );
                break;

        case 1 :  // 반올림
                if( strValue< 0 ){  //음수일 경우
               
                    if( ( Math.abs( strValue ) + 0.5 )
                         - ( parseInt( Math.abs( strValue ) + 0.5 ) ) == 0 ){
                          return ( ( parseInt( Math.abs( strValue ) + 0.5 ) * -1 ) +1 );
                    }
                    else
                    {
                          return ( parseInt( Math.abs( strValue ) + 0.5 ) * -1 );
                    }

                } else { //양수 일 경우
                     return ( parseInt( strValue + 0.5 ) )
                }
                break;

        case 2 : //올림
                if( ( value - parseInt( strValue ) ) >= 0.1 ) {
                     return ( parseInt( strValue ) +1 );
                }
                if( ( value - parseInt( strValue ) ) <= -0.1 ){
                     return ( parseInt( strValue ) -1 ) ;
                }

        }//End switch
       
}//End function
</script>

javascript Trim() 함수

String.prototype.trim() = function()
{
      return this.replace(/(^ *)|( *$)/g, "");
}

String.prototype.ltrim() = function()
{
      return this.replace(/(^ *)/g, "");
}

String.prototype.rtrim() = function()
{
      return this.replace(/( *$)/g, "");
}

사용자 삽입 이미지























 
위에 사진은 얼마전에 다녀온 외도(거제도)에서 찍은 사진이다.
그늘과 빛이 들어오는 부분이 왠지 눈에 확 들어와서 찍어봤는데....
그때 느낌이랑 조금 틀려져서...ㅠ.ㅠ


사진을 찍는걸 아주 좋아라 하지는 않지만,
그래도 나름 즐기는 편이라서 이런 저런 사진을 찍기는 한다.

그동안 아주 미천한 실력과 하이엔드 뚝딱스를 몸에 걸치고
나름 찍는다고 돌아다니긴 하지만...쩝...

인물사진은 넘 어려워서 주로 풍경을 찍곤하는데
(실은 인물 사진을 찍어주면 넘 못찍어서 이래저래 욕먹는다는.....
글구...풍경은 잘 안움직이니까 ^^;;;;)

몇 장 안되지만 그동안 찍은 사진들을
지인들에게 보여줄 기회가 생겨 보여준 적이 있었다.

그 지인들의 감상은 거의 한결 같았다고 해야...겠다...쩝....
그 중 아주 가까운 지인의 말,

'왜이리 하나같이 삭막한 분위기냐....ㅡ.ㅡ;;;
하긴. 니가 전문가도 아니니까...
뭐. 잘... 찍었네....'

사용자 삽입 이미지

친구들 덕에 어릴적 이후로 오랜만에 가본 남산타워
케이블 카를 타고 올라가는 동안
점점 높아지는 높이에 움찔움찔 했는데...

전망대에 올라 창가로 다가갔을 땐
더욱 움찔대는 몸 덕에
머리는 창가쪽에 몸통은 안쪽에 있는 민망한 자세로.......

출처 : http://tong.nate.com/reizes/22327742

LOG4J

I. 들어가면서.. 그리고 log4j
log4j는 자바 어플리케이션에서 빠르고 효과적으로 로깅 할 수 있도록 도와주는 오픈 소스 프로젝트입니다. 로깅(logging)은 코드의 가독성을 떨어뜨리는 단점이 있지만 애플리케이션에 문제가 있을 때 개발자가 자세한 상황을 파악할 수 있도록 해 주며 테스팅시 빠질 수 없는 요소입니다. 아마도 여러분들은 여러 어플리케이션이 추가되면서 각 개발자들만의 독특한 로깅방식이 서로 썩이고 얽혀서 화면에 나타나는것을 많이 봤을겁니다 -_-; 즉 로깅방법을 통일할 필요가 있는것이죠. 모든 개발자가 특정 포맷에 맞추어서 로깅 한다면 한결 로깅하기도 편하겠지요. 오픈 소스 프로젝트인 Log4j는 개발자들이 매우 손쉽고 다양한 형태로 로깅을 할 수 있도록 도와줍니다. 성능또한 우수해 더이상 System.out.println을 사용할 필요가 없습니다.

II. 다운로드
다운로드 http://logging.apache.org/log4j/docs/download.html
매뉴얼
http://logging.apache.org/log4j/docs/documentation.html
API spec
http://logging.apache.org/log4j/docs/api/index.html

III. LOG4J 구조
① Logger(Category) : 로깅 메세지를 Appender에 전달합니다.
② Appender : 전달된 로깅 메세지를 파일에다 기록할 것인지, 콘솔에 출력할 것인지 아니면 DB에 저장할 것인지 매개체 역활을 합니다.
③ Layout : Appender가 어디에 출력할 것인지 결정했다면 어떤 형식으로 출력할 것이지 출력 layout을 결졍합니다.

IV. LOG4J 로깅 레벨
① FATAL : 가장 크리티컬한 에러가 일어 났을 때 사용합니다.
② ERROR : 일반 에러가 일어 났을 때 사용합니다.
③ WARN : 에러는 아니지만 주의할 필요가 있을 때 사용합니다.
④ INFO : 일반 정보를 나타낼 때 사용합니다.
⑤ DEBUG : 일반 정보를 상세히 나타낼 때 사용합니다.

만약 로깅 레벨을 WARN 으로 설정하였다면 그 이상 레벨만 로깅하게 됩니다.
즉 WARN, ERROR, FATAL 의 로깅이 됩니다.

V. 샘플코드 1

test.jsp</P><BR><P><%@pagecontentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger = Logger.getLogger("test.jsp");
%>

<%
logger.fatal("fatal!!");

logger.fatal("fatal2!!", new NullPointerException("널"));

logger.error("error!", new NumberFormatException());

logger.error("error!2");

logger.warn("warn");

logger.info("info");

logger.debug("debug");
%>


결과 콘솔화면








static Logger logger = Logger.getLogger("test.jsp");
static 메소드 getLogger를 통해 logger 인스턴스를 가져옵니다. getLogger에는 파라미터로 스트링 혹은 클래스를 사용하는데 jsp에서는 클래스를 파라미터로 주기에는 좀 애매합니다. 그냥 스트링으로 주도록 하지요

logger.fatal("fatal!!");
[ logger.fatal("fatal2!!", new NullPointerException("널입니다요")); ]
logger에 fatal 레벨의 메세지를 전달합니다. 다음 두가지 메소드를 지원하는군요

fatal(Object message) , fatal(Object message, Throwable t)

각 레벨마다 위처럼 두가지 메소드를 지원합니다.

지원 메쏘드
logger.fatal(Object message) logger.fatal(Object message, Throwable t)
logger.error(Object message) logger.error(Object message, Throwable t)
logger.warn(Object message) logger.warn(Object message, Throwable t)
logger.info(Object message) logger.info(Object message, Throwable t)
logger.debug(Object message) logger.debug(Object message, Throwable t)

VI. 샘플코드 2

TestServlet.java

import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class TestServlet extends HttpServlet {
    static Logger logger = Logger.getLogger(TestServlet.class);

    public void init(ServletConfig config) throws ServletException {
         super.init(config);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         try {
              ...
              logger.info("Hellow World~");
              ...
          } catch (Exception e) {
              logger.error("Error at TestServlet", e);
          }
     }
}



VII. LOG4J 설정
프로그램에서 설정

<TD><%@pagecontentType="text/html;charset=MS949"  import="org.apache.log4j.*,java.io.* " %>
<%!
 static Logger logger = Logger.getLogger("log4j.jsp");
%>

<%
String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
String logfilename = "DailyLog.log";
String datePattern = ".yyyy-MM-dd ";

PatternLayout patternlayout = new PatternLayout(layout);
DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
logger.fatal("fatal!!");
%>


property 파일에 설정
log4j.properties를 만들어 /WEB-INF/classes 밑에 놓으세요

log4j.rootLogger=INFO, stdout, rolling
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
log4j.appender.rolling.File=output.log
log4j.appender.rolling.Append= true
log4j.appender.rolling.MaxFileSize=500KB
log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#최상위 카테고리에 INFO로 레벨 설정 및 appender로 stdout, rolling을 정의
log4j.rootLogger= INFO, stdout, rolling

#stdout 어펜더는 콘솔에 뿌리겠다는 정의
log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#stdout 어펜더는 patternlayout을 사용하겠다는 정의
log4j.appender.stdout.layout= org.apache.log4j.PatternLayout

#페턴은 다음과 같이 포맷팅 하겠다는 것을 정의
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#역시나 rolling 어펜더는 파일로 처리한다라고 정의
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender

#로그 파일 이름은 output.log
log4j.appender.rolling.File=output.log

#true면 톰캣을 내렸다 올려도 파일이 리셋되지 않습니다.
log4j.appender.rolling.Append= true

#파일 최대 사이즈는 500KB로 설정
log4j.appender.rolling.MaxFileSize= 500KB

#파일 포맷은 output.log.2005-03-10 으로 관리하겠다고 정의
log4j.appender.rolling.DatePattern= '.'yyyy-MM-dd

#역시나 rolling 어펜더는 패턴 레이아웃을 사용하겠다고 정의
log4j.appender.rolling.layout= org.apache.log4j.PatternLayout

#rolling 어펜더는 패턴 레이아웃 포맷
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

VIII. 설정 포맷
로그파일명 포맷 (DatePattern)
로그파일명 포맷입니다. 날짜, 시간 및 분단위로까지 로그 파일을 분리할 수 있습니다.

형식 설명
'.'yyyy-MM 매달 첫번째날에 로그파일을 변경합니다
'.'yyyy-ww 매주의 시작시 로그파일을 변경합니다.
'.'yyyy-MM-dd 매일 자정에 로그파일을 변경합니다.
'.'yyyy-MM-dd-a 자정과 정오에 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH 매 시간의 시작마다 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH-mm 매분마다 로그파일을 변경합니다.

PatternLayout 포맷
로그자체를 어떤 포맷으로 남길지 결정합니다.
layout에는 HTMLLayout, PatternLayout, SimpleLayout, XMLLayout등이 있으며 PatternLayout이 일반적으로 가장 많이 쓰입니다.

형식 설명
%p debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m 로그내용이 출력됩니다
%d 로깅 이벤트가 발생한 시간을 기록합니다.포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t 로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%% % 표시를 출력하기 위해 사용한다.
%n 플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
%c 카테고리를 표시합니다
예) 카테고리가 a.b.c 처럼 되어있다면 %c{2}는 b.c가 출력됩니다.
%C 클래스명을 포시합니다.
예) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면 %C{2}는 xyz.SomeClass 가 출력됩니다
%F 로깅이 발생한 프로그램 파일명을 나타냅니다.
%l 로깅이 발생한 caller의 정보를 나타냅니다
%L 로깅이 발생한 caller의 라인수를 나타냅니다
%M 로깅이 발생한 method 이름을 나타냅니다.
%r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x 로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를 출력합니다.
%X 로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를 출력합니다.

예시) (같은 색끼리 보시면 됩니다) 위의 test.jsp를 다음 포맷으로 출력해본다면

[%c] [%C] [%d] [%F] [%l] [%L] [%m] [%M] [%n] [%p] [%r] [%t] [%x] [%X]는 다음과 같다

[test.jsp] [org.apache.jsp.test_jsp] [2005-03-10 12:37:23,561] [test_jsp.java] [org.apache.jsp.test_jsp._jspService(test_jsp.java:64)] [64] [fatal!!] [_jspService] [개행] [FATAL] [765567] [http-8080-Processor25] [] []

=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com 
=============================================

읏... struts.xml파일을 classes폴더에 두어야 하는데......
정말 바보처럼 Web-Inf 폴더에 두고 삽질만 2시간...쩝....


튜토리얼을 보니
struts2.x 는 브라우저 주소창에 *.action이라고 적어줘야 action이 일어난다고..
action이라고 다적을라니 struts1.x 의 *.do가 너무 그리웠다...ㅡ.ㅡ;;;;
(라기보다는 action이라는 단어가 귀찮았다...쩝....)

네이버 언냐~나 구글 어빠~ 등을 전전 긍긍하면서 찾아봤더니
역시나 struts.action.extension 이라는 설정항목이 있었다.

struts.properties파일에서 설정해주는 내용들이 대부분이었는데...
설정파일이 너무 많아지는 것같아서,
'한개의 파일에서 처리는 안돼는겨??' 란 오기아닌 오기가...
(설정파일이 너무 많다는건 언제나 불만이었다.)

딱! 맞는 내용은 찾을 수 없었지만... encoding설정 하는 내용에서 힌트를 얻어서
로컬에서 테스트를 해봤다....
하지만 위에있는 실수와 삽질덕에.....

잊어버리기 전에(그렇진 않겠지만...) 적어둔다.
3개중에 1개만 해주면 된다.

=== Web.xml 에서의 설정 ===
....
<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
   <init-param>
      <param-name>struts.i18n.encoding</param-name>
      <param-value>UTF-8</param-value>
   </init-param>
   <init-param>
      <param-name>struts.devMode</param-name>
      <param-value>true</param-value>
   </init-param>
   <init-param>
      <param-name>struts.action.extension</param-name>
      <param-value>action,do</param-value>
   </init-param>
</filter>
...

=== struts.xml 에서의 설정 ===
...
<struts>
    <constant name="struts.i18n.encoding" value="UTF-8" />
    <constant name="struts.devMode" value="true" />
    <constant name="struts.action.extension" value="action,do" />
...
</struts>
...


=== struts.properties 에서의 설정 ===
...
struts.action.extension=action,do
...

+ Recent posts