1. 프로젝트 생성
1) 페이지 내에서 pj_num을 가지고 mapping 테이블에 팀원을 초대하는 부분에서 last_insert_id를 사용해서 컨트롤러에서 insert가 팀원 수만큼 반복해서 돌아가게 작성하여 mapping 테이블에 값을 넣었음(아래 방법 사용)
- project.mapper
<insert id="regist" parameterType="ProjectVO">
INSERT INTO USER_PROJECT (pj_name, pj_startdate, pj_enddate,
pj_description, pj_writer)
VALUES (#{pjName}, #{pjStartdate}, #{pjEnddate}, #{pjDescription},
#{pjWriter});
<selectKey keyProperty="pjNum" resultType="int"
order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
<insert id="registMember" parameterType="ProjectMemberVO">
INSERT INTO MAPPING (pj_num, user_id, is_observer)
VALUES (#{pjNum}, #{userId}, #{isObserver});
</insert>
- ProjectAjaxController
//등록 요청
@PostMapping("/reg-project")
@ResponseBody
public Map<String, Object> regist(@RequestBody Map<String, Object> map, RedirectAttributes ra) {
String msg = "";
int result1 = 0;
int result2 = 0;
Map<String, Object> resultMap = new HashMap<String, Object>();
//데이터 확인
// System.out.println("등록 start =======");
// System.out.println((String)map.get("pj_name")); //Object여서 형 변환
// System.out.println((String)map.get("pj_startdate"));
// System.out.println((String)map.get("pj_enddate"));
// System.out.println((String)map.get("pj_description"));
ProjectVO vo = new ProjectVO();
vo.setPjName((String)map.get("pj_name"));
vo.setPjWriter((String)map.get("pj_writer"));
vo.setPjStartdate((String)map.get("pj_startdate"));
vo.setPjEnddate((String)map.get("pj_enddate"));
vo.setPjDescription((String)map.get("pj_description"));
Gson gson = new Gson();
List<Map<String, Object>> user_list = gson.fromJson((String)map.get("user_boolean"), List.class);
System.out.println("user_list size : " + user_list.size());
//프로젝트 생성
result1 = projectService.regist(vo);
System.out.println("result1 : " + result1);
int pj_num = vo.getPjNum();
System.out.println("pj_num : " + pj_num);
//프로젝트 멤버들 생성
for (int i = 0; i < user_list.size(); i++) {
ProjectMemberVO pmvo = new ProjectMemberVO();
pmvo.setPjNum(pj_num);
pmvo.setUserId(user_list.get(i).get("team_id").toString());
pmvo.setIsObserver(user_list.get(i).get("is_observer").toString());
result2 = projectService.registMember(pmvo);
}
if(!(result1 == 0 && result2 ==0)) {
msg = "등록에 성공 하였습니다.";
}else {
msg = "등록에 실패 하였습니다.";
}
resultMap.put("msg", msg);
return resultMap;
};
2) form으로 안 보내는 이유: submit 방지 - 버튼 타입은 기본이 submit이기 때문에 type=button으로 지정해줄 것
3) pj_num에서 받아오는 부분이 계속 1로 처리 되었었는데 컨트롤러에서 받아오는 부분이 오류가 있었음
4) 처음 구상을 짤 때 팀장에 대한 정보를 넣어주지 않아서 user_project 테이블에 pj_writer 컬럼과 mapping에 팀장 이름을 처리하는 부분에서 생각했던 점
- 프로젝트 add.html에 세션값 넣어 주기 - 세션은 컨트롤러에서도 받아줄 수 있지만 html에서 타임리프 써서 user_id 받아왔음.
- .js에서 session.getattribute와 sessionStorage 방법도 사용해봤는데 되지 않아서 위와 같은 방법으로 해결함.
th:id="${session.user_id}"
2. 멘토링에 따라 변수명 모두 수정하는 과정에서 난 오류
1) mybatis 카멜 자동 변환을 사용하기 위해 아래의 코드를 사용 했음 - java config에 추가한 파일
@Configuration
@MapperScan(basePackages = {"com.jeongkyun.mapper"}
,sqlSessionFactoryRef="sqlSessionFactory"
,sqlSessionTemplateRef="sqlSessionTemplate")
public class MybatisConfig {
@Bean(name="sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
// mapper.xml 의 resultType 패키지 주소 생략
sqlSessionFactoryBean.setTypeAliasesPackage("com.jeongkyun.vo");
// mybatis 설정 파일 세팅
sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
// mapper.xml 위치 패키지 주소
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mappers/*Mapper.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean(name="sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
@MapperScan(basePackages = {"com.jeongkyun.mapper"}
,sqlSessionFactoryRef="sqlSessionFactory"
,sqlSessionTemplateRef="sqlSessionTemplate")
이 부분에서 basePackages 설정이 잘못되어 자꾸 bean 주입이 안된다는 오류가 발생했다.
@MapperScan 부분을 지워주면 정상 작동이 된다.
3. 유저 게시판
html form 태그에 enctype="multipart/form-data" 달아주고 ajax로 form-data로 data에 담을 수 있다.
여기서, html에서는 name으로 지정된 값을 컨트롤러에서는 MultipartHttpServletRequest multipart 사용해서 getParmeter로 값을 받아올 수 있음(하지만 string 값만 받아오기 때문에 형변환 해줘야 함)
public int regist(MultipartHttpServletRequest multipart, HttpSession session) throws IllegalStateException, IOException {
String msg = "";
int result = 0;
int result2 = 0;
String categorySelect = multipart.getParameter("categorySelect");
String processSelect = multipart.getParameter("processSelect");
String boardTitle = multipart.getParameter("boardTitle");
String writer = (String)session.getAttribute("user_id");
String startDate = multipart.getParameter("startDate");
String endDate = multipart.getParameter("endDate");
String description = multipart.getParameter("description");
//multipart는 string 값으로 넘어오기 때문에 int로 형변환
String pjNum = multipart.getParameter("pjNum");
int pjNum2 = Integer.parseInt(pjNum);
$.ajax({
type: "POST",
url: "../reg-board",
data: formData,
processData: false,
contentType: false,
/*contentType: "application/json",*/
cache: false,
success: function(data) {
alert("등록이 완료되었습니다.")
location.href = "/userboards/board-list?pj_num=" + urlParams.get('pj_num');
},
error: function(e) {
console.log("ERROR: ", e);
//버튼 사용 불가
$("#boardSuccess").prop("disabled", false);
alert("fail");
}
})
@GetMapping("/board-regist")
public String boardRegist(Model model,
@RequestParam("pj_num") int pjNum) {
model.addAttribute("pjNum", pjNum);
return "/userboards/board-regist";
}
그리고 등록으로 넘어갈때 pjNum 가지고 가야하기 때문에 model에 실어서 regist 페이지의 타임리프 쓴 태그에서 pjNum을 받아올 수 있었음
<input type="hidden" id="pjNum" th:name="pjNum" th:value="${pjNum}">
쿼리에서는 select last_insert_id()로 처리했더니 파일 하나에만 boardNum 값이 부여되고 나머지는 boardfileNum 값을 따라가게 되는 문제가 생김 이런식으로 max 값으로 받아와서 처리 했더니 파일 모두 같은 boardNum 값으로 넘어감
<!-- 글 등록 -->
<insert id = "getContent" parameterType="map">
insert into user_board (
pj_num, board_title, board_writer, board_process,
board_startdate, board_enddate, board_content,
board_category)
values( #{vo.pjNum}, #{vo.boardTitle}, #{vo.boardWriter},
#{vo.boardProcess}, #{vo.boardStartdate}, #{vo.boardEnddate},
#{vo.boardContent}, #{vo.boardCategory})
</insert>
<!-- 파일 등록 -->
<insert id="fileUploadList" parameterType="fileVO">
<selectKey keyProperty="boardNum" resultType="int" order="BEFORE">
SELECT MAX(board_num) from user_board
</selectKey>
insert into user_boardfile (
board_num, boardfile_path, boardfile_name)
values (#{boardNum}, #{boardfilePath}, #{boardfileName})
</insert>
4. 파일 업로드에서 multipart-form으로 넘기는 부분에서 파일을 추가하지 않아도 빈 배열이 그대로 넘어가서 content 화면에서 계속 없는 파일이 생성되는 오류가 생겼음
MultipartFile은 HTTP 요청에서 전송된 파일을 나타내는 인터페이스입니다. 이 인터페이스를 사용하여 클라이언트가 업로드한 파일을 서버로 전송하고 처리할 수 있습니다. MultipartFile을 사용하여 파일을 업로드할 때, 선택된 파일이 없는 경우 MultipartFile 객체는 비어있는 배열로 초기화됩니다. 이는 선택된 파일이 없는 경우에도 코드에서 MultipartFile 객체를 처리하기 위해서 일관성을 유지하기 위한 방법입니다. 따라서, 코드에서 MultipartFile 객체를 처리할 때, 비어있는 배열인지 여부를 확인하고 선택된 파일이 있는지 여부를 결정할 수 있습니다. 이를 통해 선택된 파일이 없는 경우에도 예외를 처리하거나 적절한 대체 동작을 수행할 수 있습니다. |
찾아보니 이런 사항들이 있었다.
modify.js 상단에 빈 파일 배열을 생성한 후
//빈 파일 배열을 생성한다 - 가공 처리(빈 배열 생성 안했을 때에는 없는 파일이 추가 되는 문제 발생)
var inputFileList = new Array();
change 될 때마다 fileList를 배열로 바꿔준다.
Array.prototype.slice.call(arguments)란 이 코드가 존재하는 함수의 매개변수로 넘어온 값들을 array로 변환하겠다는 뜻 call()은 상위 context를 변경하는 메서드이고 arguments는 함수의 매개변수에 접근할 수 있는 속성이다. |
//파일 이름 목록에 추가
$(document).ready(function() {
$('#formFile').on('change', function(e) {
let files = $(this).prop('files');
alert(files);
let fileNames = "";
if (files && files.length > 0) { // 파일이 첨부되어 있는 경우
for (var i = 0; i < files.length; i++) {
fileNames += "<li>" + files[i].name + "</li>";
}
//change 됐을 때, 파일이 첨부되어 있을 때만 e.target.files를 받아와서 빈 배열에 넣어준다.
inputFileList = []; // 기존 정보 제거(초기화 안해주면 계속 쌓임)
var fileList = e.target.files;
var filesArr = Array.prototype.slice.call(fileList);
for(f of filesArr){
inputFileList.push(f);
}
그리고 배열에 push해준 값을 formData에 다시 넣어주는 방향으로 처리 했다.
$("#boardSuccess").click(function(event) {
//기본으로 정의된 이벤트를 작동하지 못하게 함, 즉 submit을 막는다.
event.preventDefault();
/////////////유효성 검사 처리 하는 부분////////////////
formData = new FormData($('#boardRegistForm')[0]);
//push해준 값을 배열로 돌려서 넣는다. - 컨트롤러에서 처리
//배열에서 이미지들을 꺼내 폼 객체에 담는다.
for (let i = 0; i < inputFileList.length; i++) {
formData.append("fileUploadImg", inputFileList[i]);
}
5. 수정하는 화면에서 기존에 있었던 파일을 처리하는 문제
내가 구현한 파일 업로드의 방법은 파일 추가를 할 때마다 새롭게 추가된 부분을 리스트 형식으로 추가하는 것이 아닌 다시 추가를 하면 추가한 새 파일들로 변경되는 방법이다.
데이터 히스토리 보존을 위해 board_file에 삭제 여부 컬럼을 만들었고 기존에 올렸던 파일들은 삭제 여부 y로 처리해서 update 하고, 파일을 새로 insert 해주는 로직을 구현하려고 했다.
그래서 수정할 때마다 onchange 이벤트를 걸었다.
여기서 발생한 문제
= change가 되지 않아도 기존에 있었던 파일들은 남아 있어야 하는데 사라짐(파일 추가 안해도 삭제 여부가 y로 변경됨)
그래서 modify.html에 input hidden 값으로 changeChk를 실어주고 파일이 첨부되었든, 안되었든 onchange 될 때마다 changeChk의 value를 y로 세팅해주고
<input type="hidden" id="changeChk" th:name="changeChk" th:value="N">
//파일 이름 목록에 추가
$('#formFile').on('change', function(e) {
let files = $(this).prop('files');
let fileNames = "";
if (files && files.length > 0) { // 파일이 첨부되어 있는 경우
for (var i = 0; i < files.length; i++) {
fileNames += "<li>" + files[i].name + "</li>";
}
//change 됐을 때, 파일이 첨부되어 있을 때만 e.target.files를 받아와서 빈 배열에 넣어준다
var fileList = e.target.files;
var filesArr = Array.prototype.slice.call(fileList);
for(f of filesArr){
inputFileList.push(f);
}
$('#fileNames').html("<ul style='margin-top: 10px;'>" + fileNames + "</ul>");
} else { // 파일이 첨부되어 있지 않은 경우
inputFileList = []; // 기존 정보 제거
$('#fileNames').empty();
$('#fileNames').html("");
$('#fileUpload').val(""); // value 값을 ""으로 설정
// FormData 객체에서 파일 삭제
let formData = new FormData($('#form')[0]);
if (files.length === 0) {
formData.delete('fileUpload');
alert("첨부된 파일이 없습니다.");
}
}
$('#changeChk').val("Y");
});
changeChk가 y일 때만 업데이트 시켜주고, 인서트 되는 방향으로 진행해서 해결 했다.
if(changeChk.equals("Y")) {
FileVO fvo1 = new FileVO();
fvo1.setBoardNum(boardNum);
userBoardService.updateFileList(fvo1);
for (MultipartFile file : mFiles) {
//파일명
String origin = file.getOriginalFilename();
//브라우저별로 경로가 포함돼서 올라오는 경우가 있어서 간단한 처리
String fileName = origin.substring(origin.lastIndexOf("\\") + 1);
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리 (어떤값이 들어가더라도 중복이 안되도록)
String boardfileUuid = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
//최종 저장 경로
String fileUrl= filepath + "/" + boardfileUuid;
//aws에 업로드
ObjectMetadata metadata= new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
amazonS3Client.putObject(bucket,fileUrl,file.getInputStream(),metadata);
//db에 값 넣어주기
FileVO fvo2 = new FileVO();
fvo2.setBoardNum(boardNum);
fvo2.setBoardfilePath(uploadpath + fileUrl);
fvo2.setBoardfileName(file.getOriginalFilename());
fvo2.setBoardNum(boardNum);
System.out.println("fileUrl : " + fileUrl);
System.out.println("fname : " + file.getOriginalFilename());
fvoList.add(fvo2);
}
userBoardService.fileUploadList(fvoList);
}
2) projectAdd에서 진행한 것 처럼 selectKey를 써서 fileUpload insert 진행할 때 위에 getContent에서의 board_num을 받아와서 insert 해주는 부분에서 오류가 생김(처음에는 파일 등록 주석 처리 해둔 곳에 selectKey가 있었다)
- 문제점은 글 수정 화면에서 이전 글에 계속 insert 되는 오류가 있었음
- 나는 글 등록, 글 수정에서 file insert 하는 부분을 공통으로 사용하고 있었는데 글을 등록하는 곳에서는 문제가 없었는데 글을 수정하는 곳에서 file insert 쪽 select max(board_num)에서 계속 꼬이는 바람에 제대로 된 boardNum값이 들어오지 않는 것 같았다.
- 그래서 VO에 boardTypeChk를 추가해서 파일 등록이면 R로 set해주고, 파일 수정이면 M으로 set해주고 저 selectKey에 if 조건을 걸고 싶었는데 select key는 if 안에 들어갈 수가 없었다.
- 그리고 처음에 VO는 map보다는 제한적이라고 생각해서 map을 사용하고 싶었다. 그래서 처음에 getContent에서도 파라미터 타입도 map으로 받는 걸로 했는데 map으로 받으니까 last insert Id가 안 불러와져서 parameterType도 UserBoardVO로 변경해주었다.
- 결국 모두 변경 해주고 파일 수정 완료 했다.
<insert id="getContent" parameterType="UserBoardVO">
insert into user_board (
pj_num, board_title, board_writer, board_process,
board_startdate, board_enddate, board_content,
board_category)
values( #{pjNum}, #{boardTitle}, #{boardWriter},
#{boardProcess}, #{boardStartdate}, #{boardEnddate},
#{boardContent}, #{boardCategory})
<selectKey keyProperty="boardNum" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
<!-- 파일 등록 -->
<insert id="fileUploadList" parameterType="fileVO">
<!-- <selectKey keyProperty="boardNum" resultType="int" order="BEFORE">
select max(board_num) from user_board
</selectKey> -->
insert into user_boardfile (
board_num, boardfile_path, boardfile_name)
values (#{boardNum}, #{boardfilePath}, #{boardfileName})
</insert>
'프로젝트 > 최종 프로젝트' 카테고리의 다른 글
페이지 별 필요 기능 초안 (0) | 2023.03.08 |
---|---|
개발 일지 기록 (0) | 2023.03.02 |
최종 프로젝트 시작 (0) | 2023.03.02 |