拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java毕业设计求职招聘系统:SQL表结构设计与Spring Boot部署实战

简介面向毕业设计场景的求职招聘系统 Java 源码包适合计算机相关专业学生完成课程设计或毕设项目时参考。项目包含完整后端业务代码、配置文件、数据库建表脚本与说明文档涵盖招聘信息发布、职位检索、简历投递等常见功能模块可作为从零搭建类似系统的骨架。压缩包共 54 个文件以 41 个 Java 源文件为主辅以 XML、YAML、Properties 等配置类文件另含 pom.xml 和 Maven 包装器可通过命令行快速构建启动SQL 文件提供数据库初始化脚本MD 文档用于环境搭建与使用指引。整个包仅 93KB轻量紧凑便于下载与本地部署。目前已有 168 人学习下载。对需要快速理解分层架构、数据库表设计以及前后端数据交互方式的读者来说这份源码能直接导入 IDE 运行同时可根据自身需求修改扩展节省从零编写的时间。1. 一个zip包里的求职招聘系统从哪开始看拿到一个名叫JAVA毕业设计求职招聘系统源码sql文件.zip的压缩包多数人的第一反应是打开Controller看代码但真正决定这套求职招聘系统能跑多远的是那个.sql文件。表结构决定功能边界能不能按薪资筛选、能不能防重复投递、能不能延展到面试日程管理全在字段、索引和初始化数据里。下面按数据模型、Java分层、SQL导入与部署、安全与扩展这条顺序把一套可运行的求职招聘系统拆到能让它在本地浏览器里当场跑起来的程度适合正在做Java毕设、或需要快速搭招聘类后台的工程师。2. SQL文件的表结构拆解四张实体表与两张业务表2.1 打开SQL后先看哪张表用户表与角色字段打开SQL文件先扫一眼所有CREATE TABLE语句。求职招聘系统的数据模型通常围绕求职者、招聘者发布职位、投递简历、平台撮合展开核心表不会超过六张。表名职责关键外键t_user登录账号与角色无t_company企业信息company_id 关联 t_user.idt_job职位发布company_id 关联 t_company.idt_resume简历详情user_id 关联 t_user.idt_application投递记录user_id job_id 双外键t_favorite职位收藏user_id job_id 双外键先看t_user因为整个系统的权限控制从这里起步。一份典型的建表语句长这样CREATE TABLE t_user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码MD5/BCrypt密文, role tinyint(4) NOT NULL DEFAULT 0 COMMENT 0求职者 1招聘者 2管理员, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;两个容易被忽略的细节。第一role用tinyint而不用varchar拦截器里做Integer.compare(user.getRole(), 2)比字符串比较快也不会出现user和User并存导致权限分叉的脏数据。第二password列宽给到100MD5是32位、BCrypt是60位varchar(100)意味着后期从MD5升级到BCrypt不用改表结构。SQL文件怎么查看这类细节命令行执行mysql -u root -p -e USE db_job; SHOW CREATE TABLE t_user\G;比在DBeaver里看图形模式快得多。2.2 职位表与投递表的边界设计职位表是招聘者的核心实体设计时最容易踩的坑是把薪资存成字符串。下面这种两个整型字段的写法更利于检索筛选CREATE TABLE t_job ( id int(11) NOT NULL AUTO_INCREMENT, company_id int(11) NOT NULL COMMENT 关联 t_company.id, job_name varchar(100) NOT NULL, salary_min int(6) NOT NULL DEFAULT 0 COMMENT 月薪下限单位元, salary_max int(6) NOT NULL DEFAULT 0 COMMENT 月薪上限单位元, location varchar(100) DEFAULT NULL, requirement text COMMENT 职位描述学历/经验/技能, publish_time datetime DEFAULT CURRENT_TIMESTAMP, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1上架 0下架, PRIMARY KEY (id), KEY idx_company_id (company_id), KEY idx_status_time (status, publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT职位发布表;薪资用salary_min/salary_max两个int字段筛选时写WHERE salary_min 8000 AND salary_max 20000走索引的范围扫描如果拼成salary_range varchar(50)存8k-15k就只能LIKE模糊匹配数据量过万之后响应时间明显拉长。idx_status_time复合索引被很多毕设漏掉首页默认按status1过滤再按publish_time DESC排序没有这个索引就会出现filesort慢查询。投递记录表是整套系统的状态机核心CREATE TABLE t_application ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 求职者关联 t_user.id, job_id int(11) NOT NULL COMMENT 职位关联 t_job.id, resume_id int(11) DEFAULT NULL COMMENT 投递时用的简历, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1投递 2被查看 3面试 4拒绝 5入职 0取消, apply_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_job (user_id, job_id), KEY idx_status_time (status, apply_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT投递记录表;uk_user_job唯一索引从数据库层面封死了同一个人投同一职位两次的脏数据Java层即使忘了判重插入也会报Duplicate entry。拿到手的SQL文件如果没有这条唯一约束建议手动补一条ALTER TABLE t_application ADD UNIQUE INDEX uk_user_job (user_id, job_id);。ON UPDATE CURRENT_TIMESTAMP让状态变更时间自动刷新Service层少写一个setter。2.3 初始化数据、字符集与索引取舍一套能直接演示的SQL文件还要带初始化数据下面这种INSERT在毕设项目里很常见INSERT INTO t_user (id, username, password, role, phone) VALUES (1, admin, MD5(123456), 2, 13800000000), (2, jobseeker01, MD5(123456), 0, 13900000000), (3, hr01, MD5(123456), 1, 13700000000); INSERT INTO t_company (id, company_name, industry, address, user_id) VALUES (1, 星辰科技, 互联网, 北京市海淀区, 3);这份数据直接决定浏览器打开后的第一屏效果。如果SQL文件只有表结构没有INSERT页面就是空的。导入后建议先跑一条LEFT JOIN验证关联是否打通SELECT u.username, u.role, c.company_name FROM t_user u LEFT JOIN t_company c ON u.id c.user_id;字符集是个隐形坑。老项目喜欢DEFAULT CHARSETutf8但MySQL的utf8是3字节实现存emoji或生僻字会报Incorrect string value简历里又容易混入特殊字符建议统一改成utf8mb4。已经建好的库可以用ALTER TABLE t_user CHARSETutf8mb4, MODIFY username varchar(50) CHARACTER SET utf8mb4;逐表调整。索引取舍上基础索引必须有但别每个字段都加INSERT和UPDATE时索引列的更新成本会叠加复合索引等真实查询模式出现后再补。3. Java源码结构Controller-Service-Mapper三层怎么组织3.1 先看配置文件和Controller的请求入口解压zip后先看pom.xml确认技术栈。常见做法分两种Spring Boot单体应用用内嵌Tomcatmvn spring-boot:run就能跑传统SSM项目打成war扔进外置Tomcat。看依赖里的parent声明就能区分Spring Boot的pom会有一段类似这样的内容parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent确认是Spring Boot后打开src/main/resources/application.yml看数据库连接配置spring: datasource: url: jdbc:mysql://localhost:3306/db_job?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai不能省。MySQL 8默认时区与JVM不一致时插入的create_time会差8个小时本地开发一般直接配Asia/Shanghai。characterEncodingutf8在这里是连接串参数MySQL驱动会按UTF-8协商和表结构的utf8mb4不冲突。Controller层能看出代码风格。整洁的写法是请求映射、参数校验、结果封装放Controller业务判断下沉到Service。如果代码里Controller直接操作数据库那后续维护成本会很高。3.2 投递简历这条链路的代码实现用投递简历这个动作串联三层。先看ControllerRestController RequestMapping(/api/apply) public class ApplicationController { Autowired private ApplicationService applicationService; PostMapping public ResultString create(RequestBody ApplyDTO dto, HttpSession session) { User user (User) session.getAttribute(loginUser); if (user null) { return Result.error(401, 请先登录); } return applicationService.apply(user.getId(), dto); } }Service层对应的实现Service public class ApplicationServiceImpl implements ApplicationService { Override Transactional(rollbackFor Exception.class) public ResultString apply(Integer userId, ApplyDTO dto) { Job job jobMapper.selectById(dto.getJobId()); if (job null || job.getStatus() ! 1) { return Result.error(职位不存在或已下架); } Resume resume resumeMapper.selectByUserId(userId); if (resume null) { return Result.error(请先完善简历); } Integer count applicationMapper.countByUserAndJob(userId, dto.getJobId()); if (count ! null count 0) { return Result.error(您已投递过该职位); } Application application new Application(); application.setUserId(userId); application.setJobId(dto.getJobId()); application.setResumeId(resume.getId()); application.setStatus(1); applicationMapper.insert(application); return Result.success(投递成功); } }参数说明Transactional(rollbackFor Exception.class)锁住整个投递过程Spring的Transactional默认只回滚RuntimeException遇到受检异常不会回滚所以显式声明更安全。job.getStatus() ! 1对应SQL文件里status字段的语义。重复投递检查依赖代码里的count查询加表上的uk_user_job唯一索引双重保护高并发下两个请求同时通过count检查的场景靠数据库兜底。Result泛型统一包装返回结果前端只处理一套JSON结构。Mapper接口和XML的对应关系public interface ApplicationMapper { int countByUserAndJob(Param(userId) Integer userId, Param(jobId) Integer jobId); }select idcountByUserAndJob resultTypeint SELECT COUNT(*) FROM t_application WHERE user_id #{userId} AND job_id #{jobId} /select#{userId}是预编译占位符对应JDBC的PreparedStatement参数绑定不是字符串拼接。如果SQL文件里的表名是user、apply这类MySQL保留字XML里要加反引号转义否则直接报语法错误。3.3 登录拦截与角色权限求职招聘系统一般有三个角色求职者、招聘者、管理员。求职者搜索职位投简历招聘者发布职位查收简历管理员做审核与统计。代码上推荐全局拦截器做登录校验Controller方法上用注解区分角色。定义一个角色注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { int value() default 0; }拦截器实现Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getServletContext().getContextPath() /login.html); return false; } RequireRole role ((HandlerMethod) handler).getMethodAnnotation(RequireRole.class); if (role ! null user.getRole() ! role.value()) { response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:403,\msg\:\无权限\}); return false; } return true; } }先判断handler是否为HandlerMethod再取注解静态资源请求不会误伤。注册时排除登录和注册接口Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register); } }3.4 Mapper的动态SQL写法职位搜索职位搜索通常要同时满足关键词、城市、薪资三个条件全部写死会导致XML里每个方法都是一套新组合。用MyBatis动态SQL更干净select idsearchJobs resultTypecom.example.entity.Job SELECT id, job_name, salary_min, salary_max, location FROM t_job where if testkeyword ! null and keyword ! AND job_name LIKE CONCAT(%, #{keyword}, %) /if if testlocation ! null and location ! AND location #{location} /if if testminSalary ! null AND salary_max gt; #{minSalary} /if /where ORDER BY publish_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签自动去掉第一个多余的AND避免拼出WHERE AND job_name...的语法错误。LIKE CONCAT拼接时不要把%直接写进参数否则空值会变成%%匹配全部。gt;是XML里的转义写法裸写会导致XML解析失败。分页参数#{offset}, #{pageSize}写起来直观项目再大一点可以引入PageHelper或MyBatis-Plus的分页插件。4. SQL文件导入与项目部署从zip到浏览器能打开4.1 导入前的环境检查JDK、MySQL与sql文件目录先在命令行确认三件事JDK、MySQL、Maven是否到位。java -version mvn -v mysql --version如果java -version显示的是JRE而不是JDKSpring Boot项目在mvn编译时会找不到javac直接报错。mvn -v报错但java -version正常通常是JAVA_HOME环境变量没配到JDK路径需要在系统环境变量里补上JAVA_HOME并追加到PATH。sql文件如果很大几十万行INSERT用记事本打开会卡死推荐用VS Code或Notepad只看结构时执行head -50 db_job.sql更快。4.2 命令行导入SQL文件mysql db_job.sql全流程假设SQL文件名叫db_job.sql在解压目录执行mysql -u root -p db_job.sql回车后输入MySQL密码。如果SQL文件里没有CREATE DATABASE语句需要手动建库CREATE DATABASE IF NOT EXISTS db_job DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE db_job; SOURCE /path/to/db_job.sql;几个常见注意点是shell重定向不是MySQL命令MySQL 8默认认证插件是caching_sha2_password如果驱动还是老的com.mysql.jdbc.Driver连接会报Public Key Retrieval is not allowed改成com.mysql.cj.jdbc.Driver即可。报ERROR 1064语法错误时先确认MySQL版本与SQL文件的兼容性5.7的语法在8.0里部分写法会变。导入卡住时执行SHOW PROCESSLIST;看是否有别的session占着表锁。导入完成后验证mysql -u root -p -e USE db_job; SHOW TABLES; SELECT COUNT(*) FROM t_user;4.3 application.yml数据库连接与启动报错对照表数据库导入完不是终点Java项目里的连接参数不一定对得上这几类报错最常见。报错信息原因处理方式Access denied for user rootlocalhostapplication.yml密码与实际不一致修改spring.datasource.passwordUnknown database db_job库名不一致与SQL文件头部CREATE DATABASE对比Communications link failure端口或驱动版本不对确认URL端口3306驱动换com.mysql.cj.jdbc.DriverPublic Key Retrieval is not allowedMySQL8认证方式URL加allowPublicKeyRetrievaltrueTable db_job.hibernate_sequence doesnt existJPA主键策略不匹配推荐把ddl-auto改为validate继续排查改完执行mvn spring-boot:run看到Started Application in x.xxx seconds表示启动成功。默认端口在server.port配置浏览器访问http://localhost:8080。4.4 用DBeaver图形化导入sql文件不习惯命令行的路径DBeaver新建MySQL连接填主机、端口、用户名密码测试连接成功后选中目标数据库打开SQL编辑器面板选择db_job.sql文件执行脚本。sql文件太大时单条执行会超时报错需要在连接驱动属性里临时开启一个选项支持一次执行多条语句本地导入完再关掉服务端连接不建议开启这个选项它会让批量注入的风险变高。导入完成后打开浏览器用初始化数据里的管理员账号登录后台。如果在登录时看到密码错误或500优先排查密码加密算法与代码是否一致比如代码用DigestUtils.md5DigestAsHex而SQL文件存的是明文或另一种哈希格式两者对不上就会一直报错。5. 从源码到可扩展密码加密、权限细化与演示数据验证5.1 把MD5换成BCrypt毕设SQL文件里常见MD5(123456)这种写法MD5查彩虹表即可还原生产环境不够用。BCrypt每次加密的盐随机相同密码每次加密结果不同。改造时引入spring-security-crypto依赖然后把Service里的密码校验改成BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); String encoded encoder.encode(123456); boolean match encoder.matches(123456, encoded);改动之后数据库里原来的MD5值全部失效SQL文件的初始化数据也要同步生成BCrypt哈希其他密文格式混在一起会导致老账号无法登录。5.2 用一条SQL验证整条业务链路部署完环境建议跑一条关联查询验证数据模型闭合SELECT u.username AS 求职者, j.job_name AS 职位, c.company_name AS 企业, DATE_FORMAT(a.apply_time, %Y-%m-%d) AS 投递日期, CASE a.status WHEN 1 THEN 已投递 WHEN 2 THEN 已查看 WHEN 3 THEN 面试 WHEN 4 THEN 已拒绝 WHEN 5 THEN 已入职 ELSE 取消 END AS 状态 FROM t_application a JOIN t_user u ON a.user_id u.id JOIN t_job j ON a.job_id j.id JOIN t_company c ON j.company_id c.id ORDER BY a.apply_time DESC;这条语句能同时验证t_user、t_job、t_company、t_application四张表的外键关系和初始化数据是否一致。查询结果为空时检查投递记录里插的user_id是否在t_user里实际存在这种外键悬空问题在改过初始化数据的项目里很常见。5.3 给SQL文件补齐数据隔离条件招聘者后台最容易出现越权一个招聘者看到了所有企业的职位列表。用户表里已经存了role招聘者的user_id关联t_company.id那么职位查询就要把当前登录用户的company_id带进SQL加一行AND company_id #{companyId}。这个条件加上之后基于这套源码扩展多租户就有了雏形后续做角色权限也好、做企业空间隔离也好都是从这一行筛选条件开始的。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门