학원에 다니면서 전에 만들었던 자유게시판, 회원목록, 페이징을 만들었는데, 오늘부터 xml파일을 이용하고 mybatis 를 이용하니 좀 더 간결하고 쉽게 만들었지는거 같다.
전에는 DaoOracle에 append로 쿼리를 싹다 붙이고 코드줄이 엄청 길었는데, 오늘 MyBatis퍼시스턴스 프레임워크를 쓰니 세상 편하다. 쓰는 이유가 다 있구나
처음에는 View단에 싹다 JDBC나 이것저것 놨다가 두번째는 DaoOracle,Service로 나눠서 서버단과 뷰단을 나누고 하니
원리를 이해 할 수 있어서 좋았다. 마지막은 최종 스프링인데, 뭔가 엄청 어려워 보이지만 끝까지 해보자!
https://mybatis.org/mybatis-3/ko/index.html
MyBatis – 마이바티스 3 | 소개
Copyright © 2009–2021MyBatis.org. .
mybatis.org
홈페이지에 시작하기를 눌러 천천히 따라하기.
홈페이지가 한글번역이 된 MyBatis.
메이븐을 지금은 사용하지 않기 때문에
메뉴상단바 Help-Eclipse Marketplace에서
'MyBatipse' 라고 치면
전에 만들었던 자유게시판을 수정도 하고, mapper,xml 파일을 추가할 것이다.
전에는 Dao에서 만들었던 것이 Mapper로 바뀌는 것이다. 즉, DB에 접근해서 데이터를 조회하고 조작
하는 기능 담당했던 객체 Dao가 Mapper로 바뀌는 것이다.
그러면 Mapper는 무엇이길래 Dao에서 갑자기 바꾸는 것일까?
바꾸면 다음과 같이 좋은 점이 있다.
- SQLSession을 등록 하지 않아도 된다.
- DAO인터페이스와 인터페이스를 구현한 DAO클래스를 생성 안해도 된다.
- 문자열을 작성할때 실수가 줄어든다.
- IDE에서 제공하는 Code assist를 사용할 수 있다.
그리고 싱글톤 패턴이 있다.
오직 하나의 인스턴스만 사용할 수 있도록 객체를 생성하는 방법이다.
여기서 인스턴스는 클래스를 복제 해 쉽게 관리해주는 게 인스턴스다.
FreeBoardServiceImpl.java
package com.study.free.service;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.study.exception.BizNotFoundException;
import com.study.exception.BizPasswordNotMatchedException;
import com.study.free.vo.FreeBoardSearchVO;
import com.study.free.vo.FreeBoardVO;
import com.study.util.MybatisSqlSessionFactory;
public class FreeBoardServiceImpl implements IFreeBoardService {
//싱글톤 패턴
// 오직 하나의 인스턴스만 사용할 수 있도록 객체를 생성하는 방법이다.
// 최초 하나의(Single) 인스턴스만 생성하고, 이후에는 이 인스턴스를 참조하게 된다.
// 정적인 클래스 멤버변수 선언 ( - private), 관례적으로 변수명은 instance
// 생성자 숨김(private) <- 외부에서 new 생성 금지 내부에서만 호출 가능
// new를 통해서 가져가라
// 정적멤버변수를 제공(return)하는 공개 매서드 생성, 관레적으로 메서드명은 getInstance()
private static FreeBoardServiceImpl instance = new FreeBoardServiceImpl(); //내부에서 호출가능 new FreeBoardServiceImpl();는 자기자신
//생성된 인스턴스를 넘겨줘야하고, public으로 이동
private FreeBoardServiceImpl () {
//생성자는 막아줘야한다. 외부에서 생성 못하게
}
//생성된 인스턴스 도착.
public static FreeBoardServiceImpl getInstance() { //FreeBoardServiceImpl.getInstance
if(instance == null) instance = new FreeBoardServiceImpl();
return instance;
}
private SqlSessionFactory factory = MybatisSqlSessionFactory.getSqlSessionFactory();
//private String JDBC_URL = "jdbc:apache:commons:dbcp:study";
//private IFreeBoardDao boardDao = new FreeBoardDaoOracle(); //
@Override
public int getBoardCount(FreeBoardSearchVO searchVO) {
try(SqlSession session = factory.openSession()) {
//insert() , update(), delect(), selectOne(), selectList(), selectMap()
int tot = session.selectOne("com.study.free.dao.IFreeBoardDao.getBoardCount",searchVO);
return tot;
}
}
@Override
public List<FreeBoardVO> getBoardList(FreeBoardSearchVO searchVO) {
try(SqlSession session = factory.openSession()) {
//insert() , update(), delect(), selectOne(), selectList(), selectMap()
List<FreeBoardVO> list = session.selectList("com.study.free.dao.IFreeBoardDao.getBoardList",searchVO);
return list;
}
}
@Override
public FreeBoardVO getBoard(int boNo) throws BizNotFoundException {
try(SqlSession session = factory.openSession()) {
FreeBoardVO Board = session.selectOne(
"com.study.free.dao.IFreeBoardDao.getBoard",boNo);
if(Board == null) {
throw new BizNotFoundException("글번호 ["+boNo + "] 조회 실패");
}
return Board;
}
// Connection conn = null;
// try {
// conn = DriverManager.getConnection(JDBC_URL);
//
// FreeBoardVO Board = boardDao.getBoard(boNo);
// //Board가 널인 경우 BizNotFoundException 예외발생
// if(Board == null) {
// throw new BizNotFoundException("글번호 ["+boNo + "] 조회 실패");
// }
// return Board;
// } catch (SQLException e) {
// e.printStackTrace();
// throw new RuntimeException(e.getMessage(), e);
// }finally {
// if (conn != null) try {conn.close();} catch (SQLException e) {}
// }
}
@Override
public void registBoard(FreeBoardVO board) {
// Connection conn = null;
// try {
// conn = DriverManager.getConnection(JDBC_URL);
// conn.setAutoCommit(false);
//
// boardDao.insertBoard(board);
//
// conn.commit();
// }catch (SQLException e) {
// e.printStackTrace();
// try {
// conn.rollback();
// }catch (SQLException e2) {}
// throw new RuntimeException(e.getMessage(),e);
// }finally {
// if (conn != null) try {conn.close();
// }catch (SQLException e) {}
// }
}
@Override
public void modifyBoard(FreeBoardVO board)
throws BizNotFoundException, BizPasswordNotMatchedException {
// Connection conn = null;
// try {
// conn = DriverManager.getConnection(JDBC_URL);
// conn.setAutoCommit(false);
//
// FreeBoardVO vo = boardDao.getBoard(board.getBoNo());
// if(vo == null) {
// throw new BizNotFoundException("글 번호 [" + board.getBoNo() + "] 글 조회 실패");
// }
// if( !vo.getBoPass().equals(board.getBoPass() ) ) {
// throw new BizPasswordNotMatchedException("패스워드가 다릅니다.");
// }
//
// boardDao.insertBoard(board);
//
// conn.commit();
// }catch (SQLException e) {
// e.printStackTrace();
// try {
// conn.rollback();
// }catch (SQLException e2) {}
// throw new RuntimeException(e.getMessage(),e);
// }finally {
// if (conn != null) try {conn.close();
// }catch (SQLException e) {}
// }
}
@Override
public void removeBoard(FreeBoardVO board)
throws BizNotFoundException, BizPasswordNotMatchedException {
//제거
// Connection conn = null;
// try {
// conn = DriverManager.getConnection(JDBC_URL);
// conn.setAutoCommit(false);
// //비밀번호 확인
// FreeBoardVO vo = boardDao.getBoard(board.getBoNo());
// if(vo == null) {
// throw new BizNotFoundException("글 번호 [" + board.getBoNo() + "] 글 조회 실패");
// }
// if( !vo.getBoPass().equals(board.getBoPass() ) ) {
// throw new BizPasswordNotMatchedException("패스워드가 다릅니다.");
// }
// //비밀번호 확인
//
// boardDao.deleteBoard(board);
// // TODO cnt가 0 인 경우 BizNotEffectedException 예외 발생
//
// conn.commit();
// } catch (SQLException e) {
// e.printStackTrace();
// try {conn.rollback();} catch (SQLException e2) {}
// throw new RuntimeException(e.getMessage(), e);
// }finally {
// if (conn != null) try {conn.close();} catch (SQLException e) {}
// }
}
@Override
public void increaseHit(int boNo) {
// Connection conn = null;
// try {
// conn = DriverManager.getConnection(JDBC_URL);
// conn.setAutoCommit(false);
//
// boardDao.increaseHit(boNo);
//
// conn.commit();
// } catch (SQLException e) {
// e.printStackTrace();
// try {conn.rollback();} catch (SQLException e2) {}
// throw new RuntimeException(e.getMessage(), e);
// }finally {
// if (conn != null) try {conn.close();} catch (SQLException e) {}
// }
}
}
:: 주석 단게 전에 했던 Dao
이렇게 바꿨다면 이제 List,Edit,View,Delete,Modify,Regist에 getInstance를 넣어줘야한다.
전에는 객체를 계속 생성해서 바뀌는데, 오직하나의 인스턴스만 생성하기 때문에 바뀌지 않는다.
최초 하나의(Single) 인스턴스만 생성하고, 이후에는 이 인스턴스를 참조하게 된다.
정적인 클래스 멤버변수 선언 ( - private), 관례적으로 변수명은 instance
ICodeService codeService = new CodeServiceImpl(); (전)
ICodeService codeService = CodeServiceImpl.getInstance(); (후)
(코드 주석 참조)
WEB-INF/lib에 넣어놨다.
log4j.xml
log4j는 아파치에서 만든 오픈소스 라이브러리로써 로그를 남기 위해 사용되는 자바기반 로깅 유틸리티입니다.
디버그용 도구로 사용합니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration
PUBLIC "-//APACHE//DTD LOG4J 1.2//EN"
"http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<!--
** 1. jar : log4j-1.2.17.jar
2. log4.xml 파일은 /WEB-INF/classes 바로 존재해야함
maven인 경우 resource폴더 바로 밑에
일반 웹어플인 경우 src 폴더 밑에
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- 콘솔 로그 -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%-5p] %t %c.%M:%L - %m %n "/>
</layout>
</appender>
<!-- 날짜별 로그 -->
<appender name="dailyFile" class="org.apache.log4j.DailyRollingFileAppender">
<!-- 이것은 날짜별로 로그를 남김. 파일명.확장자.DatePattern으로 정의 함-->
<param name="File" value="${user.home}/log/study3.log" />
<param name="Append" value="true" />
<param name="Encoding" value="UTF-8" />
<!-- param name="MaxFileSize" value="100000KB" /> -->
<!-- param name="MaxBackupIndex" value="10" /> -->
<param name="DatePattern" value="'.'yyMMdd"/>
<!--yyMMddHHmm, 시간별로 하고싶다 HH, 일별로 하고 싶다 ddd -->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%-5p] %t %c.%M:%L - %m %n"/>
<!--
<param name="ConversionPattern" value="%t>
[%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n"/>
위 pattern 결과 :http-bio-8080-exec-6> [2017-01-12 12:07:44] [root] [179] [ERROR]
-->
</layout>
</appender>
<!-- xml 로그-->
<!-- xml형식으로 로그를 남김
<appender name="xmlout" class="org.apache.log4j.FileAppender">
<param name="file" value="log4jxml.xml"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.xml.XMLLayout"/>
</appender>
-->
<!-- 팩키지별 로그 설정 -->
<logger name="org.springframework" additivity="false">
<level value="WARN" />
<appender-ref ref="dailyFile" />
<appender-ref ref="console" />
</logger>
<logger name="com.study" additivity="false">
<level value="debug" />
</logger>
<!--
root 로거 기본 설정
- ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
DEBUG : 개발자가 변수들 확인하기 위한 용도
INFO : 개발자가 중요한 변수 확인하기 위한 용도
WARN : 에러는 아니지만 뭔가 의심스러운 변수를 확인하기 위한 용도
ERROR : 오류가 발생한 경우, 대단히 의심스러운 경우 확인
FATAL : 애플리케이션 운영에 심각한 영향이 있을 경우 FATAL로 로그를 남긴다.
-->
<root>
<level value="info" />
<appender-ref ref="console"/>
<appender-ref ref="dailyFile"/>
</root>
</log4j:configuration>
root 로거 기본 설정
- ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
DEBUG : 개발자가 변수들 확인하기 위한 용도
INFO : 개발자가 중요한 변수 확인하기 위한 용도
WARN : 에러는 아니지만 뭔가 의심스러운 변수를 확인하기 위한 용도
ERROR : 오류가 발생한 경우, 대단히 의심스러운 경우 확인
FATAL : 애플리케이션 운영에 심각한 영향이 있을 경우 FATAL로 로그를 남긴다.
ALL이나 TRACE는 사용을 잘 안하고 DEBUG부터 5단계 보통 시작한다.
app_config.properties
#config/ app_config.properties
jdbc.driverClassName =oracle.jdbc.driver.OracleDriver
jdbc.url =jdbc:oracle:thin:@127.0.0.1:1521:xe
jdbc.user =java
jdbc.password =사용하는 DB 비밀번호 입력
mybatis-config.xml
<?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>
<properties resource="config/app_config.properties" />
<settings>
<!-- table : bo_no vo : boNo
전통적인 데이터베이스 칼럼명 형태인
A_COLUMN을 CamelCase형태의 자바 프로퍼티명 형태인
aColumn으로 자동으로 매핑하도록 함
-->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!-- JDBC타입을 파라미터에 제공하지 않을때 null값을 처리한 JDBC타입을 명시한다.
일부 드라이버는 칼럼의 JDBC타입을 정의하도록 요구하지만
대부분은 NULL, VARCHAR 나 OTHER 처럼 일반적인 값을 사용해서 동작한다. -->
<setting name="jdbcTypeForNull" value="NULL" />
<!-- 가져온 값이 null일 때 setter나 맵의 put 메소드를 호출할지를 명시
Map.keySet() 이나 null값을 초기화할때 유용하다.
int, boolean 등과 같은 원시타입은 null을 설정할 수 없다는 점은 알아두면 좋다. -->
<setting name="callSettersOnNulls" value="true" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 작성한 매퍼파일 등록
스프링 연동시 "*" 사용가능 mybatis/mapper/*.xml
-->
<mappers>
<mapper resource="mybatis/mapper/freeboard.xml"/>
</mappers>
</configuration>
app_config.properties에 있는 jdbc 프로퍼티를 가져온다.
freeboard.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.free.dao.IFreeBoardDao">
<select id="getBoardCount"
resultType="int">
SELECT COUNT(*) AS tot
FROM free_board
WHERE bo_del_yn ='N'
</select>
<select id="getBoardList"
resultType="com.study.free.vo.FreeBoardVO">
SELECT bo_no
, bo_title
, bo_category
,(SELECT comm_nm from comm_code WHERE comm_cd = bo_category ) as bo_category_nm
, bo_writer
, bo_content
, bo_pass
, bo_ip
, bo_hit
, TO_CHAR(bo_reg_date, 'YYYY-MM-DD') AS bo_reg_date
, TO_CHAR(bo_mod_date, 'YYYY-MM-DD') AS bo_mod_date
, bo_del_yn
FROM free_board
WHERE bo_del_yn = 'N'
ORDER BY bo_no DESC
</select>
<!-- 프리미티브형(기본형)은 변수 자체에 값을 보유하기 때문에, 이름은 아무렇게해도 되지만
의미 있게 짓는게 좋다. -->
<select id="getBoard"
parameterType="int"
resultType="com.study.free.vo.FreeBoardVO">
SELECT bo_no
, bo_title
, bo_category
,(SELECT comm_nm from comm_code WHERE comm_cd = bo_category ) as bo_category_nm
, bo_writer
, bo_content
, bo_pass
, bo_ip
, bo_hit
, TO_CHAR(bo_reg_date, 'YYYY-MM-DD-HH24:MI:SS') AS bo_reg_date
, TO_CHAR(bo_mod_date, 'YYYY-MM-DD-HH24:MI:SS') AS bo_mod_date
, bo_del_yn
FROM free_board
WHERE bo_no = #{티스토리짱}
</select>
<insert id="insertBoard" parameterType="com.study.free.vo.FreeBoardVO">
INSERT INTO free_board (
bo_no
, bo_title
, bo_category
, bo_writer
, bo_pass
, bo_content
, bo_ip
, bo_hit
, bo_reg_date
, bo_del_yn
) VALUES (
<![CDATA[
seq_free_board.nextval
, #{boTitle}
, #{boCategory}
, #{boWriter}
, #{boPass}
, #{boContent}
, #{boIp}
, 0
, SYSDATE
, 'N' ]]>)
</insert>
<update id="updateBoard" parameterType="com.study.free.vo.FreeBoardVO">
<![CDATA[
UPDATE free_board
SET bo_title = #{boTitle}
, bo_category = #{boCategory}
, bo_writer = #{boWriter}
, bo_content = #{boContent}
, bo_ip = #{boIp}
, bo_mod_date = SYSDATE
where bo_no = #{boNo}
]]>
</update>
<delete id="deleteBoard">
<![CDATA[
UPDATE free_board
SET bo_del_yn = 'Y'
WHERE bo_no = #{boNo}
]]>
</delete>
<update id="increaseHit" >
<![CDATA[
UPDATE free_board
SET bo_Hit = bo_hit + 1
WHERE bo_no = #{은하철도999}
]]>
</update>
</mapper>
![CDATA] :
태그 안에서는 전부 문자열로 치환 합니다.
쿼리를 작성 할때 >, < 를 사용하게 되는데 닫힘 태그로 인식하기 때문에 조심해야합니다.
예를 들어
<select id = "test" resultMap="testMap">
select test_id
from supertest
where salary > 99
</select>
이런 경우 에러가 나는데 그 이유는 where절에 있는 '>'가 닫힘 태그로 인식하기 때문입니다.
그래서
<select id = "test" resultMap="testMap">
<![CDATA[
select test_id
from supertest
where salary > 99
]]>
</select>
이렇게 쓰면 문자열로 인식하기 때문에 실행이 됩니다.
'Back-End > JSP' 카테고리의 다른 글
[JSP] Log4j, slf4j 설치 (0) | 2021.07.23 |
---|---|
[JSP] 디자인 패턴 및 SOLID 원칙 (0) | 2021.07.22 |
[JSP] 페이징 (0) | 2021.07.22 |
[JSP] 회원 정보 만들기-3 (0) | 2021.07.20 |
[JSP] 회원 정보 만들기-2 (0) | 2021.07.20 |