2.3.3 数据库连接类 (DatabaseConnection)
2.3.5 多线程管理类 (MultiThreadManager)
2.3.6 通信模块类 (CommunicationModule)
3.1.2 StudentGradesDatabase(数据库)
5.1.2 StudentGradesDatabase.java
阅读建议:
1.实验的软硬件环境要求:
(1)硬件环境要求:PC机
(2)软件环境要求:Windows 环境下的 lntelliJ IDEA2.该实验采用了MySQL数据库存储学生成绩信息,因此还需要搭建对应的数据库才可以使用。
????????本应用基于C/S模式,通过网络管理服务器端存储的学生成绩。提供图形用户界面(GUI)以便直观操作,用户可通过菜单选择功能。采用Java TCP Socket技术实现多客户端与服务器端的交互,多线程处理支持多个客户端同时操作。学生成绩数据存储在MySQL数据库中。主要功能包括:初始输入学生信息和成绩、新增学生记录、修改学生成绩、按姓名或学号查询学生成绩及平均分,以及查询所有学生的各科平均分、最高分和最低分,最后能输出完整的学生成绩表。
????????关键词:C/S模式;图形用户界面(GUI);MySQL数据库;学生成绩
????????用户的需求有学生成绩的查询、更新、添加。通过系统功能分析,针对学生成绩管理系统,应有如下功能需求:
????????1.按照学号或者姓名查询该生所有的成绩;
????????2.能够输出各科的平均分、最高分、最低分,以及所有学生的成绩表;
????????3.可以添加新的学生,可以修改某学生的各科成绩。
????????本课程设计旨在实现一个基于C/S模式的学生成绩管理系统,如图2.1 学生成绩管理系统概述图。该系统将使用MySQL数据库来存储和访问学生成绩信息,通过服务端进行管理。客户端将提供图形用户界面(GUI),允许用户通过多线程技术同时操作多个客户端。服务端将使用多线程技术处理并发连接,并提供用户权限控制和错误处理机制。客户端和服务器之间的通信将通过Socket实现。整体设计将注重代码的可读性、可维护性和可扩展性,以确保系统的稳定性和长期使用。
图2.1 学生成绩管理系统概述图
????????负责提供图形用户界面,包括输入框、按钮等控件,供用户进行操作和交互,同时将用户的请求发送给服务器模块进行处理。
????????接收到用户请求后,会根据请求类型和内容与数据库模块进行交互,获取或更新学生成绩信息。然后将处理结果返回给通信模块,再由通信模块通过Socket将结果发送给客户端。
????????负责存储和管理学生成绩信息,包括学生的基本信息、各门课程的成绩和平均分等。
????????负责在客户端和服务端之间建立Socket连接,实现数据传输和通信,其功能实现如图2.2。
图2.2 模块划分图
????????属性:客户端地址、端口号、连接管理等
????????方法:连接服务器、发送请求、接收结果等
????????属性:服务器地址、端口号、连接管理等
????????方法:接收客户端连接、处理客户端请求、返回结果等
????????属性:数据库连接信息(如主机名、用户名、密码等)
????????方法:连接数据库、执行查询或更新操作等
????????属性:界面元素(如按钮、文本框等)
????????方法:处理用户界面事件(如点击按钮、输入文本等)
????????属性:线程池、任务队列等
????????方法:创建新线程、管理线程池、任务调度等
????????属性:Socket连接信息(如IP地址、端口号等)
????????方法:建立Socket连接、发送数据、接收数据等
????????public class Server {
??? ????????public static void main(String[] args) throws IOException {
??????????????? ServerSocket serverSocket=new ServerSocket(4852);
??????????????? Socket socket=null;
??????????????? while(true){
??????????????????? new ServerThread(serverSocket.accept()).start();
??????????????? }
??? ????????}
????????}
1.属性:
?? (1)serverSocket:这是一个ServerSocket对象,用于监听客户端的连接请求。
?? (2)socket:这是一个Socket对象,代表与客户端的连接。
2.方法:
?? (1)main(String[] args): 这是程序的入口点,用于启动服务器。该方法接受一个字符串数组作为参数,并抛出IOException异常。
?? (2)ServerSocket(int port): 这是一个构造函数,用于创建一个新的ServerSocket对象,监听指定的端口。
?? (3)accept(): 该方法用于接受来自客户端的连接请求,并返回一个Socket对象,代表与客户端的连接。
?? (4)start(): 该方法用于启动一个新的ServerThread线程,处理与客户端的连接。
3.异常处理:
????????IOException异常可能会在创建ServerSocket对象或接受连接请求时抛出。在这种情况下,程序需要捕获和处理这个异常,以防止程序崩溃或数据丢失。
????????public class StudentGradesDatabase {
??????????? public static void main(String[] args) {
??????????????? String url = "jdbc:mysql://localhost:3306/StudentGrades?????????serverTimezone=GMT%2B8&useSSL=False";
??????????????? String username = "root";
??????????????? String password = "CX1994cl2004all@";
??????????????? try {
??????????????????? Class.forName("com.mysql.cj.jdbc.Driver");
??????????????????? Connection conn = DriverManager.getConnection(url, username, password);
??????????????????? Statement stmt = conn.createStatement();
??????????????????? String selectSQL = "SELECT * FROM StudentsTable";
??????????????????? ResultSet rs = stmt.executeQuery(selectSQL);
??????????????????? rs.close();
??????????????????? stmt.close();
??????????????????? conn.close();
??????????????? } catch (Exception e) {
??????????????????? e.printStackTrace();
??????????????? }
??????????? }
????????}
1.属性:
?? (1)程序首先加载MySQL的JDBC驱动,然后使用这些连接信息建立到数据库(如表3.1)的连接。
?? (2)连接建立后,程序创建一个Statement对象并执行一个SQL查询,以从“StudentsTable”表(如表3.2)中检索所有学生的信息。
2.方法:
?? (1)printStudentInfoByIDAsString ()
?? (2)printStudentInfoByNameAsString ()
?? (3)printAverageGradesAsString ()
?? (4)printMaxGradesAsString ()
?? (5)printMinGradesAsString ()
?? (6)printAllStudentInfoAsString ()
?? (7)addStudentInfo ()
?? (8)updateStudentInfo ()
Field | Type | Null | Key | Default | Extra |
学号 | int | NO | PRI | NULL | |
姓名 | varchar(10) | YES | NULL | ||
语文成绩 | int | YES | NULL | ||
数学成绩 | int | YES | NULL | ||
英语成绩 | int | YES | NULL |
表3.1 数据库结构
学号 | 姓名 | 语文成绩 | 数学成绩 | 英语成绩 |
1 | 张三 | 75 | 85 | 84 |
2 | 李四 | 85 | 68 | 65 |
3 | 王五 | 96 | 76 | 74 |
表3.2 表格内容
1.成员变量:
?? (1)Socket、DataInputStream 和 DataOutputStream:这些是用于与服务器进行通信的输入输出流。它们被初始化为连接到本地主机(localhost)的特定端口(4852)的Socket连接。
?? (2)JFrame、JPanel 和 JLabel 等 GUI 组件:这些是Java Swing库中的组件,用于构建图形用户界面。它们被初始化为各种标签、文本框和按钮等,用于显示信息和接收用户输入。
2.构造函数:
????????首先创建了与服务器进行通信的Socket连接和输入输出流。然后,创建了GUI的基本框架和一些基本组件。
图3.1 查询成绩功能流程图
图3.2 添加修改功能流程图
????????问题:java.net.SocketException: 你的主机中的软件中止了一个已建立的连接。
????????分析解决:通过试错改进,为了建立长连接(即客户端和服务器之间的连接不会在每次通信后关闭,而是会一直保持打开状态,以便于后续的持续访问),在服务端使用了 While 循环,可以保持与服务器的连接并处理来自客户端的请求,从而解决了这个问题。
????????问题:使用GridLayout(网格布局)后,虽然简单易操作,但没有办法自动调整像素大小,导致每个组件大小一模一样。这样的图形界面很丑且不适宜。
????????分析解决:根据自己的想法,如图4.1所示,我采用了BorderLayout(边界布局),这个可以将组件添加到容器的各个区域,例如NORTH、SOUTH、EAST、WEST和CENTER。
图4.1 手工绘制GUI界面
????????但是每一块区域的具体布局还是有问题,在这样的情况下,我找到了GridBagLayout(网袋布局),这是一个灵活的布局管理器,可以指定组件的位置、大小和填充方式等。通过具体数值的设置便完成了整个GUI界面的设计。
????????问题:由于GUI图形化界面是自学内容,在准备编写按钮监听代码时突然想到服务端该怎么区分客户端的请求,一下子却没有办法。
????????分析解决:如何区分?就能联想到“唯一标识”,那么在C/S模式中又该怎样设置?在GUI界面中的按钮组件又该怎样设置?之后我开始阅读之前写过的C/S模式的代码(如图4.2),在接收到客户端的数据后,通过之前预定的间隔方式将其分割,然后第一个单元就是判断请求类型的数据。
图4.2 C/S模式服务端代码片段
????????同理,在发送数据时添加一个“唯一标识符”即可在服务端进行判断执行。
public class Server {
??? public static void main(String[] args) throws IOException {
??????? ServerSocket serverSocket=new ServerSocket(4852);
??????? Socket socket=null;
??????? while(true){
??????????? new ServerThread(serverSocket.accept()).start();
??????? }
??? }
}
class ServerThread extends Thread{
??? Socket socket;
??? ServerThread(Socket socket){
??????? this.socket=socket;
??? }
??? public void run(){
??????? try{
??????????? // getInputStream 从Socket中获取输入流,以便读取从客户端发送来的数据
??????????? // 基本数据类型
??????????? DataInputStream dis=new DataInputStream(socket.getInputStream());
??????????? DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
??????????? while (true){
??????????????? String url = "jdbc:mysql://localhost:3306/StudentGrades?serverTimezone=GMT%2B8&useSSL=False";
??????????????? String username = "root";
??????????????? String password = "CX1994cl2004all@";
??????????????? String value=dis.readUTF();
??????????????? String[] parts=value.split(" ");
??????????????? String type=parts[0];? // 标识码
??????????????? String param1=parts[1];? // 参数
??????????????? String param2=parts[2];
??????????????? int param3= Integer.parseInt(parts[3]);
??????????????? int param4= Integer.parseInt(parts[4]);
??????????????? int param5= Integer.parseInt(parts[5]);
??????????????? // 按学号查询某学生的各门课成绩、平均成绩
??????????????? if(type.equals("1")){
??????????????????? try (Connection conn = DriverManager.getConnection(url, username, password)) {
??????????????????????? dos.writeUTF(StudentGradesDatabase.printStudentInfoByIDAsString(conn, param1));
??????????????????? } catch (SQLException e) {
??????????????????????? throw new RuntimeException(e);
??????????????????? }
??????????????? } catch(IOException e){}
??? }
}
public class StudentGradesDatabase {
??? public static void main(String[] args) {
??????? String url = "jdbc:mysql://localhost:3306/StudentGrades?serverTimezone=GMT%2B8&useSSL=False";
??????? String username = "root";
??????? String password = "CX1994cl2004all@";
??????? try {
??????????? Class.forName("com.mysql.cj.jdbc.Driver");
??????????? Connection conn = DriverManager.getConnection(url, username, password);
??????????? Statement stmt = conn.createStatement();
??????????? String selectSQL = "SELECT * FROM StudentsTable";
??????????? ResultSet rs = stmt.executeQuery(selectSQL);
??????????? rs.close();
??????????? stmt.close();
??????????? conn.close();
??????? } catch (Exception e) {
??????????? e.printStackTrace();
??????? }
??? }
??? public static String printStudentInfoByIDAsString(Connection conn, String studentID) throws SQLException {
??????? String sql = "SELECT * FROM StudentsTable WHERE 学号 = ?";
??????? PreparedStatement stmt = conn.prepareStatement(sql);
??????? stmt.setString(1, studentID);
??????? ResultSet rs = stmt.executeQuery();
??????? if (rs.next()) {
??????????? int chinese = rs.getInt("语文成绩");
??????????? int math = rs.getInt("数学成绩");
??????????? int english = rs.getInt("英语成绩");
??????????? double average = (chinese + math + english) / 3.0;
??????????? DecimalFormat decimalFormat = new DecimalFormat("#.##");
??????????? String formattedAverage = decimalFormat.format(average);
??????????? String studentInfo = "学号: " + rs.getString("学号") + ", 姓名: " + rs.getString("姓名") + ", 语文: " + rs.getInt("语文成绩") + ", 数学: " + rs.getInt("数学成绩") + ", 英语: " + rs.getInt("英语成绩") +", 平均成绩: " + formattedAverage;
??????????? rs.close();
??????????? stmt.close();
??????????? return studentInfo;
??????? } else {
??????????? rs.close();
??????????? stmt.close();
??????????? return "没有找到该学生的信息";
??????? }
??? }
}
public class Client {
??? // C/S模式
??? private Socket socket;
??? private DataInputStream dis;
??? private DataOutputStream dos;
??? // GUI组件
??? private JFrame frame;
??? private JPanel panelHead, panelLeft, panelRight,panelCenter;
??? private JLabel labelTitle,labelBody,labelSearchId,labelSearchName,labelName,labelId,labelChinese,labelMath,labelEnglish ;
??? private JTextField inputFieldSearchId, inputFieldSearchName, inputFieldName, inputFieldId,inputFieldChinese,inputFieldMath,inputFieldEnglish;
??? private JButton buttonSearchId, buttonSearchName, buttonAver, buttonMax, buttonMin, buttonAll,buttonAdd,buttonRevise;
??? public Client() throws IOException {
??????? // 初始化C/S
??????? socket = new Socket("localhost", 4852);
??????? dis = new DataInputStream(socket.getInputStream());
??????? dos = new DataOutputStream(socket.getOutputStream());
??????? // 初始化GUI
??????? frame = new JFrame("超级管理员");
??????? frame.setSize(800, 500);
??????? frame.setLocationRelativeTo(null);
??????? frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//??????? frame.pack();
??????? frame.setVisible(true);
??????? GridBagConstraints constraints = new GridBagConstraints();
??????? labelTitle = new JLabel("欢迎使用学生成绩管理系统");
??????? labelTitle.setFont(new Font("楷体", Font.BOLD, 24));
??????? labelTitle.setHorizontalAlignment(JLabel.CENTER);
??????? panelHead = new JPanel(new GridBagLayout());
??????? panelHead.setPreferredSize(new Dimension(800, 100));
??????? constraints.gridx = 0;
??????? constraints.gridy = 0;
??????? constraints.anchor = GridBagConstraints.CENTER;
??????? panelHead.add(labelTitle,constraints);
??????? frame.getContentPane().add(BorderLayout.NORTH, panelHead);
}
buttonSearchId.addActionListener(new ActionListener() {
??????????? @Override
??????????? public void actionPerformed(ActionEvent e) {
??????????????? // 处理按学号查询的逻辑
??????????????? // 获取输入的学号
??????????????? String Id = inputFieldSearchId.getText();
??????????????? // 检查输入是否为空
??????????????? if (Id == null || Id.trim().isEmpty()) {
??????????????????? JOptionPane.showMessageDialog(frame, "请输入有效的学号!");
??????????????????? return;
??????????????? }
??????????????? try {
??????????????????? // 发送学号到服务器进行查询
??????????????????? sendNumberToServer("1 "+Id+" 0 0 0 0");
??????????????????? // 从服务器获取查询结果
??????????????????? DataInputStream dataInputStream = getDataInputStream();
??????????????????? String result = dataInputStream.readUTF();
??????????????????? // 显示查询结果
??????????????????? labelBody.setText(result);
??????????????? } catch (IOException ex) {
??????????????????? JOptionPane.showMessageDialog(frame, "查询失败,请检查网络连接或服务器状态!");
??????????????????? ex.printStackTrace();
??????????????? }
??????????? }
??????? });
public void sendNumberToServer(String text) throws IOException {
??????? dos.writeUTF(text);
??????? dos.flush();
??? }
??? public DataInputStream getDataInputStream() {
??????? return dis;
??? }
??? public void closeResources() throws IOException {
??????? if (dis != null) {
??????????? dis.close();
??????? }
??????? if (dos != null) {
??????????? dos.close();
??????? }
??????? if (socket != null) {
??????????? socket.close();
??????? }
??? }
}
图6.1 测试结果截屏
????????1.GUI界面还不够美观,目前只是对基本操作的组件进行了布局,这样的界面没有潜力,即很难继续扩充其他的操作。
????????2.通过C/S模式发送数据时还不够安全,即数据的安全性还不足。
????????3.客户端与服务端之间的数据传输过于简单,且不够严谨。一旦传输的数据发生了改变,该程序将直接崩溃。同时,数据的处理也不够好,比如要在GUI界面输出所有同学的成绩表,应该打印一个表格,但我的显示方式是逐条输出。
????????通过此次的课程设计,我深刻体会到了在开发过程中不断学习和实践的重要性。在Java GUI编程中,布局管理器是关键的一部分,它们决定了界面的外观和组件的布局方式。使用GridLayout时,我发现了它无法自动调整组件大小的问题,这使我开始寻找其他更合适的布局管理器。最终,我选择了GridBagLayout,它更加灵活,可以指定组件的位置、大小和填充方式,从而创建出更美观的界面。
????????同时,我也明白了在C/S模式中,服务端需要区分来自不同客户端的请求。通过在发送和接收数据时添加“唯一标识符”,服务端可以根据标识符来识别请求的类型并执行相应的操作。这个过程中,我意识到了预先规划好数据格式和约定的重要性,它能帮助我们在开发过程中避免混淆和错误。
????????总的来说,这个项目让我更深入地理解了Java GUI编程和C/S模式的实现方式。我不仅学到了如何使用不同的布局管理器来设计界面,还掌握了如何在服务端处理来自不同客户端的请求。这个过程中,我遇到了许多困难和挑战,但通过不断学习和实践,我成功解决了这些问题,并获得了宝贵的经验。
[1] 张思民.Java语言程序设计(第3版)[M] .北京:清华大学出版社,2015年12月.
[2] [美]Bruce Eckel.Java编程思想第四版[M] .北京:机械工业出版社,2010年2月.
[3] 郎波.Java语言程序设计(第3版)[M].北京:清华大学出版社,2016年8月.
[4] 关东升.Java从小白到大牛(第4版)[M].北京:清华大学出版社,2021年03月.
[5] 黑马程序员.Java基础入门[M].北京:清华大学出版社,2018年12月.
[6] 孙卫琴.Java面向对象编程(第2版).北京:电子工业出版社,2017年1月.
? ? ? ? 完整代码链接:https://download.csdn.net/download/weixin_73286497/88757267
????????希望大家可以在该篇课程设计中有所收获,同时也感谢各位大佬的支持。文章如有任何问题请在评论区留言斧正,鸿蒙会尽快回复您的建议!