
위 코드는 AppConfig에 application.properties 연결한 것
# HikariCP
spring.datasource.hikari.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.hikari.jdbc-url=jdbc:log4jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.hikari.username=GD
spring.datasource.hikari.password=1111
# mybatis
mybatis.config-location=mybatis/config/mybatis-config.xml
mybatis.mapper-locations=mybatis/mapper/*.xml
어제 DataSource를 히카리로 대체하고 히카리에서 사용하는 언어 기반으로 application.properties(src/main/resources, 위 코드)에 db 정보를 넣어놓고 불러들이는 코드를 제작해둔 상태
쿼리문을 옮겨서 mapper를 완성하는 작업
contactDao -> src/main/resources/mybatis/mapper/contact.xml

이런식으로 (아래꺼를 위로) 다 옮기고 나중에 mybatis에 맞게 db 부분 수정할 거임. insert update delete select 다 옮긴다.
<select> 엘리먼트에서 생략될 수 없는 속성. 나머지는 생략 가능)
select
List<ContactDto> -> 결과타입이 있으므로 resultType을 적는다. 주의. ContactDto가 리스트에 들어가있어도 리스트라고 하지 않고 리스트에 실제로 들어가 있는 타입 즉 ContactDto만 적으면 된다. (collections의 경우, collection에 포함된 type 작성)원래는 파라미터 타입이랑 리턴타입 전부 패키지 이름 일일이 써서 parameterType="com.gdu.app11.dto.ContactDto" 이런 식으로 써줬던건데 3교시에 배울 별명 지정으로 간단화된 코드임
<mapper namespace="mybatis.mapper.contact">
<insert id="insert" parameterType="ContactDto">
INSERT INTO CONTACT_T(CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT) VALUES(CONTACT_SEQ.NEXTVAL, #{name}, #{tel}, #{email}, #{address}, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'))
</insert>
<update id="update" parameterType="ContactDto">
UPDATE CONTACT_T SET NAME = #{name}, TEL = #{tel}, EMAIL = #{email}, ADDRESS = #{address} WHERE CONTACT_NO = #{contactNo}
</update>
<delete id="delete" parameterType="int">
DELETE FROM CONTACT_T WHERE CONTACT_NO = #{contactNo}
</delete>
<select id="selectList" resultType="ContactDto">
SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T ORDER BY CONTACT_NO ASC
</select>
<select id="selectContactByNo" parameterType="int" resultType="ContactDto">
SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T WHERE CONTACT_NO = #{contactNo}
</select>
</mapper>
@Repository
public class ContactDao {
@Autowired // Spring Container에 저장된 JdbcConnection 타입의 객체(Bean)을 가져온다.
private JdbcConnection jdbcConnection;
private Connection con;
private PreparedStatement ps;
private ResultSet rs;
/**
* 삽입 메소드<br>
* @param contactDto 삽입할 연락처 정보(name, tel, email, address)
* @return insertCount 삽입된 행(Row)의 개수, 1이면 삽입 성공, 0이면 삽입 실패
*/
public int insert(ContactDto contactDto) {
int insertCount = 0;
try {
con = jdbcConnection.getConnection();
String sql = "INSERT INTO CONTACT_T(CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT) VALUES(CONTACT_SEQ.NEXTVAL, ?, ?, ?, ?, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'))";
ps = con.prepareStatement(sql);
ps.setString(1, contactDto.getName());
ps.setString(2, contactDto.getTel());
ps.setString(3, contactDto.getEmail());
ps.setString(4, contactDto.getAddress());
insertCount = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
jdbcConnection.close(con, ps, rs);
}
return insertCount;
}
/**
* 수정 메소드<br>
* @param contactDto 수정할 연락처 정보(contact_no, name, tel, email, address)
* @return updateCount 수정된 행(Row)의 개수, 1이면 수정 성공, 0이면 수정 실패
*/
public int update(ContactDto contactDto) {
int updateCount = 0;
try {
con = jdbcConnection.getConnection();
String sql = "UPDATE CONTACT_T SET NAME = ?, TEL = ?, EMAIL = ?, ADDRESS = ? WHERE CONTACT_NO = ?";
ps = con.prepareStatement(sql);
ps.setString(1, contactDto.getName());
ps.setString(2, contactDto.getTel());
ps.setString(3, contactDto.getEmail());
ps.setString(4, contactDto.getAddress());
ps.setInt(5, contactDto.getContact_no());
updateCount = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
jdbcConnection.close(con, ps, rs);
}
return updateCount;
}
/**
* 삭제 메소드<br>
* @param contact_no 삭제할 연락처 번호
* @return deleteCount 삭제된 행(Row)의 개수, 1이면 삭제 성공, 0이면 삭제 실패
*/
public int delete(int contact_no) {
int deleteCount = 0;
try {
con = jdbcConnection.getConnection();
String sql = "DELETE FROM CONTACT_T WHERE CONTACT_NO = ?";
ps = con.prepareStatement(sql);
ps.setInt(1, contact_no);
deleteCount = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
jdbcConnection.close(con, ps, rs);
}
return deleteCount;
}
/**
* 전체 조회 메소드<br>
* @return 조회된 모든 연락처 정보(ContactDto)
*/
public List<ContactDto> selectList() {
List<ContactDto> list = new ArrayList<ContactDto>();
try {
con = jdbcConnection.getConnection();
String sql = "SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T ORDER BY CONTACT_NO ASC";
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()) {
ContactDto contactDto = new ContactDto();
contactDto.setContact_no(rs.getInt("CONTACT_NO"));
contactDto.setName(rs.getString("NAME"));
contactDto.setTel(rs.getString("TEL"));
contactDto.setEmail(rs.getString("EMAIL"));
contactDto.setAddress(rs.getString("ADDRESS"));
contactDto.setCreated_at(rs.getString("CREATED_AT"));
list.add(contactDto);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jdbcConnection.close(con, ps, rs);
}
return list;
}
/**
* 상세 조회 메소드<br>
* @param contact_no 조회할 연락처 번호
* @return contactDto 조회된 연락처 정보, 조회된 연락처가 없으면 null 반환
*/
public ContactDto selectContactByNo(int contact_no) {
ContactDto contactDto = null;
try {
con = jdbcConnection.getConnection();
String sql = "SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T WHERE CONTACT_NO = ?";
ps = con.prepareStatement(sql);
ps.setInt(1, contact_no);
rs = ps.executeQuery();
if(rs.next()) {
contactDto = new ContactDto();
contactDto.setContact_no(rs.getInt(1));
contactDto.setName(rs.getString(2));
contactDto.setTel(rs.getString(3));
contactDto.setEmail(rs.getString(4));
contactDto.setAddress(rs.getString(5));
contactDto.setCreated_at(rs.getString(6));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jdbcConnection.close(con, ps, rs);
}
return contactDto;
}
}
@RequiredArgsConstructor
@Repository
public class ContactDao {
private final SqlSessionTemplate sqlSessionTemplate;
/**
* 삽입 메소드<br>
* @param contactDto 삽입할 연락처 정보(name, tel, email, address)
* @return insertCount 삽입된 행(Row)의 개수, 1이면 삽입 성공, 0이면 삽입 실패
*/
public int insert(ContactDto contactDto) {
return sqlSessionTemplate.insert("mybatis.mapper.contact.insert", contactDto);
}
/**
* 수정 메소드<br>
* @param contactDto 수정할 연락처 정보(contact_no, name, tel, email, address)
* @return updateCount 수정된 행(Row)의 개수, 1이면 수정 성공, 0이면 수정 실패
*/
public int update(ContactDto contactDto) {
return sqlSessionTemplate.update("mybatis.mapper.contact.update", contactDto);
}
/**
* 삭제 메소드<br>
* @param contactNo 삭제할 연락처 번호
* @return deleteCount 삭제된 행(Row)의 개수, 1이면 삭제 성공, 0이면 삭제 실패
*/
public int delete(int contactNo) {
return sqlSessionTemplate.delete("mybatis.mapper.contact.delete", contactNo);
}
/**
* 전체 조회 메소드<br>
* @return 조회된 모든 연락처 정보(ContactDto)
*/
public List<ContactDto> selectList() {
return sqlSessionTemplate.selectList("mybatis.mapper.contact.selectList");
}
/**
* 상세 조회 메소드<br>
* @param contactNo 조회할 연락처 번호
* @return contactDto 조회된 연락처 정보, 조회된 연락처가 없으면 null 반환
*/
public ContactDto selectContactByNo(int contactNo) {
return sqlSessionTemplate.selectOne("mybatis.mapper.contact.selectContactByNo", contactNo);
}
}

왼쪽 mybatis 전, 오른쪽 mybatis 후
엄청나게 간단해졌다.
mapper에서 사용할 수 있는 유용한 옵션
파라미터타입이랑 리턴타입에 패키지 이름까지 구구절절 적어줘야하는데 너무 김.
예시 parameterType="com.gdu.app11.dto.ContactDto"
이걸 별명 지정해서 간단하게 적을 수 있음
<typeAliases>
<typeAlias alias="ContactDto" type="com.gdu.app11.dto.ContactDto"/>
</typeAliases>
alias: 돌려서 부를 별명
type: 원래 패키지명

로그찍어봤더니 hikari(사진)랑 mybatis, ibatis가 debug레벨까지 다찍혀서 보기 힘들다. 안찍히도록 info레벨로 바꾸자
<logger name="com.zaxxer.hikari" level="info" />
<logger name="org.mybatis" level="info" />
<logger name="org.apache.ibatis" level="info" />
저번에 했던 것 : AoP를 이용한 처리
주소: ContactController에 남아있다.

tx.do에 매핑되면 txText()가 돌아서 하나는 insert 성공, 하나는 insert 실패가 되어아 한다.
(트랜잭션 처리를 안하면 성공은 들어가지만 실패는 들어가지 않는다)
하나의 서비스를 구성하는 모든 DB작업은 전부 성공하거나 전부 실패해야 한다. -> 트랜잭션의 원자성
방법: ContactServiceImpl에 @Transactional 추가하고 AppConfig에서 @Transactional 허용한다는 뜻으로 @EnableTransactionManagement 추가 (aop에서는 @EnableAspectJAutoProxy에 의해서 돌았음)
@Transactional
@RequiredArgsConstructor // private final ContactDao contactDao;에 @Autowired를 하기 위한 코드이다.
@Service // ContactService 타입의 객체(Bean)을 Spring Container에 저장한다.
public class ContactServiceImpl implements ContactService {
private final ContactDao contactDao;
@Override
public int addContact(ContactDto contactDto) {
int addResult = contactDao.insert(contactDto);
return addResult;
}
@Override
public int modifyContact(ContactDto contactDto) {
int modifyResult = contactDao.update(contactDto);
return modifyResult;
}
@Override
public int deleteContact(int contactNo) {
int deleteResult = contactDao.delete(contactNo);
return deleteResult;
}
@Transactional(readOnly=true) // 조회용(성능 이점)
@Override
public List<ContactDto> getContactList() {
return contactDao.selectList();
}
@Transactional(readOnly=true) // 조회용(성능 이점)
@Override
public ContactDto getContactByNo(int contactNo) {
return contactDao.selectContactByNo(contactNo);
}
@Override
public void txTest() {
// @Transactional 활용한 트랜잭션 처리 테스트 메소드
// "성공1개+실패1개" DB처리를 동시에 수행했을 때 모두 실패로 되는지 확인하기
// 성공
contactDao.insert(new ContactDto(0, "이름", "전화번호", "이메일", "주소", null));
// 실패
contactDao.insert(new ContactDto()); // NAME 칼럼은 NOT NULL이므로 전달된 이름이 없으면 Exception이 발생한다.
}
}
12장도 mybatis 한번 더 한다. dao를 더 짧게 할 수 있다.
12장에서 소개하는 걸로 최종 파이널까지 감.
aop 복습 (대충 적어야지 ㅠㅠ 아직 잘 모름)
@Slf4j
@Aspect
@Component
public class LogAop {
// 포인트컷 : 어떤 메소드에서 동작하는지 표현식으로 작성
@Pointcut("execution(* com.gdu.app11.controller.*Controller.*(..))")
public void setPointCut() {}
// 어드바이스 : 포인트컷에서 실제로 동작할 내용
@Before("setPointCut()")
public void doLog(JoinPoint joinPoint) { // JoinPoint : 어드바이스로 전달되는 메소드
// 1. HttpServletRequest
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
// 2. 요청 파라미터 -> Map 변환
Map<String, String[]> map = request.getParameterMap();
// 3. 요청 파라미터 출력 형태 만들기
String params = "";
if(map.isEmpty()) {
params += "No Parameter";
} else {
for(Map.Entry<String, String[]> entry : map.entrySet()) {
params += entry.getKey() + ":" + Arrays.toString(entry.getValue()) + " ";
}
}
// 4. 로그 찍기 (치환 문자 {} 활용)
log.info("{} {}", request.getMethod(), request.getRequestURI()); // 요청 방식, 요청 주소
log.info("{}", params); // 요청 파라미터
}
}

(다 ContactControllerTest)
// JUnin4를 사용한다.
@RunWith(SpringJUnit4ClassRunner.class)
// 테스트에서 사용할 Bean이 @Component로 생성되었다.
@ContextConfiguration(locations="file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml")
// 메소드 이름 순으로 테스트를 수행한다.
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
// WebApplicationContext를 사용할 수 있다. (Spring Container에 Bean을 생성해 둔다.)
@WebAppConfiguration
// 로그
@Slf4j
servlet-context.xml에 component-scan으로 인해 컴포넌트로 인식되기 때문에 contextConfiguration에서 servlet-context 파일의 경로를 잡아줘야함. 컴포넌트 찾아볼 위치를 지정하는 것(src부터 다 적어서 servlet-context.xml 경로 지정해주면 된다)
-픽스메소드오더: 테스트 수행 순서, methodSorters.NAME_ASCENDING = 메소드의 이름순
여기까지가 junit 테스트에서도 사용했던 것.
junit 단위 테스트에서는 톰캣이 사용되지 않았었다. dao만 있으면 테스트 가능
통합 테스트는 컨트롤러로 테스트하기 때문에 톰캣의 개입이 필요
=> @WebAppConfiguration 적기 -> webApplicationContext를 Bean으로 생성한다
public class ContactControllerTest {
// MockMvc 객체가 생성될 때 필요한 WebApplicationContext
@Autowired
private WebApplicationContext webApplicationContext;
// MockMvc 객체 : 테스트를 수행하는 객체
private MockMvc mockMvc;
// 테스트 수행 이전에 MockMvc mockMvc 객체를 만든다.
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
// 삽입테스트
// 요청 방식 : POST
// 요청 주소 : /contact/add.do
// 요청 파라미터 : name, tel, email, address
@Test
public void test01_삽입() throws Exception {
log.info(mockMvc
.perform(MockMvcRequestBuilders // 요청 수행
.post("/contact/add.do") // 요청 방식, 주소
.param("name", "뽀로로") // 요청 파라미터
.param("tel", "02-111-1111")
.param("email", "pororo@naver.com")
.param("address", "pororo villiage"))
.andReturn() // 요청 결과
.getFlashMap() // 요청 결과가 저장된 flash attribute를 Map으로 가져옴
.toString());
}
// 상세조회테스트
// 요청 방식 : GET
// 요청 주소 : /contact/detail.do
// 요청 파라미터 : contactNo
@Test
public void test02_상세조회() throws Exception {
log.info(mockMvc
.perform(MockMvcRequestBuilders
.get("/contact/detail.do")
.param("contactNo", "1"))
.andReturn()
.getModelAndView()
.getModelMap()
.toString());
}
}


상세조회는 flush 어쩌구 아니고 그냥 model에 저장한 attribute라 getModelAndView로 가져오고 (모델 생기기 전에는 모델앤뷰라는 클래스가 있었다.) 보기 불편하니까 겟모델뷰로 모델로 바꿔서 투스트링으로 문자열로 변환
디벨로퍼 열어서 db 생성해주고

테스트
결과

콘솔에서도 삽입과 조회 로그 확인 가능
Mybatis를 인터페이스로 쓰기 (12장으로 최종버전 삼으면 됨)
dao는 본문이 있어서 메소드로 본문이 가능한데 인터페이스는 불가능
인터페이스를 쓰는 두가지 방식
1. mapper를 아예 안쓰는 방식 - 쿼리문을 애노테이션화 (쿼리문을 문자열 형식으로 자바에서 작성해야해서 불편함)
2. mapper와 인터페이스를 연결하는 방식 (더 편함. 이걸로 할거)
@Mapper
/*
* @Mapper
* 1. 매퍼의 쿼리문을 호출할 수 있는 인터페이스에 추가하는 마이바티스 어노테이션이다.
* 2. 메소드이름과 호출할 쿼리문의 아이디(id)을 동일하게 맞추면 자동으로 호출된다.
* 3. @Mapper로 등록된 인터페이스의 검색이 가능하도록 @MapperScan을 추가해야 한다. (SqlSessionTemplate Bean이 등록된 AppConfig.java에 추가한다.)
* 4. 매퍼의 namespace값을 인터페이스 경로로 작성한다. (원래는 매퍼의 위치로 함)
*/
@Mapper
public interface ContactMapper {
public int insert(ContactDto contactDto);
public int update(ContactDto contactDto);
public int delete(int contactNo);
public List<ContactDto> selectList();
public ContactDto selectContactByNo(int contactNo);
public int deleteOldestContact();
}
@MapperScan(basePackages="com.gdu.app12.dao")
추가
<mapper namespace="com.gdu.app12.dao.ContactMapper">
<insert id="insert" parameterType="ContactDto">
INSERT INTO CONTACT_T(CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT) VALUES(CONTACT_SEQ.NEXTVAL, #{name}, #{tel}, #{email}, #{address}, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'))
</insert>
<update id="update" parameterType="ContactDto">
UPDATE CONTACT_T SET NAME = #{name}, TEL = #{tel}, EMAIL = #{email}, ADDRESS = #{address} WHERE CONTACT_NO = #{contactNo}
</update>
<delete id="delete" parameterType="int">
DELETE FROM CONTACT_T WHERE CONTACT_NO = #{contactNo}
</delete>
<select id="selectList" resultType="ContactDto">
SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T ORDER BY CONTACT_NO ASC
</select>
<select id="selectContactByNo" parameterType="int" resultType="ContactDto">
SELECT CONTACT_NO, NAME, TEL, EMAIL, ADDRESS, CREATED_AT FROM CONTACT_T WHERE CONTACT_NO = #{contactNo}
</select>
<delete id="deleteOldestContact">
DELETE
FROM CONTACT_T
WHERE CONTACT_NO = (SELECT MIN(CONTACT_NO)
FROM CONTACT_T)
</delete>
</mapper>
사실상 컨택매퍼 인터페이스랑 컨택매퍼 xml은 하나인데 xml에 있는 걸 직접 부를수는 없으니 인터페이스를 사용하는 것. 인터페이스를 부르면 똑같은 이름을 가진 아이디를 찾아서 호출
ㅇ
이제 끝낫다 ㅎㅎ
@EnableScheduling // @Scheduled 허용
우리가 만든 클래스는 bean이 아니라 component로 등록
(원래 AppConfig에 아래 코드 적고 ContactScheduler 가서 실행해줬는데 아래 코드 지우고 ContactScheduler에 @Component 붙이는 걸로 바꿈

<delete id="deleteOldestContact">
DELETE
FROM CONTACT_T
WHERE CONTACT_NO = (SELECT MIN(CONTACT_NO)
FROM CONTACT_T)
</delete>
@RequiredArgsConstructor
@Slf4j
@Component
public class ContactScheduler {
private final ContactService contactService;
@Scheduled(cron="0 0/1 * * * ?")
public void doSomething() {
// 1분마다 가장 예전에 등록된 연락처를 삭제하는 스케쥴러
// DELETE - ContactMapper/contactMapper - ContactService/ContactServiceImpl
contactService.deleteOldestContact();
log.info("doSomething()");
}
}
public int deleteOldestContact();
추가
@Override
public void deleteOldestContact() {
contactDao.deleteOldestContact();
}
정리 빠뜨렸는데 db 가서 식적고 커밋도 해야함

DROP SEQUENCE CONTACT_SEQ;
CREATE SEQUENCE CONTACT_SEQ NOCACHE ORDER;
DROP TABLE CONTACT_T;
CREATE TABLE CONTACT_T (
CONTACT_NO NUMBER NOT NULL
, NAME VARCHAR2(100 BYTE) NOT NULL
, TEL VARCHAR2(100 BYTE) NULL
, EMAIL VARCHAR2(100 BYTE) NULL
, ADDRESS VARCHAR2(100 BYTE) NULL
, CREATED_AT VARCHAR2(100 BYTE) NOT NULL
, CONSTRAINT PK_CONTACT PRIMARY KEY(CONTACT_NO)
);
DELETE FROM CONTACT_T WHERE CONTACT_NO = (SELECT MIN(CONTACT_NO) FROM CONTACT_T);
SELECT * FROM CONTACT_T;
DELETE
FROM CONTACT_T
WHERE CONTACT_NO = (SELECT MIN(CONTACT_NO)
FROM CONTACT_T);
히카리 버전 5.0.1로 바꿈
컨트롤러로 들어오는 요청과 응답을 가로챈다.
HandlerInterceptor 인터페이스를 구현(권장)하거나, HandlerInterceptorAdapter 클래스를 상속하면 인터셉터가 된다.
Spring Container에 Bean으로 등록해 두어야 한다.
주요 메소드
1) preHandle() : 요청 전에 동작할 코드 작성(false를 리턴하면 컨트롤러 요청 동작을 막을 수 있다.) - 얘만 boolean으로 잡을 수 있음
2) postHandle() : 요청 후에 동작할 코드 작성(컨트롤러의 Handler 동작 이후)
3) afterCompletion() : View까지 처리가 끝난 후 동작할 코드 작성
언제 Interceptor가 동작할 것인지 servlet-context.xml에 작성한다. (인터셉터 등록은 servlet-context.xml에 한다)
1) <mapping> : 인터셉터가 동작할 요청 주소
2) <exclude-mapping> : 인터셉터가 동작하지 않을 요청 주소
3) <beans:bean> : 동작할 인터셉터
Filter vs Interceptor

@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>");
out.println("alert('인터셉터가 동작했습니다.')");
out.println("history.back()"); //못들어가게 뒤로 돌려보내기
out.println("</script>");
out.flush();
out.close();
return false; // 컨트롤러의 요청이 처리되지 않는다.
}
}


<interceptors>
<interceptor>
<mapping path="/**"/>
<exclude-mapping path="/contact/list.do"/>
<beans:bean class="com.gdu.app12.intercept.MyInterceptor" />
</interceptor>
</interceptors>
MyInterceptor로 만든걸 servlet-context에 등록
mapping path -> 언제 동작하는 인터셉터인지 설정

작성페이지로 갈 때 동작하는 인터셉터.

write 페이지로 가는 거 누르면

인터셉터 동작하면서 못 들어간다.
--
/** => 모든 요청
<exclude-mapping path="/contact/list.do"/>
이 요청은 뺀다는 뜻