7주차 - javaFX (7) DTO, DAO, Service, Controller, Main 으로 역할 나누기

2022. 9. 8. 17:00Java/javaFX & Scene Builder

지난 javaFX (6) 까지는 메인클래스, 컨트롤러클래스 두가지로 구분지어 코드를 작성하는 것까지 해보았습니다.

 

이번글의 목표

 

1) DAO, DTO, Service, Contoller, Main 으로 기능별로 클래스를 구분지어 만들어보자.

2) 로그인 화면, 회원가입 화면, 유저 화면 을 만들고 기능을 추가해보자.

3) Opener 클래스를 만들어서 화면을 열어주는 메서드를 작성해보자.

4) CommonService 클래스를 만들어서 모든 클래스에서 공통적으로 사용될 메서드를 

 

이번에 만든 클래스는 아래와 같습니다.

 

01.CommonService.java : 모든 클래스에서 공통적으로 사용할 메서드를 모아둔 클래스
02.Opener : 화면 전환해주는 클래스
03.LoginContoller : 로그인 화면에서 사용될 메서드를 모아두는 컨트롤러 클래스
04.LoginDAO : 데이터베이스에 연결되는 메서드를 모아둔 클래스
05.LoginDTO : getter, setter 를 모아둔 클래스
06.LoginService : 로그인시 데이터를 검증하는 클래스
07.Main : 로그인 화면을 시작하는 클래스
08.RegController : 회원가입화면의 컨트롤러 메서드
09.RegDAO : 데이터베이스에 연결되는 클래스
10.RegDTO : getter, setter 모아둔 클래스
11.RegService : 회원가입시 데이터를 검증하는 클래스
12.LoginForm : 로그인 화면의 fxml
13.Menu : 유저 화면의 fxml
14.regForm : 회원 가입 화면의 fxml

 


01. CommonService.java : 모든 클래스에서 공통적으로 사용할 메서드를 모아둔 클래스

public class CommonService {

	public static void msg(String content) {
		Alert alert = new Alert(AlertType.INFORMATION);
		alert.setContentText(content);
		alert.show();
	}
	
	public static void windowsClose(Parent form) {
		Stage stage = (Stage)form.getScene().getWindow();
		stage.close();
	}
}

 


02. Opener : 화면 전환해주는 클래스

public class Opener {
	private Stage primaryStage;

	public void setPrimaryStage(Stage primaryStage) {
		this.primaryStage = primaryStage;
	}

	public void regOpen() {
		FXMLLoader loader = new FXMLLoader(getClass().getResource("regForm.fxml"));
		try {
			Parent regForm = loader.load();

			RegController regCon = loader.getController();
			regCon.setRegForm(regForm);

			Scene scene = new Scene(regForm);
			Stage regStage = new Stage();

			ComboBox<String> ageBox = (ComboBox<String>) regForm.lookup("#ageCombo");
			ageBox.getItems().addAll("10대", "20대", "30대", "40대");

			regStage.setTitle("회원 가입 화면");
			regStage.setScene(scene);
			regStage.show();
		} catch (Exception e) {
			CommonService.msg("회원 가입 화면에 문제가 발생했습니다. 관리자에게 문의하세요.");
			e.printStackTrace();
		}
	}

	public void menuOpen() {
		FXMLLoader loader = new FXMLLoader(getClass().getResource("Menu.fxml"));
		try {
			Parent menuForm = loader.load();

			Scene scene = new Scene(menuForm);
			primaryStage.setTitle("메인 메뉴 화면");
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e) {
			CommonService.msg("메인 메뉴 화면에 문제가 발생했습니다. 관리자에게 문의하세요.");
			e.printStackTrace();
		}
	}
}

03. LoginContoller : 로그인 화면에서 사용될 메서드를 모아두는 컨트롤러 클래스

public class LoginController implements Initializable{
	@FXML private TextField id;
	@FXML private PasswordField pw;
	private LoginService loginService;
	private Opener opener;
	
	public void setOpener(Opener opener) {
		this.opener = opener;
	}
	
	@Override
	public void initialize(URL location, ResourceBundle resources) {
		loginService = new LoginService();
	}
	
	// 로그인 버튼 클릭 시 동작하는 메서드
	public void loginProc() {
		loginService.loginProc(id, pw);
		
		// 로그인 성공 시
		String result = loginService.getLogin(id.getText());
		if("y".equals(result)) {
			loginService.disconnection();
			opener.menuOpen();
		}
	}
	
	// 취소 버튼 클릭 시 동작하는 메서드
	public void cancelProc() {
		id.clear(); pw.clear();
		id.requestFocus();
	}
	
	// 회원 가입 버튼 클릭 시 동작하는 메서드
	public void regProc() {
//		System.out.println("회원 가입 화면 열기");
		opener.regOpen();
	}
}

04. LoginDAO : 데이터베이스에 연결되는 메서드를 모아둔 클래스

public class LoginDAO {
	private Connection con;
	private PreparedStatement ps;
	private ResultSet rs;
	
	public LoginDAO() {
		String url = "jdbc:oracle:thin:@localhost:1521:xe";
		String user = "oracle";
		String password = "oracle";
		
		try {
			Class.forName("oracle.jdbc.OracleDriver");
			con = DriverManager.getConnection(url, user, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public LoginDTO selectId(String id) {
		String sql = "SELECT * FROM fx_tmp WHERE id=?";
		
		try {
			ps = con.prepareStatement(sql);
			ps.setString(1, id);
			rs = ps.executeQuery();
			if(rs.next()) {
				LoginDTO login = new LoginDTO();
				login.setId(rs.getString("id"));
				login.setPw(rs.getString("pw"));
				login.setLogin(rs.getString("login"));
				return login;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public void setLogin(String id) {
		String sql = "UPDATE fx_tmp SET login='y' WHERE id=?";
		
		try {
			ps = con.prepareStatement(sql);
			ps.setString(1, id);
			ps.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public void disconnection() {
		try {
			if(rs != null) rs.close();
			if(ps != null) ps.close();
			if(con != null) con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public String getLogin(String id) {
	String sql = "SELECT login FROM fx_tmp WHERE id=?";
		
		try {
			ps = con.prepareStatement(sql);
			ps.setString(1, id);
			rs = ps.executeQuery();
			if(rs.next()) {
				return rs.getString("login");
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return "n";
	}
}

05.LoginDTO : getter, setter 를 모아둔 클래스

/*
  테이클 생성하는 쿼리 구문
SQL> CREATE TABLE fx_tmp(
  2  id varchar2(20),
  3  pw varchar2(20)
  4  );

SQL> INSERT INTO fx_tmp VALUES('admin', '1234');
SQL> INSERT INTO fx_tmp VALUES('user1', '1111');

SQL> ALTER TABLE fx_tmp ADD login varchar2(5);
SQL> UPDATE fx_tmp SET login='n';

SQL> SELECT * FROM fx_tmp;
SQL> commit;
 */
public class LoginDTO {
	private String id;
	private String pw;
	private String login;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
	public String getLogin() {
		return login;
	}
	public void setLogin(String login) {
		this.login = login;
	}
}

06.LoginService : 로그인시 데이터를 검증하는 클래스

 

public class LoginService {
	private LoginDAO loginDao;
	
	public LoginService() {
		loginDao = new LoginDAO();
	}
	
	public String getLogin(String id) {
		return loginDao.getLogin(id); // fx_tmp - login - value
	}
	public void disconnection() {
		loginDao.disconnection();
	}
	public void loginProc(TextField id, PasswordField pw) {
		// 빈 데이터
		if(id.getText().isEmpty() || pw.getText().isEmpty()) {
			CommonService.msg("아이디 또는 비밀번호를 입력하세요.");
			return;
		}
		
		// 테이블 데이터를 조회. 데이터(아이디, 비밀번호, 로그인여부)
		LoginDTO loginDto = loginDao.selectId(id.getText());
		if(loginDto == null) {
			CommonService.msg("아이디 또는 비밀번호를 확인 후 다시 입력하세요.");
			return;
		}
		
		// 로그인 여부 확인
		if(loginDto.getLogin().equals("y")) {
			CommonService.msg("이미 로그인된 계정입니다.");
			return;
		}
		
		// 비밀번호 비교
		if(loginDto.getPw().equals(pw.getText()) == false) {
			CommonService.msg("아이디 또는 비밀번호를 확인 후 다시 입력하세요.");
			return;
		}
		
		// 로그인 성공
		loginDao.setLogin(id.getText());
	}

}

07.Main : 로그인 화면을 시작하는 클래스

public class Main extends Application {
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		FXMLLoader loader = new FXMLLoader(getClass().getResource("loginForm.fxml"));
		
		Opener opener = new Opener();
		opener.setPrimaryStage(primaryStage);
		
		Parent loginForm = loader.load();
		LoginController loginCon = loader.getController();
		loginCon.setOpener(opener);
		
		Scene scene = new Scene(loginForm);
		primaryStage.setTitle("로그인 화면");
		primaryStage.setScene(scene);
		primaryStage.show();

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

08.RegController : 회원가입화면의 컨트롤러 메서드

public class RegController implements Initializable{
	private Parent regForm;
	private RegService regService;
	
	public void setRegForm(Parent regForm) {
		this.regForm = regForm;
	}
	
	@Override
	public void initialize(URL location, ResourceBundle resources) {
		regService = new RegService();
	}
	
	// 회원 가입 화면에서 회원가입 버튼 클릭 시 호출됨.
	public void regProc() {
		regService.regProc(regForm);
	}
	
	//	회원 가입 화면에서 취소 버튼 클릭 시 호출됨.
	public void regCancelProc() {
		CommonService.windowsClose(regForm);
	}
}

09.RegDAO : 데이터베이스에 연결되는 클래스

public class RegDAO {
	private Connection con;
	private PreparedStatement ps;
	private ResultSet rs;
	public RegDAO() {
		String url = "jdbc:oracle:thin:@localhost:1521:xe";
		String user = "oracle";
		String password = "oracle";
		
		try {
			Class.forName("oracle.jdbc.OracleDriver");
			con = DriverManager.getConnection(url, user, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void insert(RegDTO reg) {
		String sql = "INSERT INTO fx_tmp VALUES(?,?,?,?,?,?,?)";
		try {
			ps = con.prepareStatement(sql);
			ps.setString(1, reg.getId());
			ps.setString(2, reg.getPw());
			ps.setString(3, "n");
			ps.setString(4, reg.getName());
			ps.setString(5, reg.getGender());
			ps.setString(6, reg.getAge());
			ps.setString(7, reg.getHobbys());
			ps.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void disconnection() {
		try {
			if(rs != null) rs.close();
			if(ps != null) ps.close();
			if(con != null) con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

10.RegDTO : getter, setter 모아둔 클래스

/*
SQL> ALTER TABLE fx_tmp ADD name varchar2(20);
SQL> ALTER TABLE fx_tmp ADD gender varchar2(5);
SQL> ALTER TABLE fx_tmp ADD age varchar2(10);
SQL> ALTER TABLE fx_tmp ADD hobbys varchar2(30);
SQL> commit;
 */
public class RegDTO extends LoginDTO{
	private String name;
	private String gender;
	private String age;
	private String hobbys;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getAge() {
		return age;
	}
	public void setAge(String age) {
		this.age = age;
	}
	public String getHobbys() {
		return hobbys;
	}
	public void setHobbys(String hobbys) {
		this.hobbys = hobbys;
	}
}

11.RegService : 회원가입시 데이터를 검증하는 클래스

public class RegService {

	public void regProc(Parent regForm) {
		TextField name = (TextField)regForm.lookup("#name");
		TextField id = (TextField)regForm.lookup("#id");
		
		PasswordField pw = (PasswordField)regForm.lookup("#pw");
		PasswordField confirm = (PasswordField)regForm.lookup("#confirm");
		
		RadioButton manRadio = (RadioButton)regForm.lookup("#manRadio");
		RadioButton womanRadio = (RadioButton)regForm.lookup("#womanRadio");
		
		ComboBox<String> ageCombo = (ComboBox<String>)regForm.lookup("#ageCombo");
		
		CheckBox musicCheck = (CheckBox)regForm.lookup("#musicCheck");
		CheckBox movieCheck = (CheckBox)regForm.lookup("#movieCheck");
		CheckBox sportCheck = (CheckBox)regForm.lookup("#sportCheck");
		
		RegDTO reg = new RegDTO();
		reg.setId(id.getText());
		reg.setName(name.getText());
		reg.setPw(pw.getText());
		reg.setAge(ageCombo.getValue());
		
		if(manRadio.isSelected())
			reg.setGender("남");
		else if(womanRadio.isSelected())
			reg.setGender("여");
		
		String tmp = "";
		if(musicCheck.isSelected())	tmp += "음악";
		if(movieCheck.isSelected())	tmp += " 영화";
		if(sportCheck.isSelected())	tmp += " 스포츠";
		
		reg.setHobbys(tmp);
		
		// 정보 출력
//		System.out.println("아이디 : " + reg.getId());
//		System.out.println("이름 : " + reg.getName());
//		System.out.println("비밀번호 : " + reg.getPw());
//		System.out.println("성별 : " + reg.getGender());
//		System.out.println("연령구분 : " + reg.getAge());
//		System.out.println("좋아하는 것 : " + reg.getHobbys());
		
		// 아이디 중복 검증
		LoginDAO loginDao = new LoginDAO();
		LoginDTO loginDto = loginDao.selectId(reg.getId());
		loginDao.disconnection();
		if(loginDto != null) {
			CommonService.msg("아이디가 사용 중 입니다.");
			return;
		}
		
		if(pw.getText().equals(confirm.getText()) == false){
			CommonService.msg("입력한 비밀번호가 다릅니다.");
			return;
		}
		
		// 회원 가입
		RegDAO regDao = new RegDAO();
		regDao.insert(reg);
		
		regDao.disconnection();
		CommonService.windowsClose(regForm);
	}
}

12.LoginForm : 로그인 화면의 fxml

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="400.0" spacing="15.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="ex4.LoginController">
   <children>
      <HBox alignment="BOTTOM_CENTER" prefHeight="100.0" prefWidth="200.0" spacing="20.0">
         <children>
            <Label prefWidth="50.0" text="아이디" />
            <TextField fx:id="id" />
         </children>
      </HBox>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="20.0">
         <children>
            <Label prefWidth="50.0" text="비밀번호" />
            <PasswordField fx:id="pw" />
         </children>
      </HBox>
      <HBox alignment="TOP_CENTER" prefHeight="100.0" prefWidth="200.0" spacing="15.0">
         <children>
            <Button mnemonicParsing="false" onAction="#loginProc" prefHeight="30.0" prefWidth="70.0" text="로그인" />
            <Button mnemonicParsing="false" onAction="#cancelProc" prefHeight="30.0" prefWidth="70.0" text="취소" />
            <Button mnemonicParsing="false" onAction="#regProc" prefHeight="30.0" prefWidth="70.0" text="회원가입" />
         </children>
      </HBox>
   </children>
</VBox>

13.Menu : 유저 화면의 fxml

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>

<AnchorPane prefHeight="600.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Button fx:id="exitButton" mnemonicParsing="false" prefHeight="40.0" prefWidth="70.0" text="종료" AnchorPane.leftAnchor="900.0" AnchorPane.topAnchor="30.0">
         <font>
            <Font size="18.0" />
         </font>
      </Button>
      <Label layoutX="359.0" layoutY="100.0" text="xx 관리 프로그램" AnchorPane.leftAnchor="359.0" AnchorPane.topAnchor="100.0">
         <font>
            <Font size="36.0" />
         </font>
      </Label>
      <HBox alignment="CENTER" prefHeight="200.0" prefWidth="1000.0" spacing="50.0" AnchorPane.topAnchor="200.0">
         <children>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="영업">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="매출">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="품목등록">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="재고관리">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="회원관리">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="100.0" text="직원관리">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Button>
         </children>
      </HBox>
   </children>
</AnchorPane>

14.regForm : 회원 가입 화면의 fxml

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="ex4.RegController">
   <top>
      <AnchorPane prefHeight="80.0" prefWidth="600.0" BorderPane.alignment="CENTER">
         <children>
            <Label layoutX="230.0" layoutY="20.0" text="회원 가입">
               <font>
                  <Font size="36.0" />
               </font>
            </Label>
         </children>
      </AnchorPane>
   </top>
   <center>
      <VBox prefHeight="208.0" prefWidth="600.0" BorderPane.alignment="CENTER">
         <children>
            <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" spacing="40.0">
               <children>
                  <Label text="이      름">
                     <HBox.margin>
                        <Insets left="40.0" />
                     </HBox.margin>
                  </Label>
                  <TextField fx:id="name" />
                  <Label text="아     이     디" />
                  <TextField fx:id="id">
                     <HBox.margin>
                        <Insets left="5.0" />
                     </HBox.margin>
                  </TextField>
               </children>
            </HBox>
            <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" spacing="40.0">
               <children>
                  <Label text="패스워드">
                     <HBox.margin>
                        <Insets left="40.0" />
                     </HBox.margin>
                  </Label>
                  <PasswordField fx:id="pw" />
                  <Label text="패스워드 확인" />
                  <PasswordField fx:id="confirm" />
               </children>
            </HBox>
            <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" spacing="25.0">
               <children>
                  <Label text="성     별">
                     <HBox.margin>
                        <Insets left="40.0" />
                     </HBox.margin>
                  </Label>
                  <RadioButton fx:id="manRadio" mnemonicParsing="false" text="남">
                     <HBox.margin>
                        <Insets left="20.0" />
                     </HBox.margin>
                     <toggleGroup>
                        <ToggleGroup fx:id="gender" />
                     </toggleGroup>
                  </RadioButton>
                  <RadioButton fx:id="womanRadio" mnemonicParsing="false" text="여" toggleGroup="$gender" />
                  <Label text="연령   구분">
                     <HBox.margin>
                        <Insets left="65.0" />
                     </HBox.margin>
                  </Label>
                  <ComboBox fx:id="ageCombo" prefWidth="150.0" promptText="연령대 선택">
                     <HBox.margin>
                        <Insets left="25.0" />
                     </HBox.margin>
                  </ComboBox>
               </children>
               <VBox.margin>
                  <Insets />
               </VBox.margin>
            </HBox>
            <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" spacing="25.0">
               <children>
                  <Label text="좋아하는 것">
                     <HBox.margin>
                        <Insets left="40.0" />
                     </HBox.margin>
                  </Label>
                  <CheckBox fx:id="musicCheck" mnemonicParsing="false" text="음악" />
                  <CheckBox fx:id="sportCheck" mnemonicParsing="false" text="스포츠" />
                  <CheckBox fx:id="movieCheck" mnemonicParsing="false" text="영화" />
               </children>
            </HBox>
         </children>
      </VBox>
   </center>
   <bottom>
      <HBox alignment="CENTER" prefHeight="87.0" prefWidth="600.0" spacing="100.0" BorderPane.alignment="CENTER">
         <children>
            <Button mnemonicParsing="false" onAction="#regProc" prefHeight="40.0" prefWidth="120.0" text="회원 가입">
               <font>
                  <Font size="18.0" />
               </font>
            </Button>
            <Button mnemonicParsing="false" onAction="#regCancelProc" prefHeight="40.0" prefWidth="120.0" text="취소">
               <font>
                  <Font size="18.0" />
               </font>
            </Button>
         </children>
      </HBox>
   </bottom>
</BorderPane>

실행화면

 


느낀점 :

이렇게 간단한 프로그램의 구축에서 기능을 나눌때에만 해도 파일이 이렇게 늘어나는 것을 보고 적잖이 당황스러웠습니다.

아직까지도 Service, DAO, DTO, Controller 의 기능을 명확하게 구분짓고 설정하기에는 했갈리는 부분이 있음을 느꼈습니다.

그래도 느낀 것중 하나는 규모가 큰 프로그램일 수록 이렇게 나누어서 작성해야겠다는 필요성을 느꼈습니다.