
Exception 객체는 Framework layer에서만 사용할 수 있으므로 Exception 객체를 공유하려고 Exception Resolver를 사용함
동일한 try-cacth문을 계속 사용하므로 이를 해결하기 위해 Resolver를 사용
html에서 요청 방식은 GET, POST 방식 밖에 사용하지 못함
이를 해결하기 위해 일단 <input type="hidden" name="_method" value="PUT" /> 이렇게 서버에 전달해야함
그리고 서버는 delete, update를 사용하려면 무조건 컨버터를 사용해야함
Converter Reference
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-rest-method-conversion
예외 Resolver 리퍼런스
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-exceptionhandlers
SqlSession을 생성하려면 Mybatis에서는 팩토리 패턴으로 객체를 만듦
팩토리 패턴 : 특정 객체를 셍성하기 위한 팩토리를 먼저 만들고, 그 뒤에 해당 특정 객체를 생성하는 방법
@Configuration
public class DBConfiguration {
static String resource = "/mybatis-config.xml";
@Bean
public static SqlSessionFactory getSqlSessionFactory() {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
SqlSessionFactory sqlSEssionFactory = null;
InputStream is = null;
try {
is = Resources.getResourceAsStream(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSEssionFactory = sqlSessionFactoryBuilder.build(is);
}
catch (Exception e) {
e.printStackTrace();
}
return sqlSEssionFactory;
}
@Bean
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
sqlSession = getSqlSessionFactory().openSession();
return sqlSession;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
<!-- DTO 별칭 설정 -->
<typeAliases>
<typeAlias type="com.spring.dto.Dept" alias="Dept"/>
</typeAliases>
<!-- JDBC 설정 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
<property name="username" value="userid"/>
<property name="password" value="userpw"/>
</dataSource>
</environment>
</environments>
<!-- 매퍼 설정 -->
<mappers>
<mapper resource="/com/spring/mapper/DeptMapper.xml"/>
<mapper resource="/com/spring/mapper/EmpMapper.xml"/>
</mappers>
</configuration>
@Controller : 스프링이 컨트롤러로 인식함
@Autowired : 스프링 컨테이너가 의존성 주입
@Controller
public class HomeController {
@Autowired
private DeptService service;
@RequestMapping(value = "/main", method = RequestMethod.GET)
public String main(Model model) {
List<Dept> deptList = service.getAllDepts();
model.addAttribute("deptList", deptList);
return "main";
}
}
Dept CRUD 관련 컨트롤러
@ModelAttribute : 클라이언트에서 요청한 데이터를 Bean에 등록된 객체로 받을 수 있음
@PathVariable : url 파라미터로 전달받은 값을 1대1 매핑시켜줌
@Controller
public class DeptController {
@Autowired
DeptService service;
// detail
@RequestMapping(value = "/dept/{deptno}", method = RequestMethod.GET)
public String getDeptByDeptno(Model model, @PathVariable int deptno) {
Dept dept = service.getDeptByDeptno(deptno);
model.addAttribute("dept", dept);
return "deptDetail";
}
// insertForm으로 이동
@RequestMapping(value = "/dept", method = RequestMethod.GET)
public String insertDeptForm() {
return "registerDept";
}
// insert
@RequestMapping(value = "/dept", method = RequestMethod.POST)
public String insertDept(@ModelAttribute Dept newDept, Model model) {
String view = "error";
boolean result = false;
try {
result = service.insertDept(newDept);
if (result) {
// version 1
// view = "main";
// model.addAttribute("deptList", service.getAllDepts());
// main으로 이동 시 새로 생성된 dept가 포함되지 않으므로 model에 추가해줘야 함
// version 2
view = "redirect:/main";
return view;
}
} catch (Exception e) {
return view;
}
return view;
}
// updateForm으로 이동
@RequestMapping(value = "/modify/dept/{deptno}", method = RequestMethod.GET)
public String updateDeptForm(@PathVariable int deptno, Model model) {
Dept dept = service.getDeptByDeptno(deptno);
model.addAttribute("dept", dept); // name, 객체
return "updateDept";
}
// update
@RequestMapping(value = "/dept/{deptno}", method = RequestMethod.PUT)
public String updateDept(@PathVariable int deptno, Model model,
@ModelAttribute("dname") String dname, // @ModelAttribute 를 사용하면 무조건 객체와 매핑된다.
@ModelAttribute("loc") String loc) {
// deptno로 기존 dept객체 확인 -> 위에서 확인한 dname, loc 해당 객체 setter
// 제대로 update가 되었으면 -> dept/{deptno} detail페이지 이동
Dept dept = service.getDeptByDeptno(deptno);
dept.setDname(dname);
dept.setLoc(loc);
boolean result = false;
String view = "error";
try {
result = service.updateDnameAndLoc(dept);
if (result) {
view = "redirect:/dept/" + deptno;
}
} catch (Exception e) {
return view;
}
return view;
}
// delete
@RequestMapping(value = "/dept/{deptno}", method = RequestMethod.DELETE)
public String deleteDeptByDeptno(@PathVariable int deptno) {
boolean result = false;
String view = "error";
try {
result = service.deleteDeptByDeptno(deptno);
if (result) {
return "redirect:/main";
}
} catch (Exception e) {
return view;
}
return "view;";
}
// 예외 step2 : DeptController 내부에서 발생하는 모든 NPE 처리
// @ExceptionHandler(NullPointerException.class)
// public String nullPointerExceptionHandler(Exception exception) {
// return "error";
// }
}
ExceptionHandlerExceptionResolver : 우선 순위가 가장 높음, FrontController 예외 처리
@Controller
public class LoginController {
@Autowired
EmpService empService;
// logingForm
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginForm() {
return "login";
}
// version1 : login, 예외처리 : try-catch
// @RequestMapping(value = "/login", method = RequestMethod.POST)
// public String login(@RequestParam("empno") int empno, // POST 방식도 @RequestParam 사용할 수있
// @RequestParam("ename") String ename, // ModelAttribute는 객체를 받을 때 사용
// HttpSession session, Model model) {
//
// String view = "erorr";
// Emp emp = null;
//
// try {
// emp = empService.getEmpByEmpnoAndEname(empno, ename);
//
// session.setAttribute("userId", emp.getEmpno());
// session.setAttribute("userName", emp.getEname());
//
// view = "redirect:/main";
//
// } catch (Exception e) {
// e.printStackTrace();
// return view;
// }
//
// return view;
// }
// version2 : login, @ExceptionHandler로 예외 처리
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String loginTest(@RequestParam("empno") int empno,
@RequestParam("ename") String ename,
HttpSession session,
Model model) throws SQLException, Exception {
String view = "error";
Emp emp = empService.getEmpByEmpnoAndEname(empno, ename);
session.setAttribute("userId", emp.getEmpno());
session.setAttribute("userName", emp.getEname());
view = "redirect:/main";
return view;
}
// logout
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) {
if(session != null) {
session.invalidate();
}
return "redirect:/main";
}
// 예외 version2 : LoginController 내부에서 발생하는 모든 예외 처리, xml을 점점 지양하는 방향
// @ExceptionHandler(Exception.class)
// public ModelAndView nullPointerExceptionHandler(Exception exception) {
// ModelAndView mv = new ModelAndView();
// mv.addObject("exception", exception);
// mv.setViewName("error");
// return mv;
// }
}
// 예외 step3 : @ControllerAdvice, 공통적인 예외는 ControllerAdvice에 처리
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView ExceptionHandler(Exception exception) {
ModelAndView mv = new ModelAndView();
mv.addObject("exception", exception);
mv.setViewName("error");
return mv;
}
}
@Service
public class DeptService {
@Autowired
DeptMapper mapper;
@Autowired
SqlSession sqlSession;
// List
public List<Dept> getAllDepts() {
return mapper.getAllDepts();
}
// 객체
public Dept getDeptByDeptno(int deptno) {
return mapper.getDeptByDeptno(deptno);
}
// List - Map
public List<HashMap<String, Object>> getAllDeptsHashMap() {
return mapper.getAllDeptsHashMap();
}
// HashMap
public HashMap<String, Object> getDeptHashMap(int deptno) {
return mapper.getDeptHashMap(deptno);
}
public String getDnameByDeptnoAndLoc(int deptno, String loc) {
// ver1 : HashMap
// HashMap<String, Object> deptnoAndLoc = new HashMap<String, Object>();
// deptnoAndLoc.put("deptno", deptno);
// deptnoAndLoc.put("loc", loc);
// return mapper.getDnameByDeptnoAndLoc(deptnoAndLoc);
// ver2 : Param
return mapper.getDnameByDeptnoAndLoc(deptno, loc);
}
// String
public String getDnameByDeptno(int deptno) {
String dname = "";
try {
dname = mapper.getDnameByDeptno(deptno);
sqlSession.commit();
} catch (SQLException e) {
e.printStackTrace();
sqlSession.rollback();
}
sqlSession.close();
return dname;
};
// insert
public boolean insertDept(Dept dept) throws SQLException, Exception {
boolean result = false;
int res = mapper.insertDept(dept);
if(res != 0) {
result = true;
} else {
throw new Exception("부서 생성 실패");
}
return result;
}
// update
public boolean updateDnameAndLoc(Dept dept) throws SQLException, Exception {
boolean result = false;
int res = mapper.updateDnameAndLoc(dept);
if(res != 0) {
result = true;
} else {
throw new Exception("부서 수정 실패");
}
return result;
}
// delete
public boolean deleteDeptByDeptno(int deptno) throws SQLException, Exception{
boolean result = false;
int res = mapper.deleteDeptByDeptno(deptno);
if(res != 0) {
result = true;
} else {
throw new Exception("부서 삭제 실패");
}
return result;
}
}
@Service
public class EmpService {
@Autowired
EmpMapper empMapper;
public Emp getEmpByEmpnoAndEname(int empno, String ename) throws SQLException, Exception {
Emp emp = empMapper.getEmpByEmpnoAndEname(empno, ename);
if(emp == null) {
throw new Exception("해당 사원 존재하지 않음");
}
return emp;
}
}
@Mapper
public interface DeptMapper {
// List
public List<Dept> getAllDepts();
// 객체 - Dept
public Dept getDeptByDeptno(int deptno);
// HashMap
public HashMap<String, Object> getDeptHashMap(int deptno);
// List - Map
public List<HashMap<String, Object>> getAllDeptsHashMap();
// String - dname
public String getDnameByDeptno(int deptno) throws SQLException;
// ver1 : hashmap
// public String getDnameByDeptnoAndLoc(HashMap<String, Object> deptnoAndLoc);
// ver2 : Param
public String getDnameByDeptnoAndLoc(@Param("deptno") int deptno, @Param("loc") String loc);
// insert - Dept, Mybatis는 insert의 기본 리턴값이 int임
public int insertDept(Dept dept) throws SQLException;
// update
public int updateDnameAndLoc(Dept dept) throws SQLException;
// delete
public int deleteDeptByDeptno(int deptno) throws SQLException;
}
2개 이상의 파라미터에서는 @Param을 사용함
@Mapper
public interface EmpMapper {
public Emp getEmpByEmpnoAndEname(@Param("empno") int empno, @Param("ename") String ename) throws SQLException, Exception
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.mapper.DeptMapper">
<select id="getAllDepts" resultType="com.spring.dto.Dept">
SELECT deptno, dname, loc FROM dept
</select>
<select id="getDeptByDeptno" resultMap="selectDeptMap">
SELECT deptno, dname, loc FROM dept WHERE deptno = #{deptno}
</select>
<!-- 반환 객체 타입 - Map -->
<resultMap type="com.spring.dto.Dept" id="selectDeptMap">
<result column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
</resultMap>
<select id="getAllDeptsHashMap" resultType="java.util.HashMap">
SELECT deptno, dname, loc FROM dept
</select>
<select id="getDeptHashMap" resultMap="selectDeptHashMap">
SELECT dname, deptno, loc FROM dept WHERE deptno = #{deptno}
</select>
<resultMap type="java.util.HashMap" id="selectDeptHashMap">
<result column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
</resultMap>
<select id="getDnameByDeptno" parameterType="_int" resultType="string">
SELECT dname FROM dept WHERE deptno = #{deptno}
</select>
<!-- ver1 -->
<!-- <select id="getDnameByDeptnoAndLoc" parameterType="hashmap" resultType="string"> -->
<!-- ver2 -->
<select id="getDnameByDeptnoAndLoc" resultType="string">
SELECT dname
FROM dept
WHERE deptno = #{deptno} AND loc = #{loc}
</select>
<insert id="insertDept" parameterType="com.spring.dto.Dept">
INSERT INTO dept (deptno, dname, loc) VALUES (#{deptno}, #{dname}, #{loc})
</insert>
<update id="updateDnameAndLoc" parameterType="com.spring.dto.Dept">
UPDATE DEPT
SET dname = #{dname},
loc = #{loc}
WHERE deptno = #{deptno}
</update>
<delete id="deleteDeptByDeptno" parameterType="_int">
DELETE FROM DEPT
WHERE deptno = #{deptno}
</delete>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.mapper.EmpMapper">
<select id="getEmpByEmpnoAndEname" resultType="com.spring.dto.Emp">
SELECT
empno,
ename,
job,
mgr,
hiredate,
sal,
comm,
deptno
FROM emp
WHERE empno = #{empno} AND ename = #{ename}
</select>
</mapper>
예외 처리 : SimpleMappingExceptionResolver
resources 매핑
prefix, suffix 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<annotation-driven />
<resources mapping="/resources/**" location="/resources/" />
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.spring.mvc" />
<!-- 예외 step01 : SimpleMappingExceptionResolver -->
<!-- <beans:bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
exceptionMappins
<beans:property name="exceptionMappings">
<beans:props>
<beans:prop key="java.lang.Exception">error</beans:prop>
</beans:props>
</beans:property>
<beans:property name="defaultErrorView" value="error" />
</beans:bean> -->
</beans:beans>
클라이언트에서 put, delete 를 사용하기위해 컨버터를 설정해야함
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- put, delete를 연결 시켜주는 컨버터 -->
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 에러 / 예외 처리 -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/error.jsp</location>
</error-page>
</web-app>
2개 이상의 데이터를 mapper.xml에 전달하려면 @Param, HashMap을 사용해야함
대부분의 예외는 url mapping 예외나 제대로 전달하지 못한 경우가 많았음
예외 처리에서 구조에 대한 이해가 너무 어려움
@ControllerAdvice를 사용해서 하나의 핸들러에서 공통적인 예외를 처리하는데 framework layer, Servlet layer 사이에서 처리되는 것인지 FrontController 에서 처리되는 것인지 잘 모르겠음
spring은 사용하기 편리한 것 같으면서 어려운 것 같다.
부족한 기능을 또 다른 전처리를 통해서 사용하는 방법이 많았다.