2022-03-14(월)

Jeongyun Heo·2022년 3월 14일
0

Persistence Framework 도입

90-MyList프로젝트2 / 15 페이지

Persistence
데이터 유지와 관련된 일

Framework
프로그램의 실행 흐름을 미리 만들어 놓은 것
실제 작업을 수행할 컴포넌트를 개발자가 작성

라이브러리 → 도구
프레임워크는 도구를 엮어서 특정 목적을 달성할 수 있도록 도구를 엮어 놓은 것

🔹 SQL Mapper
DAO와 SQL을 연결 ---> DBMS 작업 수행

DAO --- 연결 --- SQL 파일 ---> DBMS
               개발자의 몫

🔹 OR Mapper
DAO와 API를 엮고 API는 SQL과 연결됨
SQL 자동생성

DAO --- 연결 --- API + 전용 QL --- 연결 --- SQL ---> DBMS
                  개발자의 몫             자동생성

SQL 자동생성
=> 개발자가 특정 DBMS를 고민할 필요 없다!

SQL Mapper - MyBatis

90-MyList프로젝트2 / 16 페이지

https://www.egovframe.go.kr/home/main.do

Persistent layer

① 기존 DB 프로그래밍 방식
DAO 안에 JDBC 코드와 SQL문이 들어 있다.

DAO = JDBC 코드 + SQL 코드

유사한 코드가 반복되고 있음

② 개선된 방식
JDBC 코드와 SQL 코드를 분리시킨다
DAO --- call ---> Mybatis --- 읽고 실행 ---> SQL

DAO - JDBC 코드 → MyBatis
DAO - SQL 코드 → SQL 파일

🔹 MyBatis : 반복적으로 작성하는 JDBC 코드를 쓰기 쉽게 캡슐화 (클래스와 메서드로 포장)

SQL 코드를 작성하기 쉽게
읽고 이해하기 쉽게
별도의 파일(XML)로 분리한다

DAO ---call---> Mybatis ---읽고 실행---> SQL
DAO가 Mybatis에 있는 걸 호출한다
Mybatis가 SQL을 읽어서 실행한다

MyBatis를 사용한다는 것은
개발자가 DAO, SQL을 작성한다.

이 구조로 바꿔보자!
실무에 가면 당연히 이렇게 쓴다

JDBC 코드를 캡슐화한 Mybatis 퍼시스턴스 프레임워크 사용하기

https://search.maven.org/artifact/org.mybatis/mybatis/3.5.9/jar

https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

https://github.com/mybatis

https://mybatis.org/mybatis-3/

https://github.com/mybatis/spring

App ---call---> MyBatis 라이브러리 ---call---> JDBC Driver ---> DBMS
내부적으로 JDBC Driver call

MyBatis 라이브러리를 통해서 간접적으로 call

MyBatis 라이브러리 ← JDBC Driver 사용하는 코드를 캡슐화 시킨 거

MyBatis 라이브러리: JDBC 프로그래밍 코드를 캡슐화 (클래스와 메서드로 포장)

App + Spring 프레임워크 ---call---> MyBatis 라이브러리
Spring 프레임워크에서 MyBatis 라이브러리에 있는 객체를 관리할 수 있도록 어댑터가 있어야 되는데
MyBatis-Spring 어댑터
adapt (새로운 용도·상황에) 맞추다[조정하다]
Spring 프레임워크는 MyBatis를 직접 관리할 수 없음
Spring 규칙에 따라서 만든 게 아님
둘 사이를 연결시켜주는 클래스가 MyBatis-Spring 어댑터
Spring 프레임워크 ---call---> MyBatis-Spring 어댑터 ---call---> MyBatis 라이브러리
물론 실제 DBMS에 접속하는 건 JDBC Driver 라는 건 변함없음
iBatis, MyBatis, Hibernate, Spring Data JPA 모두 JDBC 프로그래밍 코드를 캡슐화한 거

디자인 패턴에도 adapter 패턴 있음

하나의 메서드로 포장해버림

MyBatis만 있으면 다이렉트로 DBMS에 붙는다고 착각하는 경우가 있음

관리가 가능하도록 중간에 어댑터 클래스가 필요

③ Spring Boot 안에 App이 존재
Spring Boot가 App을 포함한다는 느낌이 강함

MyBatis 라이브러리를 관리하기 위해서 중간에 mybatis-spring-boot-starter

Spring Boot ---설정---> mybatis-spring-boot-starter

https://github.com/mybatis/spring-boot-starter

spring-boot-starter

http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

https://search.maven.org/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.2.2/jar

https://search.maven.org/artifact/org.mybatis/mybatis-spring/2.0.7/jar

build.gradle 파일에 추가

  // Mybatis 프레임워크 (직접 구성)
  // implementation 'org.mybatis:mybatis:3.5.9'
  // implementation 'org.mybatis:mybatis-spring:2.0.7'
  
  // Mybatis 프레임워크 (스프링 스타터로 간접 구성) 
  implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'

gradle cleanEclipse
gradle eclipse
리프레시 하고 Referenced Libraries 확인하기

Mybatis 설정 추가하기

DAO 구현체에 MyBatis 프레임워크를 적용한다.

com.eomcs.mylist.dao.mariadb.BoardDaoImpl 클래스 변경

SQL 코드를 뜯어내어 XML 파일로 옮긴다.

xml 검색

BoardDao.xml 만들기

https://mybatis.org/mybatis-3/getting-started.html

<mapper namespace="BoardDao">
BoardDao에서 뜯어낸 SQL임을 알려준다

BoardDaoImpl에서 findAll()에 있는 SQL문을 뜯어낸다.

select 해서 가져온 컬럼 값을 resultType="com.eomcs.mylist.domain.Board" 객체에 담아라

<?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="BoardDao">

  <select id="sql1" resultType="com.eomcs.mylist.domain.Board">
    select 
      board_no,
      title,
      created_date,
      view_count 
    from 
      ml_board 
    order by 
      board_no desc
  </select>
  
</mapper>

MyBatis를 호출하는 코드를 DAO에 작성해야 한다

BoardDaoImpl.java

먼저 의존 객체 SqlSessionFactory 주입받아야 됨
SqlSessionFactory ← SqlSession을 만들어주는 공장
SqlSession ← SQL을 실행시켜주는 도구

  @Autowired
  SqlSessionFactory sqlSessionFactory; // => Mybatis: SQL 실행 도구를 만들어 주는 객체
SqlSession sqlSession = sqlSessionFactory.openSession(); // SQL을 실행시켜주는 도구를 준비

sqlSession.selectList(namespace.sql_id)
sqlSession.selectList("BoardDao.sql1")

return selectList의 리턴값

  @Override
  public List<Board> findAll() {
    try (SqlSession sqlSessio n = sqlSessionFactory.openSession();) { // SQL을 실행시켜주는 도구를 준비
      return sqlSession.selectList("BoardDao.sql1");
    }
  }

findAll()에 JDBC 코드가 없음
진짜 없습니까?
selectList() 안에 들어 있음 (캡슐화)
우리가 직접 JDBC 코드를 짤 필요가 없으므로 편리하다

절대 '90-MyList프로젝트2 / 17 페이지' 그림을 잊어서는 안 됨
MyBatis 라이브러리에 있는 function을 호출한다고 해서 JDBC를 건너뛰고 가는 게 아님
MyBatis 라이브러리가 내부적으로 JDBC Driver에 있는 Connection, PreparedStatement, executeQuery() 이런 것들을 직접 호출한다
JDBC Driver가 사라지는 게 아님
JDBC Driver가 없으면 아예 동작이 안 됨

App.java

mylist-boot/app/src/main/java/com/eomcs/mylist/App.java

SqlSessionFactory ← 인터페이스
SqlSessionFactoryBean ← SqlSessionFactory 인터페이스를 구현한 구현체

mybatis setmapperlocations 검색

설정이 누락된 게 있으면 에러 뜰 거임

  // Mybatis 객체 준비
  @Bean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

    // 1) SQL을 실행할 때 사용할 DB 커넥션풀을 주입한다.
    sqlSessionFactoryBean.setDataSource(dataSource); // DB 커넥션 풀을 주입한다.

    // 2) SQL 문이 들어 있는 파일의 위치를 설정한다.
    PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("classpath:com/eomcs/mylist/dao/*.xml"));

    return sqlSessionFactoryBean.getObject();
  }

localhost:8080/board/list

No가 다 0으로 나옴

MyBatis SELECT

컬럼 이름과 필드 이름이 일치향
컬럼명과 같은 이름을 가진 필드에 값을 담는다

컬럼명과 필드 이름이 일치해야 한다
왜? Mybatis에서 컬럼값을 자바객체에 담을 때
컬럼명과 같은 이름을 가진 필드를 찾는다.

리턴 받은 게 title 밖에 없어서 그런 거

레코드 컬럼과 객체 필드 이름이 일치해야 꽂아준다

SQL에서 select 한 컬럼에 대해 객체 필드에 담는다

<?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="BoardDao">

  <select id="sql1" resultType="com.eomcs.mylist.domain.Board">
    select 
      board_no as no,
      title,
      created_date as createDate,
      view_count as viewCount
    from 
      ml_board 
    order by 
      board_no desc
  </select>
  
</mapper>

<mapper namespace="BoardDao">

  <select id="sql2" resultType="com.eomcs.mylist.domain.Board" parameterType="int">
    select 
      board_no as no,
      title,
      content,
      created_date as createdDate,
      view_count as viewCount
    from 
      ml_board 
    where 
      board_no=#{no}
  </select>

</mapper>

Mybatis select + 파라미터

sqlSession.selectOne(namespace.id, parameter 값)
sqlSession.selectOne("BoardDao.sql2", 2)

파라미터 값을 가리키는 이름
파라미터 타입이 primitive type, String일 경우
아무 이름을 적어도 된다

  @Override
  public Board findByNo(int no) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      return sqlSession.selectOne("BoardDao.sql2", no);
    }
  }

  @Override
  public Board findByNo(int no) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      System.out.println(sqlSession.getClass().getName());
      return sqlSession.selectOne("BoardDao.sql2", no);
    }
  }

org.apache.ibatis.session.defaults.DefaultSqlSession

디버그 모드로 추적...

결과를 한 개를 리턴하는 건 selectOne()

  @Override
  public int insert(Board board) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession();) { // SQL을 실행시켜주는 도구를 준비
      return sqlSession.insert("BoardDao.sql3", board);
    }
  }

몇 개 insert 했는지 개수를 리턴

정확하게 패키지명까지 다 적어주기
parameterType="com.eomcs.mylist.domain.Board"

  <insert id="sql3" parameterType="com.eomcs.mylist.domain.Board">
    insert into ml_board(title,content) 
    values(?,?)
  </insert>

Mybatis - insert + 파라미터

sqlSession.insert("BoardDao.sql3", board)

Board 클래스
no
title
content
viewCount
createdDate

insert into ml_board(title,content) ← 컬럼명
values(#{title},#{content}) ← 객체 필드명

  <insert id="sql3" parameterType="com.eomcs.mylist.domain.Board">
    insert into ml_board(title,content) 
    values(#{title},#{content})
  </insert>

http://localhost:8080/board/add?title=aaa3&content=bbb3

BoardDao.xml ← sqlmapper 파일

  <insert id="sql4" parameterType="com.eomcs.mylist.domain.Board">
    update ml_board set 
      title=#{title}, 
      content=#{content} 
    where 
      board_no=#{no}
  </insert>

insert 태그 안에 update문이 있다고 당황하지 말기
가독성 측면에서 update 태그로 써주기

  @Override
  public int update(Board board) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession();) { // SQL을 실행시켜주는 도구를 준비
      return sqlSession.insert("BoardDao.sql4", board);
    }
  }

insert 태그 안에 update문이 있다고 당황하지 말기
그래도 가독성 측면에서 update 태그로 써주기

  <update id="sql4" parameterType="com.eomcs.mylist.domain.Board">
    update ml_board set 
      title=#{title}, 
      content=#{content} 
    where 
      board_no=#{no}
  </update>
  @Override
  public int update(Board board) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession();) { // SQL을 실행시켜주는 도구를 준비
      return sqlSession.update("BoardDao.sql4", board);
    }
  }
  <select id="sql5" resultType="int">
    select count(*) from ml_board
  </select>
  <delete id="sql6" parameterType="int">
    delete from ml_board 
    where board_no=#{no}
  </delete>

  <update id="sql7" parameterType="int">
    update ml_board set
      view_count=view_count + 1
    where board_no=#{no}
  </update>

자바 소스 코드에 SQL문이 있으면 관리하기 힘들다

DAO 구현체를 자동으로 생성한다.

Mybatis에서 DAO 구현체를 자동으로 생성한다.
DAO 구현체가 사용할 SQL Mapper 파일의 위치는 인터페이스의 패키지 경로와 일치해야 한다.
예) com/eomcs/mylist/dao/BoardDao.xml
인터페이스의 메서드가 호출될 때 사용할 SQL은 ID는 메서드 이름과 일치해야 한다.
예) <select id="countAll">...</select>

resultType이 없다

Spring Boot + Mybatis

BoardDao(인터페이스), BoardDao.xmal(SQL mapper 파일) 만 만들면 된다

http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

mybatis.type-aliases-package=com.example.domain.model

Book 데이터를 저장할 테이블을 생성한다.

create table ml_book(
  book_no int not null,
  title varchar(255) not null,
  author varchar(100) not null,
  press varchar(100) not null,
  feed text not null,
  read_date date,
  page int,
  price int
);

alter table ml_book
  add constraint primary key (book_no),
  modify column book_no int not null auto_increment;

PK나 유니크 컬럼에만 auto_increment 설정할 수 있다.

com.eomcs.mylist.domain.Book 클래스 변경
primary key를 저장할 no 추가한다

여기서 오타 있는지 확인하기

parameterType="Book" // "book"도 상관없음
대소문자 상관없음

    update ml_book set 
      title='ohora', 
      author='heo',
      press='nono',
      feed='good',
      read_date='2022-2-1',
      page=200,
      price=20000
    where 
      book_no=1;

    delete from ml_book 
    where book_no=1;

http://localhost:8080/book/list

0개의 댓글