윈도우 정품 마크 숨기기

 

윈도우 정품 인증키없이 윈도우 설치시 위와 같은 워터마크가 화면에 떠있게 된다.

PC 사용엔 큰 문제가 없으나, 화면캡쳐 등의 작업시에 인증 마크가 함께 캡쳐되므로 문제가 된다.

 

MS 개발자들을 위해 정품 구입 후 사용을 권장하나,

어쩔 수 없이 어둠의 경로로 윈도우를 설치한 PC를 사용해야 하는 경우엔 아래와 같이 정품 인증 마크를 없애보자.

※ 내 경우, 개발 프로젝트를 하기 위해 파견나가있던 회사에서 지급해준 노트북이 위와 같은 상황이었다.

 

 

1. 윈도우 검색창에 cmd 입력 -> 명령 프롬프트 우클릭 -> 관리자 권한으로 실행

 

2. slmgr /rearm 입력 -> 엔터

 

3. 재부팅

 

 

※ 일정 기간이 지난 후 워터마크가 다시 나타난다면 위 방법으로 한 번 더 없애주면 된다.

 

반응형

유투브 앱  background 에서 실행하기(유투브로 멜론처럼 노래듣기)

 

멜론을 월정액으로 사용하거나 유투브 프리미엄을 가입하면 노래를 편히 들을 수 있다.

나처럼 돈이 없고 노래는 듣고 싶다면 아래와 같은 방법이 있다.

 

 

1. 모바일 기기에서 Chrome 실행 및 우측 상단 도구모음버튼 클릭

2. 데스크톱 사이트 옵션 체크 (=PC 모드로 웹창 열기)

 

 

3. 유투브(youtube.com) 접속 및 노래 검색

구글 검색창에 유투브 검색 후 유투브 링크로 접속하면 앱으로 자동 실행되므로 url 입력칸에 youtube.com 입력하여 접속.

 

4. 결과

 

※ 

다음노래 듣기/이전노래 듣기 불가, 앞/뒤로 빨리감기만 가능한 단점이 있으나,

빌보드 차트, 라이딩용 음악, 헬스용 음악 등을 검색 후 유투버가 만들어 놓은 음악 목록을 편히 들을 수 있다는 장점이 있다

반응형

[ JSP 수정 후 저장시 서버 재시작 되는 현상 해결법 ]

1. 프로젝트 클린 : project 탭 > clean

2. 서버 클린 : Servers Preview 에서 사용 중인 Server 우클릭 > clean

 

Servers Preview 에서 사용 중인 Server 더블클릭시 서버 Overview 화면이 나오는데,

  Publishing 탭을 보면 아래와 같은 3가지 퍼블리싱 옵션이 존재.

1) Never publish automatically : 저장해도 자동 서버 반영 하지 않는다

2) Automatically publish when resources change : 소스가 바뀔 경우 자동 서버 반영 

3) Automatically publish after a build event : 빌드할 경우에만 자동 서버 반영

 

해당 설정은 default 인 Automatically publish when resources change 을 사용(건들필요 없다)

반응형

[Spring property 두가지 설정 방법]

1. context:property-placeholder 태그 사용

<context:property-placeholder location="classpath:프로퍼티파일명"/>

* 빈팩토리 후처리기로써 빈 설정 메타정보 모두 준비됐을 때 메타정보 자체를 조작 : 빈 설정이 끝난 이후 ${} 표현을 찾아 해당하는 프로퍼티로 치환해준다는 의미인 듯

* ${} 사용 : "${프로퍼티key}" 와 같이 사용

* @Value annotation 에서도 사용이 가능 : @Value("${프로퍼티key}")

 

2. bean 으로 등록

<util:properties id="빈아이디" location="classpath:프로퍼티파일명" />

* Spring 3.0 이상부터 지원

* 프로퍼티 파일 내용을 Properties 타입의 빈으로 생성

* spEL(Spring Expression Language) 사용 : #{빈아이디['프로퍼티Key']} 와 같이 사용

* @Value annotation 에서도 사용이 가능 : @Value("빈아이디['프로퍼티Key']")

 

[Ex]

1. Comm.properties 파일 생성 (resources/sample/properties/Comm.properties)

api.server.url="https://sample.api.com"
db.driverClass="sampleDriverClass"
db.url="sampleDBUrl"
db.username="sampleDBUrl"
db.password="sampleDBUrl"

2. property 설정(Bean 주입, context.xml 설정)

<context:property-placeholder location="classpath:sample/properties/Comm.properties" />
혹은
<util:properties id="properties" location="classpath:sample/properties/Comm.properties" />

※서버 환경별(로컬, 개발, 운영 등) property 파일을 동적으로 다르게 사용하고자 할 경우 이곳 참고

 

3. 스프링설정파일 (~context.xml) 에서의 사용

context:property-placeholder 태그 사용한 property 설정시 :

1
2
3
4
5
6
<bean id=""dataSource" class="~">
   <property name="driverClass" value="${db.driverClass}" />
   <property name="url" value="${db.url}" />
   <property name="username" value="${db.username}" />
   <property name="password" value="${db.password}" />
</bean>
cs

util:properties 와 같이 bean 으로 property 설정시 :

1
2
3
4
5
6
7
<bean id=""dataSource" class="~">
   <property name="driverClass" value="#{properties['db.driverClass']}" />
   <property name="url" value="#{properties['db.url']}" />
   <property name="username" value="#{properties['db.username']}" />
   <property name="password" value="#{properties['db.password']}" />
</bean>
 
cs

 

4. 소스에서의 사용

context:property-placeholder 태그 사용한 property 사용시 :

@Value("${api.server.url}") 

private String apiServerUrl;

 

util:properties 와 같이 bean 으로 property 사용시 :

@Value("#{properties['api.server.url']}") 

private String apiServerUrl;

 

 

[ property 의 형변환 ]

property 값은 기본적으로 String 형태의 Text로 선언 및 사용되는게 default.

String 이 아닌 다른 기타 자료형을 바꿔서 사용해야하는 경우

1. PropertyEditor : 스프링이 default로 해당 에디터를 사용하여 property를 형변환 함 

: boolean, short, int, long, double 등의 기본 자료형 및 Boolean, Short, Integer, Long, Double 등의 wrapper클래스의 오브젝트 타입도 지원

ex) @Value("1.2") double num;

: array 도 사용 가능

ex) @value("1,2,3") int[] arr;

 

* PropertyEditor interface 직접구현하여 사용자가 직접 정의한 형태로 값을 리턴하도록 사용 가능 (Thread safe X)

 

2. ConversionService : 스프링 3.0부터 지원 (Thread safe O)

bean 으로 선언하여 사용.

 

 

공부를 더 해봐야 하겠지만 지금 당장 String 및 기본자료형 정도로만 property 사용하면 되므로

나중에 필요할 때 공부하는걸로..

 

참고 :

토비의스프링 3.1 (도서)

https://whiteship.tistory.com/2563

반응형

Mysql hierarchy sql 계층구조 쿼리 2 : 그리기(Multi selectbox)

 

https://developyo.tistory.com/54

위의 쿼리에서 꺼내온 정보를 화면에 뿌려보자.

 

 

<1: 컨트롤러 코딩>

1. 가이드 목록을 출력하기 위한 controller

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/loadGuideList.do")
public ModelAndView loadGuideList(HttpServletRequest request, @RequestBody GuideVO param) throws Exception
{    
    ModelAndView mav = new ModelAndView("jsonView");
    
    List<GuideVO> guideList = service.selectGuideList(param);
    
    mav.addObject("guideList", guideList);    //가이드 목록
    mav.addObject("paramsData", param);
    
    return mav;
}
cs

 

2. 멀티 셀렉트박스 목록을 ajax로 불러오기 위한 controller

1
2
3
4
5
6
7
8
9
@RequestMapping("/getNodeList.do")
public ModelAndView getNodeGroup(HttpServletRequest request, @RequestBody NodeVO param) throws Exception
{    
    ModelAndView mav = new ModelAndView("jsonView");
    List<NodeVO> nodes = service.selectNodeList(param);    //가이드 카테고리
    mav.addObject("nodes", nodes);
 
    return mav;
}
cs

 

* Service, DAO, VO 는 별다른 내용이 없으므로 생략..

 

<2: script 코딩>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
var loadData = {
        
        //가이드 목록 호출
        loadGuideList : function () {
            
            var totalSearchFlag = "N";    //가장 낮은 depth 의 선택 값이 전체인 경우 Y
            
            //현재 선택된 node_id 값 set (조회용)
            var nodeCnt = $(".nodeGroup").length;
            
            //조회
            var paramVal = "";
            for(var i=nodeCnt-1; i>=0; i--){
                //제일 우측(낮은 depth) 셀렉트 박스부터 제일 좌측(높은 depth) 로 반복문
                var val = $(".nodeGroup:eq("+i+")").val();
                var node_id = val.split("^")[0];
                if(node_id && node_id != ""){
                    //node_id 가 존재하는 경우(전체 선택이 아닌 경우)
                    paramVal = node_id;
                    break;
                } else {
                    //node_id 가 존재하지 않는 경우(전체 선택인 경우)
                    totalSearchFlag = "Y";
                    continue;
                }
            }
            
            $("form[name=frmGuide] input[name=totalSearchFlag]").val(totalSearchFlag);
            $("form[name=frmGuide] input[name=node_id]").val(paramVal);                    
        
            var params = {
                node_id : $("form[name=frmGuide] input[name=node_id]").val(),
                totalSearchFlag : $("form[name=frmGuide] input[name=totalSearchFlag]").val()
            };
            
             $.ajax({
                type    : "post",
                url        : "/stbcs/ipchk/loadGuideList.do",
                contentType: "application/json",
                dataType:"json",
                data     : JSON.stringify(params),    
                success    : function (data) {
                    drawData.drawGuideList(data);
                }
            }); 
        } ,
        
        //가이드 분류
        loadNodeList : function(selectBox){
                var obj = $(selectBox);
                var tmp = obj.val().split("^");
                var nodeId = tmp[0];
                var leaf_yn = tmp[1];
                var currentIdx = obj.parent(".nodeGrp").index();    //현재 node 의 idx        
                var nodeCnt = $(".nodeGrp").length;    //node 갯수                            
                
                if(nodeCnt > currentIdx){
                    for(var i=nodeCnt; i>=currentIdx; i--){    
                        console.log("remove idx : " + i);   
                        $(".nodeGrp:eq("+i+")").remove();     
                    }
                }
                
                if(leaf_yn == "Y"){
                    //마지막 노드가 아닌 경우 (자식 노드가 있는 경우)
                    var params = {
                            node_id : nodeId
                        };
                    
                    $.ajax({
                        type    : "post",
                        url        : "/getNodeList.do",
                        contentType: "application/json",
                        dataType:"json",
                        data     : JSON.stringify(params),    
                        success    : function (data) {
                            drawData.drawNodeList(data);
                        }
                    });
                }
        } 
}
var drawData = {
    drawNodeList : function (data, currentIdx){
                var nodeGroupTxt = "";
                
                try{
                    var node = data.node;
                    if(node != null && node.length > 0){
                        if($(".nodeGrp").length == 0){
                            nodeGroupTxt += "<span>분류&nbsp;&nbsp;</span>";
                        }
                        nodeGroupTxt += "<div class='selectbox small nodeGrp' style='margin-right:3px;' >";
                        nodeGroupTxt += "<select class='nodeGroup' name='node_id' onchange='javascript:loadData.loadNodeList(this);'>";
                        nodeGroupTxt += "<option value='^N'>전체</option>";
                        
                        $.each(node, function(index, item){
                            nodeGroupTxt += "<option value=" + item.node_id+"^"+item.leaf_yn + ">" + item.node_nm + "</option>";
                        });
                        
                        nodeGroupTxt += "</select>";
                        nodeGroupTxt += "</div>";
                    }
                } catch (error) {
                } finally {
                    $("#guideSearchDiv").append(nodeGroupTxt);
                }        
        }
}
cs

 

※ Multi selectbox

94line : selectbox에 onchange 이벤트를 걸어 selectbox 값이 변화할 때, 자신의 loadNodeList(this) 함수를 호출한다.

57~62line : 총 selectbox 갯수가 현재 선택된 selectbox의 index보다 큰 경우 현재 선택된 selectbox 보다 낮은 depth(우측에있는)의 selectbox를 지운다.

ex: 1, 2, 3 depth 의 selectbox 를 특정 값으로 선택해 놓고, 1번째 selectbox 의 값을 다른값으로 바꿔버린 경우 2, 3번째 selectbox tag는 .remove()로 제거한다 (remove는 싱크(동기)방식이므로 ajax 호출 순서가 보장된다)

64~80line :

selectbox에서 선택된 값이 자식노드가 있는 경우(leaf_yn == 'Y') 특정 노드를 기준으로 한 자식 노드 목록을 가져오기 위해 getNodeList.do ajax 호출

84~108line : ajax 호출을 통해 가져온 노드 목록을 selectbox로 표현

98line : 조작시 필요한 값인 노드아이디와 자식노드유무 데이터를 ^ 문자를 구분으로하여 value 값으로 넣어준다(option value = node_id^leaf_yn)

106line : 멀티셀렉트박스가 위치할 곳에 만들어준 selectbox를 .append 로 붙여넣는다

 

※ 가이드 목록(Multi selectbox 를 조건으로 걸어 조회한 결과 리스트)

4~46 line : 가이드 목록 ajax 호출

13~26 line :

가장 낮은 depth(제일 오른쪽)의 selectbox 부터 가장 높은 depth (제일 왼쪽)의 selectbox 까지 for 문을 돈다.

node_id^leaf_yn (노드아이디^자식노드존재유무) 값이 담겨있는 option value 를 꺼낸다.

node_id 가 존재하는 경우 전체 조회가 아니므로 해당 값을 server 쪽에 ajax 로 전달하면 된다. (for문을 끝낸다)

node_id 가 존재하지 않는 경우 전체 조회이므로 서버에 전달할 전체 조회 flag 값(totalSearchFlag)를 Y 로 바꿔준 후, for문을 계속 돌린다(continue;)

* 전체 조회 flag 값(totalSearchFlag)은 subquery(3-2 쿼리작성부분의 3번 쿼리) 를 사용할 경우 필요 없다. foreach를 사용할 경우 분기처리를 위해 필요. 

* 조회한 가이드 목록을 화면에 그리는 코딩은 별다를게 없으므로 생략..

 

반응형

Mysql hierarchy sql 계층구조 쿼리 : 설계 및 쿼리작성(Multi selectbox)

 

고객들이 열람할 가이드 정보가 있고,

가이드 정보의 카테고리를 N depth 로 분류(멀티 셀렉트박스 사용)하여 조회할 수 있게 하여 다음과 같은 화면을 만들어 보자.

<1: 테이블 설계>

1. '전체' 옵션의 유무

case 1 멀티 셀렉트박스에 '전체'라는 옵션이 없는 경우 :

마지막 셀렉트박스의 value(node_id)를 기준으로 가이드를 조회

case 2 멀티 셀렉트박스에 '전체'라는 옵션이 존재하는 경우 :

'전체'가 선택된 마지막 셀렉트박스 직전의 value(node_id)를 기준으로 모든 자손 node_id를 찾아 조회해야한다.

 

예를 들어,

case 1 ex) 고객지원 > 고객지원sub > 고객지원sub의sub 와 같이 마지막 select box에 특정 값이 선택된 경우 :

고객지원sub의sub 의 value(node_id) 를 기준으로 가이드 조회

case 2 ex) 고객지원 > 고객지원sub > 전체 와 같이 마지막 select box가 '전체'로 선택된 경우 :

고객지원sub을 부모 노드로 삼는 모든 자손 노드를 기준으로 가이드 조회

 

2. 동적 depth 여부

case 1 : depth 가 특정 값으로 고정인 경우, 셀렉트박스용 분류 코드테이블을 따로 두고 join 을 몇번 걸면 해결이 된다

case 2 : depth 가 동적(N)인 경우, join 이 N 번 되어야 하므로 단순한 join으로 해결 할 수 없다..

 

전체 옵션이 존재하고 depth 가 동적(N)인 경우를 전재하고 설계 및 개발해보자.

 

 

<2: 테이블 생성>

1. 메인테이블 생성 : 가이드 정보 테이블 생성

가이드 정보 테이블은 제목, 가이드의 노드 아이디, 상세 내용만 지니고 있다.

1
2
3
4
5
6
7
8
CREATE TABLE `guide_info` (
  `SEQ` int(11NOT NULL AUTO_INCREMENT COMMENT '일련번호',
  `TITLE` varchar(100NOT NULL COMMENT '제목',
  `GUIDE_ID` int(11NOT NULL COMMENT '가이드 노드 아이디',
  `TEXT_ITEM_1` text COMMENT '가이드 내용',
  PRIMARY KEY (`SEQ`)
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='고객지원 가이드 게시판';
 
cs

 

2. 서브테이블 생성 : 가이드 분류 정보를 따로 지니고 있는 노드 정보 테이블 생성

1
2
3
4
5
6
7
8
9
CREATE TABLE `node_info` ( 
  `NODE_ID` int(11NOT NULL AUTO_INCREMENT COMMENT '노드아이디'
  `PNODE_ID` int(11NOT NULL COMMENT '부모노드아이디'
  `NODE_PATH` varchar(200DEFAULT NULL COMMENT '노드경로'
  `NODE_NM` varchar(20DEFAULT NULL COMMENT '노드명'
  `DEPTH` int(11DEFAULT '1' COMMENT '노드DEPTH'
  `LEAF_YN` varchar(1DEFAULT 'N' COMMENT '자식노드존재유무'
  PRIMARY KEY (`NODE_ID`
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='노드 정보'
cs

 

생성된 테이블 :

NODE_ID(노드아이디)와 PNODE_ID(부모 노드 아이디)가 서로 참조하는 관계의 테이블 구조.

 

샘플 데이터 :

 

<3: 쿼리 작성>

필요한 쿼리 종류는 크게 두가지로 나눠볼 수 있다.

1. 셀렉트박스의 구성요소인 노드정보(node_info TABLE)를 조회하는 쿼리 : 멀티셀렉트박스를 그리기 위한 데이터를 뽑는 쿼리

2. 선택된 셀렉트박스 값들을 기준으로 가이드 목록(guide_info TABLE)을 조회한 결과 : 조회 결과를 출력하기 위한 데이터를 뽑는 쿼리

 

3-1. 셀렉트 박스의 구성요소인 노드정보를 조회하는 쿼리 :

N번째(Ndepth) 셀렉트박스가 특정값으로 선택된 경우 N+1번째(N+1depth) 셀렉트박스 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT  node_id
      , pnode_id
      , node_nm
      , depth
      , leaf_yn
  FROM  node_info 
 WHERE  1=1
<choose>
    <when test='node_id neq null and node_id neq ""'> 
       AND  pnode_id = #{node_id}
    </when>
    <otherwise>  
       AND  pnode_id = 1
    </otherwise>
</choose>
 
 
cs

* 8~15line : node_id 가 없는 경우, 아무것도 선택되지 않은 경우(화면에 최초 진입한 상태)이며 최고 조상이라 할 수 있는 node_id 1 을 기준으로 자식 노드를 구한다

 

3-2. 선택된 셀렉트박스 값들을 기준으로 가이드 목록을 조회한 결과

1) 마지막 셀렉트박스가 특정 값으로 선택된 경우 : 단순 join 으로 해결

2) 마지막 셀렉트박스가 '전체'로 선택된 경우 : 아래와 같이 해결

 

ORACLE 은 CONNECT BY 함수를 사용하여 트리구조의 데이터를 출력 할 수 있겠지만,

MYSQL 은 CONNECT BY 함수를 지원하지 않는다.

 

다음과 같이 특정 노드 밑에 있는 모든 자손, 특정 노드 상위에 있는 모든 조상 노드를 구할 수 있다.

 

1. 특정 노드의 자손 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT node_id
     , pnode_id
     , depth
     , leaf_yn
     , node_path
 FROM  (
        SELECT * 
          FROM node_info
      ORDER BY pnode_id, node_id
        ) node_sorted,
       (SELECT @pv := #{node_id}) initial
WHERE   FIND_IN_SET(pnode_id, @pv)
  AND   LENGTH(@pv := concat(@pv, ',', node_id))
cs

 

2. 특정 노드의 조상 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SELECT   nid AS node_id
       , pnode_id
       , leaf_yn
       , depth
       , node_path
  FROM (
        SELECT  @nid AS nid,
              (
               SELECT  @nid := pnode_id
                 FROM  node_info
                WHERE  node_id = nid
               ) AS pnode_id
               , leaf_yn
               , depth
               , node_path
          FROM  (
                 SELECT  @nid := #{node_id}
                ) vars
                , node_info
         WHERE  @nid <> 0
        ) as tmp
cs

 

3. 특정 분류 밑의 모든 가이드 목록 조회 (특정 노드아이디 기준 모든 자손 노드를 구하는 1번 쿼리를 sub query로 사용)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT  seq, 
        title, 
        (SELECT node_path FROM node_info where node_id = gi.guide_id) as guide_path
  FROM  guide_info gi
 WHERE  1=1
   AND  gi.guide_id IN  (SELECT * FROM (
                                             SELECT   node_id
                                               FROM  (SELECT node_id, pnode_id
                                                        FROM node_info
                                                    ORDER BY pnode_id, node_id
                                                     ) node_sorted,
                                                     (select @pv := #{node_id}) initial
                                              WHERE   FIND_IN_SET(pnode_id, @pv)
                                                AND   LENGTH(@pv := concat(@pv, ',', node_id))
                            ) as temp
                            )
ORDER BY seq
cs

 

※ 위와 같이 subquery 를 사용하여 sql 에서 처리하지 않고, 소스레벨에서 처리하고 싶은 경우 foreach 를 사용하여 다음과 같이 처리할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT   gi.title, gi.guide_id, gi.text_item_1, ni.node_path AS rs
  FROM   guide_info gi
       , node_info ni
 WHERE   1=1
   AND   ni.node_id = gi.guide_id
<if test = 'sch_word neq null and sch_word neq ""'>
   AND   title LIKE CONCAT('%',#{sch_word},'%')
</if>
<if test = 'totalSearchFlag neq null and totalSearchFlag neq ""'>
  <choose>
     <when test = 'totalSearchFlag eq "Y"'>
      AND   ni.node_id IN 
       <foreach item="node_id_grp" index="i" collection="node_id_grp" separator="," open="(" close=")">
        #{node_id_grp}
       </foreach>
      </when>
      <otherwise>
        AND   ni.node_id = #{node_id}
      </otherwise>
  </choose>
</if>
 
cs

* totalSearchFlag 는 selectBox 에서 '전체'가 선택되었음을 판별하기 위해 사용되는 값

* 1번 쿼리(특정노드의 모든 자손 구하기)로 뽑아온 모든 자손 노드를 필드명이 node_id_grp 인 List 자료형 에 담아 sql mapper 로 넘겨주어 사용

* open 은 for 문이 맨처음 돌기 전에, close 는 for문이 끝까지 돌고나서, separator 는 for 문이 한 번 씩 돌 때마다 적용된다.

* foreach(for문)로 작성된 쿼리 결과물은 다음과 같다

  (생략) ~ AND ni.node_id IN (2, 6)

 

 

 

위 쿼리를 사용해 가져온 데이터를 화면에 그려보자.

https://developyo.tistory.com/55?category=689339

반응형

1. 더하기

DATE_ADD(date, INTERVAL expr type)

DATE_ADD(날짜, INTERVAL 시간 시간타입)

 

ex) 

DATE_ADD(now(), INTERVAL 1 HOUR) : 현재 시각에 1시간 더하기

DATE_ADD(now(), INTERVAL -10 MINUTE) : 현재 시각에 10분 빼기

DATE_ADD(now(), INTERVAL 100 SECOND) : 현재 시각에 100초 더하기

1
2
3
4
SELECT  seq 
  FROM  hst_info
 WHERE  1=1
   AND  JOIN_DATE < DATE_ADD(now(), INTERVAL -1 HOUR)
cs

: 현재시각(now()) 에 -1 시간(HOUR)을 더한(DATE_ADD) 값이 JOIN_DATE 보다 더 큰 hst_info 테이블의 seq 값을 조회

* expr 값으로 음수를 넣을 경우 음수를 더하면 뺄셈이 되므로 DATE_SUB 함수와 같이 동작한다.

 

 

2. 빼기

DATE_SUB(date, INTERVAL expr type)

DATE_SUB(날짜, INTERVAL 시간 시간타입)

 

ex) 

DATE_SUB(now(), INTERVAL 1 HOUR) : 현재 시각에 1시간 빼기

DATE_SUB(now(), INTERVAL -10 MINUTE) : 현재 시각에 10분 더하기

DATE_SUB(now(), INTERVAL 100 SECOND) : 현재 시각에 100초 빼기

1
2
3
4
SELECT  seq 
  FROM  hst_info
 WHERE  1=1
   AND  JOIN_DATE < DATE_SUB(now(), INTERVAL 1 HOUR)
cs

: 현재시각(now()) 에 1 시간(HOUR)을 뺀(DATE_SUB) 값이 JOIN_DATE 보다 더 큰 hst_info 테이블의 seq 값을 조회

* expr 값으로 음수를 넣을 경우 음수를 빼는건 덧셈이 되므로( -(-1) = +1) DATE_ADD 함수와 같이 동작한다.

 

 

3. 기간 구하기

TIMESTAMPDIFF(interval, datetime_expr1, datetime_expr2)

TIMESTAMPDIFF(시간타입, 시작시각, 끝시각)

 

ex)

TIMESTAMPDIFF(DAY, '20190820', now()) : 현재년월일 - 20190820 (%Y%m%d) 을 DAY 로 환산

TIMESTAMPDIFF(SECOND, '20190820140000', now()) : 현재년월일 - 20190820140000 (%Y%m%d%H%i%s) 을 SECOND 로 환산

1
2
3
SELECT seq
  FROM hst_info
 WHERE TIMESTAMPDIFF(SECOND, JOIN_DATE, now()) > 3600
cs

: JOIN_DATE 시각 부터 현재시각(now()) 까지의 시간차이를 초(SECOND)로 환산한 값이 1시간(3600초) 보다 큰 hst_info 테이블의 seq 값을 조회

 

 

위와 같은 함수들을 사용하여

이력 및 통계를 뽑아내거나, 배치를 돌릴 때 시간을 기준으로 데이터를 조작할 수 있다.

 

참고 : https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html

 

 

* ORACLE 시간계산

https://oracle.tistory.com/216

 

 

 

반응형

[ submit, replace, href 차이 ]

submit 

페이지 이동시 다음페이지로 데이터 전송

document.id.action="/test.do";

document.id.submit();

 

href

데이터 전송없이 페이지만 이동

location.href="/test.do";

 

replace

데이터 전송없이 페이지만 이동(새로운 페이지로 변경)

location.replace("/test.do");

 

※ href와 replace의 차이

href : 속성, 뒤로가기 가능

replace : 메소드, 뒤로가기 불가

 

https://yuchae.tistory.com/223

https://opentutorials.org/module/2919/22904

 

 

반응형

* Spring 5.1.2

* jdk 1.8

* jboss/wildfly 9

* jndi 

 

1. xml + aop pointcut 을 사용한 설정 (context xml 설정)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- transactionManager 적용 범위 지정(advice) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" rollback-for="Exception" />
        <tx:method name="select*" read-only="true" rollback-for="Exception" />
        <tx:method name="insert*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>
 
<!-- transactionManager pointcut  -->
<aop:config>
    <aop:pointcut id="txMethod" expression="execution(* com.sample..*Service.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txMethod" />
</aop:config>
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
     
cs

* aop pointcut 으로 선언 및 설정할 경우 위와 같이 적용할 pointcut 범위를 지정.

(* com.sample..*Service.*(..)) : 모든 접근제한자의 com 패키지 밑의 sample 패키지 밑의 ~Service로 끝나는 모든 클래스 밑의 모든(*) 메소드

* advice 설정에 메소드별로 제약 및 옵션을 걸어 줄 수 있다.

  get* : get으로 시작하는 모든 메소드

  select* : select 로 시작하는 모든 메소드

* read-only = true : insert / update / delete 쿼리를 내부적으로 실행시 exception 을 뱉는다.

* roll-backfor = Exception : Exception (Exception 밑의 모든 Exception 포함) 이 발생할 경우 rollback 처리. (no rollback for 옵션으로 특정 Exception이 발생시 rollback 처리를 하지 않게 처리 가능)

propagation : 트랜잭션의 전파속성으로써 메소드 내에서 다른 메소드를 사용할 때 하나의 트랜잭션으로 묶을지, 별도의 트랜잭션으로 분류할지 등 과 같은 설정을 지정하는 옵션. default 값이 required 로 알고 있어 위와 같이 선언할 필요는 없는 걸로 알고 있다.. 여러가지 설정이 있는데 나중에 정리하는걸로. 

 

>> com.sample..*Service 밑의 get*, insert*, delete*, select*, update* 이름을 가진 모든 메소드는 위의 트렌잭션 설정에 의해 관리된다.

 

 

2. annotation 을 사용한 설정

1
2
3
4
5
6
7
8
9
10
11
12
<!-- transactional annotation 설정 사용 -->
<tx:annotation-driven transaction-manager="txManager" />
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
cs

* txManager 와 관련된 database 설정, transaction manager bean 선언 부분은 1번과 동일.

 

>> Transaction 으로 관리하고 싶은 메소드위에 @Transactional 어노테이션을 달아주어 사용.

 

 

* 주의사항

1. @Transactional 은 public 메소드에서만 정상 작동한다.

 

2. @Transactional 을 달아놓은 메소드가 동일한 클래스 내의 다른 메소드에 의해 호출된다면 트랜잭션이 정상 작동하지 않는다.

ex: 퍼사드 패턴이나 템플릿 패턴처럼 여러 메소드를 내부적으로 묶어 사용하고 있는 메소드가 있다면 구성요소 메소드에 @Transactional 를 달지 않고 구성요소를 묶고 있는 상위개념의 메소드에 @Transactional 을 달아주어야 한다. 구성요소 메소드에 @Transactional 을 달아 주어 트랜잭션으로 관리 할 경우 rollback 이 정상적으로 작동하지 않는 경우가 발생한다.

 

위 내용과 같은 경우가 아래에 정리되어 있다.

https://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html

propagation 속성이 required 인 경우, 트랜잭션안에서 호출되는 메소드가 트랜잭션으로 같이 묶이게 되어 예상치 못한 결과가 나올 수 있다는 내용이다.

※ required propagation 속성은 트랜잭션이 이미 존재하면 append를 하고, 트랜잭션이 존재하지 않다면 새로 생성한다. (공식 doc)

 

3. Spring Transaction 은 기본적으로 unchecked Exception (RuntimeException) 만 관리하며 checked Exception (IOException, SQLException 등) 은 관리하지 않는다.

처리방법 1: @Transactional(rollbackFor=Exception.class) 와 같이 설정하여 모든 Exception 발생시 rollback 이 발생하게 처리하거나(unchecked Exception, checked Exception 모두 Exception.class 밑에 있다.)

처리방법 2: checked Exception 이 발생할 가능성이 있는 부분을 try ~ catch (SQLException se){throw new RuntimeException(); } 과 같이 처리(checked Exception 발생시 unchecked Exception 으로 예외를 바꾸어 던지게 처리)하여 Transaction의 관리대상으로 묶어버릴 수 있다.

 

4. pointcut 을 사용한 Transaction 설정과 어노테이션을 사용한 Transaction 설정을 동시에 사용하게 될 경우, 어노테이션이 우선적용(precedence)되는 것 같다. (https://stackoverflow.com/questions/32057910/custom-spring-aop-around-transactional/33509777#33509777)

 

 

* 회고.

몇일 전, 정상적으로 트랜잭션이 관리되고 있다고 믿고 있던 내 소스가 특정 케이스에서 rollback 이 이뤄지지 않고 있다는 걸 발견했다.멘탈폭발

개발중인 소스는 4번과 같이 두 설정 모두를 사용하고 있던터라 advisor 와 어노테이션을 함께 사용하여 발생하는 문제인가 싶어 트랜잭션 관련 설정을 이렇게도 바꿔보고 저렇게도 바꿔보았다.

(stackoverflow 에서 두가지 설정 모두를 사용하여 충돌이 발생할 경우 order 설정에 의한 우선순위에 의해 트랜잭션이 관리되고, order 설정이 존재하지 않는다면 마지막에 선언된 설정을 따른다는 글을 봤던게 설정에 의심을 갖게된 결정적인 계기였다. (답변자가 부정확한 답변을 했거나, 내 해석이 잘못됐거나, 개발환경 차이에 의한 차이거나... 그중 하나겠지..))

퇴근 후 몇일간 탐구삽질해본 결과

동일한 프로젝트 내의 타 메소드에선 트랜잭션 관리가 정상적으로 되고 있다는 걸 알 수 있었고, 주말까지 잡아먹은 끝에 4번의 문제가 아닌, 2번의 문제였다는 걸 깨닫게 되었다..(어설프게 디자인 패턴 써보겠다고 소스 리팩토링 하면서 스스로 만들어낸 참사)

애초에 @Transactional 자체가 정상 작동할 수 없는 케이스에서 rollback이 이뤄지지 않는다고 다른 부분들을 전부 의심하기 시작한 셈이다.

덕분에? 관련 docs 도 많이 찾아 읽어보고, transaction 관련 설정부분 및 기본이자 핵심 부분들을 조금이나마 더 깊이있게 공부할 수 있었던 의미 있는 삽질의 시간이었다.

 

* 3번의 경우 위와 같이 설명되어 있는 글들을 여럿 봤는데, 내 설정과 코드에선 굳이 위처럼 처리하지 않더라도(단순히 @Transactional 어노테이션만 붙여도) rollback 이 잘 이루어 지고 있다.. 스프링 버전이나 jdbc 트랜잭션매니저 버전이 올라가면서 SQLException 을 스프링이 트랜잭션 관리 대상에 포함을 시킨건지.. (잘 모르겠으니 다시 구글링 좀 해보고, 테스트 코드도 작성해봐야겠다..)

 

 

반응형

jQuery API jqPlot 을 사용하여 그래프 그리기

 

jqPlot 을 사용하여 통계 데이터를 가로/세로형 막대그래프, 선형그래프, 원형그래프 등으로 쉽게 표현 할 수 있다.

* 세로형 선형그래프를 아래와 같이 구현해 보았다.

 

 

1. js import

1
2
3
4
5
6
7
8
9
10
<link rel="stylesheet" type="text/css" href="${rq.getContextPath()}/resources/css/jquery.jqplot.css">
 
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/jquery.jqplot.min.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/jquery.jqplot.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.enhancedLegendRenderer.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.barRenderer.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.highlighter.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.categoryAxisRenderer.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.cursor.js"></script>
<script type="text/javascript" src="${rq.getContextPath()}/resources/js/jqplot/plugins/jqplot.pointLabels.js"></script>
cs

jqPlot 을 사용하기 위해 관련 js를 import 한다.

 

 

2. 데이터 set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn_csCntChart : function () {
 
            var barData1 = [];
            var barData2 = [];
            var ticks = [];
            var labels = ['전체건수''1인당 처리건수'];
            
            <c:forEach var="list" items="${statsList}" >
                barData1.push("${list.cnt}");
                barData2.push("${list.cntPerEmp}")
                ticks.push("${list.graphDt}");
            </c:forEach>    
            
            var csCntMaxValue = Math.max.apply(null, barData1);        //전체 데이터 중 최대 값
            
            if(ticks.length > 1){
                csCntMaxValue = csCntMaxValue*1.2;    //전체 데이터 최대 값 * 1.2
            }else{
                csCntMaxValue = 10000;    //모든 데이터 값이 0인 경우 차트 최대값을 10000으로 set
            }
                   
            var campChart = $.jqplot('chartArea', [barData1, barData2], {
                //차트 속성 설정
            }
cs

6 Line: 데이터의 범례(각각의 그래프가 의미하는 정보 : 전체건수, 1인당 처리건수)는 lables 배열에 담는다

9 Line: 서버에서 전달한 statsList 데이터(list)에서 cnt (전체건수) 값을 barData1 array에 담는다.

10 Line:서버에서 전달한 statsList 데이터(list)에서 cntPerEmp(1인당 처리건수) 값을 barData2 array에 담는다.

11 Line : 데이터의 기준점(그리드에서 가로축)이 되는 날짜(graphDt)를 ticks 배열에 담는다.

            ticks 는 차트 옵션을 지정할 때 사용한다.

14 ~ 20 Line :

그리드 세로축의 최대값을 제한.

전체건수와 1인당 처리건수 중 전체건수의 값이 당연히 클 것임으로 list 중 전체건수의 최대 값을 뽑아낸 후  1.2를 곱한 값을 세로축의 최대값으로 사용한다.

만약 조회된 데이터가 없는 경우 세로축의 최대값을 1만으로 제한한다.

22 Line:

'chartArea' : 차트를 그릴 태그 id (id가 'chartArea' 인 tag에 완성된 차트가 그려지게 된다)

array barData1, barData2 를 array 에 다시 담아 jqplot 의 파라미터로 전달한다.

   

 

 

3. 차트 옵션 지정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var campChart = $.jqplot('campChartMonth', [barData1, barData2], {
            animate: true,
            animateReplot: true,
            cursor: {
                          show: true,
                          zoom: false,
                          looseZoom: false,
                          showTooltip: false
                    },
            series:[
                      {
                          color : 'blue',
                          renderer: $.jqplot.LineRenderer,
                      } ,
                      {
                          color : 'red',
                          renderer: $.jqplot.LineRenderer,
                      }
                    ],
            axesDefaults: {
                  pad: 0
            },
 
            /*그래프 범례*/
            legend: {
                 renderer: $.jqplot.EnhancedLegendRenderer,
                  show: true,
                  location's',
                  labels: labels,
                  rendererOptions:{
                        numberRows :1    
                     },
                    placement: 'outsideGrid',                 
            },    
            axes: {
                    xaxis: {
                                renderer: $.jqplot.CategoryAxisRenderer,
                              ticks: ticks,
                              drawMajorGridlines: false,
                              drawMinorGridlines: true,
                              drawMajorTickMarks: false,
                            },
                    yaxis: {
                                max: csCntMaxValue,
                                min: 0,
                                tickOptions: {
                                    formatString: "%'d"
                                },
                                rendererOptions: {
                                    forceTickAt0: true
                                }
                            },
                },
                highlighter: {
                                show: true
                                showLabel: true
                                tooltipAxes: 'y',
                                sizeAdjust: 7.5 , tooltipLocation : 'ne'
                            },
            }); 
cs

series : 각각의 선형그래프 관련 설정 (첫번째 그래프선을 blue로, 두번째 그래프선을 red 색상으로 지정)

          그래프의 형태는 선형그래프로 지정 (LineRenderer) (막대그래프는 BarRenderer)

legend : 그래프 범례 관련 설정 (위치를 그래프와 겹치지 않도록 바깥에 설정 (placement : outsideGrid))

            범례를 한줄로 설정 (rendererOptions : numberRows 1)

axes :

그리드 관련 설정 

xaxis : x 축 ( 세로형 선형그래프이므로 x 축에 ticks : ticks(graphDt 값을 담고있는 배열) 선언으로 데이터의 기준점(날짜기준(graphDt))을 셋팅 )

yaxis : y 축

 

 

* 가로형 그래프로 그릴 경우 x 축 y 축 설정을 반대로 주고,

그래프 데이터를 인자값으로 넘길 때 2차원 배열형태를 조금 더 신경 써서 만들어야 한다.

 

 

 

참고 : 아래 사이트에 예제를 비롯한 각각의 옵션들이 친절하고 자세하게 설명이 되어있다. 영어로...

http://www.jqplot.com/examples/barTest.php

 

Bar Charts

 

www.jqplot.com

 

 

반응형

+ Recent posts