[Java] JDBC MVC Pattern

클라우드·2024년 4월 13일
0

Java

목록 보기
17/20
post-thumbnail

1. Layered Architecture

  • Layered Architecture는 소프트웨어 개발에서 일반적으로 널리 사용되는 아키텍처를 지칭하는 용어.
  • 프로그램을 개발할 때 몇 개의 계층(Layer)를 이용할 지에 따라서 그 형태가 달라지는 데 일반적으로 많이 사용하는 것은 4계층이다. (4-Tier Architecture)
  • 객체지향적 개념으로 보자면 관심사의 분리(Seperation of Concern)와 동일한 개념이라고 생각하면 된다.
  • Layered Architecture는 소프트웨어 시스템을 여러 개의 논리적인 계층으로 나누어 구조화하는 방법론.
  • 각 계층은 특정한 역할을 수행하며, 시스템을 유연하고 유지보수가 용이하도록 만든다.
  • 각 계층은 서로 분리되어 있어, 한 계층의 변경이 다른 계층에 미치는 영향을 최소화한다.
  • 이렇게 각 계층은 자신의 역할에 집중하며, 시스템을 구조화하여 유지보수와 확장을 용이하게 한다.
  • 변경이 필요한 경우 한 계층만 수정하면 되므로 시스템의 유연성이 향상된다.
  • Presentation Layer : 사용자 인터페이스(UI), HTML, CSS, JavaScript
    • 화면을 만들어주는 Class ⇒ View 라고 한다.
  • Business Layer : 비즈니스 로직을 처리하고, 시스템의 핵심 기능을 구현, 주문을 처리하거나 계산을 수행하는 등의 작업 (데이터를 이용해서 로직을 수행, 단, DB 처리 제외)
    • Service(실제 로직을 담당하는 객체), (Domain) Model이라고 하는 데이터 객체가 포함된다. (VO)
  • Persistence Layer : 데이터의 영구적인 저장과 관리 담당, 데이터베이스와의 상호작용을 처리하고, 데이터를 저장하거나 불러오는 등의 작업, CRUD
    • 데이터베이스를 핸들링하는 객체(DAO - Data Access Object)
  • Database Layer : 실제 데이터를 저장하고 관리, 데이터베이스 관리 시스템(DBMS)
    • MySQL, Oracle

2. MVC Pattern

  • MVC는 소프트웨어 시스템을 3가지 타입의 component로 분할해서 개발하는 소프트웨어 개발 패턴.
  • Model(M) - Service (Model), Domain Model(VO)
  • View(V) - 사용자 interface 담당
  • Controller(C) - 시스템 흐름제어(View와 Service 간의 Bridge)

3. 도서 검색 프로그램을 MVC 형태로 구현

  • View : Class 하나 이용해서 화면 처리를 해야 한다. FXML로 처리한다.
  • booksearch.controller.BookSearchController.java
package booksearch.controller;

import booksearch.service.BookSearchService;
import booksearch.vo.BookVO;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;

import java.net.URL;
import java.util.ResourceBundle;

public class BookSearchController implements Initializable {

    @FXML private TextField textField;
    @FXML private Button searchBtn;
    @FXML private Button deleteBtn;
    @FXML private TableView tableView;

    @FXML private TableColumn<BookVO, String> isbn;
    @FXML private TableColumn<BookVO, String> title;
    @FXML private TableColumn<BookVO, Integer> price;
    @FXML private TableColumn<BookVO, String> author;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {

        isbn.setCellValueFactory(new PropertyValueFactory<>("bisbn"));
        title.setCellValueFactory(new PropertyValueFactory<>("btitle"));
        price.setCellValueFactory(new PropertyValueFactory<>("bprice"));
        author.setCellValueFactory(new PropertyValueFactory<>("bauthor"));

        searchBtn.setOnAction(e -> {
            // 검색 버튼을 눌렀을 때 해야 하는 작업
            // 실제 로직 처리는 Controller가 하지 않는다.
            // 로직 객체를 따로 만들어서 사용해야 한다.
            // 서비스 객체가 필요하다.
            BookSearchService service = new BookSearchService();

            // 해당 객체를 통해 비즈니스 수행
            String keyword = textField.getText();
            ObservableList<BookVO> data = service.searchBookByKeyword(keyword);
            tableView.setItems(data);
        });

        deleteBtn.setOnAction(e -> {
            // 삭제 버튼을 눌렀을 때 해야 하는 작업
            // 삭제할 책의 ISBN을 가져온다.
            BookVO selectedBook = (BookVO) tableView.getSelectionModel().getSelectedItem();
            if (selectedBook == null) {
                System.out.println("삭제할 책을 선택해주세요.");
                return;
            }

            String keywordDelete = selectedBook.getBisbn();

            String currentKeyword = textField.getText();

            BookSearchService service = new BookSearchService();
            ObservableList<BookVO> updatedData = service.deleteBookByKeyword(keywordDelete);
            ObservableList<BookVO> searchData = service.searchBookByKeyword(currentKeyword);

            tableView.setItems(searchData);

            tableView.getSelectionModel().clearSelection();
        });

    }
}
  • booksearch.dao.BookDAO.java
package booksearch.dao;

import booksearch.vo.BookVO;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class BookDAO {
    private Connection con;
    public BookDAO() {
        // JDBC Driver Loading
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String id = "root";
            String pw = "jiyun9163!";
            String JDBC_URL = "jdbc:mysql://localhost:3306/library?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
            con = DriverManager.getConnection(JDBC_URL, id, pw);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public ObservableList<BookVO> select(String keyword) {
        // IN Parameter를 이용해서 PreparedStatement를 생성한다.
        String sql = "SELECT bisbn, btitle, bprice, bauthor FROM book " + "WHERE btitle Like ?";
        ObservableList<BookVO> data = FXCollections.observableArrayList();
        try {
            PreparedStatement pstmt = con.prepareStatement(sql);
            // 당연히 In Parameter를 채워줘야 실행이 가능하다.
            pstmt.setString(1, "%" + keyword + "%");
            ResultSet rs = pstmt.executeQuery();

            // rs는 결과 레코드 집합의 포인터이다. 이것을 움직여서 우리가 select한 결과를 얻는다.
            while (rs.next()) { // 계속해서 내려간다.
                BookVO book = new BookVO(
                        rs.getString("bisbn"),
                        rs.getString("btitle"),
                        rs.getInt("bprice"),
                        rs.getString("bauthor"));
                data.add(book);
            }

        } catch (Exception e) {

        }
        return data;
    }

    public ObservableList<BookVO> delete(String keyword) {
        String sqlDelete = "DELETE FROM book WHERE bisbn = ?";
        String sqlSelect = "SELECT bisbn, btitle, bprice, bauthor FROM book";
        ObservableList<BookVO> data = FXCollections.observableArrayList();

        try {
            // 먼저 책을 삭제한다.
            PreparedStatement pstmtDelete = con.prepareStatement(sqlDelete);
            pstmtDelete.setString(1, keyword);
            int rowsAffected = pstmtDelete.executeUpdate();

            if (rowsAffected > 0) {
                System.out.println(keyword + "을(를) 삭제했습니다.");

                // 삭제 후에 변경된 데이터를 다시 조회한다.
                PreparedStatement pstmtSelect = con.prepareStatement(sqlSelect);
                ResultSet rs = pstmtSelect.executeQuery();

                // 새로 조회한 결과를 ObservableList에 추가한다.
                while (rs.next()) {
                    BookVO book = new BookVO(
                            rs.getString("bisbn"),
                            rs.getString("btitle"),
                            rs.getInt("bprice"),
                            rs.getString("bauthor"));
                    data.add(book);
                }
            } else {
                System.out.println("삭제할 데이터가 없습니다.");
            }
        } catch (Exception e) {
            System.out.println("삭제 중 오류 발생: " + e.getMessage());
            e.printStackTrace();
        }

        return data;
    }

}
  • booksearch.service.BookSearchService.java
package booksearch.service;

import booksearch.dao.BookDAO;
import booksearch.vo.BookVO;
import javafx.collections.ObservableList;

public class BookSearchService {

    public ObservableList<BookVO> searchBookByKeyword(String keyword) {
        // 이 안에서는 로직 처리를 한다.
        // 만약 Database 처리를 하게 되면 DAO 만들어서 사용해야 한다.
        // 별 다른 로직처리를 할 게 없다면 그냥 DB 처리만 하면 된다.
        BookDAO dao = new BookDAO();
        ObservableList<BookVO> result =  dao.select(keyword);

        return result;
    }
    // 로직 처리 객체
    // 당연히 이 안에는 business method가 존재한다.
    public ObservableList<BookVO> deleteBookByKeyword(String keyword) {
        // 이 안에서는 로직 처리를 한다.
        // 만약 Database 처리를 하게 되면 DAO 만들어서 사용해야 한다.
        // 별 다른 로직처리를 할 게 없다면 그냥 DB 처리만 하면 된다.
        BookDAO dao = new BookDAO();
        ObservableList<BookVO> result =  dao.delete(keyword);

        return result;
    }
}
  • booksearch.vo.BookVO.java
package booksearch.vo;

public class BookVO {
    private String bisbn;
    private String btitle;
    private int bprice;
    private String bauthor;

    public BookVO() {
    }

    public BookVO(String bisbn, String btitle, int bprice, String bauthor) {
        this.bisbn = bisbn;
        this.btitle = btitle;
        this.bprice = bprice;
        this.bauthor = bauthor;
    }

    public String getBisbn() {
        return bisbn;
    }

    public void setBisbn(String bisbn) {
        this.bisbn = bisbn;
    }

    public String getBtitle() {
        return btitle;
    }

    public void setBtitle(String btitle) {
        this.btitle = btitle;
    }

    public int getBprice() {
        return bprice;
    }

    public void setBprice(int bprice) {
        this.bprice = bprice;
    }

    public String getBauthor() {
        return bauthor;
    }

    public void setBauthor(String bauthor) {
        this.bauthor = bauthor;
    }
}
  • booksearch.view.JavaFXBookSearchTableView.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.FlowPane?>

<BorderPane fx:controller="booksearch.controller.BookSearchController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
    <bottom>
        <FlowPane prefHeight="28.0" prefWidth="600.0" BorderPane.alignment="CENTER">
            <children>
                <Label prefHeight="60.0" prefWidth="60.0" text="검색키워드">
                    <FlowPane.margin>
                        <Insets left="10.0" />
                    </FlowPane.margin>
                </Label>
                <TextField fx:id="textField" prefHeight="39.0" prefWidth="263.0">
                    <FlowPane.margin>
                        <Insets left="10.0" />
                    </FlowPane.margin>
                </TextField>
                <Button fx:id="searchBtn" mnemonicParsing="false" prefHeight="31.0" prefWidth="103.0" text="검색">
                    <FlowPane.margin>
                        <Insets left="20.0" />
                    </FlowPane.margin>
                </Button>
                <Button fx:id="deleteBtn" mnemonicParsing="false" prefHeight="30.0" prefWidth="105.0" text="선택된 책 삭제">
                    <FlowPane.margin>
                        <Insets left="20.0" />
                    </FlowPane.margin>
                </Button>
            </children>
        </FlowPane>
    </bottom>
    <center>
        <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
            <columns>
                <TableColumn fx:id="isbn" prefWidth="75.0" text="책 ISBN" />
                <TableColumn fx:id="title" prefWidth="322.0" text="책 제목" />
                <TableColumn fx:id="price" minWidth="0.0" prefWidth="75.0" text="책 가격" />
                <TableColumn fx:id="author" prefWidth="106.0" text="책 저자" />
            </columns>
        </TableView>
    </center>
</BorderPane>
package booksearch.view;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class BookSearchView extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = null;
        FXMLLoader loader = new FXMLLoader(
                getClass().getResource("JavaFXBookSearchTableView.fxml"));
        try {
            root = loader.load();
        } catch (Exception e) {
            System.err.println(e);
        }

        Scene scene = new Scene(root);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
profile
안녕하세요 :)

0개의 댓글