딸기말차
[Java] 10. CRUD 실습 본문

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)
백엔드 개발 부트캠프
백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.
playdata.io
1. CRUD 실습_2) 입력 값에 따라 작업을 수행하는 프로그램
어제 마지막에 진행하였던 실습을 강사님께서 풀이해주셨다.
혼자 진행했던 풀이와 가장 큰 차이는 클린코드를 위해 클래스를 많이 분리하신 점이었다.
- 중복 데이터 및 query들을 상수로 저장하기 위한 interface
- DB 레코드 정보를 객체화 하여 사용하기 위한 dto 패키지
- 출력 문구를 위한 PrintResult class
우선 interface를 보면, 고정값(상수)로 들어가는 변수들과 query를 작성해 implements하여 사용할 수 있도록 구현하였다.
* Oracle, MySQL, SQLite : ANSI (표준) Query
* 이 DB 들은 각각 접속방식이 다르기 때문에 driver ~ pw_mql 까지는 DB 종류마다 달라진다.
* 인터페이스는 내부 상수도 구현이 가능하고, abstract class로 바꿔 사용도 가능하다.
* 단, 추상 클래스를 사용하려면 implements 가 아니라 extends 로 바꿔 사용해야한다.
public interface Querys {
String driver = "com.mysql.cj.jdbc.Driver";
String dbName = "db_emp";
String url = "jdbc:mysql://localhost:3306/" + dbName + "?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
String id_mysql = "root";
String pw_mysql = "0000"; // 본인 비밀번호
String querySelectAll = "select * from employee";
String querySelectOne = "select * from employee where id = ?";
String queryInsert = "insert into employee "
+ "(empNo, empName, job, mgr, hireDate, sale, commission, deptNo) "
+ "values (?, ?, ?, ?, ?, ?, 0, ?)";
String queryUpdate = "update employee set sale = ? where seq_no = ?";
String queryDelete = "delete from employee where job = ?";
}
두 번째로, 데이터를 객체화하여 DTO의 역할을 할 수 있게 하는 객체를 구현하였다.
* 하나의 레코드 정보를 객체화
* dto (data transfer object) : 데이터를 싣고 다니는 객체
* spring에서는 vo (value object) 라고도 한다.
public class Employee {
private int empNo = 0; // 사원 고유번호
private String empName = ""; // 사원이름
private String job = ""; // 직급
private String mgr = ""; // 직급번호
private String hireDate = ""; // 입사일
private int sale = 0 ; // 급여
private int commission = 0; // 성과급
private int deptNo = 0; // 부서번호
public Employee() {
}
public Employee(int empNo, String empName, String job, String mgr, String hireDate, int sale, int commission,int deptNo) {
this.empNo = empNo;
this.empName = empName;
this.job = job;
this.mgr = mgr;
this.hireDate = hireDate;
this.sale = sale;
this.commission = commission;
this.deptNo = deptNo;
}
/*
* private 변수들에 대한 getter / setter
*/
@Override
public String toString() {
return "Employee [empNo=" + empNo + ", empName=" + empName + ", job=" + job + ", mgr=" + mgr + ", hireDate="
+ hireDate + ", sale=" + sale + ", commission=" + commission + ", deptNo=" + deptNo + "]";
}
}
마지막으로, 출력을 위한 PrintClass를 구현하였다.
public class PrintResult {
public PrintResult() {
}
public static void printMenu() {
System.out.println();
System.out.println("원하는 작업 번호를 입력해 주세요..");
System.out.println("1. 전체사원조회 / 2. 사원추가 / 3. 전사원 급여10%올림 / 4. 과장 직책 사원 삭제 / 5. 작업 종료: ");
}
public static void printSelectAll(ArrayList<Employee> employees) {
for (Employee e : employees)
System.out.println(e.toString());
}
}
해당 코드들로 변경 됨에 따라 기존에 구현했던 메서드들도 약간씩 코드가 변하게 되었다.
우선 selectAll() 메서드를 보면, 조회 결과를 ArrayList에 담아 return 후 PrintClass에서 사용하는 방식으로 변경되었다.
또한, 기존에 query를 하드코딩해 작성하던 부분이 interface에서 미리 구현한 상수로 대체되었다.
public ArrayList<Employee> selectAll() {
ArrayList<Employee> employees = new ArrayList<Employee>();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery(querySelectAll);
// empNo, empName, job, mgr, hireDate, sale, commission, deptNo
while (rs.next()) {
int empNo = rs.getInt("empNo");
String empName = rs.getString("empName");
String job = rs.getString("job");
String mgr = rs.getString("mgr");
String hireDate = rs.getString("hireDate");
int sale = rs.getInt("sale");
int commission = rs.getInt("commission");
int deptNo = rs.getInt("deptNo");
employees.add(new Employee(empNo, empName, job, mgr, hireDate, sale, commission, deptNo));
}
} catch (SQLException e) {
System.out.println("SelectAll ERR: " + e.getMessage());
} finally {
close(stmt, rs);
}
System.out.println("데이터 전체 조회 성공");
return employees;
}
두 번째로 update() 메서드를 보면, 기존에는 메서드 내에서 select를 통해 데이터를 조회한 후 update를 실행하도록 구현하였다.
하지만 위에 변경 된 selectAll()을 통해 데이터를 return 할 수 있게 되었기 때문에, return 한 데이터를 파라미터로 받고, 받은 데이터를 이용해 update를 실행할 수 있도록 변경되었다.
public void update(ArrayList<Employee> employees) {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(queryUpdate);
int n = 0;
for (int i = 0; i < employees.size(); i++) {
int sale = employees.get(i).getSale();
sale += (sale * 10 / 100);
pstmt.setInt(1, sale);
pstmt.setInt(2, i + 1);
n += pstmt.executeUpdate();
}
if (n > 0)
System.out.println(n + "개의 레코드 수정 성공");
} catch (SQLException e) {
System.out.println("Update ERR: " + e.getMessage());
} finally {
close(pstmt);
}
}
변경 된 파일들에 따라 Main에서 실행을 위해 호출해야 되는 부분의 코드도 변경되었다. DTO로 이용하기 위한 Employee 객체를 생성해 데이터를 담고, 이 후 switch-case를 통해 구현한 메서드들을 호출하였다.
public static void main(String[] args) {
MySQLconnector mysql = new MySQLconnector();
mysql.connectMySQL();
ArrayList<Employee> employees = null;
PrintResult.printMenu();
Scanner scan = new Scanner(System.in);
while(true) {
int n = scan.nextInt();
switch (n) {
case 1:
employees = mysql.selectAll();
PrintResult.printSelectAll(employees);
PrintResult.printMenu();
break;
case 2:
mysql.insert();
PrintResult.printMenu();
break;
case 3:
employees = mysql.selectAll();
mysql.update(employees);
PrintResult.printMenu();
break;
case 4:
mysql.delete();
PrintResult.printMenu();
break;
default:
System.exit(0);
}
}
}
2. CRUD 실습_3) 입력 값에 따라 작업을 수행하는 프로그램
동일하지만, 요구사항이 달라 내부 로직을 다르게 구현해야하는 실습을 진행하였다. 해당 실습의 요구사항은 다음과 같다.
입력값 1 : 전체 레코드 조회
입력값 2 : 성별이 기타인 사람 성별 수정
입력값 3 : 멤버 추가
입력값 4 : 평균 방문횟수보다 많이 방문한 사람 명단
입력값 5 : 나이 비공개인 사람들의 방문 횟수
입력값 6 : 작업종료

우선, 해당 기능을 구현하기 전에 interface에 초기 설정 값들과 query를 먼저 작성하였다.
public interface Querys {
String driver = "com.mysql.cj.jdbc.Driver";
String dbName = "db_bootcamp";
String url = "jdbc:mysql://localhost:3306/" + dbName + "?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
String id_mysql = "root";
String pw_mysql = "0000"; // 본인 비밀번호
String querySelectAll = "select * from bootcamp2";
String queryUpdate = "update bootcamp2 set cGenger = ? where cGenger = '기타' and cName = ?";
String queryInsert = "insert into bootcamp2 "
+ "(cName, cRating, cJoinDate, cLastDate, cVisitNo, cPostNo, cCommentNo, cGenger, cAge) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?)";
String querySelectOverVisitNoAvg = "select * from bootcamp2 where cVisitNo > ?";
String querySelectClosedAge = "select * from bootcamp2 where cAge = '비공개'";
}
그리고, DTO의 역할을 해주는 Bootcamp 클래스를 구현하였다. 해당 클래스를 구현할 때 setter는 구현하지 않고, 초기 생성 시에만 데이터를 넣어줄 수 있는 생성자와 값을 가져올 수 있는 getter만 구현하였다.
왜냐하면 DTO는 DB 내의 정보를 객체화하여 계층 간 전송을 위해 사용하는데, 이 값이 setter를 통해 쉽게 바뀔 수 있다면, 문제가 발생할 수 있기 때문이다.
public class Bootcamp {
private String cName = ""; // 이름
private String cRating = ""; // 멤버 등급
private String cJoinDate = ""; // 가입일
private String cLastDate = ""; // 최종 방문일
private int cVisitNo = 0 ; // 방문 수
private int cPostNo = 0; // 게시글 수
private int cCommentNo = 0; // 댓글 수
private String cGenger = ""; // 성별
private String cAge = ""; // 연령대
public Bootcamp() {
}
public Bootcamp(String cName, String cRating, String cJoinDate, String cLastDate, int cVisitNo, int cPostNo,
int cCommentNo, String cGenger, String cAge) {
super();
this.cName = cName;
this.cRating = cRating;
this.cJoinDate = cJoinDate;
this.cLastDate = cLastDate;
this.cVisitNo = cVisitNo;
this.cPostNo = cPostNo;
this.cCommentNo = cCommentNo;
this.cGenger = cGenger;
this.cAge = cAge;
}
public String getcName() {
return cName;
}
public String getcRating() {
return cRating;
}
public String getcJoinDate() {
return cJoinDate;
}
public String getcLastDate() {
return cLastDate;
}
public int getcVistiNo() {
return cVisitNo;
}
public int getcPostNo() {
return cPostNo;
}
public int getcCommentNo() {
return cCommentNo;
}
public String getcGenger() {
return cGenger;
}
public String getcAge() {
return cAge;
}
@Override
public String toString() {
return "Bootcamp [cName=" + cName + ", cRating=" + cRating + ", cJoinDate=" + cJoinDate + ", cLastDate="
+ cLastDate + ", cVisitNo=" + cVisitNo + ", cPostNo=" + cPostNo + ", cCommentNo=" + cCommentNo
+ ", cGenger=" + cGenger + ", cAge=" + cAge + "]";
}
}
이렇게 기능 개발을 위한 초기 세팅을 마치고, 요구사항에 맞는 기능 구현을 시작하였다.
우선 첫 번째 요구사항인 전체 레코드 조회를 할 수 있는 메서드이다. 이전 실습에서 강사님께서는 setter를 통해 데이터를 넣어주었지만, 이번 실습에서는 DTO의 역할을 지키기 위해 생성자를 통하여 데이터를 저장하였다.
public ArrayList<Bootcamp> selectAll() {
ArrayList<Bootcamp> bootcamps = new ArrayList<Bootcamp>();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery(querySelectAll);
while (rs.next()) {
bootcamps.add(new Bootcamp(rs.getString("cName"), rs.getString("cRating"),
rs.getString("cJoinDate"), rs.getString("cLastDate"), rs.getInt("cVisitNo"),
rs.getInt("cPostNo"), rs.getInt("cCommentNo"), rs.getString("cGenger"),
rs.getString("cAge")));
}
} catch (SQLException e) {
System.out.println("selectAll ERR: " + e.getMessage());
} finally {
close(stmt, rs);
}
return bootcamps;
}
다음으로, 성별이 기타인 사람의 성별을 수정하는 기능을 구현하였다. 해당 기능을 구현하는 알고리즘을 살펴보면,
1. 성별이 "기타" 로 되어있는 레코드를 찾고, 그 레코드에서 이름 데이터만 뽑아 저장 후 return 한다.
2. DB에 update 쿼리를 날리며 where 절의 조건으로 성별이 "기타" 이고 , 이름이 성별 "기타" 로 되어있는 사람의 이름이라면, update를 진행한다.
해당 구조를 위해 우선 성별이 "기타" 인 사람의 이름을 저장해 return하는 메서드를 구현하였다.
public ArrayList<String> findNames() {
ArrayList<String> names = new ArrayList<String>();
ArrayList<Bootcamp> bootcamps = selectAll();
for (Bootcamp b : bootcamps)
if (b.getcGenger().equals("기타"))
names.add(b.getcName());
return names;
}
그리고 저장한 이름 List를 사용해, DB에 update 쿼리를 날리며 where의 조건에 맞는 사람인지 찾아, 맞다면 update를 수행하였다.
public void update(ArrayList<String> names) {
PreparedStatement pstmt = null;
String male = "남";
String female = "여";
try {
pstmt = conn.prepareStatement(queryUpdate);
int n = 0;
for (String s : names) {
if (s.equals("허찬")) {
pstmt.setString(1, male);
pstmt.setString(2, s);
}
else if (s.equals("김효경")) {
pstmt.setString(1, female);
pstmt.setString(2, s);
}
else if (s.equals("이현수")) {
pstmt.setString(1, male);
pstmt.setString(2, s);
}
else if (s.equals("박정우")) {
pstmt.setString(1, male);
pstmt.setString(2, s);
}
else if (s.equals("최호준")) {
pstmt.setString(1, male);
pstmt.setString(2, s);
}
n = pstmt.executeUpdate();
}
if (n > 0)
System.out.println("update 완료");
} catch (SQLException e) {
System.out.println("update ERR: " + e.getMessage());
} finally {
close(pstmt);
}
}
세 번째로, 새 멤버를 추가하는 insert() 를 구현하였다. 새 멤버를 추가하기 위해 데이터를 생성할 여러 방법이 있다.
이 때, 메서드는 동작을 위한 기능만 수행하고, 데이터 생성 및 저장은 다른 곳에서 진행되어야 한다고 생각한다.
때문에 Main 안에서 데이터를 생성하고, 해당 데이터를 파라미터로 날려 insert를 진행하였다.
Bootcamp bootcamp = new Bootcamp("이순신", "멤버", "2023.06.22.", "2023.07.10.", 36, 0, 0, "남", "50대후반");
public void insert(Bootcamp bootcamp) {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(queryInsert);
pstmt.setString(1, bootcamp.getcName());
pstmt.setString(2, bootcamp.getcRating());
pstmt.setString(3, bootcamp.getcJoinDate());
pstmt.setString(4, bootcamp.getcLastDate());
pstmt.setInt(5, bootcamp.getcVistiNo());
pstmt.setInt(6, bootcamp.getcPostNo());
pstmt.setInt(7, bootcamp.getcCommentNo());
pstmt.setString(8, bootcamp.getcGenger());
pstmt.setString(9, bootcamp.getcAge());
int n = pstmt.executeUpdate();
if (n > 0)
System.out.println("insert 완료");
} catch (SQLException e) {
System.out.println("insert ERR: " + e.getMessage());
} finally {
close(pstmt);
}
}
네 번째로, 평균 방문 횟수보다 많이 방문한 사람의 명단을 구현하였다. 해당 기능을 구현하는 과정을 살펴보면,
1. 평균 방문 횟수를 구해 return 한다.
2. 구한 평균 방문 횟수를 통해 데이터를 선별해 저장 후 return, 출력한다.
우선 평균 방문 횟수를 구하기 위한 메서드를 구현하였다. 이는 데이터를 전부 가져오는 메서드인 selectAll()을 활용해 구현을 진행하였다.
public float visitNoAvg() {
int sum = 0;
ArrayList<Bootcamp> bootcamps = selectAll();
for (Bootcamp b : bootcamps)
sum += b.getcVistiNo();
return (float)sum / bootcamps.size();
}
구한 값을 insert()의 내부에서 사용해 평균 이상 방문한 사람의 이름을 key, 방문 횟수를 value로 하는 Map을 구현해 저장 후 return 하였다.
public Map<String, Integer> selectOverVisitNoAvg() {
Map<String, Integer> map = new HashMap<String, Integer>();
PreparedStatement pstmt = null;
ResultSet rs = null;
float visit = visitNoAvg();
System.out.println("평균 방문 횟수: " + visit);
try {
pstmt = conn.prepareStatement(querySelectOverVisitNoAvg);
pstmt.setFloat(1, visit);
rs = pstmt.executeQuery();
while (rs.next())
map.put(rs.getString("cName"), rs.getInt("cVisitNo"));
} catch (SQLException e) {
System.out.println("selectOverVisitNoAvg ERR: " + e.getMessage());
} finally {
close(pstmt, rs);
}
return map;
}
마지막으로, 나이 비공개인 사람들의 방문 횟수를 출력하는 기능을 구현하였다.
이 때 Java 쪽에선 특별히 알고리즘을 구현하진 않았고, query에서 where을 통해 조건을 줘서 데이터를 선별 후 해당 데이터의 이름을 key, 방문 횟수를 value로 하는 Map을 구현해 저장 후 return 하였다.
public Map<String, Integer> selectClosedAge() {
Map<String, Integer> map = new HashMap<String, Integer>();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery(querySelectClosedAge);
while (rs.next())
map.put(rs.getString("cName"), rs.getInt("cVisitNo"));
} catch (SQLException e) {
System.out.println("selectClosedAge ERR: " + e.getMessage());
} finally {
close(stmt, rs);
}
return map;
}
마지막으로 return 받은 데이터들을 이용해 출력을 위한 printClass를 구현하였다. 위에서부터 보면,
1. selectAll() 을 통해 return 받은 데이터를 파라미터로 받아 출력
2. selectOverVisitNoAvg() 을 통해 return 받은 데이터를 파라미터로 받아 출력
3. selectClosedAge() 을 통해 return 받은 데이터를 파라미터로 받아 출력
public static void printSelectAll(ArrayList<Bootcamp> bootcamps) {
for (Bootcamp b : bootcamps)
System.out.println(b.toString());
}
public static void printSelectOverVisitNoAvg(Map<String, Integer> map) {
System.out.println("----- 평균보다 많이 방문한 사람 -----");
for (Map.Entry<String, Integer> entry : map.entrySet())
System.out.println(entry.getKey() + "의 방문 횟수: " + entry.getValue());
}
public static void printSelectClosedAge(Map<String, Integer> map) {
System.out.println("----- 나이 비공개인 사람 -----");
for (Map.Entry<String, Integer> entry : map.entrySet())
System.out.println(entry.getKey() + "의 방문 횟수: " + entry.getValue());
}
해당 구현 클래스들을 Main에서 switch-case를 통해 출력하였다.
while(true) {
PrintResult.printMenu();
int input = scan.nextInt();
switch (input) {
case 1:
PrintResult.printSelectAll(mysql.selectAll());
break;
case 2:
mysql.update(mysql.findNames());
break;
case 3:
mysql.insert(bootcamp);
break;
case 4:
PrintResult.printSelectOverVisitNoAvg(mysql.selectOverVisitNoAvg());
break;
case 5:
PrintResult.printSelectClosedAge(mysql.selectClosedAge());
break;
default:
System.exit(0);
}
}
3. 10일차 후기
코딩은 하는 사람마다 스타일이 많이 다르다. 보통 강사님께서 전일 실습을 내주시고 다음날 아침에 해설을 해주시는데,
내가 구현한 코드와 강사님이 구현한 코드를 비교해보면 구현하는 방식이 차이가 날 때가 많다. 강사님께서 구현하실 때 조금 더 세분화 시키며 구현하는 느낌이다.
앞으로 Spring 프로젝트를 진행하면, View, Controller, Service, Domain, Repository, DTO 등 여러 계층에 따라 코드를 나눠 구현해야한다. 때문에 목적에 따라 세분화를 하는 연습을 통해, Spring 프로젝트를 진행할 때도 길을 잃지 않고 구현을 할 수 있게 되야 할 것이다.
'Bootcamp > Java' 카테고리의 다른 글
| [Java] 12. Mini Project_1 (0) | 2023.07.09 |
|---|---|
| [Java] 11. CRUD 실습 풀이 (0) | 2023.07.06 |
| [Java] 9. JDBC, CRUD (0) | 2023.07.04 |
| [Java] 8. Generic, SOLID, Lambda, Singleton, Database (0) | 2023.07.03 |
| [Java] 7. Thread, Collections, 외부 라이브러리 (0) | 2023.07.02 |