在城市公共交通日益重要的今天,一套高效、精准的信息管理系统是提升运营效率与乘客满意度的关键。本系统采用成熟的J2EE技术体系,构建了一个集信息查询与业务管理于一体的综合性平台,旨在解决传统公交管理模式中信息滞后、协同困难的核心问题。
系统采用经典的三层架构模型,清晰地将数据层、业务逻辑层和表现层分离。后端使用Java语言编写核心业务逻辑,通过Servlet作为控制器处理HTTP请求,JavaBean封装实体对象和业务规则。数据持久化层使用JDBC直接连接MySQL数据库,确保了数据操作的高效性和可控性。前端展示层则基于JSP技术,结合JSTL标签库和EL表达式,动态生成页面内容,实现了业务逻辑与用户界面的有效解耦。这种MVC模式的应用,使得系统结构清晰,各模块职责分明,极大地增强了代码的可维护性和可扩展性。
数据库架构解析
系统的数据模型设计紧紧围绕公交运营的核心实体展开,共包含5张核心数据表。其中,线路信息表(bus_line)、站点信息表(bus_stop)和车辆信息表(bus_vehicle)的设计尤为关键,它们共同构成了系统数据骨架。
线路信息表(bus_line) 的设计不仅存储了线路的基本属性,还通过特定的字段设计支持了复杂的业务逻辑。其DDL定义如下:
CREATE TABLE bus_line (
line_id INT AUTO_INCREMENT PRIMARY KEY,
line_number VARCHAR(20) NOT NULL UNIQUE,
line_name VARCHAR(100) NOT NULL,
start_stop_id INT,
end_stop_id INT,
first_bus_time TIME,
last_bus_time TIME,
ticket_price DECIMAL(5,2),
total_mileage DECIMAL(6,2),
operation_status ENUM('正常', '调整', '暂停') DEFAULT '正常',
description TEXT,
FOREIGN KEY (start_stop_id) REFERENCES bus_stop(stop_id),
FOREIGN KEY (end_stop_id) REFERENCES bus_stop(stop_id)
);
该表的一个显著设计亮点是引入了 operation_status 枚举字段,用于标识线路的实时运营状态(如正常、调整、暂停)。这为后续实现动态信息提示和高级调度功能预留了扩展空间。同时,start_stop_id 和 end_stop_id 作为外键关联站点表,确保了线路起终点数据的参照完整性,避免了数据不一致的问题。
站点信息表(bus_stop) 的设计则注重地理信息的精确性和唯一性约束。
CREATE TABLE bus_stop (
stop_id INT AUTO_INCREMENT PRIMARY KEY,
stop_name VARCHAR(100) NOT NULL,
stop_code VARCHAR(10) UNIQUE,
longitude DECIMAL(9,6) NOT NULL,
latitude DECIMAL(8,6) NOT NULL,
street_address VARCHAR(200),
district VARCHAR(50)
);
该表通过 longitude 和 latitude 字段精确记录了站点的地理坐标,这是实现基于位置的智能查询(如附近站点搜索)的数据基础。stop_code 的唯一性约束为站点提供了一个稳定、不依赖于名称变化的业务标识符,便于内部管理和外部系统集成。
车辆信息表(bus_vehicle) 的设计体现了对资产生命周期管理的支持。
CREATE TABLE bus_vehicle (
vehicle_id INT AUTO_INCREMENT PRIMARY KEY,
license_plate VARCHAR(15) NOT NULL UNIQUE,
vehicle_model VARCHAR(50),
capacity INT,
purchase_date DATE,
maintenance_due_date DATE,
current_status ENUM('运营中', '维修中', '停运') DEFAULT '运营中',
assigned_line_id INT,
FOREIGN KEY (assigned_line_id) REFERENCES bus_line(line_id)
);
maintenance_due_date 字段用于跟踪车辆的下次保养日期,结合 current_status 字段,可以轻松实现车辆保养预警和状态统计,为科学的运维管理提供了数据支持。assigned_line_id 外键则建立了车辆与运营线路之间的动态关联。
核心功能实现剖析
1. 多条件公交线路查询
乘客端的核心功能是线路查询。系统提供了灵活的查询方式,用户可以通过线路编号、线路名称或途经站点进行搜索。后端通过一个Servlet来处理复杂的查询逻辑,该Servlet会动态构建SQL查询语句。
// RouteQueryServlet.java 中的部分代码
public class RouteQueryServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String lineNumber = request.getParameter("lineNumber");
String lineName = request.getParameter("lineName");
String stopName = request.getParameter("stopName");
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DatabaseUtil.getConnection();
StringBuilder sql = new StringBuilder(
"SELECT DISTINCT bl.* FROM bus_line bl ");
ArrayList<Object> params = new ArrayList<>();
if (stopName != null && !stopName.trim().isEmpty()) {
sql.append(" JOIN line_stop_relation lsr ON bl.line_id = lsr.line_id ");
sql.append(" JOIN bus_stop bs ON lsr.stop_id = bs.stop_id WHERE bs.stop_name LIKE ?");
params.add("%" + stopName + "%");
} else if (lineNumber != null && !lineNumber.trim().isEmpty()) {
sql.append(" WHERE bl.line_number LIKE ?");
params.add("%" + lineNumber + "%");
} else if (lineName != null && !lineName.trim().isEmpty()) {
sql.append(" WHERE bl.line_name LIKE ?");
params.add("%" + lineName + "%");
}
pstmt = conn.prepareStatement(sql.toString());
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(i + 1, params.get(i));
}
rs = pstmt.executeQuery();
List<BusLine> lines = new ArrayList<>();
while (rs.next()) {
BusLine line = new BusLine();
// ... 设置线路对象属性
lines.add(line);
}
request.setAttribute("searchResults", lines);
request.getRequestDispatcher("/routeResult.jsp").forward(request, response);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DatabaseUtil.close(conn, pstmt, rs);
}
}
}
这段代码展示了如何根据不同的查询条件动态拼接SQL,并使用 PreparedStatement 来防止SQL注入攻击,确保了查询的安全性和灵活性。查询结果被封装成 BusLine 对象列表,并传递给JSP页面进行渲染。

2. 线路详情与站点序列展示
当用户点击某条具体线路时,系统会展示该线路的详细信息,包括首末班车时间、票价、总里程以及按顺序排列的所有途经站点。这需要联合查询线路表、站点表以及它们之间的关联表。
// 在RouteDetailServlet中获取线路详情和站点序列
String lineId = request.getParameter("id");
String sql = "SELECT bl.*, bs.stop_id, bs.stop_name, bs.longitude, bs.latitude, lsr.stop_sequence " +
"FROM bus_line bl " +
"LEFT JOIN line_stop_relation lsr ON bl.line_id = lsr.line_id " +
"LEFT JOIN bus_stop bs ON lsr.stop_id = bs.stop_id " +
"WHERE bl.line_id = ? " +
"ORDER BY lsr.stop_sequence ASC";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, Integer.parseInt(lineId));
rs = pstmt.executeQuery();
BusLine line = null;
List<BusStop> stops = new ArrayList<>();
while (rs.next()) {
if (line == null) {
line = new BusLine();
line.setLineId(rs.getInt("line_id"));
line.setLineNumber(rs.getString("line_number"));
// ... 设置其他线路属性
}
if (rs.getInt("stop_id") != 0) { // 确保站点信息存在
BusStop stop = new BusStop();
stop.setStopId(rs.getInt("stop_id"));
stop.setStopName(rs.getString("stop_name"));
stop.setLongitude(rs.getDouble("longitude"));
stop.setLatitude(rs.getDouble("latitude"));
stops.add(stop);
}
}
request.setAttribute("lineDetail", line);
request.setAttribute("stopSequence", stops);
此段代码通过一次SQL查询,利用左连接(LEFT JOIN)同时获取了线路的基本信息和其关联的所有站点,并按照 stop_sequence 字段排序,高效地完成了数据聚合。
3. 管理员对站点信息的增删改查(CRUD)
后台管理功能的核心是对基础数据的维护。以站点信息管理为例,系统提供了完整的CRUD操作界面。新增或修改站点信息时,数据通过POST请求提交至相应的Servlet进行处理。

数据添加与更新的Servlet处理逻辑:
// StopManageServlet.java 中的doPost方法
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
String stopIdStr = request.getParameter("stopId");
String stopName = request.getParameter("stopName");
String stopCode = request.getParameter("stopCode");
String longitude = request.getParameter("longitude");
String latitude = request.getParameter("latitude");
String address = request.getParameter("streetAddress");
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DatabaseUtil.getConnection();
String sql = null;
if ("add".equals(action)) {
sql = "INSERT INTO bus_stop (stop_name, stop_code, longitude, latitude, street_address) VALUES (?, ?, ?, ?, ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, stopName);
pstmt.setString(2, stopCode);
pstmt.setDouble(3, Double.parseDouble(longitude));
pstmt.setDouble(4, Double.parseDouble(latitude));
pstmt.setString(5, address);
} else if ("update".equals(action)) {
sql = "UPDATE bus_stop SET stop_name=?, stop_code=?, longitude=?, latitude=?, street_address=? WHERE stop_id=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, stopName);
pstmt.setString(2, stopCode);
pstmt.setDouble(3, Double.parseDouble(longitude));
pstmt.setDouble(4, Double.parseDouble(latitude));
pstmt.setString(5, address);
pstmt.setInt(6, Integer.parseInt(stopIdStr));
}
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
// 操作成功,重定向到站点列表页并提示成功信息
response.sendRedirect("stopManage?message=success");
} else {
// 操作失败处理
request.setAttribute("errorMessage", "操作失败,请重试。");
request.getRequestDispatcher("/admin/stopForm.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
request.setAttribute("errorMessage", "数据库错误: " + e.getMessage());
request.getRequestDispatcher("/admin/stopForm.jsp").forward(request, response);
} finally {
DatabaseUtil.close(conn, pstmt, null);
}
}
该Servlet根据传入的 action 参数判断是执行添加还是更新操作,动态构建SQL语句,并进行了必要的异常处理与用户反馈。
数据列表展示与删除操作:
管理页面的列表展示通常通过JSP页面调用JSTL标签库循环输出结果集来实现。删除操作则通过一个确认对话框后,发送GET请求到Servlet执行。
<%-- 在stopList.jsp中使用JSTL展示站点列表 --%>
<table class="data-table">
<tr>
<th>站点编号</th>
<th>站点名称</th>
<th>经度</th>
<th>纬度</th>
<th>操作</th>
</tr>
<c:forEach var="stop" items="${stopList}">
<tr>
<td>${stop.stopCode}</td>
<td>${stop.stopName}</td>
<td>${stop.longitude}</td>
<td>${stop.latitude}</td>
<td>
<a href="stopManage?action=edit&id=${stop.stopId}">编辑</a> |
<a href="javascript:void(0);" onclick="confirmDelete(${stop.stopId})">删除</a>
</td>
</tr>
</c:forEach>
</table>
<script>
function confirmDelete(stopId) {
if (confirm('确定要删除这个站点吗?此操作不可恢复!')) {
window.location.href = 'stopManage?action=delete&id=' + stopId;
}
}
</script>
// StopManageServlet.java 中处理删除的doGet部分
if ("delete".equals(action)) {
String id = request.getParameter("id");
try {
// 首先检查该站点是否被线路关联
String checkSql = "SELECT COUNT(*) FROM line_stop_relation WHERE stop_id = ?";
pstmt = conn.prepareStatement(checkSql);
pstmt.setInt(1, Integer.parseInt(id));
rs = pstmt.executeQuery();
if (rs.next() && rs.getInt(1) > 0) {
// 如果被关联,则不允许删除,提示用户
response.sendRedirect("stopManage?message=delete_failed_related");
return;
}
// 如果没有关联,则执行删除
String deleteSql = "DELETE FROM bus_stop WHERE stop_id = ?";
pstmt = conn.prepareStatement(deleteSql);
pstmt.setInt(1, Integer.parseInt(id));
int rows = pstmt.executeUpdate();
if (rows > 0) {
response.sendRedirect("stopManage?message=delete_success");
}
} catch (SQLException e) {
e.printStackTrace();
response.sendRedirect("stopManage?message=delete_error");
}
}
删除操作增加了安全检查,防止误删被线路引用的站点,保证了数据的参照完整性。这种设计体现了生产级应用对数据安全性的考量。
4. 车辆信息动态分配与管理
车辆管理模块允许管理员将车辆动态分配到不同线路,并跟踪其状态(如运营中、维修中)。

车辆分配功能的后端逻辑涉及更新 bus_vehicle 表中的 assigned_line_id 字段。
// VehicleAssignServlet.java
String vehicleId = request.getParameter("vehicleId");
String newLineId = request.getParameter("lineId"); // 可以为空或0,表示未分配
String sql = "UPDATE bus_vehicle SET assigned_line_id = ? WHERE vehicle_id = ?";
pstmt = conn.prepareStatement(sql);
if (newLineId == null || newLineId.isEmpty() || "0".equals(newLineId)) {
pstmt.setNull(1, Types.INTEGER); // 设置为NULL
} else {
pstmt.setInt(1, Integer.parseInt(newLineId));
}
pstmt.setInt(2, Integer.parseInt(vehicleId));
int updated = pstmt.executeUpdate();
此代码片段展示了如何使用 PreparedStatement 的 setNull 方法来处理可能为空的分配线路ID,这是一种严谨的数据库操作实践。
实体模型与业务对象
系统的核心业务逻辑通过一系列JavaBean(实体类)来承载。这些类通常与数据库表结构相对应,并包含属性的getter和setter方法。
// BusLine.java 实体类
public class BusLine {
private int lineId;
private String lineNumber;
private String lineName;
private int startStopId;
private int endStopId;
private Time firstBusTime;
private Time lastBusTime;
private BigDecimal ticketPrice;
private BigDecimal totalMileage;
private String operationStatus;
private String description;
// 关联对象:途径站点列表(非直接数据库映射)
private List<BusStop> stopList;
// 无参构造器、全参构造器、getter和setter方法...
public int getLineId() { return lineId; }
public void setLineId(int lineId) { this.lineId = lineId; }
// ... 其他getter和setter
}
// BusStop.java 实体类
public class BusStop {
private int stopId;
private String stopName;
private String stopCode;
private double longitude;
private double latitude;
private String streetAddress;
private String district;
// 构造器、getter和setter...
}
这些实体类不仅在数据传递中起作用(如从Servlet到JSP),也使得业务逻辑的编写更加面向对象,提高了代码的可读性和可维护性。
系统优化与未来展望
尽管当前系统已经实现了核心的查询与管理功能,但从技术发展和用户体验的角度,仍有多个方向可以深化和拓展。
引入缓存机制提升性能:对于线路信息、站点列表等变动不频繁的静态数据,可以引入Redis等内存数据库作为缓存层。例如,将热门查询线路的结果缓存起来,设置合理的过期时间,可以极大减轻数据库压力,提高查询响应速度。实现上,可以在Servlet中先查询缓存,命中则直接返回,未命中再查询数据库并写入缓存。
实现实时公交位置追踪:这是提升系统实用性的关键功能。需要为车辆加装GPS设备,后端建立WebSocket服务或使用长轮询技术,持续接收车辆上报的位置数据,并更新到数据库的特定表(如
vehicle_real_time_location)中。前端地图页面(可集成百度地图或高德地图API)定时向服务器请求车辆位置,动态更新地图标记。开发响应式前端界面:当前系统前端界面主要针对桌面端。利用HTML5和CSS3媒体查询技术,对现有JSP页面进行重构,使其能够自适应不同尺寸的屏幕(从PC到手机),为移动端用户提供更好的查询体验。
4