Spring Boot 게시판 만들기 2 : DB 연동 및 Mybatis 설정

Spring Boot - ver 2.1.8

Gradle

Java - ver 1.8

 

application.properties

내에 datasource 관련 설정값 선언

1
2
3
4
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC
spring.datasource.username=id
spring.datasource.password=pw
cs

 

스프링부트 앱을 구동시키는 main 메소드 내의 @SpringBootApplication 을 들어가보면 @EnableAutoConfiguration 어노테이션이 존재.

해당 어노테이션이  프로젝트 내 Bean 들의 DI 주입을 자동으로 설정해준단다.

(Spring Framework 프로젝트의 web.xml 파일 내에 context.xml 를 매핑해주는 과정을 직접 하지 않아도 된다고 한다)

이 이상 자세히 파기엔 내공의 한계가 있어 일단 나중에 더 공부해보자..

 

위와 같이 프로퍼티에 datasource 관련 설정값만 선언해주면

아래와 같은 코드와 같다고 보면 된다.

1
2
3
4
5
6
7
8
9
10
    @Bean
    public DataSource customDataSource() {
 
        return DataSourceBuilder.create()
                                .url("jdbc:mysql://ip:3306/!?useSSL=false&serverTimezone=UTC")
                                .driverClassName("com.mysql.cj.jdbc.Driver")
                                .username("id")
                                .password("pw")
                                .build();
    }
cs

 

다음으로,

SqlSessionFactory 와 SqlSessionTemplate 설정.

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
package com.jpp.main.config;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@MapperScan(value= {"com.jpp.main.mapper"})
@EnableTransactionManagement
public class MybatisConfig {
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
            
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
            
            sessionFactory.setMapperLocations(res);
            
            return sessionFactory.getObject();
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
 
cs

위와 같이 datasource 관련 설정을 application.properties 에 설정하고,

mybatis 관련 설정은 java config 로 설정할 수 있다. (mybatis 는 application.properties 에 설정 불가)

application.properties 에 설정 가능한 값들은 여기를 참고

 

 datasource도 application.properties 대신 java config 로 설정하고 싶다면 아래와 같이 datasource 를 @Bean 으로 설정해주자.

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
package com.jpp.main.config;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@MapperScan(value= {"com.jpp.main.mapper"})
@EnableTransactionManagement
public class MybatisConfig {
    
    @Bean
    public DataSource customDataSource() {
        return DataSourceBuilder.create()
                                .url("jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC")
                                .driverClassName("com.mysql.cj.jdbc.Driver")
                                .username("id")
                                .password("pw")
                                .build();
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
            
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
            
            sessionFactory.setMapperLocations(res);
            
            return sessionFactory.getObject();
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
 
cs

17 Line : @MapperScan : com.jpp.main.mapper 경로 밑에 있는 mapper Interface 를 스캔하여 빈으로 등록 (mapper Interface 대신 DAO를 사용할 경우 필요 없음)

36 Line : getResources("classpath:mapper/*Mapper.xml");

~Mapper.xml 파일명 기준 모든 sql mapper 를 sqlSessionFactory에 등록

 

 

[mainMapper.xml]

간단한 select 쿼리를 작성하여 Mapper를 작성해준다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 
<mapper namespace="com.jpp.main.mapper.MainMapper">
 
    <select id="getUserList" resultType="com.jpp.main.vo.UserVO">
        select empno as no, emp_nm as name 
        from emp_info
        limit 10
    </select>
 
</mapper>
 
 
cs

 

* Mapper Interface 사용 vs DAO 사용

1) DAO 사용시

1-1) MainDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.jpp.main.dao;
 
import java.util.List;
 
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.jpp.main.vo.UserVO;
 
@Repository
public class MainDAO {
    
   @Resource
    private SqlSessionTemplate sqlSessionTemplate;
    
    private static final String MAPPER_NM = "com.jpp.main.mapper.MainMapper.";
    
    public List<UserVO> getUserList(){
        return sqlSessionTemplate.selectList(MAPPER_NM+"getUserList");
    }
}
 
cs

1-2) MainService

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
package com.jpp.main.service;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.mapper.MainMapper;
import com.jpp.main.vo.UserVO;
 
@Service
public class MainService {
    @Autowired
    private MainDAO mainDao;
    
    public List<UserVO> getUserList() throws Exception {
        return mainDao.getUserList();
    }
}
 
cs

 

 

2) mapper Interface 사용시

2-1) MainMapper

1
2
3
4
5
6
7
8
9
10
11
package com.jpp.main.mapper;
 
import java.util.List;
 
import com.jpp.main.vo.UserVO;
 
public interface MainMapper {
    
    public List<UserVO> getUserList() throws Exception;
}
 
cs

 

2-2) MainService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.jpp.main.service;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.mapper.MainMapper;
import com.jpp.main.vo.UserVO;
 
@Service
public class MainService {
 
    @Autowired
    private MainMapper mainMapper;
    
    public List<UserVO> getUserList() throws Exception {
        
        return mainMapper.getUserList();
    }
}
 
cs

 

 

 

반응형

1. 우클릭 > New > Spring Starter Project

 

2. 프로젝트명 입력

   Gradle / Maven 선택,

   배포 파일형태 Jar/War 선택

   Java 버전 선택

 

3. Boot Version 선택

   사용할 Library 선택(WEB, JPA 등)

 

4. @SpringBootApplication 어노테이션이 붙은, (main 메소드를 지닌) 클래스의 이름을

Application 으로 수정 밑 실행(Run As > Spring Boot App)

 

5. 프로젝트 우클릭 > Gradle > Refresh Gradle Project

 

 

※ Lombok 설치 (참고)

1) lombok-1.18.10.jar 가 위치한 경로로 이동 (버전은 다를 수 있음)

2) jar 가 위치한 경로에서 shift + 우클릭하여 Powershell 실행

3) java -jar lombok-1.18.10.jar 로 lombok jar 실행 

4) Specify location 버튼을 누른 후 sts.exe(혹은 사용중인 IDE 실행파일) 선택

 

5) 완료 화면

 

6) IDE 재시작

7) sts.ini 파일을 연 후

-vmargs

-javaagent:lombok.jar (약간 다를 수 있음)

위의 내용이 추가 되었는지 확인(sts.exe와 동일한 경로에 위치)

 

참고 :

https://jojoldu.tistory.com/250?category=635883

 

 

반응형

https://github.com/ParkSB/javascript-style-guide

반응형

팝업 확장 및 구현하기 : Customized PopUp (alert, confirm, loading)

 

 

자바스크립트 내장 함수인 alert, confirm 대화상자를 사용하여 유저에게 알림팝업을 띄우거나, 동의를 구하는 팝업을 띄울 수 있다. 

하지만 팝업 UI를 개발자 마음대로 바꾸는게 불가하며 좀 더 다양한 기능(로딩 팝업 등)을 목적으로 한다면 alert confirm prompt 등의 팝업만으로의 개발에는 한계가 있다.

 

아래와 같이 총 세개의 팝업을 만들어 보자.

alert : 단순알림팝업

comfirm : 확인 및 취소 옵션을 사용자가 선택 할 수 있는 팝업

loading : 로딩(ajax 등의 서버 통신 처리 등)을 하는 동안 떠있는 팝업

 

1. HTML

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
<div class="popup_bg popup_custom_bg" style="display: none;"></div>
<div class="loadingPopup" style="display: none; text-align:center; padding:20px 10px 10px 10px; width:300px; height:250px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <img src = "/resources/img/loading_large.gif" style="width:60px;">    <!-- 로딩 이미지 -->
        <div class="button_wrap">
            <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>대기중단</span></button>
        </div>
    </div>
</div>            
 
<div class="alertPopup" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <div class="button_wrap f_r">
            <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>확인</span></button>
        </div>
    </div>
</div>        
 
<div class="confirmPopup" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <div class="button_wrap">
            <button type="button" class="btn btn_function btn_red btn_popup_ok"><span>확인</span></button>
            <button type="button" class="btn btn_function btn_border btn_popup_cancel"><span>취소</span></button>
        </div>
    </div>
</div>       
cs

1 Line : 팝업 영역을 제외한 영역에 dim 처리(불투명)를 하기 위한 태그

2~10 Line: loadingPopup

12~19 Line: alertPopup

21~29 Line: confirmPopup 

3, 13, 22 Line : 팝업 내 메시지 출력 영역

5 Line : 로딩팝업 내 로딩이미지 출력 영역

 

 

2. Javascript(jQuery)

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
var customPopup = {
 
    open : function(target, mode, msg){
        customPopup.close();
        
        var enterMsg = msg.replace(/\r?\n/g, '<br>');    //엔터 처리
 
        $(target).css("display""block");
        $(target).find(".msg").html(enterMsg);
        
        $(".popup_custom_bg").css("display""block");
    },
    
    close : function(){
        //팝업 전체 지우기
        $(".popup_custom_bg").css("display""none");
        $(".alertPopup").css("display""none");
        $(".confirmPopup").css("display""none");
        $(".loadingPopup").css("display""none");
    },
    
    alert : function(msg, callback){
        //alert 팝업
        $(".alertPopup .btn_popup_ok").unbind("click");
        
        customPopup.open($(".alertPopup"), "alert", msg);
        
        $(".alertPopup .btn_popup_ok").click(function(e){
            customPopup.close();
        });
    },
 
    confirm : function(msg, callback){
        //confirm 팝업
        $(".confirmPopup .btn_popup_ok").unbind("click");
        $(".confirmPopup .btn_popup_cancel").unbind("click");
        
        customPopup.open($(".confirmPopup"), "confirm", msg);
        
        $(".confirmPopup .btn_popup_ok").click(function(e){
            customPopup.close();            
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
 
        $(".confirmPopup .btn_popup_cancel").click(function(e){
            customPopup.close();
        });
        
    },
 
    loading : function(msg, callback){
        //로딩 팝업
        $(".loadingPopup .btn_popup_ok").unbind("click");
        
        customPopup.open($(".loadingPopup"), "loading", msg);
        
        $(".loadingPopup .btn_popup_ok").click(function(e){        
            if(xhr && xhr.readyState != 4){
                xhr.abort(); //ajax 호출 중단
            }
            customPopup.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
    }
}
cs

3~12Line : 팝업 띄우기(display block)

14~20Line : 팝업 숨기기(display none) - 특정 팝업이 호출되었을 때(기존에 떠있는 팝업을 지우기 위해), 팝업 내에서 확인/취소/대기중단 버튼이 눌려 팝업이 닫혀야 할 때 마다 호출되는 함수

22~31Line : 알림 팝업

33~51Line : 컨펌 팝업 - 알림팝업과 다르게 확인버튼에 따른 특정 처리가 필요하므로 callback 함수 필요

59~80Line : 로딩 팝업 - 버튼을 누를 경우 ajax.abort(); 로 ajax 처리를 중단한다. ajax 객체를 담고있는 xhr 변수는 전역변수로 선언.

 

 

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
function loadingSample(){
    customPopup.loading("로딩 중..."function(){
        //대기 중단
        location.href="/main.do";
    });
}
 
function alertSample(){
    customPopup.alert("성공!");
}
 
function confirmSample(){
    customPopup.confirm("조회하시겠습니까?"function(){
        //ajax 통신
    });
}
 
 
function sample(){
 
    var key = $.trim($("#key").val());
        
    customPopup.confirm("진행하시겠습니까?"function(){
 
        customPopup.loading("처리중입니다."function(){});
                
        var params = {
                said : said
            };
    
        xhr = $.ajax({
            type    : "post",
            url        : "/sample.do",
            contentType: "application/json",
            dataType:"json",
            data     : JSON.stringify(params),    
            success    : function (data) {
                if(data){
                    customPopup.alert("성공");
                } else {
                    customPopup.alert("실패");
                }
            },
            error : function (data, status, err) {
                customPopup.alert("실패");
            }
        });
    });
}
cs

 

※ css 파일은 따로 빠져있고, 관련 부분만 따로 발췌하기 힘들어 대충 인라인 태그에 작성하여 올렸습니다.

 

참고 : https://zzznara2.tistory.com/693

위에 기재한 고수 선배님의 소스를 참고하여 작성하였으며,

위 소스가 복잡하다고 생각하여(제 실력이 미천하여 제겐 너무 어려웠으므로..)

제 수준에 맞게 다시 코딩하였습니다.스크립트 공부는 언제 시작할런지

 

 

※ 위 코드를 조금 더 깔끔하게 수정하였습니다. (20191016)

[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
110
111
112
113
114
115
116
<script>
/* 커스텀 팝업 */
var cp_vars = {
    alert : $(".cp_alert"),
    confirm : $(".cp_confirm"),
    loading : $(".cp_loading"),
    bg : $(".cp_bg")    
}
var cp_funcs = {
    
    open : function(target, msg){
        if($(target).length < 1){
            throw new Error("there is no target element !");
        }
        cp_funcs.close();
        
        var enterMsg = msg.replace(/\r?\n/g, '<br>');    //엔터 처리
        
        $(target).css("display""block");
        $(target).find(".msg").html(enterMsg);
        
        cp_vars.bg.css("display""block");
    },
    
    close : function(){
        //팝업 전체 지우기
        cp_vars.bg.css("display""none");
        cp_vars.alert.css("display""none");
        cp_vars.confirm.css("display""none");
        cp_vars.loading.css("display""none");
    },
    
    alert : function(msg, callback){
        //alert 팝업
        cp_vars.alert.find(".btn_popup_ok").unbind("click");
        //$(".cp_alert .btn_popup_ok").unbind("click");
        
        cp_funcs.open(cp_vars.alert, msg);
        
        $(".cp_alert .btn_popup_ok").click(function(e){
            cp_funcs.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                         eval( callback );
                    }
                }
            } 
        });
    },
 
    confirm : function(msg, callback, callback2){
        //confirm 팝업
        cp_vars.confirm.find(".btn_popup_ok").unbind("click");
        cp_vars.confirm.find(".btn_popup_cancel").unbind("click");
        
        cp_funcs.open(cp_vars.confirm, msg);
        
        $(".cp_confirm .btn_popup_ok").click(function(e){
            cp_funcs.close();            
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
 
        $(".cp_confirm .btn_popup_cancel").click(function(e){
            cp_funcs.close();
            if(typeof callback2 != 'undefined' && callback2){
                if(typeof callback2 == 'function'){
                    callback2();
                } else {
                    if( callback2 ) {
                        eval( callback2 );
                    }
                }
            }
        });
    },
 
    loading : function(msg, callback){
        //로딩 팝업
        $(".cp_loading .btn_popup_ok").unbind("click");
        
        cp_funcs.open(cp_vars.loading, msg);
        
        $(".cp_loading .btn_popup_ok").click(function(e){
            if(typeof loopFunc != 'undefined'){
                loopFunc.stopLooping();    //Loop 중단
            }
            if(typeof xhr != 'undefined'){
                if(xhr.readyState != 4){
                    xhr.abort(); //ajax 호출 중단
                }
            }
            cp_funcs.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
    }
}
</script>
cs

수정된 위의 코드는 각 팝업의 레이아웃을 잡는 div node를 전역변수로 선언(3~8 line)하여 사용하고 있으므로

script 로드 순서를 주의해야 합니다.

위 script 소스를 아래의 body 태그보다 아래에 두어야 cp_vars.alert, cp_vars.confirm, cp_vars.loading 변수를 읽는 부분에서 undefined 에러가 나지 않습니다. 스크립트 로드 순서에 따른 문제와 이를 해결하기 위한 방법은 따로 공부를 좀 더 한 후 포스팅 하도록 하겠습니다.

 

[html+css]

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
<body>
    <!-- 커스텀 팝업 노출시 배경 DIM 처리용 -->
    <div class="popup_bg cp_bg" style="display: none;"></div>
    <!-- 로딩 팝업 -->
    <div class="popup small csPopup cp_loading" style="display: none; text-align:center; padding:20px 10px 10px 10px; width:300px; height:250px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <img src = "/resources/img/loading_large.gif" style="width:60px;">    <!-- 로딩 이미지 -->
            <div class="button_wrap">
                 <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>대기중단</span></button>
             </div>
        </div>
    </div>            
    <!-- 알림 팝업 -->
    <div class="popup small csPopup cp_alert" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:230px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <div class="button_wrap f_r">
                 <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>확인</span></button>
             </div>
        </div>
    </div>        
    <!-- 확인 팝업 -->
    <div class="popup small csPopup cp_confirm" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <div class="button_wrap">
                 <button type="button" class="btn btn_function btn_red btn_popup_ok"><span>확인</span></button>
                 <button type="button" class="btn btn_function btn_border btn_popup_cancel"><span>취소</span></button>
             </div>
        </div>
    </div>
</body>
 
cs

 

 

 

 

반응형

2019. 9. 2    : 시작

2019. 11. 21 : 아직 꿈에서 담배를 핀다.

반응형

'etc. > life' 카테고리의 다른 글

한 달간 방학 : 이직과 결혼  (2) 2020.06.11

localStorage vs sessionStorage

 

1. localStorage

1) 개념

* stores data with no expiration date : 

  사용자의 브라우저에 영구적으로 보관되는 데이터

* 5MB 제한

* 보안 취약

* 동기방식 처리

참고 : https://www.w3schools.com/html/html5_webstorage.asp

2) 사용 케이스

사용자가 마지막에 찾아본 사람을 사용자(client) 브라우저의 로컬스토리지에 저장.

사용자가 재진입시 마지막에 찾아본 사람의 정보를 재활용 할 수 있다.

참고 : https://css-tricks.com/localstorage-examples/

 

 

2. sessionStorage

1) 개념

stores data for one session (data is lost when the browser tab is closed) :

사용자가 브라우저를 닫을 경우 사라지는 데이터

 

2) 사용 케이스

ajax 비동기 처리로 서버 통신을 하여 인터페이스를 그릴 때, 화면에서 새로고침이 일어날 경우 화면에 노출중인 값들의 기준(조회조건 등)을 유지시키기 위해 sessionStorage 를 사용.

* 동일한 이유로 localStorage 를 사용해도 상관없지만 localStorage는 삭제를 해주지 않을 경우 사용자 브라우저에 영원히 남게 되므로 삭제 처리를 잘 해주어야 한다.

참고 : https://stackoverflow.com/questions/8498357/when-should-i-use-html5-sessionstorage

 

 

3. 사용법 (저장, 조회, 삭제)

1) 저장(save)

1-1) localStorage.setItem("key", "value"); 

1-2) localStorage.key = "value";

 

2) 조회(find/search/get)

2-1) localStorage.getItem("key");

2-2) var value = localStorage.key;

 

3) 삭제(remove)

localStorage.removeItem("key");

 

※ sessionStorage usage is the same as localStorage

   sessionStorage 사용법은 localStorage사용법과 같다

 

※ How and where to find local/session stored data

    storage에 저장된 데이터 확인법

1. on chrome browser, using developer tools(F12)

  크롬브라우저 개발자 도구에서 확인

: chrome developer tools(F12) > Application tab > Storage tab (left side)

  크롬 개발자도구(F12) > Application 상단탭 > Storage 좌측 탭 에서 확인이 가능하다.

 

2. at local directory

로컬 물리 경로에서 확인

참고 : https://stackoverflow.com/questions/8634058/where-the-sessionstorage-and-localstorage-stored

 

 

※ 엉터리 영어 포스팅이 재밌다.

반응형

Maven error (Missing artifact error)

pom.xml 내의 dependency 설정이 잘못되었거나 repository 로 부터 dependency jar 들을 가져오지 못하는 경우 발생하는 에러.

 

pom.xml 에 딱히 이상이 없고 project clean 을 해봐도 해당 에러가 해결되지 않는다면,

Update maven 에서 Snapshots/Releases 강제 업데이트 옵션을 체크 후 maven update.

 

참고 : https://examples.javacodegeeks.com/enterprise-java/maven/maven-resolve-missing-artifact-error-example/

반응형

배열안에 담겨있는 값(VO)들을 특정 기준으로 비교하여 순서를 정렬하기 위한 방법을 알아보자.

 

1. Car 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package comparator;
 
public class Car {
    
    private String name;
    private int price;
    
    public Car (String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
}
cs

 

2. Car 객체 정렬 및 실행을 위한 Main 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package comparator;
 
import java.util.Arrays;
 
public class Main {
 
    public static void main(String[] args) {
        
        Car c1 = new Car("K5"2000);
        Car c2 = new Car("A6"8000);
        Car c3 = new Car("BMW3"4000);
        
        Car[] cars = {c1, c2, c3};
        
        Arrays.sort(cars);
 
        for(Car tmp : cars) {
            System.out.println(tmp.getName()+" ");
        }        
    }
}
 
cs

'정렬기준이 없는데?' 라고 생각 들 수 있겠지만 일단 실행시켜보자.

 

역시나 에러가 발생한다.

comparator.Car 가 java.lang.Comparable 로 캐스팅 될 수 없다는 캐스팅 에러.

Arrays.sort 내에서 comparator, comparable 을 사용하다 에러가 발생했다.

Exception in thread "main" java.lang.ClassCastException: comparator.Car cannot be cast to java.lang.Comparable
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:290)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:157)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at comparator.Main.main(Main.java:19)

 

그럼 이제 Arrays.sort 를 탐구해보자.

 

※ Arrays.sort 

아래는 java.util.Arrays 의 정적메소드 sort.

정확한 파악은 힘들지만 대충 살펴보았을 때 2번째 인자인 Comparator c 값의 유무에 따라 사용되는 메소드가 분기처리 되는 듯 하다.

1) 은 인자가 한개인경우 호출되는 메소드,

2) 는 인자가 두개인 경우 호출되는 메소드

 

1) Arrays.sort(배열) 일 경우 사용되는 method

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
private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low;
 
        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
 
        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);
 
        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }
 
        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }
cs

내부적으로 객체를 Comparable 로 형변환을 한 후 compareTo 메소드를 호출하여 값을 비교(정렬) 하고 있다.

첫번째 인자로 넘겨준 Car 객체는 Comparable(인터페이스) 를 구현하고 있지 않으므로 Comparable 객체로 형변환시 cast exception이 발생한 것이다.

 

그럼 Car 객체가 Comparable 인터페이스를 구현하도록 Car Class 를 아래와 같이 수정해보자.

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
package comparator;
 
public class Car implements Comparable<Car> {
    
    private String name;
    private int price;
    
    public Car (String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    
    @Override
    public int compareTo(Car c) {
        
        int comPrice = c.getPrice();
        
        return this.price-comPrice;
    }
}
cs

 

다시 한 번 2번의 Main 클래스를 실행해보자

실행 결과는 아래와 같이 성공이다.

※ Car 객체의 compareTo 메소드의 return 부분(30 line : this.price-comPrice)

을 comPrice-this.price 와 같이 반대로 바꾸면 정렬 기준이 바뀐다(오름차순/내림차순).

 

위와 같이 VO 클래스를 Comparable interface 를 구현하도록 수정 후 compareTo 를 overriding 하면 원하는 방식으로 정렬을 시킬 수 있다.

VO 클래스를 이와 같이 매번 구현하여 사용하긴 번거로운데, 다른 방법은 없을까?

 

Arrays.sort 를 다시 한 번 들여다 보자.

2) Arrays.sort(배열, Comparator ?) 일 경우 사용되는 method가 아래와 같이 구현되어 있다.

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
private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;
 
        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
 
        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);
 
        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }
 
        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }
cs

내부적으로 인자값으로 넘어온 Comparator의 compare 메소드를 호출하여 값을 비교(정렬) 하고 있다.

 

Comparable 을 구현한 Car 객체는 원복 시킨 후, Main 클래스를 아래와 같이 수정해보자.

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
package comparator;
 
import java.util.Arrays;
import java.util.Comparator;
 
public class Main {
 
    public static void main(String[] args) {
        
        Car c1 = new Car("K5"2000);
        Car c2 = new Car("A6"8000);
        Car c3 = new Car("BMW3"4000);
        
        Car[] cars = {c1, c2, c3};
        
        System.out.println("sort start!");
        
        Arrays.sort(cars, new Comparator<Car>(){
            @Override
            public int compare(Car c1, Car c2) {
                //return c1.getPrice() - c2.getPrice();
                return c2.getPrice() - c1.getPrice();
            }
        });
        
        for(Car tmp : cars) {
            System.out.println(tmp.getName()+" ");
        }
        
    }
 
}
 
cs

Arrays.sort 의 두번째 인자값으로 Comparator 를 넘겨주었다. (익명클래스로 구현)

 

실행시 결과는 아래와 같다.

 

사족 : Comparable, Comparator를 통해 바라본 인터페이스에 대해..

Arrays.sort 메소드는 내부적으로 파라미터 값으로 받은 객체의 compareTo 메소드 및 Comparable interface 의 compare 메소드를 사용하고 있다.

어떤 자료형의 파라미터 값이 넘어올지 Arrays 의 sort 메소드는 알 수 없지만 파라미터로 넘겨받은 객체 내에 compareTo 가 존재하리라 가정하고 소스가 짜여져 있다.

이처럼 인터페이스는 구현을 강제하며(Car 클래스의 compareTo 메소드) 소스간 결합도(Arrays.sort 와 Car클래스)를 약하게 한다. 또한 내공이 필요 하겠으나, 개발자들은 이를 활용하여 다양한 디자인 패턴의 개발을 할 수 있다..

 

 

 

반응형

.session is already registered Exception

 

위와 같은 로그가 찍히면서 jboss 구동이 되지 않는 경우가 있다.

sometimes jboss server can not be launched with the above exception msg.

서버재시작, 서버 제거, server clean 해도 해당 exception 이 해결이 되지 않는다.

server restart, delete server/add server, server clean ways can not solve this.

이 경우 아래와 같이 해결한다.

in this case, follow below steps.

 

1. wildfly/standalone/deployments/ 경로 이동

move to wildfly/standalone/deployments/

2. 경로 밑의 사용하지 않는 .war file 들 제거

delete .war files not used

 

참고(ref) :

https://stackoverflow.com/questions/38507794/duplicateserviceexception-jboss-server

 

 

 

 

반응형

#1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> list1 = new ArrayList<String>();
        
list1.add("사과");
list1.add("딸기");
list1.add("키위");
list1.add("바나나");
 
List<String> list2 = list1;
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
 
System.out.println("list1 의 \"사과\" 제거 ");
list1.remove(0);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
cs

1) list1에 사과, 딸기, 키위, 바나나 를 넣었다 (3~6 line)
2) list2에 list1을 넣어주었다 (8 line)
3) list1에서 사과(index : 0) 값을 제거 (13 line)
4) 사과 제거 전 list1, list2 출력 (9, 10 line)
5) list1에 담긴 사과 제거 후 list1, list2 출력 (15, 16 line)

 

위의 코드 실행시 결과는 어떻게 될까?

list1 의 사과 값만 제거 했으므로 list2 엔 사과 값이 존재하고, 출력될까?

 

결과는 다음과 같다. 

list1 에서 사과를 제거했는데 list2 의 사과도 제거되었다.

 

list2 에 list1을 대입한 게 아닌, list2가 list1 의 주소값을 참조하게 했기 때문이다.

List 는 참조형 이기 때문에 값자체가 아닌 주소값을 참조하므로 

#1과 같이 코딩시 위처럼 예상치 못한 결과가 나올 수 있다. (Map 및 Set 또한 마찬가지)

아래와 같이 addAll (List, Set), putAll (Map)을 사용하여 값 자체를 대입시켜 줄 수 있다.

 

 

#2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> list1 = new ArrayList<String>();
 
list1.add("사과");
list1.add("딸기");
list1.add("키위");
list1.add("바나나");
 
List<String> list2 = new ArrayList<String>();
list2.addAll(list1);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
 
System.out.println("list1 의 \"사과\" 제거 ");
list1.remove(0);
 
System.out.println("list1 : " + list1);
System.out.println("list2 : " + list2);
cs

#1 코드에서 list2 의 선언 및 list1 의 값을 넣어주는 부분(8~9 line) 만 위와 같이 바꿔보았다.

 

결과는 다음과 같다.

#1과 달리 list2의 사과값이 제거되지 않았다.

 

#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
List<Map<StringString>> list1 = new ArrayList<Map<StringString>>();
 
Map<StringString> map1 = new HashMap<StringString>();
map1.put("과일""사과");
map1.put("동물""물고기");
map1.put("지역""서울");
 
Map<StringString> map2 = new HashMap<StringString>();
map2.put("과일""바나나");
map2.put("동물""호랑이");
map2.put("지역""부산");
 
list1.add(map1);
list1.add(map2);
 
List<Map<StringString>> list2 = new ArrayList<Map<StringString>>();
list2.addAll(list1);
 
System.out.println("조작전 list1 : " + list1);
System.out.println("조작전 list2 : " + list2);
System.out.println("=================================");
 
list1.get(0).remove("과일");
 
System.out.println("조작후 list1 : " + list1);
System.out.println("조작후 list2 : " + list2);
cs

1) map1 에 과일:사과, 동물:물고기, 지역:서울 값을 넣는다.

2) map2 에 과일:바나나, 동물:호랑이, 지역:부산 값을 넣는다.

3) list1 에 map1, map2 값을 넣는다.

4) list2 에 list1을 넣는다.

5) list1에 담긴 map1(list1.get(0))의 과일 값을 제거한다

6) 조작전 list1 과 조작 후 list1, list2 를 출력한다.

 

결과는 아래와 같다.

list2에 list1 을 addAll 로 넣어주었는데? 라고 생각할 수 있지만,

list1 이 갖고 있는 map1, map2 값 자체가 아닌 map1, map2 의 주소값만 list2 에 저장되었기 때문이다.

(#2에서 확인한 것 처럼 #3에서 역시, list2.remove(0) 으로 list2에 담겨있는 map1의 주소값을 제거할 경우 list1엔 영향을 주지 않는다)

 

#4

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
List<Map<StringString>> list1 = new ArrayList<Map<StringString>>();
 
Map<StringString> map1 = new HashMap<StringString>();
map1.put("과일""사과");
map1.put("동물""물고기");
map1.put("지역""서울");
 
Map<StringString> map2 = new HashMap<StringString>();
map2.put("과일""바나나");
map2.put("동물""호랑이");
map2.put("지역""부산");
 
list1.add(map1);
list1.add(map2);
 
List<Map<StringString>> list2 = new ArrayList<Map<StringString>>();
        
for(Map<StringString> m : list1) {
    Map<StringString> map3 = new HashMap<StringString>();
    map3.putAll(m);
    list2.add(map3);
}
 
System.out.println("조작전 list1 : " + list1);
System.out.println("조작전 list2 : " + list2);
System.out.println("=================================");
 
map2.remove("과일");
 
System.out.println("조작후 list1 : " + list1);
System.out.println("조작후 list2 : " + list2);
cs

#3 코드에서 list2 의 선언 및 list1 의 값을 넣어주는 부분(18~21 line) 만 위와 같이 바꿔보았다.

 

결과는 다음과 같다.

 

* 2중 for문 및 iterator 를 돌려 map 내의 값을 직접 꺼내 map 에 직접 넣어준 후 list 에 add 를 해줘도 된다.

* map 이 아닌 VO의 경우 clone 을 사용하여 vo 값 자체를 복사한 후, list에 넣어 줄 수도 있다.

 

※ deep copy , shallow copy 에 대한 예제와 설명이 정리되어있는 글

https://howtodoinjava.com/java/collections/arraylist/arraylist-clone-deep-copy/

 

※ 참조형 같으면서 기본형 같은 String 자료형에 대한 설명이 잘 되어있는 좋은 글

https://brunch.co.kr/@kd4/1

반응형

+ Recent posts