JavaFX 实现windows版简易的登录软件 | 整合 MyBatis 查询数据库账号 | 窗口切换 | 使用SceneBuilder进行可视化编辑页面

—— 若发现文章有任何不正确的地方,敬请指出,望不吝赐教,谢谢!

文章目录

  • 效果展示
  • 一、准备知识
    • 相关工具:Scene Builder [点击下载](https://openjfx.cn/scene-builder/)
  • 二、运行环境
  • 三、引入依赖
  • 四、项目结构介绍
  • 五、配置数据库交互层DAO层
    • 5.1 连接数据库配置
    • 5.2 MyBatis配置
    • 5.3 编写MyBatis工具类 DBUtils.java
    • 5.4 DAO层
  • 六、编写Service业务逻辑层
  • 七、JavaFX相关的类
    • 7.1 启动类
    • 7.2 编写StageController窗口控制类
    • 7.3 公用接口ControlledStage
    • 7.4 组件的Controller类
      • 7.4.1 登录控制层
      • 7.4.2 主页面控制层
  • 八、核心业务逻辑

效果展示


在登录窗口进行登录,登录验证成功后跳转到另一个窗口,若失败则弹出提示框

一、准备知识

JavaFX 中文官网:点击查看

javaFX 是一个开源的下一代客户端应用平台,适用于基于Java构建的桌面、移动端和嵌入式系统。 它是许多个人和公司的共同努力的成果,目的是为开发丰富的客户端应用提供一个现代、高效、功能齐全的工具包。

JavaFX架构:
JavaFX 实现windows版简易的登录软件 | 整合 MyBatis 查询数据库账号 | 窗口切换 | 使用SceneBuilder进行可视化编辑页面_第1张图片

相关工具:Scene Builder 点击下载

Scene Builder 是一款创建美丽的用户界面,并将您的设计变成一个交互式原型。Scene Builder通过创建可直接用于JavaFX应用程序的用户界面,缩小了设计师和开发人员之间的差距。

JavaFX相关的资料网站:

JavaFXChina 是国内JavaFX资料最为系统全面的网站之一,他们靠业余时间维护此站点,希望能帮到大家。

JavaFX 实现windows版简易的登录软件 | 整合 MyBatis 查询数据库账号 | 窗口切换 | 使用SceneBuilder进行可视化编辑页面_第2张图片

二、运行环境


  • Windows10
  • MySQL8
  • JDK8
  • Maven3.8.4
  • IDEA 2021.3.2 Ultimate
  • 其他的版本号请查看引入的Maven依赖

三、引入依赖


pom.xml


<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.0modelVersion>

    <groupId>com.unigroupId>
    <artifactId>cs-pluginsartifactId>
    <version>1.0-SNAPSHOTversion>
    <name>cs-pluginsname>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <junit.version>5.8.1junit.version>
        <java.version>8java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfxgroupId>
            <artifactId>javafx-controlsartifactId>
            <version>${java.version}version>
        dependency>
        <dependency>
            <groupId>org.openjfxgroupId>
            <artifactId>javafx-fxmlartifactId>
            <version>${java.version}version>
        dependency>
        <dependency>
            <groupId>org.kordamp.bootstrapfxgroupId>
            <artifactId>bootstrapfx-coreartifactId>
            <version>0.4.0version>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-apiartifactId>
            <version>${junit.version}version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-engineartifactId>
            <version>${junit.version}version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.25version>
        dependency>
	
        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.8.1version>
                <configuration>
                    <source>${java.version}source>
                    <target>${java.version}target>
                configuration>
            plugin>
            <plugin>
                <groupId>org.openjfxgroupId>
                <artifactId>javafx-maven-pluginartifactId>
                <version>0.0.8version>
                <executions>
                    <execution>
                        
                        <id>default-cliid>
                        <configuration>
                            <mainClass>com.uni/com.uni.AppmainClass>
                            <launcher>applauncher>
                            <jlinkZipName>appjlinkZipName>
                            <jlinkImageName>appjlinkImageName>
                            <noManPages>truenoManPages>
                            <stripDebug>truestripDebug>
                            <noHeaderFiles>truenoHeaderFiles>
                        configuration>
                    execution>
                executions>
            plugin>
        plugins>
    build>
project>

四、项目结构介绍


由于工具会涉及数据库的相关交互,同时有视图的概念,这里采用类似于Web的MVC模式进行设计,其中的M表示Model模型,在这里对应于JavaFX的Stage窗口,V则表示View视图,对应于JavaFX的Scene,C表示Controller控制层,JavaFX官方API有提供getController()的方法。

其他的相关介绍:

名称 描述
controller 存储控制层相关的类,其中比较重要的是Stage相关的类
dao 存储负责数据库交互的Mapper接口,实现则通过resources文件夹里的XML映射文件
pojo 存储实体类
service 存储业务层接口以及对应的Impl实现类
utils 存储一些工具类,比如获取MyBatis负责数据库交互的mapper类
App.java 继承于JavaFX的Application类,主要用于启动JavaFX程序以及配置相关的窗口信息
resources/com/uni/mapper/ 存储MyBatis里Mapper接口对应的XML映射文件
login.fxml 使用SceneBuilder工具进行可视化设计的登录的页面
main.fxml 使用SceneBuilder工具设计的登录成功后的主页面(这里涉及页面切换概念)
lib 存储一些关键的jar包,可以忽略,Maven导入依赖就行
database.properties 配置JDBC连接的一些信息,数据库连接地址、驱动类、用户、密码
mybatis-config.xml 配置项目的MyBatis

JavaFX 实现windows版简易的登录软件 | 整合 MyBatis 查询数据库账号 | 窗口切换 | 使用SceneBuilder进行可视化编辑页面_第3张图片

五、配置数据库交互层DAO层


5.1 连接数据库配置

/resources/database.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
user=数据库用户名
password=数据库密码

这里需要注意,为方便代码里获取到配置内容,最好是将该其放在resources资源文件夹里,方便查找以及访问

JDK官方有API操作properties的配置文件,所以不需要手动进行IO流的转换,这里则通过MyBatis框架提供的API直接连接数据库

范例: 根据MySQL的配置文件创建MyBatis框架的工厂(可通过工厂取Mapper类、执行方法等)

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

String CONFIG_PATH = "mybatis-config.xml";

InputStream config = Resources.getResourceAsStream(CONFIG_PATH);

factory = new SqlSessionFactoryBuilder().build(config);

5.2 MyBatis配置

在没有SpringBoot下配置MyBatis果然有些复杂了,不过稍微总结一下,也不难理解,它的主要配置内容如下:

  • 引入相关的数据库配置文件(可以不引入直接写值,但这会导致耦合度提升,不利于代码修改)
  • 特殊的设置(这里可有可无,而且配置类型比较多,笔者就只改了个日志输出的类logImpl,这样就可以按照自己的日志配置来
  • 设置别名,一是简化实体类(这样可以省略包名看起来简短)二是简化Mapper类的接口类,这样在写XML时指定namespace就可以省略Mapper接口的包名)
  • 配置数据库环境
  • 最后,设置Mapper的XML映射文件地址,这样MyBatis才能找到对应的XML映射

/resources/mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">


<configuration>
    
    <properties resource="database.properties"/>
    
    <settings>
        <setting name="logImpl" value="LOG4J" />
    settings>
    
    <typeAliases>
        
        <typeAlias type="com.uni.pojo.User" alias="User"/>
        
        <package name="com.uni.mapper"/>
    typeAliases>
    <environments default="development">
        <environment id="development">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${user}"/>
                <property name="password" value="${password}"/>
            dataSource>
        environment>
    environments>


    <mappers>
        <mapper resource="com/uni/mapper/UserMapper.xml"/>
    mappers>
configuration>

5.3 编写MyBatis工具类 DBUtils.java

关于MyBatis的使用,之前浅学了SSM+SpringBoot,就只记住了@Repository、@Autowired这两个注解,当不使用注解的时候,有多种选择进行创建,这里则使用其中的工厂模式来创建。

DBUtils.java

package com.uni.utils;

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 DBUtils{
    private static final String CONFIG_PATH = "mybatis-config.xml";
    private static SqlSessionFactory factory;
    // 单例模式
    static {
        try {
            // 获取配置文件输入流
            InputStream config = Resources.getResourceAsStream(CONFIG_PATH);
            // 创建工厂
             factory = new SqlSessionFactoryBuilder().build(config);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 获取session会话
    public static SqlSession getSession(){
        return factory.openSession();
    }
    // 关闭session会话
    public static void close(){
        //..
    }
}

5.4 DAO层

在编写DAO层接口前,需先编写相应的实体类,比如笔者需要实现登录效果,这里就设计一个用户User的Pojo(简单的Java对象) 类

User.java

package com.uni.pojo;


/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/

public class User {
    private Integer id;
    private String username;
    private String password;

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Dao层接口类:UserMapper.java

package com.uni.dao;

import com.uni.pojo.User;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public interface UserMapper {
    User selectByPassword(User user);
}

Dao层XML映射文件UserMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.uni.dao.UserMapper">
    <select id="selectByPassword" parameterType="User" resultType="User">
        SELECT * FROM cs_user
        WHERE username = #{username} AND password = #{password}
    select>
mapper>

六、编写Service业务逻辑层


接口类:UserService.java (实现类放在最后的部分,因为它用到了JavaFX的相关的一些类)

package com.uni.service;

import com.uni.pojo.User;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public interface UserService {
    public User login(User user);
}

七、JavaFX相关的类


7.1 启动类

App.java

package com.uni;

import com.uni.controller.StageController;
import javafx.application.Application;
import javafx.stage.Stage;


public class App extends Application {

    public static String mainViewID = "MainView";
    public static String mainViewRes = "main.fxml";

    public static String loginViewID = "LoginView";
    public static String loginViewRes = "login.fxml";

    @Override
    public void start(Stage primaryStage) {
        //新建一个StageController控制器
        StageController stageController = new StageController();
        //设置默认的主窗口
        //加载两个窗口
        stageController.loadStage(loginViewID, loginViewRes);
        stageController.loadStage(mainViewID, mainViewRes);
        //切换主窗口
        stageController.change(loginViewID);
    }

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

7.2 编写StageController窗口控制类

该类使用HashMap结构来存储对应的窗口名称、对应的Stage窗口对象,使用双端队列进行窗口的切换控制,目前还存在一些不足,仅作参考,相关的方法以及介绍。

方法名 描述
void addStage(String a, Stage b) 根据名称添加Stage窗口到Map结构中
Stage getStage(String a ) 根据Stage窗口名称获取对应的Stage对象
change(String a) 根据Stage名称切换到对应的窗口(要求Stage存在于Map中)设计双端队列的处理
loadStage(String a, String b, StageStyle …) 根据Stage的名称和对应的fxml文件位置,加上可选的窗口风格加载新的Stage对象,加载完毕后会保存到Map结构
unloadStage(String a) 根据Stage名称删除相应的窗口
stages 静态变量,存储所有的Stage名称以及Stage信息
queue 静态变量, 存储当前的窗口序列,可用于返回操作(这里暂未使用)

StageController.java

package com.uni.controller;

import com.uni.App;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public class StageController {

    private static HashMap<String, Stage> stages = new HashMap<String, Stage>();       // 存储所有Stage
    private static Deque<Stage> queue = new LinkedList<>();                            // 窗口双端队列(先进后出)(切换窗口有效)

    /**
     * 添加Stage,保存到Map结构里
     * @param name 窗口名称
     * @param stage 窗口的stage,由该类的{@link #loadStage}方法进行创建
     *
     */
    public void addStage(String name, Stage stage) {
        stages.put(name, stage);
    }

    /**
     * 根据Stage名称从Map结构里获取相应的Stage
     * @param name Stage的对应名称,加载通过{@link #loadStage(String, String, StageStyle...)} 添加通过{@link #addStage(String, Stage)}
     * @return Map里存在的Stage对象
     */
    public Stage getStage(String name) {
        return stages.get(name);
    }

    /**
     * 切换Map结构里存在的Stage,将其压入栈中。若不存在则抛出异常
     * @param stageName 切换的Stage名称
     */
    public void change(String stageName) {
        // 首先获取当前的state
        Stage stage = stages.get(stageName);
        if(stage == null)
            throw new NullPointerException("未获取到指定的Stage,请确保它已经使用过addStage()/loadStage()");
        else {
            // 遍历队列
            if (queue != null && !queue.isEmpty()) {
                // 将队首的传移动到队尾
                queue.push(queue.pollFirst());
                // 关闭所有窗口
                for (Stage temp : queue) {
                    // 若当前窗口是要切换的窗口,则移除(防止重复)
                    if(temp.equals(stage))
                        queue.remove(temp);
                    temp.close();
                }
            }
            // 头插法
            queue.addFirst(stage);
            // 启动队首窗口
            queue.getFirst().show();
        }
    }

    /**
     * 加载Stage资源,同时会调用{@link #addStage(String, Stage)},最终存储到 Map结构里
     * @param name Stage的名称
     * @param resources Stage的资源位置(建议在resources资源文件夹下)
     * @param styles  Stage的Style风格,可指定多个风格
     * @return 返回是否加载成功,只要不报错则说明加载以及保存成功, 成功返回true,反之返回false
     */
    public boolean loadStage(String name, String resources, StageStyle... styles) {
        try {
            //加载FXML资源文件
            FXMLLoader loader = new FXMLLoader(App.class.getResource(resources));
            Pane tempPane = loader.load();
            //通过Loader获取FXML对应的ViewCtr,并将本StageController注入到ViewCtr中

            // 通过官方API获取当前Stage的Controller,利用官方提供的泛型设置将其返回为接口ControlledStage
            ControlledStage controlledStage = loader.getController();
            // 调用相应的set方法,为当前Staged设置现在的Controller列
            controlledStage.setStageController(this);

            //构造对应的Stage
            Scene tempScene = new Scene(tempPane);
            Stage tempStage = new Stage();
            tempStage.setScene(tempScene);

            //初始化窗口的风格
            for (StageStyle style : styles) {
                tempStage.initStyle(style);
            }
            //将设置好Stage添加到Map结构
            this.addStage(name, tempStage);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /***
     * 卸载指定的Stage
     * @param name Stage的名称,这Stage在通过{@link #addStage} 或者 {@link #loadStage}两种方法添加后的菜菜有效
     * @return 是否卸载成功,成功为true,反之为false
     */
    public boolean unloadStage(String name) {
        if (stages.remove(name) == null) {
            System.out.println("窗口不存在,请检查名称");
            return false;
        } else {
            System.out.println("窗口移除成功");
            return true;
        }
    }
}

7.3 公用接口ControlledStage

该接口的作用是要求实现setStageController()方法,StageController则是上面那个自定义的用于处理JavaFX窗口显示的控制层类,当我们绑定一些按钮,要实现界面跳转时就得通过Controller层的逻辑代码进行处理。所以我们规定其他的Controller类都需要实现这个接口,这提示该类需要有一个StageController对象作为成员变量,方便进行窗口切换的操作。

package com.uni.controller;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public interface ControlledStage {
    public void setStageController(StageController stageController);
}

7.4 组件的Controller类

7.4.1 登录控制层

相关的fxml,通过SceneBuilder可视化工具进行设计
















<VBox prefHeight="407.0" prefWidth="560.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="com.uni.controller.LoginController"
>
  <children>
    <MenuBar VBox.vgrow="NEVER">
      <menus>
        <Menu mnemonicParsing="false" text="帮助">
          <items>
            <MenuItem mnemonicParsing="false" text="About MyHelloApp" />
          items>
        Menu>
      menus>
    MenuBar>
    <AnchorPane maxHeight="-1.0" maxWidth="-1.0" prefHeight="321.0" prefWidth="520.0" VBox.vgrow="ALWAYS">
      <children>
          <TextField fx:id="username" layoutX="217.0" layoutY="119.0" prefHeight="35.0" prefWidth="202.0" />
          <PasswordField fx:id="password" layoutX="217.0" layoutY="175.0" prefHeight="35.0" prefWidth="202.0" />
            <Button fx:id="btLogin" onAction="#btLogin" defaultButton="true" layoutX="207.0" layoutY="253.0" mnemonicParsing="false" prefHeight="30.0" prefWidth="195.0" text="登录" />
            <Button layoutX="132.0" layoutY="253.0" mnemonicParsing="false" text="注册" />
            <CheckBox layoutX="331.0" layoutY="219.0" mnemonicParsing="false" text="记住密码" />
            <Text layoutX="45.0" layoutY="82.0" strokeType="OUTSIDE" strokeWidth="0.0" text="CS1.6插件管理系统-登录">
               <font>
                  <Font size="41.0" />
               font>
            Text>
            <Label layoutX="132.0" layoutY="120.0" text="账号">
               <font>
                  <Font size="25.0" />
               font>
            Label>
            <Label layoutX="132.0" layoutY="176.0" text="密码">
               <font>
                  <Font size="25.0" />
               font>
            Label>
      children>
    AnchorPane>
  children>
VBox>

在登录控制层中,通过@FXML获取到了fxml格式的UI配置文件里使用fx:id标记的组件,然后直接调用业务层的login方法进行登录,最后根据返回的结果是否为null则判断是否登录成功,若登录成功则需要跳转窗口,若失败则弹出提示窗口(这个功能在业务逻辑层实现)

package com.uni.controller;

import com.uni.App;
import com.uni.pojo.User;
import com.uni.service.UserService;
import com.uni.utils.ServiceUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;

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

public class LoginController implements ControlledStage, Initializable {

    private static UserService userService = ServiceUtils.userService;

    StageController controller;

    @FXML
    private TextField username;

    @FXML
    private PasswordField password;

    @FXML
    private void btLogin(){
        User login = userService.login(new User(username.getText(), password.getText()));
        if(login != null)
            goToMain();
    }

    public void setStageController(StageController stageController) {
        this.controller = stageController;
    }

    public void initialize(URL location, ResourceBundle resources) {
    }

    public void goToMain(){
        controller.change(App.mainViewID);
    }
}

7.4.2 主页面控制层

这里还没有设计逻辑跳转,但可以参照之前的登录控制层进行添加

package com.uni.controller;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public class MainController implements ControlledStage{
    private StageController controller;

    @Override
    public void setStageController(StageController stageController) {
        this.controller = stageController;
    }
}

八、核心业务逻辑

之前声明了UserService接口,定义了login()方法,先进行补充:

这里用到了JavaFX的Alert警告框,通过调用showAndWati()方法就可以弹出这个窗口,则密码验证失败后弹出提示用户输入错误,方便用户操作。

package com.uni.service.impl;

import com.uni.dao.UserMapper;
import com.uni.pojo.User;
import com.uni.service.UserService;
import com.uni.utils.DBUtils;
import javafx.scene.control.Alert;

/**
 * @作者: Unirithe
 * @日期: 2022/2/27
 **/
public class UserServiceImpl implements UserService {

    private static UserMapper userMapper;
    public UserServiceImpl(){
        if(userMapper == null)
            userMapper = DBUtils.getSession().getMapper(UserMapper.class);
    }
    @Override
    public User login(User user) {
        User result = userMapper.selectByPassword(user) ;
        if(result == null){
            Alert alert = new Alert(Alert.AlertType.WARNING);
            alert.setTitle("登录提示");
            alert.setContentText("输入的账号或密码有误");
            alert.showAndWait();
        }
        return result;
    }
}

你可能感兴趣的:(windows,java,maven)