데이터베이스와 Java를 연결하는 법을 배웠으니 이제 CRUD의 기본을 이해해 볼 차례이다. CRUD란 데이터 베이스에 생성/읽기/수정/삭제의 작업을 나열한 것이다. MVC의 구조에서 클라이언트가 발생시킨 요청이 컨트롤러에 도착하고, 컨트롤러는 다시 모델로 들어가 모델에서는 데이터베이스에 연결되어 최종적으로 요청에 맞는 값을 들고 다시 컨트롤러로 돌아온 뒤 클라이언트에 뷰로 전송하는 과정을 거친다. 이 때, 모델과 데이터베이스 사이에서 일어나는 정보의 교환을 통틀어 CRUD라고 칭할 수 있겠다.
데이터베이스 연결하기
private Connection DBconn() throws Exception{
//Step1 JDBC 드라이버 로딩
//특정 클래스를 찾아주는 역할
Class.forName("com.mysql.jdbc.Driver");
//Step2 connection 객체 생성
//주의1 !데이터베이스 생성여부!
//주의2 !WEB-INF/lib 폴더에 jar파일 있는지 확인!
Connection conn = null;
String database = "jdbc:mysql://localhost:3306/login_crud";
//데이터베이스의 주소와 테이블 명
String id = "root";
//데이터베이스의 아이디
String password="1234";
//데이터베이스의 비밀번호
conn = DriverManager.getConnection(database,id,password);
//Connection 객체에 주소-아이디-비밀번호의 값을 넣어 연결 완료
return conn;
}
본격적으로 CRUD를 위한 함수 작성에 앞서, 해당 작업은 모두 데이터 베이스와 연결이 되어 있어야 하는 상태이므로 필수로 연결하는 과정이 필요하다. 제일 먼저 Java와 데이터 베이스(SQL) 사이에서 번역기의 역할을 해줄 JDBC 드라이버를 불러와야 한다. 그리고 이를 연결시켜주는 Connection 객체를 사용하여 해당 함수에 주소/아이디/비밀번호를 입력하면 연결은 끝난다. 이 때, Connection 객체는 모든 CRUD함수에서 사용되어야 하므로 리턴 타입은 Connection 객체로 해주어야 한다.
CREATE (생성하기)
이제 연결준비는 끝났으니 HTML의 요청에 따라 생성하는 함수를 작성해보자. 단순하게 아이디 / 비밀번호 / 나이를 받아오는 회원가입 폼을 만들고, 전송한 뒤 값을 컨트롤러로 넘어온 상태 라고 가정하겠다.
req.setCharacterEncoding("utf-8");
//Step01 전처리
String id = req.getParameter("id");
String pw = req.getParameter("pw");
int age = Integer.parseInt(req.getParameter("age"));
//데이터 묶음 처리
member_dto dto = new member_dto();
dto.setId(id);
dto.setPw(pw);
dto.setAge(age);
//Step02 모델이동
member_repository mr = member_repository.getInstance();
mr.member_create(dto);
//Step03 뷰이동
resp.sendRedirect("readAll");
먼저 <form> 태그를 통해 넘어온 값을 컨트롤러에서 Java가 사용할 수 있도록 저장하는 작업이 필요하다. 아이디와 비밀번호는 문자열로 받고, 나이는 숫자로 받아야 하지만 request에 저장되는 객체는 모두 String타입이기 때문에 parseInt를 통하여 형변환을 해주어야 한다. 이렇게 받아온 데이터는 다시 모델로 보내져 데이터베이스에 등록하는 과정을 거치게 되는데, 이 과정에서는 요청 발생 후 돌아갈 View가 없기 때문에(return이 필요하지 않음) 모든 데이터 베이스를 들고와 보여주는 Read 작업을 실행하게 된다.
public void member_create(member_dto dto) {
try {
Connection conn = DBconn();
//Connection타입으로 받아와서 사용함
Statement stmt = conn.createStatement();
//java.sql.Statement import할 것
//SQL문을 작성할 수 있도록 도와주는 개체
String user_id = dto.getId();
String pw = dto.getPw();
int age = dto.getAge();
String sql = "insert into member values('" + user_id + "','"+ pw +"'," + age + ")";
stmt.executeUpdate(sql);
//업데이트를 진행
} catch (Exception e) { System.out.println("데이터 베이스 연결 실패(오류)"); }
}
함수를 통해 dto 객체를 모델로 전송했다. 가장 먼저 데이터 베이스에 연결하기 위해 앞서 생성해두었던 함수를 호출하여 Connection객체를 생성하고, 이 커넥션을 통하여 SQL문을 작성할 수 있도록 도와주는 Statement 객체를 생성한다. 이제 컨트롤러에서 가져온 dto 객체에서 필요한 값들을 꺼내어 SQL에 작성할 명령어를 String 객체에 저장해준다. <form>태그로 받아온 회원정보를 데이터베이스에 처음 등록하는 과정이니까 insert를 사용하고, member 테이블에 값들( 아이디, 비밀번호, 나이 ) 을 담아준다.
여기서 유의해야 할 점은 작은 따옴표(' ')의 사용인데, SQL에서는 문자나 문자열은 모두 작은 따옴표 내에 작성하게 되어있다. 그것이 규칙이므로(큰 따옴표를 사용해도 되지만, Java에서 큰 따옴표는 문자열로 해석하기 때문에 Java → SQL로 넘어가기 위해서는 작은 따옴표로 고정하여 사용한다) Java에서 작성할 때 따옴표 사용에 주의해서 주소를 적어주어야 한다. 어떻게 생성이 되었는지 모르겠다면 콘솔창에 해당 변수를 띄워보는 것도 방법이다.
이렇게 SQL에 내려질 명령어를 String에 담았다면 executeUpdate() 함수를 통해 데이터베이스에 명령을 내린다. 이것이 CREATE(생성)의 흐름이다.
Read (읽어오기)
CREATE(생성)이 완료되었다면 받을 return값은 없다(라기보단 필요가 없다, 몇 줄이 삽입되었는지에 관한 갯수를 반환하기 때문이다). 따라서 사용자의 요청에 따라 삽입된 것을 바로 확인할 수 있도록 전체회원을 조회해주는 HTML을 작성하여 response를 통해 다른 페이지로 보내줘야 한다. 이제 이 페이지에서는 데이터 베이스를 불러와 내용을 가져오는 작업을 해보자.
public ArrayList<member_dto> getAllmember(){
ArrayList<member_dto> arr = new ArrayList<member_dto>();
ResultSet rs = null;
//데이터베이스가 보내주는 데이터를 받을 장소
try {
Connection conn = DBconn();
Statement stmt = conn.createStatement();
String sql = "select * from member";
rs = stmt.executeQuery(sql);
//데이터베이스에서 ResultSet이라는 객체에 담아 Java로 전송해준다
//다음행이 있으면 True
while(rs.next()) {
String id = rs.getString("id"); //컬럼명
String pw = rs.getString("pw");
int age = rs.getInt("age");
member_dto dto = new member_dto();
dto.setAge(age);
dto.setId(id);
dto.setPw(pw);
//dto.setAge(rs.getString("id")); 바로 담아도 상관 없음
arr.add(dto);
}
} catch(Exception e) { System.out.println("불러오기 실패"); }
return arr;
}
<form> 태그로 전송되어 저장된 멤버의 단위는 dto라는 객체 하나에 담겨있다. 전체 회원 조회를 위해서는 이 객체를 여러개 받아와야 하는데, 이를 한 곳에 묶어 컨트롤러로 내보내기 위해서는 객체를 담을 수 있는 컬렉션 프레임워크(여기서는 ArrayList)를 사용해야 한다. 따라서 dto만 담길 ArrayList를 생성한 뒤, 방금과 같이 데이터 베이스에 접근하기 위한 함수를 생성한다.
다만 여기서는 추가만 했던 방금과는 다르게 데이터 베이스가 보내주는 데이터를 받아와서 저장해야 한다. 이 때 SQL에서 보내오는 데이터는 모두 ResultSet 이라는 객체에 담아오게 되는데, 해당 객체의 형태는 엑셀문서처럼 행 단위로 이루어진 텍스트 파일을 보낸다고 생각하면 편하다. 따라서 ResultSet이라는 객체에서 내용물을 하나씩 불러오기 위해서는 while문을 통해 한 줄씩 꺼내어 dto객체에 담고, 이를 다시 ArrayList에 추가하여 최종적으로 컨트롤러에 모든 정보를 전송할 수 있게 된다. 저장할 때는 executeUpdate() 함수를 사용했지만, 반대로 받아오기 위해서는 executeQuery() 함수를 사용한다.
while문은 조건식이 true면 동작하는 구조를 가지고 있다. next() 함수를 통해 다음 행(데이터)이 존재 하는지에 대한 여부를 true/false로 반환하게 되는데, 마지막 데이터를 지나 eof(문서의 끝)를 만나게 되면 -1(false)를 반환하고 반복은 종료되게 된다. true가 실행되면 ResultSet이 가지고 있는 행 한 줄에서 get함수를 통해 데이터를 가져올 수 있다. 이 때 데이터 타입은 SQL에서 컬럼을 지정할 때 사용한 데이터 타입으로 받아와야 하며, 함수의 내용에는 문자열로 컬럼명을 입력하면 된다.
이렇게 받아온 데이터는 다시 dto 객체에 담기 위해 반복 때마다 new로 생성하고 그 안에 데이터를 set한 뒤, ArrayList에 추가해주면 전체 회원을 모두 담는데 성공할 수 있다. 이렇게 가져온 데이터는 컨트롤러로 전송되고, 이는 다시 HTML(JSP)로 전송되며 최종적으로 아래의 코드를 실행해주면 데이터 베이스에 등록된 모든 회원들을 가져올 수 있다.
<%
ArrayList<member_dto> arr = (ArrayList<member_dto>)request.getAttribute("list");
for(int i=0; i<arr.size(); i++){
member_dto mb = arr.get(i);
%>
<tr>
<td><%= mb.getId() %></td>
<td><%= mb.getPw() %></td>
<td><%= mb.getAge() %></td>
<td> <a href="update?id=<%= mb.getId()%>">수정</a> </td>
<td> <a href="delete?id=<%= mb.getId()%>">삭제</a> </td>
</tr>
<%
}
%>
request객체에 setAttribute로 저장하는 것은 모든 타입을 무시하고 조상인 Object형식으로 저장되기 때문에 getAttribute로 가져올 때는 반드시 형변환 작업을 거쳐야 한다. 이렇게 받아온 List를 for문을 통해 객체 하나를 꺼내어 View 형식으로 작성하면 끝이다.
데이터 베이스에 데이터를 추가하고 불러오기까지 했으니 이제 이 데이터를 수정하는 방법을 알아보자. 사용자가 수정하기 위해서는 기존 값을 들고와야 할 필요가 있다. 여기서는 MySQL에서 테이블을 만들 때, ID컬럼에 primary key 명령어를 추가하여 유일한 값이 되도록 설정해두었다. 따라서 필요에 의해 데이터를 조회할 때, 아이디를 사용하게 되면 사용자가 원하는 한 줄만 출력되게 지정할 수 있는 상태다. 하지만 중요한 점은 지금 필요한 행위가 '한 줄만 출력' 되게 한다는 점이다. 이 부분 역시 Read(읽어오기)에 관한 작업이기 때문에 다시 데이터를 불러오는 작업을 한번 더 복습해보자.
public member_dto getOnemember(String id) {
ResultSet rs = null;
member_dto dto = new member_dto();
try {
Connection conn = DBconn();
Statement stmt = conn.createStatement();
String sql = "select * from member where id='"+id+"'";
rs = stmt.executeQuery(sql);
if(rs.next()) {
String userid = rs.getString("id");
String pw = rs.getString("pw");
int age = rs.getInt("age");
dto.setAge(age);
dto.setId(userid);
dto.setPw(pw);
}
}
catch(Exception e) { System.out.println("수정실패"); }
return dto;
}
전체 조회를 할 때는 모든 정보를 얼만큼 가져올 지 모르기 때문에 while문을 통해 문서의 끝을 만날 때까지 반복하는 작업을 거쳤다. 하지만 이번에는 사용자가 수정을 위한 요청에 아이디를 담아 전송했기 때문에 데이터 베이스에서 하나의 값만 선택해서 가져올 수 있다. SQL문을 작성하기 위해 select * from member로 필요한 테이블을 선택한 후, where로 어느 행을 지정할 것인지 작성해주는데 이 때 유일한 값인 아이디가 들어가게 된다.
따라서 이번에는 하나의 행만 가져올 수 있으니 while문으로 반복을 돌릴 필요가 없으며, next() 함수를 활용하여 행에 데이터를 가지고 있는지 조회한 후 가져온 데이터를 담아 dto 객체에 담아 return한다. 전체 조회는 여러개의 객체를 반환해야 하기 때문에 ArrayList에 담았고, 지금은 하나의 객체(행, 데이터)만 필요하기 때문에 dto에 담는 것이다.
<%
member_dto dto = (member_dto)request.getAttribute("dto");
%>
<form action="update" method="post">
<p>ID : <input readonly type="text" name="id" value="<%= dto.getId() %>"> </p>
<p>PW : <input type="text" name="pw" value="<%= dto.getPw() %>"> </p>
<p>age : <input type="text" name="age" value="<%= dto.getAge() %>"> </p>
<input type="submit" value="수정하기">
</form>
다시 컨트롤러에서 View로 전송되며, 사용자에게 수정 전의 데이터를 보여주기 위해 value로 미리 값을 보여준다. 여기서 ID는 유일한 값이며 변치 말아야 하니 readonly로 수정하지 못하게 막았고, 비밀번호와 나이를 수정할 수 있도록 해두었다.
UPDATE(수정하기)
이제 사용자가 <form>태그를 통해 수정된 값을 다시 전송해 올 것이다. 그렇다면 이 데이터를 받아서 다시 SQL에 수정하라는 명령어를 전송하면 되는데, 이 과정은 CREATE와 유사한 방법이 되겠다. 따라서 처리하는 컨트롤러 또한 CREATE와 바뀐 점이 없다. 수정 요청을 처리한 후, 다시 내보낼 return값은 없기 때문에 마찬가지로 전체회원 목록을 보여주면 끝이니 sendRedirect를 통하여 기존에 만들어 둔 전체 회원 조회(Read)파트로 넘어가면 된다.
public void update_member(member_dto dto) {
try {
Connection conn = DBconn();
Statement stmt = conn.createStatement();
String sql = "update member set pw='"+ dto.getPw()
+ "',age="+ dto.getAge()
+ " where id='"+ dto.getId() + "'";
stmt.executeUpdate(sql);
} catch(Exception e) { System.out.println("수정실패"); }
}
처음 생성하기를 진행했을 때 변수에 담아 명령어를 작성하였지만, 담지 않고 바로 함수로 호출하여 사용해도 문제가 없다. 따로 변수처리가 되지 않아 좀 더 간결해진 코드를 확인할 수 있을 것이다. 이번에는 insert가 아니라 기존에 있는 행의 데이터를 수정하기 위함이니 update member set으로 'member 테이블에서 바꿔' 달라는 명령어를 붙인 뒤, 아이디를 제외한 값들을 나열해준다. 그리고 where 명령어로 어느 행인지 아이디로 지정해주면 선택한 줄만 내용이 바뀌는 코드를 완성할 수 있다.
DELETE(삭제하기)
이제 마지막으로 삭제하는 작업을 진행해보자. 방금과 같은 작업이 복잡하게 느껴질 순 있으나 기본적인 작동원리는 모두 비슷하다. 따라서 삭제 또한 하나의 행만 삭제하기 때문에 UPDATE와 같이 파라미터로 고유한 값인 ID를 가져와야 한다. 수정과 삭제 모두 <a>태그로 넘어올 때 ID 값을 가져왔으니 이를 컨트롤러에서 받아 모델로 넘어오기만 하면 뒤는 간단하다.
public void deleteUser(String id) {
try {
Connection conn = DBconn();
Statement stmt = conn.createStatement();
String sql = "delete from member where id='"+id+"'";
stmt.executeUpdate(sql);
} catch (Exception e) { System.out.println("삭제실패"); }
}
삭제 또한 생성과 수정처럼 return할 값이 없다. 따라서 void로 작성하였고, 수정때와 큰 차이는 없으며 대신 SQL에 내려야 할 명령어만 다른 상태이다. delete form member로 'member테이블을 삭제' 라는 명령어와 함께 where로 아이디 값을 입력하면 해당하는 아이디 값을 가진 행이 삭제되고 sendRedirect를 통하여 회원 조회 창으로 돌아오면 삭제가 되어있는 것을 확인할 수 있다.
여기서 생성/수정/삭제 파트는 모두 sendRedirect를 사용하여 페이지를 이동시켰는데, 이는 forward와 다르게 서버를 완전히 나갔다가 다시 찾아 들어오는 구조기 때문에 데이터베이스의 변경사항이 실시간으로 반영되는 것처럼 보여지기 위함이다.