基于JSP的公交线路查询与管理系统 - 源码深度解析

JavaJavaScriptHTMLCSSMySQLJSP+Servlet
2026-02-284 浏览

文章摘要

本项目是一款基于JSP技术栈构建的公交线路查询与管理系统,旨在解决城市公共交通信息不透明、管理效率低下的核心痛点。系统通过整合公交线路、站点及实时车辆信息,为乘客提供便捷的查询服务,同时为公交运营单位提供高效的在线管理工具,显著提升了信息服务的准确性与管理操作的协同性。 在技术实现上,系统采用经典...

在城市公共交通日益重要的今天,一套高效、精准的信息管理系统是提升运营效率与乘客满意度的关键。本系统采用成熟的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_idend_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)
);

该表通过 longitudelatitude 字段精确记录了站点的地理坐标,这是实现基于位置的智能查询(如附近站点搜索)的数据基础。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();

此代码片段展示了如何使用 PreparedStatementsetNull 方法来处理可能为空的分配线路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),也使得业务逻辑的编写更加面向对象,提高了代码的可读性和可维护性。

系统优化与未来展望

尽管当前系统已经实现了核心的查询与管理功能,但从技术发展和用户体验的角度,仍有多个方向可以深化和拓展。

  1. 引入缓存机制提升性能:对于线路信息、站点列表等变动不频繁的静态数据,可以引入Redis等内存数据库作为缓存层。例如,将热门查询线路的结果缓存起来,设置合理的过期时间,可以极大减轻数据库压力,提高查询响应速度。实现上,可以在Servlet中先查询缓存,命中则直接返回,未命中再查询数据库并写入缓存。

  2. 实现实时公交位置追踪:这是提升系统实用性的关键功能。需要为车辆加装GPS设备,后端建立WebSocket服务或使用长轮询技术,持续接收车辆上报的位置数据,并更新到数据库的特定表(如vehicle_real_time_location)中。前端地图页面(可集成百度地图或高德地图API)定时向服务器请求车辆位置,动态更新地图标记。

  3. 开发响应式前端界面:当前系统前端界面主要针对桌面端。利用HTML5和CSS3媒体查询技术,对现有JSP页面进行重构,使其能够自适应不同尺寸的屏幕(从PC到手机),为移动端用户提供更好的查询体验。

4

本文关键词
JSP公交线路查询管理系统源码解析数据库架构

上下篇

上一篇
没有更多文章
下一篇
没有更多文章