반응형

■ Visual Studio 2010, SQL Server 2014 기준으로 개발

SQL 서버에 OpenAPI 호출하는 CLR을 등록하여, 거래처가 특정 조건을 만족하는지 검사하는 기능을 개발하게 되었는데 이 과정의 일부를 남깁니다.

CLR 프로젝트 생성

우선 Visual Studio에서 다음과 같이 SQL Server > Visual C# SQL CLR 데이터베이스 프로젝트를 시작합니다.

개발할 CLR이 SQL Sever에서 참조할 구성요소를 선택할 필요가 없으면 데이터베이스 참조는 취소해도 됩니다.

저의 경우 JSON을 처리하는 다른 DLL의 참조가 필요하여 DB 연결 정보를 입력하였습니다.
만약 연결된 DB 정보가 없이 참조 추가하려고 하면 다음과 같이 추가가 불가능합니다.

Visual Studio에서 기본적으로 생성되는 프로젝트 구성은 다음과 같습니다.

클래스 파일 추가

이제 실제 로직을 C#으로 구현하기 위하여 새로운 클래스 파일을 추가합니다 (이름은 NIMS.cs로 하였습니다).

추가된 빈 클래스 파일의 모습입니다.

API 스펙 확인

사용하려는 OpenAPI의 스펙은 다음과 같습니다.

JSON for. NET

위 OpenAPI의 호출 결과는 JSON으로 전달되기 때문에 JSON을 파싱 하기 위하여 JSON for. NET이라는 오픈 라이브러리를 사용합니다.

다음 사이트에서 파일을 내려받습니다.
sourceforge.net/projects/csjson/

내려받은 json-for-dotnet-1.2.zip 파일의 압축을 풀어서 System.Net.Json.dll 파일을 SQL 서버에 복사합니다.

제가 만든 CLR에서 이 System.Net.Json.dll을 참조해야 하므로 다음과 같이 SQL 서버에 어셈블리 등록을 합니다.

EXEC SP_CONFIGURE 'clr enabled', 1
RECONFIGURE WITH OVERRIDE 

ALTER DATABASE [MyDB] SET 
TRUSTWORTHY ON

CREATE ASSEMBLY Json
FROM 'D:\System.Net.Json.dll'    
WITH PERMISSION_SET = UNSAFE

그런데 JSON for. NET에서 제공해주는 기본 파서 기능이 좀 빈약하여, OpenAPI 결과를 처리할 수 있도록 기능 보완된 파서를 만들기 위하여 클래스 파일을 하나 더 추가합니다 (이름은 MyJsonParser.cs으로 했습니다).

그리고 다음과 같이 로직을 작성하였습니다.

MyJsonParser.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Json;

namespace NIMSAPI
{
    class MyJsonParser
    {
        private JsonTextParser parser;
        private JsonObjectCollection joc;

        public MyJsonParser(string planText = "")
        {
            parser = new JsonTextParser();
            if (!string.IsNullOrEmpty(planText))
            {
                joc = (JsonObjectCollection)parser.Parse(planText);
            }
        }

        // 생성자에서 주어진 초기 문자열 외에 추가적으로 파싱할 때 사용
        public void Parse(string planText)
        {
            planText = planText.Replace("\r", "").Replace("\n", "").Replace("\t", "");    // 파싱 오류방지를 위해 tab 및 개행문자열 제거

            joc = (JsonObjectCollection)parser.Parse(planText);
        }

        public object GetValue(string namePath, char split = '/', bool returnEmptyWhenNull = true)
        {
            string[] names = namePath.Split(split);
            JsonObjectCollection obj = null;

            if (names.Length == 1)
            {
                if (joc[namePath] is JsonObjectCollection)
                    obj = (JsonObjectCollection)joc[namePath];

                if (joc[namePath] is JsonArrayCollection)
                    obj = joc;
            }
            else
            {
                for (int i = 0; i < names.Length - 1; i++)
                {
                    if (i == 0)
                        obj = (JsonObjectCollection)joc[names[i]];
                    else
                        obj = (JsonObjectCollection)obj[names[i]];
                }
            }

            if (obj != null)
            {
                if (obj[names[names.Length - 1]] is JsonArrayCollection)
                    return obj[names[names.Length - 1]];
                else
                    return obj[names[names.Length - 1]].GetValue();
            }
            else
            {
                object retObj = null;

                if ((joc as JsonObjectCollection)[namePath] != null)
                {
                    retObj = (joc as JsonObjectCollection)[namePath].GetValue();
                }

                if (retObj == null && returnEmptyWhenNull)
                {
                    retObj = string.Empty;
                }

                return retObj;
            }
        }
    }
}

참조 추가

솔루션 탐색기에서 참조 추가를 눌러서 

앞에서 등록한 Json 어셈블리를 선택합니다.

정상적으로 추가되면 다음과 같이 참조 하위에 포함되게 됩니다.

이제 NIMS.cs에 OpenAPI를 호출하고 결과를 MyJsonParser로 파싱 처리하는 소스를 작성하였습니다.

NIMS.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.Net.Json;

namespace NIMSAPI
{
    public class NIMS
    {
        // 상대 마약류취급자식별번호가 존재하는지 체크
        //      BizNo: 매출거래처의 사업자번호
        public static bool IsExistsBsshCode(string BizNo)
        {
            string Url = "https://www.nims.or.kr/api/bsshinfo.do";
            bool result = false;

            try
            {
                WebClient webClient = new WebClient();

                NameValueCollection formData = new NameValueCollection();
                formData["k"] = "XXXXXXXXX"; //인증키
                formData["fg"] = "1";
                formData["pg"] = "1";
                formData["bi"] = BizNo;

                byte[] responseBytes = webClient.UploadValues(Url, "POST", formData);
                string Result = Encoding.UTF8.GetString(responseBytes);

                MyJsonParser myParser = new MyJsonParser(Result);

                string ResultCode = myParser.GetValue("response/header/RESULT_CODE").ToString();
                int CustCount = int.Parse(myParser.GetValue("response/body/TOTAL_COUNT").ToString());

                JsonArrayCollection lists = (JsonArrayCollection)myParser.GetValue("response/body/list"); 

                int count = 0;
                foreach (JsonObjectCollection collection in lists)
                {
                    myParser.Parse(collection.ToString());  // 배열 요소를 다시 파싱 시킨다

                    string OppNarcoticManagerID = myParser.GetValue("BSSH_CD").ToString();        // 상대 마약류취급자식별번호
                    result = !string.IsNullOrEmpty(OppNarcoticManagerID);

                    if (++count == 1) break;    // 동일 거래처가 여러개 조회되는 경우가 있어서 한 번만 처리되게 함
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }
    }

}

F6을 눌러서 빌드합니다.

빌드된 NIMSAPI.dll 파일을 SQL 서버에 복사하고 어셈블리 등록을 합니다.

CREATE ASSEMBLY NIMS
FROM 'D:\NIMSAPI.dll'    
WITH PERMISSION_SET = UNSAFE

참고로 다음과 같이 소유자 SID 문제로 어셈블리 등록이 되지 않는 경우 ALTER AUTHORIZATION로 해당 DB의 소유자를 변경해야 합니다.

/*
master 데이터베이스에 기록된 데이터베이스 소유자 SID가 데이터베이스 'MyDB'에 기록된 
데이터베이스 소유자 SID와 다릅니다. 
ALTER AUTHORIZATION 문을 사용하여 데이터베이스 'MyDB'의 소유자를 다시 설정하여 
이 문제를 해결해야 합니다.
*/

-- 현 소유자 확인
SELECT name AS [Database],
       SUSER_SNAME(owner_sid) AS [Owner]
FROM sys.databases
WHERE [name] = 'MyDB'

-- 소유자 변경
ALTER AUTHORIZATION ON DATABASE:: MyDB TO sa ;

사용자 함수 생성

등록된 CLR 어셈블리를 SQL 서버에서 함수 형태로 호출할 수 있도록 함수를 생성합니다.

CREATE FUNCTION FN_NIMS_CUST_EXISTS
(
    @p_BizNo NVARCHAR(20)
)
RETURNS BIT AS EXTERNAL NAME NIMS.[NIMSAPI.NIMS].IsExistsBsshCode


테스트

이 글이 도움이 되셨으면 공감(하트)과 댓글을 부탁 드려요!

반응형
반응형

매번 컴퓨터 사용을 끝내고 윈도우를 종료했다가 다시 부팅하면 또다시 기본적인 프로그램을 실행시켜야 하고 그 전날 또는 마지막 작업한 환경을 불러오는 등의 번거로운 과정을 반복해야 합니다.

잠깐 동안 컴퓨터를 사용하지 않는 경우라면 절전모드를 이용할 수도 있지만, 장시간 또는 사무실과 같이 퇴근하고 다음날 다시 켜야 하는 경우에는 '최대 절전 모드'를 활용하면 불필요한 전력을 낭비하지도 않고 컴퓨터를 끄기 직전의 상태를 그대로 원복 시켜 주기 때문에 곧바로 마지막 작업한 이후의 업무를 신속하게 이어갈 수 있습니다.

방법은 간단합니다. 윈도우 시작 > 전원 버튼을 누르면 나오는 메뉴에서 최대 절전 모드를 선택하면 메모리를 dump 해서 하드디스크에 저장한 후 컴퓨터가 종료하게 됩니다.

그런데 만약 다음과 같이 최대 절전 모드 메뉴가 보이지 않는다면 이를 아래의 과정을 통해서 활성화시켜야 합니다.

우선 명령 프롬프트를 열고 다음과 같이 입력하여 최대 절전 모드 기능을 사용할 수 있도록 합니다.

powercfg /h on

다음으로 시작 메뉴 검색창에서 '전원'을 입력하여 '전원 및 절전 설정'을 선택하고 다시 '추가 전원 설정'을 선택합니다.

제어판의 전원 옵션 창이 뜨면 '전원 단추 작동 설정'을 선택합니다.

이어지는 화면에서 '현재 사용할 수 없는 설정 변경'을 선택합니다.

종료 설정 옵션 하위의 '최대 절전 모드'를 선택(체크)하고 저장 버튼을 누릅니다.
참고로 '전원 단추를 누를 때:'의 옵션으로 최대 절전 모드를 지정해 놓으면 컴퓨터 전원 버튼만으로 최대 절전 모드 사용이 가능합니다.

이제 윈도우 시작 > 전원 버튼을 누르면 '최대 절전 모드' 항목이 나타나게 됩니다. 

반응형
반응형

이따금 SQL서버에서
"메시지 3998, 커밋할 수 없는 트랜잭션이 일괄 처리 맨 끝에서 검색되었습니다. 트랜잭션이 롤백됩니다."
라는 오류가 발생하는 경우가 있습니다.

기술적 내용을 알고나면 지극히 당연한 오류 메시지인데, 그 전에는 도대체 무슨 뜻인지 이해하기가 쉽지 않습니다.

SQL서버에서 명시적으로 트랜잭션을 시작하는 경우, 런타임 중에 오류가 발생하면 해당 트랜잭션에 대하여 직접 완료(커밋 또는 롤백)처리를 해야 하는 모드와 SQL서버가 자동으로 롤백 처리해 주는 모드가 있습니다.

다음 명령을 통해서 모드 변경이 가능 합니다.

SET XACT_ABORT { ON | OFF }

T-SQL 디폴트는 OFF 상태 이며, 이 때는 직접 완료처리를 해야 합니다.

USE AdventureWorks2014
GO

SET XACT_ABORT OFF;
GO

BEGIN TRANSACTION; --- ①

SELECT 10 /0       --- ②
SELECT TOP 1 * FROM Sales.Store    --- ③

SELECT @@TRANCOUNT as trnCount

① 에서 명시적 트랜잭션이 시작 되었습니다.
② 에서 0으로 나누기 오류가 발생 됩니다. 그러나 다음 행의 쿼리가 계속 수행 됩니다.
③ SELECT 문이 정상적으로 수행 됩니다.

결과는 다음과 같습니다.

이 시점에서 @@TRANCOUNT는 보시는 바와 같이 1 입니다.
따라서 개발자가 롤백을 하든지 커밋을 하든지 직접 해야 합니다.
물론 위에서는 UPDATE, DELETE 가 없으므로 커밋을 하든 롤백을 하든 결과가 달라질 것은 없습니다.
XACT_ABORT가 OFF 상태일 때는 런타임 오류가 발생하여도 오류가 발생된 이후의 쿼리가 계속 수행되고
트랜잭션 상태가 유지 됩니다.

이제 XACT_ABORT가 ON인 경우를 보겠습니다.

USE AdventureWorks2014
GO

SET XACT_ABORT ON;   --- ①
GO

BEGIN TRANSACTION;

SELECT 10 /0   --- ②
SELECT TOP 1 * FROM Sales.Store

SELECT @@TRANCOUNT as trnCount

① 에서 XACT_ABORT가 ON으로 변경 되었고, 나머지 쿼리는 전과 동일 합니다.

결과는 다음과 같습니다.

② 에서 0 나누기 오류가 발생하자 그 이후의 쿼리는 수행되지 않고 트랜잭션이 종료되고 자동으로 롤백이 됩니다.
따라서 이때 개발자가 직접 롤백처리 하고자 하면 오류가 발생 합니다.

그럼 "커밋할 수 없는 트랜잭션이 일괄 처리 맨 끝에서 검색되었습니다. 트랜잭션이 롤백됩니다." 오류는 언제 발생하는 걸까요?

다음 쿼리의 실행 결과를 보겠습니다.

USE AdventureWorks2014
GO

SET XACT_ABORT ON;
GO

BEGIN TRANSACTION;
BEGIN TRY   --- ①
    SELECT * FROM person.Person WHERE BusinessEntityID = 1

    INSERT INTO person.personPhone (BusinessEntityID, PhoneNumber, PhoneNumberTypeID)
    VALUES (1, '697-555-0142', 1)    --- ②

    COMMIT
END TRY
BEGIN CATCH  --- ③
    SELECT XACT_STATE() AS XACTSTATE,  ERROR_NUMBER() AS ERR_NUMBER,   --- ④
           ERROR_MESSAGE() AS ERR_MESSAGE 
END CATCH
GO

SELECT @@TRANCOUNT as trnCount

명시적 트랜잭션 실행 후,
① 에서 TRY/CATCH를 처리하고 있습니다.
② 에서 PRIMARY KEY 제약 조건 위반(중복 키 삽입) 오류가 발생 됩니다.
③ TRY 내에서 발생된 오류(예외)로 인해서 CATCH로 제어가 넘어 오게 됩니다.

드디어 "커밋할 수 없는 트랜잭션이 일괄 처리 맨 끝에서 검색되었습니다. 트랜잭션이 롤백됩니다." 오류가 발생 하였습니다. 

XACT_ABORT가 ON 상태에서 명시적으로 트랜잭션이 시작되고, TRY 내에서 예외 발생이 되었으나 CATCH에서 트랜잭션에 대하여 롤백 처리를 하지 않고 일괄처리가 끝나면 위와 같은 오류가 발생하게 됩니다.

이제는 오류 메시지가 무슨 의미인지 이해가 될 것입니다.

참고로 ④에서 XACT_STATE()를 사용하고 있는데 반환값이 -1 이면 활성 사용자 트랜잭션이 있지만 오류가 발생하여 트랜잭션이 커밋할 수 없는 상태라는 의미 입니다.
만약 이 상태에서 CATCH 내에서 ROLLBACK이 아닌 COMMIT을 시도하게 되면 다음과 같은 오류가 발생 합니다.

현재 트랜잭션은 커밋할 수 없으며 로그 파일에 쓰는 작업을 지원하지 않습니다. 트랜잭션을 롤백하십시오

이 글이 도움이 되셨으면 추천과 댓글을 부탁 드려요!

반응형
반응형
  • BOX(container) 및 Item 들을 열과 행에 자유자재로 나타내기 위한 CSS 기술
  • 예전에는 layout을 위해 table, float tag를 사용 했으나 현재는 flexbox로 모두 대체 가능
  • 서로 다른 screen size 에서 HTML 요소들의 재배치를 통해서 반응형 웹 구현 가능

1. main axis, cross axis 개념

  • 횡(가로)을 중심축으로 하면 직각으로 대응되는 종(세로)이 반대축이 된다.

  • 종(세로)을 중심축으로 하면 직각으로 대응되는 횡(가로)이 반대축이 된다.

2. single line, multi line 개념

  • multi line은 flex-basis 값과 flex-wrap:wrap 속성에 의해 한 줄로 표현하기에 빈 공간이 모자라는 경우 발생

3. container에 적용되는 속성

  • display: flexbox를 쓰기 위하여 flex로 지정
  • flex-direction: main 축 기준으로 각 item이 놓여지는 방향 (row, column, row-reverse, column-reverse)
  • flex-wrap: 한 줄에 item이 지정된 크기를 유지할 수 없을만큼 꽉 찼을 떄 자동으로 행 분리 여부 (nowrap, wrap,wrap-reverse).
    default인 nowrap인 경우에는 item의 크기를 줄여서 한 행을 유지.
  • flex-flow: flex-direction 과 flex-wrap 속성값을 한꺼번에 지정 가능
  • justify-content: 중심축을 기준으로 각 item 사이의 간격 및 부모 box와의 간격 (flex-start, flex-end, center, space-around, space-evenly, space-between)

justify-content

  • align-items: 반대축에서 item을 어떻게 정렬할 지 (stretch, flex-start, flex-end, center, baseline, stretch)

align-items

  • align-content: 반대축에서 item을 어떻게 배치할 지 (stretch, flex-start, flex-end, center, space-around, space-evenly, space-between)

align-content

4. item에 적용되는 속성

  • order: HTML tag가 나오는 순서와 관계 없이 item이 나열되는 순서를  임의로 변경할 때 숫자 지정
  • flex-grow: container가 늘어날 때 width 크기에 맞춰서 한 줄을 꽉 채우도록 item의 크기를 늘릴 비례값(숫자) 지정(기본값 0)

flex-grow

  • flex-shrink: flex-grow와 반대로 container가 줄어들 때 width 크기에 맞춰서 item의 크기를 줄일 비례값(숫자) 지정(기본값 1)
  • flex-basis: 기본 값 auto(grow, shrink에 지정된 값 이용). grow, shrink 없이 basis에 비율('%')로 값을 지정
  • flex: grow, shrink, basis를 한꺼번에 지정
  • align-self: container 지정값(justify-content, align-items, align-content)을 벗어나 item 독자적으로 배치 (center, flex-start, flex-end)

align-self

* 본문 내 이미지 일부는 css-tricks.com/snippets/css/a-guide-to-flexbox/ 에서 capture 함.
 
반응형

+ Recent posts