Day 48. Servlet을 이용한 CRUD 구현

ho_c·2022년 4월 24일
0

국비교육

목록 보기
47/71
post-thumbnail

한 주의 마지막, 어느덧 벌써 10주차가 끝이 난다.
생각해보면 ‘언제 종강할까, 취업할 수 있을까’ 라는 고민이 많았던 것 같은데 요즘엔 ‘뭘 만들까, 이건 어떤 원리로 돌아갈까’라는 생각만 하는 것 같다.

무튼 잡설은 여기까지 :)

오늘은 지난 시간에 했던 질문을 통해 call by reference 의 개념도 살펴봤고, 그 다음에는 서블릿으로 GET방식을 통한 CRUD 구현을 해봤다.

📝목차

  1. Servlet의 원리
  2. CRUD 구현하기

1. Servelt(서블릿)의 원리

수업 시작을 하면서 다음 질문이 나왔다.

왜 서블릿에서 응답은 리턴을 하지 않아도, append() 로 화면에 반영되나요?

질문의 의도는 메서드 작성 시, return을 사용하지 않아도 해당 메서드의 처리 내용이 클라이언트의 화면에 반영되는 것이었다. 즉, ‘return이 없는데 메서드의 결과가 반영되냐’ 라는 것이 본질인 것 같다.

이에 대한 답변은 “return할 필요가 없다”이다.

먼저 메서드 밖으로 값을 내보내는 방법은 크게 두 가지이다.

① Call by value
② Call by reference

1) Call by value

우리가 흔히 사용하는 방식이 ①번으로 return을 통해서 값을 메서드가 반환한다. 흔한 예시로 다음 코드를 보자.


public static int func(int num) {
	num = 20;
	return num;
}
// 메서드 표기는 간략하게 하겠다.
main() { 
	int a = 10;
	func(a);
	print(a); // 10
}

메인에서 int a = 10; 으로 값을 초기화했다. 후에 func(); 로 값을 변경한 후 출력했지만, 여전히 메인에서 a는 10이다.

이는 메모리의 원리와 관련이 있다. 먼저 메모리는 4개의 영역으로 구성되는데, 메서드가 실행될 때마다 독자적인 스택 영역이 생성된다. 구체적으로 main이 실행되면 스택이 생성되고, 그 위에 args(메인의 인자값) → 지역변수 → 메서드 차례로 그 위에 쌓인다.

위 과정에서 보면 메인 내의 func()메서드가 또 쌓였고, 그 위에 똑같이 int num이 쌓이고 그 값이 생성되었다. 그 후 메서드가 끝나면 스택 구조대로 후입선출의 방식으로 사라지게 된다.

따라서 값을 return으로 메인에서 뽑아냈지만, 애초에 매개변수로 돌려받은 a는 단순히 그 자체를 복사해서 사용했기 때문에 메인 위에 쌓인 a의 값은 변하지 않았다.

이런 Call by value의 특징은 다음과 같다.

  • return을 사용 시 보내는 값은 단 한 개의 주소이다.
  • 반환을 위해선 메서드가 종료되어야 한다.

2) Call by reference

Call by reference 는 일단 return의 특징을 극복할 수 있다. 즉, return을 사용하지 않기에 몇 개의 값이든 호출 시 반환할 수 있고, 굳이 호출한 메서드가 종료하지 않아도 된다.

원래 이 방식은 pointer를 사용해서 구현한다. C에선 포인터 문법을 통해 얼마든지 할 수 있지만, 자바에서는 포인터를 사용할 수 없기에 클래스를 이용한다.

먼저 Call by reference 는 주소 자체를 이용하는 방식이다. 다음 예시를 보자.

public class T {
	public static int a = 1;
}

public class Test {
	
	public static void func() {
		T.a = 10;
	}

	public static void main(String[] args) {
		
		System.out.println(T.a); // 1
		
		T.a = 20;
		System.out.println(T.a); // 20
		
		func();
		System.out.println(T.a); // 10
	}
}

먼저 임의로 static을 사용했지만, 굳이 안 그래도 된다. 무튼 클래스의 참조변수는 유일하게 자바에서 허용하는 주소이다. 그리고 참조변수는 heap에 생성된 실제 값이 저장된 주소를 담고 있다.

따라서 main에서 값을 처음 출력하면 1 이 나오게 된다. 그리고 이 값을 20으로 초기화하게 되면, 주소를 따라가서 아예 해당 주소 안에 저장된 값을 변경한다.

이와 마찬가지로 func()에서 또한 주소로 접근하여 값을 바꿔주는 것이다.


정리

답변을 정리하면, 서블릿은 Call by reference를 사용한다. 서블릿에선 main이 톰캣 서버에게 존재하고, 톰캣은 서블릿에게 자신이 가지고 있는 HttpservletResponse, HttpservletRequest 인스턴스의 주소를 참조변수를 통해서 전달한다.

그럼 서블릿은 참조변수를 통해 접근하여, 내용을 채워 넣는다. 다른 관점에선 톰캣이 주도권을 갖고 개발자에게 ‘implement’ 하라는 것이다. 이는 단순히 서블릿 내의 메서드만 국한되는 것이 아니라, 서블릿 자체에 해당한다.

또한, 톰캣은 클라이언트의 요청에 따라 서블릿의 메서드를 실행하는데, 이는 하나의 Call back 패턴이다.

< 요약 >

  • 함수 호출 방식은 두 가지이다.
  • 하나는 값을 복사해서 사용하고, 다른 하나는 주소를 사용한다.
  • 톰캣과 서블릿의 원리도 주소를 사용한다.
  • 톰캣이 전달한 주소를 통해 값을 채워넣고, 톰캣은 다시 이 값을 가지고 응답한다.

2. CRUD 구현하기

서블릿으로 DB연동까지 해서 CRUD를 구현해보자.

1) 외장 라이브러리 추가

webapp-WEB-INF-lib 라이브러리 복사

2) 클라이언트 화면 구현

클라이언트에 띄울 간단한 index, input.html 문서를 만든다.

<!--index.html-->

    <table border="1" align="center">

        <tr>
            <th colspan="2"> Contact </th>
        </tr>

        <tr>
            <td><button id="input">입력</button></td>
            <td><button id="output">출력</button></td>
        </tr>
    </table>

    
    <script>
        $("#input").on("click", function(){
            location.href="input.html"
        });

        $("#output").on("click", function(){
            location.href="ListServlet"
        });
    </script>


<!--input.html-->

	<form action="AddServlet">
		<table border="1" align="center">
			<tr>
				<th colspan="4">Contact</th>
			</tr>
			<tr>
				<td>name</td>
				<td><input type="text" name="name" required></td>
			</tr>

			<tr>
				<td>contact</td>
				<td><input type="text" name="contact" required></td>
			</tr>
			<tr>
				<td colspan="4" align="center">
					<button id="add">추가</button>
					<button id="cancel">취소</button>
				</td>
			</tr>
		</table>
	</form> 

	<script>
        $("#cancel").on("click", function () {
            location.href = "index.html";
        });
    </script>
  • 제이쿼리의 location.href= 는 이벤트 발생 시, 설정한 페이지, 프로그램으로 넘어간다.
  • input.html은 table을 form으로 감싸서, submit이 일어나면 ‘AddServlet’으로 데이터를 전송한다. 기본값 전송 메서드는 ‘GET’이다.

3) DAO 만들기

메서드는 말 그대로 요청을 교환하는 용도로 사용한다. 따라서 실제 DB에 연결되어 작업할 DAO를 따로 만들어 준다. (DTO는 값에 따라서 알아서 만들면 된다)

// DAO
public class ContactDAO {

	private BasicDataSource bds = null;

	public ContactDAO() {

		this.bds = new BasicDataSource(); // lib에 라이브러리를 넣어줘야됨
		bds.setInitialSize(30);
		bds.setUsername("ID");
		bds.setPassword("PW");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
	}

	// Connection 객체 반환
	private Connection getConnection() throws Exception{
		return bds.getConnection();
	};

	// Create
	public int createData(String name, int contact) throws Exception{

		String sql = "insert into contact values(contact_seq.nextval, ?, ?, sysdate)"; 
		// try-resource
		try (
				Connection con = this.getConnection();
				PreparedStatement pstat = con.prepareStatement(sql);
				){

			pstat.setString(1, name);
			pstat.setInt(2, contact);

			int result = pstat.executeUpdate();
			con.commit();

			return result;
		}
	}

	// Read
	public List<ContactDTO> ReadeData() throws Exception {

		String sql = "select * from contact";
		try (
				Connection con = this.getConnection();
				PreparedStatement pstat = con.prepareStatement(sql);

				) {

			try(
					ResultSet rs = pstat.executeQuery();
					){

				List<ContactDTO> dtoArr = new ArrayList();

				while(rs.next()) {
					int id = rs.getInt("id");
					String name = rs.getString("name");
					int contact = rs.getInt("contact");
					String date = rs.getString("reg_date");

					ContactDTO dao =  new ContactDTO(id, name, contact, date);

					dtoArr.add(dao);
				}

				return dtoArr;
			}

		}
	}


	// Delete
	public int deleteData(int id) throws Exception{
    
		String sql = "delete from contact where id=?";
		try(
				Connection con = this.getConnection();
				PreparedStatement pstat =  con.prepareStatement(sql);

				){
			pstat.setInt(1, id);

			int result = pstat.executeUpdate();
			con.commit();

			return result;
		}
	}


	// Update
	public int updateData(int id, String name, int contact) throws Exception{
		String sql =  "update contact set name=?, contact=?, reg_date=sysdate where id=?";

		try(
				Connection con = this.getConnection();
				PreparedStatement pstat = con.prepareStatement(sql);

				){
			pstat.setString(1, name);
			pstat.setInt(2, contact);
			pstat.setInt(3, id);

			int result = pstat.executeUpdate();
			con.commit();
			return result;
		}
}
  • DB에 insert 할 때, sequence에는 꼭 nextval을 붙여줘야 한다.

4) Create

@WebServlet("/AddServlet") // 어노테이션을 통해서 들어온다.
public class CreateServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 입력된 parameter를 가져오기 
		String name = request.getParameter("name"); // name 속성값
		int contact = Integer.parseInt(request.getParameter("contact"));
		
		ContactDAO dao = new ContactDAO();
		
		try {
			int result = dao.createData(name, contact);
			if (result > 0) {
				response.sendRedirect("index.html");				
			}
		} catch (Exception e) {
			e.printStackTrace();
			response.sendRedirect("error.html");
		}
	}
}
  • input.html에서 값을 입력 후, 전송
  • getParameter 로 값을 옮겨담기
  • DAO 객체 생성 및 메서드 실행
  • 반환값을 통해서 사후 처리 진행

5) Read

Servlet의 한계로 인해, append를 메서드로 요청 시 클라이언트 화면에 html을 직접 작성해줬다.

@WebServlet("/ListServlet")
public class ReadServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		PrintWriter pw = response.getWriter();
		
		pw.append("<html>");
		pw.append("<head>");
		pw.append(" <script src=\"https://code.jquery.com/jquery-3.6.0.js\"></script>");

		pw.append("<title> Menu List </title>");
		pw.append("</html>");

		pw.append("<body>");
		pw.append("<table border=1 align=center>");
		pw.append("<tr>");
		pw.append("<th colspan=5> Contact List");
		pw.append("</tr>");

		pw.append("<tr>");
		pw.append("<th>id");
		pw.append("<th>name");
		pw.append("<th>contact");
		pw.append("<th>date");
		pw.append("</tr>");
		
		try {
			ContactDAO dao = new ContactDAO();
			List<ContactDTO> dtoArr	= dao.ReadeData();
			
			for (ContactDTO dto : dtoArr) {
				pw.append("<tr>");

				int id = dto.getId();
				String name = dto.getName();
				int contact = dto.getContact();
				String date = dto.getDate();

				pw.append("<td align=center>" + id);
				pw.append("<td align=center>" + name);
				pw.append("<td align=center>" + "0" + contact);
				pw.append("<td align=center>" + date);

				pw.append("</tr>");
				
			}
			
			
		} catch (Exception e) {
			e.printStackTrace();
			response.sendRedirect("error.html");

		}
		
		// 삭제 버튼
		pw.append("<tr>");
		pw.append("<form action='DelServlet'>");
		pw.append("<td colspan=5><input type=text name=id placeholder='Input pid to delete'><button id=delete>Delete</button>"); // name 속성 넣어줘야 날라간다.
		pw.append("</form>");		
		pw.append("</tr>");		
		
		
		// 수정 버튼
		pw.append("<tr>");
		pw.append("<form action='UpdateServlet'>");
		pw.append("<td colspan=5>");
		pw.append("<input type=text name=id placeholder='Input id to update'><br>"); 
		pw.append("<input type=text name=name placeholder='Input name to update'><br>");
		pw.append("<input type=text name=contact placeholder='Input contact to update'><br>"); 
		pw.append("<button id=update>Update</button>");
		pw.append("</form>");		
		pw.append("</tr>");	


		// 뒤로가기 버튼
		pw.append("<tr>");
		pw.append("<td colspan=5 align=center><button id=back>Back</button>");
		pw.append("</tr>");		
		pw.append("</table>");
		pw.append("<script>");

		pw.append("$('#back').on('click', function(){location.href='index.html'})");

		pw.append("</script>");

		pw.append("</body>");	
	}
}
  • 사용자의 '출력' 요청
  • printwriter 인스턴스 생성
  • append로 html 문서 작성
  • DAO를 통해서 출력값을 받은 뒤, append로 html 작성.

6) Delete

@WebServlet("/![](https://velog.velcdn.com/images/ho_c/post/2b6421c0-8aac-4e44-b916-982c4cf10dab/image.jpg)
")
public class DelServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		int id = Integer.parseInt(request.getParameter("id"));

		try {
			ContactDAO dao = new ContactDAO();

			int result = dao.deleteData(id);

			if(result>0) {
				response.sendRedirect("ListServlet");
			}
		} catch (Exception e) {
			e.printStackTrace();
			response.sendRedirect("error.html");
		}
	}
}
  • 삭제할 id 입력 후, 데이터 전송
  • getParameter 로 값을 옮겨담기
  • DAO 객체 생성 및 메서드 실행
  • 처리 후, 반환값을 통해 응답으로 ListServlet을 재전송.

7) Update

@WebServlet("/UpdateServlet")
public class UpdateServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		int id = Integer.parseInt(request.getParameter("id"));
		String name = request.getParameter("name");
		int contact = Integer.parseInt(request.getParameter("contact"));

		try {
			ContactDAO dao = new ContactDAO();

			int result = dao.updateData(id, name, contact);

			if(result>0) {
				response.sendRedirect("ListServlet");
			}
		} catch (Exception e) {
			e.printStackTrace();
			response.sendRedirect("error.html");
		}
	}
}
  • 수정 id 입력 및 수정할 데이터 전송
  • getParameter 로 값을 옮겨담기
  • DAO 객체 생성 및 메서드 실행
  • 처리 후, 반환값을 통해 응답으로 ListServlet을 재전송.

< 요약 >

profile
기록을 쌓아갑니다.

0개의 댓글