ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java核心技术开发汽车CRM:数据模型、Servlet与踩坑全解析

Java核心技术开发汽车CRM:数据模型、Servlet与踩坑全解析 简介这是一份基于Java核心技术开发的汽车客户关系管理系统CRM完整设计源码面向需要学习企业级Web系统与前后端分离架构的Java开发者、高校学生及相关从业者可用于理解汽车行业客户管理场景中的数据建模与业务逻辑实现。压缩包共包含532个文件大小11.01MB主要涵盖Java源文件与class文件、Vue组件、JavaScript脚本、CSS样式以及SVG/GIF图形资源并配有XML配置、SQL脚本、字体文件等类型目录结构清晰便于按模块查阅。系统采用MVC模式构建前端由Vue负责视图展示与数据交互后端Java处理核心业务与持久化逻辑。目前已有265人学习下载。通过源码可以学习客户管理、订单服务、员工管理等模块的具体实现掌握Maven依赖管理与前后端工程整合方式是一份适合二次开发和实战演练的高质量参考资料。1. 汽车客户关系管理系统这套「Java核心技术」源码到底解决了什么问题“基于Java核心技术的汽车客户关系管理系统”对Java新人来说是一个特别典型的练手题目它没有算法难度却能逼着你在一个具体行业场景里把Servlet、JDBC、面向对象分层、集合与异常处理串成一条完整可跑的链路。很多同学拿到现成源码直接改改到一半发现客户列表查不出来、中文乱码、删除客户报外键错误——根因往往不在代码而在数据模型和连接配置。这篇笔记从技术选型、数据模型、核心代码到踩坑排查把一套基于Java核心技术的汽车CRM从零到能跑通讲清楚。适合做课程设计、毕业设计或者想快速入门业务系统开发的读者照着一路实现。2. 技术选型与模块边界把Java核心技术拆成三层而不是一套框架2.1 为什么「Java核心技术」不等于Spring Boot全家桶看到这个标题一个最常见的疑问是现在工作里不都用Spring Boot吗为什么课程设计还要写Servlet和JDBC这里要区分两个目标你要交付的是一个能演示客户管理流程的源码包而标题强调的是“Java核心技术”这一层——也就是说Servlet、JDBC、JSP这些Java基础技术栈才是评分和答辩时被追问最多的地方。Spring Boot确实方便内嵌Tomcat、自动配置、依赖管理都是黑匣子答辩时老师问“你的数据库连接是怎么管理的”很多同学答不上来。而用Servlet JDBC JSP这套组合每一个环节都能讲清楚请求从哪里进来、参数怎么拿、SQL怎么执行、结果怎么渲染。这也是为什么课程设计和毕业设计里“基于Java核心技术的XX系统”经久不衰。我一般建议的做法是主技术栈用Servlet 3.0以上 JDBC JSP项目结构上保留web、service、dao三个经典分层。这套结构看起来“老”但它和Spring MVC在思想上完全一致——前端控制器、业务逻辑封装、数据访问抽象——把这三层写熟了后面学Spring Boot反而是水到渠成的事。2.2 模块划分客户、跟进、试驾、订单各管一摊汽车CRM和通用CRM最大的差别在业务对象普通客户管理只需要联系人字段汽车CRM则要围绕“买车”这个动作展开。一套最小可用版本功能上必须具备以下模块。模块涉及的实体对应核心接口登录认证系统用户、角色登录校验、退出登录客户管理客户资料新增、查询、修改、删除客户跟进记录跟进历史添加跟进、按客户查历史试驾管理试驾记录安排试驾、登记试驾结果销售订单订单录入订单、查看成交情况每个模块落到三层架构里时规则是固定的Servlet负责收参数和跳转页面Service负责业务判断比如登记跟进前检查客户是否存在DAO负责SQL执行。不要在Servlet里写SQL也不要在JSP里直接调JDBC——这条纪律能避免大多数后期改需求的翻车。2.3 先搭目录骨架再写代码少走一半弯路拿到题目后不要急着写代码先把项目骨架立起来。常见做法是创建一个标准的Maven Web项目没条件用Maven也可以直接在IDE里建Dynamic Web Projectlib目录手动放mysql-connector-java.jar。两种方式都能跑通Maven只是方便管理依赖不改变分层结构本身。项目最小目录结构如下按这个骨架填充文件就不会出现“类找不到”或者“包越写越乱”的问题。src/main/java ├── cn/crm/entity # User、Customer、FollowUp 等实体类 ├── cn/crm/dao # 每个实体对应的DAO接口与实现 ├── cn/crm/service # 业务逻辑封装 ├── cn/crm/web # Servlet 控制层 └── cn/crm/util # DBUtil、MD5Util 等工具类 src/main/webapp ├── jsp # login.jsp、customerList.jsp 等页面 └── WEB-INF/web.xml # Servlet与过滤器配置其中web目录下的每个Servlet按“模块 动作”命名比如CustomerServlet对应客户模块用method参数区分list、add、edit、delete四种操作。页面统一放在webapp/jsp下避免直接暴露在根目录。骨架定好后第3章的数据模型就是下一步的重心——它决定了你这个源码能不能被答辩老师认可。3. 汽车CRM数据模型设计车型字典表到跟进记录的五张核心表3.1 车型字典汽车CRM与通用客户管理的第一处不同普通CRM的客户表只需要姓名、电话、公司、等级。汽车CRM多出来的业务字段集中在“购车意向”上客户想看什么品牌、什么车型、预算区间是多少、有没有牌照指标。如果不做车型字典表直接在客户表里存“奥迪A6L”这种字符串文本系统上线后会出现同一个车型三种写法——“A6L”“a6l”“奥迪A6 2023款”统计销售线索时数据直接没法看。所以第一张表要先建车型字典品牌、车系、车型、年款、指导价。库存表和订单表都通过model_id引用它页面上用下拉框取值而不是自由输入。这一步做完整个系统的数据口径就统一了。车型字典表设计如下重点字段都加了注释。CREATE TABLE car_model ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 车型ID, brand VARCHAR(30) NOT NULL COMMENT 品牌比如奥迪, series VARCHAR(50) NOT NULL COMMENT 车系比如A6L系列, model_name VARCHAR(100) NOT NULL COMMENT 具体配置型号, model_year VARCHAR(10) DEFAULT NULL COMMENT 年款比如2024款, guide_price DECIMAL(10,2) DEFAULT NULL COMMENT 指导价单位万元, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_model (brand, series, model_name, model_year) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车型字典表;这里用联合唯一索引防止重复录入插入数据前先查一遍约束。注意车型“年份”不要做成整数列因为年款可能是“2024款”“2023改款”这类文本VARCHAR更稳妥。3.2 客户表、跟进记录、试驾记录字段怎么定才算内行客户表是CRM的核心字段设计直接决定后续功能好不好写。除了通用的姓名、手机号、微信汽车行业特色字段要包含购车类型、预算区间、意向车型、意向等级、牌照状态。CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 客户ID, name VARCHAR(50) NOT NULL COMMENT 客户姓名, phone VARCHAR(20) NOT NULL COMMENT 手机号, wechat VARCHAR(50) DEFAULT NULL COMMENT 微信号, city VARCHAR(50) DEFAULT NULL COMMENT 所在城市, intention_model INT DEFAULT NULL COMMENT 意向车型ID关联car_model, budget_min DECIMAL(10,2) DEFAULT NULL COMMENT 预算下限万元, budget_max DECIMAL(10,2) DEFAULT NULL COMMENT 预算上限万元, purchase_type TINYINT DEFAULT 1 COMMENT 1新车 2二手车, license_status TINYINT DEFAULT 0 COMMENT 0无牌照 1有牌照, level TINYINT DEFAULT 2 COMMENT 1高意向 2中意向 3低意向, source VARCHAR(30) DEFAULT NULL COMMENT 线索来源, remark VARCHAR(500) DEFAULT NULL COMMENT 备注, is_deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记, create_by INT DEFAULT NULL COMMENT 创建人关联sys_user, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_phone (phone), KEY idx_model (intention_model), KEY idx_level (level) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户表;意向模型字段加普通索引因为销售顾问最常用的操作是按意向等级筛选客户。预算用DECIMAL不用INT避免出现“预算二十万客户”被四舍五入成整数的尴尬。跟进记录表是这套系统里最能体现“客户关系管理”价值的一张表。它的设计原则只有一个只追加、不修改、不删除。不要把跟进内容设计成一条可以反复编辑的记录否则销售改来改去历史痕迹全丢了。CREATE TABLE follow_up ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 跟进ID, customer_id INT NOT NULL COMMENT 客户ID关联customer, user_id INT NOT NULL COMMENT 跟进人关联sys_user, content VARCHAR(1000) NOT NULL COMMENT 跟进内容, next_time DATETIME DEFAULT NULL COMMENT 下次跟进时间, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 跟进时间, KEY idx_customer (customer_id), KEY idx_next_time (next_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户跟进记录表;试驾记录表单独建因为试驾是汽车销售特有的高价值线索成交概率比普通电话跟进高得多。字段至少包含试驾车型、试驾时间、试驾结果、陪同销售顾问。CREATE TABLE test_drive ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 试驾记录ID, customer_id INT NOT NULL COMMENT 客户ID, model_id INT NOT NULL COMMENT 试驾车型ID, user_id INT NOT NULL COMMENT 试驾陪同销售, drive_time DATETIME DEFAULT NULL COMMENT 约定试驾时间, result VARCHAR(500) DEFAULT NULL COMMENT 试驾结论, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_customer (customer_id), KEY idx_drive_time (drive_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试驾记录表;3.3 外键加不加课程设计要加生产级代码反而要拆很多课程设计喜欢在表之间建物理外键删除客户时数据库自动限制删除子表记录。这个习惯在答辩时是加分项面试官或老师看到“外键约束”会觉得你考虑了数据完整性。但真实的线上业务很少用物理外键——高并发写入下外键检查会拖慢性能而且一张大表删除数据时要一层一层解开约束非常痛苦。这个系统我建议采用折中方案建表时保留外键约束用于答辩和演示但代码层面删除客户走逻辑删除。也就是前面客户表里的is_deleted字段查询时统一加“WHERE is_deleted 0”过滤删除变成UPDATE操作而不是DELETE操作。逻辑删除可以避免外键报错又能保留销售线索的历史数据一举两得。表之间的关系在代码里维护DAO层写SQL时用显式JOIN关联car_model取车型名称。如果某天想把car_model表升级成第三方车型库也只需要改DAO的SQL不动业务逻辑层这也是三层分层的意义所在。4. 从登录到客户管理JDBC连接、Servlet控制层与分层落地的核心代码4.1 JDBC连接工具类连接串里的参数一个都不能省数据模型建好后第一个要写的Java代码是JDBC工具类。不要在每一个DAO里都写一遍DriverManager.getConnection那样连接对象散落各处后期改密码要改几十个文件。下面是DBUtil的经典写法连接串里的每一组参数都有明确的用途注释里做了说明。package cn.crm.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/car_crm ?useUnicodetrue characterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps ! null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }静态代码块里加载JDBC驱动保证连接前驱动一定注册成功。字符编码参数必须和数据库的utf8mb4一致避免中文写入后乱码。useSSLfalse是因为本地开发通常没有配置SSL证书MySQL 8默认会尝试密钥交换不关掉可能报连接错误。serverTimezone明确指定时区避免驱动拿服务器默认时区去解析时间字段导致时间差8小时。这里有一个经验明明是本机连接却比想象中更容易失败绝大多数情况下都是连接串参数问题而不是MySQL服务没启动。遇到连接失败先ping一下3306端口再把连接串原样打印出来核对一遍比反复重启Tomcat有效得多。4.2 登录认证从请求参数到Session会话的完整链路登录模块是每个系统的门面也是理解Servlet生命周期最直观的例子。用户提交表单Servlet用doPost接收请求调用Service层校验账号密码成功就写入Session失败就退回登录页并展示错误提示。密码不能明文存储教程级的做法是MD5加盐。下面是一个简化版的统一加密工具。package cn.crm.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Util { public static String md5(String source) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] result md.digest(source.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : result) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException | java.io.UnsupportedEncodingException e) { e.printStackTrace(); return null; } } }登录校验的逻辑放在LoginServlet里它会调用UserService由UserService去查DAO把数据访问细节和请求处理细节彻底隔离。package cn.crm.web; import cn.crm.entity.User; import cn.crm.service.UserService; import cn.crm.util.MD5Util; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, MD5Util.md5(password)); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /customer/list); } else { req.setAttribute(error, 账号或密码错误); req.getRequestDispatcher(/jsp/login.jsp).forward(req, resp); } } }这段代码里有三个细节值得注意。req.setCharacterEncoding必须在读取第一个参数之前调用否则POST提交的中文参数全是乱码。登录成功后用sendRedirect实现重定向而不是forward这样可以避免用户按F5刷新时表单被重复提交。密码发给Service层前已经做了MD5转换DAO只负责拿密文匹配即使有人截获数据库密码字段也还原不出原始口令。4.3 客户列表分页DAO层SQL与前端回显的配合客户管理最核心的列表页面需要分页。分页查询包含两个动作查当前页的数据查总条数做页码条。一个常见的翻车点是只查当前页数据忘了查总数导致页码条永远只有第1页。CustomerDAO里这两个方法必须成对出现。public ListCustomer findPage(int pageNum, int pageSize) { ListCustomer list new ArrayList(); String sql SELECT c.id, c.name, c.phone, c.budget_min, c.budget_max, c.level, c.created_at, m.brand, m.series, m.model_name FROM customer c LEFT JOIN car_model m ON c.intention_model m.id WHERE c.is_deleted 0 ORDER BY c.created_at DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { Customer c new Customer(); c.setId(rs.getInt(id)); c.setName(rs.getString(name)); c.setPhone(rs.getString(phone)); c.setBudgetMin(rs.getBigDecimal(budget_min)); c.setBudgetMax(rs.getBigDecimal(budget_max)); c.setLevel(rs.getInt(level)); c.setCreatedAt(rs.getTimestamp(created_at)); c.setModelName(rs.getString(brand) rs.getString(series)); list.add(c); } } catch (SQLException e) { e.printStackTrace(); } return list; } public int count() { String sql SELECT COUNT(*) FROM customer WHERE is_deleted 0; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return 0; }LIMIT的两个参数先用setInt绑定不要拼进SQL字符串拼SQL不但有注入风险数值类型也可能被错误转换。LEFT JOIN连接car_model表取车型名称避免在Java代码里二次查库。页码从1开始传给数据库的偏移量需要先转换第一页传0第二页传pageSize。前端JSP接收Servlet存入request作用域的list和totalPage用JSTL的forEach标签渲染表格并生成页码链接。页码链接的常见实现是循环输出“上一页、1、2、3、下一页”每个页码带上pageNum参数Servlet通过 request.getParameter(pageNum) 读取注意处理pageNum为空或非数字的情况默认给1。5. 本地跑通与避坑排查连接串、乱码、外键和日期处理的五个真问题5.1 页面全是问号数据库存的也是问号统一编码才能根治现象登录页输入中文用户名跳转后控制台打印的用户名是“???”存入数据库的客户姓名也是问号。原因编码问题从来不止一处。JSP页面本身的编码、浏览器提交表单时的编码、Tomcat接收参数的编码、JDBC连接串的编码四层里任何两层不一致中文就变问号。解决把四处统一成UTF-8。JSP页面头部写全pageEncoding和contentTypeServlet第一行执行request.setCharacterEncodingJDBC连接串带characterEncodingutf8MySQL建库时指定DEFAULT CHARSETutf8mb4。我排查顺序是先看浏览器Network面板里的请求头Content-Type再看数据库字段的字符集最后检查代码里是否漏了编码设置。经验是八成情况是JDBC连接串少了characterEncoding。5.2 MySQL 8连接报错useSSL和Public Key Retrieval是两回事现象MySQL 8.0版本下运行项目报错提示“Public Key Retrieval is not allowed”或者“The server requested authentication method unknown to the client”。原因MySQL 8默认使用caching_sha2_password认证插件老版本驱动mysql-connector-java 5.x不认识这个插件同时客户端连接时没有允许获取服务端RSA公钥。两个问题叠加连接直接失败。解决把驱动换成mysql-connector-java 8.0.x并在连接串里追加allowPublicKeyRetrievaltrue。完整连接串可以参考第4章的写法在useSSLfalse之外显式补上allowPublicKeyRetrievaltrue。如果你用的是MySQL 5.7反而不要加这个参数保持原来的useSSLfalse和serverTimezone就够了。5.3 删除客户报错外键失败先看清楚三张表的依赖顺序现象点击删除某个客户页面抛出SQLException提醒违反外键约束删不掉。原因第3章的模型里follow_up和test_drive都通过customer_id指向客户表。有外键约束保护时主表被引用记录存在数据库拒绝直接删除。课程设计里建外键容易删除时就会被这条约束卡住。解决数据库层面先删子表再删主表代码层面多数业务场景不需要物理删除。推荐做法是把删除改成逻辑删除即执行“UPDATE customer SET is_deleted 1 WHERE id ?”所有查询语句统一加is_deleted 0条件。既保住外键设计的答辩亮点又避免删除报错。5.4 日期怎么存都差8小时timestamp和时区有关现象数据库表里保存的create_time与实际时间差了8小时或者前端查询某天的跟进记录查不到当天数据。原因MySQL以服务器时区解析DATETIME/TIMESTAMP而JDBC驱动又没有明确指定时区两边各用一个时区时间自然错位。更隐蔽的是查询单日数据时用 BETWEEN 2025-01-01 00:00:00 AND 2025-01-01 23:59:59忽略了DATETIME带时分秒后“当天”边界很可能算错。解决连接串固定加serverTimezoneAsia/ShanghaiJava代码里给PreparedStatement设置日期参数时优先使用setTimestamp而不是setDatesetDate会把时间部分抹成00:00:00。查询某天的记录正确写法是把当天结束时间显式设为第二天的零点也就是“2025-01-01 00:00:00 AND 2025-01-02 00:00:00”这是血泪经验换来的。5.5 Tomcat 10启动后找不到javax.servlet版本号与命名空间不匹配现象本地用Tomcat 10跑项目启动报错ClassNotFoundException提示找不到javax.servlet.http.HttpServlet。原因Tomcat 10开始把Servlet API从javax.迁移到jakarta.旧项目里的import javax.servlet.*全部失效。很多课程设计源码基于Tomcat 8/9编写直接跑到Tomcat 10必然翻车。解决三个方案任选。方案一把部署环境换回Tomcat 9源码不用动最快。方案二全局替换import把javax.servlet批量改成jakarta.servlet同时检查web.xml头部的命名空间版本。方案三用Maven引入jakarta.servlet-api作为provided依赖。对课程设计而言方案一最省事但这也能成为你展示排错能力的临场题。6. 从课程设计到可上线系统权限拦截、并发保护与数据一致性的三步进阶6.1 把登录校验从每个Servlet里剥出来统一Filter拦截未登录请求现在登录逻辑已经写了客户列表、跟进记录也都跑通了但有一个隐患目前每个Servlet都要自己判断session里有没有loginUser漏加一个判断就能绕过登录页直接访问系统。生产级别的做法是用Filter统一做请求拦截——这正是Java核心技术里Servlet规范提供的标准组件。package cn.crm.web; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); String uri req.getRequestURI(); boolean needLogin !uri.endsWith(/login) !uri.contains(/jsp/login.jsp) !uri.contains(/css/) !uri.contains(/js/); if (needLogin (session null || session.getAttribute(loginUser) null)) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }这段代码用“放行指定页面 拦截其余请求”的组合完成权限控制比在Servlet里逐个检查更集中。注意getSession(false)不创建新会话避免攻击者随便访问一次就被种上无意义会话。业务如果要细分角色再在Filter里从session取出user.getRole()判断即可。6.2 并发场景下跟进记录被覆盖乐观锁是Java技术人最容易忽略的一课课程设计阶段单机单用户演示没有并发问题可一旦进到真实门店两个销售同时跟进同一个客户互相提交的跟进内容就会你覆盖我、我覆盖你。这里涉及到Java程序员面试里常被追问的数据一致性问题——更新丢失。最稳妥的轻量方案是给客户表加version版本号字段更新前先查出version更新时带version条件。受影响行数为0则说明这条记录已被其他人修改过返回冲突提示并拒绝覆盖。-- 更新前查出version SELECT id, name, version FROM customer WHERE id ?; -- 携带version条件执行更新 UPDATE customer SET name ?, version version 1 WHERE id ? AND version ?;在代码里通过DAO的update返回int值判断影响行数是否为0来决定后续操作。这种方法叫乐观锁不加数据库锁机制也不影响读性能适合CRM这类读多写少的业务。如果真要把这套源码往生产靠版本字段、操作日志、导入导出Excel这三件事加上才算完整。我现在的习惯是拿到一套CRM题目第一件事不是看代码细节而是先打开ER图和数据字典——表与表之间的血缘关系理顺了代码怎么写都不会出大格。这套基于Java核心技术的汽车客户关系管理系统数据模型就是它的骨架Servlet和JDBC则是肌肉很多所谓“跑了没用”的源码问题都出在骨架歪了。希望这个习惯能帮你在下一个项目里少走同样的弯路。本文还有配套的精品资源点击获取
返回列表