우선 메이븐 프로젝트로 마이바티스 프로젝트를 다시 만들어보자
퀵스타트로 프로젝트 만들고
pom.xml studymybatis 에 있는거 덮어쓰기
pom.xml 들어가서 artifactId name url 프로젝트명과 같게 수정
메인과 테스트 폴더에 있는 app apptest 삭제
빌드패스 들어가서 수정 (소스-라이브러리-오더 순서)
프로퍼티스 들어가서
컴파일러 - 클래스파일 제너레이선 다 체크
리소스 - utf8로 수정
lg4j2 studymybatis - 메인 -리소시스 에 있는거 덮어쓰기
------------------------------------------------------------------------------------
*웹 3계층 *MVC패턴
(1) Presentation Layer (표현계층) ---------------------> View
- 화면을 만들어 내는 계층
: HTML, CSS, JSP + MODEL (데이터)
(2) Business Layer (비지니스계층) ---------------------> Controller => Model 생성
- 비지니스 로직을 수행하는 모든 객체
: Service 객체
: DTO 객체
(3) Persistence Layer (영속성계층) ----------------------> DAO
- 영속성 : 영구적으로 저장
- DB 테이블을 조작하는 모든 객체
: DAO 객체 (Data Access Object)
Mapper
VO 객체 (Value Object)
------------------------------------------------------------------------------------
* Framework vs Library
- 프레임워크 : I just follow framework, 프레임워크의 규칙을 따라야함 (mybatis, django)
- 라이브러리 : I'm in controll, 내가 호출함 (jQuery, React)
------------------------------------------------------------------------------------
MyBatis SQL Mapper Framework
가. 웹3계층중 영속성계층 구현을 위한 프레임워크이다
나. 프레임워크이기 때문에 프로그램 실행흐름의 제어권을 내주고 따라가야함
다. 목적(하는일) : SQL 문장의 수행을 대신 철
라. 모든 SQL 문장은 2가지 방법으로 저장가능
- (1) Mapper XML 파일에 저장 (현업에선 XML Mapper 파일이라고 부름)
- (2) 자바 인터페이스의 추상메소드에 저장
(mybatis가 제공하는 Annotation을 추상메소드에 붙여서 그 Annotation 안에 속성으로 SQL 문장을 저장)
(여기서 사용되는 Annotation은 mybatis 라이브러리가 제공)
마. 스프링의 선행요소 기술로 배우는 이유
- 스프링 기반으로 개발하더라도, SQL 문장의 처리는 Mybatis가 하고,
스프링 + Mybatis 2개의 프레임워크를 연동하기 위함.
------------------------------------------------------------------------------------
MyBatis SQL Mapper Framework 사용하기
1. MyBatis Library Dowload
-> 우리는 메이븐을 사용하여 의존성 (즉 필요한 라이브러리들)을 관리하기 때문에
메이븐 중앙저장소에 접속해서 pom.xml에 의존성 추가해주면 끝이다.
https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.10에 접속해
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
이거 pom.xml에 복붙해주자
2. MyBatis의 설정 파일(XML 파일)을 생성
- 설정파일의 이름은 관례상 "mybatis-config.xml"이라고 지음
- 이 파일의 위치는 src/main/resources에 저장 (자원파일이기 때문)
- MyBatis가 SQL을 처리할 수 있는 실행환경을 설정파일에 설정
https://mybatis.org/mybatis-3/ko/getting-started.html을 참고하여 차근차근 해보자
<configuration>
<environments default="사용할 실행환경의 이름">
<environment id="실행환경이름1">
<transactionManager type="JDBC" /> // 트랜잭션 관리자 유형 설정 JDBC로 설정
<dataSource type="UNPOOLED"> // Connection Pool의 형태를 갖추고 있지 않다는 의미
기본적인 JDBC 연결생성을 위한 4가지 정보 설정 (url, driver, user, pass)
</dataSource>
</environment>
<environment id="실행환경이름2">
<transactionManager type="JDBC" /> // 트랜잭션 관리자 유형 JDBC로 설정
<dataSource type="POOLED"> // Connection Pool의 형태를 갖추고 있다는 의미
기본적인 JDBC 연결생성을 위한 4가지 정보 설정 (url, driver, user, pass)
Connection Pool 특성에 관여하는
</dataSource>
</environment>
</environments>
</configuration>
* 트랜잭션 관리자란 무엇인가? => 트랜잭션 관리
1) WAS 안에 이미 구현체가 있음
2) JDBC Driver 라이브러리 안에도 이미 구현체가 있음
3) Spring Framework 안에도 이미 구현체가 있음
4) 데이터베이스 인스턴스 안에도 이미 구현체가 있음
- 위의 모든 트랜젝션 관리자란 구현체는 모두 같은 표준에 따라 구현하게 되어 있음.
* 데이터 소스란 무엇인가? => Connection Pool을 제공하는 객체
- Connection Pool은 무엇인가? => JDBC Connection들이 잔뜩 들어있는 객체
- Connection Pool의 규격(OOP언어에서는 자바 인터페이스를 의미)을 가지고 있음
- 실제타입 : javax.sq.DataSource 인터페이스 (규격)
- Connection Pool은 DataSource라고 부르는 인터페이스의 구현객체
* Connection Pool의 특징
- Pool 안에 생성될 JDBC Connection 개수에 대하여 최대 몇개, 최소 몇개라는 정보를 가짐
- JDBC Connection의 필요량에 따라 Max개수만큼 늘어났다가(exteced), Min 개수만큼 줄어듬 (shrinked)
3. Mapper XML 파일에 mybatis가 수행할 SQL 문장을 만들어 저장하자 (2가지 방법)
1) Mapper XML 파일에 SQL 문장을 저장
- Mapper XML 파일명의 관례 -> 테이블명Mapper.xml ex)BoardMapper.xml
2) 자바 인터페이스의 추상메소드에 저장 ("Mapper Interface" 방식)
- MyBatis가 제공하는 Annotation을 추상메소드 위에 붙이고 이 Annotation 속성으로 SQL 문장을 저장
-> 우선 첫번째 방법으로 해보자
역시 https://mybatis.org/mybatis-3/ko/configuration.html#settings에서 차근차근 해보자
-> DOCTYPE 복붙해주고 <mapper> 태그로 만들어준다.
mapper의 namespace와 select id는 고유해야한다.
-> SQL 문장을 <select> <insert> <update> <delete> 등에 알맞게 넣어주면 된다.
우리는 간단하게 시간을 조회해보자
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 여러개의 Mapper XML 파일이 존재할 때,
각 Mapper XML 파일의 최상위 태그의 namespace 속성 값은 고유해야함 (충돌이 없어야함)
관례상 파일명과 동일하게 설정해준다. -->
<mapper namespace="BoardMapper">
<!-- 현재의 Mapper XML 파일 안에 등록되어 있는 수 많은 다른 태그들과 충돌되지 않는
고유한 식별자 값을 id 속성에 지정해야함 (현재 파일 내에서 충돌이 없어야함) -->
<select
id="getCurrentTime"
resultType="java.lang.String">
SELECT to_char(current_date, 'yyyy/MM/dd HH24:mi:ss') FROM dual
</select>
</mapper>
BoardMapper.xml
*이제 mybatis-config.xml에 mappers 태그로 BoardMapper.xml 등록해주자
<mappers>
<!-- Mapper XML 파일 등록 -->
<mapper resource="BoardMapper.xml" />
</mappers>
mybatis-config.xml
---
src/test/java에 persistence 패키지를 만들어주자 (mybatis가 영속성을 다루기 때문에)
BoardMapper.xml을 테스트하는 BoardMapperTests 클래스를 만들어주자
@Log4j2
@NoArgsConstructor
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(OrderAnnotation.class)
public class BoardMapperTests {// Mybatis Framework 객체는 바로 SqlSessionFactory 객체임
// 1) SqlSessionFactoryBuilder를 통해 생성
// 2) 이 Builder 객체를 통해 핵심객체인 SqlSessionFactory 객체를 생성하려면
// 마이바티스의 설정파일 mybatis-config.xml에 대한 InputStream 객체가 필요함
// 1. SqlSessionFactory 객체 생성
private SqlSessionFactory sqlSessionFactory;
@BeforeAll // 1회성 전처리 작업 수행
void beforeAll() throws IOException {
log.trace("beforeAll() invoked.");
// 2. 마이바티스의 설정파일(mybatis-config.xml)에 대한 입력스트림 객체 is를 생성하자
// InputStream을 얻을 경로를 필드에 저장해주자
String path = "mybatis-config.xml";
@Cleanup // 자원객체 썼으면 닫자
InputStream is = Resources.getResourceAsStream(path);
// 단 한번 SqlSessionFactory 공장객체를 생성해서, 필드에 저장
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
this.sqlSessionFactory = builder.build(is);
// 객체가 null인지 여부 검증
assert this.sqlSessionFactory != null;
log.info("\t+ this.sqlSessionFactory : {}", this.sqlSessionFactory);
} // beforeAll
@Test
@Order(1)
@DisplayName("1. testGetCurrentTime")
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void testGetCurrentTime( ) throws SQLException {
log.trace("testGetCurrentTime() invoked.");
// 얘는 밑에서 try-with-resources로 닫아보자
SqlSession sqlSession = this.sqlSessionFactory.openSession();
// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서
// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
// 어떤 태그의 SQL문장을 사용할지 결정 (by "id")
try (sqlSession) {
String namespace = "BoardMapper";
String sqlId = "getCurrentTime";
String sql = namespace + "." + sqlId; // Mapped Statement
String now = sqlSession.<String>selectOne(sql);
assert now != null;
log.info("\t+ now : {}", now);
} // try-with-resources (Auto-closeable 해야 사용가능)
} // testGetCurrentTime
BoardMapperTests.java
---
4. MyBatis의 SqlSessionFactory를 이용해서 Mapper XML 파일에 등록된 특정 SQL 문장을 수행시키는 방법
1) SqlSessionFactoryBuilder 객체를 생성
2) Bulider 객체의 build 메소드를 호출 => SqlSessionFactory 객체 생성
3) SqlSessionFactory.openSession() 메소드 호출로 하나의 SqlSession 객체를 생산
4) SqlSession.selsectOne(String mappedStatement) 메소드로 Mapper XML 파일에 등록된 특정 SQL 문장 실행
.selectList(String mappedStatement)
Mapped Statement -> namespace + "." + id = SQL 문장이 Mapping
---
3번으로 돌아가서 2번째 방법인 "Mapper Interface" 방식으로 해보자
src/main/java에 mapper 패키지 만들어주고 그 아래에 BoardMapper 인터페이스 만들어주자
// MyBatis가 수행할 SQL 문장을 저장하는 자바 인터페이스 => Mapper Interface라고 부름
public interface BoardMapper {
// MyBatis가 제공하는 Annotation의 속성에 SQL 문장을 저장
// 마이바티스의 SQL 문장에서, 바인드 변수는 #{변수명} 형식으로 기재함
// @Param (바인드 변수명) 형태로 사용해서 바인드 변수에 값 전달
@Select("SELECT * FROM tbl_board WHERE bno > #{theBno} AND title LIKE '%'||#{search}||'%'")
public abstract List<BoardVO> selectAllBoards(@Param("theBno")Integer bno, @Param("search")String title); // 여러 값이니까 List 객체이용
@Select("SELECT * FROM tbl_board WHERE bno = #{theBno}")
public abstract BoardVO selectBoard(@Param("theBno") Integer bno);
} // end interface
BoardMapper.java
------------------------------------------------
* VO패턴, DTO 패턴
1) VO패턴 = Value Object (값객체)
- 영속성 계층에 속한 테이블의 한 개의 레코드를 저장하고 수정할 수 없게 만든 객체를 찍어낼 수 있는 클래스를 만드는 설계방식
- 이 패턴을 따르는 클래스를 Value Object Class, VO 클래스라고 함
- 클래스의 필드는 테이블의 스키마와 동일하게 작성해야함
------------------------------------------------
src/main/java에 domain 패키지 만들어주고 그 아래에 BoardVO 클래스 만들어주자
//2. 직접 작성하지 말고 롬복 어노테이션으로 만들자.
//@EqualsAndHashCode // 중복판단
//@ToString
//@Getter(lombok.AccessLevel.PUBLIC) // 접근제한자를 public으로 설정해준다.
//@AllArgsConstructor
//3. 위에 것들 한번에 만들어주자. cf) DTO는 @Data
@Value
public class BoardVO {
// 0) 테이블의 컬럼의 순서와 필드의 순서를 동일하게 작성한다.
// 1) VO class의 필드는 외부에서 접근하지 못하게 private으로 한다.
// 2) 테이블의 컬럼은 NULL (결측치)가 있을 수 있기 때문에 기본 타입인 int가 아니고
// 결측치를 표현할 수 있는 Integer로 필드를 선언해야한다.
// 3) INSERT_TS, UPDATE_TS는 한번 작성하면 건드리면 안 되기에 만들지 말자
// private Integer BNO;
// private String TITLE;
// private String CONTENT;
// private String WRITER;
// 자바 식별자 규칙에 따르면 파이널 상수만이 모두 대문자로 표기하기 때문에 소문자로 바꿔주자
private Integer bno;
private String title;
private String content;
private String writer;
// 1. 생성자를 직접 작성해보자
// public BoardVO(Integer BNO, String TITLE, String CONTENT, String WRITER) {
// this.BNO = BNO;
// this.TITLE = TITLE;
// this.CONTENT = CONTENT;
// this.WRITER = WRITER;
// } // constructor
//
// 2. 수정불가능(Immutable)한 읽기 전용 객체여야 하기 때문에 (테이블의 값을 조회하는데 값을 수정하면 안 되잖슴)
// Setter는 말고 Getter만 만들어준다 (상단탭-source-generate getters and setters)
// public Integer getBNO() {
// return BNO;
// } // getBNO
//
// public String getTITLE() {
// return TITLE;
// } // getTITLE
//
// public String getCONTENT() {
// return CONTENT;
// } // getCONTENT
//
// public String getWRITER() {
// return WRITER;
// } // getWRITER
} // end class
BoardVO.java
BoardMapperTests에 테스트 메소드를 추가해주자
@Log4j2
@NoArgsConstructor
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(OrderAnnotation.class)
public class BoardMapperTests {// Mybatis Framework 객체는 바로 SqlSessionFactory 객체임
// 1) SqlSessionFactoryBuilder를 통해 생성
// 2) 이 Builder 객체를 통해 핵심객체인 SqlSessionFactory 객체를 생성하려면
// 마이바티스의 설정파일 mybatis-config.xml에 대한 InputStream 객체가 필요함
// 1. SqlSessionFactory 객체 생성
private SqlSessionFactory sqlSessionFactory;
@BeforeAll // 1회성 전처리 작업 수행
void beforeAll() throws IOException {
log.trace("beforeAll() invoked.");
// 2. 마이바티스의 설정파일(mybatis-config.xml)에 대한 입력스트림 객체 is를 생성하자
// InputStream을 얻을 경로를 필드에 저장해주자
String path = "mybatis-config.xml";
@Cleanup // 자원객체 썼으면 닫자
InputStream is = Resources.getResourceAsStream(path);
// 단 한번 SqlSessionFactory 공장객체를 생성해서, 필드에 저장
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
this.sqlSessionFactory = builder.build(is);
// 객체가 null인지 여부 검증
assert this.sqlSessionFactory != null;
log.info("\t+ this.sqlSessionFactory : {}", this.sqlSessionFactory);
} // beforeAll
@Test
@Order(1)
@DisplayName("1. testGetCurrentTime")
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void testGetCurrentTime( ) throws SQLException {
log.trace("testGetCurrentTime() invoked.");
// 얘는 밑에서 try-with-resources로 닫아보자
SqlSession sqlSession = this.sqlSessionFactory.openSession();
// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서
// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
// 어떤 태그의 SQL문장을 사용할지 결정 (by "id")
try (sqlSession) {
String namespace = "BoardMapper";
String sqlId = "getCurrentTime";
String sql = namespace + "." + sqlId; // Mapped Statement
String now = sqlSession.<String>selectOne(sql);
assert now != null;
log.info("\t+ now : {}", now);
} // try-with-resources (Auto-closeable 해야 사용가능)
} // testGetCurrentTime
@Test
@Order(2)
@DisplayName("2. testSelectAllBoards")
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void testSelectAllBoards(){
log.trace("testSelectAllBoards() invoked.");
@Cleanup // 이번에는 Cleanup으로 닫자
SqlSession sqlSession = this.sqlSessionFactory.openSession();
// Mapper Interface 방식은 이렇게 한다!
BoardMapper mapper = sqlSession.<BoardMapper>getMapper(BoardMapper.class);
assertNotNull(mapper);
log.info("\t+ mapper : {}", mapper);
List<BoardVO> list = mapper.selectAllBoards(200, "6");
Objects.requireNonNull(list);
list.forEach(log::info);
} // testSelectAllBoards
@Test
@Order(3)
@DisplayName("3. testSelectBoard")
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void testSelectBoard(){
log.trace("testSelectBoard() invoked.");
@Cleanup // 이번에는 Cleanup으로 닫자
SqlSession sqlSession = this.sqlSessionFactory.openSession();
// Mapper Interface 방식은 이렇게 한다!
// 지정한 Mapper Interface의 구현객체인 MapperProxy 획득
BoardMapper mapper = sqlSession.<BoardMapper>getMapper(BoardMapper.class);
assertNotNull(mapper);
log.info("\t+ mapper : {}", mapper);
BoardVO board = mapper.selectBoard(199);
Objects.requireNonNull(board);
log.info("\t+ board : {}", board);
} // testSelectBoard
@Test
@Order(4)
@DisplayName("4. testSelectAllBoardsByMapperXML")
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void testSelectAllBoardsByMapperXML( ) throws SQLException {
log.trace("testGetCurrentTime() invoked.");
// 얘는 밑에서 try-with-resources로 닫아보자
SqlSession sqlSession = this.sqlSessionFactory.openSession();
// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서
// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
// 어떤 태그의 SQL문장을 사용할지 결정 (by "id")
try (sqlSession) {
String namespace = "BoardMapper";
String sqlId = "selectAllBoards";
String sql = namespace + "." + sqlId; // Mapped Statement
Map<String, Object> params = new HashMap<>();
params.put("theBno", 200);
params.put("search", "7");
List<BoardVO> list = sqlSession.<BoardVO>selectList(sql, params);
assert list != null;
list.forEach(log::info);
} // try-with-resources (Auto-closeable 해야 사용가능)
} // testGetCurrentTime
} // end class
BoardMapperTests.java
'국비학원' 카테고리의 다른 글
| [국비지원] KH 정보교육원 92-94일차 (0) | 2022.08.11 |
|---|---|
| [국비지원] KH 정보교육원 91일차 (0) | 2022.08.11 |
| [국비지원] KH 정보교육원 88일차 (0) | 2022.08.02 |
| [국비지원] KH 정보교육원 86일차 (0) | 2022.07.29 |
| [국비지원] KH 정보교육원 85일차 (0) | 2022.07.28 |