ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot+MyBatis的公交线路查询系统设计与实现

基于SpringBoot+MyBatis的公交线路查询系统设计与实现 简介一份基于Java与Spring Boot的公交线路查询系统毕业设计完整资料包面向计算机专业毕业生以及需要快速搭建管理类Web项目的学习者。系统采用B/S架构与MVC分层模式集成SpringBoot、MyBatis、MySQL、Maven、Ajax、Vue等技术栈覆盖管理员端与用户端两大角色管理员可对用户、公交线路、网站公告、友情链接进行增删改查用户注册登录后可查看线路详情、进行收藏与评论业务闭环完整能较全面地展示权限角色设计与数据库交互能力。压缩包约16.5MB内含项目源码与配套论文目录结构清晰接口分层明确可直接导入IDEA/Eclipse运行调试亦可作为毕业设计答辩或课程设计的参考蓝本。资源目前已有55人学习适合需要完整实战案例、论文素材与功能演示的开发者使用。1. 打开浏览器就能跑的公交线路查询系统是怎么组装出来的本地启动一个 SpringBoot 工程浏览器输入 localhost:8080注册账号、登录、在公交线路列表里检索线路、查看票价和首末班时间、收藏一条常坐的线路再顺手留一条评论。这一连串操作背后牵涉的是 SpringBoot 的路由分发、MyBatis 的 SQL 绑定、MySQL 的建表与索引设计以及登录会话怎么在多个接口之间保持一致。这套公交线路查询系统的毕设源码把管理员后台的增删改查和用户前台查询、收藏、评论放在同一个工程里没有微服务也没有分布式但把 B/S 应用的常规链路完整走了一遍。对正在准备 springboot 相关毕业设计、或者手里有 Java 基础但没做过完整项目的人来说这套源码加论文的结构很适合拿来做二次开发。2. 公交线路系统的表结构设计与MyBatis数据层构建2.1 为什么毕业设计选SpringBoot MyBatis而不是SSH在 springboot 框架流行之前这类校园信息管理系统常见的是 SSM 组合spring-mvc、mybatis、spring 三套配置文件互相引用光是理解 web.xml 里的监听器和 dispatch-servlet 的关系就要花不少时间。SpringBoot 把配置收敛成自动装配之后开发者的注意力从配置文件转移到业务代码上。这个项目的选型恰好是最主流的配置JDK 1.8 配合 SpringBoot 2.x数据层用 MyBatis前端页面通过 Ajax 访问后端的 JSON 接口整体是标准的 B/S 架构和 MVC 分层。MVC 在这里的分工很清晰Controller 层接收前端请求并返回 JSONService 层处理业务规则Mapper 层负责和 MySQL 交互。这个分层对毕业设计的好处是论文可以按层次写代码结构也能直接套到其他管理系统的二次开发里。MyBatis 在这个项目中主要承担两类职责一是把 SQL 写在 XML 里避免 Java 代码中拼接字符串二是利用动态 SQL 处理线路查询中的多条件组合问题后面第五章会详细说。业务上真正复杂的地方不多难点集中在用户注册登录的会话处理和线路收藏的防重复设计上。2.2 数据库六张表如何支撑线路查询业务源码配套的 MySQL 5.7 数据库一共包含六张核心业务表覆盖用户、线路、收藏、评论、公告、友情链接。其中业务主表是用户表和公交线路表收藏表和评论表通过外键关联这两张主表。下面是建表语句字段注释已经按源码中的实体类补齐CREATE TABLE bus_user ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(32) NOT NULL COMMENT 登录账号, password varchar(64) NOT NULL COMMENT 加盐后的密码, nickname varchar(32) DEFAULT NULL COMMENT 昵称, phone varchar(11) DEFAULT NULL COMMENT 手机号, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE bus_line ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 线路ID, line_no varchar(16) NOT NULL COMMENT 线路编号如K1、101路, start_station varchar(32) NOT NULL COMMENT 起点站, end_station varchar(32) NOT NULL COMMENT 终点站, first_time varchar(8) DEFAULT 06:00 COMMENT 首班时间, last_time varchar(8) DEFAULT 22:00 COMMENT 末班时间, interval_min int(4) DEFAULT 10 COMMENT 发车间隔分钟, price decimal(4,1) DEFAULT 2.0 COMMENT 票价元, route_desc text COMMENT 途经站点描述, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_line_no (line_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT公交线路表;用户表里 username 设置了唯一索引uk_username这一点在注册时用于数据库层面的账号查重。bus_line 表把首末班时间设计成 varchar 而不是 datetime原因是这里只需要显示 “06:30” 这样的时刻不需要参与日期运算字符串排序也能得到正确的时间顺序。票价用decimal(4,1)而不是 float避免浮点数在金额比较时出现的精度偏差。发车间隔和票价这两个字段第三章和第五章都会用到它们在管理员的查询页面里承担筛选条件。收藏表和评论表是关联表用户和线路是多对多关系。收藏表在 user_id 和 line_id 上建了联合唯一索引uk_user_line这个索引是防重复收藏的关键防线代码层面即使漏了判断数据库也会拒绝重复数据。评论表相对独立每次评论记录用户ID、线路ID、内容和创建时间删除线路时级联删除评论和收藏避免产生孤儿数据CREATE TABLE bus_favorite ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 用户ID, line_id int(11) NOT NULL COMMENT 线路ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_line (user_id,line_id), CONSTRAINT fk_fav_user FOREIGN KEY (user_id) REFERENCES bus_user(id) ON DELETE CASCADE, CONSTRAINT fk_fav_line FOREIGN KEY (line_id) REFERENCES bus_line(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表; CREATE TABLE bus_comment ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 评论用户, line_id int(11) NOT NULL COMMENT 线路ID, content varchar(500) NOT NULL COMMENT 评论内容, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_line_id (line_id), CONSTRAINT fk_cmt_user FOREIGN KEY (user_id) REFERENCES bus_user(id) ON DELETE CASCADE, CONSTRAINT fk_cmt_line FOREIGN KEY (line_id) REFERENCES bus_line(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表;公告表和友情链接表的字段比较简单公告表是 title、content、create_time友情链接表是 name、url、sort_order。这两个模块在管理员角色下走标准 CRUD代码模式与公交线路管理完全一致不再单独建表展示。2.3 Mapper接口与XML的映射方式MyBatis 的 Mapper 接口和 XML 文件通过 namespace 绑定。接口里定义方法XML 里写 SQL方法的参数通过#{}占位符传入。这里以线路表的新增操作为例接口定义为BusLineMapper.javapublic interface BusLineMapper { int insert(BusLine line); BusLine selectById(Long id); ListBusLine selectByCondition(Param(lineNo) String lineNo, Param(startStation) String startStation, Param(offset) int offset, Param(size) int size); }对应的 XML 在resources/mapper/BusLineMapper.xml中namespace 必须和接口全限定名一致mapper namespacecom.example.bus.mapper.BusLineMapper select idselectById parameterTypelong resultTypeBusLine SELECT id, line_no, start_station, end_station, first_time, last_time, interval_min, price, route_desc FROM bus_line WHERE id #{id} /select insert idinsert parameterTypeBusLine INSERT INTO bus_line (line_no, start_station, end_station, first_time, last_time, interval_min, price, route_desc) VALUES (#{lineNo}, #{startStation}, #{endStation}, #{firstTime}, #{lastTime}, #{intervalMin}, #{price}, #{routeDesc}) /insert /mapperParam注解在多参数方法中是必需的否则 MyBatis 无法识别参数名。resultType配置为BusLine时MyBatis 会自动把下划线字段line_no映射到实体类的lineNo属性前提是在application.properties里开启驼峰映射mybatis.configuration.map-underscore-to-camel-casetrue。没有开启的话查询结果会把line_no赋给lineNo失败返回的字段全是 null。这个配置在源码包里已经写好但如果自己重新搭建工程这一步很容易漏掉。3. 注册登录、线路查询与收藏评论的核心代码实现3.1 用户注册的账号查重与密码加密用户注册的核心不是 INSERT 语句本身而是注册之前的账号唯一性校验和密码处理。Controller 层接收前端传来的 JSONService 层先调用findByUsername查一次库存在则直接返回提示信息。这个校验在数据库层面有唯一索引做兜底即使两个请求同时进来也只有一个 INSERT 能成功RestController RequestMapping(/api) public class UserController { Autowired private UserService userService; PostMapping(/register) public Result register(RequestBody User user) { // 1. 业务校验账号非空、密码非空、长度限制 if (user.getUsername() null || user.getPassword() null) { return Result.error(账号和密码不能为空); } // 2. 唯一性校验数据库唯一索引兜底 if (userService.findByUsername(user.getUsername()) ! null) { return Result.error(注册账号不能重复); } // 3. 密码加密后入库 String encrypted Sha256Utils.encode(user.getPassword()); user.setPassword(encrypted); userService.register(user); return Result.ok(注册成功); } }这里的Sha256Utils是一个静态工具类使用 JDK 自带的MessageDigest生成 SHA-256 摘要并在密码字符串末尾拼接一个固定盐值防止简单明文入库。毕业设计阶段这么做足够安全实际项目里更常见的替代方案是引入spring-security-crypto依赖使用BCryptPasswordEncoder它能生成带随机盐的密文每次加密结果都不同碰撞更难。3.2 登录会话保持与拦截器配置登录成功后把用户信息放进 HttpSession后续接口通过拦截器判断是否放行。源码采用 Spring 的HandlerInterceptor实现统一的登录校验preHandle方法在 Controller 执行之前调用未登录时直接重定向到登录页Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login.html); return false; } return true; } }配置类里注册拦截器并指定拦截路径。静态资源不需要拦截登录和注册接口需要放行Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/user/**, /favorite/**) .excludePathPatterns(/login.html, /register.html, /api/login, /api/register, /js/**, /css/**, /images/**); } }拦截路径设计上有个值得注意的点/api/login和/api/register放行其余/api/**下的业务接口默认都是受保护资源。收藏接口放在/favorite/**路径下必须登录后携带有效 Session 才能操作避免了未登录用户直接通过接口伪造收藏请求。3.3 收藏和评论的业务闭环收藏功能是用户端的核心交互。我在实践里添加收藏时同时处理了两种状态已经收藏就取消未收藏就新增。前后端共用一个按钮返回布尔值表示当前状态。数据库的联合唯一索引uk_user_line用来兜底重复插入即使前端点击很快发出两个请求第二个请求也会被 MySQL 拒掉public boolean toggleFavorite(Long userId, Long lineId) { BusFavorite favorite favoriteMapper.selectByUserIdAndLineId(userId, lineId); if (favorite ! null) { favoriteMapper.deleteById(favorite.getId()); return false; // 取消收藏 } BusFavorite newFav new BusFavorite(); newFav.setUserId(userId); newFav.setLineId(lineId); favoriteMapper.insert(newFav); return true; // 收藏成功 }评论功能更直接前端 Post 过来一条 JSON包含 lineId 和 content后端从 Session 中取出当前登录用户的 ID补全之后再写入评论表。查询评论时需要对用户表做一次关联取出 nickname 一并返回给前端GetMapping(/comment/list) public Result listByLine(RequestParam Long lineId) { ListCommentVO list commentMapper.selectByLineId(lineId); return Result.ok(list); }CommentVO里除了评论内容、创建时间还包含用户昵称和头像地址。这个 VO 层是 MyBatis 关联查询中常用的做法SQL 里通过 LEFT JOIN 把bus_user.nickname查出来剩余字段由AutoMapping自动填充。写这段 SQL 时要注意resultType换成com.example.bus.vo.CommentVO实体类和 VO 的字段名保持一致才能自动映射成功。4. 从IDEA到Tomcat环境对齐与部署排错4.1 组件的版本匹配关系这个项目的运行环境是 JDK 1.8、Maven 3.6、MySQL 5.7、Tomcat 8.0/9.0。版本对齐是源码导入后最容易出问题的地方尤其是 SpringBoot 的版本选择。SpringBoot 3.x 强制要求 JDK 17和 JDK 1.8 不兼容所以源码里的 SpringBoot 必然落在 2.x。对于 JDK 8 来说2.7.18 是最后一个稳定版本而 MyBatis 官方提供的mybatis-spring-boot-starter选择 2.3.2 可以与之兼容parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.48/version /dependency /dependenciesMySQL 驱动版本需要特别留意。源码使用的是 MySQL 5.7配mysql-connector-java5.1.48 时驱动类是com.mysql.jdbc.Driver如果升级到 8.x 驱动驱动类名要改成com.mysql.cj.jdbc.Driver连接串也需要追加serverTimezoneAsia/Shanghai。这两种写法的混用是启动时ClassNotFoundException和时区异常的高发原因。4.2 导入IDEA后的启动关键配置拿到源码后不要急着点运行按钮先把三个环境和依赖项解决。第一确认 IDEA 中 Maven 设置指向 Maven 3.6并指定 JDK 1.8 作为 Project SDK第二在 SQLyog 或 Navicat 里创建数据库并导入项目附带的bus_system.sql第三修改application.properties里的数据库账号密码然后才能启动主类BusSystemApplication。server.port8080 spring.datasource.urljdbc:mysql://localhost:3306/bus_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password123456 spring.datasource.driver-class-namecom.mysql.jdbc.Driver mybatis.mapper-locationsclasspath:mapper/*.xml mybatis.configuration.map-underscore-to-camel-casetrue mybatis.type-aliases-packagecom.example.bus.entitymybatis.type-aliases-package配置了实体类的包路径这样 XML 里的resultType可以直接写BusLine不需要写全限定名。mapper-locations指定 XML 文件位置如果 Mapper XML 没有放在这个目录下启动时会报Invalid bound statement (not found)。打包运行时可以通过下面的命令验证工程是否能正确编译mvn clean package -DskipTests java -jar target/bus-system-0.0.1-SNAPSHOT.jar第一次启动较慢是因为 Maven 需要下载依赖。看到Started BusSystemApplication日志后浏览器访问localhost:8080/login.html验证。如果端口被占用日志会提示Port 8080 was already in use此时在 Windows 下执行netstat -ano | findstr 8080找到 PID再用taskkill /PID pid /F释放端口在 macOS 下则是lsof -i :8080和kill -9 pid。4.3 外置Tomcat部署的改造默认情况下 SpringBoot 内置 Tomcat打成 jar 包后直接java -jar运行即可。部分学校要求采用外置 Tomcat 方式部署这时需要把内置 Tomcat 的作用域改成provided并让启动类继承SpringBootServletInitializer。源码中如果已经包含这个改造直接打包成 war 放进 Tomcat 的 webapps 目录就能运行SpringBootApplication public class BusSystemApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(BusSystemApplication.class, args); } Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(BusSystemApplication.class); } }pom.xml 中要显式声明dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency改造之后访问路径会带上项目名例如http://localhost:8080/bus-system/login.html。如果前端页面里的 Ajax 请求全部写的绝对路径/api/...部署到外置 Tomcat 时会出现 404因为上下文路径变了。常见做法是在页面里用一个全局变量保存 contextPathvar ctx ${pageContext.request.contextPath};Ajax 请求统一在 URL 前拼接ctx。这个细节是外置部署最普遍的坑源码里如果注释掉了内置 Tomcat务必检查所有请求路径。5. 用MyBatis动态SQL扩展公交线路的多条件组合查询管理员的线路管理页面上查询条件往往不止一个输入线路编号、选择起点站、设置最高票价、限制发车间隔。把这些条件用 if 标签和 where 标签组合在一个select里是 MyBatis 动态 SQL 最典型的应用场景。改造前的版本通常是用字符串拼接 SQL条件一多就容易漏空格或者拼接出WHERE AND的语法错误。动态 SQL 把条件判断挪到 XML 里Java 代码不需要维护 SQL 片段select idsearchLines parameterTypemap resultTypeBusLine SELECT id, line_no, start_station, end_station, first_time, last_time, interval_min, price, route_desc FROM bus_line where if testlineNo ! null and lineNo ! AND line_no LIKE CONCAT(%, #{lineNo}, %) /if if teststartStation ! null and startStation ! AND start_station #{startStation} /if if testmaxInterval ! null AND interval_min lt; #{maxInterval} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if /where ORDER BY choose when testsortBy price price ASC /when when testsortBy interval interval_min ASC /when otherwise line_no ASC /otherwise /choose /selectwhere标签会自动去掉第一个条件前的ANDif标签的 test 表达式里! null和空字符串都要判断否则前端传一个空串过来会导致条件误判。interval_min lt; #{maxInterval}这里的lt;是 XML 转义字符直接写会被解析器当成标签的开始报错。choose和when配合sortBy参数控制排序字段优先级高于ORDER BY line_no ASC的默认排序。测试时可以在控制台开启 MyBatis SQL 日志确认动态拼接出来的语句符合预期logging.level.com.example.bus.mapperdebug当查询参数只有maxPrice3时最终执行的 SQL 是WHERE price 3 ORDER BY line_no ASC当什么都不传时where标签自动隐去执行全表查询。这套写法同样适用于公告列表、友情链接列表的分页查询把分页参数offset和size放进where后面就能复用同一个 Mapper 方法完成带条件的分页。动态 SQL 写多了以后注意把常用的条件片段抽取成sql标签比如SELECT的公共列减少重复代码同时保持 XML 文件的可读性。本文还有配套的精品资源点击获取
返回列表