Spring Boot + MyBatis 数据访问层实战:配置、踩坑与性能优化
简介面向Java Web后端开发者这是一套SpringBoot与MyBatis整合的项目示例围绕高效微服务开发场景引入Druid连接池与Swagger接口文档工具可降低项目初期配置成本统一数据持久层访问并为API提供可视化调试入口。压缩包共159个文件约1.81MB内含Java源码、class编译文件、JS页面脚本及XML/Properties配置整体目录结构清晰适合导入IDE直接研读。从内容预览看源码覆盖Controller、Service、MyBatis映射、RedisConfig、Swagger2、FastJson配置及文件上传等模块基本囊括SpringBoot常用集成要点。其中Druid可对SQL执行进行监控Swagger可在网页端在线调试接口有助于观察数据库访问情况与API出入参格式。代码采用Controller-Service-Mapper分层便于二次开发或迁移到实际项目。已有159人学习/下载对正在搭建SpringBoot基础工程或需要参考Druid监控与Swagger整合的开发者具有直接借鉴价值。1. springbootmybatis数据访问层最稳的组合但别当偷懒工具用Spring Boot 负责装配数据源、连接池、事务管理器、Mapper 扫描全部交给自动配置MyBatis 负责映射表字段和 Java 属性对应、SQL 语句和接口方法绑定。两者拼起来一个 Java 后端的数据访问层半小时内就能跑通这也是现在多数中小项目和新外包团队的标准起手式。但很多人把 MyBatis 当成少写 JDBC 的偷懒工具等真遇到分页串到别的 SQL 上、事务悄悄失效、字段映射全 null 的时候才发现手边连排查思路都没有。这篇笔记按我的落地习惯从依赖选型、最小工程、CRUD 姿势、分页批量和坑位排查五条线走一遍最后给出一套执行计划与压测验证的方法。适合刚接触这个组合的后端开发也适合想系统梳理边界的熟手。2. 搭起最小可运行工程starter 选型、数据源配置与第一个 Mapper2.1 依赖怎么选mybatis-spring-boot-starter 是一切的起点MyBatis 官方为 Spring Boot 提供了独立的起步依赖它跟 Spring Boot 官方 starter 不是一回事。这个 starter 会拉进 mybatis、mybatis-spring、spring-boot-autoconfigure 三样东西自动配置类负责创建 SqlSessionFactory、注册 SqlSessionTemplate、扫描带 Mapper 注解的接口。你不需要自己写 Bean 配置但必须搞清楚版本对应关系Spring Boot 2.x 用 2.x 系列的 starterSpring Boot 3.x 必须用 3.x 系列因为后者整个依赖体系从 javax 迁到了 jakarta版本选错项目连启动都起不来。dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.x/version /dependency这个版本变量我习惯留给父工程统一管理。注意一个细节mybatis-spring-boot-starter 并没有被放进 spring-boot-dependencies 的 BOM 清单里所以即使项目里有 spring-boot-starter-parent也必须在 dependency 里显式写版本号否则 Maven 会告诉你缺少版本。这个坑在多人协作的工程里非常典型——有人升级了 Spring BootMyBatis starter 没跟着升最后启动报错。数据库驱动用 MySQL 的话当前推荐 com.mysql:mysql-connector-j。老教程里常见的坐标是 mysql:mysql-connector-java那对应的是旧版驱动命名新项目不要照抄。驱动版本交给 Spring Boot 管理就行runtime 作用域足够不需要在代码里直接引用。2.2 最小工程三步走数据源、实体类、Mapper 接口先建好数据表我习惯用一个最简单的 user 表说明。接着写 application.yml最省事的最小配置文件长这样spring: datasource: url: jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplurl 里的几个参数不是可有可无的。serverTimezone 不写数据库和 JVM 时区不一致时会报时间转换异常allowPublicKeyRetrievaltrue 在 MySQL 8 的 caching_sha2_password 认证方式下必须开否则连本地库都会报连不上useSSLfalse 是本地开发省掉证书检查。driver-class-name 写 com.mysql.cj.jdbc.Driver这是 MySQL 8 的全新驱动类。mybatis 开头的配置是 MyBatis 自己的。mapper-locations 指向 XML 映射文件否则接口和 XML 对不上type-aliases-package 让 XML 里写 resultType 时能直接用类名小写别名map-underscore-to-camel-case 负责把 order_no 这种列名自动映射到 orderNo 属性log-impl 配成 StdOutImpl 会在控制台直接打印 SQL 和参数排查问题时先开它。实体类只是一个普通 POJO字段对应表列。关键在 Mapper 接口Mapper public interface UserMapper { User findByUsername(Param(username) String username); }确保接口能被扫描常见做法有两种一种就是上面的 Mapper 逐接口标注另一种是在启动类上写 MapperScan(com.example.demo.mapper)整包注册。我一般用 MapperScan包路径一劳永逸新接口不用记着加注解。但要注意它扫的是接口包别把实体类放同一个包底下。对应的 XML 文件放在 resources/mapper 目录里mapper namespacecom.example.demo.mapper.UserMapper select idfindByUsername resultTypecom.example.demo.entity.User SELECT id, username, email FROM user WHERE username #{username} /select /mappernamespace 必须是接口的全限定名id 必须是方法名这一个对上号运行期才找得到 SQL。resultType 可以直接写全限定类名也可以因为上面配了 type-aliases-package 而简写成 user但多模块项目里我宁愿写全限定名少猜。别名冲突时它不报错只按最后一个类匹配这种黑匣子行为没有必要赌。2.3 连接池参数HikariCP 的这几个值别直接抄默认HikariCP 是 Spring Boot 2.x 之后的默认连接池它的默认值对小型项目很友好但对并发稍高的服务并不合适。两个最关键的参数是 maximum-pool-size 和 connection-timeout。maximum-pool-size 默认 10它要和 Tomcat 的 max-threads 联动。Tomcat 默认线程池 200如果 200 个请求线程同时进来查库连接池只有 10那 190 个线程全部卡在 connection-timeout 上等请求直接堆积。粗估公式是高峰并发线程数 × 单条 SQL 执行时间秒再留 20% 余量。单条 SQL 50ms 的服务200 并发约需要 10 个连接看起来数字不大但前提是每条 SQL 都快。如果混进几条 500ms 的慢查询连接瞬间被占满。所以真正要调的不是池子而是慢 SQL这是后话。HikariCP 有几个参数行为跟直觉不一致。idle-timeout 默认 10 分钟但它只在 minimum-idle 小于 maximum-pool-size 时才生效池子恒定时这个参数等于没写max-lifetime 默认 30 分钟它必须小于数据库端的 wait_timeout否则连接被 MySQL 主动断开后池里还在用connection-timeout 默认 30 秒对在线服务来说太长生产我一般调到 10000 到 15000宁可快速失败让上游重试也不要让线程傻等 30 秒。参数常见生产值说明maximum-pool-size10~50与 Tomcat 线程数和 SQL 耗时联动minimum-idle与 maximum 相等避免频繁建连波动大时优先恒定池connection-timeout10000~15000ms超过即抛异常别用默认 30 秒max-lifetime1800000ms应小于数据库 wait_timeoutidle-timeout600000ms仅当 min max 时生效这一章跑通后接口能查到数据控制台能看到 SQL连接池也有基本概念。接下来就是写业务时躲不开的 CRUD 三种姿势。3. CRUD 落地姿势注解 SQL、resultMap 与参数传递的边界3.1 注解式 Mapperspringbootmybatis 简单 CRUD 的推荐写法MyBatis 从 3.4 起支持在 Mapper 接口方法上直接写 SQL 注解。单表插入、按主键查、条件简单用注解最省事文件数量也最少。Mapper public interface UserMapper { Select(SELECT id, username, email FROM user WHERE username #{username}) User findByUsername(Param(username) String username); Insert(INSERT INTO user(username, email) VALUES(#{username}, #{email})) Options(useGeneratedKeys true, keyProperty id) int insert(User user); Update(UPDATE user SET email #{email} WHERE id #{id}) int updateEmail(Param(id) Long id, Param(email) String email); Delete(DELETE FROM user WHERE id #{id}) int deleteById(Param(id) Long id); }Options(useGeneratedKeys true, keyProperty id) 是插入后回填自增主键的关键。不用它insert 方法执行完user.getId() 还是 null前端刚建完数据想拿主键做下一步操作就会踩空。keyProperty 写的是 Java 属性名不是数据库列名这一点写反的人不少。返回值 int 是受影响行数不是主键。这种写法我最多容忍到三个条件以内的查询。超过三个 if 条件、有批量操作、或多表关联注解就撑不住了。MyBatis 注解里其实也能写script标签做动态 SQL但字符串拼接出来的可读性很差多人维护时基本靠猜不如老老实实去 XML。另一个要留意的点是注解里如果出现特殊字符比如大于号、小于号必须转义这在 XML 里同样存在。3.2 XML 映射复杂 SQL 的兜底方案也是可控性的来源XML 映射的核心价值在于 SQL 与 Java 代码分离。DBA 可以直接看 XML 评审 SQL不用在 Java 字符串里翻。一个典型的带关联查询的 XML 写下来是这样mapper namespacecom.example.demo.mapper.OrderMapper resultMap idOrderUserMap typecom.example.demo.entity.Order id propertyid columnid/ result propertyorderNo columnorder_no/ association propertyuser javaTypecom.example.demo.entity.User id propertyid columnuid/ result propertyusername columnusername/ /association /resultMap select idselectOrderWithUser resultMapOrderUserMap SELECT o.id, o.order_no, u.id AS uid, u.username FROM orders o LEFT JOIN user u ON o.user_id u.id WHERE o.id #{id} /select /mapperresultMap 里的 id 标签标记主键列result 标签映射普通列association 处理订单里嵌一个用户这种一对一关系。注意 user 的主键列在查询里起了别名 uid不然 u.id 和 o.id 都叫 id结果集里后一个会覆盖前一个这就是多表查询最常见的查出来主键不对的根源。为什么不用 resultType 加驼峰映射因为多表 JOIN 的情况下user 表的列没法直接映射进 Order 里的 user 对象。一旦 JOIN列名重复、嵌套对象、一对多集合resultType 根本表达不了。我的习惯是单表简单查询用 resultType任何 JOIN 场景一律 resultMap别硬拿驼峰映射去填坑。XML 顶部要不要写 DOCTYPE 取决于 MyBatis 版本新版 starter 里已经内置了 XML Mapper 的 schema复制粘贴老帖子的 DOCTYPE 反而可能报未知错误建议直接省略。3.3 参数传递与返回映射五种高频翻车现场参数问题在 MyBatis 里的报错信息很直白但很多人第一次见还是会慌。第一种方法有多个参数但没加 Param。接口写 selectByNameAndEmail(String name, String email)XML 里用了 #{name}运行报 Parameter name not found。解决方式是每个参数都加 Param或者把参数封装成一个对象我更推荐后者参数超过三个时维护成本明显更低。第二种返回类型用了基本类型。int 类型的 count 在某些聚合条件下会返回 nullMyBatis 尝试把 null 赋给 int 直接抛异常。所有可能为 null 的返回都用包装类 Integer、Long这是改起来最便宜的一条。第三种查询条件用了 ${parameter} 而不是 #{parameter}。${} 是字符串替换直接拼 SQL有注入风险#{} 是预编译占位符安全且能享受数据库预编译缓存。业务条件全部用 #{}唯一可以碰 ${} 的是 ORDER BY 排序字段这种无法参数化的地方而且必须做白名单校验不能直接透传前端传上来的列名。第四种数据库列是下划线实体类属性是驼峰没开 map-underscore-to-camel-case结果 SELECT 出来全是 null。开了这个开关user_name 能映射到 userName但像 user_bind_info 这种三段式的列名MyBatis 只做一层下划线转驼峰映射出来是 userBindInfo 而不是预期的 userBindInfo实际不会报错但拿不到值。所以复杂列名干脆写别名别指望开关全兜住。第五种XML 里 resultType 写了这个实体类但类的 setter 是私有的或没有。MyBatis 走的是 setter 反射不认字段直接写入除非你显式配置了字段映射。检查实体类 setter 是不是 public这是很多人忽略的一步。这五条基本覆盖了参数传递和返回映射的大部分线上事故。下一章把分页、动态 SQL 和批量操作这三个业务里最爱出问题的点单独拉开讲。4. 分页、动态 SQL 与批量操作参数设不对全部白费4.1 PageHelper 分页springbootmybatis 三个必调参数与紧跟第一条 SQL原则分页插件底层原理是 MyBatis 拦截器拦截 Executor 的 query 方法拿到 startPage 塞进 ThreadLocal 的分页参数改写即将执行的 SQL在末尾追加 LIMIT 语句。理解了这一层就能明白它最著名的坑startPage 之后必须紧跟第一条 SQL 查询中间插任何一条别的查询分页参数都会串到那条 SQL 上。dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.x/version /dependencypagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: countcountSqlhelper-dialect 告诉插件按 MySQL 的 LIMIT 语法改写不配也能自动识别但多数据源时显式声明更稳。reasonabletrue 会把 pageNum 小于 1 的自动重置为 1超过总页数时自动查最后一页。好处是前端翻页不会白屏坏处是测试时经常发现我明明传了 pageSize100 但只返回 10 条因为合理校验把超出允许范围的值也规范了。support-methods-argumentstrue 允许从方法参数里直接取 pageNum 和 pageSize不用显式调 startPage老项目改造分页时很方便。PageHelper.startPage(pageNum, pageSize); ListUser list userMapper.selectList(); PageInfoUser pageInfo new PageInfo(list);这里有个使用习惯分页结果塞进 PageInfo页面上的 total、pageNum、pages 全从 PageInfo 拿。也可以把返回值强转成 Page 再 getTotal()但 PageInfo 的字段更全页面组件需要哪个字段都能对上。分页 SQL 的 count 查询是自动执行的原 SQL 特别复杂时 count 也可能很慢params 里配 countcountSql 是告诉插件用原 SQL 包一层做 count对绝大多数场景够了。提示我排查过很多分页串到别的查询的线上问题真正原因都是 startPage 和执行查询之间夹了打印日志、调了别的方法这种事情。PageHelper 用 ThreadLocal 存参数执行完会清理但如果中途抛出异常参数可能残留。遇到这次分页没生效下次生效的玄学问题先检查异常路径上有没有没被 try-finally 保护的 startPage。4.2 动态 SQLwhere 与 foreach 的边界写错就是全表扫描动态 SQL 是 MyBatis 最值钱的能力。写一个多条件用户搜索最常见的正确姿势是select idsearchUsers resultTypecom.example.demo.entity.User SELECT id, username, email FROM user where if testusername ! null and username ! AND username LIKE CONCAT(%, #{username}, %) /if if testemail ! null and email ! AND email #{email} /if /where /select标签会自动去掉第一个条件前面的 AND 或 OR所以子句里写不写开头的 AND 都安全。很多新手习惯写 WHERE 11 再拼 也能跑但 11 是不必要的常量条件写多了容易养成坏习惯统一用 更干净。里的 test 是 OGNL 表达式字符串判空必须同时写 null 和空串两个条件只写 null 时前端传个空字符串就会被拼进查询。数值类型注意 0 也是合法值if teststate ! null 对 state0 是成立的别误写条件让 0 被当成空值过滤掉。批量操作离不开 。最常见的是批量插入insert idbatchInsert parameterTypelist INSERT INTO user(username, email) VALUES foreach collectionlist itemitem separator, (#{item.username}, #{item.email}) /foreach /insertcollection 在接口参数是 List 且没加 Param 时固定写 list加了 Param(userList) 就写 userList这两个单词的差异是报错重灾区。separator, 生成的是一条多 VALUES 的 INSERT比循环单条插入快一个数量级。但注意大批量时单条 SQL 太大MySQL 的 max_allowed_packet 会截断我一般按 500 条一批分片执行。批量更新更麻烦update idbatchUpdate foreach collectionlist itemitem separator; UPDATE user SET email #{item.email} WHERE id #{item.id} /foreach /update这种写法生成的是用分号连接的多个 UPDATE 语句必须保证 JDBC 连接 url 带 allowMultiQueriestrue否则 MySQL 驱动会拒绝执行。没开这个参数时的报错也常被误判成 SQL 语法错误实际是驱动层直接禁了多语句。4.3 事务边界与分页测试两个容易误判的小细节先说事务。MyBatis 的 Mapper 方法本身不带事务接口里的多条 SQL 默认是自动提交的。要保证多条 SQL 原子性必须把事务放在 Service 层用 Transactional 标到 public 方法上这一点放到第 5 章展开。分页测试里有个细节。前面提到 reasonabletrue 会把超范围页规范掉测试时要验证分页到底生效没有别只看返回条数应该看 SQL 日志里到底有没有出现 LIMIT 关键字。如果 PreparedStatement 里直接显示 LIMIT 10分页就是生效的返回条数少只是合理校验在起作用。另外分页插件对子查询和 UNION 复杂语句的 count 生成有时不准遇到分页结果总数不对的情况优先怀疑 count 子句可以直接打印出来看它统计的是什么。5. springbootmybatis 避坑指南五个高频故障的排查路径5.1 Invalid bound statement (not found) 的完整排查现象调用接口方法时抛 BindingException异常信息里带着接口全限定名和方法名比如 UserMapper.findById。原因MyBatis 在运行期把接口方法绑定到 XML 里的 SQL 语句绑定失败无非三件事XML 文件没有被加载、namespace 写错、id 与方法名不一致。解决第一步确认 resources/mapper 目录下的 XML 确实被拷贝到了 classpath 下target/classes/mapper 里没有这个文件就是构建没包含检查 pom.xml 的资源过滤配置。第二步确认 mybatis.mapper-locations 路径和文件实际位置匹配写 classpath:mapper/*.xml 就要求 XML 在 mapper 目录下。第三步XML 里 namespace 和 id 逐一核对namespace 是接口全限定名id 是方法名多一个空格都对不上。如果这三步都检查过还报错把 log-impl 调成 StdOutImpl 重启看启动日志里 MyBatis 到底扫描到了哪些 XML。5.2 驼峰映射不生效配置开关不是万能的现象查询返回的实体对象整体不为空但每个属性都是 nullSQL 里却明明 SELECT 了对应列。原因字段名下划线和 Java 属性驼峰对不上同时 map-underscore-to-camel-case 没开或者这条查询走了 resultMap。解决全局配置文件加 mybatis.configuration.map-underscore-to-camel-case: true。但记住一个关键点一旦查询指定了 resultMap驼峰开关对这条语句就不起作用resultMap 会完全接管列到属性的映射。多表 JOIN 的查询与其抱怨开关不生效不如直接在 resultMap 里写全映射。还有一类隐性情况SQL 里写了聚合函数结果列名变成 SUM(order_amount)实体属性叫 totalAmount驼峰开关对这类函数生成的列名不做处理统一用别名 total_amount 或直接 resultMap。5.3 事务不生效自调用、可见性与被吞的异常现象方法标了 Transactional里面先做删除再插入第二步抛异常数据却只回滚了一半。原因最常见的有三种。第一种是同一个类内部方法调用public 方法调了同类里另一个 Transactional 方法Spring 的代理没有经过事务切面根本没触发。第二种是方法不是 publicSpring 的注解驱动对非 public 方法不代理。第三种是异常被 catch 住吞掉事务只有看到异常从方法抛出才会回滚catch 里打了日志不重新抛SQL 全部提交。解决自调用改成把事务方法移到另一个 Service Bean 里或者注入自身代理方法保持 public异常处理原则是 catch 后要么重新抛出要么手动调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。另外 Transactional 默认只回滚 RuntimeException受检异常不会触发回滚要显式写成 rollbackFor Exception.class。如果在方法里同时操作了多个数据源还要确认事务管理器是否配了对应数据源默认的 DataSourceTransactionManager 只认主数据源。5.4 连接池耗尽先查慢 SQL别急着调大池子现象日志高频出现 HikariPool-1 - Connection is not available, request timed out after 30000ms请求大面积超时。原因不是连接池参数的问题就是某个 SQL 长期占用连接。最典型的是 WHERE 条件大量使用 LIKE %xxx 或没走索引的查询数据库 CPU 飙升每一条 SQL 执行几百毫秒10 个连接很快被占满。解决在 MySQL 里打开慢查询日志定位慢 SQL用 EXPLAIN 看执行计划没有索引就建索引。这个阶段调大 maximum-pool-size 只能缓解几十秒SQL 慢的本质不解决池子调多大都会满。另一个被忽略的点是事务内做远程调用连接在等待外部接口返回时一直持有这种架构问题调连接池参数完全无效。排查时别只盯着 Hikari 的报错把堆栈打出来看哪些线程持有连接、它们在等什么比猜原因快得多。5.5 多数据源时的 Mapper 扫描冲突现象项目里配了两个数据源启动正常但运行期总出现找错 Mapper或使用的数据源不对。原因一个 MapperScan 把两个包都覆盖了或者两个数据源的 SqlSessionFactory 各自扫描了对方包里的 Mapper。Spring 容器里 Mapper 注册到了同一个 Bean 容器后扫描的会把先扫描的覆盖掉。解决每个数据源单独建一个 Configuration 类内部定义自己的 SqlSessionFactory并给各自的 SqlSessionFactory 设置精确的 mapper-locations。MapperScan 的 basePackages 缩小到具体子包别用父包。多数据源下还有一个习惯把 XML 文件按数据源分目录比如 mapper/primary 和 mapper/secondary配置和代码结构一一对应出问题一眼能看出归属。这个场景里 Mapper 注解和 MapperScan 不要混用统一用一种方式注册否则扫描顺序带来的覆盖问题很难排查。6. 把性能边界摸清楚执行计划、二级缓存与压测验证6.1 用 EXPLAIN 反推索引而不是凭感觉加索引拿到一条慢 SQL先执行 EXPLAIN关注 type 和 rows。EXPLAIN SELECT id, username, email FROM user WHERE username admin;type 为 ALL 就是全表扫描为 ref 或 range 说明走了普通索引为 const 说明命中了主键或唯一索引。看到 ALL 别急着加索引先确认 WHERE 条件里的列有没有被函数包裹。WHERE DATE(create_time) 2024-01-01 这种写法即使 create_time 有索引也用不上改成范围查询 create_time 2024-01-01 AND create_time 2024-01-02 就行。联合索引的顺序遵守最左前缀原则索引 (username, email) 能命中 WHERE username ? AND email ?但单独 WHERE email ? 就命中不了。6.2 二级缓存这个后悔药多数场景不该吃MyBatis 的二级缓存按 namespace 隔离开启只需要在 XML 里加一行cache/。缓存的是查询结果对象但有个严重问题多表 JOIN 的查询缓存归某个 namespace 管一旦另一张关联表更新这个 namespace 不会感知读出来就是脏数据。缓存对象的序列化、失效策略、并发写都要额外配置Spring Boot 集成没有对这块做默认优化。我的建议是二级缓存解决的是同一 SQL 高频重复查询的问题这种问题用 Redis 或本地 Caffeine 做业务缓存更可控还能顺便控制失效维度。MyBatis 二级缓存只适合单表、读多写少、对脏数据容忍度高的场景其余情况哪怕命中率再高也不建议开。6.3 最小压测脚本验证优化到底有没有效优化前后没有数据对比就等于没优化。用 ab 做一个最粗糙的并发验证ab -n 1000 -c 50 http://localhost:8080/api/user?usernameadmin看两个指标Requests per second 和 Time per request (mean)。优化前跑一遍加了索引或改了 SQL 再跑一遍指标提升多少直接说话。同时观察 MySQL 的慢查询日志在三分钟窗口里是否还有新的慢 SQL 记录。整个 springbootmybatis 的功夫最后都落在SQL 本身快不快这一件事上框架层面的配置都是辅助。我自己的习惯是每接手一个 springbootmybatis 工程第一件事不是看业务代码而是先开 SQL 日志、看连接池参数、跑一遍主要列表页的慢查询把这三个底摸清楚再动业务。这套组合的绝大多数事故都发生在配置默认值和 SQL 编写习惯上。希望这篇笔记里梳理的选型和踩坑记录能帮你把数据访问层从能跑推到跑得稳少走我当年绕过的弯路。本文还有配套的精品资源点击获取

相关新闻

64位PowerShell避坑指南:从32位进程架构到内存溢出与注册表重定向

64位PowerShell避坑指南:从32位进程架构到内存溢出与注册表重定向

简介:面向仍在使用Windows Server 2003等旧版64位系统的运维人员与开发者,这是一份难得的Windows PowerShell 64位安装包。该压缩包恰好补足了微软官方不再为这些老系统提供下载的缺口,共包含2个文件:PowerShell_Setup_amd64.msi安…

2026/10/13 10:29:05 阅读更多 →
C# WinForms+SQLite设备管理系统开发:台账、维修与报表全攻略

C# WinForms+SQLite设备管理系统开发:台账、维修与报表全攻略

简介:这是一套基于C#与ASP.NET的Web设备管理系统完整源码工程,主要面向C#初中级学习者、高校软件相关专业学生以及需要快速搭建设备管理后台的开发者。系统覆盖设备档案维护、借用归还登记、维修工单处理和报表统计等典型管理环节,源码中充分…

2026/10/13 10:29:05 阅读更多 →
Oracle rownum与rowid底层原理与实战避坑指南

Oracle rownum与rowid底层原理与实战避坑指南

1. 为什么刚学Oracle的人总在rownum上栽跟头?刚接触Oracle的开发者,十有八九会在rownum上卡住至少三天。我见过某高校数据库课程的期末项目里,A同学用SELECT * FROM emp WHERE rownum > 5想跳过前5条查后面的数据,结果返回空集…

2026/10/13 10:28:04 阅读更多 →

最新新闻

Java实战缓冲带:跨语言开发者快速落地JDK17+工程实践

Java实战缓冲带:跨语言开发者快速落地JDK17+工程实践

简介:本资源是《Java for Programmers 第二版》配套的完整示例代码包,专为具备编程基础、正系统学习Java的开发者设计,旨在通过可运行的实战案例深化对Java核心机制的理解与应用。压缩包为RAR格式,共34.9MB,涵盖从基础…

2026/10/13 11:17:32 阅读更多 →
JanusGraph连接五种模式深度解析:嵌入、远程、脚本与容器适配

JanusGraph连接五种模式深度解析:嵌入、远程、脚本与容器适配

1. 为什么Gremlin-Console不是“连上就行”,而是JanusGraph运维的试金石很多人第一次接触JanusGraph,看到官方文档里那句“使用gremlin.sh启动控制台”就以为万事大吉。我见过太多人,在本地跑通一个g.V().limit(10)后就兴冲冲去部署生产环境&…

2026/10/13 11:17:32 阅读更多 →
Greenplum集群部署:CentOS 7下可控可审计的GPDB初始化方案

Greenplum集群部署:CentOS 7下可控可审计的GPDB初始化方案

简介:本资源是面向大数据开发工程师、数据库运维人员及Linux系统管理员的Greenplum MPP数据库CentOS 7专属安装包,解决企业在国产化信创环境中快速部署高性能分析型数据仓库的核心需求。压缩包共32个文件,含9个YAML/Ansible配置文件&#xff…

2026/10/13 11:17:32 阅读更多 →
桥梁裂缝检测数据集筛选与YOLOv8适配指南

桥梁裂缝检测数据集筛选与YOLOv8适配指南

简介:本资源为桥梁裂缝检测专用图像数据集,面向土木工程、计算机视觉及智能检测方向的研究者与一线工程师,旨在支撑裂缝识别、分类与发展趋势预测等算法研发与工程验证。数据集共210个文件,包含84张带裂缝标注的JPG原始图像&#…

2026/10/13 11:17:32 阅读更多 →
JavaWeb实验室管理系统实战:SSM框架下的设备预约与权限控制

JavaWeb实验室管理系统实战:SSM框架下的设备预约与权限控制

简介:一套基于Java Web的研究生实验室管理系统项目包,面向Java Web学习者、课程设计或毕业设计者。系统覆盖教师端与学生端完整功能:教师可查看学生历次汇报与代码、按时间排序审核、创建限时会议、发起讨论并审批请假;学生可创建…

2026/10/13 11:17:32 阅读更多 →
Navicat 15安装配置与使用指南:从连接到高效管理

Navicat 15安装配置与使用指南:从连接到高效管理

很多人拿到Navicat 15,第一反应是去搜各种激活工具、注册码,但这条路既容易踩到恶意软件的坑,也会给自己留下不必要的隐患。我见过不少开发者因为图省事,最后数据库连接工具里被塞了一堆不明插件,轻则弹窗广告&#xf…

2026/10/13 11:16:31 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →