Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 70일차(15주차2일) - JDBC: myapp-39~42중간(커넥션 풀, PreparedStatement, Mybatis) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/DBMS, SQL, JDBC, Servlet

[비트캠프] 70일차(15주차2일) - JDBC: myapp-39~42중간(커넥션 풀, PreparedStatement, Mybatis)

끈기JK 2023. 2. 14. 13:16

 

39. 멀티스레드와 DB 커넥션 툴

 

Local 의 C1, C2, C3 에서 Application Server 접속시 Thread 생성하여 Handler 와 대응한다. Handler 에서 DAO 로 Connection 객체로 통신하고 여기서 DBMS Table 의 Thread 와 통신한다. Thread 는 Table 과 I/O 한다.

 

 

① 이전 방식

C1 이 insert, update 한다. 그러면 DBMS Thread 의 임시DB 에 insert, update 한다.

C3 가 delete, delete 한다. 그러면 DBMS Thread 의 임시DB 에 delete, delete 한다.

C2 가 update, delete 한다. 그러다 실행 중 오류 발생하고 rollback 요청한다.

C2 가 rollback 을 요청하면 Thread가 보관한 임시 작업 결과를 모두 취소한다! 문제는 C1 과 C3 의 작업 결과도 취소된다는 것이다!

왜? DB 커넥션을 공유하기 때문이다.

 

☆결론

멀티 스레드 환경에서 DB Connection 을 공유하지 말라!

 

 

 

멀티 스레드가 DB 커넥션을 공유할 때 발생하는 문제점 경험하기

 

C1 에서 학생 입력한다. Application Server 에 Thread 생성되고 member insert, student insert 한다. DBMS Thread 의 임시DB 에 명령이 입력된다. 그 후 30초간 정지된다.

C2 에서 강사 입력한다. Applicatoin Server 에 Thread 생성되고 member insert, teacher insert 하는데 DBMS Thread 의 임시 DB 에 명령이 입력된다. 그러다 오류 발생해서 rollback 된다.

C1 에서 30초가 지나 commit 한다. 그러나 C2 에서 rollback 했기 때문에 commit 된 것은 없다. → 커넥션을 공유하기 때문

 

 

 

Thread 별로 Connection 관리

 

ConnectionFactory 에 ThreadLocal<Connection> conLocal 선언한다. getConnection() 메서드 생성한다.

DAO 에서 ConnectionFactory 객체 포함한다. insert(), update(), ... 으로 getConnection() ① call 하면 con 객체 ② return 한다. 이를 ③ 사용 한다.

 

 

 

스레드 별로 Connection 관리

 

C1 에서 학생 입력한다. Application Server 에 Thread 생성되고 여기서 ConnectionFactory 에게 getConnection() 으로 Con 요청해서 return 받는다. insert, insert 하면 DBMS Thread 의 임시DB 에 insert, insert 명령 결과가 저장된다.

C2 에서 강사 입력한다. Application Server 에 Thread 생성되고 여기서 ConnectionFactory 에게 getConnection() 으로 Con 요청해서 return 받는다. insert, insert 중 오류 발생하면 DBMS Thread 의 임시DB insert 된 것 rollback 으로 취소한다.

C1, C2 의 Con 다르기 때문에 C2 의 rollback 이 C1 에 영향 끼치지 않는다.

 

 

39. 멀티 스레드와 DB 커넥션 관리
- Thread를 이용하여 다중 클라이언트 요청을 동시에 처리하는 방법
- 멀티 스레드 환경에서 DB 커넥션을 다루는 방법

 

ConnectionFactory.java 파일 생성해서 util 패키지에 넣는다.

현재 스레드의 ThreadLocal 에 con 객체 있으면 리턴하고 없으면 생성해서 리턴한다.

public class ConnectionFactory {
  String jdbcUrl;
  String username;
  String password;

  // 스레드 전용 변수
  // => 스레드 마다 별도로 존재하는 변수다.
  // => Connection 객체를 담는 변수다.
  ThreadLocal<Connection> conLocal = new ThreadLocal<>();

  public ConnectionFactory(String jdbcUrl, String username, String password) {
    this.jdbcUrl = jdbcUrl;
    this.username = username;
    this.password = password;
  }

  public Connection getConnection() throws Exception {
    // 현재 이 명령을 실행하는 스레드에 마련된 conLocal 변수에서 값을 꺼낸다.
    Connection con = conLocal.get();

    if (con == null) {
      // 현재 스레드 변수에 값이 Connection 객체가 들어 있지 않다면 새로 만든다.
      con = DriverManager.getConnection(jdbcUrl, username, password);

      // 현재 스레드가 다음에도 사용할 수 있도록 Connection 객체를 스레드 변수에 보관한다.
      conLocal.set(con);

      System.out.printf("[%s] Connection 객체 생성!\n", Thread.currentThread().getName());
    }

    System.out.printf("[%s] Connection 객체 리턴!\n", Thread.currentThread().getName());
    // 현재 스레드를 위해 준비한 Connection 객체를 리턴한다.
    return con;
  }
}

 

ConnectionFactory 객체 생성한다. ----DaoImpl, ----Handler 에 주입한다.

execute() 에서 serverSocket().accept() 이 후 Thread 생성해서 다중 사용자와 대응한다.

public class ServerApp {

  ConnectionFactory conFactory = new ConnectionFactory(
      "jdbc:mariadb://localhost:3306/studydb", "study", "1111");

  StudentHandler studentHandler;
  TeacherHandler teacherHandler;
  BoardHandler boardHandler;
  HelloHandler helloHandler = new HelloHandler();

  public static void main(String[] args) {
    try {
      new ServerApp().execute(8888);
    } catch (Exception e) {
      System.out.println("서버 실행 오류!");
      e.printStackTrace();
    }
  }

  public ServerApp() throws Exception{

    BoardDaoImpl boardDao = new BoardDaoImpl(conFactory);
    MemberDaoImpl memberDao = new MemberDaoImpl(conFactory);
    StudentDaoImpl studentDao = new StudentDaoImpl(conFactory);
    TeacherDaoImpl teacherDao = new TeacherDaoImpl(conFactory);

    this.studentHandler = new StudentHandler("학생", conFactory, memberDao, studentDao);
    this.teacherHandler = new TeacherHandler("강사", conFactory, memberDao, teacherDao);
    this.boardHandler = new BoardHandler("게시판", boardDao);
  }

  void execute(int port) {
    try (ServerSocket serverSocket = new ServerSocket(port)) {
      System.out.println("서버 실행 중...");

      while (true) {
        Socket socket = serverSocket.accept();
        new Thread(() -> service(socket)).start();
      }

    } catch (Exception e) {
      System.out.println("서버 소켓 오류!");
      e.printStackTrace();
    }
  }

  private void hello(StreamTool streamTool) throws Exception {
    streamTool.println("비트캠프 관리 시스템")
    .println("  Copyright by 네이버클라우드1기")
    .println("--------------------------------------")
    .println("안녕하세요!")
    .println()
    .send();
  }

  private void processRequest(StreamTool streamTool) throws Exception {

    loop: while (true) {
      String command = streamTool.readString();

      if (command.equals("menu")) {
        menu(streamTool);
        continue;
      }

      int menuNo;
      try {
        menuNo = Integer.parseInt(command);
      } catch (Exception e) {
        streamTool.println("메뉴 번호가 옳지 않습니다!").println().send();
        continue;
      }

      try {
        switch (menuNo) {
          case 1:
            studentHandler.service(streamTool);
            break;
          case 2:
            teacherHandler.service(streamTool);
            break;
          case 3:
            boardHandler.service(streamTool);
            break;
          case 4:
            helloHandler.service(streamTool);
            break;
          case 9:
            break loop; // loop 라벨이 붙은 while 문을 나간다.
          default:
            streamTool.println("잘못된 메뉴 번호 입니다.").send();
        }



      } catch (Exception e) {
        streamTool.printf("명령 실행 중 오류 발생! - %s : %s\n",
            e.getMessage(),
            e.getClass().getSimpleName()).send();
      }
    }

    // while 종료하면 클라이언트와 연결을 끊는다.
    streamTool.print("quit").send();

  }

  void menu(StreamTool streamTool) throws Exception {
    streamTool.println("1. 학생관리")
    .println("2. 강사관리")
    .println("3. 게시판")
    .println("4. 인사")
    .println("9. 종료")
    .println("메뉴 번호:")
    .send();
  }

  public void service(Socket clientSocket) {
    // 스레드가 실행할 코드를 둔다.
    try (Socket socket = clientSocket;
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream())) {

      // 입출력 보조 도구 준비
      StreamTool streamTool = new StreamTool(in, out);

      String clientIP = socket.getInetAddress().getHostAddress();
      System.out.printf("접속: %s\n", clientIP);

      hello(streamTool);
      processRequest(streamTool);

      System.out.printf("끊기: %s\n", clientIP);

    } catch (Exception e) {
      System.out.println("클라이언트 요청 처리 오류!");
      e.printStackTrace();
    }
  }
}

 

----DaoImpl 생성자에 ConnectionFactory 객체 주입한다.

conFactory.getConnection() 으로 con 객체 가져온다.

public class BoardDaoImpl implements BoardDao {

  ConnectionFactory conFactory;

  public BoardDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Board b) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("insert into app_board(title, content, pwd) values('%s', '%s', '%s')",
          b.getTitle(), b.getContent(), b.getPassword());

      stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Board> findAll() {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery(
            "select board_id, title, created_date, view_cnt from app_board order by board_id desc")) {

      ArrayList<Board> list = new ArrayList<>();
      while (rs.next()) {
        Board b = new Board();
        b.setNo(rs.getInt("board_id"));
        b.setTitle(rs.getString("title"));
        b.setCreatedDate(rs.getString("created_date"));
        b.setViewCount(rs.getInt("view_cnt"));

        list.add(b);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Board findByNo(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery(
            "select board_id, title, content, pwd, created_date, view_cnt from app_board where board_id=" + no)) {

      if (rs.next()) {
        Board b = new Board();
        b.setNo(rs.getInt("board_id"));
        b.setTitle(rs.getString("title"));
        b.setContent(rs.getString("content"));
        b.setPassword(rs.getString("pwd"));
        b.setCreatedDate(rs.getString("created_date"));
        b.setViewCount(rs.getInt("view_cnt"));
        return b;
      }

      return null;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public void increaseViewCount(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format(
          "update app_board set"
              + " view_cnt = view_cnt + 1"
              + " where board_id=%d",
              no);

      stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Board> findByKeyword(String keyword) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery(
            "select board_id, title, created_date, view_cnt"
                + " from app_board"
                + " where title like('%" + keyword + "%')"
                + " or content like('%" + keyword + "%')"
                + " order by board_id desc")) {

      ArrayList<Board> list = new ArrayList<>();
      while (rs.next()) {
        Board b = new Board();
        b.setNo(rs.getInt("board_id"));
        b.setTitle(rs.getString("title"));
        b.setCreatedDate(rs.getString("created_date"));
        b.setViewCount(rs.getInt("view_cnt"));

        list.add(b);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Board b) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("update app_board set title='%s', content='%s' where board_id=%d",
          b.getTitle(), b.getContent(), b.getNo());

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("delete from app_board where board_id=%d", no);

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

 

public class MemberDaoImpl implements MemberDao {

  ConnectionFactory conFactory;

  public MemberDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Member m) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format(
          "insert into app_member(name, email, pwd, tel)"
              + " values('%s', '%s', sha2('%s',256), '%s')",
              m.getName(),
              m.getEmail(),
              m.getPassword(),
              m.getTel());

      stmt.executeUpdate(
          sql,
          Statement.RETURN_GENERATED_KEYS // insert 실행 후 자동 증가된 PK 값을 리턴 받겠다고 선언한다.
          );

      // 1) insert 후에 자동 생성된 PK 값을 꺼내올 도구를 준비한다.
      try (ResultSet keyRs = stmt.getGeneratedKeys()) {

        // 2) keyRs 도구를 이용하여 서버에서 PK 값을 가져온다.
        keyRs.next();

        // 3) 가져온 PK 레코드에서 PK 값을 꺼낸다.
        int autoGeneratedMemberId = keyRs.getInt(1); // PK 컬럼에 한 개이기 때문에 PK 레코드에도 한 개의 컬럼만 있다.

        // 4) DBMS에서 insert 할 때 자동 생성한 member_id 값을 Member 객체에 리턴한다.
        m.setNo(autoGeneratedMemberId);
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Member> findAll() {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery(
            "select member_id, name, email, created_date"
                + " from app_member"
                + " order by member_id desc")) {

      ArrayList<Member> list = new ArrayList<>();
      while (rs.next()) {
        Member m = new Member();
        m.setNo(rs.getInt("member_id"));
        m.setName(rs.getString("name"));
        m.setEmail(rs.getString("email"));
        m.setCreatedDate(rs.getDate("created_date"));

        list.add(m);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Member findByNo(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery(
            "select member_id, name, email, tel, created_date"
                + " from app_member"
                + " where member_id=" + no)) {

      if (rs.next()) {
        Member m = new Member();
        m.setNo(rs.getInt("member_id"));
        m.setName(rs.getString("name"));
        m.setEmail(rs.getString("email"));
        m.setTel(rs.getString("tel"));
        m.setCreatedDate(rs.getDate("created_date"));
        return m;
      }
      return null;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Member m) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format(
          "update app_member set "
              + " name='%s', email='%s', pwd=sha2('%s',256), tel='%s'"
              + " where member_id=%d",
              m.getName(),
              m.getEmail(),
              m.getPassword(),
              m.getTel(),
              m.getNo());

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format(
          "delete from app_member"
              + " where member_id=%d", no);

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

public class StudentDaoImpl implements StudentDao {

  ConnectionFactory conFactory;

  public StudentDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Student s) {

    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("insert into app_student("
          + "  member_id,"
          + "  pst_no,"
          + "  bas_addr,"
          + "  det_addr,"
          + "  work,"
          + "  gender,"
          + "  level)"
          + " values('%s','%s','%s','%s',%b,'%s',%d)",
          s.getNo(), // app_member 테이블에 입력한 후 자동 생성된 PK 값
          s.getPostNo(),
          s.getBasicAddress(),
          s.getDetailAddress(),
          s.isWorking(),
          s.getGender(),
          s.getLevel());

      stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Student> findAll() {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery("select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  s.work,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " order by"
            + "   m.name asc")) {

      ArrayList<Student> list = new ArrayList<>();
      while (rs.next()) {
        Student s = new Student();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setEmail(rs.getString("email"));
        s.setTel(rs.getString("tel"));
        s.setWorking(rs.getBoolean("work"));
        s.setLevel(rs.getByte("level"));

        list.add(s);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Student findByNo(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery("select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  m.created_date,"
            + "  s.pst_no,"
            + "  s.bas_addr,"
            + "  s.det_addr,"
            + "  s.work,"
            + "  s.gender,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " where s.member_id=" + no)) {

      if (rs.next()) {
        Student s = new Student();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setEmail(rs.getString("email"));
        s.setTel(rs.getString("tel"));
        s.setCreatedDate(rs.getDate("created_date"));
        s.setPostNo(rs.getString("pst_no"));
        s.setBasicAddress(rs.getString("bas_addr"));
        s.setDetailAddress(rs.getString("det_addr"));
        s.setWorking(rs.getBoolean("work"));
        s.setGender(rs.getString("gender").charAt(0));
        s.setLevel(rs.getByte("level"));
        return s;
      }

      return null;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Student> findByKeyword(String keyword) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery("select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  s.work,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " where"
            + "   m.name like('%" + keyword + "%')"
            + "   or m.email like('%" + keyword + "%')"
            + "   or m.tel like('%" + keyword + "%')"
            + "   or s.bas_addr like('%" + keyword + "%')"
            + "   or s.det_addr like('%" + keyword + "%')"
            + " order by"
            + "   m.member_id desc")) {

      ArrayList<Student> list = new ArrayList<>();
      while (rs.next()) {
        Student s = new Student();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setEmail(rs.getString("email"));
        s.setTel(rs.getString("tel"));
        s.setWorking(rs.getBoolean("work"));
        s.setLevel(rs.getByte("level"));

        list.add(s);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Student s) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("update app_student set "
          + "  pst_no='%s',"
          + "  bas_addr='%s',"
          + "  det_addr='%s',"
          + "  work=%b,"
          + "  gender='%s',"
          + "  level=%d "
          + " where member_id=%d",
          s.getPostNo(),
          s.getBasicAddress(),
          s.getDetailAddress(),
          s.isWorking(),
          s.getGender(),
          s.getLevel(),
          s.getNo());

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("delete from app_student"
          + " where member_id=%d", no);
      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

public class TeacherDaoImpl implements TeacherDao {

  ConnectionFactory conFactory;

  public TeacherDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Teacher s) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("insert into app_teacher("
          + " member_id,"
          + " degree,"
          + " school,"
          + " major,"
          + " wage)"
          + " values(%d, %d,'%s','%s',%d)",
          s.getNo(),
          s.getDegree(),
          s.getSchool(),
          s.getMajor(),
          s.getWage());

      stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Teacher> findAll() {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery("select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.tel,"
            + "  t.degree,"
            + "  t.major,"
            + "  t.wage"
            + " from app_teacher t"
            + "   inner join app_member m on t.member_id = m.member_id"
            + " order by"
            + "   m.name asc")) {

      ArrayList<Teacher> list = new ArrayList<>();
      while (rs.next()) {
        Teacher s = new Teacher();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setTel(rs.getString("tel"));
        s.setDegree(rs.getInt("degree"));
        s.setMajor(rs.getString("major"));
        s.setWage(rs.getInt("wage"));

        list.add(s);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Teacher findByNo(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement();
        ResultSet rs = stmt.executeQuery("select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  m.created_date,"
            + "  t.degree,"
            + "  t.school,"
            + "  t.major,"
            + "  t.wage"
            + " from app_teacher t"
            + "   inner join app_member m on t.member_id = m.member_id"
            + " where m.member_id=" + no)) {

      if (rs.next()) {
        Teacher s = new Teacher();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setEmail(rs.getString("email"));
        s.setTel(rs.getString("tel"));
        s.setCreatedDate(rs.getDate("created_date"));
        s.setDegree(rs.getInt("degree"));
        s.setSchool(rs.getString("school"));
        s.setMajor(rs.getString("major"));
        s.setWage(rs.getInt("wage"));
        return s;
      }

      return null;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Teacher t) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format(
          "update app_teacher set "
              + " degree=%d,"
              + " school='%s',"
              + " major='%s',"
              + " wage=%d "
              + " where member_id=%d",
              t.getDegree(),
              t.getSchool(),
              t.getMajor(),
              t.getWage(),
              t.getNo());

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (Statement stmt = conFactory.getConnection().createStatement()) {

      String sql = String.format("delete from app_teacher"
          + " where member_id=%d", no);

      return stmt.executeUpdate(sql);

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

}

 

----Handler에서 input, modify, deleteMember 할때 현재 스레드의 Connection 객체를 리턴 받는다.

public class StudentHandler {

  private ConnectionFactory conFactory;
  private MemberDao memberDao;
  private StudentDao studentDao;
  private String title;

  public StudentHandler(String title, ConnectionFactory conFactory, MemberDao memberDao, StudentDao studentDao) {
    this.title = title;
    this.conFactory = conFactory;
    this.memberDao = memberDao;
    this.studentDao = studentDao;
  }

  private void inputMember(StreamTool streamTool) throws Exception {
    Student s = new Student();
    s.setName(streamTool.promptString("이름? "));
    s.setEmail(streamTool.promptString("이메일? "));
    s.setPassword(streamTool.promptString("암호? "));
    s.setTel(streamTool.promptString("전화? "));
    s.setPostNo(streamTool.promptString("우편번호? "));
    s.setBasicAddress(streamTool.promptString("주소1? "));
    s.setDetailAddress(streamTool.promptString("주소2? "));
    s.setWorking(streamTool.promptInt("0. 미취업\n1. 재직중\n재직자? ") == 1);
    s.setGender(streamTool.promptInt("0. 남자\n1. 여자\n성별? ") == 0 ? 'M' : 'W');
    s.setLevel((byte) streamTool.promptInt("0. 비전공자\n1. 준전공자\n2. 전공자\n전공? "));

    // 현재 스레드가 갖고 있는 Connection 객체를 리턴 받는다.
    Connection con = conFactory.getConnection();
    con.setAutoCommit(false);
    try {
      memberDao.insert(s);
      studentDao.insert(s);
      con.commit();
      streamTool.println("입력했습니다!").send();

    } catch (Exception e) {
      con.rollback();
      streamTool.println("입력 실패입니다!").send();
      e.printStackTrace();

    } finally {
      con.setAutoCommit(true);
    }

  }

  private void printMembers(StreamTool streamTool) throws Exception {
    List<Student> members = this.studentDao.findAll();
    streamTool.println("번호\t이름\t전화\t재직\t전공");
    for (Student m : members) {
      streamTool.printf("%d\t%s\t%s\t%s\t%s\n",
          m.getNo(), m.getName(), m.getTel(),
          m.isWorking() ? "예" : "아니오",
              getLevelText(m.getLevel()));
    }
    streamTool.send();
  }

  private void printMember(StreamTool streamTool) throws Exception {
    int memberNo = streamTool.promptInt("회원번호? ");

    Student m = this.studentDao.findByNo(memberNo);

    if (m == null) {
      streamTool.println("해당 번호의 학생이 없습니다.").send();
      return;
    }

    streamTool
    .printf("    이름: %s\n", m.getName())
    .printf("    전화: %s\n", m.getTel())
    .printf("우편번호: %s\n", m.getPostNo())
    .printf("기본주소: %s\n", m.getBasicAddress())
    .printf("상세주소: %s\n", m.getDetailAddress())
    .printf("재직여부: %s\n", m.isWorking() ? "예" : "아니오")
    .printf("    성별: %s\n", m.getGender() == 'M' ? "남자" : "여자")
    .printf("    전공: %s\n", getLevelText(m.getLevel()))
    .printf("  등록일: %s\n", m.getCreatedDate())
    .send();

  }

  // 인스턴스 멤버(필드나 메서드)를 사용하지 않기 때문에
  // 그냥 스태틱 메서드로 두어라!
  private static String getLevelText(int level) {
    switch (level) {
      case 0: return "비전공자";
      case 1: return "준전공자";
      default: return "전공자";
    }
  }

  private void modifyMember(StreamTool streamTool) throws Exception {
    int memberNo = streamTool.promptInt("회원번호? ");

    Student old = this.studentDao.findByNo(memberNo);

    if (old == null) {
      streamTool.println("해당 번호의 회원이 없습니다.").send();
      return;
    }

    // 변경할 데이터를 저장할 인스턴스 준비
    Student m = new Student();
    m.setNo(old.getNo());
    m.setCreatedDate(old.getCreatedDate());
    m.setName(streamTool.promptString(String.format("이름(%s)? ", old.getName())));
    m.setEmail(streamTool.promptString(String.format("이메일(%s)? ", old.getEmail())));
    m.setPassword(streamTool.promptString("암호? "));
    m.setTel(streamTool.promptString(String.format("전화(%s)? ", old.getTel())));
    m.setPostNo(streamTool.promptString(String.format("우편번호(%s)? ", old.getPostNo())));
    m.setBasicAddress(streamTool.promptString(String.format("기본주소(%s)? ", old.getBasicAddress())));
    m.setDetailAddress(streamTool.promptString(String.format("상세주소(%s)? ", old.getDetailAddress())));
    m.setWorking(streamTool.promptInt(String.format(
        "0. 미취업\n1. 재직중\n재직여부(%s)? ",
        old.isWorking() ? "재직중" : "미취업")) == 1);
    m.setGender(streamTool.promptInt(String.format(
        "0. 남자\n1. 여자\n성별(%s)? ",
        old.getGender() == 'M' ? "남자" : "여자")) == 0 ? 'M' : 'W');
    m.setLevel((byte) streamTool.promptInt(String.format(
        "0. 비전공자\n1. 준전공자\n2. 전공자\n전공(%s)? ",
        getLevelText(old.getLevel()))));

    String str = streamTool.promptString("정말 변경하시겠습니까?(y/N) ");
    if (str.equalsIgnoreCase("Y")) {
      // 현재 스레드가 갖고 있는 Connection 객체를 리턴 받는다.
      Connection con = conFactory.getConnection();
      con.setAutoCommit(false);
      try {
        memberDao.update(m);
        studentDao.update(m);
        con.commit();
        streamTool.println("변경했습니다.");

      } catch (Exception e) {
        con.rollback();
        streamTool.println("변경 실패했습니다!");
        e.printStackTrace();

      } finally {
        con.setAutoCommit(true);
      }
    } else {
      streamTool.println("변경 취소했습니다.");
    }
    streamTool.send();
  }

  private void deleteMember(StreamTool streamTool) throws Exception {
    int memberNo = streamTool.promptInt("회원번호? ");

    Student m = this.studentDao.findByNo(memberNo);

    if (m == null) {
      streamTool.println("해당 번호의 회원이 없습니다.").send();
      return;
    }

    String str = streamTool.promptString("정말 삭제하시겠습니까?(y/N) ");
    if (!str.equalsIgnoreCase("Y")) {
      streamTool.println("삭제 취소했습니다.").send();
      return;
    }

    // 현재 스레드가 갖고 있는 Connection 객체를 리턴 받는다.
    Connection con = conFactory.getConnection();
    con.setAutoCommit(false);
    try {
      studentDao.delete(memberNo);
      memberDao.delete(memberNo);
      con.commit();
      streamTool.println("삭제했습니다.").send();

    } catch (Exception e) {
      con.rollback();
      streamTool.println("삭제 실패했습니다.").send();

    } finally {
      con.setAutoCommit(true);
    }
  }

  private void searchMember(StreamTool streamTool) throws Exception {
    String keyword = streamTool.promptString("검색어? ");

    List<Student> students = this.studentDao.findByKeyword(keyword);

    streamTool.println("번호\t이름\t전화\t재직\t전공");
    for (Student m : students) {
      streamTool.printf("%d\t%s\t%s\t%s\t%s\n",
          m.getNo(), m.getName(), m.getTel(),
          m.isWorking() ? "예" : "아니오",
              getLevelText(m.getLevel()));
    }
    streamTool.send();
  }

  public void service(StreamTool streamTool) throws Exception {

    menu(streamTool);

    while (true) {
      String command = streamTool.readString();

      if (command.equals("menu")) {
        menu(streamTool);
        continue;
      }

      int menuNo;
      try {
        menuNo = Integer.parseInt(command);
      } catch (Exception e) {
        streamTool.println("메뉴 번호가 옳지 않습니다!").println().send();
        continue;
      }

      try {
        switch (menuNo) {
          case 0:
            streamTool.println("메인화면으로 이동!").send();
            return;
          case 1: this.inputMember(streamTool); break;
          case 2: this.printMembers(streamTool); break;
          case 3: this.printMember(streamTool); break;
          case 4: this.modifyMember(streamTool); break;
          case 5: this.deleteMember(streamTool); break;
          case 6: this.searchMember(streamTool); break;
          default:
            streamTool.println("잘못된 메뉴 번호 입니다.").send();
        }
      } catch (Exception e) {
        streamTool.printf("명령 실행 중 오류 발생! - %s : %s\n",
            e.getMessage(),
            e.getClass().getSimpleName()).send();
      }
    }
  }

  void menu(StreamTool streamTool) throws Exception {
    streamTool.printf("[%s]\n", this.title)
    .println("1. 등록")
    .println("2. 목록")
    .println("3. 조회")
    .println("4. 변경")
    .println("5. 삭제")
    .println("6. 검색")
    .println("0. 이전")
    .send();
  }
}

 

public class TeacherHandler {

  private ConnectionFactory conFactory;
  private MemberDao memberDao;
  private TeacherDao teacherDao;
  private String title;

  public TeacherHandler(String title, ConnectionFactory conFactory, MemberDao memberDao, TeacherDao teacherDao) {
    this.title = title;
    this.conFactory = conFactory;
    this.memberDao = memberDao;
    this.teacherDao = teacherDao;
  }

  private void inputTeacher(StreamTool streamTool) throws Exception {
    Teacher m = new Teacher();
    m.setName(streamTool.promptString("이름? "));
    m.setEmail(streamTool.promptString("이메일? "));
    m.setPassword(streamTool.promptString("암호? "));
    m.setTel(streamTool.promptString("전화? "));
    m.setDegree(streamTool.promptInt("1. 고졸\n2. 전문학사\n3. 학사\n4. 석사\n5. 박사\n0. 기타\n학위? "));
    m.setSchool(streamTool.promptString("학교? "));
    m.setMajor(streamTool.promptString("전공? "));
    m.setWage(streamTool.promptInt("강의료(시급)? "));

    // 현재 스레드에 보관된 Connection 객체를 리턴 받는다.
    Connection con = conFactory.getConnection();
    con.setAutoCommit(false);
    try {
      memberDao.insert(m);
      teacherDao.insert(m);
      con.commit();
      streamTool.println("입력했습니다!").send();

    } catch (Exception e) {
      con.rollback();
      streamTool.println("입력 실패했습니다!").send();
      e.printStackTrace();

    } finally {
      con.setAutoCommit(true);
    }
  }

  private void printTeachers(StreamTool streamTool) throws Exception {

    List<Teacher> teachers = this.teacherDao.findAll();

    streamTool.println("번호\t이름\t전화\t학위\t전공\t시강료");
    for (Teacher m : teachers) {
      streamTool.printf("%d\t%s\t%s\t%s\t%s\t%d\n",
          m.getNo(), m.getName(), m.getTel(),
          getDegreeText(m.getDegree()), m.getMajor(), m.getWage());
    }
    streamTool.send();
  }

  private void printTeacher(StreamTool streamTool) throws Exception {
    int teacherNo = streamTool.promptInt("강사번호? ");

    Teacher m = this.teacherDao.findByNo(teacherNo);
    if (m == null) {
      streamTool.println("해당 번호의 강사가 없습니다.").send();
      return;
    }

    streamTool.printf("    이름: %s\n", m.getName())
    .printf("  이메일: %s\n", m.getEmail())
    .printf("    전화: %s\n", m.getTel())
    .printf("    학위: %s\n", getDegreeText(m.getDegree()))
    .printf("    학교: %s\n", m.getSchool())
    .printf("    전공: %s\n", m.getMajor())
    .printf("  강의료: %s\n", m.getWage())
    .printf("  등록일: %s\n", m.getCreatedDate())
    .send();
  }

  private static String getDegreeText(int degree) {
    switch (degree) {
      case 1: return "고졸";
      case 2: return "전문학사";
      case 3: return "학사";
      case 4: return "석사";
      case 5: return "박사";
      default: return "기타";
    }
  }

  private void modifyTeacher(StreamTool streamTool) throws Exception {
    int teacherNo = streamTool.promptInt("강사번호? ");

    Teacher old = this.teacherDao.findByNo(teacherNo);
    if (old == null) {
      streamTool.println("해당 번호의 강사가 없습니다.");
      return;
    }

    // 변경할 데이터를 저장할 인스턴스 준비
    Teacher m = new Teacher();
    m.setNo(old.getNo());
    m.setCreatedDate(old.getCreatedDate());
    m.setName(streamTool.promptString(String.format("이름(%s)? ", old.getName())));
    m.setEmail(streamTool.promptString(String.format("이메일(%s)? ", old.getEmail())));
    m.setPassword(streamTool.promptString("암호? "));
    m.setTel(streamTool.promptString(String.format("전화(%s)? ", old.getTel())));
    m.setDegree(streamTool.promptInt(String.format(
        "1. 고졸\n2. 전문학사\n3. 학사\n4. 석사\n5. 박사\n0. 기타\n학위(%s)? ",
        getDegreeText(old.getDegree()))));
    m.setSchool(streamTool.promptString(String.format("학교(%s)? ", old.getSchool())));
    m.setMajor(streamTool.promptString(String.format("전공(%s)? ", old.getMajor())));
    m.setWage(streamTool.promptInt(String.format("강의료(시급)(%s)? ", old.getWage())));

    String str = streamTool.promptString("정말 변경하시겠습니까?(y/N) ");
    if (str.equalsIgnoreCase("Y")) {
      // 현재 스레드에 보관된 Connection 객체를 리턴 받는다.
      Connection con = conFactory.getConnection();
      con.setAutoCommit(false);
      try {
        memberDao.update(m);
        teacherDao.update(m);
        con.commit();
        streamTool.println("변경했습니다.");

      } catch (Exception e) {
        con.rollback();
        streamTool.println("변경 실패했습니다.");
        e.printStackTrace();

      } finally {
        con.setAutoCommit(true);
      }
    } else {
      streamTool.println("변경 취소했습니다.");
    }
    streamTool.send();

  }

  private void deleteTeacher(StreamTool streamTool) throws Exception {
    int teacherNo = streamTool.promptInt("강사번호? ");

    Teacher m = this.teacherDao.findByNo(teacherNo);
    if (m == null) {
      streamTool.println("해당 번호의 강사가 없습니다.").send();
      return;
    }

    String str = streamTool.promptString("정말 삭제하시겠습니까?(y/N) ");
    if (!str.equalsIgnoreCase("Y")) {
      streamTool.println("삭제 취소했습니다.").send();
      return;
    }

    // 현재 스레드에 보관된 Connection 객체를 리턴 받는다.
    Connection con = conFactory.getConnection();
    con.setAutoCommit(false);
    try {
      teacherDao.delete(teacherNo);
      memberDao.delete(teacherNo);
      streamTool.println("삭제했습니다.").send();

    } catch (Exception e) {
      con.rollback();
      streamTool.println("삭제 실패했습니다.").send();
      e.printStackTrace();

    } finally {
      con.setAutoCommit(true);
    }
  }

  public void service(StreamTool streamTool) throws Exception {
    menu(streamTool);

    while (true) {

      String command = streamTool.readString();
      if (command.equals("menu")) {
        menu(streamTool);
        continue;
      }

      int menuNo;
      try {
        menuNo = Integer.parseInt(command);
      } catch (Exception e) {
        streamTool.println("메뉴 번호가 옳지 않습니다!").println().send();
        continue;
      }

      try {
        switch (menuNo) {
          case 0:
            streamTool.println("메인화면으로 이동!").send();
            return;
          case 1: this.inputTeacher(streamTool); break;
          case 2: this.printTeachers(streamTool); break;
          case 3: this.printTeacher(streamTool); break;
          case 4: this.modifyTeacher(streamTool); break;
          case 5: this.deleteTeacher(streamTool); break;
          default:
            streamTool.println("잘못된 메뉴 번호 입니다.").send();
        }
      } catch (Exception e) {
        streamTool.printf("명령 실행 중 오류 발생! - %s : %s\n",
            e.getMessage(),
            e.getClass().getSimpleName()).send();
      }
    }
  }

  void menu(StreamTool streamTool) throws Exception {
    streamTool.printf("[%s]\n", this.title)
    .println("1. 등록")
    .println("2. 목록")
    .println("3. 조회")
    .println("4. 변경")
    .println("5. 삭제")
    .println("0. 이전")
    .send();
  }
}

 

 

 

40. DB 커넥션 풀 도입

 

C1 에서 ConnectionPool 에 ① getConnection() 한다. Connection con1 을 ② 생성해서 주면 C1 에서 ③ 사용한다. con1 사용 후 ④ returnConnection() 해서 ConnectionPool 에 반납하면 여기서 List 에 ⑤ 보관한다.

C2 에서 ConnectionPool 에 ① getConnection() 한다. ConnectionPool 의 List 에 보관된 것(con1)이 있으면 ② 꺼낸다. ③ 기존 객체 리턴 (con1)하면 C2 가 ④ 사용한다. 사용 후 ⑤ returnConnection() 하면 ConnectionPool 객체가 List 에 ⑥ 보관한다.

 

☆Pooling 기법

① 객체 생성 시간이 많이 소요되는 경우

② 객체가 무거운 경우(메모리 많이 사용)

생성 → 사용 → 가비지 ↔ 보관, 꺼내기

 

 

 

ConnectionPool의 사용

 

C1 에서 ConnectionFactory 객체 통해 getConnection() 한다. 여기서 ConnectionPool 객체 통해 getConnection() 한다. 커넥션 없으면 커넥션 생성하고 있으면 기존의 커넥션 리턴 한다.

con 사용 후 ① C1 에서 ConnectionFactory 객체 통해 closeConnection() 하면 여기서 ConnectionPool 객체 통해 ② returnConnection() 한다. ConnectionPool 객체에서 List 에 ③ 보관 한다.

 

→ ConnectionFactory, ConnectionPool 을 "DataSource" 라 한다.

 

 

### 40. DB 커넥션 풀을 이용한 Connetion 재사용
  - 풀링기법(pooling)을 이용하여 Connection 객체를 재사용하는 방법
    - GoF의 'Flyweight' 디자인 패턴

 

public class ConnectionPool {

  String jdbcUrl;
  String username;
  String password;

  List<Connection> conList = new ArrayList<>();

  public ConnectionPool(String jdbcUrl, String username, String password) {
    this.jdbcUrl = jdbcUrl;
    this.username = username;
    this.password = password;
  }

  public Connection getConnection() throws Exception {
    if (conList.size() == 0) {
      System.out.println("Connection 생성!");
      return DriverManager.getConnection(jdbcUrl, username, password);
    }
    return conList.remove(0);
  }

  public void returnConnection(Connection con) {
    conList.add(con);
  }
}

 

public class ConnectionFactory {

  ConnectionPool connectionPool;
  ThreadLocal<Connection> conLocal = new ThreadLocal<>();

  public ConnectionFactory(ConnectionPool connectionPool) {
    this.connectionPool = connectionPool;
  }

  public Connection getConnection() throws Exception {
    // 현재 이 명령을 실행하는 스레드에 마련된 conLocal 변수에서 값을 꺼낸다.
    Connection con = conLocal.get();

    if (con == null) {
      // 현재 스레드 변수에 Connection 객체가 들어 있지 않다면 커넥션풀에서 가져온다.
      con = connectionPool.getConnection(); // 새로 커넥션을 생성하거나 기존 커넥션을 리턴한다.

      // 현재 스레드가 다음에도 사용할 수 있도록 Connection 객체를 스레드 변수에 보관한다.
      conLocal.set(con);
    }

    System.out.printf("[%s] Connection 객체 리턴!\n", Thread.currentThread().getName());
    // 현재 스레드를 위해 준비한 Connection 객체를 리턴한다.
    return con;
  }

  public void closeConnection() {
    // 현재 스레드가 사용한 커넥션 객체를 꺼내 ConnectionPool에 반납한다.
    Connection con = conLocal.get();
    if (con != null) {
      connectionPool.returnConnection(con);
      System.out.printf("[%s] 커넥션 객체 반납했습니다.\n", Thread.currentThread().getName());
    }
  }
}

 

public class ServerApp {

  ConnectionPool connectionPool = new ConnectionPool(
      "jdbc:mariadb://localhost:3306/studydb", "study", "1111");
  ConnectionFactory conFactory = new ConnectionFactory(connectionPool);

  StudentHandler studentHandler;
  TeacherHandler teacherHandler;
  BoardHandler boardHandler;
  HelloHandler helloHandler = new HelloHandler();

  public static void main(String[] args) {
    try {
      new ServerApp().execute(8888);
    } catch (Exception e) {
      System.out.println("서버 실행 오류!");
      e.printStackTrace();
    }
  }

  public ServerApp() throws Exception{

    BoardDaoImpl boardDao = new BoardDaoImpl(conFactory);
    MemberDaoImpl memberDao = new MemberDaoImpl(conFactory);
    StudentDaoImpl studentDao = new StudentDaoImpl(conFactory);
    TeacherDaoImpl teacherDao = new TeacherDaoImpl(conFactory);

    this.studentHandler = new StudentHandler("학생", conFactory, memberDao, studentDao);
    this.teacherHandler = new TeacherHandler("강사", conFactory, memberDao, teacherDao);
    this.boardHandler = new BoardHandler("게시판", boardDao);
  }

  void execute(int port) {
    try (ServerSocket serverSocket = new ServerSocket(port)) {
      System.out.println("서버 실행 중...");

      while (true) {
        Socket socket = serverSocket.accept();
        new Thread(() -> service(socket)).start();
      }

    } catch (Exception e) {
      System.out.println("서버 소켓 오류!");
      e.printStackTrace();
    }
  }

  private void hello(StreamTool streamTool) throws Exception {
    streamTool.println("비트캠프 관리 시스템")
    .println("  Copyright by 네이버클라우드1기")
    .println("--------------------------------------")
    .println("안녕하세요!")
    .println()
    .send();
  }

  private void processRequest(StreamTool streamTool) throws Exception {

    loop: while (true) {
      String command = streamTool.readString();

      if (command.equals("menu")) {
        menu(streamTool);
        continue;
      }

      int menuNo;
      try {
        menuNo = Integer.parseInt(command);
      } catch (Exception e) {
        streamTool.println("메뉴 번호가 옳지 않습니다!").println().send();
        continue;
      }

      try {
        switch (menuNo) {
          case 1:
            studentHandler.service(streamTool);
            break;
          case 2:
            teacherHandler.service(streamTool);
            break;
          case 3:
            boardHandler.service(streamTool);
            break;
          case 4:
            helloHandler.service(streamTool);
            break;
          case 9:
            break loop; // loop 라벨이 붙은 while 문을 나간다.
          default:
            streamTool.println("잘못된 메뉴 번호 입니다.").send();
        }



      } catch (Exception e) {
        streamTool.printf("명령 실행 중 오류 발생! - %s : %s\n",
            e.getMessage(),
            e.getClass().getSimpleName()).send();
      }
    }

    // while 종료하면 클라이언트와 연결을 끊는다.
    streamTool.print("quit").send();

  }

  void menu(StreamTool streamTool) throws Exception {
    streamTool.println("1. 학생관리")
    .println("2. 강사관리")
    .println("3. 게시판")
    .println("4. 인사")
    .println("9. 종료")
    .println("메뉴 번호:")
    .send();
  }

  public void service(Socket clientSocket) {
    // 스레드가 실행할 코드를 둔다.
    try (Socket socket = clientSocket;
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream())) {

      // 입출력 보조 도구 준비
      StreamTool streamTool = new StreamTool(in, out);

      String clientIP = socket.getInetAddress().getHostAddress();
      System.out.printf("접속: %s\n", clientIP);

      hello(streamTool);
      processRequest(streamTool);

      System.out.printf("끊기: %s\n", clientIP);

    } catch (Exception e) {
      System.out.println("클라이언트 요청 처리 오류!");
      e.printStackTrace();

    } finally {
      // 현재 스레드가 갖고 있는 커넥션 객체를 ConnectionPool에 반납시킨다.
      conFactory.closeConnection();
    }
  }
}

 

 

 

41. PreparedStatement를 이용한 SQL 삽입 공격 막기

 

### 41. PreparedStatement를 이용한 SQL 삽입 공격 막기
  - SQL 삽입 공격 체험
  - SQL 삽입 공격을 막는 방법

 

PreparedStatement 로 SQL 문을 미리 준비해 놓는다.

public class BoardDaoImpl implements BoardDao {

  ConnectionFactory conFactory;

  public BoardDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Board b) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "insert into app_board(title, content, pwd) values(?, ?, ?)")) {

      stmt.setString(1, b.getTitle());
      stmt.setString(2, b.getContent());
      stmt.setString(3, b.getPassword());

      stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Board> findAll() {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select board_id, title, created_date, view_cnt from app_board order by board_id desc");
        ResultSet rs = stmt.executeQuery()) {

      ArrayList<Board> list = new ArrayList<>();
      while (rs.next()) {
        Board b = new Board();
        b.setNo(rs.getInt("board_id"));
        b.setTitle(rs.getString("title"));
        b.setCreatedDate(rs.getString("created_date"));
        b.setViewCount(rs.getInt("view_cnt"));

        list.add(b);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Board findByNo(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select board_id, title, content, pwd, created_date, view_cnt from app_board where board_id=?")) {

      stmt.setInt(1, no);

      try (ResultSet rs = stmt.executeQuery()) {
        if (rs.next()) {
          Board b = new Board();
          b.setNo(rs.getInt("board_id"));
          b.setTitle(rs.getString("title"));
          b.setContent(rs.getString("content"));
          b.setPassword(rs.getString("pwd"));
          b.setCreatedDate(rs.getString("created_date"));
          b.setViewCount(rs.getInt("view_cnt"));
          return b;
        }
        return null;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public void increaseViewCount(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "update app_board set"
            + " view_cnt = view_cnt + 1"
            + " where board_id=?")) {

      stmt.setInt(1, no);
      stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Board> findByKeyword(String keyword) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select board_id, title, created_date, view_cnt"
            + " from app_board"
            + " where title like(?)"
            + " or content like(?)"
            + " order by board_id desc")) {

      stmt.setString(1, "%" + keyword + "%");
      stmt.setString(2, "%" + keyword + "%");

      try (ResultSet rs = stmt.executeQuery()) {
        ArrayList<Board> list = new ArrayList<>();
        while (rs.next()) {
          Board b = new Board();
          b.setNo(rs.getInt("board_id"));
          b.setTitle(rs.getString("title"));
          b.setCreatedDate(rs.getString("created_date"));
          b.setViewCount(rs.getInt("view_cnt"));

          list.add(b);
        }
        return list;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Board b) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "update app_board set title=?, content=? where board_id=?")) {

      // 미리 준비한 SQL에 값만 별도록 설정한다.
      // 이때 문자열 안에 들어 있는 '(작은 따옴표)는 일반 문자로 간주한다.
      // 따라서 SQL 삽입 공격이 불가능 하다!
      //
      stmt.setString(1, b.getTitle());
      stmt.setString(2, b.getContent());
      stmt.setInt(3, b.getNo());

      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "delete from app_board where board_id=?")) {

      stmt.setInt(1, no);
      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

public class MemberDaoImpl implements MemberDao {

  ConnectionFactory conFactory;

  public MemberDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Member m) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "insert into app_member(name, email, pwd, tel)"
            + " values(?, ?, sha2(?,256), ?)", Statement.RETURN_GENERATED_KEYS)) {

      stmt.setString(1, m.getName());
      stmt.setString(2, m.getEmail());
      stmt.setString(3, m.getPassword());
      stmt.setString(4, m.getTel());

      stmt.executeUpdate();

      try (ResultSet keyRs = stmt.getGeneratedKeys()) {
        keyRs.next();
        int autoGeneratedMemberId = keyRs.getInt(1);
        m.setNo(autoGeneratedMemberId);
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Member> findAll() {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select member_id, name, email, created_date"
            + " from app_member"
            + " order by member_id desc");
        ResultSet rs = stmt.executeQuery()) {

      ArrayList<Member> list = new ArrayList<>();
      while (rs.next()) {
        Member m = new Member();
        m.setNo(rs.getInt("member_id"));
        m.setName(rs.getString("name"));
        m.setEmail(rs.getString("email"));
        m.setCreatedDate(rs.getDate("created_date"));

        list.add(m);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Member findByNo(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select member_id, name, email, tel, created_date"
            + " from app_member"
            + " where member_id=?")) {

      stmt.setInt(1, no);

      try (ResultSet rs = stmt.executeQuery()) {
        if (rs.next()) {
          Member m = new Member();
          m.setNo(rs.getInt("member_id"));
          m.setName(rs.getString("name"));
          m.setEmail(rs.getString("email"));
          m.setTel(rs.getString("tel"));
          m.setCreatedDate(rs.getDate("created_date"));
          return m;
        }
        return null;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Member m) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "update app_member set "
            + " name=?, email=?, pwd=sha2(?,256), tel=?"
            + " where member_id=?")) {

      stmt.setString(1, m.getName());
      stmt.setString(2, m.getEmail());
      stmt.setString(3, m.getPassword());
      stmt.setString(4, m.getTel());
      stmt.setInt(5, m.getNo());

      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "delete from app_member"
            + " where member_id=?")) {

      stmt.setInt(1, no);
      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

public class StudentDaoImpl implements StudentDao {

  ConnectionFactory conFactory;

  public StudentDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Student s) {

    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "insert into app_student("
            + "  member_id,"
            + "  pst_no,"
            + "  bas_addr,"
            + "  det_addr,"
            + "  work,"
            + "  gender,"
            + "  level)"
            + " values(?,?,?,?,?,?,?)")) {

      stmt.setInt(1, s.getNo());
      stmt.setString(2, s.getPostNo());
      stmt.setString(3, s.getBasicAddress());
      stmt.setString(4, s.getDetailAddress());
      stmt.setBoolean(5, s.isWorking());
      stmt.setString(6, String.valueOf(s.getGender()));
      stmt.setInt(7, s.getLevel());

      stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Student> findAll() {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  s.work,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " order by"
            + "   m.name asc");
        ResultSet rs = stmt.executeQuery()) {

      ArrayList<Student> list = new ArrayList<>();
      while (rs.next()) {
        Student s = new Student();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setEmail(rs.getString("email"));
        s.setTel(rs.getString("tel"));
        s.setWorking(rs.getBoolean("work"));
        s.setLevel(rs.getByte("level"));

        list.add(s);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Student findByNo(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  m.created_date,"
            + "  s.pst_no,"
            + "  s.bas_addr,"
            + "  s.det_addr,"
            + "  s.work,"
            + "  s.gender,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " where s.member_id=?")) {

      stmt.setInt(1, no);

      try (ResultSet rs = stmt.executeQuery()) {
        if (rs.next()) {
          Student s = new Student();
          s.setNo(rs.getInt("member_id"));
          s.setName(rs.getString("name"));
          s.setEmail(rs.getString("email"));
          s.setTel(rs.getString("tel"));
          s.setCreatedDate(rs.getDate("created_date"));
          s.setPostNo(rs.getString("pst_no"));
          s.setBasicAddress(rs.getString("bas_addr"));
          s.setDetailAddress(rs.getString("det_addr"));
          s.setWorking(rs.getBoolean("work"));
          s.setGender(rs.getString("gender").charAt(0));
          s.setLevel(rs.getByte("level"));
          return s;
        }
        return null;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Student> findByKeyword(String keyword) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  s.work,"
            + "  s.level"
            + " from app_student s"
            + "   inner join app_member m on s.member_id = m.member_id"
            + " where"
            + "   m.name like(?)"
            + "   or m.email like(?)"
            + "   or m.tel like(?)"
            + "   or s.bas_addr like(?)"
            + "   or s.det_addr like(?)"
            + " order by"
            + "   m.member_id desc")) {

      stmt.setString(1, "%" + keyword + "%");
      stmt.setString(2, "%" + keyword + "%");
      stmt.setString(3, "%" + keyword + "%");
      stmt.setString(4, "%" + keyword + "%");
      stmt.setString(5, "%" + keyword + "%");

      try (ResultSet rs = stmt.executeQuery()) {
        ArrayList<Student> list = new ArrayList<>();
        while (rs.next()) {
          Student s = new Student();
          s.setNo(rs.getInt("member_id"));
          s.setName(rs.getString("name"));
          s.setEmail(rs.getString("email"));
          s.setTel(rs.getString("tel"));
          s.setWorking(rs.getBoolean("work"));
          s.setLevel(rs.getByte("level"));

          list.add(s);
        }
        return list;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Student s) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "update app_student set "
            + "  pst_no=?,"
            + "  bas_addr=?,"
            + "  det_addr=?,"
            + "  work=?,"
            + "  gender=?,"
            + "  level=? "
            + " where member_id=?")) {


      stmt.setString(1, s.getPostNo());
      stmt.setString(2, s.getBasicAddress());
      stmt.setString(3, s.getDetailAddress());
      stmt.setBoolean(4, s.isWorking());
      stmt.setString(5, String.valueOf(s.getGender()));
      stmt.setInt(6, s.getLevel());
      stmt.setInt(7, s.getNo());

      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "delete from app_student"
            + " where member_id=?")) {

      stmt.setInt(1, no);
      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }
}

 

public class TeacherDaoImpl implements TeacherDao {

  ConnectionFactory conFactory;

  public TeacherDaoImpl(ConnectionFactory conFactory) {
    this.conFactory = conFactory;
  }

  @Override
  public void insert(Teacher s) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "insert into app_teacher("
            + " member_id,"
            + " degree,"
            + " school,"
            + " major,"
            + " wage)"
            + " values(?, ?, ?, ?, ?)")) {

      stmt.setInt(1, s.getNo());
      stmt.setInt(2,  s.getDegree());
      stmt.setString(3,  s.getSchool());
      stmt.setString(4,  s.getMajor());
      stmt.setInt(5,  s.getWage());

      stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public List<Teacher> findAll() {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.tel,"
            + "  t.degree,"
            + "  t.major,"
            + "  t.wage"
            + " from app_teacher t"
            + "   inner join app_member m on t.member_id = m.member_id"
            + " order by"
            + "   m.name asc");
        ResultSet rs = stmt.executeQuery()) {

      ArrayList<Teacher> list = new ArrayList<>();
      while (rs.next()) {
        Teacher s = new Teacher();
        s.setNo(rs.getInt("member_id"));
        s.setName(rs.getString("name"));
        s.setTel(rs.getString("tel"));
        s.setDegree(rs.getInt("degree"));
        s.setMajor(rs.getString("major"));
        s.setWage(rs.getInt("wage"));

        list.add(s);
      }
      return list;

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public Teacher findByNo(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "select"
            + "  m.member_id,"
            + "  m.name,"
            + "  m.email,"
            + "  m.tel,"
            + "  m.created_date,"
            + "  t.degree,"
            + "  t.school,"
            + "  t.major,"
            + "  t.wage"
            + " from app_teacher t"
            + "   inner join app_member m on t.member_id = m.member_id"
            + " where m.member_id=?")) {

      stmt.setInt(1, no);

      try (ResultSet rs = stmt.executeQuery()) {
        if (rs.next()) {
          Teacher s = new Teacher();
          s.setNo(rs.getInt("member_id"));
          s.setName(rs.getString("name"));
          s.setEmail(rs.getString("email"));
          s.setTel(rs.getString("tel"));
          s.setCreatedDate(rs.getDate("created_date"));
          s.setDegree(rs.getInt("degree"));
          s.setSchool(rs.getString("school"));
          s.setMajor(rs.getString("major"));
          s.setWage(rs.getInt("wage"));
          return s;
        }
        return null;
      }

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int update(Teacher t) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "update app_teacher set "
            + " degree=?,"
            + " school=?,"
            + " major=?,"
            + " wage=?"
            + " where member_id=?")) {

      stmt.setInt(1, t.getDegree());
      stmt.setString(2, t.getSchool());
      stmt.setString(3, t.getMajor());
      stmt.setInt(4, t.getWage());
      stmt.setInt(5, t.getNo());

      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

  @Override
  public int delete(int no) {
    try (PreparedStatement stmt = conFactory.getConnection().prepareStatement(
        "delete from app_teacher"
            + " where member_id=?")) {

      stmt.setInt(1, no);
      return stmt.executeUpdate();

    } catch (Exception e) {
      throw new DaoException(e);
    }
  }

}

 

 

 

Mybatis API 사용 준비

 

 

아래 사이트에서 xml 소스 코드 가져온다.

mybatis.org > Github Project > mybatis-3

 

 

myapp-server에 mybatis 라이브러리 추가

app-server > build.gradle 에 아래 코드 추가한다.

dependencies {
    
    // Mybatis SQL Mapper 라이브러리
    implementation 'org.mybatis:mybatis:3.5.11'

 

https://central.sonatype.com/ > org.mybatis 검색해서 Gradle (short) 복사한 것이다.

 

명령창에 $ gradle eclipse 하면 다운로드 한다.

Referenced Libraries 에 mybatis-3.5.11.jar 추가된 것 확인된다.

 

 

mybatis.org 에서 github 으로 가서 mybatis.github.io/mybatis-3/ 에서 아래 페이지 접근 가능하다.

 

 

 

42. Mybatis SQL 매퍼 사용하기 

 

### 42. Mybatis SQL 매퍼 사용하기 
- Mybatis SQL 매퍼를 사용하여 DAO를 구현하는 방법

 

ServerApp.java 에 아래 처럼 입력한다.

public class ServerApp {

  ConnectionPool connectionPool = new ConnectionPool(
      "jdbc:mariadb://localhost:3306/studydb", "study", "1111");
  ConnectionFactory conFactory = new ConnectionFactory(connectionPool);

  StudentHandler studentHandler;
  TeacherHandler teacherHandler;
  BoardHandler boardHandler;
  HelloHandler helloHandler = new HelloHandler();

  public static void main(String[] args) {
    try {
      new ServerApp().execute(8888);
    } catch (Exception e) {
      System.out.println("서버 실행 오류!");
      e.printStackTrace();
    }
  }

  public ServerApp() throws Exception{

    // Mybatis API 사용 준비
    // 1) Mybatis 설정파일 준비
    //    => resources/bitcamp/myapp/config/mybatis-config.xml

    // 2) SQL Mapper 파일 준비
    //    => resources/bitcamp/myapp/mapper/BoardMapper.xml

    // 3) Mybatis 설정 파일을 읽을 때 사용할 입력 스트림 객체 준비
    InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
        "bitcamp/myapp/config/mybatis-config.xml");
    // Resources.getResourceAsStream : 경로 기준은 classpath 이다.
    // classpath : myapp-server/bin/main

    // 4) SqlSessionFactoryBuilder 객체 준비
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

    // 5) builder를 이용하여 SqlSessionFactory 객체 생성
    SqlSessionFactory sqlSessionFactory = builder.build(mybatisConfigInputStream);

    BoardDaoImpl boardDao = new BoardDaoImpl(sqlSessionFactory);
    MemberDaoImpl memberDao = new MemberDaoImpl(conFactory);
    StudentDaoImpl studentDao = new StudentDaoImpl(conFactory);
    TeacherDaoImpl teacherDao = new TeacherDaoImpl(conFactory);

    this.studentHandler = new StudentHandler("학생", conFactory, memberDao, studentDao);
    this.teacherHandler = new TeacherHandler("강사", conFactory, memberDao, teacherDao);
    this.boardHandler = new BoardHandler("게시판", boardDao);
  }

 

 

Mybatis API 사용 준비

 

1) Mybatis 설정파일 준비

mybatis.org 사이트에서 아래 부분 복사해서 사용한다.

 

src/main 에 resources 폴더 만들고 $ gradle eclipse 하면 이클립스 프로젝트 폴더로 인식한다.

mybatis-config.xml 파일 저장 위치: src.main.resources.bitcamp.myapp.config.mybatis-config.xml

 

mybatis-config.xml

environments default 에 "development" 입력하면 environment id 가 "development" 인 것을 사용한다. 이런식으로 설정 여러개 해놓고 DB 골라서 사용할 수 있다.

아래 <mapper resources=" "> 에 BoardMapper.xml 경로 입력한다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="org.mariadb.jdbc.Driver"/>
        <property name="url" value="jdbc:mariadb://localhost:3306/studydb"/>
        <property name="username" value="study"/>
        <property name="password" value="1111"/>
      </dataSource>
    </environment>
      
  <mappers>
    <mapper resource="bitcamp/myapp/mapper/BoardMapper.xml"/>
  </mappers>
</configuration>

복사한 코드

    <environment id="test">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

 

driver 주소는 myapp-server > Referenced Libraries > mariadb-java-client-3.1.2.jar > META-INF > services > java.sql.Driver 에 있다.

 

 

2) SQL Mapper 파일 준비

boardmapper 는 mybatis.org 사이트에서 아래 코드 복사한다.

 

BoardMapper.xml 파일 저장 위치: src.main.resources.bitcamp.myapp.mapper.BoardMapper.xml

 

 

BoardMapper.xml

namespace.id (BoardMapper.findAll) 처럼 사용한다. 

예) sqlSession.selectList("BoardMapper.findAll");

 

insert 에서 #{title} 로 사용시 parameterType 에 해당하는 클래스 파일에서 프로퍼티 찾아서 값 입력해준다.

select 에서 select 문으로 가져온 데이터를 resultType 의 클래스의 객체에 넣어준다. DB 에 저장한 컬럼명을 찾아서 객체 프로퍼티와 동일한 필드에 넣는다. DB 와 객체 프로퍼티가 다르면 select 문에 as 로 객체 프로퍼티와 동일한 이름으로 별명 붙여준다.

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

  <insert id="insert" parameterType="bitcamp.myapp.vo.Board">
    insert into app_board(title, content, pwd)
    values(#{title}, #{content}, #{password})
  </insert>

  <select id ="findAll" resultType="bitcamp.myapp.vo.Board">
    select 
      board_id as no,
      title, 
      created_date as createdDate, 
      view_cnt as viewCount
    from 
      app_board 
    order by 
      board_id desc
  </select>

</mapper>

 

 

3) Mybatis 설정 파일을 읽을 때 사용할 입력 스트림 객체 준비

mybatis.org 에서 Building SqlSessionFactory from XML 코드 중 해당 부분 가져온다.

    InputStream mybatisConfigInputStream = Resources.getResourceAsStream(
        "bitcamp/myapp/config/mybatis-config.xml");
    // Resources.getResourceAsStream : 경로 기준은 classpath 이다.
    // classpath : myapp-server > bin > main

 

 

4) SqlSessionFactoryBuilder 객체 준비
5) builder를 이용하여 SqlSessionFactory 객체 생성

sqlSessionFactoryBuilder 객체 생성하고 메서드 build() 매개변수에 3) 에서 선언한 입력 스트림 객체를 넣는다.

// 4) SqlSessionFactoryBuilder 객체 준비
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

// 5) builder를 이용하여 SqlSessionFactory 객체 생성
SqlSessionFactory sqlSessionFactory = builder.build(mybatisConfigInputStream);

사이트 에서는 한 줄에 입력하였다.

 

BoardDaoImpl.java 에서 sqlSessionFactory 사용해서 insert(), findAll() 코드 변경한다.

기본 autoCommit = false 라 insert() 에서 commit() 해야 반영된다.

public class BoardDaoImpl implements BoardDao {

  ConnectionFactory conFactory;
  SqlSessionFactory sqlSessionFactory;

  public BoardDaoImpl(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

  @Override
  public void insert(Board b) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      sqlSession.insert("BoardMapper.insert", b);
      sqlSession.commit();
    }
  }

  @Override
  public List<Board> findAll() {
    // openSession() 에서 던지는 Exception 이 runtime Exception 이다.
    // 자동 close 하려고 try 문 쓴다.
    // SqlSession 은 스레드에 종속된 커넥션을 사용하므로 공유하면 안된다.
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      return sqlSession.selectList("BoardMapper.findAll");
    }
  }

 

sqlSession.update() 이런 식으로 사용하면 Mybatis 내부에서 JDBC 사용해서 통신한다.

org.apache.ibatis.executor.statement.SimpleStatementHandler 코드 참고

public class SimpleStatementHandler extends BaseStatementHandler {

  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

 

 

 


 

조언

 

*자동차를 탄다고 모든 기능을 공부하고 타지 않는다. 정비공이 되려면 정비공이 되기 위해 필요한 지식을 배우면 된다. 엔진 연구원이 되려면 엔진 연구하기 위해 필요한 지식을 배우면 된다.

*객체지향 프로그램 잘짜는 방법: 객체가 어떤 역할 하는지에만 집중하라. 자세한 동작 원리를 너무 깊게 파고들지 말라.

 

 


 

과제

 

/