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
관리 메뉴

개발자입니다

[비트캠프] 69일차(15주차1일) - JDBC: myapp-38 본문

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

[비트캠프] 69일차(15주차1일) - JDBC: myapp-38

끈기JK 2023. 2. 13. 13:54

 

Desktop Application 과 Application Server

 

Desktop App. (Local)

  • Standalone App (Local)
    지뢰찾기, 메모장, 포토샵, 주소록, Eclipse
  • Client/Server (Local/Remote)
    WoW, LoL, 메일 Client, git Client

 

Application Server

예) Web mail

 

사용자가 Web Browser 로 입력하면 여기서 Web Server 로 요청한다. App. (Application Server)에서 실행하고 DBMS 와 통신한다.

Web Server 에서 응답을 Web Browser 로 하고 여기서 사용자로 출력한다.

 

Web Browser 는 Local 이며 입력 받기, 출력하기(UI 출력) 한다.

Web Server, App. DBMS 는 Remote 이며 업무 로직 수행, UI 생성 한다.

 

이점

  • 기능 변경이 쉽다.
    • 서버 쪽만 변경하면 된다.
    • 클라이언트를 재설치 할 필요가 없다.
  • 중앙에서 권한 관리
    • 보안 관리가 쉽다.
  • 서버에서 모든 기능을 실행
    • Thin Client  예) 크롬북

 

 

 

38. 트랜잭션 다루기

 

 

app_student, app_teacher 의 공통 컬럼을 app_member 로 뽑아내고 포함 관계로 만든다.

 

 

 

포함 관계와 배타적 관계

 

① 포함 관계(inclusive relationship)

회원 데이터는 학생과 강사 데이터와 동시에 관련될 수 있다. 즉 강사이면서 학생일 수 있을 때 이런 관계로 표현한다.

 

② 배타적 관계(exclusive relationship)

회원 데이터는 학생, 매니저, 강사 중 한 개의 데이터 하고만 관련될 수 있다. 즉 강사이면서 학생일 수 없다. 학생으로 따로 가입해야하는 경우.

 

 

 

DAO 와 테이블

 

① 이전 방식

StudentHandler 가 StudentDao 를 call 한다. 여기서 《table》app_student 의 owner 이고 insert, update, delete, select 를 할 수 있다.

 

② 변경 방식

StudentHandler 에서 《interface》MemberDao 를 call 한다. 여기서 《table》app_member 의 owner 이고 insert, update, delete, select 를 할 수 있다. app_member에 member_id (pk) 가 있다.

 

StudentHandler 에서 《interface》StudentDao 를 call 한다. 여기서 《table》app_student 의 owner 이고 insert, update, delete, select 를 할 수 있다. member_id (pk, fk) pk = fk 가 있다. 또한 app_member 의 viewer 이고 select 할 수 있다.

app_member 와 app_student 는 식별관계(identifying relationship) 이다.

 

 

 

Handler - DAO - Table

 

1번 업무처리를 Handler 로 한다. Handler 에서 DAO 1 을 call 한다. DAO 는 Table 의 owner 이고 변경권한(insert/update/delete) 가 있다.

2번 업무처리를 Handler 로 한다. Handler 에서 DAO 1,2,3 을 call 한다. 각 DAO 는 Table 의 owner 이다.

테이블이 작을 경우 한 개의 DAO 가 두 테이블의 ownership을 가질 수 있다. → 관리가 용이할 때

3번 업무처리를 Handler 로 한다. Handler 에서 DAO 2,3 을 call 한다. 각 DAO 는 Table 의 owner 이다.

DAO 3은 다른 Table 의 viewer 여서 select 만 가능하다. 한 개의 테이블을 여러 개의 DAO 가 변경해서는 안된다. 변경에 대한 추적이 어렵다. 관리자가 두명 이상이면 배가 산으로 간다. 책임 소재가 불문명.

 

Handler 끼리는 서로 사용하지 않는다. DAO 끼리도 서로 사용하지 않는다.

다른 핸들러를 사용하는 순간 그 핸들러에 종속된다. 업무 로직이 독립적이 않게 된다. 재사용성이 줄어든다.

 

 

 

학생 입력

 

StudentHandler에서 ① inputMember() 가 실행되면

MemberDao 객체의 ② insert() 가 호출된다. 여기서 SQL ③ insert 문이 app_member 에 실행된다.

StudentDao 객체의 ④ insert() 가 호출된다. 여기서 SQL ⑤ insert 문이 app_student 에 실행된다.

app_student 의 fk 제한에 따라 app_member SQL insert 후 app_student SQL insert 해야한다.

 

 

 

학생 변경

 

StudentHandler에서 ① modifyMember() 가 실행되면

MemberDao 객체의 ② update() 가 호출된다. 여기서 SQL ③ update 문이 app_member 에 실행된다.

StudentDao 객체의 ④ update() 가 호출된다. 여기서 SQL ⑤ update 문이 app_student 에 실행된다.

app_student 의 fk 제한에 따라 app_member SQL update 후 app_student SQL update 해야한다.

 

 

 

학생 삭제

 

StudentHandler에서 ① deleteMember() 가 실행되면

StudentDao 객체의 ② delete() 가 호출된다. 여기서 SQL ③ delete 문이 app_student 에 실행된다.

MemberDao 객체의 ④ delete() 가 호출된다. 여기서 SQL ⑤ delete 문이 app_member 에 실행된다.

app_student 의 fk 제한에 따라 app_student SQL delete 후 app_member SQL delete 해야한다.

 

 

테스트 코드는 아래 처럼 작성한다.

  public static void main(String[] args) throws Exception {
    Connection con = DriverManager.getConnection(
        "jdbc:mariadb://localhost:3306/studydb", "study", "1111");

    MemberDaoImpl dao = new MemberDaoImpl(con);

    //    Member m = new Member();
    //    m.setName("aaa");
    //    m.setTel("11114");
    //
    //    dao.insert(m);

    //    List<Member> list = dao.findAll();
    //    for (Member m : list) {
    //      System.out.println(m.toString());
    //    }

    //    Member m = dao.findByNo(2);
    //    System.out.println(m);

    //    Member m = new Member();
    //    m.setNo(2);
    //    m.setName("xxx");
    //    m.setTel("101010");
    //    System.out.println(dao.update(m));

    System.out.println(dao.delete(3));

    con.close();
  }

 

Eclipse 에서 과거 저장내역은 Compare With > Local History 에서 확인 가능

 

### 38. 트랜잭션 다루기
- 자동 커밋과 수동 커밋 
- 여러 개의 데이터 변경 작업을 한 단위로 묶는 방법  - doc/ddl2.sql 실행

 

ddl2.sql 생성하고 app_member 테이블 정의한다.

name, tel, created_date 는 app_member 테이블에서 사용하므로 app_student, app_teacher 에서 삭제한다. 

drop table if exists app_student restrict;
drop table if exists  app_teacher restrict;
drop table if exists app_member restrict;

create table app_member(
  member_id int not null,
  name varchar(50) not null,
  email varchar(50) not null,
  pwd varchar(20) not null,
  tel varchar(20),
  created_date datetime default now()
);

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

alter table app_member
  add constraint app_member_uk unique (email);
  
alter table app_member
  modify column pwd varchar(100) not null;

  
create table app_student(
  member_id int not null,
  pst_no varchar(5),
  bas_addr varchar(255),
  det_addr varchar(255),
  work boolean,
  gender char(1),
  level int
);

alter table app_student
  add constraint primary key (member_id),
  add constraint app_student_fk foreign key (member_id) references app_member (member_id);

alter table app_student
  modify column work boolean not null,
  modify column level int not null;

create table app_teacher(
  member_id int not null,
  degree int,
  school varchar(50),
  major varchar(50),
  wage int
);

alter table app_teacher
  add constraint primary key (member_id),
  add constraint app_teacher_fk foreign key (member_id) references app_member (member_id);

alter table app_teacher
  modify column degree int not null,
  modify column wage int not null;

 

 

myapp-common

 

Member.java 에 email, password 추가한다.

public class Member implements java.io.Serializable {
  private static final long serialVersionUID = 1L;

  private int no;
  private String name;
  private String email;
  private String password;
  private String tel;
  private Date createdDate;

 

Student.java, Teacher.java 에서 no, name, tel, email 삭제한다.

public class Student extends Member implements java.io.Serializable {
  private static final long serialVersionUID = 1L;

  private String postNo;
  private String basicAddress;
  private String detailAddress;
  private boolean working;
  private char gender;
  private byte level;
public class Teacher extends Member implements java.io.Serializable {
  private static final long serialVersionUID = 1L;

  private int degree;
  private String school;
  private String major;
  private int wage;

 

《interface》----Dao 에서 배열 사용하던 걸 List<E> 로 변경한다.

update, delete 는 성공시 숫자 반환하도록 반환타입 int 로 바꾼다.

public interface MemberDao {
  void insert(Member m);
  List<Member> findAll();
  Member findByNo(int no);
  int update(Member m);
  int delete(int no);
}
public interface StudentDao {
  void insert(Student s);
  List<Student> findAll();
  Student findByNo(int no);
  List<Student> findByKeyword(String keyword);
  int update(Student s);
  int delete(int no);
}
public interface TeacherDao {
  void insert(Teacher t);
  List<Teacher> findAll();
  Teacher findByNo(int no);
  int update(Teacher t);
  int delete(int no);
}

 

 

myapp-server

 

폴더 구조 및 파일 상태는 다음과 같다.

 

TeacherHandler 생성자 매개변수에 Connection, MemberDao 객체 받도록 수정한다.

inputTeacher() 에서 트랜잭션 적용한다. autoCommit false 후 memberDao 에 insert 후 teacherDao 에 insert 한다. 성공시 commit() 한다. 실패시 예외 문에서 rollback() 한다. finally 에서 autoCommit true 로 변경한다.

public class TeacherHandler {

  private Connection con;
  private MemberDao memberDao;
  private TeacherDao teacherDao;
  private String title;

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

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

    con.setAutoCommit(false);
    try {
      memberDao.insert(t);
      teacherDao.insert(t);
      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 t : teachers) {
      streamTool.printf("%d\t%s\t%s\t%s\t%s\t%d\n",
          t.getNo(), t.getName(), t.getTel(),
          getDegreeText(t.getDegree()), t.getMajor(), t.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.getTel())
    .printf("  이메일: %s\n", m.getEmail())
    .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.setTel(streamTool.promptString(String.format("전화(%s)? ", old.getTel())));
    m.setEmail(streamTool.promptString(String.format("이메일(%s)? ", old.getEmail())));
    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")) {

      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;
    }

    con.setAutoCommit(false);
    try {
      teacherDao.delete(teacherNo);
      memberDao.delete(teacherNo);
      con.commit();
      streamTool.println("삭제했습니다.").send();

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

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

 

MemberDaoImpl.java 생성한다. 그리고 수정한다.

MemberDaoImpl.java 에서는 name, email, password, tel 만 다룬다.

pwd insert 시 sha2 로 암호화 적용한다.

insert executeUpdate 시 generatedKey 를 받는다고 설정한다. 그리고 PK 를 받아서 m 객체에 저장한다.

후에 TeacherDao 나 StudentDao 에서 insert 할 때 no (fk)가 설정된 상태에서 insert 할 수 있다.

배열 return하던 부분을 List return으로 변경한다.

public class MemberDaoImpl implements MemberDao {

  Connection con;

  public MemberDaoImpl(Connection con) {
    this.con = con;
  }

  @Override
  public void insert(Member m) {
    try (Statement stmt = con.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 = con.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 = con.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 = con.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 = con.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);
    }
  }

 

TeacherDaoImpl.java 생성한다. 그리고 수정한다.

TeacherDaoImpl.java 에서는 Member 에서 다루지 않는 degree, school, major, wage 다룬다.

단, select 문 작성시는 app_member, app_teacher 모두에서 데이터 가져온다.

public class TeacherDaoImpl implements TeacherDao {

  Connection con;

  public TeacherDaoImpl(Connection con) {
    this.con = con;
  }

  @Override
  public void insert(Teacher t) {
    try (Statement stmt = con.createStatement()) {

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

      stmt.executeUpdate(sql);

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

  @Override
  public List<Teacher> findAll() {
    try (Statement stmt = con.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 t = new Teacher();
        t.setNo(rs.getInt("member_id"));
        t.setName(rs.getString("name"));
        t.setTel(rs.getString("tel"));
        t.setDegree(rs.getInt("degree"));
        t.setMajor(rs.getString("major"));
        t.setWage(rs.getInt("wage"));

        list.add(t);
      }
      return list;

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

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

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

      return null;

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

  @Override
  public int update(Teacher t) {
    try (Statement stmt = con.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 = con.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);
    }
  }

 

ServerApp.java 에서 new StudentHandler() 등 생성할 때 con, memberDao 를 함께 넘긴다.

public class ServerApp {

/* 생략 */

  public ServerApp() throws Exception{
    this.con = DriverManager.getConnection(
        "jdbc:mariadb://localhost:3306/studydb", "study", "1111");

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

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

 

StudentHandler, BoardHandler 수정한다.

public class StudentHandler {

  private Connection con;
  private MemberDao memberDao;
  private StudentDao studentDao;
  private String title;

  public StudentHandler(String title, Connection con, MemberDao memberDao, StudentDao studentDao) {
    this.title = title;
    this.con = con;
    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전공? "));

    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.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")) {
      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;
    }

    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 class BoardHandler {

  private BoardDao boardDao;
  private String title;

  public BoardHandler(String title, BoardDao boardDao) {
    this.title = title;
    this.boardDao = boardDao;
  }

  private void inputBoard(StreamTool streamTool) throws Exception {
    Board b = new Board();
    b.setTitle(streamTool.promptString("제목? "));
    b.setContent(streamTool.promptString("내용? "));
    b.setPassword(streamTool.promptString("암호? "));

    this.boardDao.insert(b);

    streamTool.println("입력했습니다!").send();
  }

  private void printBoards(StreamTool streamTool) throws Exception {
    List<Board> boards = this.boardDao.findAll();
    streamTool.println("번호\t제목\t작성일\t조회수");
    for (Board b : boards) {
      streamTool.printf("%d\t%s\t%s\t%d\n",
          b.getNo(), b.getTitle(), b.getCreatedDate(), b.getViewCount());
    }
    streamTool.send();
  }

  private void printBoard(StreamTool streamTool) throws Exception {
    int boardNo = streamTool.promptInt("게시글 번호? ");

    Board b = this.boardDao.findByNo(boardNo);

    if (b == null) {
      streamTool.println("해당 번호의 게시글 없습니다.").send();
      return;
    }

    this.boardDao.increaseViewCount(boardNo);

    streamTool
    .printf("    제목: %s\n", b.getTitle())
    .printf("    내용: %s\n", b.getContent())
    .printf("  등록일: %s\n", b.getCreatedDate())
    .printf("  조회수: %d\n", b.getViewCount())
    .send();
  }

  private void modifyBoard(StreamTool streamTool) throws Exception {
    int boardNo = streamTool.promptInt("게시글 번호? ");

    Board old = this.boardDao.findByNo(boardNo);

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

    // 변경할 데이터를 저장할 인스턴스 준비
    Board b = new Board();
    b.setNo(old.getNo());
    b.setCreatedDate(old.getCreatedDate());
    b.setTitle(streamTool.promptString(String.format("제목(%s)? ", old.getTitle())));
    b.setContent(streamTool.promptString(String.format("내용(%s)? ", old.getContent())));
    b.setPassword(streamTool.promptString("암호? "));
    b.setViewCount(old.getViewCount());

    if (!old.getPassword().equals(b.getPassword())) {
      streamTool.println("암호가 맞지 않습니다!").send();
      return;
    }

    String str = streamTool.promptString("정말 변경하시겠습니까?(y/N) ");
    if (str.equalsIgnoreCase("Y")) {
      this.boardDao.update(b);
      streamTool.println("변경했습니다.");
    } else {
      streamTool.println("변경 취소했습니다.");
    }
    streamTool.send();
  }

  private void deleteBoard(StreamTool streamTool) throws Exception {
    int boardNo = streamTool.promptInt("게시글 번호? ");

    Board b = this.boardDao.findByNo(boardNo);

    if (b == null) {
      streamTool.println("해당 번호의 게시글이 없습니다.").send();
      return;
    }

    String password = streamTool.promptString("암호? ");
    if (!b.getPassword().equals(password)) {
      streamTool.println("암호가 맞지 않습니다!").send();
      return;
    }

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

    this.boardDao.delete(boardNo);

    streamTool.println("삭제했습니다.").send();

  }

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

    List<Board> boards = this.boardDao.findByKeyword(keyword);

    streamTool.println("번호\t제목\t작성일\t조회수");
    for (Board b : boards) {
      streamTool.printf("%d\t%s\t%s\t%d\n",
          b.getNo(), b.getTitle(), b.getCreatedDate(), b.getViewCount());
    }
    streamTool.send();
  }

 

StudentDaoImpl, BoardDaoImpl 생성한다. 그리고 수정한다.

public class StudentDaoImpl implements StudentDao {

  Connection con;

  public StudentDaoImpl(Connection con) {
    this.con = con;
  }

  @Override
  public void insert(Student s) {
    try (Statement stmt = con.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 = con.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 = con.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 = con.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"
            + "   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 = con.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 = con.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 BoardDaoImpl implements BoardDao {

  Connection con;

  public BoardDaoImpl(Connection con) {
    this.con = con;
  }

  @Override
  public void insert(Board b) {
    try (Statement stmt = con.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 = con.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 = con.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 = con.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 = con.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 = con.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 = con.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);
    }
  }
}

 

 

 


 

조언

 

*개발 업무 하면서 빅데이터나 인공지능 대학원 다니면 좋다.

*우선 개발자로 시작하라. 그리고 그 안에서 길을 찾아라. 코딩 잘하는 사람이 있고, 테스트 잘하는 사람이 있다.

*테스트도 개발의 일부이므로 반드시 모든 케이스 꼼꼼히 테스트하라!

 

 


 

과제

 

/