본문 바로가기
Study/중앙정보처리학원과정

54일차. MVC Pattern에서 Controller의 역활 그리고 Connection Pool을 이용한 DB 접근

by 얏옹이 2023. 1. 6.
반응형

Controller의 역활

 

중요하다고 꼭꼭 외워두라고 강사님이 말씀하신 4가지 역활

 

  1. Parameter를 얻어온다.
  2. 비즈니스 로직을 실행한다
  3. 수행 결과를 모델화 한다
  4. response의 결과에 따라 보여줄 View를 결정한다

 

우리는 이 기능을 숙달시키기 위해서 View를 만들고, properties 파일로 URL Mapping을 해주고, 

 

그 Mapping한 URL로 접근을 통해 Controller를 호출하여 비즈니스 로직을 수행한후, 수행 결과를 Response 받았다면 해당 Response에 따라 View를 각각 지정해서 보여주는 실습을 하고 있다.

 

/join.do=member.command.JoinHandler
/login.do=auth.command.LoginHandler
/logout.do=auth.command.LogoutHandler
/changePwd.do=member.command.ChangePasswordHandler
/article/write.do=article.command.WriteArticleHandler
/article/list.do=article.command.ListarticleHandler
/article/read.do=article.command.ReadArticleHandler
/article/modify.do=article.command.ModifyArticleHandler

 

이는 URL의 주소에 따라서 호출되는 Controller를 각각 지정해준 properties 파일이다.

 

http://Ip주소(localhost):8088(포트번호)/ContextPath 뒤에 /join.do를 입력하면 member.command 패키지 안의 JoinHandler라는 Java 클래스가 실행된다

 

package member.command;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mvc.command.CommandHandler;

public class JoinHandler implements CommandHandler {
	private static String FORM_VIEW = "view/member/joinForm_p600.jsp";
	private static String TEST_VIEW = "view/testView.jsp";


	@Override
	public String process(HttpServletRequest request, HttpServletResponse response) throws Exception {
//		1.파라미터 가져오기
//		2.비즈니스 로직 수행
//		3.response 결과 모델화
//		4.view 지정
		
		System.out.println("JoinHandler 진입 성공");
		return FORM_VIEW;
	}

}

 

호출된 JoinHandler 클래스이다.

 

CommandHandler Interface를 상속받아서 필수적으로 process라는 Method를 Override 해야하며 이 메소드를 통하여

 

Reqest와 Response가 호출될때마다 수시로 호출하고 응답하게 된다.

 

여기에서 일단은 Controller의 역활중 4번으로 언급한 View 지정에 대해서만 현재 지정해주었다.

 

Final 상수 메소드로 FORM_VIEW라는 변수에 보여줄 View(JSP파일)에 대한 경로를 지정해주었고. 해당 변수를 리턴값으로 설정하였다.

 

이렇게 설정함으로써 우리는 http://localhost:8088/mBoard/join.do 라는 URL을 호출하면 해당 과정을 통하여 최종적으로는 joinForm.jsp라는 view로 이동되는것이다.

 

Server를 올리고 인터넷창을 켜서 해당 경로로 접속을 해줘보았다. Port번호는 사용자마다 다르게 적용되어있을수도있다.

 

종종 Tomcat server를 올리다보면 port 충돌이 일어날수 있어서 변경해주는 경우가 많다. 필자같은경우에는 port번호가 9015이다.

 

따라서 http://localhost:9015/memberBoard/join.do를 입력하면

 

 

이런식으로 간단하게 만들어놓은 회원가입 View Page가 출력되게 된다.

 

이것이 바로 MVC Pattern의 C(Controller)를 통해 V(View)를 보여주고 컨트롤 하는것이다. 

 

다음은 Connection pool 방식으로 DB의 Connection과 각종 DB 접근 관련 객체를 얻어오는 방법을 알아보겠다.

 

우리는 기존에 JAVA 혹은 JavaScript에서 코드로 하나하나 한땀한땀 정성들여 Connection 클래스를 불러와 객체를 생성해주며 해당 파일을 실행할때마다 Connection 객체를 얻어오면서 수업을 진행했었다.

 

하지만 이제는 별도로 분리된 Class를 이용하여 Connection 객체를 얻는 Class / 각종 객체들을 닫아주는 Close Class 등 기능별로 Class를 세분화 하여 그때그때 필요한 객체를 생성하고 Connection을 유지해주는 관리를 해줄수 있다.

 

먼저 DBCPinitListner라는 클래스이다.

 

package jdbc;

import java.io.IOException;
import java.io.StringReader;
import java.sql.DriverManager;
import java.util.Properties;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDriver;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

//커넥션관련코드(p418->p510->p573->)p583
//DBCP를 이용하여 커넥션풀 사용(P417참고)
public class DBCPInitListener implements ServletContextListener {

	//poolConfig 컨텍스트 초기화
	@Override 
	public void contextInitialized(ServletContextEvent sce) {
		String poolConfig = 
				sce.getServletContext().getInitParameter("poolConfig");
		Properties prop = new Properties();//Properties객체생성
		//"키=값"형식의 문자열로부터 Properties를 로딩
		//String 키명=String 값
		try {
			prop.load(new StringReader(poolConfig));//prop.load() "키=값"형식의 문자열로부터 Properties를 로딩
		} catch (IOException e) {
			throw new RuntimeException("config load fail", e);
		}
		loadJDBCDriver(prop);
		initConnectionPool(prop);
	}
	
	//매개변수 Properties prop에 담겨온 내용을 이용하여 JDBCDriver를 로딩
	private void loadJDBCDriver(Properties prop) {
		       //"키=값"형식
		//jdbcdriver=com.mysql.jdbc.Driver
		String driverClass = prop.getProperty("jdbcdriver");
		//com.mysql.jdbc.Driver값이  String타입의 driverClass변수에 저장
		try {
			Class.forName(driverClass);
		} catch (ClassNotFoundException ex) {
			throw new RuntimeException("fail to load JDBC Driver", ex);
		}
	}

	//ConnectionPool 초기화
	private void initConnectionPool(Properties prop) {
		try {//참고 jdbc:mysql://ip주소 : port번호/DB스키마명?characterEncoding=UTF-8&serverTimezone=UTC
			/*     키명=값
			 *  jdbcUrl=jdbc:mysql://localhost:3306/board?characterEncoding=utf8&serverTimezone=UTC
				dbUser=jspexam
				dbPass=jsppw
			 */
			String jdbcUrl = prop.getProperty("jdbcUrl");
			String username = prop.getProperty("dbUser");
			String pw = prop.getProperty("dbPass");

			ConnectionFactory connFactory = 
					new DriverManagerConnectionFactory(jdbcUrl, username, pw);

			PoolableConnectionFactory poolableConnFactory = 
					new PoolableConnectionFactory(connFactory, null);
			
			//풀의 커넥션 (유효성)검사		
			String validationQuery = prop.getProperty("validationQuery");
			if (validationQuery != null && !validationQuery.isEmpty()) {
				poolableConnFactory.setValidationQuery(validationQuery);
			}
			GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
			poolConfig.setTimeBetweenEvictionRunsMillis(1000L * 60L * 5L);
			poolConfig.setTestWhileIdle(true);
			
			//최소 유휴 커넥션설정
			//최소 유휴 커넥션:사용되지 않고 풀에 저장될 수 있는 최소 커넥션 개수
			int minIdle = getIntProperty(prop, "minIdle", 5);
			poolConfig.setMinIdle(minIdle);
			
			//최대 유휴 커넥션설정
			int maxTotal = getIntProperty(prop, "maxTotal", 50);
			poolConfig.setMaxTotal(maxTotal);

			GenericObjectPool<PoolableConnection> connectionPool = 
					new GenericObjectPool<>(poolableConnFactory, poolConfig);
			poolableConnFactory.setPool(connectionPool);
			
			//org.apache.commons.dbcp2패키지.PoolingDriver클래스를 불러오기
			Class.forName("org.apache.commons.dbcp2.PoolingDriver");
			PoolingDriver driver = (PoolingDriver)
				DriverManager.getDriver("jdbc:apache:commons:dbcp:");
			
			//poolName을 이용해서 가져온 board값을
			//String타입의  poolName변수에 저장한다
			//여기에서는 ->String poolName="board";
			String poolName = prop.getProperty("poolName");//poolName을 가져와 변수에 저장
			driver.registerPool(poolName, connectionPool);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	
	private int getIntProperty(Properties prop, String propName, int defaultValue) {
		String value = prop.getProperty(propName);
		if (value == null) return defaultValue;
		return Integer.parseInt(value);
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
	}

}

 

반응형

 

그리고 web.xml파일에 추가해줄 설정코드이다.

 

  <!-- p585. servlet context listener 등록 -->
  <listener>
	<listener-class>jdbc.DBCPInitListener</listener-class>
  </listener>
  
  <!-- getInitParameter("PoolConfig"); -->
  <context-param>
  	<param-name>poolConfig</param-name>
  	<param-value>
  	jdbcdriver=com.mysql.jdbc.Driver
  	jdbcUrl=jdbc:mysql://localhost:3306/board?useUnicode=true&amp;characterEncoding=utf8
  	dbUser=gangnam
  	dbPass=asdf111
  	validationQuery=select 1
  	minIdle=3
  	maxTotal=30
  	poolName=board
  	</param-value>
  </context-param>

 

무슨기능인지 정확하게는 모르더라도 코드만봐도 Parameter엔 Mysql의 URL, ID, PASSWORD값이 포함되어있고

이 Parameter의 이름은 PoolConfig라고 되어있다.

 

해당 이름을 호출해줌으로써 우리는 늘 String 변수에 URL값 입력하고, ID값 입력하고 하는 번거로움을 덜수 있는것이다.

 

잘 해놓은 설정환경 파일이 열아들 안부럽다더니....그말이 딱이다.

 

그리고 Connection 객체만 따로 관리하는 ConnectionProvider라는 클래스를 하나 더 생성해주겠다.

 

package jdbc.conn;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

//web.xml에서 설정한 poolName=board으로 지정한 board를 풀 이름으로 사용하여 Connection을 제공하는 클래스
public class ConnectionProvider {
	
	public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection("jdbc:apache:commons:dbcp:board");
		
	}
	
}

 

 

Static 메소드기때문에 객체를 생성해주지 않아도 클래스명.메소드명으로 호출이 가능하다.

 

정리하자면 ConnectionProvider.getConnection(); 으로 Connection을 가져온다. getConnection이 Connection을 반환하기때문에 Connection 타입의 참조변수 = ConnectionProvider.getConnection();으로 객체를 생성해주고

 

PrepareStatement 타입의 참조변수 = Connection 타입의 참조변수.PrepareStatements(쿼리문); 으로 정상 쿼리문을 입력해준다.

 

그리고 Preparestatement타입의 참조변수.executequery() 혹은 executeUpdate()를 통하여 쿼리문을 실행해준후

 

반환받은 Response를 Return 혹은 출력해주면 되는것이다.

 

처음 환경설정이 까다로워서 그렇지 잘 해놓고 나면 나중에는 계속 객체를 꺼내어서 쓰고 닫고 해주면 되기때문에 훨씬 편리하다.

 

마지막으로 사용한 객체들은 close처리 해줘야하니 Close 클래스도 하나 생성했다.

 

package jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

//jdbcutil 클래스, 객체를 닫고, 롤백에 대한 기능을 제공하는 클래스
public class JdbcUtil {
	
	public static void close(ResultSet rs) {
		if(rs != null) {
			try {
				 rs.close();
			} catch(SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	public static void close(Statement stmt) {
		if( stmt != null) {
			try {
				 stmt.close();
			} catch(SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	
	public static void close(Connection conn) {
		if( conn != null) {
			try {
				 conn.close();
			} catch(SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	public static void rollback(Connection conn) {
		if( conn != null) {
			try {
				 conn.rollback();
			} catch(SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
} //class의 끝

 

코드내용을 보면 알겠지만 ResultSet의 참조변수 rs, Connection 타입의 conn 참조변수, Preparestatement 타입의 stmt

 

이 셋을 if문을 이용하여 Null이 아닐때 닫아준다.

 

따라서 모든 작업이 끝나면 꼭 이 Overloding된 Method를 호출하여 각각의 매개변수를 참조하여 각 객체들을 Close 해준다.

 

반응형