---
title: "Java 정리 46"
date: "2019-01-21"
tags: ["Java"]
status: archived
verified: false
---

---


## 개발

* 핵심 기능 구현 완료..
* [도시락 주문 관리 프로그램 패키지](https://github.com/younggeun0/SSangYoung/tree/master/dev/workspace/lunch_prj/src/kr/co/sist/lunch)

* 완성된 클래스다이어그램
![01](/md/legacy-assets/java/46/01.webp)

* **Thread로 서버측에서 클라이언트 주문을 읽기**
  * 서버측에서 DB내용을 계속 읽어들이면 변경 내용을 바로 JTable에 표현가능
    * 다만 **부하가 많이 발생**
    * **Thread.sleep()을 사용하여 일정시간마다 갱신하는 방법을 사용가능**
  * 또는 **소켓으로 데이터를 받으면 갱신하는 방법도 가능**
    * read()메소드는 무한루프에 있더라도 읽어들일 내용이 있을때만 동작
    * 부하가 적다.

```java
// LunchMainController, Runnable을 implements

@Override
public void run() {
     // 30초마다 한번씩 조회를 수행
     try {
          while (true) {
              searchOrder();
              Thread.sleep(1000 * 30);
          }
     } catch (InterruptedException e) {
          JOptionPane.showMessageDialog(lmv, "조회 중  문제가 발생");
          e.printStackTrace();
     }
}

...

@Override
public void mouseClicked(MouseEvent me) {
     if (me.getSource() == lmv.getJtb()) { // 탭에서  클릭이 발생했을 때만
          if (lmv.getJtb().getSelectedIndex() == 1) { //  두번째 탭(주문)에서 이벤트 발생
              // 실시간으로 DB를 조회하여 주문현황을  갱신
              if (threadOrdering == null) {
                   threadOrdering = new Thread(this);
                   threadOrdering.start();
              }
          }
     }
...
```

* 클라이언트 주문 목록 조회를 **프로시저**로 구현
  * **Out Parameter**로 커서를 전달할 땐 **SYS_REFCURSOR** 사용

```sql
-- procedure lunch_order_select.sql

CREATE OR REPLACE PROCEDURE lunch_order_select(
    i_order_name IN VARCHAR2,
    i_phone IN VARCHAR2,
    cur_order_list OUT SYS_REFCURSOR
)
IS                                           

BEGIN                                        

    OPEN cur_order_list FOR
        SELECT l.lunch_name,
            TO_CHAR(o.order_date, 'yyyy-mm-dd day hh24:mi:ss') order_date,
             o.quan
        FROM ordering o, lunch l
        WHERE l.lunch_code = o.lunch_code
         AND o.order_name=i_order_name AND o.phone=i_phone
        ORDER BY o.order_date DESC;

END;
/
```

![02](/md/legacy-assets/java/46/02.webp)

![03](/md/legacy-assets/java/46/03.webp)

* 자바에서 프로시저를 사용할 땐 **CallableStatement**를 사용

```java
// LunchClientDAO
...
public List<OrderListVO> selectOrder(OrderInfoVO oivo)  throws SQLException {
     List<OrderListVO> list = new  ArrayList<OrderListVO>();
     
     Connection con = null;
     CallableStatement cstmt = null;
     ResultSet rs = null;
     
     try {
          
          con = getConn();
          cstmt = con.prepareCall(" { call  lunch_order_select(?,?,?) }");
          // in parameter : 외부 값을 프로시저 안으로  넣을 때
          cstmt.setString(1, oivo.getOrderName());
          cstmt.setString(2, oivo.getOrderTel());
          // out parameter : 프로시저 내부 값을 외부에서  받을 때
          cstmt.registerOutParameter(3,  OracleTypes.CURSOR);
          
          cstmt.execute();
          
          rs = (ResultSet)cstmt.getObject(3);
          
          OrderListVO olvo = null;
          while(rs.next()) {
              olvo = new  OrderListVO(rs.getString("lunch_name"),
                        rs.getString("order_date"),  rs.getInt("quan"));
              System.out.println(olvo);
              list.add(olvo);
          }
          
     } finally {
          if (rs != null) { rs.close(); }
          if (cstmt != null) { cstmt.close(); }
          if (con != null) { con.close(); }
     }
     return list;
}
...
```

* 접두사, 접미사가 정해진 파일을 한정지을 땐 **contains말고 startsWith나 endsWith를 사용한다!**

```java
private String[] lunchImageList() {
     String[] fileList = null;
     String path =  "C:/Users/owner/youngRepositories/SSangYoung/dev/workspace/lunch_prj/src/kr/co/sist/lunch/user/img/";
     File dir = new File(path);

     // 큰 이미지(s_가 붙지 않은)만 배열에 넣으세요.
     List<String> listFileName = new ArrayList<String>();
     for(String f_name : dir.list()) {
          if (!f_name.startsWith("s_")) { // contains사용하면 위험!!!
              listFileName.add(f_name);
          }
     }
     
     fileList = new String[listFileName.size()];
     listFileName.toArray(fileList);
     
     return fileList;
}
```


* 서버에 도시락 추가 시 클라이언트가 가지지 않은 이미지 파일을 전송하는 기능이 필요

![04](/md/legacy-assets/java/46/04.webp)

* 도시락 프로그램은 2 Tier 구조
  * DB 서버 - 서버
  * DB 서버 - 클라이언트
* 메뉴를 추가하면 추가된 이미지가 서버에만 추가됨
  * 클라이언트에서 서버에 이미지명 배열을 보내면 서버가 없는 파일을 판단 후 파일로 전달
  * 클라이언트와 서버에 Tier가 추가되어 n Tier 구조가 된다.

![05](/md/legacy-assets/java/46/05.webp)

* 웹에선 DB 서버- Web 서버 - Web 클라이언트 3 Tier 구조가 일반적
  * DB서버와 Web 서버 사이에 Middleware가 들어가 n Tier 구조가 될 수 있음

![06](/md/legacy-assets/java/46/06.webp)


## 데이터를 전달하는 방식

* **Server Push**
  * **서버가 모든 클라이언트에게 발생된 데이터를 보내주는 방식**
    * 클라이언트는 가만히 있어도 데이터가 갱신된다
    * 단점은 서버가 모든 클라이언트를 관리해야 함
      * 자원 소모가 심함(웹에선 불가능)
* **Client Pull**
  * **클라이언트가 서버로 데이터를 요청**하여 변경된 데이터를 받는 방식
  * **서버가 클라이언트를 관리할 필요가 없다.**
* 도시락앱에선 이미지명을 담은 배열을 서버에 보내 **Client Pull** 수행
  * **서버** 
    * [LunchAdminMain](https://github.com/younggeun0/SSangYoung/blob/master/dev/workspace/lunch_prj/src/kr/co/sist/lunch/admin/run/LunchAdminMain.java)
      * 서버가 시작되자마자 서버가 가진 이미지파일목록을 static list변수에 저장하고 FileServer Thread를 실행시킨다.
    * [FileServer](https://github.com/younggeun0/SSangYoung/blob/master/dev/workspace/lunch_prj/src/kr/co/sist/lunch/admin/file/FileServer.java)
      * FileServer는 접속한 클라이언트가 있으면 클라이언트가 보내온 이미지파일목록과 비교하여 없는 파일을 보내주는 Thread
  * **클라이언트** [LunchClientController](https://github.com/younggeun0/SSangYoung/blob/master/dev/workspace/lunch_prj/src/kr/co/sist/lunch/user/controller/LunchClientController.java)
    * 클라이언트는 이벤트객체가 생성되면서 현재 이미지파일목록을 서버에 전송하고 없는 파일을 전달받는다.
* **다시 정리**
  * 서버는 시작되면서 static list변수로 현재 가진 이미지파일목록을 저장
  * 클라이언트는 시작하면 자신이 가진 이미지파일목록을 서버에 전달
  * 서버는 클라이언트가 보낸 목록과 static변수와 비교, 없는 파일의 수를 클라이언트에게 전달
    * 클라이언트가 가지지 않은 파일명과 파일조각(바이트배열)을 파일의 수만큼 반복해서 서버는 전송, 클라이언트는 전달 받는다.
