개발자입니다
[비트캠프] 69일차(15주차1일) - JDBC: myapp-38 본문
[비트캠프] 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);
}
}
}
조언
*개발 업무 하면서 빅데이터나 인공지능 대학원 다니면 좋다.
*우선 개발자로 시작하라. 그리고 그 안에서 길을 찾아라. 코딩 잘하는 사람이 있고, 테스트 잘하는 사람이 있다.
*테스트도 개발의 일부이므로 반드시 모든 케이스 꼼꼼히 테스트하라!
과제
/
'네이버클라우드 AIaaS 개발자 양성과정 1기 > DBMS, SQL, JDBC, Servlet' 카테고리의 다른 글
[비트캠프] 71일차(15주차3일) - JDBC(MyBatis), myapp-42~43(SqlSession, 프록시 패턴) (0) | 2023.02.15 |
---|---|
[비트캠프] 70일차(15주차2일) - JDBC: myapp-39~42중간(커넥션 풀, PreparedStatement, Mybatis) (0) | 2023.02.14 |
[비트캠프] 68일차(14주차5일) - JDBC: myapp-36~37 (0) | 2023.02.10 |
[SQL] 예제 소스 정리 - JDBC (0) | 2023.02.09 |
[비트캠프] 67일차(14주차4일) - JDBC(JDBC API), myapp-35 (0) | 2023.02.09 |