1. 서블릿의 비즈니스 로직 처리 방법
서블릿 비즈니스 처리 작업이란 무엇일까?
서블릿이 클라이언트로부터 요청을 받으면 그 요청에 대해 작업을 수행하는 것을 의미한다.
웹 프로그램에서 대부분의 비즈니스 처리작업은 데이터베이스 연동 관련 작업이지만 다른 서버와 연동해서 데이터를 얻는 작업도 수행한다. 이 기능의 서블릿의 핵심 기능이라고도 볼 수 있다.
서블릿의 비즈니스 작업의 대표적인 예 3가지
1) 웹 사이트 회원 등록 요청 처리 작업
2) 웹 사이트 로그인 요청 처리 작업
3) 쇼핑몰 상품 주문 처리 작업
2. 서블릿의 데이터베이스 연동하기
2-1. 서블릿으로 회원 정보 테이블의 회원 정보 조회
1) 웹 브라우저가 서블릿에게 회원 정보를 요청
2) MemberServlet은 요청 받은 후 MemberDAO 객체를 생성하여 listMembers() 메서드를 호출
3) listMembers()에서 다시 connDB() 메서드를 호출하여 데이터베이스와 연결한 후 SQL문을 실행해 회원 정보를 조회
4) 조회된 회원 정보를 MemberVO 속성에 설정한 후 다시 ArrayList에 저장
5) ArrayList를 다시 메서드를 호출한 MemberServlet으로 반환한 후 ArrayList의 MemberVO를 차례대로 가져와 회원 정보를 HTML태그의 문자열 만듦
6) 만들어진 HTML태그를 웹 브라우저로 전송해서 회원 정보를 출력
이제 테이블을 생성했으니 자바 클래스 파일을 생성한다.
클래스 파일은 총 세개, Servlet, DAO,VO 클래스를 작성한다.
MemberServlet.java
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
MemberDAO dao=new MemberDAO();
List<MemberVO> list=dao.listMembers();
out.print("<html><body>");
out.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
out.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td></tr>");
for (int i=0; i<list.size();i++){
MemberVO memberVO=(MemberVO) list.get(i);
String id=memberVO.getId();
String pwd = memberVO.getPwd();
String name=memberVO.getName();
String email=memberVO.getEmail();
Date joinDate = memberVO.getJoinDate();
out.print("<tr><td>"+id+"</td><td>"+
pwd+"</td><td>"+
name+"</td><td>"+
email+"</td><td>"+
joinDate+"</td></tr>");
}
out.print("</table></body></html>");
}
MemberVO.java
private String id;
private String pwd;
private String name;
private String email;
private Date joinDate;
public MemberVO() {
System.out.println("MemberVO 생성자 호출");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getJoinDate() {
return joinDate;
}
public void setJoinDate(Date joinDate) {
this.joinDate = joinDate;
}
VO(ValueObject)클래스는 값을 전달하는데 사용된다.
테이블에서 조회한 레코드의 컬럼 값을 속성에 저장해야 하므로 컬럼 이름과 동일한 자료형과 이름으로 속성을 선언하고 getter/setter를 각각 생성한다.
MemberDAO.java
private static final String driver = "oracle.jdbc.driver.OracleDriver";
private static final String url = "jdbc:oracle:thin:@localhost:1521:XE";
private static final String user = "scott";
private static final String pwd = "tiger";
private Connection con;
private Statement stmt;
public List<MemberVO> listMembers() {
List<MemberVO> list = new ArrayList<MemberVO>();
try {
//네가지 정보로 DB연결
connDB();
String query = "select * from t_member ";
System.out.println(query);
//SQL문으로 회원정보를 조회
ResultSet rs = stmt.executeQuery(query);
while(rs.next()) {
//조회한 레코드의 각 컬럼 값을 받아온다.
String id = rs.getString("id");
String pwd = rs.getString("pwd");
String name = rs.getString("name");
String email = rs.getString("email");
Date joinDate = rs.getDate("joinDate");
//각 컬럼 값을 다시 MemberVO 객체의 속성에 설정한다.
MemberVO vo = new MemberVO();
vo.setId(id);
vo.setPwd(pwd);
vo.setName(name);
vo.setEmail(email);
vo.setJoinDate(joinDate);
//설정된 MemberVO 객체를 다시 ArrayList에 저장
list.add(vo);
}
rs.close();
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
private void connDB() {
try {
Class.forName(driver);
System.out.println("Oracle 드라이버 로딩 성공");
con = DriverManager.getConnection(url, user, pwd);
System.out.println("Connection 생성 성공");
stmt = con.createStatement();
System.out.println("Statement 생성 성공");
} catch (Exception e) {
e.printStackTrace();
}
}
회원 정보 조회 SQL문을 실행하여 조회한 레코드들의 컬럼 값을 다시 MemberVO 객체의 속성에 설정한 다음 ArrayList에 저장하고 호출한 곳으로 반환한다.
회원 정보가 웹 브라우저로 잘 출력되는걸 확인 할 수 있다.
2-2. PreparedStatement를 이용한 회원정보 실습
위의 예제는 Statement인터페이스를 이용하여 데이터베이스와 연동했다.
Statement를 이용해서 데이터베이스와 연동할 경우에는 연동 할 때마다 DBMS에서 다시 SQL문을 컴파일을 해야 하므로 속도가 느리다는 단점이 있다.
이럴 경우 PreparedStatement 인터페이스를 사용하면 SQL문을 미리 컴파일해서 재사용하므로 Statement인터페이스보다 훨씬 빠르게 데이터베이스 작업을 수행할 수 있다.
PreparedStatement 인터페이스의 특징
1) Statement 인터페이스를 상속하므로 지금까지 사용한 메서드를 그대로 사용
2) Statement 인터페이스가 DBMS에 전달하는 SQL문은 단순한 문자열이므로 DBMS는 이 문자열을 DBMS가 이해할 수 있도록 컴파일하여 실행
3) 반면에 PreparedStatement 인터페이스는 컴파일된 SQL문을 DBMS에 전달하여 성능을 향상
4) PreparedStatement 인터페이스에서는 실행하려는 SQL문에 "?"를 넣어서 값만 바꾸어 쉽게 설정이 가능하여 Statement보다 SQL문작성이 편리
MemberDAO.java
// PreparedStatement()메서드에 SQL문을 전달해 PreparedStatemen 객체 생성
pstmt = con.prepareStatement(query);
//executeQuery()메서드를 호출해 미리 설정한 SQL문을 실행
ResultSet rs = pstmt.executeQuery();
DAO클래스에 있는 Statement를 PrepareStatement로 변경해준다.
눈으로 볼때 결과물은 동일하지만, 데이터베이스와 연동할 경우 수행 속도가 좀 더 빠르다는 차이가 있다.
3. DataSource 이용해 데이터베이스 연동하기
위의 예제들에선 다양한 방법으로 회원 테이블에서 회원 정보를 조회하는 과정을 확인해 봤다.
단순 실습용으로 있는 예제들에는 많은 데이터가 들어있지 않아서 속도차이를 느낄 수 없다. 하지만 서비스를 구동하는 온라인 쇼핑몰 같은 경우 Statement 또는 PreparedStatement로 작업을 한다면 비효율적이다.
그래서 현재는 웹 애플리케이션이 실햄된과 동시에 연동할 데이터베이스와의 연결을 미리 설정해두고, 필요할 때마다 미리 연결해 놓은 상태를 이용해 빠르게 연동하여 작업한다. 해당 기술을 커넥션 풀이라고 부른다.
미리 데이터베이스와 연결시킨 상태를 유지하는 기술 = 커넥션풀(ConnectionPool)
3-1. 커넥션풀 동작 과정
1) 톰캣 컨테이너를 실행한 후 응용프로그램을 실행
2) 톰캣 컨테이너 실행시 ConnectionPool 객체를 생성
3) 생성된 커넥션 객체는 DBMS와 연결
4) 데이터베이스와의 연동 작업이 필요할 경우 응용 프로그램은 ConnectionPool에서 제공하는 메서드를 호출하여 연동
톰캣 컨테이너는 자체적으로 ConnectionPool 기능을 제공한다. 톰캣 실행 시 톰캣은 설정 파일에 설정된 데이터베이스 정보를 이용해 미리 데이터베이스와 연결하여 ConnectionPool 객체를 생성한 후 애플리케이션이 데이터베이스와 연동할 일이 생기면 ConnectionPool 객체의 메서드를 호출해 빠르게 연동하여 작업한다.
3-2. JNDI
JNDI(Java Naming and Directory Interface)
필요한 자원을 키/값(key/value) 쌍으로 저장한 후 필요할 때 키를 이용해 값을 얻는 방법
미리 접근할 자원에 키를 지정한 후 애플리케이션이 실행 중일 때 이 키를 이용해 자원에 접근해서 작업을 진행한다.
JNDI의 사용 예
웹 브라우저에서 name/value 쌍으로 전송한 후 서블릿에서 getParameter(name)로 값을 가져올 때
해시맵(HashMap)이나 해시테이블(HashTable)에 키/값으로 저장한 후 키를 이용해 값을 가져올 때
웹 브라우저에서 도메인 네임으로 DNS 서버에 요청할 경우 도메인 네임에 대한 IP 주소를 가져올 때
커넥션풀에 적용하기
톰캣 컨테이너가 ConnectionPool 객체를 생성하면 이 객체에 대한 JNDI 이름(key)을 미리 설정해 놓으면 웹 애플리케이션에서 데이터베이스와 연동 작업을 할 때 이 JNDI 이름(key)으로 접근하여 작업한다.
3-3. 톰캣의 DataSource설정 및 사용방법
톰캣의 ConnectionPool설정 과정
실제 톰캣에서 ConnectionPool 기능을 사용하려면 이 기능을 제공하는 DBCP 라이브러리를 따로 내려받아야 함
이 라이브러리 파일은 다음과 같이 jar 압축 파일 형태로 제공된다.
다운로드 링크
http://www.java2s.com/Code/Jar/t/Downloadtomcatdbcp7030jar.htm
Download tomcat-dbcp-7.0.30.jar : tomcat dbcp « t « Jar File Download
www.java2s.com
3-4. 이클립스에서 톰캣 DataSource 설정
context.xml파일을 보면 <Resource>태그를 이용해 톰캣 실행 시 연결할 데이터베이스를 설정할 수 있다.
오라클데이터베이스를 연결할 때 다른 속성들은 고정적으로 사용하며, 프로그래머가 주로 설정하는 정보는 driverClassName, user, password, url만 변경해서 설정한다.
ConnectionPool로 연결할 데이터베이스 속성
속성 | 설명 |
name | DataSource에 대한 JNDI 이름 |
auth | 인증 주체 |
driverClassName | 연결할 데이터베이스 종류에 따른 드라이버 클래스 이름 |
factory | 연결할 데이터베이스 종류에 따른 ConnectionPool 생성 클래스 이름 |
maxActive | 동시에 최대로 데이터베이스에 연결할 수 있는 Connection 수 |
maxIdle | 동시에 idle 상태로 대기할 수 있는 최대 수 |
maxWait | 새로운 연결이 생길 때까지 기다릴 수 있는 최대 시간 |
user | 데이터베이스 접속 ID |
password | 데이터베이스 접속 비밀번호 |
type | 데이터베이스 종류별 DataSource |
url | 접속할 데이터베이스 주소와 포트 번호 및 SID |
3-5. 톰캣의 DataSource로 연동해 회원정보 조회 실습
MemberDAO.java
private Connection con;
private PreparedStatement pstmt;
private DataSource dataFactory;
public MemberDAO() {
try {
//JNDI에 접근하기 위해 기본 경로 (java:/comp/env)를 지정
Context ctx = new InitialContext();
Context envContext = (Context) ctx.lookup("java:/comp/env");
//context.xml에 설정한 name값인 jdbc/oracle을 이용해 미리 연결한 DataSource를 받아옴
dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
} catch(Exception e) {
e.printStackTrace();
}
}
public List<MemberVO> listMembers() {
List list = new ArrayList();
try {
// DataSource를 이용해 데이터베이스에 연결
con=dataFactory.getConnection();
String query = "select * from t_member ";
System.out.println("prepareStatememt: " + query);
// SQL문으로 회원정보를 조회
pstmt = con.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// 조회한 레코드의 각 컬럼 값을 받아온다.
String id = rs.getString("id");
String pwd = rs.getString("pwd");
String name = rs.getString("name");
String email = rs.getString("email");
Date joinDate = rs.getDate("joinDate");
// 각 컬럼 값을 다시 MemberVO 객체의 속성에 설정한다.
MemberVO vo = new MemberVO();
vo.setId(id);
vo.setPwd(pwd);
vo.setName(name);
vo.setEmail(email);
vo.setJoinDate(joinDate);
// 설정된 MemberVO 객체를 다시 ArrayList에 저장
list.add(vo);
}
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
이전에 데이터베이스와 연결할 때 사용한 connDB()메서드 대신, 생성자에서 톰캣 실행시 톰캣에서 미리 생성한 DataSource를 name값인 jdbc/oracle을 이용해 미리 받아온다. 마지막으로 서블릿에서 listMembers()메서드를 호출하면 getConnection() 메서드를 호출하여 DataSource에 접근한 후 데이터베이스와의 연동 작업을 수행한다.
4. DataSource 이용해 회원 정보 등록하기
memberForm.html
<script type="text/javascript">
function fn_sendMember() {
var frmMember = document.frmMember;
var id = frmMember.id.value;
var pwd = frmMember.pwd.value;
var name = frmMember.name.value;
var email = frmMember.email.value;
if (id.length == 0 || id == "") {
alert("아이디는 필수입니다.");
} else if (pwd.length == 0 || pwd == "") {
alert("비밀번호는 필수입니다.");
} else if (name.length == 0 || name == "") {
alert("이름은 필수 입니다")
} else if (email.length == 0 || email == "") {
alert("이메일 입력은 필수 입니다.");
} else {
frmMember.method = "post";
frmMember.action = "member3";
frmMember.submit();
}
}
</script>
</head>
<body>
<from name="frmMember">
<table>
<th>회원가입창</th>
<tr>
<td>아이디</td>
<td><input type="text" name="id"></td>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="pwd"></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>이메일</td>
<td><input type="text" name="emaile"></td>
</tr>
</table>
<input type="button" value="가입하기" onclick="fn_sendMember()">
<input type="reset" value="다시 입력">
<input type="hidden" name="command" value="addMember">
</from>
</body>
hidden태그를 이용해 회원가입창에서 새 회원 등록요청을 서블릿에 전달한다.
MemberServlet.java
private void doHandle(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
MemberDAO dao=new MemberDAO();
PrintWriter out=response.getWriter();
String command=request.getParameter("command");
if(command!= null && command.equals("addMember")){
String _id=request.getParameter("id");
String _pwd=request.getParameter("pwd");
String _name=request.getParameter("name");
String _email=request.getParameter("email");
MemberVO vo=new MemberVO();
vo.setId(_id);
vo.setPwd(_pwd);
vo.setName(_name);
vo.setEmail(_email);
dao.addMember(vo);
}else if(command!= null && command.equals("delMember")) {
String id = request.getParameter("id");
dao.delMember(id);
}
List list=dao.listMembers();
out.print("<html><body>");
out.print("<table border=1><tr align='center' bgcolor='lightgreen'>");
out.print("<td>아이디</td><td>비밀번호</td><td>이름</td><td>이메일</td><td>가입일</td><td >삭제</td></tr>");
for (int i=0; i<list.size();i++){
MemberVO memberVO=(MemberVO) list.get(i);
String id=memberVO.getId();
String pwd = memberVO.getPwd();
String name = memberVO.getName();
String email =memberVO.getEmail();
Date joinDate = memberVO.getJoinDate();
out.print("<tr><td>"+id+"</td><td>"
+pwd+"</td><td>"
+name+"</td><td>"
+email+"</td><td>"
+joinDate+"</td><td>"
+"<a href='/pro07/member3?command=delMember&id="+id+"'>삭제 </a></td></tr>");
}
out.print("</table></body></html>");
out.print("<a href='/pro07/memberForm.html'>새 회원 등록하기</a");
}
MemberDAO.java
public List<MemberVO> listMembers() {
List<MemberVO> list = new ArrayList<MemberVO>();
try {
// connDB();
con = dataFactory.getConnection();
String query = "select * from t_member ";
System.out.println("prepareStatememt: " + query);
pstmt = con.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String id = rs.getString("id");
String pwd = rs.getString("pwd");
String name = rs.getString("name");
String email = rs.getString("email");
Date joinDate = rs.getDate("joinDate");
MemberVO vo = new MemberVO();
vo.setId(id);
vo.setPwd(pwd);
vo.setName(name);
vo.setEmail(email);
vo.setJoinDate(joinDate);
list.add(vo);
}
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
public void addMember(MemberVO memberVO) {
try {
//DataSource를 이용해 데이터베이스와 연결
con = dataFactory.getConnection();
//테이블에 저장할 회원정보를 받아옴
String id = memberVO.getId();
String pwd = memberVO.getPwd();
String name = memberVO.getName();
String email = memberVO.getEmail();
//insert문을 문자열로 만듦
String query = "insert into t_member";
query += " (id,pwd,name,email)";
query += " values(?,?,?,?)";
System.out.println("prepareStatememt: " + query);
pstmt = con.prepareStatement(query);
//insert문의 각 "?"에 순서대로 회원 정보를 세팅
pstmt.setString(1, id);
pstmt.setString(2, pwd);
pstmt.setString(3, name);
pstmt.setString(4, email);
//회원 정보를 테이블에 추가
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
PreparedStatement에서 insert문 사용하는 방법
1) PreparedStatement의 insert문은 회원 정보를 저장하기 위해 ?(물음표)를 사용함
2) ?는 id, pwd, name, age에 순서대로 대응
3) 각 ?에 대응하는 값을 지정하기 위해 PreparedStatement의 setter를 이용
4) setter( ) 메서드의 첫 번째 인자는 '?'의 순서를 지정
5) ?은 1부터 시작
6) insert, delete, update문은 executeUpdate( ) 메서드를 호출
5. 회원정보 삭제하기
MemberDAO.java
public void delMember(String id) {
try {
con = dataFactory.getConnection();
//delete문을 문자열로 만듦
String query = "delete from t_member" + " where id=?";
System.out.println("prepareStatememt:" + query);
pstmt = con.prepareStatement(query);
//첫번재 "?"에 전달된 ID를 인자로 넣는다
pstmt.setString(1, id);
//delete문을 실행해 테이블에서 해당 ID의 정보를 삭제
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
delete문의 첫번재 "?"에 전달된 ID를 인자로 executeUpdate()메서드를 호출한다
정리
이번장에서는 서블릿이 클라이언트로부터 요청을 받으면 그 요청에 대해 작업을 수행하는 방법에 대해서 알아보았다.
이번에 학습한 6장과 7장이 JSP나 서블릿의 제일 중요한 내용이라고 생각한다.
프로그래밍은 솔직히 반복이라고 생각한다. 해당 내용은 프로젝트를 진행할때 정말 손에 익어서 기계적으로 찍어내는 수준이었는데, 오랜만에 보니 낯설다^^..
회원정보와 같은 기본적인 틀의 코드들은 완벽하게 학습하고 넘어가자!
참고자료 : 자바 웹을 다루는 기술
'💻 Web_Back end > Servlet' 카테고리의 다른 글
서블릿의 필터와 리스너 기능 (0) | 2023.04.26 |
---|---|
쿠키와 세션 (0) | 2023.04.12 |
서블릿 API 사용하기 (0) | 2023.04.12 |
서블릿 기초 2 (0) | 2023.04.06 |
서블릿의 기초1 (0) | 2023.04.05 |