이클립스 내에서 따로 생성하실 수 있습니다.

Ctrl + N을 누르거나

패키지를 마우스로 우클릭하신 후 New - Other를 선택

General - Untitled Text File이란 목록이 있다.

이걸 선택하면 Untitled 1 이런 식으로 창이 하나 뜬다.

여기서 db.properties의 내용을 적은 후

Save As - 파일 경로 선택 및 File Name(db.properties) 기입

이렇게 하시면 db.properties파일을 생성하실 수 있다

라디오 버튼이나 체크박스를 클릭할때
해당 버튼을 클릭하지 않고 버튼옆에 텍스트를 클릭해도
그 버튼이 선택되도록 하는 태그입니다.
스크립트를 쓰지 않고 태그로 간단하게 사용이 가능합니다.

일반 사용예 : <input type="checkbox" id="chk1">체크박스1
label 사용예 : <label for="chk2"><input type="checkbox" id="chk2" name="chk2">체크박스2</label>

해당 input 태그에 사용할 id 를 정해주고
위 처럼 라벨 태그의 for="해당id" 해주시면 됩니다.

'JQuery' 카테고리의 다른 글

체크박스 하나만 선택되도록 하는 함수  (0) 2020.02.11

function onlyOneCheck(groupName){
     var chk = $('[data-group="'+groupName+'"]').find('input[type="checkbox"]');
     chk.click(function(){
         if($(this).is(':checked')){
            chk.not(this).prop('checked',false); // .not() : 선택한 요소 제외한 나머지 요소
        }
    })
}

//체크박스 한개만 선택 되도록 onload시 설정 

$(document).ready(function(){
    onlyOneCheck("checkFail");
});

<th> 내/외부요인 </th>

<td data-group="checkFail">

    <div clas ="check floatLeft">

    <input id="in"  class="checkbox-custom" name="anctInoutFactorCode" value="I" type="checkbox" /> 내

    <input id="out"  class="checkbox-custom" name="anctInoutFactorCode" value="O" type="checkbox" /> 외

   </div>

</td> 

 

 

 

개요
Character warrior = Character.newWarrior();
Character mage = Character.newMage();

객체 생성을 캡슐화 하는 기법이다. 좀 더 자세히 설명하자면 객체를 생성하는 메소드를 public static method로  만드는 것이다.

예를 들어 자주 사용하는 BigInterger의 valueOf() 메소드가 대표적이다.

static으로 선언된 메서드이며, new BigInteger(...)를 은닉하고 있다는 사실을 알 수 있다.

BigInteger answer = BigInteger.valueOf(42L); // BigInteger(42L)를 리턴한다. 

BigInteger의 valueOf메소드를 살펴보자.  return new BigInteger(val)이 보이는가?

public static 메소드 내부에서 객체를 생성해서 return해주고 있다.  

 public static BigInteger valueOf(long val) {
        // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
        if (val == 0)
            return ZERO;
        if (val > 0 && val <= MAX_CONSTANT)
            return posConst[(int) val];
        else if (val < 0 && val >= -MAX_CONSTANT)
            return negConst[(int) -val];

        return new BigInteger(val);
    }

valueOf 외에, 정적 팩토리 메서드의 이름으로 흔히 사용되는 것들은 다음과 같다.

  • valueOf
  • of
  • getInstance
  • newInstance
  • getType
  • newType
Effective java 규칙1 - 생성자 대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라.
  • 단, GoF-Design-Pattern 책에 나오는 팩토리 메서드 패턴과는 다른 패턴이다. 이름만 비슷하다.
  • Effective Java 저자 조슈아 블로흐도 GoF-Design-Pattern 책에 나온 어떤 패턴과도 맞아 떨어지지 않는다며 주의하라고 한다.

Effective Java에서는 다음과 같은 장단점을 설명한다.

  • 장점
    1. 이름이 있으므로 생성자에 비해 가독성이 좋다.
    2. 호출할 때마다 새로운 객체를 생성할 필요가 없다.
    3. 하위 자료형 객체를 반환할 수 있다.
    4. 형인자 자료형(parameterized type) 객체를 만들 때 편하다.
  • 단점
    1. 정적 팩토리 메서드만 있는 클래스라면, 생성자가 없으므로 하위 클래스를 못 만든다.
    2. 정적 팩토리 메서드는 다른 정적 메서드와 잘 구분되지 않는다. (문서만으로 확인하기 어려울 수 있음)
  • 특징

1.가독성이 좋다.

다음은 전사와 마법사가 나오는 판타지 게임 소스 코드의 일부이다.

class Character {

    int intelligence, strength, hitPoint, magicPoint;

    public Character(int intelligence, int strength, int hitPoint, int magicPoint) {
        this.intelligence = intelligence;   // 지능
        this.strength = strength;           // 힘
        this.hitPoint = hitPoint;           // HP
        this.magicPoint = magicPoint;       // MP
    }

    // 정적 팩토리 메소드
    public static Character newWarrior() {
        return new Character(5, 15, 20, 3);     // 전사는 힘과 HP가 높다
    }

    // 정적 팩토리 메소드
    public static Character newMage() {
        return new Character(15, 5, 10, 15);    // 마법사는 지능과 MP가 높다
    }
}

만약 생성자를 사용해 전사나 마법사를 생성한다면 다음과 같을 것이다.

Character warrior = new Character(5, 15, 20, 3);
Character mage = new Character(15, 5, 10, 15);

변수명이 없었다면 5, 15, 20, 3 같은 연속된 숫자만으로는 캐릭터의 직업을 알아보기 어려웠을 것이다.

하지만 정적 팩토리 메서드를 사용한다면 좀 더 읽기 쉬운 코드가 된다.

2.호출할 때마다 새로운 객체를 생성할 필요가 없다

사실 위와 같이 마법사와 전사를 만드는 코드는 정적 팩토리 메서드를 호출할 때마다 new Character(...)를 호출하게 된다. 그러나 immutable 객체를 캐시해서 쓰고 있다면 굳이 일일이 new 같은 비싼 연산을 사용할 필요가 없다.

다음은 개요에서 호출 코드의 예로 사용했던 java.math.BigInteger.valueOf메서드의 코드이다.

public static final BigInteger ZERO = new BigInteger(new int[0], 0);

private final static int MAX_CONSTANT = 16;
private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1];
private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1];

static {
    /* posConst에 1 ~ 16까지의 BigInteger 값을 담는다. */
    /* negConst에 -1 ~ -16까지의 BigInteger 값을 담는다. */
}

public static BigInteger valueOf(long val) {
    // 미리 만들어둔 객체를 리턴한다
    if (val == 0)
        return ZERO;
    if (val > 0 && val <= MAX_CONSTANT)
        return posConst[(int) val];
    else if (val < 0 && val >= -MAX_CONSTANT)
        return negConst[(int) -val];

    // 새로운 객체를 만들어 리턴한다
    return new BigInteger(val);
}

위와 같은 방법을 사용하면 흔히 사용하는 0 같은 값을 호출시마다 일일이 생성하는 일을 피할 수 있다.

3.하위 자료형 객체를 반환할 수 있다

리턴하는 객체의 타입을 유연하게 지정할 수 있다. 다음은 어느 가상의 인터넷 쇼핑몰에서 할인 코드를 처리하는 정적 팩토리 메서드이다.

class OrderUtil {

    public static Discount createDiscountItem(String discountCode) throws Exception {
        if(!isValidCode(discountCode)) {
            throw new Exception("잘못된 할인 코드");
        }
        // 쿠폰 코드인가? 포인트 코드인가?
        if(isUsableCoupon(discountCode)) {
            return new Coupon(1000);
        } else if(isUsablePoint(discountCode)) {
            return new Point(500);
        }
        throw new Exception("이미 사용한 코드");
    }
}

class Coupon extends Discount { }
class Point extends Discount { }

할인 코드의 규칙에 따라 Coupon과 Point 객체를 선택적으로 리턴하고 있다.

이를 위해서는 두 하위 클래스가 같은 인터페이스를 구현하거나, 같은 부모 클래스를 갖도록 하면 된다.

만약 파일을 분리하기 애매한 작은 클래스가 있다면 private class를 활용할 수도 있다.

다음은 java.util.Collections에서 EMPTY_MAP 부분만 발췌한 것이다.

@SuppressWarnings("rawtypes")
public static final Map EMPTY_MAP = new EmptyMap<>();

/**
 * Returns an empty map (immutable).  This map is serializable.
 */
@SuppressWarnings("unchecked")
public static final <K,V> Map<K,V> emptyMap() {
    return (Map<K,V>) EMPTY_MAP;
}

private static class EmptyMap<K,V> extends AbstractMap<K,V> implements Serializable {
    /* 생략 */
}

EmptyMap 클래스는 java.util.Collections 내에 private static으로 선언되었으며, emptyMap이라는 정적 팩토리 메서드를 통해 캐스팅된 인스턴스를 얻을 수 있다.

3. 형인자 자료형 객체를 만들 때 편리하다

Java 1.7 이전에는 다음과 같이 형인자 자료형 객체를 만들어야 했다.

Map<String, List<String>> list = new HashMap<String, List<String>>();

아무리 자동 완성이 있어도 타이핑하기 굉장히 짜증나는데, 정적 팩토리 메서드를 사용해서 다음과 같이 사용할 수 있었다.

// 정적 팩토리 메서드: type inference를 이용한다
public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}
// 위의 정적 팩토리 메서드를 사용한다
Map<String, List<String>> list = HashMap.newInstance();

그러나 이 장점은 Java 1.7 이후로는 의미를 거의 잃었다.

Map<String, List<String>> list = new HashMap<>();

쿼리를 짜다보면 트리 구조 형태로 데이터를 만들어야 하는 경우가 종종있다. 이를 경우 MSSQL에서 사용할 수 있는 방법이 있다. 바로 재귀 쿼리 이다. 재귀적 CTE의 기본 형식은 다음과 같다. 

WITH CTE_RECURSIVE(column_name[,...n]) AS
(
    <쿼리문1: SELECT * FROM TABLE_A // Anchor member
    UNION ALL
    <쿼리문2: SELECT * FROM TABLE_A JOIN CTE_테이블 이름 // Recursive member
)
-- Statement using the CTE --
SELECT * FROM CTE_RECURSIVE

재귀 실행의 의미 체계는 다음과 같습니다.

1. CTE 식을 앵커 멤버와 재귀 멤버로 분할합니다.
2. 앵커 멤버를 실행(쿼리문1)하여 첫 번째 호출 또는 기본 결과 집합(T0)을 만듭니다.
3. Ti는 입력으로 사용하고 Ti+1은 출력으로 사용하여 재귀 멤버(쿼리문2)를 실행합니다.
4. 빈 집합이 반환될 때까지 3단계를 반복합니다.
5. 결과 집합을 반환합니다. 이것은 T0에서 Tn까지의 UNION ALL입니다.

지금부터 예제를 통해 적용해보자. 

아래는 트리구조로 만든 임시 테이블이다.  

WITH TEST(UPPER_CODE,CODE,NAME) AS (
	SELECT NULL,10, 'LEVEL1'
	UNION ALL 
	SELECT '10','1001','LEVEL2'
	UNION ALL
	SELECT NULL,20, 'LEVEL1'
	UNION ALL
	SELECT '20','2001','LEVEL2'
	UNION ALL 
	SELECT '2001','2001001','LEVLE3'
)
SELECT * FROM TEST

<실행결과>

UPPER_CODE      CODE          NAME
NULL                 10              CODE10 
10                     1001           CODE1001 
NULL                 20              CODE20 
20                    2001           CODE2001 
2001                 2001001      CODE2001001

CODE 2001001을 보자. UPPER_CODE 가 2001이다.  그럼 다시 CODE가 2001인 데이터는 UPPDER CODE가 20이고 ITEM CODE=20인 데이터는 UPPER_CODE가 NULL이다. 즉, 레벨  구조가 아래와 같다. 

20                                                                                                                                                         L  2001                                                                                                                                                       L2001001

그럼 재귀 쿼리를 이용하여 각 CODE의 레벨을 지정 해보자. (레벨 1: 1, 레벨2: 2, 레벨3: 3) 각 레벨에 숫자를 부여하는 쿼리와 실행 결과는 아래를 참고하기 바란다. 

실행 결과는 아래와 같다 

그럼 좀더 응용해보자.  총 레벨이 3단계 까지 있으므로 각 코드의 상위 레벨들의 CODE와 NAME을 한 ROW에 모두 나오도록 QUERY를 작성해보자. 

예를 들어, 20                                   의 경우                                                                                                             L  2001                                                                                                                                                       L2001001

CODE1 CODE1_NAME CODE2 CODE2_NAME    CODE3     CODE3_NAME   LEVEL
20        ITEM20          2001  ITEM2001         2001001      ITEM2001001     3

이렇게 출력 될 수 있도록 말이다. 그럼 우선, 위 쿼리 실행 결과를 뷰 테이블로 만들고 다시 재귀 쿼리를 이용해야 한다. Microsoft SQL Server Management Studio에서 View 테이블을 직접 만들어도 되지만, 여기서 공부하는 용으로 정리하는 것이니 WITH AS 을 이용하여 위 데이터를 가지는 임시 vm_table을 만들어 사용했다.

WITH  VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS (
SELECT NULL, '10', 'ITEM10', 1
UNION ALL 
SELECT NULL, '20' ,'ITEM20', 1
UNION ALL 
SELECT '20', '2001' ,'ITEM12001', 2
UNION ALL
SELECT '2001', '2001001', 'ITEM2001001', 3
UNION ALL
SELECT '10', '1001', 'ITEM101001', 2
UNION ALL
SELECT '1001', '1001001', 'ITEM101001', 3

SELECT * FROM VM_CTE

이 뷰테이블 VM_CTE를 이용하여  작성한 쿼리와 실행 결과를 아래에 정리해두었다. 참고하기 바란다. 

 

WITH VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS (
    SELECT NULL, '10', 'ITEM10', 1
    UNION ALL
    SELECT NULL, '20' ,'ITEM20', 1
    UNION ALL
    SELECT '20', '2001' ,'ITEM2001', 2
    UNION ALL
    SELECT '2001', '2001001', 'ITEM2001001', 3
    UNION ALL
    SELECT '10', '1001', 'ITEM1001', 2
    UNION ALL
    SELECT '1001', '1001001', 'ITEM1001001', 3
) ,CTE_RESURSIVE AS (
    SELECT CODE, CODE CODE1,
          CASE WHEN 1=2 THEN CODE ELSE NULL END CODE2,
          CASE WHEN 1=2 THEN CODE ELSE NULL END CODE3,
          NAME NAME1,
          CASE WHEN 1=2 THEN NAME ELSE NULL END NAME2,
          CASE WHEN 1=2 THEN NAME ELSE NULL END NAME3,
          LEVEL
    FROM VM_CTE WHERE UPPER_CODE IS NULL
   
    UNION ALL
    SELECT SC.CODE, CC.CODE1 CODE1,
              CASE WHEN SC.LEVEL = 1 THEN NULL
                      WHEN SC.LEVEL = 2 THEN SC.CODE
                      WHEN SC.LEVEL = 3 THEN CC.CODE2 END CODE2,
              CASE WHEN SC.LEVEL = 1 THEN NULL
                      WHEN SC.LEVEL = 2 THEN NULL
                      WHEN SC.LEVEL = 3 THEN SC.CODE END CODE3 ,
              CC.NAME1 NAME1,
              CASE WHEN SC.LEVEL = 1 THEN NULL
                      WHEN SC.LEVEL = 2 THEN SC.NAME
                      WHEN SC.LEVEL = 3 THEN CC.NAME2 END NAME2,
              CASE WHEN SC.LEVEL = 1 THEN NULL
                      WHEN SC.LEVEL = 2 THEN NULL
                      WHEN SC.LEVEL = 3 THEN SC.NAME END NAME3 ,
             SC.LEVEL
    FROM VM_CTE SC INNER JOIN CTE_RESURSIVE CC ON SC.UPPER_CODE=CC.CODE
    WHERE UPPER_CODE IS NOT NULL ) SELECT * FROM CTE_RESURSIVE
WITH  VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS (
	SELECT NULL,	'10',	'ITEM10',	1
	UNION ALL 
	SELECT NULL,	'20'	,'ITEM20',	1
	UNION ALL 
	SELECT '20',	'2001'	,'ITEM2001',	2
	UNION ALL
	SELECT '2001',	'2001001',	'ITEM2001001',	3
	UNION ALL
	SELECT '10',	'1001',	'ITEM1001',	2
	UNION ALL
	SELECT '1001',	'1001001',	'ITEM1001001',	3
) ,CTE_RESURSIVE AS (
	SELECT CODE, 
	            CODE  CODE1,
				CASE WHEN 1=2 THEN CODE
						 ELSE NULL END CODE2, 
				CASE WHEN 1=2 THEN CODE
						 ELSE NULL END CODE3, 
				NAME NAME1,
				CASE WHEN 1=2 THEN NAME
						 ELSE NULL END NAME2, 
				CASE WHEN 1=2 THEN NAME
						 ELSE NULL END NAME3, 
				LEVEL
	FROM VM_CTE
	WHERE UPPER_CODE IS NULL

	UNION ALL
	SELECT SC.CODE, 
				CC.CODE1 CODE1, 
				CASE WHEN SC.LEVEL=1  THEN NULL
						 WHEN SC.LEVEL=2  THEN SC.CODE
						 WHEN SC.LEVEL=3 THEN  	CC.CODE2	
				END CODE2, 
				CASE WHEN SC.LEVEL= 1 THEN NULL 	
						WHEN SC.LEVEL = 2 THEN NULL
						WHEN SC.LEVEL = 3 THEN SC.CODE
				END CODE3 , 
				CC.NAME1 NAME1,
				CASE WHEN SC.LEVEL=1  THEN NULL
						 WHEN SC.LEVEL=2  THEN SC.NAME
						 WHEN SC.LEVEL=3  THEN CC.NAME2			
				END NAME2, 
				CASE WHEN SC.LEVEL= 1 THEN NULL 	
						WHEN SC.LEVEL = 2 THEN NULL
						WHEN SC.LEVEL = 3 THEN SC.NAME
				END NAME3 ,
				SC.LEVEL 
	FROM VM_CTE SC
	INNER JOIN CTE_RESURSIVE CC ON SC.UPPER_CODE=CC.CODE
	WHERE UPPER_CODE IS NOT NULL
)
SELECT *
FROM CTE_RESURSIVE

<<실행결과 >>

위 쿼리를 잠깐 살펴보자. 

재귀 쿼리는 Anchor 멤버를 먼저 실행후 ,T0을 만든다고 했다. 그래서 위 쿼리의 Anchor멤버를 실행하게 되면 T0가 생성이 된다. 

T0
CODE CODE1 CODE2 CODE3 NAME1 NAME2 NAME3 LEVEL
10 10 NULL NULL ITEM10 NULL NULL 1
20 20 NULL NULL ITEM20 NULL NULL 1
T1

T0가 입력으로 사용되어 Recursive멤버가 실행되게 된다. 위 쿼리에서 Recursive 멤버인 아래 쿼리가 실행이 되는 것이다. 

SELECT  SC.CODE,  
......생략......
SC.LEVEL  
FROM VM_CTE SC 
INNER JOIN CTE_RESURSIVE CC ON SC.UPPER_CODE=CC.CODE 
WHERE UPPER_CODE IS NOT NULL

그럼 T0은 CC 로 Alias된 부분이 되고 SC로 ALIAS된 Table과 Inner Join하게 되는 것이다. 즉, T0의 CODE가 SC의 UPPER_CODE인 Record를 찾게된다. 그 결과가 T1이 된다. 

T2

위에서 생성된 T1이 다시 입력으로 들어가서 SC로 Alias된 Table과 Inner Join하게 되고 그 결과로 T2가 만들어진다. 

T3

위에서 생성된 T2가 다시 입력으로 들어가서 SC로 Alias된 Table과 Inner Join하게 되고 그 결과로 T3가 만들어진다. 

그런데 그 결과 Record가 존재 하지 않는다. 그래서 T3는 만들어 지지 않고  재귀 호출은 여기서 종료 된다. 

T1,T2,T3 UNION ALL

재귀 호출이 종료된 후 지금 까지 생성된 T0,T1,T2 가 UNION ALL되어 출력되게 된다.

처음 재귀 쿼리를 접했을때 이 내용을 이해하기 까지 시간이 오래 걸렸다. 나 처럼 재귀쿼리 첨 접하는 사람에게 조금이나마 이해하는데 도움이 되었으면 좋겠다... 

 

크린탑 황사마스크-KF94 184(KF-94) 소형 화이트 (30EA), 184 (KF-94) 소형 화이트 (10개묶음)KF94 어린이용 순면 황사 방역마스크 KF94 순면어린이 마스크 먼지 방역 일회용, 화이트1매 엔 AIRGUARD 미세먼지 스포츠 마스크 성인용 KF94, 1개, 1개

 

'MSSQL' 카테고리의 다른 글

db링크 확인 하는 법  (0) 2020.03.27
bulk insert  (0) 2020.03.26
STRING ORDER BY 가 안될때  (2) 2020.03.20
숫자+문자를 포함한 데이터 order by 가 안되는 경우  (0) 2020.02.04
여러 행의 컬럼값을 한줄로 나타내기  (0) 2019.12.30

쿼리를 작성하다 보면 숫자와 문자가 뒤섞인 데이터들이 많이 있다. 이런 데이터들을 order by 하면 내가 원하는 순서대로 오름차순 또는 내림차순이 되지 않는 경우가 있다. 예를 들어 아래 쿼리를 보자. 내림차순 정렬을 했지만, 출력되는 순서는 5S,11S,10S인걸 알수 있다.  이처럼 데이터 정렬이 제대로 안될경우 해결 방법이 있다. 

이를 경우는 order by Len( column_Name) 을 사용하면 된다. 아래처럼 원하는 순서로 출력되는 것을 볼 수 있다. 

'MSSQL' 카테고리의 다른 글

db링크 확인 하는 법  (0) 2020.03.27
bulk insert  (0) 2020.03.26
STRING ORDER BY 가 안될때  (2) 2020.03.20
[MS SQL] 재귀쿼리 트리구조 쿼리 WITH CTE  (0) 2020.02.04
여러 행의 컬럼값을 한줄로 나타내기  (0) 2019.12.30

앞글에서 서비스 추상화에 대해 살펴보았다. 회원의 레벨을 업그레이드 할때 예외가 발생하게 되면 어떤일이 발생할까?

예외 발생 전 회원만 레벨이 업그레이드 되고, 그 후 회원들은 레벨이 그대로 일것이다. 이러한 현상이 실제 게임상에 일어난다면 회원들의 반발이 클것이라 예상할 수있다. 이러한 문제를 사전에 예방하기 위해 우리는 트랜잭션이라는 개념을 이용하여 처리 할 수 있다.

트랜잭션이란 더 이상 나눌 수 없는 단위 작업이다.

여기서 중요한 개념 2가지를 짚고 넘어가도록 하자.

두가지 이상의 작업이 하나의 트랜잭션이 되려면 모든 작업이 성공적으로 수행 되기 전 문제가 발생 했을시에 앞서 처리한 DB 작업이 모두 취소 되어야 한다. 이것이 바로 트랜잭션 롤백(Transaction Rollback)이다.  그리고 모든 작업이 정상적으로 수행 되었다면 DB에게 작업 완료를 알려야 한다. 이것이 바로 트랜잭션 커밋(Transaction Commit)이다.

JDBC 트랜잭션의 트랜잭션 경계설정

모든 트랜잭션은 시작하는 지점과 끝나는 지점이 있다. 시작하는 방법은 한가지 이지만 끝나는 방법은 두가지이다. 모든 작업을 무효화 하는 롤백과 모든 작업을 확정하는 커밋이다.

트랜잭션에서 경계란 어플리케이션안에서 트랜잭션이 시작되고 끝나는 위치를 말한다.

아래는 트랜잭션을 적용하는 간단한 예제이다.

JDBC의 트랙재션은 하나의 Connection을 가져와 사용하다가 닫는 사이에서 일어난다. 트랜잭션의 시작과 종료는 Connection오브젝트를 통해 이뤄지기 때문이다. JDBC에서 트랙잭션을 시작하려면 자동커밋 옵션을 false로 만들어 주면 된다.(setAutoCommit(false)) JDBC의 기본 설정은 DB작업을 수행 직후에 자동으로 커밋이 되도록 되어 있다. 트랜잭션이 한번 시작되면 commit or rollback()메소드가 호출될 때까지의 작업이 하나의 트랜잭션으로 묶인다. 작업중에 예외가 발생하면 트랜잭션을 rollback한다.

이렇게 setAutoCommit(false)로 트랜잭션을 시작하고, commit() or rollback()으로 트랜잭션을 종료하는 작업을 트랜잭션의 경계설정(transaction demarcation) 이라고 한다. 트랜잭션의 경계는 하나의 Connection이 만들어지고 닫히는 범위안에 존재한다.

자, 그럼 이제 회원 레벨업그레이드를 위한 메소드 구조를 살펴보자. 아래는  예외 발생시 롤백 처리 하고 문제 없을시 커밋 처리 하기 위한 트랜잭션 경계설정 구조이다.

트랜잭션을 사용하는 전형적인 JDBC코드의 구조이다.  여기서  눈여겨 봐야 할 점은 DB connection을 생성한 후, connection 객체를 DAO 메소드에 넘겨 주어 사용할 수 있도록 해야 한다. 트랜잭션의 경계는 하나의 connection 객체로 정해 지기 때문에 동일한 트랜잭션 처리를 하기 위해서는 같은 connection을 사용해야 한다.

하지만 이렇게 할 경우 DAO메소드를 호출 할때마다  매번 connection객체를 넘겨줘야 하는 불편함이 있다. 이를 위해 스프링은 트랜잭션 동기화 방식을 제공한다.

트랜잭션 동기화 방식 (transaction synchronizaion)

(1) UserService는 Connection을 생성하고 (2) 이를 트랜잭션 동기화 저장소에 저장해 두고 Connection의 setAutoCommit(false)를 호출해 트랜잭션을 시작시킨 후에 DAO의 기능을 이용하기 시작한다. (3) 첫번째 update()메소드가 호출되고, update()메소드 내부에서 이용하는 JdbcTemplate메소드에서는 가장 먼저 (4)트랜잭션 동기화 저장소에 현재 시작된 트랜잭션을 가진 Connection오브젝트가 존재하는지 확인한다. (2) upgraLevels() 메소드 시작 부분에서 저장해둔 Connection을 발견하고 이를 가져온다. 가져온 (5) Connection을 이용해 PreparedStatement를 만들어 수정 SQL을 실행한다. (6) 두번째 update()가 실행되면 이때도 마찬가지로 (7) 트랜잭션 동기화 저장소에서 Connection을 가져와 (8)사용한다.(9) 마지막 update()도 (10)같은 트랜잭션을 가진 connection을 가져와 (11)사용한다. 모든 작업이 정상적으로 끝났다면 (12) Connection의 commit()을 호출하여 트랜잭션을 완료시킨다. 마지막으로 (13) 트랜잭션저장소가 더이상 Connection 오브젝트를 저장해두지 않도록 Connection객체를  제거한다. 어느 작업중에 예외 발생시에는 즉시 Connection의 rollback()을 호출하고 트랜잭션을 종료하며 이때에도 트랜잭션저장소가 더이상 Connection 오브젝트를 저장해두지 않도록 Connection객체를 제거한다.

이제 위를 구현한 코드를 살펴보자.

public void upgradeLevels() throws Exception {
		TransactionSynchronizationManager.initSynchronization();  //트랜잭션 동기화 관리자를 이용해 동기화 작업을 초기화 한다. 
		Connection c = DataSourceUtils.getConnection(dataSource); //DB 커넥션을 생성하고 트랜잭션을 시작한다. 이후의 DAO작업은 모두 여기서 시작한 트랜잭션 안에서 실행된다. 
		c.setAutoCommit(false);
		
		try {									   
			List<User> users = userDao.getAll();
			for (User user : users) {
				if (canUpgradeLevel(user)) {
					upgradeLevel(user);
				}
			}
			c.commit();  
		} catch (Exception e) {    
			c.rollback(); //예외 발생시 rollback
			throw e;
		} finally {
			DataSourceUtils.releaseConnection(c, dataSource);	// DB 커넥션을 닫는다. 
			TransactionSynchronizationManager.unbindResource(this.dataSource);  // 동기화 작업종료및 정리 
			TransactionSynchronizationManager.clearSynchronization();  // 동기화 작업종료및 정리 
		}
	}
스프링의 트랜잭션 서비스 추상화  

스프링은 기술에 독립적은 트랜잭션을 사용 할 수 있는 기술을 제공한다. JMS, JDBC등 다양한 기술의 트랜잭션을 추상화하여 제공 함으로써 기술에 독립적은 트랜잭션 경계설정이 가능해졌다. 아래 그림은 스프링의 트랜잭션 서비스 추상화 계층을 그림으로 나타낸것이다.

위 그림에서 알 수 있듯이 우리가 지금까지 사용한 JDBC를 이용하기 위해서는 추상 Interface PlatformTransactionManager을 구현한 DataSourceTransactionManager를 사용한다. 지금부터 이를 사용하여 UserService의 트랜잭션 경계설정을 해보도록 하자. 아래 코드를 참고하기 바란다.

public void upgradeLevels() throws Exception {
		//사용할 DB(JDBC)의 DataSource를 생성자에 넣어 트랜잭션 추상 오브젝트 생성
		PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); 
		//TransactionDefinition( abstract Interface) <- DefalutTransactionDefinition
		TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition() );  //트랜잭션 시작 
		 
		try {									   
			List<User> users = userDao.getAll();
			for (User user : users) {
				if (canUpgradeLevel(user)) {
					upgradeLevel(user);
				}
			}
			transactionManager.commit(status);  
		} catch (Exception e) {    
			transactionManager.rollback(status); //예외 발생시 rollback
			throw e;
		} finally {
			
		}
	}

그럼 여기서 하나 질문을 해보자 . JDBC가 아니라 JTA를 이용한다고 한다면 어떻게 하면 될까?

PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); 

-> PlatformTransactionManager transactionManager = new JTATransactionManager();

이렇게 바꿔주기만 하면 된다. 우리가 지금까지 배워왔던   스프링의 DI를 이용해서 다시 코드를 변경해보자. UserService와 Context  File을 아래와 같이 수정하였다.

public class UserService implements UserLevelUpgradePolicy{
	@Autowired
	private UserDao userDao ;
	public static final int MIN_LOGCOUNT_FOR_SILVER=50;
	public static final int MIN_RECOMMEND_FOR_GOLD=30;
	@Autowired
	private PlatformTransactionManager transactionManager;
	
	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
	public void upgradeLevels() throws Exception {
		//사용할 DB(JDBC)의 DataSource를 생성자에 넣어 트랜잭션 추상 오브젝트 생성
		TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition() );  //트랜잭션 시작 
		try {									   
			List<User> users = userDao.getAll();
			for (User user : users) {
				if (canUpgradeLevel(user)) {
					upgradeLevel(user);
				}
			}
			transactionManager.commit(status);  
		} catch (Exception e) {    
			transactionManager.rollback(status); //예외 발생시 rollback
			throw e;
		} finally {
			
		}
	}
	public void upgradeLevel(User user) {
		// TODO Auto-generated method stub
		user.upgradeLevel();	
		userDao.update(user);
	}
	public boolean canUpgradeLevel(User user) {
		// TODO Auto-generated method stub
		Level currentLevel =user.getLevel();
		switch(currentLevel) {
			case BASIC: return (user.getLogin() >= MIN_LOGCOUNT_FOR_SILVER);
			case SILVER: return (user.getRecommend() >= MIN_RECOMMEND_FOR_GOLD);
			case GOLD: return false;
			default: throw new IllegalArgumentException("Unknowned Level : " + currentLevel);
		}
	}
	public void add(User user) {
		// TODO Auto-generated method stub
		if(user.getLevel() == null) user.setLevel(Level.BASIC);
		this.userDao.add(user);
	}
}
     Contex설정 파일에는 아래내용을 추가해 주면 된다.
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
<bean id="userService" class="springbook.user.dao.UserService">
  <property name="userDao" ref="userDao"/>
  <property name="transactionManager" ref="transactionManager"/>
</bean>

'Spring' 카테고리의 다른 글

log4J.xml 파일을 못찾을때 해결법  (0) 2020.03.14
5장 서비스 추상화-I  (0) 2020.01.14
4장 예외  (0) 2020.01.05
3장 템플릿IV - 템플릿/콜백의 응용  (0) 2020.01.04
3장 템플릿 III - 스프링의 JdbcTemplate  (0) 2020.01.04
픽스쳐(fixture)

테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스쳐(fixture)라고 한다. UserDaoTest에서라면 dao가 대표적인 픽스처라고 볼 수 있다.

+ Recent posts