javaFX maven 프로젝트를 빌드하면 Target 폴더에 jar파일이 생성되게 된다. 동일한 폴더에 jre와 javafx-sdk를 복사해 넣으면 작동 환경은 구성된 것이다.
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><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.example.javafx05.HelloApplication</mainClass></transformer></transformers></configuration></execution></executions></plugin></plugins>
실행은 우측의 Maven아이콘 클릭 후, 수명주기 -> package를 더블 클릭하면 패키징이 되면서 jar파일이 생성된다.
C# WPF로 작업하던 프로그램이 오류를 뱉는데, 디버깅이 안되어 Java로 전부 바꾸는 실험을 했다. WPF는 Visual Studio Code에서 만든거라, 디버깅이 좀 힘들었다. 반면에 Java는 무료이면서도 강력한 IntelliJ가 있다. 물론 이클립스도 있다.
이번엔 IntelliJ로 프로젝트를 만들었는데, 사실 그것도 처음이라 좀 많이 힘들었다.
이번 프로젝트는 출입 통제하는 프로그램이다. 자료실에 드나드는 사람에게 ID 태그를 읽혀서 들어올땐 "안녕하세요", 나갈땐 "안녕히가세요"를 출력해주고, 다른 탭에서 전체 출입인원 목록을 확인하면 된다.
프로젝트 구성은 아래와 같다.
Spring Boot를 해본 사람이라면 좀 익숙할 수도 있겠다. 그것과 비슷한 구조로 만들려고 신경을 써봤다. 각 파일들의 코드를 기록으로 남기고자 한다. 참고하려는 분들은 아래의 프로젝트 구성도를 계속 확인하면서 보면 좋겠다.
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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로 만들어보는건 처음이라..
시작점이 되는 파일이다. 코드 아래쪽을 보면 SerialService라는 서비스를 불러내는데, controller를 인자로 넘겨준다. controller는 화면 구성요소와 그에 대한 controll을 하는 부분이다. 이 controller를 넘겨줘야 Serial 데이터의 입력에 맞춰 화면에 정보를 뿌릴 수 있다.
위의 HelloApplication에서 시작하여 Serial데이터를 받기 시작하는 서비스이다. 데이터를 너무 빨리 처리하면 Serial데이터가 두번에 나눠져 들어온다. 그래서 초기에 약간의 대기시간(50ms)을 두었으며, 그래도 모를 예외를 대비해서 12자리를 모두 채우면 데이터를 처리하도록 만들었다.
데이터(message)가 취득되면 emp_check이라는 직원 체크 함수로 간다. 이 함수에서 MyBatis를 통한 DB데이터를 가져오고, 최종 만들어진 데이터 및 인사말은
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.javafx05.emp.EmpMapper"><selectid="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><selectid="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><selectid="history_in_log">
insert into BEMP_DATAROOM_HSTRY (emp_#, in_d, out_d, purpose, location)
values (#{emp_no}, sysdate,NULL,#{purpose},#{location})
</select><selectid="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){
}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.javafx05.history.HistoryMapper"><selectid="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_#"
<iftest="emp_name!=null and emp_name!=''">
and b.kornm_n like '%'||#{emp_name}||'%'
</if><iftest="purpose!=null and purpose!=''">
and a.purpose like '%'||#{purpose}||'%'
</if><iftest="location!=null and location!=''">
and a.location like '%'||#{location}||'%'
</if><iftest="from!=null and from!=''">
and a.in_d > to_date(#{from},'YYYY-MM-DD')
</if><iftest="to!=null and to!=''">
and a.in_d < to_date(#{to},'YYYY-MM-DD')
</if>
order by a.in_d desc
</select></mapper>
<?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="org.example.mapper.dbMapper">
<select id="selectAll" resultType="org.example.model.dbModel">
select emp_# as emp_no,
emp_x,
kornm_n,
hannm_n,
engnm_n,
res_#1 as res
from temp a
where a.kornm_n='김동개'
</select>
</mapper>
8. org.example.view > MainView.java 생성
package org.example.view;
import org.example.dto.dbDto;
import org.example.service.DbService;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
publicclassMainViewextendsJFrame{
DbService dbService = new DbService();
JFrame f1 =new JFrame(); // Main Frame
JMenuBar mb = new JMenuBar(); // 메뉴바
JPanel sidePanel = new JPanel(); // 사이드 패널
JPanel contentPanel = new JPanel(); // 컨텐츠 패널
JPanel footerPanel = new JPanel(); // 푸터 패널
JButton jb1 = new JButton("search"); // 버튼 초기화
JButton jb2 = new JButton("insert"); // 버튼 초기화
JButton jb3 = new JButton("delete"); // 버튼 초기화// JTable data_table = new JTable(); //테이블 생성시에는 초기값과 헤더를 넣어줘야한다. // 이 작업은 초기 화면 생성하는 함수에 넣어주기 위해, 아래와 같이 null로 우선 생성한다.
JTable data_table = null;
publicvoidset_style(Component target){
// 스타일 적용(Look & Feel)try{
UIManager.setLookAndFeel ("com.birosoft.liquid.LiquidLookAndFeel"); //Liquid
}catch(Exception e){
System.out.println(e + "오류 발생");
}
SwingUtilities.updateComponentTreeUI(target) ;
}
publicvoidcreateFrame(){
// Main Frame 세팅
f1.setSize(1024,760);//크기
f1.setDefaultCloseOperation(f1.EXIT_ON_CLOSE);
f1.setLocationRelativeTo(null);
// 스타일 적용
f1.setDefaultLookAndFeelDecorated(true);
set_style(f1);
// 아이콘 적용
Image icon = Toolkit.getDefaultToolkit().getImage("D:\\7_System_dev2\\4_Java\\01_gui\\src\\icon.png");
f1.setIconImage(icon);
// 레이아웃 적용
BorderLayout bl = new BorderLayout();
f1.setLayout(bl);
// 화면 요소 생성 및 추가
createMenu();
createSidePanel();
createContentPanel();
createFooter();
f1.add(mb, BorderLayout.NORTH);
f1.add(sidePanel, BorderLayout.WEST); // f1라는 프레임에 sidePanel추가
f1.add(contentPanel, BorderLayout.CENTER); // f1라는 프레임에 contentPanel추가
f1.add(footerPanel, BorderLayout.SOUTH); // f1라는 프레임에 FooterPanel추가
f1.setTitle("Frame Test");//제목
f1.setVisible(true);//생성
}
// 메뉴바publicvoidcreateMenu(){
JMenu fileMenu = new JMenu("File");
fileMenu.add(new JMenuItem("New"));
fileMenu.add(new JMenuItem("Open"));
fileMenu.add(new JMenuItem("Preferences"));
mb.add(fileMenu);
mb.add(new JMenu("Edit"));
mb.add(new JMenu("About"));
mb.add(new JMenu("Help"));
setJMenuBar(mb);
}
// 사이드 패널publicvoidcreateSidePanel(){
sidePanel.setPreferredSize(new Dimension(100, 300)); // 사이드패널 사이즈 조절
sidePanel.setBorder(BorderFactory.createEmptyBorder(15 , 10, 10 , 10));
// sidePanel.setLayout(new BoxLayout(sidePanel, BoxLayout.Y_AXIS));
sidePanel.add(jb1);
sidePanel.add(jb2);
sidePanel.add(jb3);
set_style(sidePanel);
jb1.addActionListener(new ActionListener() {
@OverridepublicvoidactionPerformed(ActionEvent e){
data_mapping();
// 패널 갱신
contentPanel.revalidate();
contentPanel.repaint();
}
});
}
// Contents 패널publicvoidcreateContentPanel(){
contentPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
JLabel Headline = new JLabel("Swing Table Data with Styles");
Font f = new Font("고딕", Font.BOLD,20);
Headline.setFont(f);
contentPanel.add(Headline);
// 테이블 준비
String[] header = {"EMP No", "Name"};
String[][] contents = {{"",""},{"",""},{"",""}};
data_table = new JTable(contents, header);
Font font = new Font("고딕", Font.PLAIN,12);
data_table.setFont(font);
contentPanel.add(new JScrollPane(data_table), BorderLayout.CENTER);
// JScrollPane(data_table)에 넣어주지 않으면 header가 나타나지 않는다.
set_style(contentPanel);
}
// FooterpublicvoidcreateFooter(){
footerPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
footerPanel.setBackground(Color.orange);
JLabel Footer = new JLabel("Copyright by Wilkyway");
footerPanel.add(Footer);
// set_style(footerPanel);
}
publicvoiddata_mapping(){
List<dbDto> models = dbService.selectAll();
int i=0;
for (dbDto model : models) {
System.out.println("ID: " + model.emp_no() + ", Name: " + model.kornm_n());
data_table.setValueAt(model.emp_no(),i,0);
data_table.setValueAt(model.kornm_n(),i,1);
i++;
}
}
}
인텔리제이(Intellij) IDE 를 사용하여 간단한 텍스트 에디터를 만들어보도록 하겠습니다.
1. 새 프로젝트 생성
인텔리제이 메뉴에서 File - New - Project 를 클릭하여 새 프로젝트를 생성해줍니다. Name은 editor로 하고, 적당한 위치에 아래와 같은 세팅으로 진행할 예정입니다. JDK는 설치되어있지 않다면 JDK의 드롭다운 메뉴 중 Download JDK를 눌러, 적당한 버전을 고른 후 다운로드 받아줍니다. 혹은 직접 원하는 JDK를 다운로드 받아서 선택할 수도 있습니다.
2. Main Class 생성
프로젝트 하위 폴더 중 src에서 우클릭하여 New - Java Class 를 클릭한 후, 클래스 이름을 editor로 하여 새로운 클래스를 생성해줍니다.
linux를 처음 알고 시작했을 당시만해도 xmms라는 콘솔에서 실행하는 프로그램을 설치하고 음악을 듣곤 했었는데, 거기서부터 파생된 gui형태의 프로그램들이 많이 나온것 같습니다. xmms2도 있었고, audacious라는 프로그램을 최근까지도 메인으로 사용하고 있었는데, 얼마전 qmmp라는 또다른 프로그램을 알게 되었습니다. 이름에서 알 수 있듯이 QT기반의 프로그램 이라고 합니다. 어떤 녀석인지 한 번 설치해 보도록 하겠습니다.
현재 제 데스크탑에는 ubuntu 20.04 lts가 설치되어 있습니다. 그래서 ubuntu package manager로 가서 qmmp를 찾아봅니다. 2개가 나오는데 어떤걸까요? 아래쪽이 1.5 버전이라고 되어있어서 아래쪽 프로그램을 설치하겠습니다.
설치하고 나니 보이긴 하는데, 아이콘이 안나오네요...ㅠㅠ
프로그램 실행은 문제가 없어 보입니다.
아이콘이 안나오는게 찜찜하네요. 아까 설치하지 않은 1.3버전을 설치해보도록 하겠습니다. 혹시 모르니 qmmp 사이트에서 repository를 추가한 후 업데이트를 합니다.