ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 22-09-21_jsp(1)
    main 2022. 9. 21. 12:44

    java코드에서 directly 하게 SQL을 사용할 순 없어서, 이걸 전달할 수 있는 객체가 필요. Statement라는 Query 객체가 필요함. 

     

    query실행을 한다하면, 대부분의 쿼리는 DML임. (이미 존재하는 데이터를 조작하는 언어) (DBL : 데이터를 생성하고 관리함./

    TCL(Data transctionlanguige, transaction 자체를 조작할때 commit이나 이런거) )

     

    DML은

    select계열

    insert, delete, update계열

    로 나뉨

     

    resultSet 받아오기 위한 executeQuery 필요

     

    돌아오는 반환값이 존재하지 않는 executeUpdate

     

    메서드를 통해서 쿼리 실행했다면, 결과값이 있거나, DML이 실행되었거나. 

     

    --

    그러면 그 다음 단계는 결과를 가져오는 것. 

    --

    java쪽에서 표현할땐 object로 표현, 쿼리에선 다르게 표현. 데이터 표현방시기 다름. 

    그래서 데이터 가져올 땐 가장 중요한건, 표현방식을 맞춰줘야 함. 

     

    raw data 가져와서 logic에따라 처리 : 1단계 끝남. 

    그러면 가져왔으니까 연결자원 끝내줌. 그래야 부하가 안 걸림. 

     

    data 가져오는 7단계. 

     

    그 단계 안에 심각한 형태의 코드 낭비가 발생함. 

    코드의 단계가 반복된다는건, 전형적으로 반복되는 코드 형태가 존재한다는 것. 

     

    ex)

    DataBasePropertyDAOImpl 볼 것. 

    저장방법(접근하는DB)가 달라져서 새로운 impl을 만든 것. 

    selectProperty와 selectProperties가 있는데 거의 같음. 중복되는 코드가 많음. 

    이게 바로 쓰레기 코드가 낭비되는 것..

    20:80의 법칙......파레토 법칙

     

    실제 의미 있는 코드는 20%미만이라는 것. 이걸 해결하기 위한 구조가 mybatis, ibatis

     

    오늘까진 jdbc 날코딩 하면서 문제점을 찾아보고, ??? , 그걸 실제로 코드화 시켜보는 framework를 사용해볼것 (3단계로)

     

     

    오늘 수업의 키워드도 DataBase

     

    기본적인 연결에 두 가지 정도를 더 얹으려고 하는데, 

     

    어제 배운 공격쿼리 막는 법 (SQL injection)

    그리고 Connection Pulling을 배울 것

     

    ----

          String sql = "SELECT PROPERTY_NAME, PROPERTY_VALUE, DESCRIPTION FROM DATABASE_PROPERTIES";
                sql += " WHERE PROPERTY_NAME = '"+ propertyName +"'";

    이 코드의 문제점을 찾아보자. 

    톰캣연산자인  propertyName을 넣고 이 리터럴이 문자라는걸 표현하기 위해 single quotation (작은따옴표)가 들어가있죠

     

    Test code에서

          String sql = "SELECT PROPERTY_NAME, PROPERTY_VALUE, DESCRIPTION FROM DATABASE_PROPERTIES";
                sql += " WHERE PROPERTY_NAME = '1' or '1'='1 '";

    우리가  이렇게 연산한거랑 같음. 

     

    or 연산자나 and연산자는 short ???? 연산자... 좌측 피연산자가 false면 위의 연산도 봐야 함. 여기가 true 면 이 조건절도 true가 됨. 

    그럼 아래의 if문을 타고 내력...

     

    개발자의 의도와 다른 상황이 발생...

     

     

    Test 코드 다시 보면...

     

    package kr.or.ddit.props.dao;
    
    import static org.junit.Assert.assertNull;
    
    import org.junit.Test;
    
    import kr.or.ddit.props.vo.PropertyVO;
    
    public class DataBasePropertyDAOImplTest {
    	PropertyDAO dao = new DataBasePropertyDAOImpl();
    	
    	@Test
    	public void testSelectProperty() {
    		PropertyVO vo = dao.selectProperty("1' or '1'='1; delete from member;");
    		System.out.println(vo);
    		//assertNull(vo);
    	}
    
    }

    이런식이 가능해지는 공격방법.... 앞의 연산을 끝내고 뒤에 다 삭제

     

    or가 데이터가 아니라 키워드로 사용됨

    delete가 단순한 데이터가 아니라 delete쿼리문으로 사용됨. 

     

    이 공격방법을 막을 수 있는 가장 간단한 방법

    위의 or, delete가 단순한 텍스트로 사용되면 이 공격기법 막을 수 있죠. 

     

     

    이게 이렇게 쓰여지면 단순 문자가 아니라 쿼리문처럼 쓰여지게 된다는 거예요

    저게 코드의 일부로 쓰이면 안된다는 거예요, 

     

    입력데이터인 parameter를 미리 검증을 하면 해결할 수 있음

    그런데 검증의 폭이 너무 넓음. 그래서 prepared를 사용함. 

     

    저 부분은 문자열이 아니라 

          String sql = "SELECT PROPERTY_NAME, PROPERTY_VALUE, DESCRIPTION FROM DATABASE_PROPERTIES";
                sql += " WHERE PROPERTY_NAME = ?";

    이렇게 코드의 일부로 사용아 안 되게 쿼테이션 사라짐.

    그러면 이제 저 ? 를 누군가 parsing해 줘야 함.

     

    이렇게 prepared로 사용함

     

    그런데 바로 쿼리문을 달라고 함. 그게 특징임. 

    그래서 쿼리문을 동적으로 바꿀 수가 없음. 

          try(
             Connection oracleConn = ConnectionFactory.getConnection();
             PreparedStatement oracleStmt = oracleConn.prepareStatement(sql);
          ) {
             ResultSet rs = oracleStmt.executeQuery();
             									// 괄호 안에 지워짐

     

     

    괄호 안에 쿼리파라미터를 채워 줘야 함. 

     

    쿼리파랕미터는

          String sql = "SELECT PROPERTY_NAME, PROPERTY_VALUE, DESCRIPTION FROM DATABASE_PROPERTIES";
                sql += " WHERE PROPERTY_NAME = ?";

    얘임

     

    그러면

    oracleStmt.setString(1, propertyName);

    이렇게 추가해줌, parameter를 여기서 설정해줌. 1은 db의 인덱스는 1부터 시작하고, 우리가 쿼리파라미터에 ? 가 한개라 하나만 추가해줌. 

     

    이렇게 수정하면

     

    정확하게 null이 나옴. propertyName으로 들어간 or, delete 같은 것들이 쿼리문으로 쓰인 게 아닌 단순한 문자로 쓰이게 된 것

     

    ** 이유를 찾아내고 알아내서 써야지, 그 코드를 납득하고 쓸 수 있다. 

     

    ---

    propertyMng.jsp에 가 봅시다

     

    이제 체크하면 아래에서 값 보여줌

    이제 아래에서 새 프로퍼티 추가 기능을 구현해야 하는데

     

    DATABASE_PROPERTIES 얘는 view라서 얘를 대상으로는 insert 같은게 불가능 함

    여기선 불가능함.

     

       @Override
       public void insertProperty(PropertyVO propertyVO) {
    	   if(1==1)
    		   throw new RuntimeException("해당 뷰는 insert 대상이 아님.");
    
       }

    이 메서드를 오버라이드 해야

     

     

    --

    이제 미션이예요

    저 주석의 내용을 해보세요

    그리고 해당 데이터를 조회하는데 소요된 시간이 얼마인지 체크

     

    <%@page import="java.util.Date"%>
    <%@page import="java.sql.ResultSet"%>
    <%@page import="java.sql.PreparedStatement"%>
    <%@page import="kr.or.ddit.db.ConnectionFactory"%>
    <%@page import="java.sql.Connection"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <!-- Model1 구조를 이용하여, -->
    <!-- 'a001' 사용자의 이름을 조회하라. -->
    
    <%
    	String sql = "SELECT MEM_NAME FROM MEMBER WHERE MEM_ID = ?";
    	String memName = null;
    	
    	long currTime = System.currentTimeMillis();
    	long endTime = 0;
    	double runTime = 0;
    	try(
    		Connection conn = ConnectionFactory.getConnection();
    		PreparedStatement pstmt = conn.prepareStatement(sql);
    	){
    		pstmt.setString(1, "a001");
    		ResultSet rs = pstmt.executeQuery();
    		if(rs.next()){
    			memName = rs.getString("MEM_NAME");
    		}
    	}
    	//전체 코드 실행된 후에 endtime 잡음.
    	endTime = System.currentTimeMillis();
    	runTime = (double)(endTime - currTime); 
    %>
    <h4>'a001' 멤버 이름 : <%=memName %></h4>
    <h4>데이터 조회에 걸린 시간 : <%=runTime %>ms</h4>
    </body>
    </html>

    * 법칙을 하나 정하자, SQL 쓸 때 ,또 테이블 ..이나 컬럼같은 식별자도 대문자로 쓰자... 자바 코드 안에서 snakeCode방식 쓰지 않고 Camel쓰자.

    1. ctrl + shift + y : 대문자 -> 소문자 변환
    2. ctrl + shift + x : 소문자 -> 대문자 변환

     

    자 이제 조회해 볼건데

     

    소요시간이 실행할수록 줄어들죠. 이 시간은 뭘까.

     

    이 소요시간은

    1~4번까지의 작업이 다 끝나는 시간임. 

     

     ( 연결수립, 쿼리실행, ?, 출력 )

     

    이 시간을 최대한 줄이고 싶어. 

    그러면 가장 먼저, 

     

      가장 오랜시간이 걸리는 작업이 무엇인지 찾아야 함. 

     (1, 4가 가장 오래걸림)

     

    코드를 수정해봅시다.

     

    이렇게 for문으로 감싸서 100번 실행하게 해보자

     

    근데 실행하면 500에러나져?

     

    DB가 부하를 감당 못하고 터진거임.

     

    아직 열려있는 연결들을 다 닫기 전에 새 연결들이 계속 열리는거임.

    그런데 우리가 사용하는 버전은 최대부하30개인데 이게 닫히기전에 열리다보니 30개 초과해서 터진거임. 

     

    소요시간이 문제가 아니라 아무리 많은 요청이 들어와도 DB에 걸리는 부하는 일정수준만 걸려야 하고, 그 이상 걸리면 지금처럼 DB가 감당을 못함. 이게 우리가 첫번째 해결해야 하는 문제고

    두번째가 소요시간 줄이기임. 

     

     

     

    DB 새로 하나 만들어 줄거예요

     

     

     

     

    리미트 밸류가 200갠데? 30개가 아니네

    근데 어차피 우리 지금 서버 죽었음. 현재 사용량이 31,32개인데 ..

    ...?????

     

    ORACLE SESSION COUNT SETTING 을 검색해줍니다.

    그러면 ORACLE 사이트의 SESSION COUNT 문서를 확인

    https://community.oracle.com/tech/developers/discussion/464717/session-count

     

    Session count

    Does Oracle XE have a limit on sessions?

    community.oracle.com

    Try this:
    alter system set sessions=200 scope=spfile;
    alter system set processes=200 scope=spfile;

    Then restart the database.

     

    이렇게 하래요

     

     

    이렇게 변경해주고나서, 이제 db를 RESTART 해야 함. 다 접속해제 하고, 이클립스 도는 서버도 끄고 재시작.

     

     

    이제 CMD를 켜서 해줄거예요

    관리자 권한으로 cmd를 실행해주고요

     

    stopdb명령어를 써서 db를 멈춰주시고요

     

    stopdb해주고요

    lsnrctl stop

    으로 리스너를 멈춰주고요

     ** 리스너의 역할? DB도 서버잖아. client의 요청을 받아줘야 하니, 그걸 받아줘야 하는 감시자가 필요한데, 그 감시자가 이 listener임.

    아까는 너무 많은 요청이 있어서 아깐 거절된거임. 

    listner가 없으면 application에서 해당 db를 쓸 수 없음

     

     

     

    그리고 startdb로 db를 재시작해줍니다.

     

     

     

    lsnrctl status 명령어로 db가 실행이 잘 되었는지 확인해줍니다

    저기 뭐 xe가 보이는데 그러면 잘 실행된거래요

     

     

    이제 소스코드를 다시 동작시켜봅시다

    자 이제 한참 걸리긴 하지만 터지지는 않져

    근데 심하게 느리죠? 그러면

    연결을 한번만 하는거로 코드를 수정합시다.

     

    <%@page import="java.util.Date"%>
    <%@page import="java.sql.ResultSet"%>
    <%@page import="java.sql.PreparedStatement"%>
    <%@page import="kr.or.ddit.db.ConnectionFactory"%>
    <%@page import="java.sql.Connection"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <!-- Model1 구조를 이용하여, -->
    <!-- 'a001' 사용자의 이름을 조회하라. -->
    
    <%
    	String sql = "SELECT MEM_NAME FROM MEMBER WHERE MEM_ID = ?";
    	String memName = null;
    	
    	long currTime = System.currentTimeMillis();
    	long endTime = 0;
    	double runTime = 0;
    		try(
    			Connection conn = ConnectionFactory.getConnection();
    			PreparedStatement pstmt = conn.prepareStatement(sql);
    		){
    			for(int i=1; i<=100; i++){
    				pstmt.setString(1, "a001");
    				ResultSet rs = pstmt.executeQuery();
    				if(rs.next()){
    					memName = rs.getString("MEM_NAME");
    		}//4번단계에 눈엔 안보이지만 close가 finally로 돌고있는건데, close되기 전에 open되고있는거.
    	}	//for end
    	//전체 코드 실행된 후에 endtime 잡음.
    		}
    	endTime = System.currentTimeMillis();
    	runTime = endTime - currTime; 
    %>
    <h4>'a001' 멤버 이름 : <%=memName %></h4>
    <h4>데이터 조회에 걸린 시간 : <%=runTime %>ms</h4>
    
    <h4>전체 소요 시간(response time) 확인</h4>
    <h4>한번 연결 수립하고, 한번 쿼리 실행, 한번 출력 : 11.0ms</h4>
    <h4>백번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 1076.0ms</h4>
    <h4>한번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 15.0ms</h4>
    
    </body>
    </html>

    굉장히 빨라짐

     

    여기까지 코드

    <%@page import="java.util.Date"%>
    <%@page import="java.sql.ResultSet"%>
    <%@page import="java.sql.PreparedStatement"%>
    <%@page import="kr.or.ddit.db.ConnectionFactory"%>
    <%@page import="java.sql.Connection"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <!-- Model1 구조를 이용하여, -->
    <!-- 'a001' 사용자의 이름을 조회하라. -->
    
    <%
    	String sql = "SELECT MEM_NAME FROM MEMBER WHERE MEM_ID = ?";
    	String memName = null;
    	
    	long currTime = System.currentTimeMillis();
    	long endTime = 0;
    	double runTime = 0;
    		try(
    			Connection conn = ConnectionFactory.getConnection();
    			PreparedStatement pstmt = conn.prepareStatement(sql);
    		){
    			for(int i=1; i<=100; i++){
    				pstmt.setString(1, "a001");
    				ResultSet rs = pstmt.executeQuery();
    				if(rs.next()){
    					memName = rs.getString("MEM_NAME");
    		}//4번단계에 눈엔 안보이지만 close가 finally로 돌고있는건데, close되기 전에 open되고있는거.
    	}	//for end
    	//전체 코드 실행된 후에 endtime 잡음.
    		}
    	endTime = System.currentTimeMillis();
    	runTime = endTime - currTime; 
    %>
    <h4>'a001' 멤버 이름 : <%=memName %></h4>
    <h4>데이터 조회에 걸린 시간 : <%=runTime %>ms</h4>
    
    <h4>전체 소요 시간(response time) 확인</h4>
    <h4>한번 연결 수립하고, 한번 쿼리 실행, 한번 출력 : 11.0ms</h4>
    <h4>백번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 1076.0ms</h4>
    <h4>한번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 15.0ms</h4>
    
    
    
    </body>
    </html>

     

     

     

    --풀링이라는 개념을 써서 속도를 빠르게 해봅시다. 느리게 하는 원인을 제거해봅시다

     

     

    mariadb 사이트로 다시 갑니다.

    https://mariadb.com/

     

    Open Source Database (RDBMS) for the Enterprise | MariaDB

    MariaDB provides open source database and database as a service (DBaaS) solutions to support scalability, mission-critical deployments, and more.

    mariadb.com

     

     

    저 jdbc가 있져 들어가면

     

    좌측 여러 example 중 connect

     

    제일 아래에

    이런것도 있죠

     

    그런데 우리가 지금 할 것은

     

    시간이 가장 오래걸리는 Connection을 어떻게 해 놓았는가....

     

     

    얘가 apache commons라는게 있는데 dbcp2란 걸 가지고있대요.

    제일 아래에 code example도 있음. 

     

     

    이걸 해보려면 일단 dbcp2가 필요하죠. 아파치 사이트로 갑시다

    commons에
    이렇게 하위놈들이있는데

    여길 들어가보면

     

    dbcp2가 pool2라는 걸 의존하고 있대요 그럼 쟤도 필요한거지

     

     

     

    일단 dbcp2이놈 받아두고

     

    이놈. 근데 오브젝트에 담는대여. 담아두는 그릇인거죠

    일반적인 객체도 풀링의 대상이 될 수 있음. 

     

    이놈도 받아줍니다
    이렇게 다운로드
    이렇게 챙겨서
    이클립스의 lib 폴더에 넣읍시다

     

     

    pool도 똑같이 긇어서 동일한 위치에 넣어주세요
    이렇게

    이제 돌고 있는 서버를 끄고

     

     

     

     

    --

    우리는 ConnectionFactory를 통해서 연결을 하고 있었는데요. 

    여기 들어가보면

    하단에 getconnection,

     

    이건 우리가 요청이 들어올때마다 연결을 시작하고 있었던 거죠. 

    그러면 요청 올때마다 말고 미리 준비를 해 놔야 하죠. 

    그러면 static블럭의 코드가 바뀌어야 하죠

     

     

     

    이제 꺼내는것도 필요...

    dbcp에 다시 가세요

    https://commons.apache.org/proper/commons-dbcp/

     

    DBCP – Overview

    The DBCP Component Many Apache projects support interaction with a relational database. Creating a new connection for each user can be time consuming (often requiring multiple seconds of clock time), in order to perform a database transaction that might ta

    commons.apache.org

     

    여기 example에서 제일 쉬운 소스를 일단 털어올거예요

    https://gitbox.apache.org/repos/asf?p=commons-dbcp.git;a=blob_plain;f=doc/BasicDataSourceExample.java;hb=HEAD 

     

    Apache GitBox Repositories

    ozone.git Scalable, redundant, and distributed object store for Apache ... < 12 hours ago Summary | Short Log | Full Log | Tree View

    gitbox.apache.org

    얘에요

     

    자바 소스들이 보이죠

    connection을 직접 안 만들고 아래에 보면 

     

    conn = dataSource.getConnection();

    datasource라는데에서 받아오죠. 

    그건

    DataSource dataSource = setupDataSource(args[0]);

    이렇게 하고 있는데 이게 어디 있겠죠?

     

        public static DataSource setupDataSource(String connectURI) {
            BasicDataSource ds = new BasicDataSource();
            ds.setDriverClassName("org.h2.Driver");
            ds.setUrl(connectURI);
            return ds;
        }

     

    이렇게 소스를 만드는데, 어디서 많이 본 Driver, url이라는 개념이 사용되네요

     

     우리가 connection을 사용하기 위해 직접 사용을 했었는데,BasicDataSource 라는걸 이용해 대신 사용하고 있죠

     

    이클립스에 가서 보면

    javax는 신형이예요
    잘 구분해야 해요 우리는 commons꺼 쓸거예요

    package kr.or.ddit.db;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.Properties;
    
    import javax.sql.DataSource;
    
    import org.apache.commons.dbcp2.BasicDataSource;
    
    /**
     * 
     * Design Pattern
     *  크게 세 종류가 있음
     *   1. (객체)생성 패턴 : Singleton Pattern, Factory Method Pattern, Builder Pattern
     *   2. (객체 사이의 관계 설정하는) 구조 패턴 : Adapter[Wrapper] Pattern, Proxy Pattern
     *   3. (문제 해결 과정을 어떻게 캡슐화 할 것인지 표현하는) 행위 패턴 : Template Method Pattern, Command Pattern, Strategy Pattern
     *   
     *
     */
    public class ConnectionFactory {
    	//전역변수로 쓰고 static으로 줍니다 
    	private static String oracleURL;
    	private static String oracleUser;
    	private static String oraclePassword;
    	
    	private static DataSource dataSource;
    	
    	static {
    		Properties dbInfo = new Properties();
    		try (
    			InputStream is = 
    				ConnectionFactory.class.getResourceAsStream("/kr/or/ddit/db/DBInfo.properties");
    				
    		){
    			//파일에 있던 데이터가 메모리에 로드 됨. 이제 모든 데이터는 dbInfo를 통해 처리 가능
    			dbInfo.load(is);
    			oracleURL = dbInfo.getProperty("url");
    			oracleUser = dbInfo.getProperty("user");
    			oraclePassword = dbInfo.getProperty("password");
    //			Class.forName(dbInfo.getProperty("driverClassName"));
    			BasicDataSource bds = new BasicDataSource();
    //			bds.setDriverClassName(dbInfo.getProperty("driverClassName"))
    			bds.setUrl(oracleURL);		//이제 url을 이용해서 connection 형성하는게 내가 아니라 library가 됨
    			bds.setUsername(oracleUser);
    			bds.setPassword(oraclePassword);
    			//이제 내 손을 떠나서 라이브러리가 알아서 처리 해 주니까 ClassNotFoundException 이런 거 필요 없죠
    			
    			dataSource = bds;	//dataSource를 bds로 초기화
    			
    		}catch(IOException e) {
    			throw new RuntimeException(e);
    		}
    	}
    	
    	public static Connection getConnection() throws SQLException {
    //		return DriverManager.getConnection(oracleURL, oracleUser, oraclePassword);
    		return dataSource.getConnection();
    	}
    }

     

     

     

     

     

    --

    지금까지는 우리가 리모콘을 써왔음. 직접 연결을 했는데 이제 라이브러리가 만들어 줄거임. 

    	public static Connection getConnection() throws SQLException {
    //		return DriverManager.getConnection(oracleURL, oracleUser, oraclePassword);
    		return dataSource.getConnection();
    	}

    이렇게

    이제 어떤 차이가 있는지 알아보자

     

    <%@page import="java.util.Date"%>
    <%@page import="java.sql.ResultSet"%>
    <%@page import="java.sql.PreparedStatement"%>
    <%@page import="kr.or.ddit.db.ConnectionFactory"%>
    <%@page import="java.sql.Connection"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <!-- Model1 구조를 이용하여, -->
    <!-- 'a001' 사용자의 이름을 조회하라. -->
    
    <%
    	String sql = "SELECT MEM_NAME FROM MEMBER WHERE MEM_ID = ?";
    	String memName = null;
    	
    	long currTime = System.currentTimeMillis();
    	long endTime = 0;
    	double runTime = 0;
    		try(
    			Connection conn = ConnectionFactory.getConnection();
    			PreparedStatement pstmt = conn.prepareStatement(sql);
    		){
    			for(int i=1; i<=100; i++){
    				pstmt.setString(1, "a001");
    				ResultSet rs = pstmt.executeQuery();
    				if(rs.next()){
    					memName = rs.getString("MEM_NAME");
    		}//4번단계에 눈엔 안보이지만 close가 finally로 돌고있는건데, close되기 전에 open되고있는거.
    	}	//for end
    	//전체 코드 실행된 후에 endtime 잡음.
    		}
    	endTime = System.currentTimeMillis();
    	runTime = endTime - currTime; 
    %>
    <h4>'a001' 멤버 이름 : <%=memName %></h4>
    <h4>데이터 조회에 걸린 시간 : <%=runTime %>ms</h4>
    
    <h4>전체 소요 시간(response time) 확인</h4>
    <h4>한번 연결 수립하고, 한번 쿼리 실행, 한번 출력 : 11.0ms</h4>
    <h4>백번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 1076.0ms</h4>
    <h4>한번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : 15.0ms</h4>
    <hr />
    <h4> connection pooling 이후</h4>
    <h4>한번 연결 수립하고, 한번 쿼리 실행, 한번 출력 : ?</h4>
    <h4>백번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : ?</h4>
    <h4>한번 연결 수립하고, 백번 쿼리 실행, 백번 출력 : ?</h4>
    
    
    </body>
    </html>

     

    이렇게 했는데 지금 안되죠?

    드라이버에 문제가 있던거예요 코드를 수정해줍니다.

    이걸 없애놨었어서 그래요 이렇게 추가해줍니다.

     

     

    한번 연결 수립하고, 백번 쿼리 실행, 백번 출력

    잘되고 속도가 아주 줄었죠

     

    이제 두번째 

    백번 연결 수립하고, 백번 쿼리 실행, 백번 출력

     

     

    근데 우리가 풀링해서 속도가 빨라졌지만 놓치고 있는게있져

     

    몇 인분을 준비할건지. 이걸 계산 해 둬야 함.

     

     

    basicdatasource에 기본값이 있어서, 기본적으로 10개의 connection을 미리 만들아ㅓ 놓음

     

     

    ???

    커넥션을 재사용 재사용하는 이점이 있어서 서버 입장에서느 아주 좋음

    DB서버에서는 어떤 상황이 발생해도 일정한 부하가 발생해야 하는 부분이 해결된 것. 

     

     

    connectionFactory에서 10개의 connection을 미리 만들어주니까.

     

    그런데 이걸 계속 두면 안 됨. 

     

    몇 개를 준비할지는 어떻게 결정이되지? 그때그때의 상황에 따라 결정이 되어야 하지.

    미리 만들어야 하는 connection의 갯수는, 내가 개발하고 있는 application의 traffic이 얼마나 발생하느냐에 따라서 달라져야 함,

     

     

    			bds.setInitialSize(5);
    			bds.setMaxWaitMillis(2000);
    			bds.setMaxTotal(10);
    			bds.setMaxIdle(5);

    setInitialSize 이거는 초기값이고 

    setMaxWaitMillis, 나눠줄 수 있는 커넥션이 없을 때 기다려 달라고 하는 시간임. 

    long 타입의 밀리세컨드 달라 함

    저 시간 안에도 반환되는 커넥션이 없어서 연결을 해줄수가 없다?

    그러면 어떻게 해야 할 지를 결정해야 함. 

     

    setMaxTotal

    최대 운영할 수 있는 connection의 갯수를 설정 해 주는 거임

     

    즉, 5개의 커넥션 운영하는데 , 지금 연결할 수 있는 커넥션이 없어. 2초간 기다려줌. 반납된게 없어. 그런데 total이 10개야 그러면 하나의 커넥션을 추가로 만들어줘. 

    그러면 6개지. 6개를 돌려

    이렇게 쭉쭉 가다가

    max가 가득 찼어. 그러면 그때는

    setMaxIdle, 이거는 최대 휴지기임. 놀수있는 애들의 갯수임. 

    놀고있는 커넥션이 있는데, 5개 미만이다? 일안하는애들 놀게 치워버림

     

    --

    ???????

     

    dbinfo.properties 수정할거예요

    driverClassName=oracle.jdbc.driver.OracleDriver
    url=jdbc:oracle:thin:@//localhost:1521/XE
    user=ddit
    password=java
    
    #pooling count
    initialSize=2
    maxTotal=3
    maxIdle=2
    maxWait=2000

    풀링카운트를 수정해줍시다.

     

     

    ConnectionFactory도 이에 맞게 수정하줍니다.

    			bds.setInitialSize(Integer.parseInt(dbInfo.getProperty("initialSize")));
    			bds.setMaxWaitMillis(Long.parseLong(dbInfo.getProperty("maxWait")));
    			bds.setMaxTotal(Integer.parseInt(dbInfo.getProperty("maxTotal")));
    			bds.setMaxIdle(Integer.parseInt(dbInfo.getProperty("maxIdle")));

     

     

     

    --??

     

     

    pooling 하면 singleton의 장점을 대신 사용하기 위해 얘를 사용하기도 함

    singleton은 하나만 인데

    pooling은 여러개를 유지..어..사용...가능...

     

    이걸...해보세요... 숙제....

     

    0--

    암ㄴ럳ㅈㅁ

     

    --

    이제 내일까지

    이 테이블로 연습할거예요

     

     

    ---

    객체지향의 5원칙 SOLID

     

    SRP, 단일책임의원칙...

    model2, mvc, layerd.....적용되어야...

     

    중프때 여러분 코딩할때요

    설계를 하지 않고 기존 코드를 사용해서 코딩부터 했죠

    그럼 잘 안됨

     

    설계를 먼저하자

     

    --종이

     

    DB를 가져오기 위해 먼저 DL필요

     

     

     

    패키지 만들어서, VO를 먼저 만들자.

     

    나중에 우리 VO와 VO 관계를 형성해야 하니까... 기존에 있는 vo 패키지에 만들자

     

     

    -

    DB....

    DATA가 scheme로 관리되는 view를 dictionary view라고 하고 이걸 만들때 쓴ㄷ게 dictionary table

    dictionary view임
    이거 몇개 모아서 만든ㄷ게
    얘임

    다 대문자로 되어있음. 원래 오라클은 대소문자 구분 안하는데 스키마 관리할땐 대문자로 관리함. 

     

     

     

     

    워후 이렇게 나오면 위에 주석처럼 자바에서 하나하나 쓸 일 없이 저 코드를 수정해서 가져가면 되겠죠

     

    슬슬 비슷해지고있어요

    이제 그러면 data type만 String이라 int 등으로 바꾸면 되겠져, 지금 보니까 number인지 아닌지에 따라 나뉜다 정도로 볼 수 있겠져

     

    decode 함수 쓸 수있겠져 근데 if와 else가 있어야 함 얘는 

    DECODE(DATA_TYPE, 'NUMBER','Integer', 'String')

    이렇게

    data type이 number면 integer 아니면(else) string

     

    좋아요 비슷해지고있죠

    --private String memId;
    SELECT 'priavate '|| 
            DECODE(DATA_TYPE, 'NUMBER','Integer', 'String')||
            ' ' ||
            COLUMN_NAME
    FROM COLS
    WHERE TABLE_NAME = 'MEMBER';

     

    근데 DB에서는 snake표기법쓰는데, java에서는 camel 표기법 쓸거거든요

     

     

     

    --private String memId;
    SELECT 'private '|| 
            DECODE(DATA_TYPE, 'NUMBER','Integer', 'String')||
            ' ' ||
            SUBSTR(LOWER(COLUMN_NAME),1,3) ||
            INITCAP(SUBSTR(COLUMN_NAME,5)) ||
            ';'
    FROM COLS
    WHERE TABLE_NAME = 'MEMBER';

    저는 이렇게 짰는데요

     

    쌤은 이렇게 짜셨습니다

    --private String memId;
    SELECT 'private '|| 
            DECODE(DATA_TYPE, 'NUMBER','Integer', 'String')||
            ' ' ||
            LOWER(SUBSTR(COLUMN_NAME,1,1)) ||
            SUBSTR(REPLACE(INITCAP(COLUMN_NAME),'_',''),2 )||
            ';'
    FROM COLS
    WHERE TABLE_NAME = 'MEMBER';

     

    결과는 똑같다 

    복사붙여넣기 하면 이렇게 됩니다. 

     

    이 작업의 목적은, 코드를 쉽게 빨리 짠다. 가 아니라 DATABASE의 scheme 구조를 그대로 반영하기 위한 것임. 

     

     

    이건 자주 쓸거니까요. 저렇게 블럭 씌워서 코드 조각으로 저장
    해 줍니다

     

     

    그럼 이렇게 쓰면 된다.

     

    이 코드조각을 함수나 프로시져로 만들어 보자. 

     

    먼저는 이걸 함수로 할 지 프로시져로 할 지를 결정해야 하는데요

    둘 다 여러개의 명령어를 모아둔 거거든요. 

     

    코드가 실행된 이후에 결과의 존재여부에 따라 결정하면 됩니다. 

     

    지금 이 코드조각은 snake 표기법을 넘기면 camel 표기법이 나온다는 결과값이 있음. 결과가 타입이 있다는건 함수임. 함수로 만들자. 

     

    DBL을 쓰는거예요

     

    --function test(){}, --자바에서는 이렇게 만듦. 여기서도 똑같은 구조 필요
    
    CREATE OR REPLACE FUNCTION SNAKETOCAEML()
    IS
    BEGIN
    END;

    오라클에서는 함수를 중괄호 대신 저렇게 시작과 종료를 정하거든요

     

    이렇게 함수 만들어서 

    이렇게 만들면 됩니다.

     

    좌측 하단에 함수. 여기서 볼수도 수정할수도 있음. 지금 에러가 좀 떠있지만

     

     

     

    이렇게 함수를 만들어두면 위에 만든 코드조각이

    이렇게 심플해지죠

     

    이제 이  함수를 이용해서 코드조각을 수정해 놓으시면 됩니다.  

     

     

     

    --

    이제 다시 이클립스로 돌아가서 vo를 완성해줍니다. 

    *

    db에 primary key는 뭐예요?

     1) 유일한 키다

     2) 나머지 컬럼들은 p.k에 완전종속된다. 

    *

    즉 pk 하나만 동일하면 동일한 객체로 판단된다 입니다. 그래서 

    VO만들 때 equals를 설정 해 줄때도

     

    이렇게 memId라는 키 하나만 동일하면 동일하나는 scheme를 반영해줍니다. 

     

    복합키의 경우에도 복합키도 반영해줘야 함

     

     

    toString도

     

    --

     

     

    혹시 이 안에 BLOB, CLOB 타입이 있으면, (Large object) 이거 toString으로 찍으면 부하 걸려서 에러나니까 빼 줘야 함

     

    마지막으로 serialize 해 줘야 하는데

    저 정보들은 넘어가면 안 되니까 transient 처리를 해 줍니다.

    (transient 는 Serialize하는 과정에 제외하고 싶은 경우 선언하는 키워드입니다.)

     

    @JsonIgnore

    처리로 마샬링에서도 제외해줍니다. 

    (@JsonIgnore어노테이션이 붙은 필드는 json으로 출력시에 무시된다)

     

    ---

    이제 dao만뜰거예요 interface로

     

    alt+shift+j.... 메서드설명주석...

    주석을 넣는 이유는, 나중에 메서드 코드를 예상 할 수도 있고, 협업시에 동료에게 알릴 수도 있음. 

    package kr.or.ddit.member.dao;
    
    import java.util.List;
    
    import kr.or.ddit.vo.MemberVO;
    
    /**
     * 회원 관리(Persistence Layer), CRUD
     * CRUD를 할 수 있는 메서드의 시그니쳐 필요. 
     * 새로 만드는 코드인C, 가져오는 R, 그리고 update와 delete
     *
     */
    public interface MemberDAO {
    	/**
    	 * 회원 정보 등록
    	 * @param member
    	 * @return 성공 : 1, 실패 : 0
    	 */
    	public int insertMember(MemberVO member);
    	
    	/**
    	 * 회원 정보 상세 조회 
    	 * @param memId 조회할 회원의 아이디
    	 * @return 존재하지 않는다면, null 반환
    	 */
    	public MemberVO selectMember(String memId);
    	
    	/**
    	 * 회원 목록 조회
    	 * @return  size==0 테이블 empty
    	 */
    	public List<MemberVO> selectMemberList();
    	
    	/**
    	 * 회원 정보 수정
    	 * @param member
    	 * @return 성공 : 1, 실패 : 0
    	 */
    	public int updateMember(MemberVO member);
    	
    	/**
    	 * 회원 정보 삭제
    	 * @param memId
    	 * @return 성공 : 1, 실패 : 0
    	 */
    	public int deleteMember(String memId);
    }

     

    이렇게 만들어 둘 거예요

     

    인터페이스가 있으면 구현체가 있어야겠죠

    memberDAO에서는 데이터를 받아서 갈아끼우는 코드가 많을 거예요

    raw data를 받아올건데 이걸 가공해야 하는 Business Logic Layer가 필요한거죠

    그러면 걔랑 DAO 사이에 관계가 형성되어야 해요

     

     

    이제 service inferface, business logic layer를 만들건데요

     

    메서드들의 반환형식을 int로 놓기에는 이게 성공인지 실패인지 모르잖아요? 그러니까 enum type을 하나 새로 만들어서 성공, 실패 여부를 확인합시다. 

     

    package kr.or.ddit.enumpkg;
    
    public enum ServiceResult {
    	OK, FAIL, INVALIDPASSWORD
    }

    이렇게 만들어줘요

    그러면 세 가지의 케이스로 세 가지를 식별할 수 있죠 SERVICE에서 
    (성공, 실패, 잘못된 비밀번호)

     

    그리고 business Logic Layer를 이렇게 만들어줘요

    package kr.or.ddit.member.service;
    
    import java.util.List;
    
    import kr.or.ddit.enumpkg.ServiceResult;
    import kr.or.ddit.vo.MemberVO;
    
    /**
     * 
     * 회원 관리(Business Logic Layer)
     *
     */
    public interface MemberService {
    	public ServiceResult createMember(MemberVO member);
    	public MemberVO retrieveMember(String memId);
    	public List<MemberVO> retrieveMemberList();	
    	public ServiceResult modifyMember(MemberVO member);
    	public ServiceResult removeMember(MemberVO member);
    }

    retrieveMember에서 회원이 존재하지 않는다는 정보를 포함할 수 없는... null값이 반환된다고 하면. 

    그러면 null 말고, 그런 사람 없어요. 를 반환해 주는게 나음. 

     

     

     

    ---오후에는...나머지 layer들을 짤거고요..

     

     

     

     

    -- 못쓴거있을건데 이따 수정해야지^^....

    'main' 카테고리의 다른 글

    정규식 편하게하는 라이브러리  (0) 2022.09.23
    22-09-21_jsp(2)  (0) 2022.09.21
    22-09-20_jsp  (2) 2022.09.20
    22-09-20_python  (0) 2022.09.20
    22-09-19_jsp  (2) 2022.09.19
Designed by Tistory.