본문 바로가기
JAVA

FormData에 객체 배열(object[])을 담을 수 있는 방법

by code:J 2023. 7. 25.
반응형
SMALL

나는 javascript 의 FormData() 를 사용하여 객체 배열을 넣고 싶었다...

성격급한 우리 한국인과 개발자분들은 시간이 없으니

우선 이런식으로 보낸다고 알려드리고 밑에서 예시를 보여드리겠습니다.

let array = [
	obj1 : {
    	data1 : 'java',
        data2 : 'javascript'
        },
    obj2 : {
        data1 : 'spring',
        data2 : 'react'
    }
]
// array라는 배열안에는 2개의 obj1, obj2 객체가 존재하여 
// 이것을 spring Controller 단에 객체에 바인딩을 하고 싶을 때

//javascript 진영
let formData = new FormData()

for (let i=0; i<array.length; i++) {
   formData.append("formDto["+i+"].procData", array[i].procData);
}

//java 진영

public class FormDto {

     List<DTO> formDto;

}


FormData를 다루기는 매우(?) 쉽습니다.
단 필드 하나일 때는 단순하게 아래처럼 입력한 값들을 가져와서 append() 메서드를 사용하여 처리할 수 있습니다.

//HTML 진영에서 이런식으로 사용을 하고 싶다면
<input class='text-area' id='dataDesc' name='dataDesc' />


// javascript 진영에서는 append() 메서드를 사용하여 쉽게 form에 넣어서 데이터를 보낼 수 있습니다.
let dataDesc = document.getElementById('dataDesc').value;
formData.append('dataDesc',dataDesc);

 

하지만 이 포스팅의 목적이자 머리를 아프게 했었던 formData에 객체배열을 Controller로 넘기는것 입니다.

이번에 예시를 들어볼 것은 파일 업로드를 할 때, 단순 파일만 딸랑 업로드 하는것이 아닌 

파일업로드를 하며 내가 원하는 타입으로 ,원하는 이름으로, 파일의 정확한 크기를 나타내는

DataType, dataName , fileSize를 DB에 저장하는 법입니다.

 

또한, 내가 업로드를 한 파일을 빈 DIV 태그에 보여진 후, 바로 업로드 하는 것이 아닌 , 잘못 올려서 삭제도 가능하여 DB에 저장을 원할때 데이터들을 모아서 한번에 보내는 법입니다.

JSP로 파일업로드로 예시를 들어보겠습니다.

 

HTML
<div class="content-body">
            <div class="create-box">
                <div class="create-header">
                </div>
                <div class="table-create">
                    <table>
                        <colgroup>
                            <col width="15%">
                            <col width="35%">
                            <col width="15%">
                            <col width="25%">
                            <col width="10%">
                        </colgroup>
                        <tbody>
                        <form:form name="insertForm" id="insertForm"  class="form-inline">
                        <input type="hidden" id="projectId" name="projectId" value="${projectInfo.projectId}" />
                        <input type="hidden" id="uploadFileDiv"></input>
                        <tr>
                            <th scope="row"><span>DataSet 명</span></th>
                            <td colspan="4"><input type="text" class="input" id="datasetName" name="datasetName"></td>
                        </tr>
                        <tr>
                            <th scope="row"><span>스토리지</span></th>
                            <td>
                                <div class="select-box">
                                    <select id="storageType" name="storageType" style="width:100px">
                                        <option value="Ceph">Ceph</option>
                                        <option value="MinIO">MinIO</option>
                                    </select>
                                </div>
                            </td>
                            <th scope="row"><span>도메인 유형</span></th>
                            <td colspan="2">
                                <div class="select-box">
                                    <select id="dataDomainTypeList" name="domainType" style="width:100px">
                                        <c:forEach items="${taskDomainTypeList}" var="item" varStatus="status">
                                            <option value="${item.dtlCd}">${item.dtlCdName}</option>
                                        </c:forEach>
                                    </select>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <th scope="row"><span>DataSet 설명</span></th>
                            <td colspan="4">
                                <textarea id="editorContents" rows="10" cols="80"></textarea>
                            </td>
                        </tr>
                        <tr>
                            <th scope="row"><span>데이터 명</span></th>
                            <td colspan="3"><input type="text" class="input" id="dataName" name="dataName"></td>
                            <th scope="row" rowspan="3" class="th-last">
                                <div class="td-box"><button type="button" class="btn btn-m-defalt" id="addBtn" onclick="addData()">추가</button></div>
                            </th>
                        </tr>
                        <tr>
                            <th scope="row"><span>데이터 경로</span></th>
                            <td colspan="3">
                                <div class="td-box">
                                    <input type="text" class="input" id="" name="">
                                    <button type="button" class="btn-folder"><i class='bx bxs-folder-open'></i></button>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <th scope="row"><span>데이터 파일</span></th>
                            <td>
                                <div class="input-file-box">
                                    <label for="uploadFile" class="btn btn-m-point">파일선택</label>
                                    <input class="input upload_name"  id="uploadFileName" value="" readonly>
                                    <input type="file" id="uploadFile" name="uploadFile" class="a11y-hidden" />
                                    <input type="hidden" id="addDataList" name="dataList">
                                </div>
                            </td>
                            <th scope="row"><span>데이터 유형</span></th>
                            <td>
                                <div class="select-box">
                                    <select id="dataType" name="dataType">
                                        <option value="single">Single</option>
                                        <option value="multi">Multi</option>
                                    </select>
                                </div>
                            </td>
                        </tr>
                        </form:form>
                        </tbody>

                    </table>
                </div>
            </div>

            <div class="create-box">
                <div class="create-header">
                    <h3 class="title">Data 추가 목록</h3>
                </div>
                <div>
                    <div class="table-type">
                        <table>
                            <colgroup>
                                <col width="6%">
                                <col width="18%">
                                <col width="10%">
                                <col width="10%">
                                <col width="auto">
                                <col width="10%">
                            </colgroup>
                            <thead>
                            <tr>
                                <th>No.</th>
                                <th>Name</th>
                                <th>데이터 유형</th>
                                <th>데이터 사이즈</th>
                                <th>스토리지 endpoint</th>
                                <th></th>
                            </tr>
                            </thead>
                            <tbody id="dataList" >
                            </tbody>
                            <form id="addDataForm"></form>
                        </table>
                    </div>
                </div>
            </div>

            <div class="btn-area">
                <button type="button" class="btn-m-base" onclick="goToProject()">취소</button>
                <button type="button" class="btn-m-defalt" onclick="registerDataset()">저장</button>
            </div>
        </div>
javascript
let paramArr = [];
// 업로드를 한 후 추가 할때 버튼을 누를 때 발생하는 function 입니다.
function addData() {

        if (!$('#dataName').val()) {
            alert('데이터명은 필수값 입니다.');
            $('#dataName').focus();
            return;
        }
        if (!$('#uploadFile').val()) {
            alert('데이터 파일이 없습니다.');
            return;
        }
        
        let fileTag = document.getElementById('uploadFile');
        // input type이 file이며 id가 uploadFile 인 태그를 가져옵니다.
        
        let file = fileTag.files[0];
        // 가져온 태그의 files를 통해 파일 정보를 가져옵니다.
         
        let cloneFile = fileTag.cloneNode();
        // 업로드를 하여 가져온 파일 데이터들을 임시로 cloneNode()를 통해서 복제합니다.
        fileTag.value = '';
        cloneFile.id = '';
        // 가져온 clone파일의 Id로는 가져오지 않을것이고 겹칠 수도 있으니 공백으로 만들어줍니다.
        cloneFile.name = `uploadFiles`;
        // 여기가 중요합니다. name을 spring 진영에서 
        // @RequestParam("uploadFiles") List<MultipartFile> uploadFiles 와 바인딩 해줍니다.
        
        cloneFile.classList.remove('ally-hidden');
        // 단순 class 제거 입니다.
        
        insertForm.appendChild(cloneFile);
		// form의 id 가 insertForm 인 곳에 appnedChild()를 통해 복제한 데이터를 넣어줍니다.
        
        let dataName = $('#dataName').val();
        let fileName = file.name;
        let dataType = $('#dataType').val();
        // 위처럼 입력한 데이터들을 변수로 만들어 준 후 아래의 객체 param에 넣어줍니다.
        let param = {
            dataName : dataName,
            fileName : fileName,
            dataType : dataType,
            fileSize : Number(file.size)
        };

        paramArr.push(param);
        // 맨 위에서 만든 paramArr = []; 에 밀어넣어줍니다.

        let html = '';
        // 아래는 위의 param 에 넣었던 객체들로 업로드 했던 데이터들을 보여줄 공간입니다.
        paramArr.forEach(function(v,i) {
            html += `
                    <tr id="\${file.lastModified}">
                        <td data-index='\${i+1}'>\${i+1}</td>
                        <td>\${v.dataName}</td>
                        <td>\${v.dataType}</td>
                        <td>\${formatBytes(v.fileSize)}</td>
                        <td>\${v.fileName}</td>
                        <td>
                            <div class="td-box">
                                <button data-index='\${file.lastModified}' type="button" class="btn btn-m-base" onclick="removeFile()">삭제</button>
                            </div>
                        </td>
                    </tr>
        `;
        })
        // <div id='dataList'>
        // </div>
        // 라는 태그를 만들어놓고 아래에 그대로 밀어넣어줍니다.
        $('#dataList').html(html);

    }

여기서 끝난 게 아니라 밀어넣어준 데이터들을 보내주어야겠죠.

 

// 최종으로 등록하는 function
function registerDataset() {
        let insertForm = $('#insertForm')[0];
        let formData = new FormData(insertForm);
        let datasetDesc = CKEDITOR.instances.editorContents.getData();
        // CK Editor 로 입력한 글을 datasetDesc 라는 변수로 넣어줍니다.
        
        formData.append('datasetDesc',datasetDesc);
        // 단건 데이터 넣어주기
        let dataList = [];
        // 빈 dataList라는 배열을 만들어 준 뒤, 위에서 만든 paramArr 에 들어가있는 데이터들을 아래처럼 넣어줍니다.
        
        for (let i = 0; i < paramArr.length; i++) {
            formData.append("addDataList["+i+"].dataName", paramArr[i].dataName);
            formData.append("addDataList["+i+"].storageEndPoint", paramArr[i].fileName);
            formData.append("addDataList["+i+"].dataPositionType", paramArr[i].dataType);
            formData.append("addDataList["+i+"].dataSize", paramArr[i].fileSize);
        }

			confirm('저장하시겠습니까?',function() {

            $.ajax({
                url : '/api/v1/dataSet/register',
                type: "post",
                contentType : false,
                //multipart로 보낼 때는 항상 contentType 은 false로 설정해주는것을 잊지 말아주세요
              
                enctype: "multipart/form-data",
                processData: false,
                data : formData,
                cache : false,
                success : function (res) {
                    if (res.result) {
                        goToProject();
                    }
                },
                
            })
        });

    }

 

spring
// 클래스
public Class UploadDto {

	private String boardtitle;
    private String boardDesc;
    private String datasetDesc;
    private List<Data> addDataList; 
    //이 변수가 위 스크립트의 addData()에서 append()해주었던 변수와 바인딩 됩니다.
    
}
// addDataList 변수의 타입
public Class Data {
	private String fileName;
    private String fileSize;
    private String storageEndPoint;
    private String dataPositionType;
}


@PostMapping("/api/v1/dataset/register")
@ResponseBody
public ResponseEntity<Object> register(@RequestParam("uploadFiles") List<MultipartFile> uploadFiles,
									   UploadDto uploadDto) {
     // 이하 생략
     }



꿀팁! 숫자를 바이트 단위로 변환하는 방법

function formatBytes(bytes, decimals = 2) {
        if (bytes == 0) return '0 Bytes';
        const k = 1024;
        const dm = decimals < 0 ? 0 : decimals;
        const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    };
    
    // 사용법
    int dataSize = 190323;
    
    formatBytes(dataSize); //<- 요렇게!





반응형
LIST