第三阶段:项目实战①小型项目开发(上篇)

第三阶段:项目实战①小型项目开发(上篇)


📋 阶段概述

欢迎来到Java学习的第三阶段!在前两个阶段中,您已经掌握了Java的基础语法和面向对象编程的核心概念。现在是时候将这些知识付诸实践,通过开发真实的小型项目来巩固和提升您的编程技能。

本阶段将通过两个经典的小型项目——学生管理系统(控制台版)简易图书管理系统(Swing GUI),帮助您从理论走向实践,体验完整的软件开发流程。

🎯 学习目标

⏱️ 预计学习时间:6-8小时

  • 📖 理论学习:1-2小时(项目规划和技术选型)
  • 💻 代码实践:4-5小时(控制台应用开发)
  • 🧪 测试调试:1小时(单元测试和运行验证)

完成本阶段学习后,您将能够:

  • 项目规划能力:学会分析需求、设计系统架构、规划开发流程
  • 综合应用技能:熟练运用面向对象编程思想解决实际问题
  • 代码组织能力:掌握良好的代码结构和项目组织方式
  • 调试和测试:具备基本的程序调试和错误处理能力
  • 用户界面开发:了解控制台和GUI界面的开发方法
  • 数据管理:掌握基本的数据存储和检索技术

📈 预期成果

  • 完成2个功能完整的小型项目
  • 建立良好的编程习惯和代码风格
  • 具备独立开发简单应用程序的能力
  • 为后续学习数据库、Web开发等高级主题打下坚实基础

🗂️ 项目选择和规划指导

项目开发流程

  1. 需求分析:明确项目功能和用户需求
  2. 系统设计:设计类结构和模块划分
  3. 编码实现:按模块逐步实现功能
  4. 测试调试:验证功能正确性
  5. 优化完善:改进用户体验和代码质量

技术选型原则

  • 循序渐进:从简单到复杂,逐步增加技术难度
  • 实用导向:选择实际开发中常用的技术和模式
  • 可扩展性:为后续功能扩展预留空间

🚀 项目一:学生管理系统(控制台版)

项目概述

开发一个基于控制台的学生信息管理系统,实现学生信息的增删改查功能。这是一个经典的CRUD(Create, Read, Update, Delete)应用,非常适合初学者练习面向对象编程。

📸 效果预览

以下是学生管理系统的运行效果图:

1. 应用程序启动界面

在这里插入图片描述

应用程序启动成功,显示欢迎信息和系统初始化状态

2. 主菜单界面

在这里插入图片描述

清晰的菜单选项,提供完整的学生管理功能

3. 显示所有学生功能

在这里插入图片描述

格式化的表格显示,包含学生的完整信息

4. 统计信息功能

在这里插入图片描述

详细的数据统计分析,包括总数、平均分、分数分布等

技术要求

  • 核心框架:Spring Boot 3.2+
  • 数据持久化:Spring Data JPA、Hibernate 6.x
  • 数据库:MySQL 8.0+
  • Java版本:JDK 17+(Spring Boot 3.x最低要求)
  • 连接池:HikariCP(Spring Boot默认)
  • 构建工具:Maven
  • 开发工具:Spring Boot DevTools
  • 设计模式:Repository模式、Service模式、依赖注入

功能需求

核心功能
  1. 学生信息管理

    • 添加学生信息
    • 查看学生列表
    • 修改学生信息
    • 删除学生信息
    • 按条件搜索学生
  2. 数据持久化

    • 使用Spring Data JPA进行数据持久化
    • 通过Repository接口实现CRUD操作
    • 利用JPA注解进行对象关系映射
  3. 用户交互

    • 友好的菜单界面
    • 输入验证和错误提示

环境准备

项目初始化

使用Spring Initializr创建Spring Boot项目,或手动创建Maven项目。

重要提醒:Spring Boot 3.x的重要变化:

  • 最低JDK版本:要求JDK 17或更高版本
  • Jakarta EE:从javax.*迁移到jakarta.*包名
  • 原生编译:支持GraalVM原生镜像编译
  • 可观测性:增强的监控和追踪功能
Maven依赖配置

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
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>org.chapter09</groupId>
    <artifactId>chapter09_01</artifactId>
    <version>1.0.0</version>
    <name>Chapter09_01 - Student Management Console</name>
    <description>学生管理系统(控制台版)</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Data JPA Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Spring Boot DevTools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot Test Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring Boot Validation Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.25</version>
        </dependency>

        <!-- H2数据库用于测试 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- JaCoCo Plugin for code coverage -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.8</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
数据库准备
  1. 安装MySQL数据库
  2. 创建数据库
-- 创建数据库
CREATE DATABASE student_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
应用配置

创建src/main/resources/application.yml配置文件:

spring:
  datasource:
    url: jdbc:mysql://localhost:${MYSQL_PORT}/student_management?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
    username: root
    password: ${MYSQL_PASSWORD}  # 请修改为实际密码,我这里使用环境变量
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update  # 自动创建/更新表结构
    show-sql: true      # 显示SQL语句
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        format_sql: true

  # 应用配置
  application:
    name: student-management

  # 控制台应用配置
  main:
    web-application-type: none  # 禁用Web功能

# 日志配置
logging:
  level:
    org.chapter09: DEBUG
    org.hibernate.SQL: DEBUG

实现步骤

第一步:主启动类

🔍 学习要点:

  • @SpringBootApplication注解 - 这是Spring Boot的核心注解,包含了@Configuration、@EnableAutoConfiguration和@ComponentScan
  • SpringApplication.run() - 启动Spring Boot应用的标准方法
  • 控制台应用 - 通过CommandLineRunner实现非Web应用
package org.chapter09.studentmanagement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 学生管理系统主启动类(控制台版)
 * <p>
 * 基于Spring Boot的控制台应用程序,展示了非Web应用的开发模式。
 *
 * <h3>🔍 学习要点:</h3>
 * <ul>
 *   <li><strong>@SpringBootApplication</strong> - Spring Boot核心注解</li>
 *   <li><strong>控制台应用</strong> - 非Web应用的开发方式</li>
 *   <li><strong>自动配置</strong> - Spring Boot的自动配置机制</li>
 * </ul>
 */
@SpringBootApplication
public class StudentManagementConsoleApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentManagementConsoleApplication.class, args);
        System.out.println("学生管理系统(控制台版)启动成功!");
    }
}
第二步:学生实体类

🔍 学习要点:

  • JPA实体映射 - 使用@Entity、@Table、@Column等注解进行对象关系映射
  • 数据类型选择 - BigDecimal用于精确的数值计算,避免浮点数精度问题
  • 时间戳管理 - @CreationTimestamp和@UpdateTimestamp自动管理创建和更新时间
  • 数据验证 - 使用Bean Validation注解进行数据校验
package org.chapter09.studentmanagement.entity;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.*;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 学生信息实体类
 */
@Entity
@Table(name = "t_students")
public class Student {

    @Id
    @Column(name = "id_", length = 20)
    private String id;  // 学号

    @Column(name = "name_", nullable = false, length = 50)
    private String name;  // 姓名

    @Column(name = "age_", nullable = false)
    private Integer age;  // 年龄

    @Column(name = "gender_", length = 10)
    private String gender;  // 性别

    @Column(name = "major_", length = 100)
    private String major;  // 专业

    @Column(name = "score_", precision = 5, scale = 2)
    private BigDecimal score;  // 成绩

    @CreationTimestamp
    @Column(name = "created_time_", updatable = false)
    private LocalDateTime createdTime;  // 创建时间

    @UpdateTimestamp
    @Column(name = "updated_time_")
    private LocalDateTime updatedTime;  // 更新时间

    // 默认构造方法
    public Student() {}

    // 带参构造方法
    public Student(String id, String name, Integer age, String gender, String major, BigDecimal score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.major = major;
        this.score = score;
    }

    // getter和setter方法
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    public String getGender() { return gender; }
    public void setGender(String gender) { this.gender = gender; }

    public String getMajor() { return major; }
    public void setMajor(String major) { this.major = major; }

    public BigDecimal getScore() { return score; }
    public void setScore(BigDecimal score) { this.score = score; }

    public LocalDateTime getCreatedTime() { return createdTime; }
    public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; }

    public LocalDateTime getUpdatedTime() { return updatedTime; }
    public void setUpdatedTime(LocalDateTime updatedTime) { this.updatedTime = updatedTime; }

    @Override
    public String toString() {
        return String.format("学号: %s, 姓名: %s, 年龄: %d, 性别: %s, 专业: %s, 成绩: %.2f",
                id, name, age, gender, major, score);
    }
}
第三步:数据访问层(Repository)
package org.chapter09.studentmanagement.repository;

import org.chapter09.studentmanagement.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.util.List;

/**
 * 学生数据访问接口
 */
@Repository
public interface StudentRepository extends JpaRepository<Student, String> {

    /**
     * 根据姓名查找学生
     */
    List<Student> findByNameContaining(String name);

    /**
     * 根据专业查找学生
     */
    List<Student> findByMajorContaining(String major);

    /**
     * 根据性别查找学生
     */
    List<Student> findByGender(String gender);

    /**
     * 根据成绩范围查找学生
     */
    List<Student> findByScoreBetween(BigDecimal minScore, BigDecimal maxScore);

    /**
     * 根据年龄范围查找学生
     */
    List<Student> findByAgeBetween(Integer minAge, Integer maxAge);

    /**
     * 自定义查询:根据关键词搜索学生(姓名或专业)
     */
    @Query("SELECT s FROM Student s WHERE s.name LIKE %:keyword% OR s.major LIKE %:keyword%")
    List<Student> searchByKeyword(@Param("keyword") String keyword);

    /**
     * 自定义查询:根据专业统计学生数量
     */
    @Query("SELECT s.major, COUNT(s) FROM Student s GROUP BY s.major")
    List<Object[]> countStudentsByMajor();

    /**
     * 自定义查询:查找成绩优秀的学生(成绩>=90)
     */
    @Query("SELECT s FROM Student s WHERE s.score >= 90 ORDER BY s.score DESC")
    List<Student> findExcellentStudents();
}
第四步:业务逻辑层(Service)
package org.chapter09.studentmanagement.service;

import org.chapter09.studentmanagement.entity.Student;
import org.chapter09.studentmanagement.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

/**
 * 学生管理业务逻辑类
 */
@Service
@Transactional
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    /**
     * 添加学生
     */
    public Student addStudent(Student student) {
        // 检查学号是否已存在
        if (studentRepository.existsById(student.getId())) {
            throw new RuntimeException("学号已存在:" + student.getId());
        }

        // 数据验证
        validateStudent(student);

        return studentRepository.save(student);
    }

    /**
     * 根据学号查找学生
     */
    public Student findStudentById(String id) {
        Optional<Student> student = studentRepository.findById(id);
        return student.orElse(null);
    }

    /**
     * 获取所有学生
     */
    public List<Student> getAllStudents() {
        return studentRepository.findAll();
    }

    /**
     * 更新学生信息
     */
    public Student updateStudent(Student student) {
        // 检查学生是否存在
        if (!studentRepository.existsById(student.getId())) {
            throw new RuntimeException("学生不存在:" + student.getId());
        }

        // 数据验证
        validateStudent(student);

        return studentRepository.save(student);
    }

    /**
     * 删除学生
     */
    public void deleteStudent(String id) {
        if (!studentRepository.existsById(id)) {
            throw new RuntimeException("学生不存在:" + id);
        }
        studentRepository.deleteById(id);
    }

    /**
     * 搜索学生
     */
    public List<Student> searchStudents(String keyword) {
        return studentRepository.searchByKeyword(keyword);
    }

    /**
     * 根据姓名查找学生
     */
    public List<Student> findStudentsByName(String name) {
        return studentRepository.findByNameContaining(name);
    }

    /**
     * 根据专业查找学生
     */
    public List<Student> findStudentsByMajor(String major) {
        return studentRepository.findByMajorContaining(major);
    }

    /**
     * 根据性别查找学生
     */
    public List<Student> findStudentsByGender(String gender) {
        return studentRepository.findByGender(gender);
    }

    /**
     * 根据成绩范围查找学生
     */
    public List<Student> findStudentsByScoreRange(BigDecimal minScore, BigDecimal maxScore) {
        return studentRepository.findByScoreBetween(minScore, maxScore);
    }

    /**
     * 查找优秀学生
     */
    public List<Student> findExcellentStudents() {
        return studentRepository.findExcellentStudents();
    }

    /**
     * 统计各专业学生数量
     */
    public List<Object[]> countStudentsByMajor() {
        return studentRepository.countStudentsByMajor();
    }

    /**
     * 验证学生信息
     */
    private void validateStudent(Student student) {
        if (student.getId() == null || student.getId().trim().isEmpty()) {
            throw new RuntimeException("学号不能为空");
        }

        if (student.getName() == null || student.getName().trim().isEmpty()) {
            throw new RuntimeException("姓名不能为空");
        }

        if (student.getAge() == null || student.getAge() < 0 || student.getAge() > 150) {
            throw new RuntimeException("年龄必须在0-150之间");
        }

        if (student.getScore() != null && (student.getScore() < 0 || student.getScore() > 100)) {
            throw new RuntimeException("成绩必须在0-100之间");
        }
    }
}
第五步:控制台交互类
package org.chapter09.studentmanagement.console;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.date.DateUtil;
import org.chapter09.studentmanagement.entity.Student;
import org.chapter09.studentmanagement.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.List;
import java.util.Scanner;

/**
 * 学生管理系统控制台交互类
 */
@Component
public class StudentConsoleRunner implements CommandLineRunner {

    @Autowired
    private StudentService studentService;

    private Scanner scanner = new Scanner(System.in);

    @Override
    public void run(String... args) throws Exception {
        System.out.println("=== 欢迎使用学生管理系统(控制台版) ===");

        while (true) {
            showMenu();
            int choice = getChoice();

            switch (choice) {
                case 1:
                    addStudent();
                    break;
                case 2:
                    displayAllStudents();
                    break;
                case 3:
                    searchStudent();
                    break;
                case 4:
                    updateStudent();
                    break;
                case 5:
                    deleteStudent();
                    break;
                case 6:
                    showStatistics();
                    break;
                case 0:
                    System.out.println("感谢使用,再见!");
                    System.exit(0);
                    break;
                default:
                    System.out.println("无效选择,请重新输入!");
            }
        }
    }

    private void showMenu() {
        System.out.println("\n=== 主菜单 ===");
        System.out.println("1. 添加学生");
        System.out.println("2. 显示所有学生");
        System.out.println("3. 搜索学生");
        System.out.println("4. 修改学生信息");
        System.out.println("5. 删除学生");
        System.out.println("6. 统计信息");
        System.out.println("0. 退出系统");
        System.out.print("请选择操作: ");
    }

    private int getChoice() {
        try {
            return Integer.parseInt(scanner.nextLine().trim());
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    private void addStudent() {
        System.out.println("\n=== 添加学生信息 ===");

        System.out.print("请输入学号: ");
        String id = scanner.nextLine().trim();
        if (StrUtil.isBlank(id)) {
            System.out.println("学号不能为空!");
            return;
        }

        System.out.print("请输入姓名: ");
        String name = scanner.nextLine().trim();
        if (StrUtil.isBlank(name)) {
            System.out.println("姓名不能为空!");
            return;
        }

        System.out.print("请输入年龄: ");
        Integer age;
        try {
            age = Integer.parseInt(scanner.nextLine().trim());
        } catch (NumberFormatException e) {
            System.out.println("年龄格式错误!");
            return;
        }

        System.out.print("请输入性别: ");
        String gender = scanner.nextLine().trim();

        System.out.print("请输入专业: ");
        String major = scanner.nextLine().trim();

        System.out.print("请输入成绩: ");
        BigDecimal score;
        try {
            score = new BigDecimal(scanner.nextLine().trim());
        } catch (NumberFormatException e) {
            System.out.println("成绩格式错误!");
            return;
        }

        Student student = new Student(id, name, age, gender, major, score);
        try {
            studentService.addStudent(student);
            System.out.println("学生信息添加成功!");
        } catch (RuntimeException e) {
            System.out.println("添加失败:" + e.getMessage());
        }
    }

    // 其他方法实现...
    // (为节省篇幅,完整代码请参考原文档)
}

测试与运行实践

单元测试

Service层单元测试
package org.chapter09.studentmanagement.service;

import org.chapter09.studentmanagement.entity.Student;
import org.chapter09.studentmanagement.repository.StudentRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

/**
 * 学生服务层单元测试
 */
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {

    @Mock
    private StudentRepository studentRepository;

    @InjectMocks
    private StudentService studentService;

    private Student testStudent;

    @BeforeEach
    void setUp() {
        testStudent = new Student();
        testStudent.setId("2023001");
        testStudent.setName("张三");
        testStudent.setAge(20);
        testStudent.setGender("男");
        testStudent.setMajor("计算机科学与技术");
        testStudent.setScore(85.5);
    }

    @Test
    void testAddStudent_Success() {
        // Given
        when(studentRepository.existsById(anyString())).thenReturn(false);
        when(studentRepository.save(any(Student.class))).thenReturn(testStudent);

        // When
        Student result = studentService.addStudent(testStudent);

        // Then
        assertNotNull(result);
        assertEquals("张三", result.getName());
        verify(studentRepository).existsById("2023001");
        verify(studentRepository).save(testStudent);
    }

    @Test
    void testAddStudent_DuplicateId() {
        // Given
        when(studentRepository.existsById(anyString())).thenReturn(true);

        // When & Then
        RuntimeException exception = assertThrows(RuntimeException.class,
            () -> studentService.addStudent(testStudent));
        assertEquals("学号已存在:2023001", exception.getMessage());
        verify(studentRepository, never()).save(any());
    }
}

测试配置

创建src/test/resources/application-test.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:

  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect

  main:
    web-application-type: none

logging:
  level:
    org.chapter09: DEBUG

运行脚本

创建scripts/run.sh

#!/bin/bash

echo "=== 学生管理系统(控制台版) ==="
echo "请确保MySQL数据库已启动并创建了student_management数据库"

JAR_FILE="target/student-management-console-1.0.0.jar"

if [ ! -f "$JAR_FILE" ]; then
    echo "JAR文件不存在,开始编译..."
    mvn clean package -DskipTests

    if [ $? -ne 0 ]; then
        echo "编译失败,请检查代码"
        exit 1
    fi
fi

echo "启动学生管理系统..."
echo "注意:这是一个控制台交互应用,请在终端中操作"

java -jar $JAR_FILE

🎯 运行效果说明

当您成功运行应用程序后,将看到如下效果:

  1. 启动阶段

    • 显示Spring Boot启动日志
    • 数据库连接初始化
    • 表结构自动创建
    • 显示"学生管理系统启动成功"消息
  2. 主菜单阶段

    • 清晰的功能菜单选项(1-7)
    • 每个选项都有明确的功能说明
    • 用户可以通过数字选择相应功能
  3. 功能操作阶段

    • 显示学生:格式化的表格展示,包含ID、姓名、年龄、性别、专业、成绩等信息
    • 添加学生:逐步引导用户输入学生信息,包含输入验证
    • 统计信息:显示学生总数、平均成绩、最高/最低分、各分数段分布等
  4. 用户体验特点

    • 友好的中文界面
    • 清晰的操作提示
    • 完善的错误处理
    • 优雅的数据格式化显示

💡 提示:如果您看到的效果与上方预览图不同,请检查:

  • 数据库连接是否正常
  • 是否有测试数据
  • 控制台编码是否设置为UTF-8

项目特点

  1. 控制台交互:通过命令行界面进行所有操作,如上方效果图所示的清晰菜单结构
  2. Spring Boot集成:使用CommandLineRunner实现控制台应用,启动界面显示完整的初始化过程
  3. 数据持久化:基于JPA的数据库操作,支持完整的CRUD功能
  4. 用户友好:清晰的菜单和格式化输出,如效果图中展示的表格化数据显示
  5. 数据统计:提供丰富的统计功能,包括平均分、分数分布等可视化信息
  6. 错误处理:完善的输入验证和错误提示机制
  7. 中文界面:全中文操作界面,符合国内用户使用习惯

❓ 常见问题解答

Q1: 为什么使用BigDecimal而不是Double来存储成绩?

A: BigDecimal提供精确的十进制运算,避免了浮点数的精度问题。对于涉及金钱、分数等需要精确计算的场景,BigDecimal是更好的选择。

Q2: CommandLineRunner和@PostConstruct有什么区别?

A:

  • CommandLineRunner: 在Spring Boot应用启动完成后执行,可以访问命令行参数
  • @PostConstruct: 在Bean初始化完成后立即执行,执行时机更早

Q3: 为什么要使用@Transactional注解?

A: @Transactional确保数据库操作的原子性,如果方法执行过程中出现异常,会自动回滚事务,保证数据一致性。

Q4: 如何处理用户输入验证?

A: 可以通过以下方式:

  • 使用Bean Validation注解(@NotNull、@Size等)
  • 在Service层添加业务逻辑验证
  • 在控制台交互层进行格式验证

Q5: 控制台应用如何优雅地退出?

A: 可以通过以下方式:

  • 捕获Ctrl+C信号,添加关闭钩子
  • 提供退出菜单选项
  • 使用System.exit(0)正常退出

Q6: 为什么我的运行效果与效果图不一致?

A: 可能的原因和解决方案:

  • 中文乱码:确保控制台编码设置为UTF-8,Windows用户可以运行chcp 65001
  • 数据显示异常:检查数据库连接是否正常,是否有测试数据
  • 菜单显示不全:确保控制台窗口足够大,建议最小80x25字符
  • 启动失败:检查MySQL服务是否启动,数据库是否创建
  • 格式错乱:确保使用等宽字体(如Consolas、Courier New)

Q7: 如何添加测试数据来获得更好的演示效果?

A: 可以通过以下方式添加测试数据:

  • StudentConsoleRunner中添加初始化数据的代码
  • 使用SQL脚本在数据库中直接插入测试数据
  • 通过应用程序的"添加学生"功能手动添加
  • 建议添加10-20条不同专业、不同成绩的学生数据以获得最佳演示效果

📖 本篇小结

在上篇中,我们完成了:

环境搭建:Spring Boot 3.x + JPA + MySQL的完整配置
实体设计:学生实体类和数据库表映射
数据访问:Repository接口和自定义查询方法
业务逻辑:Service层的完整业务实现
控制台交互:用户友好的命令行界面
测试实践:单元测试和集成测试示例

🔗 相关文章


🎉 上篇完成!

你已经掌握了Spring Boot控制台应用开发的核心技能!
接下来让我们进入中篇,学习Swing GUI应用开发。

📖 继续阅读:中篇 - 图书管理系统GUI版

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值