1. 项目概述
最近在整理Spring框架的教学案例时,我重新设计了一个基于控制台的用户登录验证系统。这个案例特别适合刚接触Spring JDBC的开发者,通过一个完整的登录流程,展示了Spring如何简化数据库操作。不同于简单的CRUD示例,这个项目包含了用户交互、数据验证和结果反馈等实际开发中的常见环节。
系统核心功能很简单:用户在控制台输入用户名和密码,程序会查询数据库进行验证。如果验证通过,就显示该用户所属班级;如果失败,则给出明确提示。虽然功能简单,但涉及的技术栈很完整:Spring Core管理Bean生命周期,Spring JDBC处理数据访问,MySQL作为持久化存储。
提示:这个项目使用的是Spring 5.2.x版本,与最新Spring 6.x在配置上有些差异。如果你用的新版本,需要注意部分API的变化。
2. 环境准备与数据库设计
2.1 MySQL数据库配置
首先需要准备MySQL环境。我推荐使用5.7或8.0版本,这两个版本在企业中应用最广泛。安装好MySQL后,用Navicat或命令行创建数据库:
sql复制CREATE DATABASE spring CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE spring;
这里特意指定了utf8mb4字符集,这是为了完整支持emoji和所有Unicode字符。虽然本案例用不到这些特性,但在实际项目中很可能会遇到需要存储特殊符号的情况。
2.2 学生表结构设计
学生表的设计考虑了最简单的登录验证需求:
sql复制CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
course VARCHAR(255) COMMENT '班级/课程信息'
);
几个关键设计点:
- username设为UNIQUE确保唯一性
- 密码字段使用VARCHAR(255)而不是CHAR固定长度,这是为了后续可能升级到加密存储
- course字段存储班级信息,用COMMENT做了说明
初始化数据时,我特意使用了三种常见场景:
sql复制INSERT INTO student(username, password, course) VALUES
('zhangsan', '123456', '语文'),
('tom', '654321', '数学'),
('jack', '111111', '英语');
注意:实际项目中密码绝对不能明文存储!这里只是为了演示方便。生产环境应该使用BCrypt等加密算法。
3. 项目搭建与配置
3.1 Maven项目初始化
使用IDEA创建Maven项目时,有几个关键选择会影响后续开发:
- GroupId通常使用公司域名倒写,如com.example
- ArtifactId应该具有描述性,如spring-jdbc-demo
- 打包方式选择jar即可
创建完成后,第一件事就是配置pom.xml。Spring JDBC需要一组相关的依赖:
xml复制<dependencies>
<!-- Spring核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- Spring JDBC支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>
3.2 项目结构规划
合理的包结构能让项目更易维护。我采用了典型的三层架构:
code复制src/main/java
├── com.demo9
│ ├── Entity # 实体类
│ ├── Dao # 数据访问接口
│ └── Impl # 接口实现
这种结构虽然简单,但清晰地分离了不同职责的代码。随着项目复杂度的增加,可以进一步细分为controller、service等更多层次。
4. 核心代码实现
4.1 实体类设计
Student实体类对应数据库表结构,采用标准的JavaBean规范:
java复制public class Student {
private Integer id;
private String username;
private String password;
private String course;
// 标准的getter和setter方法
// 省略...
}
这里有个细节:所有字段都用包装类型Integer而不是基本类型int。这是因为数据库中的NULL值需要用对象类型来表示。
4.2 DAO层实现
数据访问层先定义接口,再提供实现:
java复制public interface StudentDao {
List<Student> findAllStudent();
}
实现类使用Spring的JdbcTemplate来简化操作:
java复制public class StudentDaoImpl implements StudentDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<Student> findAllStudent() {
String sql = "SELECT * FROM student";
RowMapper<Student> rowMapper = new BeanPropertyRowMapper<>(Student.class);
return jdbcTemplate.query(sql, rowMapper);
}
}
BeanPropertyRowMapper是Spring提供的一个便捷工具,它能自动将查询结果映射到JavaBean属性上,前提是列名与属性名匹配。
4.3 Spring配置
applicationContext-student.xml配置了所有必要的Bean:
xml复制<!-- 数据源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- DAO配置 -->
<bean id="studentDao" class="com.demo9.Impl.StudentDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
注意MySQL 8.0+需要指定serverTimezone参数,否则会出现时区错误。
5. 业务逻辑实现
5.1 控制台交互
测试类StudentTest实现了控制台交互逻辑:
java复制public class StudentTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-student.xml");
StudentDao studentDao = context.getBean("studentDao", StudentDao.class);
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
List<Student> students = studentDao.findAllStudent();
boolean found = false;
for (Student student : students) {
if (student.getUsername().equals(username)) {
found = true;
System.out.print("请输入密码:");
String password = scanner.nextLine();
if (student.getPassword().equals(password)) {
System.out.println("登录成功!");
System.out.printf("用户 %s 属于 %s 班%n",
student.getUsername(), student.getCourse());
} else {
System.out.println("密码错误!");
}
break;
}
}
if (!found) {
System.out.println("用户名不存在!");
}
}
}
5.2 验证流程优化
原始代码的验证逻辑有些问题:只要输入的用户名不匹配第一个学生记录,就会立即返回错误。我优化后的版本会遍历所有学生记录,只有确认用户名不存在时才提示。
6. 常见问题与解决方案
6.1 数据库连接问题
问题现象:出现"Communications link failure"错误
可能原因:
- MySQL服务未启动
- 连接URL中的数据库名错误
- 用户名或密码错误
- 服务器防火墙阻止了3306端口
解决方案:
- 检查MySQL服务状态:
sudo systemctl status mysql - 验证连接参数是否正确
- 尝试用命令行工具连接测试
6.2 时区问题
问题现象:出现"The server time zone value 'xxx' is unrecognized"错误
解决方案:
在JDBC URL中添加时区参数:
code复制jdbc:mysql://localhost:3306/spring?serverTimezone=UTC
6.3 依赖冲突
问题现象:启动时抛出NoSuchMethodError或ClassNotFoundException
解决方案:
- 执行
mvn dependency:tree查看依赖树 - 排除冲突的依赖版本
- 确保所有Spring模块版本一致
7. 项目扩展建议
这个基础版本可以进一步扩展:
- 密码加密:集成Spring Security的BCryptPasswordEncoder
- 连接池:改用HikariCP等高性能连接池
- 异常处理:自定义异常处理登录失败情况
- 日志记录:添加Log4j2记录登录尝试
- 单元测试:使用JUnit5编写DAO层测试
例如,改用HikariCP只需修改配置:
xml复制<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maximumPoolSize" value="10"/>
</bean>
这个项目虽然简单,但涵盖了Spring JDBC开发的核心要点。通过这个案例,可以掌握Spring配置、数据访问和基本的业务逻辑实现。对于初学者来说,理解这个案例后再学习更复杂的Spring Boot就会容易得多。