728x90
반응형
728x90
반응형
728x90
반응형

1. string 타입의 값을 객체에 key로 만들고 싶다.

1
2
3
4
5
6
7
var arrKey = ['k1''k2''k3'];
var arrVal = ['abc'120'000'];
 
var kkk = new Object();
kkk[arrKey[0]] = arrVal[0];
kkk[arrKey[1]] = arrVal[1];
kkk[arrKey[2]] = arrVal[2];
cs

 

 

크롬 console 창에서 실행 결과 1

 

 

 

2. 해당 키값이 있나 확인하고 싶다.

1
2
3
4
5
6
7
8
9
10
11
console.log('k1' in kkk); // true
console.log('k9' in kkk); // false
 
kkk.k8 = {
    q1 : 'qeg',
    q2 : 141,
    q3 : 'gkgk'
};
 
console.log('q1' in kkk); // false
console.log('q1' in kkk.k8); // true
cs

 

 

크롬 console 창에서 실행 결과 2

 

 

3. 간단한 오브젝트 함수

1
2
3
4
var objTemp = {name : 'kim', age : 22, height : 175};
Object.entries(objTemp);
Object.keys(objTemp);
Object.values(objTemp);
cs

 

4. object 에 객체 붙이기

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

 

Object.assign() - JavaScript | MDN

Object.assign() 메서드는 출처 객체들의 모든 열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환합니다.

developer.mozilla.org

 

 

 

 

 

 

 

 

 

.

728x90
반응형
728x90
반응형

목표는 대량 데이터에 대해서 페이지 화면 단위로 사용하기 위한 용도다.

(참고로 AdminLte3의 템플릿을 기반으로 한 내용이다.)

 

# jQuery DataTable Server Side의 기본정보 : https://datatables.net/examples/data_sources/server_side

 

DataTables example - Server-side processing

Server-side processing There are many ways to get your data into DataTables, and if you are working with seriously large databases, you might want to consider using the server-side options that DataTables provides. With server-side processing enabled, all

datatables.net

 

1. 필요한 css와 js 적용

몇개는 사용하지 않을 수도 있는 내용이나, 우선 DataTable의 기능을 다 쓴다는 가정하에 작성 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- css files for DataTables -->
<link rel="stylesheet" href="../../plugins/datatables-bs4/css/dataTables.bootstrap4.min.css">
<link rel="stylesheet" href="../../plugins/datatables-responsive/css/responsive.bootstrap4.min.css">
<link rel="stylesheet" href="../../plugins/datatables-buttons/css/buttons.bootstrap4.min.css">
 
<!-- javascript files for DataTables & Plugins -->
<script src="../../plugins/datatables/jquery.dataTables.min.js"></script>
<script src="../../plugins/datatables-bs4/js/dataTables.bootstrap4.min.js"></script>
<script src="../../plugins/datatables-responsive/js/dataTables.responsive.min.js"></script>
<script src="../../plugins/datatables-responsive/js/responsive.bootstrap4.min.js"></script>
<script src="../../plugins/datatables-buttons/js/dataTables.buttons.min.js"></script>
<script src="../../plugins/datatables-buttons/js/buttons.bootstrap4.min.js"></script>
<script src="../../plugins/jszip/jszip.min.js"></script>
<script src="../../plugins/pdfmake/pdfmake.min.js"></script>
<script src="../../plugins/pdfmake/vfs_fonts.js"></script>
<script src="../../plugins/datatables-buttons/js/buttons.html5.min.js"></script>
<script src="../../plugins/datatables-buttons/js/buttons.print.min.js"></script>
<script src="../../plugins/datatables-buttons/js/buttons.colVis.min.js"></script>
cs

2. html table 작성

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
<table id="table" >
    <thead>
        <tr>
            <th>Group Code</th>
            <th>Name</th>
            <th>Used Yes/No</th>
            <th>Delete Yes/No</th>
            <th>Create Date</th>
        </tr>
    </thead>
    <!-----------------------
    
    table ajax return data
        
   ------------------------>
    <tfoot>
        <tr>
            <th>Group Code</th>
            <th>Name</th>
            <th>Used Yes/No</th>
            <th>Delete Yes/No</th>
            <th>Create Date</th>
        </tr>
    </tfoot>
</table>
cs

 

3. DataTable 적용을 위한 javascript

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
var orderColumn = [ "GRP_CD""GRP_NM""USE_YN""DEL_YN""CRT_DT" ];   // 정렬조건의 컬럼명
 
$('#table').DataTable({
    // "scrollY" : 300,
    // "scrollCollapse" : true, // 일정높이 이상 시 Y스크롤 추가
    "paging" : true,
    "searching" : false,
    "info" : true,
    "autoWidth" : false,
    "responsive" : true,
    "lengthChange" : true,          // 페이지 조회 시 row를 변경할 것인지
    // "pageLength" : 15,           // lengthChange가 false인 경우 조회 row 수
    "lengthMenu" : [ 102050 ],  // lengthChange가 true인 경우 선택할 수 있는 값 설정
    "ordering" : true,
    "columns" : [ {                 // 테이블에 맵핑할 리턴 파라미터명 순서
        "data" : "grpCd"
    }, {
        "data" : "grpNm"
    }, {
        "data" : "useYn"
    }, {
        "data" : "delYn"
    }, {
        "data" : "crtDt"
    } ],
    "processing" : true,
    "serverSide" : true,        // serverside 사용 여부
    "ajax" : {
        url : "/ajax/getPageList",
        data : function(d) {
            // d.page = $('#table').DataTable().page.info().page + 1;    // 페이지 번호, DataTable().page.info().page은 0임
            // d.pageSize = $('#table').DataTable().page.len();            // 페이지 사이즈, 한 페이지에 몇개의 row인지
            // d.orderBy = orderColumn[$('#table').DataTable().order()[0][0]];        // 정렬조건 컬럼명
            // d.orderCondition = $('#table').DataTable().order()[0][1];            // 오름 또는 
        },
    },
// "dom" : 'Bfrtip',        // 버튼 추가
// "buttons" : [
// "copy"
// , {
// extend : 'csv',
// charset : 'UTF-8',
// bom : true,
// }
// , "excel"
// , "pdf"
// , "print"
// , "colvis"
// ],
});
cs

1) 15번 라인의 columns는 리턴 파리미터명에 대한 것으로 html의 thead, tfoot에 작성한 순서와 맞춰 맵핑해 준다.

2) 27번 라인 serverSide는 true여야 한다.

3) 31~34라인은 서버쪽 소스를 수정하지 않기 위해 내가 추가한 부분이다.

해당 부분을 제외하면 아래 URL 셈플과 같이 서버에 요청한다.

URL 셈플 : /ajax/getPageList?draw=1&columns[0][data]=grpCd&columns[0][name]=&columns[0][searchable]=true&columns[0][orderable]=true&columns[0][search][value]=&columns[0][search][regex]=false&columns[1][data]=grpNm&columns[1][name]=&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false&columns[2][data]=useYn&columns[2][name]=&columns[2][searchable]=true&columns[2][orderable]=true&columns[2][search][value]=&columns[2][search][regex]=false&columns[3][data]=delYn&columns[3][name]=&columns[3][searchable]=true&columns[3][orderable]=true&columns[3][search][value]=&columns[3][search][regex]=false&columns[4][data]=crtDt&columns[4][name]=&columns[4][searchable]=true&columns[4][orderable]=true&columns[4][search][value]=&columns[4][search][regex]=false&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&search[regex]=false&_=1617172835689

 

* 31~34라인에 사용한 api 정보

https://datatables.net/reference/api/page.info()

https://datatables.net/reference/api/page.len()

https://datatables.net/reference/event/order

 

4) 37~49라인은 테이블 상단에 아래와 같이 버튼을 넣어줄 수 있다.

주의할 점은 버튼 기능의 데이터 범위는 페이지 조회시 조회한 데이터 정보 만큼이다. (serverside는 보이는 라인만큼)

테스트 해 보니 기본 csv와 pdf에서 한글이 깨지고 있다.

(1) csv는 다른 버튼과 같이 {} 없이 "csv"를 사용하면 깨지므로 위와 같이 사용한다. 

* csv 한글깨짐 현상 정보 : https://datatables.net/forums/discussion/37150/buttons-export-to-csv-doesnt-seem-to-take-charset-setting-into-account

(2) pdf는 해당되는 플러그인 파일을 변경해 주면된다.

* pdf 한글깨짐 현상 정보 : http://blog.naver.com/PostView.nhn?blogId=rosijin&logNo=221322270984&parentCategoryNo=&categoryNo=4&viewDate=&isShowPopularPosts=false&from=postView

 

pdfmake 한글 깨짐

문제의 원인은 vfs_fonts.js 가 한글 지원이 안되는것 같다. 한글지원되는 첨부파일로 바꾸면 해결됨

blog.naver.com

* 혹시 링크가 사라지면 아래 파일 사용

pdf.zip
4.78MB

 

 

4. 응답 파라미터에 맞춰 서버에서 리턴

예시와 같이 리턴 파라미터를 작성해 주면 자동으로 페이징을 적용한다.

원래 내 의도는 서버쪽의 응답을 수정하지 않고 javascript단에서 서버의 리턴 내용을 수정하여 DataTable에 끼워 넣고 싶었으나 몇가지 원하는 정보가 들어가지 않았다.

그런데 메뉴얼 대로 서버의 응답을 만들었더니 잘 적용되고 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# 요청, 응답 파라미터를 잘 정리해 둔 블로그가 있어 링크 추가 : https://www.leafcats.com/63 

 

DataTables Serverside processing 개발 예제

※이 방식은 dataTables 1.9이하와 1.10이상의 버전 모두에서 사용 가능한 코드로 짜여진 예제입니다. 1.9 이하와 1.10 이상의 버전에서는 데이터를 주고받는 방식과 전송되는 매개변수가 다릅니다. 하

www.leafcats.com

 

 

 

# 관련글

[jQuery DataTable] work page reload : https://deonggi.tistory.com/101

[jQuery DataTable] row click event : https://deonggi.tistory.com/102

 

728x90
반응형
728x90
반응형

spring에섯 response를 json으로 받을때 날짜 포맷 처리

 

1. gradle

1
2
implementation('com.fasterxml.jackson.datatype:jackson-datatype-jdk8')
implementation('com.fasterxml.jackson.datatype:jackson-datatype-jsr310')
cs

 

2. application.properties

1
2
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
cs

3. 1,2번을 적용해도 반영되지 않을때

@EnableWebMvc 값을 주석 처리 

참고 : https://github.com/spring-projects/spring-boot/issues/6642

 

728x90
반응형
728x90
반응형

페이징을 처음 하나하나 만들었을때는 정말... 어렵다기보단 귀찮고 손이 많이 갔다.

다행히 최근에 알게된 pagehelper를 사용하면 페이징에 필요한 정보를 한번에 간단하게 얻을 수 있게 된다.

 

아래는 pagehelper를 적용하기 위한 셈플 소스

 

1. gradle에 추가

1
implementation group: 'com.github.pagehelper', name: 'pagehelper-spring-boot-starter', version: '1.2.10'
cs

2. properties 추가

1
2
3
# Pagehelper Setting (사용하는 DB, 페이지 번호의 가능 범위 자동 제한)
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
cs

3. controller

1
2
3
4
    @GetMapping(value = "/getPageList")
    public PageInfo<GroupCode> getPageList(SearchGrpCodeReqDTO serachDTO) throws Exception {
        return new PageInfo<GroupCode>(service.getPageList(serachDTO));
    }
cs

4. service

PageHelper.startPage(현재 페이지 번호, 한 페이지에 노출할 row 수);

1
2
3
4
    public Page<GroupCode> getPageList(SearchGrpCodeReqDTO reqDTO) throws Exception {
        PageHelper.startPage(reqDTO.getPage(), reqDTO.getPageSize());
        return (Page<GroupCode>) groupCoceMapper.getPageList(reqDTO);
    }
cs

5. mapper

1
 public Page<GroupCode> getPageList(SearchGrpCodeReqDTO serachReqDTO) throws Exception ;
cs

6. mapper.xml

1
2
3
4
5
6
    <select id="getPageList" 
        parameterType="com.deonggi.bookkeeping.admin.system.grpcd.service.dto.SearchGrpCodeReqDTO" 
        resultType="com.deonggi.bookkeeping.core.model.entity.GroupCode">
        SELECT * FROM GROUP_CODE
        ORDER BY CRT_DT ASC
    </select>
cs

7. 리턴된 결과

(참고로 GROUP_CODE 테이블의 전체 row 수는 16개다.)

mapper.xml에서는 모든 row를 조회하지만,

pagehelper를 적용하면 실제 list에는 아래 쿼리 조회 결과만 들어 있다.

 

SELECT * FROM GROUP_CODE
ORDER BY CRT_DT ASC LIMIT 5

 

쿼리문에 limit 가 추가되어 있는데 이 정보는 위 4번에서 PageHelper.startPage()에 의해 적용되는 정보다.

 

결과적으로 pagehelper를 적용하면 개발자는 한번의 조회를 위한 코드를 통해 페이징을 위한 모든 정보를 얻을 수 있게 된다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

## pagehelper github의 정보 : github.com/pagehelper/Mybatis-PageHelper

 

pagehelper/Mybatis-PageHelper

Mybatis通用分页插件. Contribute to pagehelper/Mybatis-PageHelper development by creating an account on GitHub.

github.com

중국어라 왠지 신뢰가 안 갈 수 있지만 상당히 쓸만하다....

728x90
반응형

'코딩 삽질' 카테고리의 다른 글

[jQuery DataTable] table paging 적용  (0) 2021.03.31
[java, spring] spring.jackson.date-format 적용하기  (0) 2021.03.31
[java] StringTrimConverter  (0) 2021.03.24
[java, spring] modelmapper 사용하기  (0) 2021.03.24
[sts] war 생성  (0) 2020.09.16
728x90
반응형

객체에 사용되는 string에 대해 한번에 trim해 주는 메소드입니다. 

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
public class StringTrimConverter {
 
    public static Object trimReflective(Object object) throws Exception {
        if (object == null)
            return null;
 
        Class<extends Object> c = object.getClass();
        
        try {
            // Introspector usage to pick the getters conveniently thereby
            // excluding the Object getters
            for (PropertyDescriptor propertyDescriptor : Introspector
                    .getBeanInfo(c, Object.class).getPropertyDescriptors()) {
                Method method = propertyDescriptor.getReadMethod();
                String name = method.getName();
 
                // If the current level of Property is of type String
                if (method.getReturnType().equals(String.class)) {
                    String property = (String) method.invoke(object);
                    if (property != null) {
                        Method setter = c.getMethod("set" + name.substring(3),
                                new Class<?>[] { String.class });
                        if (setter != null)
                            // Setter to trim and set the trimmed String value
                            setter.invoke(object, property.trim());
                    }
                }
 
                // If an Object Array of Properties - added additional check to
                // avoid getBytes returning a byte[] and process
                if (method.getReturnType().isArray()
                        && !method.getReturnType().isPrimitive()
                        && !method.getReturnType().equals(String[].class)
                        && !method.getReturnType().equals(byte[].class)) {
                    System.out.println(method.getReturnType());
                    // Type check for primitive arrays (would fail typecasting
                    // in case of int[], char[] etc)
                    if (method.invoke(object) instanceof Object[]) {
                        Object[] objectArray = (Object[]) method.invoke(object);
                        if (objectArray != null) {
                            for (Object obj : (Object[]) objectArray) {
                                // Recursively revisit with the current property
                                trimReflective(obj);
                            }
                        }
                    }
                }
                // If a String array
                if (method.getReturnType().equals(String[].class)) {
                    String[] propertyArray = (String[]) method.invoke(object);
                    if (propertyArray != null) {
                        Method setter = c.getMethod("set" + name.substring(3),
                                new Class<?>[] { String[].class });
                        if (setter != null) {
                            String[] modifiedArray = new String[propertyArray.length];
                            for (int i = 0; i < propertyArray.length; i++)
                                if (propertyArray[i] != null)
                                    modifiedArray[i] = propertyArray[i].trim();
 
                            // Explicit wrapping
                            setter.invoke(object,
                                    new Object[] { modifiedArray });
                        }
                    }
                }
                // Collections start
                if (Collection.class.isAssignableFrom(method.getReturnType())) {
                    Collection collectionProperty = (Collection) method
                            .invoke(object);
                    if (collectionProperty != null) {
                        for (int index = 0; index < collectionProperty.size(); index++) {
                            if (collectionProperty.toArray()[index] instanceof String) {
                                String element = (String) collectionProperty
                                        .toArray()[index];
 
                                if (element != null) {
                                    // Check if List was created with
                                    // Arrays.asList (non-resizable Array)
                                    if (collectionProperty instanceof List) {
                                        ((List) collectionProperty).set(index,
                                                element.trim());
                                    } else {
                                        collectionProperty.remove(element);
                                        collectionProperty.add(element.trim());
                                    }
                                }
                            } else {
                                // Recursively revisit with the current property
                                trimReflective(collectionProperty.toArray()[index]);
                            }
                        }
                    }
                }
                // Separate placement for Map with special conditions to process
                // keys and values
                if (method.getReturnType().equals(Map.class)) {
                    Map mapProperty = (Map) method.invoke(object);
                    if (mapProperty != null) {
                        // Keys
                        for (int index = 0; index < mapProperty.keySet().size(); index++) {
                            if (mapProperty.keySet().toArray()[index] instanceof String) {
                                String element = (String) mapProperty.keySet()
                                        .toArray()[index];
                                if (element != null) {
                                    mapProperty.put(element.trim(),
                                            mapProperty.get(element));
                                    mapProperty.remove(element);
                                }
                            } else {
                                // Recursively revisit with the current property
                                trimReflective(mapProperty.get(index));
                            }
 
                        }
                        // Values
                        for (Map.Entry entry : (Set<Map.Entry>) mapProperty
                                .entrySet()) {
 
                            if (entry.getValue() instanceof String) {
                                String element = (String) entry.getValue();
                                if (element != null) {
                                    entry.setValue(element.trim());
                                }
                            } else {
                                // Recursively revisit with the current property
                                trimReflective(entry.getValue());
                            }
                        }
                    }
                } else {// Catch a custom data type as property and send through
                        // recursion
                    Object property = (Object) method.invoke(object);
                    if (property != null) {
                        trimReflective(property);
                    }
                }
            }
 
        } catch (Exception e) {
            throw new Exception("Strings cannot be trimmed because: ", e);
        }
 
        return object;
 
    }
}
cs

 

실제 vo에 사용 시 아래와 같이 사용하면 됩니다.

1
GrpCodeReqDTO req = (GrpCodeReqDTO) StringTrimConverter.trimReflective(reqDTO);
cs

당연하지만 getter, setter가 있어야 합니다.

이걸 빼먹고 1시간 삽질했네요.

 

아쉽게도 해외 원문 작성글이 있었는데 찾을 수가 없어서 링크를 걸어줄 수 없군요.

728x90
반응형
728x90
반응형

간단하게 dto를 entity에 넣어주고 싶을때 modelmapper를 사용하면 한줄로 해결할 수 있다.

 

# modelmapper 사용하는 방법

1. gradle에 추가하기

1
implementation group: 'org.modelmapper', name: 'modelmapper', version: '2.3.2'
cs

 

2. Bean 등록

1
2
3
4
5
6
7
    /**
     * ModelMapper 설정
     */
    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
cs

 

3. Service에서 modelmapper 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Autowired
    private ModelMapper modelMapper;
 
    public GroupCode insertItem(GrpCodeReqDTO reqDTO) {
        GroupCode entity = modelMapper.map(reqDTO, GroupCode.class);
        return groupCodeRepository.save(entity);
    }
 
    public GroupCode updateItem(GrpCodeReqDTO reqDTO) {
        GroupCode entity = groupCodeRepository.findById(reqDTO.getGrpCd());
        modelMapper.map(reqDTO, entity);
        return groupCodeRepository.save(entity);
    }
 
cs

5번 라인의 GroupCode entity = modelMapper.map(reqDTO, GroupCode.class);

11번 라인의 modelMapper.map(reqDTO, entity);

해당 라인을 참고하여 사용하면 된다.

 

 

※ 참고사항: 맵핑에 대한 설정정보

ModelMapper - Configuration

Configuration ModelMapper uses a set of conventions and configuration to determine which source and destination properties match each other. Available configuration, along with default values, is described below: Setting Description Default Value Access le

modelmapper.org

 

728x90
반응형
728x90
반응형

# STS version 4.1.1.RELEASE

1. Package Explorer의 패키지 폴더 우클릭 -> Run As -> Run Configurations

 

2. Gradle Project -> 상단 좌측의 New launch configuration

3. 새 환경의 Gradle Tasks에 'bootWar' 입력 -> Working Directory에 '${workspace_loc:/

ProjectName

}' -> Apply

4. Run을 선택하면 프로젝트\build\libs\ 폴더에 war 파일 생성됨.

 

728x90
반응형
728x90
반응형

Spring boot의 war 파일을 weblogic에서 배포시 net.sf.log4jdbc.sql.jdbcapi.DriverSpy 에러와 함께 배포가 되지 않는 문제가 있다.

 

weblogic version : 12.2.1.3.0

 

변경 내용을 활성화하는 중 오류가 발생했습니다. 자세한 내용은 로그를 참조하십시오.

java.lang.IllegalStateException: Cannot load driver class: net.sf.log4jdbc.sql.jdbcapi.DriverSpy

Cannot load driver class: net.sf.log4jdbc.sql.jdbcapi.DriverSpy

 

 

# db connection1
spring.datasource.url=jdbc:log4jdbc:mysql://xxx.xxx.xxx.xxx:3306/myDb
spring.datasource.username=accountName
spring.datasource.password=password
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy

 

db 연결에 대한 정보에서 driver 문제로 인해 발생된 것.

아래와 같이 변경하면 배포 된다. (대신 로그가 간소화 된다는 점 참고.)


# db connection2

spring.datasource.url=jdbc:log4jdbc:mysql://xxx.xxx.xxx.xxx:3306/myDb
spring.datasource.username=accountName
spring.datasource.password=password

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

 

 

 

 

728x90
반응형

+ Recent posts