반응형

어쩌다보니 Maven에 익숙해졌고, 이번 프로젝트는 Mybatis, JavaFX가 함께 들어가는 프로젝트가 되었다. 이걸 만드는데까지는 괜찮았는데, 배포하려니 또 어렵다. 중요한건 jre와 javafx-sdk가 jar파일 또는 exe파일과 함께 있어야 한다는 것이다.

 

1. jre

각종 버전 문제를 피하기 위해 배포할때엔 런타임환경을 함께 구성해서 배포하는게 낫겠다고 판단했다. 

jre는 새로 다운받는게 아니라, jdk폴더의 bin, lib, conf폴더만 복사해서 가져오면 된다.

   /jre
     ㄴ/bin
     ㄴ/lib
     ㄴ/conf

 

<2025.07.10수정>

위의 방식으로는 자바가 전혀 안깔린 PC에서는 작동하지 않는다. 아래의 jlink 명령어로 jre를 만든다. 이렇게하면 javafx까지 포함된 jre가 만들어진다. 단, 이러고도 실행이 되지 않을 수 있다. 그럴땐 그냥 JDK를 복사해서 jre라고 바꾼 후 진행한다. 

jlink --module-path "%JAVA_HOME%\jmods;C:\javafx-sdk-17.0.15\lib" ^
      --add-modules java.base,java.desktop,java.logging,java.sql,java.xml,javafx.controls,javafx.fxml ^
      --output jre

2. javafx-sdk

인텔리제이에서 pom.xml 설정으로 라이브러리가 받아졌겠지만, 다시한번 적당한 버전을 받는다. 

- 공식사이트: https://gluonhq.com/products/javafx

 

JavaFX - Gluon

Roadmap Release GA Date Latest version Minimum JDK Long Term Support Extended or custom support Details 25 September 2025 early access 22 no 24 March 2025 24.0.1 (April 2025) 22 no upon request details 23 September 2024 23.0.2 (January 2025) 21 no upon req

gluonhq.com

- 직접다운로드: https://download2.gluonhq.com/openjfx/17.0.15/openjfx-17.0.15_windows-x64_bin-sdk.zip

 

3. 폴더 구성

javaFX maven 프로젝트를 빌드하면 Target 폴더에 jar파일이 생성되게 된다. 동일한 폴더에 jre와 javafx-sdk를 복사해 넣으면 작동 환경은 구성된 것이다. 그런데 이렇게 해도 잘 안된다. 그냥 javafx-sdk에 있는 lib, bin을 jre폴더에 추가 복사해넣는게 제일 낫다.

 

4. pom.xml

우선 jar파일이 정상적으로 실행이 되어야한다. 그런데 실행하다보면 MainClass가 빠졌다는 등의 오류가 발생하는 경우가 있다. 이런경우 maven-shade-plugin을 사용해 Fat JAR을 생성한다. 아래 플러그인 부분에서 Fat JAR만들기 부분 참조

<plugins>
          <!-- 컴파일러 -->
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.11.0</version>
              <configuration>
                  <source>17</source>
                  <target>17</target>
              </configuration>
          </plugin>

          <!-- ✅ Fat JAR (실행 가능한 JAR) 만들기 -->
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-shade-plugin</artifactId>
              <version>3.5.0</version>
              <executions>
                  <execution>
                      <phase>package</phase>
                      <goals>
                          <goal>shade</goal>
                      </goals>
                      <configuration>
                          <transformers>
                              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                  <mainClass>com.example.javafx05.HelloApplication</mainClass>
                              </transformer>
                          </transformers>
                      </configuration>
                  </execution>
              </executions>
          </plugin>
      </plugins>

 

 

실행은 우측의 Maven아이콘 클릭 후, 수명주기 -> package를 더블 클릭하면 패키징이 되면서 jar파일이 생성된다.

 

java --module-path "C:\javafx-sdk-17.0.15\lib" 
     --add-modules javafx.controls,javafx.fxml 
     -jar target/javafx05-1.0-SNAPSHOT-shaded.jar

jar 파일 생성 후에는 해당 폴더에서 위의 형식으로 명령어를 주면 실행되는 모습을 볼 수 있다.

 

5. Launch4j로 exe파일 만들기

launch4j는 지금 실행한 환경을 하나의 파일로 packaging하는 것 뿐이다.

 

우선, 아웃풋 파일명과 Jar파일의 위치를 인식시키고..

 

 

그 다음으로는 미리 준비해둔 jre의 위치, 최소 jre버전정보, 그리고 JVM option을 넣어준 뒤 빌드하면 된다. 마지막 배포시에도 jre와 java-fx폴더는 exe파일과 동일한 폴더에 계속 함께 존재해야 실행된다.

 

<2025.07.10 수정>

bundled jre path는 exe파일과 같은 폴더에 넣을 예정이므로, 그냥 "jre"다.

 

- 끝 -

반응형
반응형

C# WPF로 작업하던 프로그램이 오류를 뱉는데, 디버깅이 안되어 Java로 전부 바꾸는 실험을 했다. WPF는 Visual Studio Code에서 만든거라, 디버깅이 좀 힘들었다. 반면에 Java는 무료이면서도 강력한 IntelliJ가 있다. 물론 이클립스도 있다. 

이번엔 IntelliJ로 프로젝트를 만들었는데, 사실 그것도 처음이라 좀 많이 힘들었다.

 

이번 프로젝트는 출입 통제하는 프로그램이다. 자료실에 드나드는 사람에게 ID 태그를 읽혀서 들어올땐 "안녕하세요", 나갈땐 "안녕히가세요"를 출력해주고, 다른 탭에서 전체 출입인원 목록을 확인하면 된다.

 

프로젝트 구성은 아래와 같다. 

Spring Boot를 해본 사람이라면 좀 익숙할 수도 있겠다. 그것과 비슷한 구조로 만들려고 신경을 써봤다. 각 파일들의 코드를 기록으로 남기고자 한다. 참고하려는 분들은 아래의 프로젝트 구성도를 계속 확인하면서 보면 좋겠다.

 

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  .
  .
  .
   <!-- MyBatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
    </dependency>
    
    <!-- OraCLE.. 두가지가 다 필요했다. -->
    <dependency>
      <groupId>com.oracle.database.jdbc</groupId>
      <artifactId>ojdbc11</artifactId>
      <version>23.5.0.24.07</version>
    </dependency>

    <dependency>
      <groupId>com.oracle.ojdbc</groupId>
      <artifactId>orai18n</artifactId>
      <version>19.3.0.0</version>
    </dependency>

    <!-- Serial 통신 라이브러리 -->
    <dependency>
      <groupId>com.fazecast</groupId>
      <artifactId>jSerialComm</artifactId>
      <version>2.11.0</version>
    </dependency>

  </dependencies>

  ...

 

 

2. 화면구성(hello-view.fxml)

화면은 단순하다. 그렇지만 수많은 시행착오끝에 만든 화면이다. JavaFX로 만들어보는건 처음이라..

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.String?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<TabPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="1400.0" tabClosingPolicy="UNAVAILABLE" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.javafx05.HelloController">
  <tabs>
    <Tab text="Home">
      <content>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
          <children>
            <VBox alignment="TOP_CENTER" prefHeight="571.0" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
              <children>
                <Label alignment="CENTER" contentDisplay="CENTER" prefHeight="64.0" prefWidth="1000.0" style="-fx-font-weight: bold; -fx-text-fill: #2c3e50;" text="DATA ROOM SECURITY" textAlignment="CENTER">
                  <font>
                    <Font name="System Bold" size="72.0" />
                  </font>
                           <VBox.margin>
                              <Insets bottom="20.0" top="20.0" />
                           </VBox.margin>
                </Label>
                <Label fx:id="welcomeText" alignment="CENTER" contentDisplay="CENTER" prefHeight="200.0" prefWidth="1200.0" style="-fx-text-fill: #3498db;" text="입/퇴장시 체크해주세요" textAlignment="CENTER">
                  <font>
                    <Font name="System Bold" size="96.0" />
                  </font>
                           <VBox.margin>
                              <Insets bottom="60.0" top="30.0" />
                           </VBox.margin>
                </Label>
                <HBox alignment="CENTER" prefHeight="278.0" prefWidth="1400.0">
                  <children>
                    <VBox alignment="CENTER" prefHeight="334.0" prefWidth="500.0">
                      <children>
                            <GridPane alignment="CENTER" prefHeight="293.0" prefWidth="500.0">
                              <columnConstraints>
                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="194.0" minWidth="10.0" prefWidth="103.0" />
                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="297.0" minWidth="10.0" prefWidth="297.0" />
                              </columnConstraints>
                              <rowConstraints>
                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                  <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                  <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                              </rowConstraints>
                               <children>
                                  <Button alignment="CENTER" contentDisplay="RIGHT" mnemonicParsing="false" prefHeight="50.0" prefWidth="200.0" text="Purpose" GridPane.halignment="CENTER">
                                             <font>
                                                <Font size="24.0" />
                                             </font></Button>
                                  <Button alignment="CENTER" mnemonicParsing="false" prefHeight="50.0" prefWidth="200.0" text="Location" textAlignment="CENTER" wrapText="true" GridPane.halignment="CENTER" GridPane.rowIndex="1">
                                             <font>
                                                <Font size="24.0" />
                                             </font></Button>
                                  <Button alignment="CENTER" mnemonicParsing="false" prefWidth="200.0" text="DEPT" GridPane.halignment="CENTER" GridPane.rowIndex="2">
                                             <font>
                                                <Font size="24.0" />
                                             </font></Button>
                                  <Button alignment="CENTER" mnemonicParsing="false" prefWidth="200.0" text="ID" GridPane.halignment="CENTER" GridPane.rowIndex="3">
                                             <font>
                                                <Font size="24.0" />
                                             </font></Button>
                                  <Button alignment="CENTER" mnemonicParsing="false" prefWidth="200.0" text="NAME" GridPane.halignment="CENTER" GridPane.rowIndex="4">
                                             <font>
                                                <Font size="24.0" />
                                             </font></Button>
                                  <ComboBox fx:id="purpose_combo" prefHeight="50.0" prefWidth="300.0" promptText="도서 열람" style="-fx-alignment: CENTER; -fx-font-size: 18px; -fx-font-weight: bold;" GridPane.columnIndex="1" GridPane.rowIndex="0">
                                    <items>
                                      <FXCollections fx:factory="observableArrayList">
                                        <String fx:value="도서 열람" />
                                        <String fx:value="도서 대출" />
                                        <String fx:value="도서 반납" />
                                        <String fx:value="스캔" />
                                        <String fx:value="기타" />
                                      </FXCollections>
                                    </items>
                                  </ComboBox>
                                  <ComboBox fx:id="location_combo" prefHeight="50.0" prefWidth="300.0" promptText="무인기사업부자료실" style="-fx-font-size: 18px;" GridPane.columnIndex="1" GridPane.rowIndex="1">
                                    <items>
                                    <FXCollections fx:factory="observableArrayList">
                                      <String fx:value="무인기사업부자료실" />
                                      <String fx:value="군용기정비자료실" />
                                      <String fx:value="품질경영부" />
                                    </FXCollections>
                                  </items>
                                 </ComboBox>
                                  <TextField fx:id="dept_field" prefHeight="50.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
                                             <font>
                                                <Font size="18.0" />
                                             </font></TextField>
                                  <TextField fx:id="id_field" prefHeight="50.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
                                             <font>
                                                <Font size="18.0" />
                                             </font></TextField>
                                  <TextField fx:id="name_field" prefHeight="50.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="4">
                                             <font>
                                                <Font size="18.0" />
                                             </font></TextField>
                               </children>
                            </GridPane>
                      </children>
                    </VBox>
                  </children>
                </HBox>
              </children>
            </VBox>
          </children></AnchorPane>
      </content>
    </Tab>
      <Tab text="History">
          <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="794.0" prefWidth="1400.0">
                  <children>
                      <VBox prefHeight="778.0" prefWidth="1400.0" AnchorPane.bottomAnchor="-8.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="-2.0" AnchorPane.topAnchor="1.0">
                          <children>
                              <GridPane alignment="CENTER_RIGHT">
                                  <columnConstraints>
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                                  </columnConstraints>
                                  <rowConstraints>
                                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                                  </rowConstraints>
                                  <children>
                                      <Label text="이름" GridPane.halignment="RIGHT">
                                 <GridPane.margin>
                                    <Insets right="10.0" />
                                 </GridPane.margin></Label>
                                      <Label text="Purpose" GridPane.halignment="RIGHT" GridPane.rowIndex="1">
                                 <GridPane.margin>
                                    <Insets right="10.0" />
                                 </GridPane.margin></Label>
                                      <Label text="Location" GridPane.halignment="RIGHT" GridPane.rowIndex="2">
                                 <GridPane.margin>
                                    <Insets right="10.0" />
                                 </GridPane.margin></Label>
                                      <Label text="From" GridPane.columnIndex="2" GridPane.halignment="RIGHT">
                                 <GridPane.margin>
                                    <Insets right="10.0" />
                                 </GridPane.margin></Label>
                                      <Label text="To" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="1">
                                 <GridPane.margin>
                                    <Insets right="10.0" />
                                 </GridPane.margin></Label>
                                      <TextField fx:id="search_name" GridPane.columnIndex="1" />
                                      <ComboBox fx:id="search_purpose" prefHeight="17.0" prefWidth="297.0" promptText="도서 열람" GridPane.columnIndex="1" GridPane.rowIndex="1">
                                          <items>
                                              <FXCollections fx:factory="observableArrayList">
                                                  <String fx:value="" />
                                                  <String fx:value="도서 열람" />
                                                  <String fx:value="도서 대출" />
                                                  <String fx:value="도서 반납" />
                                                  <String fx:value="스캔" />
                                                  <String fx:value="기타" />
                                              </FXCollections>
                                          </items>
                                      </ComboBox>
                                      <ComboBox fx:id="search_location" prefHeight="17.0" prefWidth="297.0" promptText="무인기사업부자료실" GridPane.columnIndex="1" GridPane.rowIndex="2">
                                          <items>
                                              <FXCollections fx:factory="observableArrayList">
                                                  <String fx:value="" />
                                                  <String fx:value="무인기사업부자료실" />
                                                  <String fx:value="군용기정비자료실" />
                                                  <String fx:value="품질경영부" />
                                              </FXCollections>
                                          </items>
                                      </ComboBox>
                                      <DatePicker fx:id="search_from" GridPane.columnIndex="3" />
                                      <DatePicker fx:id="search_to" GridPane.columnIndex="3" GridPane.rowIndex="1" />
                                      <Button fx:id="history_search" minWidth="100.0" mnemonicParsing="false" onAction="#history_search_click" text="검색" GridPane.columnIndex="5" />
                                      <Button fx:id="initiate_search" minWidth="100.0" mnemonicParsing="false" onAction="#initiate_search_click" text="초기화" GridPane.columnIndex="5" GridPane.rowIndex="1" />
                                  </children>
                                  <VBox.margin>
                                      <Insets top="20.0" />
                                  </VBox.margin>
                              </GridPane>
                              <TableView fx:id="historyTable" prefHeight="500.0" prefWidth="1400.0" VBox.vgrow="ALWAYS">
                                  <columns>
                                      <TableColumn fx:id="colEmpNo" minWidth="100.0" prefWidth="150.0" text="사번" style="-fx-alignment:CENTER"/>
                                      <TableColumn fx:id="colName" minWidth="100.0" prefWidth="150.0" text="이름"  style="-fx-alignment:CENTER"/>
                                      <TableColumn fx:id="colDept" minWidth="100.0" prefWidth="150.0" text="부서"  style="-fx-alignment:CENTER"/>
                                      <TableColumn fx:id="colIn_D" minWidth="200.0" prefWidth="250.0" sortType="DESCENDING" text="입장" />
                                      <TableColumn fx:id="colOut_D" minWidth="200.0" prefWidth="250.0" text="퇴장" />
                                      <TableColumn fx:id="colPurpose" minWidth="150.0" prefWidth="200.0" text="방문목적" />
                                      <TableColumn fx:id="colLocation" minWidth="200.0" prefWidth="300.0" resizable="true" text="위치" />
                                  </columns>
                                  <VBox.margin>
                                      <Insets top="15.0" />
                                  </VBox.margin>
                              </TableView>
                          </children>
                      </VBox>
                  </children></AnchorPane>
          </content>
      </Tab>

  </tabs>
</TabPane>

 

3. module-info.java

JavaFX에서 모듈을 인식하기 위해서는 module-info.java가 필요하다. 한 프로젝트 안에서 동작하는 것임에도 손으로 하나씩 추가해야하는 부분이 많다. 뭐 하나라도 빠지면 동작을 안해서 난감했다.

module com.example.javafx05 {
    requires javafx.controls;
    requires javafx.fxml;
    requires org.mybatis;  <--추가
    requires java.sql;     <--추가
    requires com.fazecast.jSerialComm;  <--추가


    opens com.example.javafx05 to javafx.fxml;
    exports com.example.javafx05;
    exports com.example.javafx05.emp to org.mybatis;     <--추가(Mapper용)
    exports com.example.javafx05.history to org.mybatis; <--추가(Mapper용)

    opens mapper;<--추가(Mapper용)
}

 

 

4. Database접속(mybatis-config.xml)

이 파일은 특별할 건 없다. 형식에 맞춰서 입력만 해주면 된다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
                <property name="url" value="jdbc:oracle:thin:@10.48.63.71:1526:database_name"/>
                <property name="username" value="my_user_name"/>
                <property name="password" value="my_user_pw"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/empMapper.xml"/>
        <mapper resource="mapper/historyMapper.xml"/>
    </mappers>
</configuration>

 

5. MyBatisUtil.java

MyBatis를 사용시 데이터베이스 연결을 관리하는 파일이다. 

package com.example.javafx05;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }
}

 

6. HelloApplication

시작점이 되는 파일이다. 코드 아래쪽을 보면 SerialService라는 서비스를 불러내는데, controller를 인자로 넘겨준다. controller는 화면 구성요소와 그에 대한 controll을 하는 부분이다. 이 controller를 넘겨줘야 Serial 데이터의 입력에 맞춰 화면에 정보를 뿌릴 수 있다. 

package com.example.javafx05;

import com.example.javafx05.emp.EmpDto;
import com.example.javafx05.emp.EmpService;
import com.fazecast.jSerialComm.SerialPort;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(loader.load(), 900, 800);
        HelloController controller = loader.getController();

        stage.setTitle("Data Room Security");
        stage.setScene(scene);
        stage.show();

        SerialService serialService = new SerialService(controller);  //<-- 시리얼 통신 + UI 컨트롤
        serialService.initializeSerialCommunication();                //<-- 시리얼 통신 + UI 컨트롤
    }

    public static void main(String[] args) {
        launch();
    }
}

 

7. SerialService.java

위의 HelloApplication에서 시작하여 Serial데이터를 받기 시작하는 서비스이다. 데이터를 너무 빨리 처리하면 Serial데이터가 두번에 나눠져 들어온다. 그래서 초기에 약간의 대기시간(50ms)을 두었으며, 그래도 모를 예외를 대비해서 12자리를 모두 채우면 데이터를 처리하도록 만들었다.

 

데이터(message)가 취득되면 emp_check이라는 직원 체크 함수로 간다. 이 함수에서 MyBatis를 통한 DB데이터를 가져오고, 최종 만들어진 데이터 및 인사말은 

```Platform.runLater(() -> controller.updateUI(emp, finalHello));``` 

부분에서 화면으로 전달된다.

 

package com.example.javafx05;

import com.example.javafx05.emp.EmpDto;
import com.example.javafx05.emp.EmpService;
import com.fazecast.jSerialComm.SerialPort;
import javafx.application.Platform;
import javafx.scene.control.Alert;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SerialService {

    private static final int MESSAGE_LENGTH = 12;
    private final HelloController controller;
    private final EmpService empService = new EmpService();
    private SerialPort serialPort;
    private final StringBuilder messageBuilder = new StringBuilder();
    private final ExecutorService serialThreadPool = Executors.newSingleThreadExecutor();


    public SerialService(HelloController controller){
        this.controller = controller;
    }

    public void initializeSerialCommunication(){
        SerialPort[] ports = SerialPort.getCommPorts();
        if (ports.length ==0){
            System.out.println("No serial ports available");
            return;
        }

        serialPort = ports[0];
        //serialPort = SerialPort.getCommPort(controller.getSelectedPort());
        serialPort.setComPortParameters(9600, 8, 1, 0);

        if(!serialPort.openPort()){
            System.err.println("Failed to open serial port: " + serialPort.getSystemPortName());
            showAlert(Alert.AlertType.ERROR, "에러","에러","Failed to open serial port: "+serialPort.getSystemPortName());
            return;
        }
        System.out.println("Serial port opend: " + serialPort.getSystemPortName());

        // 데이터 수신 스레드 시작
        //new Thread(() -> {
        serialThreadPool.execute(() -> {
                    byte[] readBuffer = new byte[MESSAGE_LENGTH*2];
                    while (serialPort.isOpen()) {
                        try {
                            if (serialPort.bytesAvailable() > 0) {
                                Thread.sleep(50); // 짧은 시간 대기
                                int numRead = serialPort.readBytes(readBuffer, Math.min(serialPort.bytesAvailable(), readBuffer.length));
                                if (numRead > 0) {
                                    messageBuilder.append(new String(readBuffer, 0, numRead));
                                }
                                //messageBuffer.append(receivedData);
                                // 12자리 데이터가 모두 수신되었는지 확인
                                if (messageBuilder.length() >= MESSAGE_LENGTH) {
                                    String fullMessage = messageBuilder.substring(0, MESSAGE_LENGTH);
                                    messageBuilder.delete(0,MESSAGE_LENGTH);

                                    String empId = fullMessage.substring(4, 11);

                                    try {
                                        emp_check(empId);
                                    } catch (Exception e) {
                                        //Platform.runLater(() -> showAlert(Alert.AlertType.WARNING, "에러", "에러",e.toString()));
                                        showAlert(Alert.AlertType.WARNING, "에러", "에러",e.toString());
                                    }
                                    Thread.sleep(2000); // 3초
                                    init_screen();
                                }
                            } else {
                                // 데이터가 일정 시간 동안 들어오지 않으면 (예: 50ms) message 초기화
                                // 이는 데이터가 끊겨서 들어올 때 이전 데이터와 합쳐지는 것을 방지합니다.
                                try {
                                    Thread.sleep(10); // 짧은 시간 대기
                                } catch (InterruptedException e) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            // 스레드 종료 전에 포트 닫기 고려
                            if (serialPort != null && serialPort.isOpen()) {
                                serialPort.closePort();
                            }
                            break; // 예외 발생 시 스레드 종료. 다음 프로세스로 진행을 멈춤
                        }
                    }
                    if (serialPort != null && serialPort.isOpen()) {
                        serialPort.closePort();
                        System.out.println("Serial Port Closed.");
                    }
        });
        //}).start();
    }



    private void emp_check(String emp_n){
        String hello=""; // 인사말 타이틀 메시지
        EmpDto emp = null;


        try{
            emp = empService.selectAll(emp_n);
        } catch (Exception e){
            e.printStackTrace();
            showAlert(Alert.AlertType.WARNING, "에러", "에러",e.toString());

            return; // 예외 발생 시 이후 로직 실행 방지
        }

        if(emp==null){
            showAlert(Alert.AlertType.WARNING, "에러", "에러","해당 사원 번호에 대한 정보가 없습니다.");

            init_screen();
            return;
        }

        Map<String, String> history = empService.check_history(emp_n);
        String purpose= controller.getSelectedPurpose();
        String location=controller.getSelectedLocation();

        if(history==null){
            hello="안녕하세요";
            empService.in_log(emp_n, purpose, location);
        }else{
            hello="수고하셨습니다.";
            empService.out_log(emp_n);
        }

        final String finalHello=hello;
        final EmpDto finalEmp = emp;
        Platform.runLater(() -> {
            try{
                controller.updateUI(finalEmp, finalHello);
            } catch (Exception e){
                e.printStackTrace();
            }
        });
    }

    private void init_screen(){
        Platform.runLater(() -> controller.updateUI(null,"입/퇴장시 체크해주세요"));
    }

    private void showAlert(Alert.AlertType type, String title, String header, String content){
        Platform.runLater(() -> {
            Alert alert = new Alert(type);
            alert.setTitle(title);
            alert.setHeaderText(header);
            alert.setContentText(content);
            alert.showAndWait();
        });
    }
}

 

8. EmpService (직원정보 관련 서비스)

package com.example.javafx05.emp;

import com.example.javafx05.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.HashMap;
import java.util.Map;

public class EmpService {
    public EmpDto selectAll(String emp_number){
        SqlSession session = MyBatisUtil.getSession();
        EmpDto emp_found = null;

        try{
            EmpMapper mapper = session.getMapper(EmpMapper.class);
            emp_found = mapper.select_member(emp_number);
//            System.out.println(emp_found.toString());
        } catch (Exception ex){
            ex.printStackTrace();
        } finally {
            session.close();
        }
        return emp_found;
    }

    public Map<String, String> check_history(String emp_number){
        SqlSession session = MyBatisUtil.getSession();
        Map<String, String> result = new HashMap<String, String>();

        try{
            EmpMapper mapper = session.getMapper(EmpMapper.class);
            result = mapper.history_found(emp_number);
        } catch (Exception ex){
            ex.printStackTrace();
        } finally {
            session.close();
        }
        return result;
    }

    public void in_log(String emp_number, String purpose, String location){
        SqlSession session = MyBatisUtil.getSession();
        try{
            EmpMapper mapper = session.getMapper(EmpMapper.class);
            mapper.history_in_log(emp_number, purpose, location);
        } catch (Exception ex){
            ex.printStackTrace();
        } finally {
            session.close();
        }
        return;
    }

    public void out_log(String emp_number){
        SqlSession session = MyBatisUtil.getSession();
        try{
            EmpMapper mapper = session.getMapper(EmpMapper.class);
            mapper.history_out_log(emp_number);
        } catch (Exception ex){
            ex.printStackTrace();
        } finally {
            session.close();
        }
        return;
    }

}

 

9. EmpDto 

package com.example.javafx05.emp;

public record EmpDto (
    String emp_no,
    String emp_x,
    String kornm_n,
    String hannm_n,
    String engnm_n,
    String res,
    String dept_c,
    String dept_n
) {
}

 

10. EmpMapper

package com.example.javafx05.emp;

import org.apache.ibatis.annotations.Param;

import java.util.Map;

public interface EmpMapper {
    EmpDto select_member(String emp_no);
    Map<String, String> history_found(String emp_no);
    void history_in_log(@Param("emp_no") String emp_no,@Param("purpose")  String purpose,@Param("location") String location);
    void history_out_log(String emp_no);
}

 

 

11. empMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.javafx05.emp.EmpMapper">
    <select id="select_member" resultType="com.example.javafx05.emp.EmpDto">
        select emp_# as emp_no,
        emp_x,
        kornm_n,
        hannm_n,
        engnm_n,
        res_#1 as res,
        dept_c,
        dept_n
        from bemp a
        where a.emp_#=#{emp_no}
    </select>

    <select id="history_found" resultType="map">
        select EMP_# as emp_no,
        IN_D,
        OUT_D,
        PURPOSE,
        LOCATION
        FROM BEMP_DATAROOM_HSTRY a
        where a.emp_#=#{emp_no}
        AND to_char(a.in_d,'YYYYMMDD')=to_char(SYSDATE,'YYYYMMDD')
        AND a.OUT_D IS null
        ORDER BY a.IN_D DESC
    </select>

    <select id="history_in_log">
        insert into BEMP_DATAROOM_HSTRY (emp_#, in_d, out_d, purpose, location)
        values (#{emp_no}, sysdate,NULL,#{purpose},#{location})
    </select>

    <select id="history_out_log">
        update BEMP_DATAROOM_HSTRY a
        set a.out_d=sysdate
        where a.emp_#=#{emp_no}
        and a.out_d IS NULL
        AND to_char(a.in_d,'YYYYMMDD')=to_char(SYSDATE,'YYYYMMDD')
    </select>
</mapper>

 

12. HistoryDto

package com.example.javafx05.history;

public record HistoryDto( String emp_no,
                          String kornm_n,
                          String dept_c,
                          String in_d,
                          String out_d,
                          String purpose,
                          String location) {
}

 

13. HistoryMapper

package com.example.javafx05.history;

import org.apache.ibatis.annotations.Param;

import java.time.LocalDate;
import java.util.List;

public interface HistoryMapper {
    List<HistoryDto> historyRecords(@Param("emp_name") String emp_name,
                                    @Param("purpose") String purpose,
                                    @Param("location")  String location,
                                    @Param("from")  String from,
                                    @Param("to")  String to); //,, LocalDate to
}

 

14. HistoryService

package com.example.javafx05.history;

import com.example.javafx05.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

public class HistoryService {
    public List<HistoryDto> find_history(String emp_name, String purpose, String location, String from, String to){
        SqlSession session = MyBatisUtil.getSession();
        List<HistoryDto> result = new ArrayList<>();

        try{
            HistoryMapper mapper = session.getMapper(HistoryMapper.class);
            result = mapper.historyRecords(emp_name, purpose, location, from, to);
        } catch (Exception ex){
            ex.printStackTrace();
        } finally {
            session.close();
        }
        return result;
    }
}

 

15. historyMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.javafx05.history.HistoryMapper">
    <select id="historyRecords" resultType="com.example.javafx05.history.HistoryDto">
        select a.emp_# ,
        b.KORNM_N ,
        b.dept_c,
        a.in_d,
        a.out_d,
        a.purpose,
        a.location
        from temp_dataroom_hstry a, temp b
        where a.emp_#=b."EMP_#"
        <if test="emp_name!=null and emp_name!=''">
            and b.kornm_n like '%'||#{emp_name}||'%'
        </if>
        <if test="purpose!=null and purpose!=''">
            and a.purpose like '%'||#{purpose}||'%'
        </if>
        <if test="location!=null and location!=''">
            and a.location like '%'||#{location}||'%'
        </if>
        <if test="from!=null and from!=''">
            and a.in_d &gt; to_date(#{from},'YYYY-MM-DD')
        </if>
        <if test="to!=null and to!=''">
            and a.in_d &lt; to_date(#{to},'YYYY-MM-DD')
        </if>
        order by a.in_d desc
    </select>

</mapper>

 

 

[결과 화면 ]

 

반응형
반응형

 

wpf처럼 테마 적용법을 찾아내고 싶었으나 잘 안된다. 대신 material skin을 적용하면 쉽게 화면 스타일을 바꿀 수 있다.

 

1. NugetPackageManager GUI 설치

2. MaterialSkin 설치
 - VSCode에서 Ctrl+Shift+P ->Nuget Package Manager GUI에서 materialskin 검색

 

3. Form1.cs 파일에 적용

namespace winform_ex02;
using System;
using System.Text;
using System.Xml;
using MaterialSkin; 	//추가
using MaterialSkin.Controls;	//추가


// public partial class Form1 : Form
public partial class Form1 : MaterialForm
{
    public Form1()
    {
        InitializeComponent();
        // 아래 추가
        var materialSkinManager = MaterialSkinManager.Instance;
        materialSkinManager.AddFormToManage(this);
        materialSkinManager.Theme = MaterialSkinManager.Themes.DARK;
        materialSkinManager.ColorScheme=new ColorScheme(Primary.BlueGrey900, Primary.BlueGrey900, Primary.BlueGrey500, Accent.LightBlue200, TextShade.WHITE);
		// 여기까지		
        
        // 이하는 버튼 클릭 이벤트...
        this.button1.Click += new System.EventHandler(this.button1_Click);
    }

    private void button1_Click(object sender, EventArgs e)
	{
		OpenFileDialog openFileDlg = new OpenFileDialog();        
		openFileDlg.Filter = "XML Files (*.xml)|*.xml";
		openFileDlg.ShowDialog();
		// MessageBox.Show("Hello World!");
		if (openFileDlg.FileName.Length > 0)
		{
			foreach (string Filename in openFileDlg.FileNames)
			{
				this.textBox1.Text = Filename;                
				
				XmlDocument xmlDoc = new XmlDocument();  // XmlDocument 객체 생성
				xmlDoc.Load(Filename);                 // XML 파일 로드                               
				XmlNodeList allNodes = xmlDoc.SelectNodes("//bookstore//*");
				StringBuilder sb = new StringBuilder();
				
				foreach(XmlNode node in allNodes){
					if(node.NodeType == XmlNodeType.Element){
						sb.Append(node.InnerText);
					}
					else if (node.NodeType == XmlNodeType.Text){
						sb.Append(node.Value );
					}
					// sb.Append("\n");
					sb.Append(Environment.NewLine);
				}

				this.result1.Text = sb.ToString();
			}
		}
	}
}

 

ColorScheme클래스의 생성 인자

materialSkinManager.ColorScheme=new ColorScheme(Primary.BlueGrey900, Primary.BlueGrey900, Primary.BlueGrey500, Accent.LightBlue200, TextShade.WHITE);
        // 인자1 `Primary.BlueGrey900`: 테마의 기본 색상. 메인 강조 색상. 진한 청회색.
        // 인자2 `Primary.BlueGrey900`: 보조 색상으로 호버 색상. 초점 색상.
        // 인자3 `Primary.BlueGrey500`: 3차 색상. 배경색 or 은은한 그림자 색상으로 가끔 사용됨. 
        // 인자4 `Accent.LightBlue200`: 버튼, 링크 등 강조 색상. 연한 파란색
        // 인자5 `TextShade.WHITE`: 텍스트 음영

 

4. Form1.Designer.cs (버튼 클릭 이벤트/폼 예제)

namespace winform_ex02;

partial class Form1
{
    /// <summary>
    ///  Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    ///  Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    ///  Required method for Designer support - do not modify
    ///  the contents of this method with the code editor.
    /// </summary>
    private Button button1;
    private TextBox textBox1;
    private TextBox result1;
    private Panel panel1;
    private void InitializeComponent()
    {
        this.components = new System.ComponentModel.Container();
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(800, 450);
        this.Text = "Form1";

        // 버튼 컨트롤 생성 및 설정
        this.button1 = new System.Windows.Forms.Button();
        this.button1.Location = new System.Drawing.Point(10, 10);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(70, 50);
        this.button1.Text = "Click Me!";
        this.button1.UseVisualStyleBackColor = true;
        // this.button1.Click += new System.EventHandler(this.button1_Click);  // 클릭 이벤트 핸들러 등록

        // 버튼을 폼에 추가
        this.Controls.Add(this.button1);

        // 텍스트 박스 생성 및 추가
        this.textBox1 = new System.Windows.Forms.TextBox();
        this.textBox1.Location = new System.Drawing.Point(10, 70);
        this.textBox1.Name = "textBox1";
        this.textBox1.Size = new System.Drawing.Size(500, 500);
        this.textBox1.Text = "";
        // this.textBox1.UseVisualStyleBackColor = true;
        this.Controls.Add(this.textBox1);

        // 패널 생성 및 추가  
        this.panel1 = new System.Windows.Forms.Panel();   
        this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right | System.Windows.Forms.AnchorStyles.Bottom)));
        this.panel1.Location = new System.Drawing.Point(10, 100);   
        this.panel1.Name = "panel1";        
        this.panel1.Size = new System.Drawing.Size(1090,330);
        // this.panel1.TabIndex = 0;
        // this.panel1.Width = this.ClientSize.Width;
        // this.panel1.Height = this.ClientSize.Width;
        // this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
        this.panel1.AutoScroll = true;
        this.panel1.BackColor = Color.DimGray;
        
        this.Controls.Add(this.panel1);

        // 텍스트 박스 생성 및 추가
        this.result1 = new System.Windows.Forms.TextBox();
        this.result1.Name = "resul1";
        this.result1.Multiline = true;    
        this.result1.Location = new System.Drawing.Point(0, 0);     
        this.result1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
        this.result1.Dock = System.Windows.Forms.DockStyle.Fill; // Dock 속성 설정
        this.result1.Text = "";

        // 텍스트 박스의 크기와 위치를 조정               
        // this.result1.Width = this.panel1.ClientRectangle.Width;
        // this.result1.Height = this.panel1.ClientRectangle.Height;        
        this.panel1.Controls.Add(this.result1);
    }

    #endregion
}

 

반응형
반응형

재귀적으로 처리하는 여러 예제가 있는데, AI한테 물어보니 간단한 방법을 알려줬다.

 

import org.apache.commons.io.FileUtils;

public class CopyDirectoryCommonsIO {
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("C:/source/folder");
        Path target = Paths.get("C:/target/folder");

        FileUtils.copyDirectory(source.toFile(), target.toFile());
        System.out.println("폴더 복사 완료");
    }
}

 

 

반응형
반응형

 

특정 API에서 파일 다운로드를 제공할 때, 이를 받아들이는 Javascript 예제.

파일명은 API의 Header에 'Content-Disposition' 값에서 가져온다.

 

fetch('/api/download')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }

    // Content-Disposition 헤더에서 파일명 추출
    const contentDisposition = response.headers.get('Content-Disposition');
    const filenameRegex = /filename="?([^"]+)"?/i;
    const matches = filenameRegex.exec(contentDisposition);
    const filename = matches && matches[1];

    return response.blob();
  })
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('   
download', filename || 'download.bin'); // 파일명이 없을 경우 기본값 설정
    document.body.appendChild(link);
    link.click();
    window.URL.revokeObjectURL(url);
  })
  .catch(error => {
    console.error('Error:', error);
  });
반응형
반응형

 

@PreAuthorize()는 메서드를 실행하기 전에 하는 권한 검사

@PostAuthorize()는 메서드를 실행하고 클라이언트에게 응답을 하기 직전에 하는 권한 검사

 

<예시>

@PreAuthorize("isAuthenticated()")

 

인자로 올 수 있는 값...

  • hasRole([role]) : 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true
@PreAuthorize("hasRole('ADMIN') or hasRole('MANAGER')")

 

  • hasAnyRole([role1,role2]) : 현재 사용자의 권한이 파라미터의 권한 중 일치하는 것이 있는 경우 true
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")

 

  • principal : 사용자를 증명하는 주요객체(User)를 직접 접근할 수 있다.
  • authentication : SecurityContext에 있는 authentication 객체에 접근 할 수 있다.
@PreAuthorize("#userId == authentication.principal.id")
or
@PreAuthorize("#username == authentication.name")

 

  • permitAll() : 모든 접근 허용
  • denyAll() : 모든 접근 비허용
  • isAnonymous() : 현재 사용자가 익명(비로그인)인 상태인 경우 true
  • isRememberMe() : 현재 사용자가 RememberMe 사용자라면 true
  • isAuthenticated() : 현재 사용자가 익명이 아니라면 (로그인 상태라면) true
  • isFullyAuthenticated() : 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true
@PreAuthorize("isAuthenticated()")
@PreAuthorize("isAnonymous()")

 

 

반응형
반응형

평생 공짜로 쓸 수 있다고 해서 Oracle Freetier에 장비 하나를 구성해두었습니다. 

한참 잊고 지내고 있었는데, 그때 돌려놓았던 node 서버가 여전히 잘 돌아가고 있더군요.

 

이번엔 Oracle Freetier서버에 ExpressJS로 백엔드 서비스를 구성하고,

Netlify에 Jquery로 통신이 가능한 웹프로그램을 구성해 보았습니다.

 

1. Oracle Freetier

Cloud Free Tier | Oracle 대한민국

 

클라우드 서비스 무료 이용

Oracle Cloud Free Tier는 기업에게 무제한으로 사용할 수 있는 상시 무료 클라우드 서비스를 제공합니다.

www.oracle.com

 

[src/index.js]

간단하게 'Hello World'만 return하는 서버를 만들어 두었습니다. Github에서 해당 서버로 자동배포하는 것까지 구성은 못해서 FTP로 일일이 옮겨넣었습니다.

var express = require('express');
var app = express();
var user = require('./routes/user');

const cors = require('cors');
app.use(cors());

app.get('/', function (req, res) {	// 기본 root('/') 는 main.js에서 routing
    res.send('Hello World');
});

//app.use('/user', user);         	// 나머지 접근은 router(/routes/user)에서 routing


app.listen(3000, function () {   	// 3000 포트로 서버 실행
    console.log('Example App is listening on port 3000');
});

소스코드에 보시면, 다른 서버로부터의 요청이 허용되도록 app.use(cors())를 선언해줘야합니다.

위치는 라우팅보다 앞에 선언되어야 합니다.

 

2. Netlify

Scale & Ship Faster with a Composable Web Architecture | Netlify

 

Scale & Ship Faster with a Composable Web Architecture | Netlify

Realize the speed, agility and performance of a scalable, composable web architecture with Netlify. Explore the composable web platform now!

www.netlify.com

 

Netlify는 Github이나 Gitlab과 연동이 잘 되어 별도의 구성 없이도 CI/CD가 쉽게 가능합니다. 그래서 다른 Front Framework 사용하지 않고 html하고 jquery만으로 데이터를 받아오는 연습을 해봤습니다.

 

[index.html]

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./js/jquery.min.js"></script>
</head>
<body>
 <div id="headline">Hello!!</div>
 <button onclick="ajax_request()">Ajax Request</button>
<script>
  function ajax_request(){
      $.ajax({
        url:'/api/', //'http://1xx.6xx.2xx.2xx:3000/', <- netlify에서 https요청으로 보내면, netlify.toml의 proxy설정을 통해 해당 서버로 요청됨
        method:'GET',
        async:false,
        success: function(data){
          console.log(data);
          document.getElementById('headline').innerText =data;
        }
      })
  }
 
</script>  
</body>
</html>

 

[netlify.toml]

[[redirects]]
  from = "/api/*"
  to = "http://1xx.6xx.2xx.2xx:3000/:splat"
  status = 200
  force = true

 

oracle 서버에서 cors 허용을 해놓았는데도 원래의 주소로 request를 보내면 오류가 납니다. 이유는 oracle에서는 http로 서비스를 하고 있고, netlify에서는 https로 서비스를 하고 있어서 그렇습니다. oracle에도 https서비스를 위한 인증서를 구해서 넣으면 좋지만, 위와 같이 proxy 우회 방법으로 간단히 두 서버간 통신이 가능합니다.

 

netlify에서는 ajax요청할 때, '/api/' 로 요청을 보내고,

netlify proxy설정을 통해 원래 서버로 전달됩니다.

 

 

그럼 이만~!

반응형

'Programming > ExpressJS' 카테고리의 다른 글

Node강좌 - 미들웨어  (0) 2020.08.09
Node강좌 - express 설치 / router설정  (0) 2020.08.09
반응형

 

위와 같이 프로젝트가 구성되있다고 할 때, pom.xml에 mybatis와 database driver를 설치해줍니다.

 

1. pom.xml (Maven기준)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>abc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>javafx03_mybatis</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.10.0</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>17.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>17.0.6</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>12.4.2.jre11</version>
            <scope>runtime</scope>
        </dependency>
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>org.example.abc.HelloApplication</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

 

2. module-info.java

스프링처럼 사용하려면 module-info.java 파일이 필요함. 아래의 추가부분을 설정해준다. (이 부분을 몰라서 한참 헤멤)

module org.example.abc {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.sql;
    requires org.mybatis;  // <-- Add this

    opens org.example.abc to javafx.fxml;
    exports org.example.abc;
    exports org.example.abc.mapper;
    opens org.example.abc.mapper to javafx.fxml;
    exports org.example.abc.dto;
    opens org.example.abc.dto to javafx.fxml;
    opens mapper; // <-- 이거 추가해야 mapper 인식
}

 

3. MyBatisUtil.java

 

package org.example.abc;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }
}

 

4. mybatis-config.xml

특별한 건 없습니다. DB 접속정보를 적어주고, Mapper의 위치를 적어줍니다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
                <property name="url" value="jdbc:sqlserver://<databaseURL>:<port_number>;databaseName=<DB>;encrypt=true;trustServerCertificate=true;"/>
                <property name="username" value="<username>"/>
                <property name="password" value="<password>"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/<매퍼이름>.xml"/>
    </mappers>
</configuration>

 

Mapper.xml 상세와 DTO, UI상세 등은 Spring과 동일함.

반응형

+ Recent posts