java.lang.IllegalStateException: Ambiguous handler methods mapped for '/api/member/members/approvaly/1': {public com.nordic.dto.common.ResponseDto com.nordic.api.MemberApiController.PointArrange(java.lang.String,int) throws java.lang.Exception, public com.nordic.dto.common.ResponseDto com.nordic.api.MemberApiController.MemberState(java.lang.String,int) throws java.lang.Exception} at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:432) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:383) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:125) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:67) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:498) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1265) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1047) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.23.jar:5.3.23] at javax.servlet.http.HttpServlet.service(HttpServlet.java:670) ~[tomcat-embed-core-9.0.68.jar:4.0.FR] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23] at javax.servlet.http.HttpServlet.service(HttpServlet.java:779) ~[tomcat-embed-core-9.0.68.jar:4.0.FR] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.68.jar:9.0.68] at java.lang.Thread.run(Unknown Source) [na:1.8.0_333]
매핑이 모호하다는 메시지를 보고 생각을 해봤는데
포인트 정렬의 매핑값은 "/members/{pointArrange}/{pageNum}" 이고
활동 상태별 정렬의 매핑값은 "/members/{memberState}/{pageNum}" 이라서
select member_code, member_name, password
from member
where member_code = '10001';
단방향 암호화 SHA256 - 복호화 x
여러가지 종류가 있다.
비밀번호 암호화에 사용된다.
UPDATE member SET
PASSWORD = SHA2('1234', 256)
WHERE member_code = '10000';
양방향 암호화 Base64
보안을 위해서 사용하는 암호화 방식 x
binary 파일을 텍스트로 나타날 때 사용.
2진 데이터를 아스키 코드화 하는 해시 암호화.
SELECT
member_code, member_name,
TO_BASE64('password'), FROM_BASE64(TO_BASE64('password'))
FROM member
WHERE member_code = '10000';
양방향 암호화 AES-ENCRYPT
개인정보를 암호화 해서 전송할 때 사용. 비밀번호는 양방향이 아닌 단방향 방식을 사용한다.
hex(AES_ENCRYPT('암호화 할 문자열', '암호화 키'))
--문자열에는 비밀번호를, 암호화 키에는 복호화 시 사용할 키 입력
UPDATE member SET
PASSWORD = HEX(AES_ENCRYPT('1234', '1'))
WHERE member_code = '10000';
양방향 복호화 AES_DECRYPT
AES_DECRYPT(UNHEX(복호화 할 컬럼), '암호화 키')
--복호화 할 컬럼 명을 입력하고, 암호화 시 지정했던 키를 입력한다
SELECT
member_code, member_name,
AES_DECRYPT(UNHEX(password), '1')
FROM member
WHERE member_code = '10000';
text 폼에 키워드를 입력해서 백으로 넘기면 DB에서 데이터를 찾아 프론트로 넘겨 화면에 출력을 해야한다.
그래서 SearchDto를 따로 만들고, 컨트롤러부터 매퍼까지 작성을 마쳤다.
그런데 자꾸 문제가 일어났다.
1. 프론트에서 검색 분류와 키워드를 입력해 백으로 넘기면 null 값으로 도착함
이것은 두 가지 방법으로 해결했다.
1) 주소값에 search와 keyword를 담아서 오기
/******************************** 검색으로 회원 조회 ********************************/
@ApiOperation(value="검색으로 회원 조회")
@GetMapping("/members/{search}/{keyword}/{pageNum}")
public ResponseDto SearchMember(@PathVariable int pageNum,
@PathVariable SearchDto searchDto) throws Exception {
그런데도 실행이 되지 않아 로그를 찍어보니 Dto에 정보가 담겨있지 않았다. (죄다 null)
그래서 파라미터를 searchDto에서 String Search, String Keyword로 바꾸고
Dto에 직접 주입해주었다.
/******************************** 검색으로 회원 조회 ********************************/
@ApiOperation(value="검색으로 회원 조회")
@GetMapping("/members/{search}/{keyword}/{pageNum}")
public ResponseDto SearchMember(@PathVariable int pageNum,
@PathVariable String search,
@PathVariable String keyword) throws Exception {
SearchDto searchDto = new SearchDto();
searchDto.setSearch(search);
searchDto.setKeyword(keyword);
log.info(searchDto.toString());
이렇게 하니 로그에 찍힌 DTO 값이 null 이 아닌 내가 입력한 값으로 잘 나왔다.
2. 동적 SQL문 문제
컨트롤러~매퍼까지 잘못 입력한게 없는 것 같은데 프론트로 도착하는 데이터를 보면 무언가 이상했다.
'민' 이 들어간 회원 이름을 검색했는데 도착한 데이터들을 보면 '민'이 들어가지 않은 회원의 이름도 보였다.
아무리 봐도 잘못한게 없는 것 같고,
이전 프로젝트 때에도 검색 기능을 문제없이 구현했던 터라 도대체 무엇이 문제일까 골똘히 생각했다.
생각을 하고, 검색을 해도 답을 찾지 못해 옆 자리 분께 여쭤보니 똑같은 문제로 골치 아팠다며 단번에 해결책을 주셨다....
그건 바로.. 사용하는 데이터베이스의 종류였다...
옆자리 분과 1차 프로젝트도 함께 하고 이번 파이널 프로젝트까지 함께 하게 됐는데,
그 분도 나와 같은 실수를 했던 것이었다....
1차 때 사용한 데이터베이스는 오라클이었고, 이번에는 MySQL을 사용하고 있기 때문에... ㅜㅜㅜ
-- 오라클
<select id="doSearch" parameterType="SearchDto" resultType="MemberDto">
select * from member
<where>
<if test="keyword != null and search != null">
${search} like '%'||#{keyword}||'%'
</if>
</where>
</select>
-- MySQL
<select id="doSearch" parameterType="SearchDto" resultType="MemberDto">
select * from member
<where>
<if test="keyword != null and search != null">
${search} like CONCAT('%',#{keyword},'%')
</if>
</where>
</select>
/********************* 회원정보수정 실행 *********************/
function doModify() {
var member_code = document.getElementById("member_code").value;
var member_name = document.getElementById("member_name").value;
var mobile_no = document.getElementById("mobile_no").value;
var address = document.getElementById("address").value;
var age = document.getElementById("age").value;
var sex = document.getElementById("sex").value;
var password = document.getElementById("memberCode").value;
console.log(member_code);
console.log(member_name);
var url = "http://localhost/api/member/modify/";
var data1 = $("#frmData").serialize();
alert(data1);
// JSON.stringify({
// "member_code" : member_code,
// "member_name" : member_name,
// "mobile_no" : mobile_no,
// "address" : address,
// "age" : 10,
// "sex" : sex,
// "password" : password
// })
fetch(url+member_code, {
method: "PUT",
// mode:'cors',
headers: {
// 'Content-Type' : 'application/json',
},
body: data1
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
.catch(error => {
console.log("에러입니다");
alert(error);
})
}
fetch 말고 ajax로 해결했다
백은 @PutMapping만@PostMapping으로 고쳤다.
기존에 백에서 사용하던 @PutMapping에 맞춰서 ajax함수의 타입을 put으로 설정했는데 이건 오류가 났다.
ajax의 타입은 Post, 백의 어노테이션은 @PostMapping.
fetch로 하루를 붙들고 있었는데
ajax로 고치자마자 바로 해결되어서 얼떨떨하다.. 이럴수가
/********************* 회원정보수정 실행 *********************/
function doModify() {
var member_code = document.getElementById("member_code").value;
var member_name = document.getElementById("member_name").value;
var mobile_no = document.getElementById("mobile_no").value;
var address = document.getElementById("address").value;
var age = document.getElementById("age").value;
var sex = document.getElementById("sex").value;
var password = document.getElementById("memberCode").value;
console.log(member_code);
console.log(member_name);
var url1 = "http://localhost/api/member/modify/";
var data1 = $("#frmData").serialize();
$(function() {
$.ajax({
type : "post",
url : url1 + member_code,
data : data1,
success : function(result) {
alert(result);
alert("회원정보수정 완료!");
}
}); // ajax end
}); // jQuery function end
}
var tableColumnL = (response.data.length); //1438 (행 길이)
console.log(Object.values(response.data[0])); // 회원의 상세정보
for (var j=0; j<tableColumnL; j++){
var memValues = Object.values(response.data[j]);
// memValues.length //22 (열 길이)
$("#members").append(`<tr>`); // id가 members인 테이블에 <tr> 태그 추가
var data1 = (`<td>${j+1}</td>`); // id가 members인 테이블에 인덱스 번호 열 추가
$("#members").append(data1);
var i=0;
while (i < memValues.length) {
var data2 = `
<td>${memValues[i]}</td>
`
$("#members").append(data2);
i++
} // while end (td 추가 끝) // json 데이터의 회원 정보를 뽑아와
// id가 members인 테이블에 열 추가
$("#members").append("</tr>"); // id가 members인 테이블에 </tr> 추가
} //for end
대체 왜 행 바꾸기가 안되는 거냐고~!!!!
정답은 tbody 태그였다 ^^
F12를 눌러 개발자 도구의 요소 검사를 통해 보니
<tr></tr>만 1438개가 생성되어 있었다....
그리고 가장 마지막에 회원 정보 데이터가 행바꿈이 하나도 안된채로 가로로 길게 나열되어 있었음...
1438명의 회원데이터가 모두..... 테이블 컬럼이 22개나 되는건데...
진짜 어이없었음 내가 짠 for문은
1) <tr> 추가
2) 회원정보 데이터를 while문으로 뽑아내서 추가
3) </tr> 추가
이렇게 순서대로 요소가 table에 추가되도록 만든 건데...
그래서 이걸 어떻게 해결했냐면.. 정답은 tbody 였다.
내가 테이블을 만들때 thead와 tbody를 따로 지정하지 않았는데 그게 문제였다.
<table id="members"> 가 아닌
<tbody id="members"> 로 지정하여
그 영역에 for문으로 뽑아낸 회원 데이터를 추가했어야 하는 거였다.
그랬더니 해결!
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>전체 회원 정보</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='main.css'>
<script src='main.js'></script>
</head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
function findAll() {
fetch("http://localhost:80/member/members", {
method: "GET",
mode: 'cors',
headers: {
'Content-Type' : 'application/json',
}
}) // fetch end
.then(response => response.json())
.then(response => {
var tableColumnL = (response.data.length); //1438 (행 길이)
console.log(Object.values(response.data[0])); // 회원의 상세정보
for (var j=0; j<tableColumnL; j++){
var memValues = Object.values(response.data[j]);
// memValues.length //22 (열 길이)
$("#members").append(`<tr>`); // 테이블의 tbody 영역에 <tr> 태그 추가
var data1 = (`<td>${j+1}</td>`); // 테이블의 tbody 영역에 인덱스 번호 열 추가
$("#members").append(data1);
var i=0;
while (i < memValues.length) {
var data2 = `
<td>${memValues[i]}</td>
`
$("#members").append(data2);
i++
} // while end (td 추가 끝) // json 데이터의 회원 정보를 뽑아와
// 테이블의 tbody 영역에 열 추가
$("#members").append("</tr>"); // 테이블의 tbody 영역에 </tr> 추가
} //for end
})
.catch(error => {
console.log(error);
alert(error);
});
} // findOne end
</script>
<body>
<button onClick="findAll()">회원정보 불러오기</button>
<div id="adminCheck">
<input type="checkbox" id="aC" value=1>
<label for="aC"> 관리자 여부 </label>
</div>
<br><br><br>
<table border="1">
<tr>
<thead>
<th></th>
<th>회원코드</th>
<th>이름</th>
<th>핸드폰</th>
<th>주소</th>
<th>나이</th>
<th>성별</th>
<th>동의여부</th>
<th>비밀번호</th>
<th>가입승인여부</th>
<th>가입승인일시</th>
<th>활동중지여부</th>
<th>활동중지일시</th>
<th>관리자여부</th>
<th>관리자등록일</th>
<th>누적포인트</th>
<th>가용포인트</th>
<th>사용포인트</th>
<th>비고</th>
<th>등록자</th>
<th>등록일시</th>
<th>변경자</th>
<th>변경일시</th>
</tr>
</thead>
<tbody id="members">
</tbody>
</table>
</body>
</html>