404에러
- 404를 처리하는 방법 2가지
-> ✨✨filter, web.xml 수정 
자바


HomeController
@Controller
public class HomeController {
	@RequestMapping("/")
	public String home() {
		return "home";
	}
}
MemberContorller
@Controller
@RequestMapping("member")
public class MemberController {
	
	@Autowired
	private MemberService service;
	
	@GetMapping("login")
	public String login() {
		return "member/login";
	}
	
	
	@PostMapping("login")
	public String login(MemberDto dto) {
		return "member/login";
	}
	
	
	@GetMapping("join")
	public String join() {
		return "member/join";
	}
	
	
	@PostMapping("join")
	public String join(MemberDto dto) throws Exception {
		System.out.println(dto);
		
		return "member/join";
	}
}

MemberDto
@Data
public class MemberDto {
	private long userNo;
	private String userId;
	private String userPwd;
	private String userNick;
	private int userAge;
	private String userGender;
	private String userProfile;
	private MultipartFile f;
	private String changeName;
}
MemberService 인페
public interface MemberService {
	int join(MemberDto dto) throws Exception;
}
MemberServiceImpl
@Service
@Slf4j
@Transactional
public class MemberServiceImpl implements MemberService {
	@Autowired
	private MemberDao dao;
	
	@Override
	public int join(MemberDto dto) throws Exception{
		
		
		
		int no = dao.getMemberSeq();
		
		dto.setUserNo(no);
		log.info(dto.toString());
		int result = dao.insertMember(dto);
		
		
		
		
		
		MultipartFile f = dto.getF();
		
		if(!f.isEmpty()) {
			
			
			
			String changeName = System.currentTimeMillis()+"_"+f.getOriginalFilename();
			dto.setChangeName(changeName);
			
			System.out.println("===============");
			System.out.println(f.getOriginalFilename());
			System.out.println(f.getSize());
			System.out.println(f.getContentType());
			System.out.println("===============");
			
			
			File file = new File("D:/uploadForSpring/999prj/profile/"+ f.getOriginalFilename());
			f.transferTo(file);
			
			
			dao.uploadProfile(dto);
		}
		
		return result;
	}
}
MemberDao 인페
public interface MemberDao {
	
	int getMemberSeq() throws Exception;
	int insertMember(MemberDto dto) throws Exception;
	void uploadProfile(MemberDto dto) throws Exception;
}
MemberDaoImpl
@Repository
public class MemberDaoImpl implements MemberDao{
	@Autowired
	private SqlSession sqlSession;
	
	
	@Override
	public int getMemberSeq() throws Exception {
		return sqlSession.selectOne("member.getSeq");
	}
	@Override
	public int insertMember(MemberDto dto) throws Exception{
		return sqlSession.insert("member.insertMember", dto);
	}
	@Override
	public void uploadProfile(MemberDto dto) throws Exception {
		sqlSession.insert("member.insertProfile", dto);
	}
	
}
member-mapper.xml
<mapper namespace="member">
	<select id="getSeq" resultType="int">
		SELECT MEMBER_SEQ.NEXTVAL FROM DUAL
	</select>
	
	<insert id="insertMember" parameterType="memberDto">
		INSERT INTO MEMBER
		(
			 USER_NO 
		    ,USER_ID
		    ,USER_PWD 
		    ,USER_NICK 
		    ,USER_AGE   
		    ,USER_GENDER 
		    ,USER_PROFILE
		)
		VALUES
		(
			#{userNo}
			,#{userId}
			,#{userPwd}
			,#{userNick}
			,#{userAge}
			,#{userGender}
			,#{userProfile}
		)
		
	</insert>
 	<insert id="insertProfile" parameterType="memberDto">
 		INSERT INTO MEMBER_PROFILE
 		(
 		    FILE_NO
		    ,USER_NO
		    ,CHANGE_NAME
 		)
 		VALUES
 		(
 			 MEMBER_PROFILE_SEQ.NEXTVAL
 			 ,#{userNo}
 			 ,#{changeName}
 		)
 	
 	</insert>  
  
</mapper>
mybatis-config.xml
<configuration>
	<settings>
		<setting name="cacheEnabled" value="true"/>
		<setting name="autoMappingBehavior" value="FULL"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
	</settings>
	
	
	<typeAliases>
		<typeAlias type="com.kh.app999.member.entity.MemberDto" alias="memberDto"/>
	
	</typeAliases> 
	
</configuration>
디비
MEMBER
drop table member;
CREATE TABLE MEMBER(
    USER_NO NUMBER PRIMARY KEY
    ,USER_ID VARCHAR2(100)
    ,USER_PWD VARCHAR2(100)
    ,USER_NICK VARCHAR2(100)
    ,USER_AGE   NUMBER
    ,USER_GENDER    CHAR(1)
    ,USER_PROFILE   VARCHAR2(500)
);
DROP SEQUENCE MEMBER_SEQ;
CREATE SEQUENCE MEMBER_SEQ NOCACHE NOCYCLE;
COMMIT;
SELECT * FROM MEMBER;
SELECT MEMBER_SEQ.NEXTVAL FROM DUAL;
MEMBER_PROFILE
DROP TABLE MEMBER_PROFILE;
CREATE TABLE MEMBER_PROFILE(
    FILE_NO NUMBER PRIMARY KEY
    ,USER_NO NUMBER
    ,CHANGE_NAME VARCHAR2(512)
    ,CONSTRAINT MEMBER_PROFILE_FK FOREIGN KEY(USER_NO) REFERENCES MEMBER(USER_NO) ON DELETE CASCADE
);
DROP SEQUENCE MEMBER_PROFILE_SEQ;
CREATE SEQUENCE MEMBER_PROFILE_SEQ NOCACHE NOCYCLE;
SELECT * FROM MEMBER_PROFILE;


에러 처리
✨ControllerAdvice)
ErrorProcessor
@ControllerAdvice(annotations = Controller.class)
@Slf4j
public class ErrorProcessor {
	@ExceptionHandler(Exception.class)
	public String errorProcess(Exception e) {
		
		log.error(e.toString());
		return "error/exception";
	}
}
log4j.xml
	
	<appender name="fatxt" class="org.apache.log4j.FileAppender">
		<param name="file" value="D:/logForSpring/logs/myLogFile.txt" />
		<param name="append" value="true" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss}] %-5p: %c - %m %n" />
		</layout>
	</appender>
...
	
	<root>
		<priority value="warn" />
		<appender-ref ref="fatxt" />
		<appender-ref ref="console" />
	</root>


web.xml
	<error-page>
		<error-code>404</error-code>
		<location>/WEB-INF/views/error/exception404.jsp</location>
	</error-page>
뷰

home.jsp
<body>
	<%@ include file="/WEB-INF/views/common/header.jsp" %>
	
	<div id="div-main">
		<h1>홈페이지</h1>
	</div>
</body>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/common/header.css">
	<div id="div-header">
		<table border="1">
			<tr>
				<td>빈칸</td>
				<td colspan="2"><img width="100%" height="50px" src="${pageContext.request.contextPath}/resources/imgs/qr.png">로고</td>
				<td>빈칸</td>
			</tr>
			<tr>
				<td>메뉴1</td>
				<td>메뉴2</td>
				<td>메뉴3</td>
				<td><a href="member/login">로그인</a></td>
			</tr>
		</table>
	</div>
- ✨위아래 html, head, body..  등 없애기
 
member/join.jsp
<body>
	<%@ include file="/WEB-INF/views/common/header.jsp" %>
	
	<div id="div-main">
		<h1>회원가입 페이지</h1>
		
		<form action = "" method = "post" enctype="multipart/form-data">
	        아이디 : <input type = "text" name = "userId"/><br>
	        비밀번호 : <input type = "password" name = "userPwd"/><br>
	        닉네임 : <input type = "text" name = "userNick"/><br>
	        나이 : <input type = "number" min="0" name = "userAge"/><br>
	        성별 : 
	        <select name="userGender">
	        	<option value="m">남자</option>
	        	<option value="f">여자</option>
	        </select><br>
	        사진 : <input type = "file" name = "f" accept=".jpg, .png"/><br>
	        <img id="profileImg">
	        <input type = "submit" value ="회원가입"/>
	    </form>
	</div>
	
	<script type="text/javascript">
		let fileTag = document.querySelector("input[name=f]");
		
		fileTag.onchange = function(){
			
			
			if(fileTag.files.length > 0){
				
				let reader = new FileReader();
				reader.onload = function(data){
					console.log(data);
					let imgTag = document.querySelector("#profileImg");
					imgTag.src = data.target.result;
					
				}
				reader.readAsDataURL(fileTag.files[0]);
			}else{
				
			}
		}
	
	</script>
</body>

member/login.jsp
<body>
	<%@ include file="/WEB-INF/views/common/header.jsp" %>
	
	<div id="div-main">
		<h1>로그인 페이지</h1>
	
		<form action = "" method = "post">
	        아이디 : <input type = "text" name = "userId"/><br>
	        비밀번호 : <input type = "password" name = "userPwd"/><br>
	        <input type = "submit" value ="로그인"/>
	    </form>
		
		<a href="join">회원가입</a>
	</div>
</body>
✨✨css, assets 경로

src/main/resources/ 에 css 폴더, imgs 폴더 등... 
#div-header{
	width: 80vw;
	height: 20vh;
	margin:0 auto;
}
#div-header table tr td{
    cursor:pointer;
}
#div-header table{
	width: 80%;
	height: 100%;
	text-align: center;
	margin:0 auto;
}
#div-header table tr:nth-child(1){
	height: 100px;
}
#div-header table tr:nth-child(2){
	height: 50px;
}
#div-main{
	width:80vw;
	margin: auto;
}