일을 하다보면 다른 소유자(계정)간에 협업을 할 때 데이터 제공 등의 목적으로 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항목에서 설정한 포트에 맞게 호출하여 정상적으로 실행되고 있는지 확인합니다. 정상적으로 실행되고 있다면 아래와 같은 화면을 확인할 수 있습니다.



+ Recent posts