내 위치 기반 공공 와이파이 정보를 제공하는 웹서비스 개발

웹 서비스 개발과정

서울 공공 와이파이 Open API는 아래 링크에서 가져와서 사용. Seoul Data Link

샘플 URL, 요청인자와 출력값등을 먼저 확인하고 해당 데이터가 잘 전달되는지 간단하게 테스트 해보기 위해 Postman으로 먼저 샘플 URL로 Get방식으로 요청을 보내 응답데이터를 확인해 보았습니다.

키는 서울 공공와이파이 사이트에 가입하셔서 인증키 신청하셔서 발급 받으시면 됩니다. http://openapi.seoul.go.kr:8088/7378727a7473656f3537714e6f7557/json/TbPublicWifiInfo/1/5 5개 정도만 받아서 확인해보니, 잘 넘어오는군요.

{
    "TbPublicWifiInfo": {
        "list_total_count": 24584,
        "RESULT": {
            "CODE": "INFO-000",
            "MESSAGE": "정상 처리되었습니다"
        },
        "row": [
            {
                "X_SWIFI_MGR_NO": "ARI00019",
                "X_SWIFI_WRDOFC": "서대문구",
                "X_SWIFI_MAIN_NM": "상수도사업본부",
                "X_SWIFI_ADRES1": "서소문로 51",
                "X_SWIFI_ADRES2": "본관 4F",
                "X_SWIFI_INSTL_FLOOR": "",
                "X_SWIFI_INSTL_TY": "7-1-3. 공공 - 시산하기관",
                "X_SWIFI_INSTL_MBY": "서울시(AP)",
                "X_SWIFI_SVC_SE": "공공WiFi",
                "X_SWIFI_CMCWR": "자가망_수도사업소망",
                "X_SWIFI_CNSTC_YEAR": "2019",
                "X_SWIFI_INOUT_DOOR": "실내",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.561924",
                "LNT": "126.96675",
                "WORK_DTTM": "2024-07-04 11:12:58.0"
            },
            {
                "X_SWIFI_MGR_NO": "DDM200019",
                "X_SWIFI_WRDOFC": "동대문구",
                "X_SWIFI_MAIN_NM": "행복마을경로당",
                "X_SWIFI_ADRES1": "고산자로56길 53",
                "X_SWIFI_ADRES2": "1층 거실",
                "X_SWIFI_INSTL_FLOOR": "1",
                "X_SWIFI_INSTL_TY": "6-2. 복지 - 노인",
                "X_SWIFI_INSTL_MBY": "자치구",
                "X_SWIFI_SVC_SE": "",
                "X_SWIFI_CMCWR": "임대망",
                "X_SWIFI_CNSTC_YEAR": "2021",
                "X_SWIFI_INOUT_DOOR": "실내",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.589996",
                "LNT": "127.03865",
                "WORK_DTTM": "2024-07-04 11:12:59.0"
            },
            {
                "X_SWIFI_MGR_NO": "DDM200020",
                "X_SWIFI_WRDOFC": "동대문구",
                "X_SWIFI_MAIN_NM": "전농1동경로당",
                "X_SWIFI_ADRES1": "전농로27길 81",
                "X_SWIFI_ADRES2": "1층 안방 문 앞",
                "X_SWIFI_INSTL_FLOOR": "1",
                "X_SWIFI_INSTL_TY": "6-2. 복지 - 노인",
                "X_SWIFI_INSTL_MBY": "자치구",
                "X_SWIFI_SVC_SE": "",
                "X_SWIFI_CMCWR": "임대망",
                "X_SWIFI_CNSTC_YEAR": "2021",
                "X_SWIFI_INOUT_DOOR": "실내",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.578793",
                "LNT": "127.052124",
                "WORK_DTTM": "2024-07-04 11:12:59.0"
            },
            {
                "X_SWIFI_MGR_NO": "DDM240009",
                "X_SWIFI_WRDOFC": "동대문구",
                "X_SWIFI_MAIN_NM": "신답초스쿨존",
                "X_SWIFI_ADRES1": "전농동 625-6",
                "X_SWIFI_ADRES2": "JN1_C077",
                "X_SWIFI_INSTL_FLOOR": "-",
                "X_SWIFI_INSTL_TY": "1. 주요거리",
                "X_SWIFI_INSTL_MBY": "자치구",
                "X_SWIFI_SVC_SE": "",
                "X_SWIFI_CMCWR": "자가망(U-무선망)",
                "X_SWIFI_CNSTC_YEAR": "2023",
                "X_SWIFI_INOUT_DOOR": "실외",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.577408",
                "LNT": "127.04498",
                "WORK_DTTM": "2024-07-04 11:12:59.0"
            },
            {
                "X_SWIFI_MGR_NO": "ARI00020",
                "X_SWIFI_WRDOFC": "서대문구",
                "X_SWIFI_MAIN_NM": "상수도사업본부",
                "X_SWIFI_ADRES1": "서소문로 51",
                "X_SWIFI_ADRES2": "본관 4F",
                "X_SWIFI_INSTL_FLOOR": "",
                "X_SWIFI_INSTL_TY": "7-1-3. 공공 - 시산하기관",
                "X_SWIFI_INSTL_MBY": "서울시(AP)",
                "X_SWIFI_SVC_SE": "공공WiFi",
                "X_SWIFI_CMCWR": "자가망_수도사업소망",
                "X_SWIFI_CNSTC_YEAR": "2019",
                "X_SWIFI_INOUT_DOOR": "실내",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.561924",
                "LNT": "126.96675",
                "WORK_DTTM": "2024-07-04 11:12:58.0"
            }
        ]
    }
}

넘어오는 데이터와 프로젝트에서 요구하는 화면에 필요한 데이터를 보여주기 위해 테이블 생성과 클래스 모델링을 해주었습니다.

Table DDL

wifiinfo

CREATE TABLE `wifiinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mgrNo` varchar(50) DEFAULT NULL,
  `wrdofc` varchar(50) DEFAULT NULL,
  `mainNm` varchar(100) DEFAULT NULL,
  `adres1` varchar(100) DEFAULT NULL,
  `adres2` varchar(100) DEFAULT NULL,
  `instlFloor` varchar(50) DEFAULT NULL,
  `instlTy` varchar(50) DEFAULT NULL,
  `instlMby` varchar(50) DEFAULT NULL,
  `svcSe` varchar(50) DEFAULT NULL,
  `cmcwr` varchar(50) DEFAULT NULL,
  `cnstcYear` varchar(50) DEFAULT NULL,
  `inoutDoor` varchar(50) DEFAULT NULL,
  `remars3` varchar(100) DEFAULT NULL,
  `lat` double DEFAULT NULL,
  `lnt` double DEFAULT NULL,
  `workDttm` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25576 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

searchhistory

CREATE TABLE `searchhistory` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lat` double DEFAULT NULL,
  `lnt` double DEFAULT NULL,
  `searchTime` timestamp NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci

bookmark

CREATE TABLE `bookmark` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `wifiId` int(11) DEFAULT NULL,
  `groupId` int(11) DEFAULT NULL,
  `createdAt` timestamp NULL DEFAULT current_timestamp(),
  `updatedAt` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `groupId` (`groupId`),
  KEY `wifiId` (`wifiId`),
  CONSTRAINT `bookmark_ibfk_1` FOREIGN KEY (`wifiId`) REFERENCES `wifiinfo` (`id`),
  CONSTRAINT `bookmark_ibfk_2` FOREIGN KEY (`groupId`) REFERENCES `bookmarkgroup` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

bookmarkgroup

CREATE TABLE `bookmarkgroup` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `groupName` varchar(255) NOT NULL,
  `sortOrder` int(11) NOT NULL,
  `createdAt` timestamp NULL DEFAULT current_timestamp(),
  `updatedAt` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Model

WifiInfo

package com.example.model;

import java.math.BigDecimal;

public class WifiInfo {
    private Long id;
    private String mgrNo;
    private String wrdofc;
    private String mainNm;
    private String adres1;
    private String adres2;
    private String instlFloor;
    private String instlTy;
    private String instlMby;
    private String svcSe;
    private String cmcwr;
    private String cnstcYear;
    private String inoutDoor;
    private String remars3;
    private BigDecimal lat;
    private BigDecimal lnt;
    private String workDttm;
    private double distance;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getMgrNo() {
        return mgrNo;
    }

    public void setMgrNo(String mgrNo) {
        this.mgrNo = mgrNo;
    }

    public String getWrdofc() {
        return wrdofc;
    }

    public void setWrdofc(String wrdofc) {
        this.wrdofc = wrdofc;
    }

    public String getMainNm() {
        return mainNm;
    }

    public void setMainNm(String mainNm) {
        this.mainNm = mainNm;
    }

    public String getAdres1() {
        return adres1;
    }

    public void setAdres1(String adres1) {
        this.adres1 = adres1;
    }

    public String getAdres2() {
        return adres2;
    }

    public void setAdres2(String adres2) {
        this.adres2 = adres2;
    }

    public String getInstlFloor() {
        return instlFloor;
    }

    public void setInstlFloor(String instlFloor) {
        this.instlFloor = instlFloor;
    }

    public String getInstlTy() {
        return instlTy;
    }

    public void setInstlTy(String instlTy) {
        this.instlTy = instlTy;
    }

    public String getInstlMby() {
        return instlMby;
    }

    public void setInstlMby(String instlMby) {
        this.instlMby = instlMby;
    }

    public String getSvcSe() {
        return svcSe;
    }

    public void setSvcSe(String svcSe) {
        this.svcSe = svcSe;
    }

    public String getCmcwr() {
        return cmcwr;
    }

    public void setCmcwr(String cmcwr) {
        this.cmcwr = cmcwr;
    }

    public String getCnstcYear() {
        return cnstcYear;
    }

    public void setCnstcYear(String cnstcYear) {
        this.cnstcYear = cnstcYear;
    }

    public String getInoutDoor() {
        return inoutDoor;
    }

    public void setInoutDoor(String inoutDoor) {
        this.inoutDoor = inoutDoor;
    }

    public String getRemars3() {
        return remars3;
    }

    public void setRemars3(String remars3) {
        this.remars3 = remars3;
    }

    public BigDecimal getLat() {
        return lat;
    }

    public void setLat(BigDecimal lat) {
        this.lat = lat;
    }

    public BigDecimal getLnt() {
        return lnt;
    }

    public void setLnt(BigDecimal lnt) {
        this.lnt = lnt;
    }

    public String getWorkDttm() {
        return workDttm;
    }

    public void setWorkDttm(String workDttm) {
        this.workDttm = workDttm;
    }

    public double getDistance() {
        return distance;
    }

    public void setDistance(double distance) {
        this.distance = distance;
    }
}

SearchHistory

package com.example.model;

import java.sql.Timestamp;

public class SearchHistory {
    private Long id;
    private double lat;
    private double lnt;
    private Timestamp searchTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public double getLat() {
        return lat;
    }

    public void setLat(double lat) {
        this.lat = lat;
    }

    public double getLnt() {
        return lnt;
    }

    public void setLnt(double lnt) {
        this.lnt = lnt;
    }

    public Timestamp getSearchTime() {
        return searchTime;
    }

    public void setSearchTime(Timestamp searchTime) {
        this.searchTime = searchTime;
    }
}

Bookmark

package com.example.model;

import java.sql.Timestamp;

public class Bookmark {
    private Long id;
    private Long sortOrder;
    private Long wifiId;
    private Long groupId;
    private String mainNm;
    private String groupName;
    private Timestamp createdAt;
    private Timestamp updatedAt;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getWifiId() {
        return wifiId;
    }

    public void setWifiId(Long wifiId) {
        this.wifiId = wifiId;
    }

    public Long getGroupId() {
        return groupId;
    }

    public void setGroupId(Long groupId) {
        this.groupId = groupId;
    }

    public Timestamp getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Timestamp createdAt) {
        this.createdAt = createdAt;
    }

    public Timestamp getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Timestamp updatedAt) {
        this.updatedAt = updatedAt;
    }

    public String getMainNm() {
        return mainNm;
    }

    public void setMainNm(String mainNm) {
        this.mainNm = mainNm;
    }

    public Long getSortOrder() {
        return sortOrder;
    }

    public void setSortOrder(Long sortOrder) {
        this.sortOrder = sortOrder;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
}

BookmarkGroup

package com.example.model;

import java.sql.Timestamp;

public class BookmarkGroup {
    private Long id;
    private String groupName;
    private int sortOrder;
    private Timestamp createdAt;
    private Timestamp updatedAt;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public int getSortOrder() {
        return sortOrder;
    }

    public void setSortOrder(int sortOrder) {
        this.sortOrder = sortOrder;
    }

    public Timestamp getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Timestamp createdAt) {
        this.createdAt = createdAt;
    }

    public Timestamp getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Timestamp updatedAt) {
        this.updatedAt = updatedAt;
    }
}

서울 공공 와이파이 Open API 가이드에서 “List_total_count가 1,000이 넘을 경우 Open API는 1회에 1,000건을 넘을 수 없으므로 분리해서 호출합니다.” 라는 설명은 데이터를 1000개씩 나누어서 받아야 한다고 합니다.

다음 코드는, 서울시 공공 와이파이 데이터를 OpenAPI를 통해 가져와 데이터베이스에 저장하는 Java 서블릿입니다. 이 서블릿은 WifiDataFetcherServlet으로, 데이터를 API에서 가져와 데이터베이스에 저장합니다. 전체적인 과정은 데이터베이스 연결, API 호출, 데이터 파싱 및 저장으로 구성됩니다.

package com.example.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

import org.json.JSONArray;
import org.json.JSONObject;

@WebServlet("/fetch-seoul-wifi-data")
public class WifiDataFetcherServlet extends HttpServlet {

    private static final String API_URL = "http://openapi.seoul.go.kr:8088";
    private static final String API_KEY = "506b73675173656f313943556a5343"; // OpenAPI 인증키
    private static final String SERVICE = "TbPublicWifiInfo";
    private static final String DATA_TYPE = "json";
    private static final int PAGE_SIZE = 1000; // 한번에 가져올 데이터 수
    private static final String DB_URL = "jdbc:mariadb://localhost:3306/seoul_wifi?useUnicode=true&characterEncoding=utf8mb4";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "zerobase";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int savedCount = 0;
        Connection dbConn = null;
        PreparedStatement pstmt = null;
        try {
            // JDBC 드라이버 등록
            Class.forName("org.mariadb.jdbc.Driver");

            // 데이터베이스 연결
            dbConn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
            String sql = "INSERT INTO WifiInfo (mgrNo, wrdofc, mainNm, adres1, adres2, instlFloor, instlTy, instlMby, svcSe, cmcwr, cnstcYear, inoutDoor, remars3, lat, lnt, workDttm) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
            pstmt = dbConn.prepareStatement(sql);

            // API 호출 반복
            int startIndex = 1;
            int endIndex = PAGE_SIZE;
            boolean moreData = true;

            while (moreData) {
                StringBuilder urlBuilder = new StringBuilder(API_URL);
                urlBuilder.append("/" + URLEncoder.encode(API_KEY, "UTF-8"));
                urlBuilder.append("/" + URLEncoder.encode(DATA_TYPE, "UTF-8"));
                urlBuilder.append("/" + URLEncoder.encode(SERVICE, "UTF-8"));
                urlBuilder.append("/" + URLEncoder.encode(String.valueOf(startIndex), "UTF-8"));
                urlBuilder.append("/" + URLEncoder.encode(String.valueOf(endIndex), "UTF-8"));

                URL url = new URL(urlBuilder.toString());
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setRequestProperty("Content-type", "application/json");
                System.out.println("Response code: " + conn.getResponseCode());

                BufferedReader rd;
                if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
                    rd = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
                } else {
                    rd = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
                }
                StringBuilder content = new StringBuilder();
                String line;
                while ((line = rd.readLine()) != null) {
                    content.append(line);
                }
                rd.close();
                conn.disconnect();

                System.out.println("API Response: " + content.toString());

                JSONObject json = new JSONObject(content.toString());
                JSONObject tbPublicWifiInfo = json.getJSONObject("TbPublicWifiInfo");
                JSONArray rows = tbPublicWifiInfo.getJSONArray("row");
                int totalDataCount = tbPublicWifiInfo.getInt("list_total_count");

                for (int i = 0; i < rows.length(); i++) {
                    JSONObject row = rows.getJSONObject(i);
                    System.out.println("Row Data: " + row.toString());
                    pstmt.setString(1, row.getString("X_SWIFI_MGR_NO"));
                    pstmt.setString(2, row.getString("X_SWIFI_WRDOFC"));
                    pstmt.setString(3, row.getString("X_SWIFI_MAIN_NM"));
                    pstmt.setString(4, row.getString("X_SWIFI_ADRES1"));
                    pstmt.setString(5, row.getString("X_SWIFI_ADRES2"));
                    pstmt.setString(6, row.getString("X_SWIFI_INSTL_FLOOR"));
                    pstmt.setString(7, row.getString("X_SWIFI_INSTL_TY"));
                    pstmt.setString(8, row.getString("X_SWIFI_INSTL_MBY"));
                    pstmt.setString(9, row.getString("X_SWIFI_SVC_SE"));
                    pstmt.setString(10, row.getString("X_SWIFI_CMCWR"));
                    pstmt.setString(11, row.getString("X_SWIFI_CNSTC_YEAR"));
                    pstmt.setString(12, row.getString("X_SWIFI_INOUT_DOOR"));
                    pstmt.setString(13, row.getString("X_SWIFI_REMARS3"));
                    pstmt.setBigDecimal(14, new BigDecimal(row.getString("LAT")));
                    pstmt.setBigDecimal(15, new BigDecimal(row.getString("LNT")));
                    pstmt.setString(16, row.getString("WORK_DTTM"));
                    pstmt.addBatch();
                    savedCount++;
                }

                pstmt.executeBatch();

                startIndex += PAGE_SIZE;
                endIndex += PAGE_SIZE;

                if (startIndex > totalDataCount) {
                    moreData = false;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (pstmt != null) try {
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (dbConn != null) try {
                dbConn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        req.setAttribute("savedCount", savedCount);
        req.getRequestDispatcher("/WEB-INF/views/fetchResult.jsp").forward(req, resp);
    }
}

JSON 응답을 파싱하여 원하는 데이터를 추출하는 과정을 정리하면,

JSON 데이터 파싱 과정

JSON 응답 문자열을 JSONObject로 변환

JSONObject json = new JSONObject(content.toString());

이 줄은 서버로부터 받은 응답 문자열을 JSONObject로 변환하는 과정입니다. content.toString()은 API 응답을 문자열로 변환한 것입니다. JSONObject는 키-값 쌍으로 이루어진 JSON 객체를 표현합니다. 예를 들어, 응답 문자열이 다음과 같다고 가정합니다:

{
    "TbPublicWifiInfo": {
        "list_total_count": 24584,
        "RESULT": {
            "CODE": "INFO-000",
            "MESSAGE": "정상 처리되었습니다"
        },
        "row": [
            {
                "X_SWIFI_MGR_NO": "ARI00019",
                "X_SWIFI_WRDOFC": "서대문구",
                "X_SWIFI_MAIN_NM": "상수도사업본부",
                "X_SWIFI_ADRES1": "서소문로 51",
                "X_SWIFI_ADRES2": "본관 4F",
                "X_SWIFI_INSTL_FLOOR": "",
                "X_SWIFI_INSTL_TY": "7-1-3. 공공 - 시산하기관",
                "X_SWIFI_INSTL_MBY": "서울시(AP)",
                "X_SWIFI_SVC_SE": "공공WiFi",
                "X_SWIFI_CMCWR": "자가망_수도사업소망",
                "X_SWIFI_CNSTC_YEAR": "2019",
                "X_SWIFI_INOUT_DOOR": "실내",
                "X_SWIFI_REMARS3": "",
                "LAT": "37.561924",
                "LNT": "126.96675",
                "WORK_DTTM": "2024-07-04 11:12:58.0"
            },
            ...
        ]
    }
}

특정 키를 가진 JSONObject를 추출

JSONObject tbPublicWifiInfo = json.getJSONObject("TbPublicWifiInfo");

이 줄은 json 객체에서 “TbPublicWifiInfo”라는 키를 가진 JSON 객체를 추출합니다. 이 객체는 JSON 응답의 루트에 있는 “TbPublicWifiInfo”에 해당합니다. 예를 들어 위의 JSON 응답에서 tbPublicWifiInfo는 다음과 같은 구조를 가집니다:

{
    "list_total_count": 24584,
    "RESULT": {
        "CODE": "INFO-000",
        "MESSAGE": "정상 처리되었습니다"
    },
    "row": [
        {
            "X_SWIFI_MGR_NO": "ARI00019",
            "X_SWIFI_WRDOFC": "서대문구",
            "X_SWIFI_MAIN_NM": "상수도사업본부",
            ...
        },
        ...
    ]
}

특정 키를 가진 JSONArray를 추출

JSONArray rows = tbPublicWifiInfo.getJSONArray("row");

이 줄은 tbPublicWifiInfo 객체에서 “row”라는 키를 가진 JSON 배열을 추출합니다. 이 배열은 여러 개의 와이파이 정보 객체로 구성되어 있습니다. 위의 JSON 예제에서 rows는 다음과 같은 구조를 가집니다:

[
    {
        "X_SWIFI_MGR_NO": "ARI00019",
        "X_SWIFI_WRDOFC": "서대문구",
        "X_SWIFI_MAIN_NM": "상수도사업본부",
        ...
    },
    ...
]

요약 이 과정을 통해 API로부터 받은 JSON 응답에서 원하는 데이터를 추출할 수 있습니다. JSONObject와 JSONArray를 사용하여 계층적 구조의 JSON 데이터를 효율적으로 파싱하고 필요한 정보를 추출합니다. 각 단계는 다음과 같습니다:

전체 JSON 응답을 JSONObject로 변환 특정 키를 가진 JSONObject 추출 (“TbPublicWifiInfo”) 특정 키를 가진 JSONArray 추출 (“row”) 이 추출 과정을 통해 각 와이파이 정보를 개별적으로 접근할 수 있으며, 이를 데이터베이스에 삽입할 수 있습니다.

addBatch

addBatch는 쿼리 실행을 하지 않고 쿼리 구문을 메모리에 올려두었다가, 실행 명령(executeBatch)이 있으면 한번에 DB쪽으로 쿼리를 날린다. Array Processing 기능을 활용하면 한 번의 SQL 수행으로 대량의 로우를 동시에 insert/update/delete 할 수 있다. 다음 사이트에 정리가 잘되어 있으니 참고하세요. JDBC 대량 쿼리문

그리고 다음 과정을 이해하기 위해 ChatGPT에게 간단한 개념을 설명을 듣고 가려합니다.

req.setAttribute("savedCount", savedCount);
req.getRequestDispatcher("/WEB-INF/views/fetchResult.jsp").forward(req, resp);

위에 코드는 Java Servlet에서 요청(Request)과 응답(Response)을 처리하는 중요한 개념입니다.

  1. HttpServletRequest와 HttpServletResponse HttpServletRequest: 클라이언트의 요청 정보를 담고 있는 객체입니다. 사용자가 보낸 데이터를 포함하며, 서버가 요청을 처리하는 데 필요한 정보를 제공합니다. HttpServletResponse: 서버의 응답 정보를 담고 있는 객체입니다. 서버가 클라이언트에게 데이터를 보내기 위해 사용됩니다.
  2. Request Attributes (요청 속성) Attributes: HttpServletRequest 객체에 데이터를 저장할 수 있는 방법 중 하나입니다. 키-값 쌍의 형태로 데이터를 저장할 수 있으며, JSP 페이지 또는 다른 서블릿으로 전달할 수 있습니다. req.setAttribute(String name, Object o): 요청 객체에 데이터를 저장합니다. 여기서 name은 키, o는 저장할 값입니다.
  3. RequestDispatcher (요청 디스패처) RequestDispatcher: 요청을 다른 서블릿이나 JSP 페이지로 전달(포워딩)하는 데 사용됩니다. req.getRequestDispatcher(String path): 지정된 경로의 서블릿 또는 JSP 페이지에 대한 RequestDispatcher 객체를 반환합니다. forward(ServletRequest request, ServletResponse response): 현재 요청과 응답 객체를 다른 서블릿이나 JSP 페이지로 전달합니다. 전달된 서블릿 또는 JSP 페이지는 클라이언트에게 응답을 보내기 전에 요청을 처리합니다.

savedCount로 전달한 값을 fetchResult.jsp에서 받아서 처리하는 과정입니다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>와이파이 정보 가져오기 결과</title>
</head>
<body>
<h1>와이파이 정보 가져오기 결과</h1>
<p>${savedCount}개의 WIFI 정보를 정상적으로 저장하였습니다.</p>
<a href="index.jsp">홈으로 가기</a>
</body>
</html>

다음은 초기 홈화면인 index.jsp입니다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>와이파이 정보 구하기</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        function getLocation() {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(showPosition);
            } else {
                alert("Geolocation is not supported by this browser.");
            }
        }

        function showPosition(position) {
            document.getElementById("lat").value = position.coords.latitude;
            document.getElementById("lnt").value = position.coords.longitude;
        }

        function getNearestWifi() {
            const lat = $('#lat').val();
            const lnt = $('#lnt').val();
            $.ajax({
                url: 'wifi',
                type: 'get',
                data: {
                    action: 'getNearest',
                    lat: lat,
                    lnt: lnt
                },
                success: function (data) {
                    console.log('근처 와이파이 20개 데이터를 성공적으로 가져왔습니다.');
                    $('#result').html(data);
                },
                error: function () {
                    console.log('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
                    alert('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
                }
            });
        }

        function getHistory() {
            $.ajax({
                url: 'wifi',
                type: 'get',
                data: {
                    action: 'getHistory'
                },
                success: function (data) {
                    console.log('히스토리 데이터를 성공적으로 가져왔습니다.');
                    $('#result').html(data);
                },
                error: function () {
                    console.log('히스토리 데이터를 가져오는 중 오류가 발생했습니다.');
                    alert('히스토리 데이터를 가져오는 중 오류가 발생했습니다.');
                }
            });
        }

        function getBookmarks() {
            $.ajax({
                url: 'wifi',
                type: 'get',
                data: {
                    action: 'getBookmarks'
                },
                success: function (data) {
                    console.log('북마크 데이터를 성공적으로 가져왔습니다.');
                    $('#result').html(data);
                },
                error: function () {
                    console.log('북마크 데이터를 가져오는 중 오류가 발생했습니다.');
                    alert('북마크 데이터를 가져오는 중 오류가 발생했습니다.');
                }
            });
        }

        function getBookmarkgroups() {
            $.ajax({
                url: 'wifi',
                type: 'get',
                data: {
                    action: 'getBookmarkgroups'
                },
                success: function (data) {
                    console.log('북마크 그룹 데이터를 성공적으로 가져왔습니다.');
                    $('#result').html(data);
                },
                error: function () {
                    console.log('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
                    alert('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
                }
            });
        }

    </script>
</head>
<body>
<h1>와이파이 정보 구하기</h1>
<nav>
    <a href="index.jsp"></a> |
    <a href="javascript:void(0);" onclick="getHistory()">위치 히스토리 목록</a> |
    <a href="fetch-seoul-wifi-data">Open API 와이파이 정보 가져오기</a> |
    <a href="javascript:void(0);" onclick="getBookmarks()">북마크 보기</a> |
    <a href="javascript:void(0);" onclick="getBookmarkgroups()">북마크 그룹 관리</a>
</nav>
<form id="locationForm">
    <input type="hidden" name="action" value="getNearest">
    <label for="lat">위도:</label>
    <input type="text" id="lat" name="lat">
    <label for="lnt">경도:</label>
    <input type="text" id="lnt" name="lnt">
    <button type="button" onclick="getLocation()">내 위치 가져오기</button>
    <button type="button" onclick="getNearestWifi()">근처 WIFI 정보 보기</button>
</form>
<div id="result"></div>
</body>
</html>

홈, 위치 히스토리 목록, Open API 와이파이 정보 가져오기, 북마크 보기, 북마크 그룹 관리 기능이 있고 Ajax를 활용해 비동기적으로 화면 이동없이 필요한 데이터를 서버에서 받아와 화면에 업데이트하는 방식으로 데이터를 가져오게 작성했습니다.

Ajax의 개념

Ajax(Asynchronous JavaScript and XML)는 웹 페이지를 비동기적으로 업데이트할 수 있는 기술입니다. Ajax를 사용하면 전체 페이지를 다시 로드하지 않고도 서버와 데이터를 주고받을 수 있습니다. 이는 웹 애플리케이션을 더 빠르고, 더 인터랙티브하게 만들어 줍니다. 다음은 Ajax의 주요 개념들입니다:

비동기적 통신: Ajax는 웹 페이지가 서버에 요청을 보내고 응답을 받을 때 다른 작업을 계속할 수 있게 해줍니다. 이 방식은 사용자가 페이지와 계속 상호작용할 수 있도록 합니다. JavaScript: Ajax의 핵심은 JavaScript입니다. JavaScript를 사용하여 서버와 통신하고, 서버에서 데이터를 받아와 페이지의 일부를 업데이트합니다. XMLHttpRequest 객체: Ajax 요청을 생성하고 관리하는데 사용되는 기본 객체입니다. 이를 통해 서버에 요청을 보내고 응답을 처리할 수 있습니다. 요즘에는 JSON 형식의 데이터를 주고받는 것이 일반적입니다. JSON: 서버와 클라이언트 간의 데이터 교환 형식으로 JSON(JavaScript Object Notation)이 널리 사용됩니다. 이는 XML보다 더 가볍고 JavaScript와 쉽게 호환됩니다. jQuery: jQuery 라이브러리는 Ajax를 더 쉽게 사용할 수 있게 해줍니다. 예제 코드에서도 $.ajax 메서드를 사용하여 Ajax 요청을 간단하게 처리하고 있습니다.

위치 히스토리 목록 가져오기

function getHistory() {
    $.ajax({
        url: 'wifi',
        type: 'get',
        data: {
            action: 'getHistory'
        },
        success: function (data) {
            console.log('히스토리 데이터를 성공적으로 가져왔습니다.');
            $('#result').html(data);
        },
        error: function () {
            console.log('히스토리 데이터를 가져오는 중 오류가 발생했습니다.');
            alert('히스토리 데이터를 가져오는 중 오류가 발생했습니다.');
        }
    });
}

근처 WiFi 정보 가져오기

function getNearestWifi() {
    const lat = $('#lat').val();
    const lnt = $('#lnt').val();
    $.ajax({
        url: 'wifi',
        type: 'get',
        data: {
            action: 'getNearest',
            lat: lat,
            lnt: lnt
        },
        success: function (data) {
            console.log('근처 와이파이 20개 데이터를 성공적으로 가져왔습니다.');
            $('#result').html(data);
        },
        error: function () {
            console.log('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
            alert('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
        }
    });
}

북마크 보기

function getBookmarks() {
    $.ajax({
        url: 'wifi',
        type: 'get',
        data: {
            action: 'getBookmarks'
        },
        success: function (data) {
            console.log('북마크 데이터를 성공적으로 가져왔습니다.');
            $('#result').html(data);
        },
        error: function () {
            console.log('북마크 데이터를 가져오는 중 오류가 발생했습니다.');
            alert('북마크 데이터를 가져오는 중 오류가 발생했습니다.');
        }
    });
}

북마크 그룹 관리

function getBookmarkgroups() {
    $.ajax({
        url: 'wifi',
        type: 'get',
        data: {
            action: 'getBookmarkgroups'
        },
        success: function (data) {
            console.log('북마크 그룹 데이터를 성공적으로 가져왔습니다.');
            $('#result').html(data);
        },
        error: function () {
            console.log('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
            alert('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
        }
    });
}

내 위치 가져오기

“내 위치 가져오기” 버튼을 클릭했을 때 실행되는 로직을 상세히 설명하겠습니다. 이 로직은 사용자의 현재 위치를 가져와 해당 위치를 HTML 폼의 숨겨진 필드에 설정합니다. 이 작업은 JavaScript의 Geolocation API와 jQuery를 사용하여 구현됩니다.

주요 단계 설명

  1. 버튼 클릭 이벤트 핸들러 등록 HTML에서 “내 위치 가져오기” 버튼을 클릭하면 getLocation() 함수가 호출됩니다. 이 함수는 사용자의 현재 위치를 가져오는 역할을 합니다.
<button type="button" onclick="getLocation()">내 위치 가져오기</button>
  1. Geolocation API 사용 getLocation() 함수는 브라우저의 Geolocation API를 사용하여 현재 위치를 가져옵니다.
function getLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showPosition);
    } else {
        alert("Geolocation is not supported by this browser.");
    }
}

navigator.geolocation: 브라우저의 Geolocation 객체입니다. 이 객체를 사용하여 위치 정보를 가져올 수 있습니다. getCurrentPosition(): 현재 위치를 비동기적으로 가져오는 메서드입니다. 성공 시 showPosition 콜백 함수가 호출됩니다. showPosition(position): 위치 정보를 처리하는 콜백 함수입니다. 위치 정보를 가져오지 못할 경우 alert으로 사용자에게 알립니다.

위치 정보 설정 위치 정보가 성공적으로 가져와지면, showPosition 함수가 호출됩니다. 이 함수는 위치 정보를 HTML 폼 필드에 설정합니다.

function showPosition(position) {
    document.getElementById("lat").value = position.coords.latitude;
    document.getElementById("lnt").value = position.coords.longitude;
}

position.coords.latitude: 위치 정보 객체의 위도 값입니다. position.coords.longitude: 위치 정보 객체의 경도 값입니다. document.getElementById(“lat”).value: 폼의 위도 필드에 값을 설정합니다. document.getElementById(“lnt”).value: 폼의 경도 필드에 값을 설정합니다.

콜백 함수란?

콜백 함수는 다른 함수에 인자로 전달되어 특정 작업이 완료된 후 호출되는 함수입니다. 자바스크립트와 같은 비동기 프로그래밍 환경에서 콜백 함수는 중요한 역할을 합니다. 다음은 콜백 함수의 개념과 사용 예제를 설명합니다.

콜백 함수의 개념

콜백 함수는 주로 비동기 작업(예: 데이터 가져오기, 타이머, 이벤트 처리 등)에서 사용됩니다. 비동기 작업이 완료된 후, 해당 작업의 결과를 처리하기 위해 호출됩니다. 이는 코드의 흐름을 제어하고, 비동기 작업의 결과를 처리하는 데 유용합니다.

콜백 함수의 예제

기본 콜백 함수 사용 예제

function doSomething(callback) {
    console.log("Doing something...");
    callback();
}

function sayHello() {
    console.log("Hello!");
}

doSomething(sayHello);

위 코드에서 doSomething 함수는 작업을 수행한 후 callback으로 전달된 sayHello 함수를 호출합니다.

비동기 작업에서 콜백 함수 사용 예제 자바스크립트의 setTimeout 함수는 지정된 시간 후에 콜백 함수를 호출하는 예입니다.

function delayedGreeting() {
    console.log("Hello, after 2 seconds!");
}

setTimeout(delayedGreeting, 2000); // 2000 밀리초(2초) 후에 delayedGreeting 함수 호출

AJAX 요청에서 콜백 함수 사용 예제 다음은 jQuery의 $.ajax 메서드를 사용하여 콜백 함수로 서버의 응답을 처리하는 예제입니다.

$.ajax({
    url: 'https://api.example.com/data',
    type: 'GET',
    success: function(data) {
        console.log('Data received:', data);
    },
    error: function(error) {
        console.error('Error:', error);
    }
});

위 코드에서 success와 error는 콜백 함수입니다. AJAX 요청이 성공하면 success 콜백이 호출되고, 실패하면 error 콜백이 호출됩니다.

콜백 함수의 장점 비동기 작업 처리: 콜백 함수는 비동기 작업의 완료를 기다렸다가 결과를 처리할 수 있게 해줍니다. 코드의 재사용성: 여러 작업에서 동일한 콜백 함수를 재사용할 수 있습니다. 코드의 구조화: 콜백 함수는 비동기 작업의 결과를 처리하는 코드를 분리하여, 코드의 가독성과 유지보수성을 높입니다. 콜백 함수의 단점 및 해결 방법 콜백 헬(Callback Hell): 중첩된 콜백 함수가 많아지면 코드의 가독성이 떨어지고 유지보수가 어려워집니다. 이를 해결하기 위해 Promise, async/await를 사용할 수 있습니다.

예제: 콜백 헬

doSomething(function(result1) {
    doSomethingElse(result1, function(result2) {
        doAnotherThing(result2, function(result3) {
            doSomethingMore(result3, function(result4) {
                console.log('Final result:', result4);
            });
        });
    });
});

Promise 사용 예제

doSomething()
    .then(result1 => doSomethingElse(result1))
    .then(result2 => doAnotherThing(result2))
    .then(result3 => doSomethingMore(result3))
    .then(result4 => console.log('Final result:', result4))
    .catch(error => console.error('Error:', error));

async/await 사용 예제

async function executeTasks() {
    try {
        const result1 = await doSomething();
        const result2 = await doSomethingElse(result1);
        const result3 = await doAnotherThing(result2);
        const result4 = await doSomethingMore(result3);
        console.log('Final result:', result4);
    } catch (error) {
        console.error('Error:', error);
    }
}

executeTasks();

결론 콜백 함수는 자바스크립트에서 비동기 작업을 처리하는 중요한 개념입니다. 콜백 함수를 사용하면 작업이 완료된 후 특정 코드를 실행할 수 있습니다. 그러나 콜백 헬 문제를 해결하기 위해 Promise와 async/await와 같은 더 나은 방법을 사용할 수도 있습니다.

getNearestWifi

const lat = $('#lat').val();
const lnt = $('#lnt').val();

이 코드는 jQuery를 사용하여 HTML 요소에서 값을 가져와 lat 변수에 저장하는 것입니다. 각 부분의 의미를 상세히 설명하면 다음과 같습니다:

jQuery 선택자 $(‘#lat’) $: jQuery 라이브러리에서 사용되는 전역 함수입니다. 이 함수를 사용하면 HTML 문서 내의 요소를 선택하고 조작할 수 있습니다. ‘#lat’: jQuery 선택자로, HTML 문서에서 id가 lat인 요소를 선택합니다. #는 id 선택자를 의미합니다. 예를 들어, 다음과 같은 HTML 요소를 가리킵니다:

<input type="text" id="lat" name="lat">

.val() .val(): jQuery 메서드로, 선택된 요소의 값을 가져오거나 설정하는 데 사용됩니다. 위 코드에서는 $(‘#lat’).val()가 id가 lat인 입력 요소의 현재 값을 가져옵니다. const lat const lat: ECMAScript 6(ES6)에서 도입된 const 키워드를 사용하여 변수를 선언합니다. const는 상수를 선언할 때 사용되며, 한번 값을 할당하면 나중에 변경할 수 없습니다. 여기서는 lat이라는 변수를 선언하고, $(‘#lat’).val()로 가져온 값을 할당합니다. 전체 코드 의미 전체 코드를 분석하면, lat 변수는 HTML 입력 요소의 값을 가져와 저장합니다. 예를 들어 사용자가 폼에 자신의 위도(latitude)를 입력했을 때, 이 코드를 통해 해당 값을 가져올 수 있습니다.

$(‘#result’).html(data);는 jQuery를 사용하여 AJAX 요청으로부터 받은 데이터를 HTML 요소에 동적으로 삽입하는 구문입니다. 아래에서 이 구문의 동작 방식을 단계별로 설명하겠습니다.

AJAX 요청

$.ajax({
    url: 'wifi',
    type: 'get',
    data: {
        action: 'getNearest',
        lat: lat,
        lnt: lnt
    },
    success: function (data) {
        console.log('근처 와이파이 20개 데이터를 성공적으로 가져왔습니다.');
        $('#result').html(data);
    },
    error: function () {
        console.log('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
        alert('근처 와이파이 20개 데이터를 가져오는 중 오류가 발생했습니다.');
    }
});

$.ajax는 jQuery의 AJAX 메서드로, 서버에 비동기 요청을 보내기 위해 사용됩니다.
url: 'wifi' 요청을 보낼 URL을 지정합니다.
type: 'get' HTTP GET 요청을 보낸다는 것을 의미합니다.
data는 서버로 보낼 데이터를 객체 형태로 지정합니다.
success 콜백 함수는 요청이 성공적으로 완료되었을  호출되며, 서버로부터 받은 응답 데이터가 data 매개변수로 전달됩니다.
error 콜백 함수는 요청이 실패했을  호출됩니다.

성공 콜백 함수의 동작

```javascript
success: function (data) {
    console.log('근처 와이파이 20개 데이터를 성공적으로 가져왔습니다.');
    $('#result').html(data);
}

success 콜백 함수는 서버로부터 데이터를 성공적으로 받은 후 실행됩니다. console.log(‘근처 와이파이 20개 데이터를 성공적으로 가져왔습니다.’);는 받은 데이터를 성공적으로 가져왔음을 브라우저 콘솔에 출력합니다. $(‘#result’).html(data);는 서버로부터 받은 데이터를 HTML 요소에 삽입합니다. $(‘#result’).html(data);의 동작

$(‘#result’)는 jQuery 선택자로, id가 result인 HTML 요소를 선택합니다. .html(data)는 선택한 요소의 HTML 콘텐츠를 지정된 data로 설정합니다. 예를 들어, 만약 서버로부터 받은 data가 다음과 같은 HTML 문자열이라면:

<table>
    <tr>
        <td>와이파이 이름</td>
        <td>거리</td>
    </tr>
    <tr>
        <td>서울시 와이파이</td>
        <td>100m</td>
    </tr>
</table>

$(‘#result’).html(data);는 id가 result인 요소의 내부 HTML을 위의 HTML 문자열로 설정합니다. 결과적으로, id가 result인 요소는 다음과 같이 업데이트됩니다:

<div id="result">
    <table>
        <tr>
            <td>와이파이 이름</td>
            <td>거리</td>
        </tr>
        <tr>
            <td>서울시 와이파이</td>
            <td>100m</td>
        </tr>
    </table>
</div>

$(‘#result’).html(data);는 jQuery 선택자를 사용하여 id가 result인 HTML 요소를 선택하고, 이 요소의 내용을 서버로부터 받은 data로 교체합니다. 이렇게 하면 AJAX 요청으로 받은 데이터를 웹 페이지의 특정 위치에 동적으로 표시할 수 있습니다.

Controller

다음 코드는 요청에 대한 응답을 하는 controller역할을 하는 WifiServlet클래스 입니다. 위에서 get으로 요청한 작업에 대해 처리하는 코드와 post로 요청된 작업에 대해 처리해주는 로직이 있습니다.

package com.example.controller;

import com.example.model.Bookmark;
import com.example.model.BookmarkGroup;
import com.example.model.SearchHistory;
import com.example.model.WifiInfo;
import com.example.repository.BookmarkGroupRepository;
import com.example.repository.BookmarkRepository;
import com.example.repository.SearchHistoryRepository;
import com.example.repository.WifiInfoRepository;
import com.google.gson.Gson;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

@WebServlet("/wifi")
public class WifiServlet extends HttpServlet {

    private final SearchHistoryRepository historyRepository = new SearchHistoryRepository();
    private final WifiInfoRepository wifiRepository = new WifiInfoRepository();
    private final BookmarkRepository bookmarkRepository = new BookmarkRepository();
    private final BookmarkGroupRepository bookmarkGroupRepository = new BookmarkGroupRepository();

    @Override
    public void init() throws ServletException {
        super.init();
        try {
            Class.forName("org.mariadb.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new ServletException("MariaDB JDBC Driver not found!", e);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getParameter("action");

        if ("deleteHistory".equals(action)) {
            handleDeleteHistory(req, resp);
        } else if ("addBookmark".equals(action)) {
            handleAddBookmark(req, resp);
        } else if ("deleteBookmark".equals(action)) {
            handleDeleteBookmark(req, resp);
        } else if ("updateBookmarkGroup".equals(action)) {
            handleUpdateBookmarkGroup(req, resp);
        } else if ("deleteBookmarkGroup".equals(action)) {
            handleDeleteBookmarkGroup(req, resp);
        } else if ("addBookmarkGroup".equals(action)) {
            handleAddBookmarkGroup(req, resp);
        }

    }

    private void handleDeleteHistory(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        long id = Long.parseLong(req.getParameter("id"));

        try {
            historyRepository.deleteSearchHistory(id);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        // After deletion, redirect to the history list page to see the updated list
        resp.sendRedirect("wifi?action=getHistory");
    }

    private void handleAddBookmark(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        long wifiId = Long.parseLong(req.getParameter("wifiId"));
        long groupId = Long.parseLong(req.getParameter("groupId"));

        try {
            bookmarkRepository.addBookmark(wifiId, groupId);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"success\"}");
        } catch (SQLException e) {
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 추가 중 오류가 발생했습니다.\"}");
        }
    }

    private void handleDeleteBookmark(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        long id = Long.parseLong(req.getParameter("id"));

        try {
            bookmarkRepository.deleteBookmark(id);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"success\"}");
            resp.sendRedirect("wifi?action=getBookmarks");
        } catch (SQLException e) {
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 삭제 중 오류가 발생했습니다.\"}");
        }
    }

    private void handleUpdateBookmarkGroup(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        long id = Long.parseLong(req.getParameter("id"));
        String groupName = req.getParameter("groupName");
        int sortOrder = Integer.parseInt(req.getParameter("sortOrder"));

        try {
            bookmarkGroupRepository.updateGroup(id, groupName, sortOrder);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"success\"}");
        } catch (SQLException e) {
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 그룹 수정 중 오류가 발생했습니다.\"}");
        }
    }

    private void handleDeleteBookmarkGroup(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        long id = Long.parseLong(req.getParameter("id"));

        try {
            // 먼저 bookmark 테이블에서 관련 행을 삭제합니다.
            bookmarkRepository.deleteBookmarksByGroupId(id);

            // 그런 다음 bookmarkgroup 테이블에서 행을 삭제합니다.
            bookmarkGroupRepository.deleteGroup(id);

            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"success\"}");
        } catch (SQLException e) {
            e.printStackTrace();
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 그룹 삭제 중 오류가 발생했습니다.\"}");
        }
    }

    private void handleAddBookmarkGroup(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String groupName = req.getParameter("groupName");
        int sortOrder = Integer.parseInt(req.getParameter("sortOrder"));

        try {
            bookmarkGroupRepository.addGroup(groupName, sortOrder);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"success\"}");
        } catch (SQLException e) {
            e.printStackTrace();
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 그룹 추가 중 오류가 발생했습니다.\"}");
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getParameter("action");

        if ("getNearest".equals(action)) {
            handleGetNearest(req, resp);
        } else if ("getHistory".equals(action)) {
            handleGetHistory(req, resp);
        } else if ("getWifiDetails".equals(action)) {
            handleGetWifiDetails(req, resp);
        } else if ("getBookmarks".equals(action)) {
            handleGetBookmarks(req, resp);
        } else if ("getBookmarkgroups".equals(action)) {
            handleGetBookmarkgroups(req, resp);
        } else if ("getBookmarkGroupOptions".equals(action)) {
            handleGetBookmarkGroupOptions(req, resp);
        }
    }

    private void handleGetNearest(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String lat = req.getParameter("lat");
        String lnt = req.getParameter("lnt");

        try {
            historyRepository.saveSearchHistory(Double.parseDouble(lat), Double.parseDouble(lnt));
        } catch (SQLException e) {
            e.printStackTrace();
        }

        List<WifiInfo> wifiList = null;
        try {
            wifiList = wifiRepository.findNearestWifi(Double.parseDouble(lat), Double.parseDouble(lnt));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        req.setAttribute("wifiList", wifiList);
        req.getRequestDispatcher("/WEB-INF/views/wifiList.jsp").forward(req, resp);
    }

    private void handleGetHistory(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            List<SearchHistory> historyList = historyRepository.getSearchHistory();
            req.setAttribute("historyList", historyList);
            req.getRequestDispatcher("/WEB-INF/views/historyList.jsp").forward(req, resp);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void handleGetWifiDetails(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        long wifiId = Long.parseLong(req.getParameter("id"));
        WifiInfo wifiInfo = null;
        try {
            wifiInfo = wifiRepository.findWifiById(wifiId);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        req.setAttribute("wifi", wifiInfo);
        req.getRequestDispatcher("/WEB-INF/views/wifiDetails.jsp").forward(req, resp);
    }

    private void handleGetBookmarks(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            List<Bookmark> bookmarks = bookmarkRepository.getAllBookmarks();
            req.setAttribute("bookmarkList", bookmarks);
            req.getRequestDispatcher("/WEB-INF/views/bookmarkList.jsp").forward(req, resp);
        } catch (SQLException | ServletException e) {
            e.printStackTrace();
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "북마크 데이터를 가져오는 중 오류가 발생했습니다.");
        }
    }

    private void handleGetBookmarkgroups(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            List<BookmarkGroup> bookmarkGroups = bookmarkGroupRepository.getAllGroups();
            req.setAttribute("bookmarkGroups", bookmarkGroups);
            req.getRequestDispatcher("/WEB-INF/views/bookmarkGroupList.jsp").forward(req, resp);
        } catch (SQLException | ServletException e) {
            e.printStackTrace();
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void handleGetBookmarkGroupOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            List<BookmarkGroup> bookmarkGroups = bookmarkGroupRepository.getAllGroups();
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write(new Gson().toJson(bookmarkGroups));
        } catch (SQLException e) {
            e.printStackTrace();
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("application/json");
            resp.setCharacterEncoding("UTF-8");
            resp.getWriter().write("{\"status\":\"error\", \"message\":\"북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.\"}");
        }
    }
}

Repository

BookmarkGroupRepository

package com.example.repository;

import com.example.model.BookmarkGroup;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class BookmarkGroupRepository {
    private String jdbcUrl = "jdbc:mariadb://localhost:3306/seoul_wifi";
    private String jdbcUsername = "root";
    private String jdbcPassword = "zerobase";

    public List<BookmarkGroup> getAllGroups() throws SQLException {
        List<BookmarkGroup> groupList = new ArrayList<>();
        String sql = "SELECT * FROM bookmarkgroup ORDER BY id";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                BookmarkGroup group = new BookmarkGroup();
                group.setId(rs.getLong("id"));
                group.setGroupName(rs.getString("groupName"));
                group.setSortOrder(rs.getInt("sortOrder"));
                group.setCreatedAt(rs.getTimestamp("createdAt"));
                group.setUpdatedAt(rs.getTimestamp("updatedAt"));
                groupList.add(group);
            }
        }

        return groupList;
    }

    public BookmarkGroup getGroupById(long id) throws SQLException {
        String sql = "SELECT id, groupName, sortOrder, createdAt, updatedAt FROM bookmarkgroup WHERE id = ?";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, id);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                BookmarkGroup group = new BookmarkGroup();
                group.setId(rs.getLong("id"));
                group.setGroupName(rs.getString("groupName"));
                group.setSortOrder(rs.getInt("sortOrder"));
                group.setCreatedAt(rs.getTimestamp("createdAt"));
                group.setUpdatedAt(rs.getTimestamp("updatedAt"));
                return group;
            }
        }
        return null;
    }

    public void addGroup(String groupName, int sortOrder) throws SQLException {
        String sql = "INSERT INTO bookmarkgroup (groupName, sortOrder, createdAt, updatedAt) VALUES (?, ?, NOW(), NOW())";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, groupName);
            pstmt.setInt(2, sortOrder);
            pstmt.executeUpdate();
        }
    }

    public void updateGroup(long id, String groupName, int sortOrder) throws SQLException {
        String sql = "UPDATE bookmarkgroup SET groupName = ?, sortOrder = ?, updatedAt = NOW() WHERE id = ?";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, groupName);
            pstmt.setInt(2, sortOrder);
            pstmt.setLong(3, id);
            pstmt.executeUpdate();
        }
    }

    public void deleteGroup(long id) throws SQLException {
        String sql = "DELETE FROM bookmarkgroup WHERE id = ?";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, id);
            pstmt.executeUpdate();
        }
    }
}

BookmarkRepository

package com.example.repository;

import com.example.model.Bookmark;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class BookmarkRepository {
    private String jdbcUrl = "jdbc:mariadb://localhost:3306/seoul_wifi";
    private String jdbcUsername = "root";
    private String jdbcPassword = "zerobase";

    public void addBookmark(long wifiId, long groupId) throws SQLException {
        String sql = "INSERT INTO bookmark (wifiId, groupId, createdAt, updatedAt) VALUES (?, ?, NOW(), NOW())";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, wifiId);
            pstmt.setLong(2, groupId);
            pstmt.executeUpdate();
        }
    }

    public void deleteBookmark(long id) throws SQLException {
        String sql = "DELETE FROM bookmark WHERE id = ?";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, id);
            pstmt.executeUpdate();
        }
    }

    public List<Bookmark> getAllBookmarks() throws SQLException {
        List<Bookmark> bookmarkList = new ArrayList<>();
        String sql = "SELECT b.id, g.groupName, w.id as wifiId, w.mainNm, b.createdAt " +
                "FROM bookmark b " +
                "JOIN bookmarkgroup g ON b.groupId = g.id " +
                "JOIN wifiinfo w ON b.wifiId = w.id " +
                "ORDER BY g.sortOrder";
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                Bookmark bookmark = new Bookmark();
                bookmark.setId(rs.getLong("id"));
                bookmark.setGroupName(rs.getString("groupName"));
                bookmark.setWifiId(rs.getLong("wifiId"));
                bookmark.setMainNm(rs.getString("mainNm"));
                bookmark.setCreatedAt(rs.getTimestamp("createdAt"));
                bookmarkList.add(bookmark);
            }
        }
        return bookmarkList;
    }

    public void deleteBookmarksByGroupId(long groupId) throws SQLException {
        String sql = "DELETE FROM bookmark WHERE groupId = ?";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, groupId);
            pstmt.executeUpdate();
        }
    }
}

SearchHistoryRepository

package com.example.repository;

import com.example.model.SearchHistory;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SearchHistoryRepository {
    private String jdbcUrl = "jdbc:mariadb://localhost:3306/seoul_wifi";
    private String jdbcUsername = "root";
    private String jdbcPassword = "zerobase";

    public void saveSearchHistory(double lat, double lnt) throws SQLException {
        String sql = "INSERT INTO SearchHistory (lat, lnt) VALUES (?, ?)";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setDouble(1, lat);
            pstmt.setDouble(2, lnt);
            pstmt.executeUpdate();
        }
    }

    public void deleteSearchHistory(long id) throws SQLException {
        String sql = "DELETE FROM SearchHistory WHERE id = ?";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, id);
            pstmt.executeUpdate();
        }
    }

    public List<SearchHistory> getSearchHistory() throws SQLException {
        List<SearchHistory> historyList = new ArrayList<>();
        String sql = "SELECT * FROM SearchHistory ORDER BY searchTime DESC";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {

            while (rs.next()) {
                SearchHistory history = new SearchHistory();
                history.setId(rs.getLong("id"));
                history.setLat(rs.getDouble("lat"));
                history.setLnt(rs.getDouble("lnt"));
                history.setSearchTime(rs.getTimestamp("searchTime"));
                historyList.add(history);
            }
        }

        return historyList;
    }


}

WifiInfoRepository

package com.example.repository;

import com.example.model.WifiInfo;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class WifiInfoRepository {
    private String jdbcUrl = "jdbc:mariadb://localhost:3306/seoul_wifi";
    private String jdbcUsername = "root";
    private String jdbcPassword = "zerobase";

    public List<WifiInfo> findNearestWifi(double lat, double lnt) throws SQLException {
        List<WifiInfo> wifiList = new ArrayList<>();
        String sql = "SELECT *, (6371 * acos(cos(radians(?)) * cos(radians(lat)) * cos(radians(lnt) - radians(?)) + sin(radians(?)) * sin(radians(lat)))) AS distance FROM WifiInfo ORDER BY distance LIMIT 20";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setDouble(1, lat);
            pstmt.setDouble(2, lnt);
            pstmt.setDouble(3, lat);

            ResultSet rs = pstmt.executeQuery();

            while (rs.next()) {
                WifiInfo wifi = new WifiInfo();
                wifi.setId(rs.getLong("id"));
                wifi.setMgrNo(rs.getString("mgrNo"));
                wifi.setWrdofc(rs.getString("wrdofc"));
                wifi.setMainNm(rs.getString("mainNm"));
                wifi.setAdres1(rs.getString("adres1"));
                wifi.setAdres2(rs.getString("adres2"));
                wifi.setInstlFloor(rs.getString("instlFloor"));
                wifi.setInstlTy(rs.getString("instlTy"));
                wifi.setInstlMby(rs.getString("instlMby"));
                wifi.setSvcSe(rs.getString("svcSe"));
                wifi.setCmcwr(rs.getString("cmcwr"));
                wifi.setCnstcYear(rs.getString("cnstcYear"));
                wifi.setInoutDoor(rs.getString("inoutDoor"));
                wifi.setRemars3(rs.getString("remars3"));
                wifi.setLat(BigDecimal.valueOf(rs.getDouble("lat")));
                wifi.setLnt(BigDecimal.valueOf(rs.getDouble("lnt")));
                wifi.setWorkDttm(rs.getString("workDttm"));
                wifi.setDistance(rs.getDouble("distance"));
                wifiList.add(wifi);
            }
        }

        return wifiList;
    }

    public WifiInfo findWifiById(long wifiId) throws SQLException {
        WifiInfo wifi = null;
        String sql = "SELECT * FROM WifiInfo WHERE id = ?";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setLong(1, wifiId);

            ResultSet rs = pstmt.executeQuery();

            if (rs.next()) {
                wifi = new WifiInfo();
                wifi.setId(rs.getLong("id"));
                wifi.setMgrNo(rs.getString("mgrNo"));
                wifi.setWrdofc(rs.getString("wrdofc"));
                wifi.setMainNm(rs.getString("mainNm"));
                wifi.setAdres1(rs.getString("adres1"));
                wifi.setAdres2(rs.getString("adres2"));
                wifi.setInstlFloor(rs.getString("instlFloor"));
                wifi.setInstlTy(rs.getString("instlTy"));
                wifi.setInstlMby(rs.getString("instlMby"));
                wifi.setSvcSe(rs.getString("svcSe"));
                wifi.setCmcwr(rs.getString("cmcwr"));
                wifi.setCnstcYear(rs.getString("cnstcYear"));
                wifi.setInoutDoor(rs.getString("inoutDoor"));
                wifi.setRemars3(rs.getString("remars3"));
                wifi.setLat(BigDecimal.valueOf(rs.getDouble("lat")));
                wifi.setLnt(BigDecimal.valueOf(rs.getDouble("lnt")));
                wifi.setWorkDttm(rs.getString("workDttm"));
            }
        }

        return wifi;
    }
}

다음 코드들은 컨트롤러와 레포지토리에서 처리된 값들을 화면에 보여주는 JSP 파일들 입니다.

JSP

wifiList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>근처 WIFI 정보</title>
</head>
<body>
<h1>근처 WIFI 정보</h1>
<table border="1">
    <thead>
    <tr>
        <th>거리(KM)</th>
        <th>관리번호</th>
        <th>자치구</th>
        <th>와이파이명</th>
        <th>도로명주소</th>
        <th>상세주소</th>
        <th>설치위치(층)</th>
        <th>설치유형</th>
        <th>설치기관</th>
        <th>서비스구분</th>
        <th>망종류</th>
        <th>설치년도</th>
        <th>실내외구분</th>
        <th>WIFI접속환경</th>
        <th>X좌표</th>
        <th>Y좌표</th>
        <th>작업일자</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach var="wifi" items="${wifiList}">
        <tr>
            <td>${wifi.distance}</td>
            <td>${wifi.mgrNo}</td>
            <td>${wifi.wrdofc}</td>
            <td><a href="wifi?action=getWifiDetails&id=${wifi.id}">${wifi.mainNm}</a></td>
            <td>${wifi.adres1}</td>
            <td>${wifi.adres2}</td>
            <td>${wifi.instlFloor}</td>
            <td>${wifi.instlTy}</td>
            <td>${wifi.instlMby}</td>
            <td>${wifi.svcSe}</td>
            <td>${wifi.cmcwr}</td>
            <td>${wifi.cnstcYear}</td>
            <td>${wifi.inoutDoor}</td>
            <td>${wifi.remars3}</td>
            <td>${wifi.lat}</td>
            <td>${wifi.lnt}</td>
            <td>${wifi.workDttm}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

wifiDetails.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.example.model.WifiInfo" %>
<%@ page import="com.example.repository.WifiInfoRepository" %>
<!DOCTYPE html>
<html>
<head>
    <title>WIFI 상세 정보</title>
</head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
    function addBookmark(wifiId) {
        var groupId = $('#bookmarkGroup').val();
        $.ajax({
            url: 'wifi',
            type: 'post',
            data: {
                action: 'addBookmark',
                wifiId: wifiId,
                groupId: groupId
            },
            success: function (response) {
                alert('북마크가 추가되었습니다.');
            },
            error: function () {
                alert('오류가 발생했습니다.');
            }
        });
    }

    function loadBookmarkGroups() {
        $.ajax({
            url: 'wifi',
            type: 'get',
            data: {
                action: 'getBookmarkGroupOptions'
            },
            success: function (data) {
                var bookmarkGroupSelect = $('#bookmarkGroup');
                bookmarkGroupSelect.empty();
                $.each(data, function (index, group) {
                    bookmarkGroupSelect.append($('<option>', {
                        value: group.id,
                        text: group.groupName
                    }));
                });
            },
            error: function () {
                alert('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
            }
        });
    }

    $(document).ready(function() {
        loadBookmarkGroups();
    });

    function goHome() {
        window.location.href = 'index.jsp';
    }
</script>
<body>
<%
    long id = Long.parseLong(request.getParameter("id"));
    WifiInfoRepository wifiRepo = new WifiInfoRepository();
    WifiInfo wifi = null;
    try {
        wifi = wifiRepo.findWifiById(id);
    } catch (Exception e) {
        e.printStackTrace();
    }
%>
<h1>WIFI 상세 정보</h1>
<div>
    <label for="bookmarkGroup">북마크 그룹 선택:</label>
    <select id="bookmarkGroup">
        <!-- 옵션은 AJAX 요청 후 동적으로 추가됩니다. -->
    </select>
    <button onclick="addBookmark(<%= wifi.getId() %>)">북마크 추가하기</button>
    <button onclick="goHome()"></button>
</div>
<table border="1">
    <tr>
        <th>거리(KM)</th>
        <td><%= wifi.getDistance() %></td>
    </tr>
    <tr>
        <th>관리번호</th>
        <td><%= wifi.getMgrNo() %></td>
    </tr>
    <tr>
        <th>자치구</th>
        <td><%= wifi.getWrdofc() %></td>
    </tr>
    <tr>
        <th>와이파이명</th>
        <td><%= wifi.getMainNm() %></td>
    </tr>
    <tr>
        <th>도로명주소</th>
        <td><%= wifi.getAdres1() %></td>
    </tr>
    <tr>
        <th>상세주소</th>
        <td><%= wifi.getAdres2() %></td>
    </tr>
    <tr>
        <th>설치위치(층)</th>
        <td><%= wifi.getInstlFloor() %></td>
    </tr>
    <tr>
        <th>설치유형</th>
        <td><%= wifi.getInstlTy() %></td>
    </tr>
    <tr>
        <th>설치기관</th>
        <td><%= wifi.getInstlMby() %></td>
    </tr>
    <tr>
        <th>서비스구분</th>
        <td><%= wifi.getSvcSe() %></td>
    </tr>
    <tr>
        <th>망종류</th>
        <td><%= wifi.getCmcwr() %></td>
    </tr>
    <tr>
        <th>설치년도</th>
        <td><%= wifi.getCnstcYear() %></td>
    </tr>
    <tr>
        <th>실내외구분</th>
        <td><%= wifi.getInoutDoor() %></td>
    </tr>
    <tr>
        <th>WIFI접속환경</th>
        <td><%= wifi.getRemars3() %></td>
    </tr>
    <tr>
        <th>X좌표</th>
        <td><%= wifi.getLat() %></td>
    </tr>
    <tr>
        <th>Y좌표</th>
        <td><%= wifi.getLnt() %></td>
    </tr>
    <tr>
        <th>작업일자</th>
        <td><%= wifi.getWorkDttm() %></td>
    </tr>
</table>
</body>
</html>

$(document).ready(function() { loadBookmarkGroups(); });는 jQuery를 사용하여 문서(DOM)가 완전히 로드되고 준비되었을 때 특정 함수를 실행하기 위한 구문입니다. 아래에서 이 구문이 하는 작업을 상세히 설명하겠습니다.

구문의 목적 이 구문은 웹 페이지의 DOM(Document Object Model)이 완전히 로드된 후에 특정 작업을 수행하도록 설정합니다. 여기서 설정된 작업은 loadBookmarkGroups 함수를 호출하는 것입니다.

동작 방식 $(document).ready(function() { … });

$(document).ready(…)는 jQuery에서 제공하는 이벤트 핸들러입니다. 이는 HTML 문서의 구조(DOM)가 완전히 로드되고 파싱된 후에 코드를 실행할 수 있게 합니다. 이는 브라우저가 HTML 문서를 파싱하고 DOM 트리를 완성한 후에 스크립트를 실행하도록 보장합니다. 따라서 스크립트가 DOM 요소에 접근할 때 해당 요소가 이미 로드되어 있어야 합니다. function() { loadBookmarkGroups(); }

function() { … }는 익명 함수입니다. 이는 document가 준비되면 실행될 코드 블록을 정의합니다. 이 코드 블록 내에서 loadBookmarkGroups 함수를 호출합니다. loadBookmarkGroups 함수의 역할 loadBookmarkGroups 함수는 AJAX 요청을 사용하여 서버에서 북마크 그룹 데이터를 가져와서, 가져온 데이터를

function loadBookmarkGroups() {
    $.ajax({
        url: 'wifi',
        type: 'get',
        data: {
            action: 'getBookmarkGroupOptions'
        },
        success: function (data) {
            var bookmarkGroupSelect = $('#bookmarkGroup');
            bookmarkGroupSelect.empty();
            $.each(data, function (index, group) {
                bookmarkGroupSelect.append($('<option>', {
                    value: group.id,
                    text: group.groupName
                }));
            });
        },
        error: function () {
            alert('북마크 그룹 데이터를 가져오는 중 오류가 발생했습니다.');
        }
    });
}

요약 $(document).ready(function() { … });: 이 구문은 DOM이 완전히 로드된 후에 내부에 정의된 코드를 실행하도록 합니다. loadBookmarkGroups() 함수 호출: DOM이 준비된 후에 loadBookmarkGroups 함수를 호출하여 서버로부터 북마크 그룹 데이터를 가져와

historyList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>위치 히스토리</title>
</head>
<body>
<h1>위치 히스토리</h1>
<table border="1">
    <tr>
        <th>ID</th>
        <th>위도</th>
        <th>경도</th>
        <th>검색 시간</th>
        <th>비고</th>
    </tr>
    <c:forEach var="history" items="${historyList}">
        <tr>
            <td>${history.id}</td>
            <td>${history.lat}</td>
            <td>${history.lnt}</td>
            <td>${history.searchTime}</td>
            <td>
                <form action="wifi" method="post" style="display:inline;">
                    <input type="hidden" name="action" value="deleteHistory">
                    <input type="hidden" name="id" value="${history.id}">
                    <button type="submit">삭제</button>
                </form>
            </td>
        </tr>
    </c:forEach>
</table>
<a href="index.jsp">홈으로 가기</a>
</body>
</html>

fetchResult.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>와이파이 정보 가져오기 결과</title>
</head>
<body>
<h1>와이파이 정보 가져오기 결과</h1>
<p>${savedCount}개의 WIFI 정보를 정상적으로 저장하였습니다.</p>
<a href="index.jsp">홈으로 가기</a>
</body>
</html>

bookmarkList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>북마크 목록</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        function updateBookmark(id) {
            // Update bookmark logic
        }

        function deleteBookmark(id) {
            $.ajax({
                url: 'wifi',
                type: 'post',
                data: {
                    action: 'deleteBookmark',
                    id: id
                },
                error: function () {
                    alert('북마크 삭제 중 오류가 발생했습니다.');
                }
            });
        }
    </script>
</head>
<body>
<h1>북마크 목록</h1>
<table border="1">
    <thead>
    <tr>
        <th>ID</th>
        <th>북마크 이름</th>
        <th>와이파이명</th>
        <th>등록일자</th>
        <th>비고</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach var="bookmark" items="${bookmarkList}">
        <tr>
            <td>${bookmark.id}</td>
            <td>${bookmark.groupName}</td>
            <td><a href="wifi?action=getWifiDetails&id=${bookmark.wifiId}">${bookmark.mainNm}</a></td>
            <td>${bookmark.createdAt}</td>
            <td>
                <form action="wifi" method="post" style="display:inline;">
                    <input type="hidden" name="action" value="deleteBookmark">
                    <input type="hidden" name="id" value="${bookmark.id}">
                    <button type="submit">삭제</button>
                </form>
            </td>
        </tr>
    </c:forEach>
    </tbody>
</table>
<a href="index.jsp">홈으로 가기</a>
</body>
</html>

bookmarkGroupList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>북마크 그룹 목록</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        function updateBookmarkGroup(id) {
            var groupName = prompt("새로운 북마크 그룹 이름을 입력하세요:");
            var sortOrder = prompt("새로운 순서를 입력하세요:");

            if (groupName && sortOrder) {
                $.ajax({
                    url: 'wifi',
                    type: 'post',
                    data: {
                        action: 'updateBookmarkGroup',
                        id: id,
                        groupName: groupName,
                        sortOrder: sortOrder
                    },
                    success: function () {
                        alert('북마크 그룹이 수정되었습니다.');
                        location.reload();
                    },
                    error: function () {
                        alert('북마크 그룹 수정 중 오류가 발생했습니다.');
                    }
                });
            }
        }

        function deleteBookmarkGroup(id) {
            if (confirm('정말로 이 북마크 그룹을 삭제하시겠습니까?')) {
                $.ajax({
                    url: 'wifi',
                    type: 'post',
                    data: {
                        action: 'deleteBookmarkGroup',
                        id: id
                    },
                    success: function () {
                        alert('북마크 그룹이 삭제되었습니다.');
                        location.reload();
                    },
                    error: function () {
                        alert('북마크 그룹 삭제 중 오류가 발생했습니다.');
                    }
                });
            }
        }

        function addBookmarkGroup() {
            var groupName = prompt("추가할 북마크 그룹 이름을 입력하세요:");
            var sortOrder = prompt("추가할 순서를 입력하세요:");

            if (groupName && sortOrder) {
                $.ajax({
                    url: 'wifi',
                    type: 'post',
                    data: {
                        action: 'addBookmarkGroup',
                        groupName: groupName,
                        sortOrder: sortOrder
                    },
                    success: function () {
                        alert('북마크 그룹이 추가되었습니다.');
                        location.reload();
                    },
                    error: function () {
                        alert('북마크 그룹 추가 중 오류가 발생했습니다.');
                    }
                });
            }
        }
    </script>
</head>
<body>
<h1>북마크 그룹 목록</h1>
<button onclick="addBookmarkGroup()">북마크 그룹 이름 추가</button>
<table border="1">
    <thead>
    <tr>
        <th>ID</th>
        <th>북마크 이름</th>
        <th>순서</th>
        <th>등록일자</th>
        <th>수정일자</th>
        <th>비고</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach var="group" items="${bookmarkGroups}">
        <tr>
            <td>${group.id}</td>
            <td>${group.groupName}</td>
            <td>${group.sortOrder}</td>
            <td>${group.createdAt}</td>
            <td>${group.updatedAt}</td>
            <td>
                <a href="javascript:void(0);" onclick="updateBookmarkGroup(${group.id})">수정</a> |
                <a href="javascript:void(0);" onclick="deleteBookmarkGroup(${group.id})">삭제</a>
            </td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

Leave a comment