1. 项目概述
停车场管理系统是现代化城市管理中不可或缺的基础设施。这个基于Java Swing和SQL Server开发的系统,完美解决了传统停车场管理效率低下、数据统计困难等问题。作为一名有多年Java开发经验的工程师,我在实际项目中多次遇到需要定制化停车场管理系统的需求,这套方案经过多次迭代已经相当成熟。
系统采用经典的MVC架构设计,前端使用Swing构建用户界面,后端通过JDBC连接SQL Server数据库。整个系统包含15个核心功能模块,从基础的车辆出入场管理,到复杂的计费标准设置、用户信息维护等一应俱全。特别值得一提的是,系统实现了双重权限控制(用户和管理员),这在同类开源项目中并不多见。
2. 环境配置与项目搭建
2.1 开发环境准备
工欲善其事,必先利其器。在开始编码前,我们需要配置好开发环境:
-
JDK 1.8:这是长期支持版本,稳定性最好。建议从Oracle官网下载安装包,配置JAVA_HOME环境变量时要注意路径不能有中文或空格。
-
Eclipse IDE:推荐使用Eclipse 2020-06版本,这个版本对Java 8的支持最完善。安装时选择"Eclipse IDE for Enterprise Java Developers"版本,它自带了必要的Java EE工具。
-
SQL Server 2008:虽然现在已经有更新的版本,但2008版在中小型项目中仍然表现稳定。安装时要注意选择混合身份验证模式,并记住sa密码。
提示:如果使用Windows身份验证,需要在JDBC连接字符串中配置integratedSecurity=true,并拷贝sqljdbc_auth.dll到Java的bin目录。
2.2 数据库配置
数据库设计是系统的核心,我们的停车场管理系统需要以下关键表:
sql复制CREATE TABLE users (
cardid INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
cardtype VARCHAR(20),
carid INT,
tel VARCHAR(20),
overage INT,
userstype VARCHAR(20) CHECK (userstype IN ('普通用户','管理员'))
);
CREATE TABLE parking_record (
recordid INT IDENTITY(1,1) PRIMARY KEY,
cardid INT FOREIGN KEY REFERENCES users(cardid),
entry_time DATETIME NOT NULL,
exit_time DATETIME,
fee INT,
status VARCHAR(10) CHECK (status IN ('在场','已离场'))
);
CREATE TABLE charger (
id INT IDENTITY(1,1) PRIMARY KEY,
cardtype VARCHAR(20),
stationtype VARCHAR(20),
charge INT
);
在实际部署时,建议为cardid和carid字段建立索引,可以显著提高查询速度:
sql复制CREATE INDEX idx_users_cardid ON users(cardid);
CREATE INDEX idx_users_carid ON users(carid);
2.3 项目结构说明
标准的MVC项目结构如下:
code复制ParkingSystem/
├── src/
│ ├── whsdu/
│ │ ├── se/
│ │ │ ├── DAO/ # 数据模型
│ │ │ ├── DAL/ # 数据访问层
│ │ │ ├── UI/ # 用户界面
│ │ │ └── Util/ # 工具类
├── lib/ # 依赖库
└── res/ # 资源文件
关键依赖库:
- sqljdbc4.jar:SQL Server的JDBC驱动
- jcalendar-1.4.jar:日期选择组件
3. 核心功能实现
3.1 登录模块设计
登录界面是系统的门户,我们采用Swing的JFrame实现。LoginFrame类的主要设计要点:
- 布局管理:使用BorderLayout作为主布局,中部放置输入面板,底部放置按钮面板
- 事件处理:为登录按钮添加ActionListener,验证通过后跳转主界面
- 密码安全:使用JPasswordField并设置回显字符为'*'
java复制// 密码输入框配置
password = new JPasswordField(20);
password.setEchoChar('*');
password.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
login.doClick(); // 回车触发登录
}
}
});
验证逻辑中特别要注意SQL注入防护:
java复制public static users check(String name, String pwd) {
String sql = "SELECT * FROM users WHERE name=? AND password=?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, name);
pstmt.setString(2, pwd);
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
// 结果集映射到users对象
}
} catch(SQLException e) {
e.printStackTrace();
}
return null;
}
3.2 车辆出入场管理
出入场管理是系统的核心业务,主要涉及以下技术点:
- 入场记录:
java复制public boolean addEntryRecord(int cardid) {
String sql = "INSERT INTO parking_record(cardid, entry_time, status) "
+ "VALUES(?, GETDATE(), '在场')";
// 执行SQL并更新车位状态
}
- 出场计费:
java复制public int calculateFee(int cardid) {
// 1. 查询入场时间
String sql1 = "SELECT entry_time FROM parking_record "
+ "WHERE cardid=? AND status='在场'";
// 2. 获取用户类型对应的费率
String sql2 = "SELECT charge FROM charger c "
+ "JOIN users u ON c.cardtype=u.cardtype "
+ "WHERE u.cardid=?";
// 3. 计算费用 = (当前时间 - 入场时间) * 费率
// 4. 更新记录状态和费用
}
注意:时间计算要使用Java 8的Duration类,避免手动计算带来的时区问题:
java复制Duration duration = Duration.between(entryTime, exitTime); long hours = duration.toHours();
3.3 计费标准管理
计费策略采用策略模式设计,支持不同车型、不同时段的差异化定价:
java复制public interface ChargingStrategy {
int calculateFee(LocalDateTime entry, LocalDateTime exit);
}
// 普通时段计费
class NormalStrategy implements ChargingStrategy {
private int hourlyRate;
public int calculateFee(LocalDateTime entry, LocalDateTime exit) {
// 实现普通计费逻辑
}
}
// 夜间时段计费
class NightStrategy implements ChargingStrategy {
private int nightlyRate;
public int calculateFee(LocalDateTime entry, LocalDateTime exit) {
// 实现夜间计费逻辑
}
}
在管理员界面中,可以通过组合框动态切换计费策略:
java复制JComboBox<String> strategyBox = new JComboBox<>(new String[]{"普通","夜间"});
strategyBox.addActionListener(e -> {
String selected = (String)strategyBox.getSelectedItem();
switch(selected) {
case "普通":
currentStrategy = new NormalStrategy(5); // 5元/小时
break;
case "夜间":
currentStrategy = new NightStrategy(3); // 3元/小时
break;
}
});
4. 数据库操作优化
4.1 使用连接池
频繁创建数据库连接会严重影响性能。我们采用HikariCP连接池:
java复制public class DataSource {
private static HikariConfig config = new HikariConfig();
private static HikariDataSource ds;
static {
config.setJdbcUrl("jdbc:sqlserver://localhost:1433;databaseName=ParkingDB");
config.setUsername("sa");
config.setPassword("yourpassword");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
ds = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
4.2 事务管理
出入场操作需要保证数据一致性:
java复制public boolean processExit(int cardid) {
Connection conn = null;
try {
conn = DataSource.getConnection();
conn.setAutoCommit(false);
// 1. 计算费用
int fee = calculateFee(cardid, conn);
// 2. 更新用户余额
updateBalance(cardid, fee, conn);
// 3. 更新停车记录
updateRecord(cardid, fee, conn);
conn.commit();
return true;
} catch(SQLException e) {
if(conn != null) {
conn.rollback();
}
return false;
} finally {
if(conn != null) {
conn.close();
}
}
}
5. 界面设计技巧
5.1 使用JTabbedPane组织功能
主界面采用选项卡式布局,提升用户体验:
java复制public class MdiFrame extends JFrame {
private JTabbedPane tabbedPane;
public MdiFrame() {
tabbedPane = new JTabbedPane();
// 添加各功能面板
tabbedPane.addTab("车辆入场", new EntryPanel());
tabbedPane.addTab("车辆出场", new ExitPanel());
tabbedPane.addTab("计费标准", new ChargePanel());
this.add(tabbedPane);
}
}
5.2 表格数据展示
使用JTable展示停车记录时,要自定义TableModel实现数据绑定:
java复制public class ParkingRecordModel extends AbstractTableModel {
private List<ParkingRecord> data;
private String[] columnNames = {"记录ID", "卡号", "入场时间", "出场时间", "费用"};
// 实现必要的抽象方法
public int getRowCount() { return data.size(); }
public int getColumnCount() { return columnNames.length; }
public Object getValueAt(int row, int col) {
ParkingRecord record = data.get(row);
switch(col) {
case 0: return record.getRecordid();
case 1: return record.getCardid();
case 2: return record.getEntryTime();
case 3: return record.getExitTime();
case 4: return record.getFee();
default: return null;
}
}
// 刷新数据
public void refresh() {
data = ParkingDAO.getAllRecords();
fireTableDataChanged();
}
}
6. 常见问题与解决方案
6.1 中文乱码问题
SQL Server连接字符串需要指定字符集:
java复制String url = "jdbc:sqlserver://localhost:1433;databaseName=ParkingDB;"
+ "useUnicode=true&characterEncoding=UTF-8";
6.2 日期时间处理
建议统一使用Java 8的LocalDateTime:
java复制// 数据库读写
pstmt.setTimestamp(1, Timestamp.valueOf(localDateTime));
rs.getTimestamp("column").toLocalDateTime();
// 界面显示
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String formatted = localDateTime.format(formatter);
6.3 界面卡顿优化
长时间数据库操作应该在SwingWorker中执行:
java复制new SwingWorker<List<ParkingRecord>, Void>() {
protected List<ParkingRecord> doInBackground() throws Exception {
return ParkingDAO.getRecentRecords();
}
protected void done() {
try {
List<ParkingRecord> records = get();
tableModel.setData(records);
} catch(Exception e) {
JOptionPane.showMessageDialog(null, "加载数据失败");
}
}
}.execute();
7. 项目扩展建议
- 车牌识别集成:通过OpenCV实现车牌自动识别,替代手动输入
- 移动端支持:开发微信小程序,支持远程查询和缴费
- 数据统计分析:使用JFreeChart生成停车高峰时段统计图
- 云部署方案:将SQL Server迁移到Azure云数据库
我在实际部署中发现,系统性能瓶颈主要在数据库IO上。对于大型停车场,建议:
- 将频繁查询的表(如parking_record)做分区
- 考虑使用Redis缓存热点数据
- 定期归档历史记录到单独的数据库
这个项目最值得骄傲的设计是采用了清晰的层次划分,使得后续维护和功能扩展变得非常容易。比如要添加一个新的计费策略,只需要实现ChargingStrategy接口,而不需要修改现有业务逻辑。