ARTICLE DETAIL

资讯详情

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

SpringBoot手动整合MyBatis:SqlSessionFactory与MapperScannerConfigurer配置详解

SpringBoot手动整合MyBatis:SqlSessionFactory与MapperScannerConfigurer配置详解 简介这份资源面向正在学习或使用Spring Boot进行后端开发的Java开发者尤其是需要将MyBatis持久层框架接入Spring Boot项目的初中级工程师。内容围绕整合的核心流程展开涵盖依赖导入、数据源与MyBatis配置、Mapper接口与XML映射文件编写以及在Service层调用Mapper完成数据库查询的完整实例帮助读者理解Spring Boot与MyBatis协作的配置要点与常见写法。资源包共1个PDF文件大小约57KB以图文与代码片段结合的方式呈现便于按步骤对照实践。目前已有452人学习下载适合作为整合MyBatis时的速查参考也可用于梳理配置项与映射文件的对应关系快速搭建可运行的持久层结构。1. 为什么现在还要手写一遍 SpringBoot 整合 MyBatis很多人第一次接触 SpringBoot 持久层直接上 MyBatis-Plus 或者 JPABaseMapper一继承CRUD 全有了确实省事。但真到了排查慢 SQL、调分页插件、写动态 SQL、看事务为什么没回滚的时候底层的SqlSessionFactoryBean、MapperScannerConfigurer、DataSourceTransactionManager这三件套没搞明白就只能靠猜。这份实例用的是 SpringBoot 1.3.0.RELEASE MyBatis 3.2.7 mybatis-spring 1.2.2 的组合版本偏老但配置骨架和今天 SpringBoot 2.x/3.x 整合 MyBatis 的思路完全一致只是注解和 starter 换了个壳。适合两类人一是刚学完 SSM 想过渡到 SpringBoot 的二是用了几年 MyBatis-Plus 却说不清MapperScan背后干了什么的。把这份配置从头搭一遍比看十篇面试题管用。2. pom.xml 依赖坐标与数据源选型2.1 为什么不用官方 mybatis-spring-boot-starter这份实例走的是「手动装配」路线没有引入mybatis-spring-boot-starter而是自己声明mybatis、mybatis-spring、spring-jdbc三个依赖再用Configuration类把SqlSessionFactoryBean和MapperScannerConfigurer手动注册成 Bean。这么做的价值在于你能清楚看到 starter 到底帮你自动配置了什么。starter 内部无非就是MybatisAutoConfiguration里干了同样的事一旦自动配置失效比如多数据源场景你还是得回到手动装配。依赖清单里几个坐标值得单独说依赖版本作用注意点spring-boot-starter-parent1.3.0.RELEASE统一版本管理老版本新项目建议 2.7.x 或 3.xmybatis3.2.7MyBatis 核心与 mybatis-spring 版本要匹配mybatis-spring1.2.2桥接 Spring1.2.x 对应 MyBatis 3.2.xmysql-connector-java6.0.5MySQL 驱动6.x 驱动类名带cjc3p00.9.5.2连接池实例里声明了但实际用 Druidspring-jdbc由 parent 管理事务与 JdbcTemplate事务管理器依赖它这里有个容易踩的坑实例的 pom 里同时声明了 c3p0 和后面代码里用的 Druid但 pom 片段里并没有列出 Druid 依赖。实际跑起来要么补上com.alibaba:druid要么把DBConfig里的DruidDataSource换成ComboPooledDataSource。我一般会统一成 Druid因为它的监控和maxActive参数更好调。2.2 依赖引入的完整写法properties start-classcom.us.Application/start-class mybatis.version3.2.7/mybatis.version mybatis-spring.version1.2.2/mybatis-spring.version maven.compiler.target1.8/maven.compiler.target maven.compiler.source1.8/maven.compiler.source /properties dependencies !-- Web 能力Controller 层需要 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MySQL 驱动6.x 版本驱动类为 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version6.0.5/version /dependency !-- MyBatis 核心与 Spring 桥接版本必须成对 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version${mybatis-spring.version}/version /dependency !-- 事务管理器依赖由 parent 统一版本 -- dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId /dependency /dependenciesstart-class是 SpringBoot 打包可执行 jar 时指定的入口类用spring-boot-maven-plugin时它会读这个属性。maven.compiler.source/target锁 1.8是因为 MyBatis 3.2.7 和 SpringBoot 1.3 在更高 JDK 上编译容易出字节码兼容问题。参数上唯一需要你改的是mybatis.version和mybatis-spring.version两者对应关系错了会在启动时报NoSuchMethodError这是整合阶段最高频的报错之一。3. 数据源、SqlSessionFactory 与 Mapper 扫描的装配3.1 DBConfig用 Environment 读外部配置数据源不写死在代码里而是通过Environment从application.properties读这样打包后改配置不用重新编译。Configuration public class DBConfig { Autowired private Environment env; Bean(name dataSource) public DruidDataSource dataSource() { // checkNotNull 保证 url/username 缺失时启动即失败而不是运行到查询才报错 final String url Preconditions.checkNotNull(env.getProperty(ms.db.url)); final String username Preconditions.checkNotNull(env.getProperty(ms.db.username)); final String password env.getProperty(ms.db.password); // 默认 200配置里覆盖为 500 final int maxActive Integer.parseInt(env.getProperty(ms.db.maxActive, 200)); DruidDataSource dataSource new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setMaxActive(maxActive); return dataSource; } }Preconditions.checkNotNull来自 Guava作用是「快速失败」url 或 username 没配应用启动阶段就抛NullPointerException而不是等第一个请求进来才报连接错误。maxActive是连接池最大活跃连接数实例给到 500这个值要结合数据库max_connections一起看盲目调大会把数据库连接打满。Bean(name dataSource)显式命名是因为后面SqlSessionFactoryBean和事务管理器都要按名字注入多数据源时这个名字就是区分依据。3.2 application.properties 里的连接串参数ms.db.urljdbc:mysql://localhost:3306/dev?prepStmtCacheSize517cachePrepStmtstrueautoReconnecttruecharacterEncodingutf-8allowMultiQueriestrue ms.db.usernameroot ms.db.passwordadmin ms.db.maxActive500连接串里几个参数值得逐个拆cachePrepStmtstrue开启预编译语句缓存配合prepStmtCacheSize517设定缓存条数对高频重复 SQL 提升明显autoReconnecttrue让连接断开后自动重连但要注意它和连接池的testOnBorrow有职责重叠生产环境更推荐靠连接池的保活检测characterEncodingutf-8防止中文乱码allowMultiQueriestrue允许一次执行多条 SQL批量操作时有用但也放大了 SQL 注入的风险面非必要不开。提示ms.db.这个前缀是自定义的不是 SpringBoot 约定的spring.datasource.。用自定义前缀的好处是不会和自动配置冲突坏处是自动配置彻底失效所有 Bean 都得自己声明。3.3 MyBatisConfigSqlSessionFactoryBean 扫描 mapper.xmlConfiguration public class MyBatisConfig { Autowired private DataSource dataSource; Bean(name sqlSessionFactory) public SqlSessionFactoryBean sqlSessionFactory(ApplicationContext applicationContext) throws Exception { SqlSessionFactoryBean sessionFactory new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // classpath*: 会扫描所有 jar 和目录下的 mapper/*.xml sessionFactory.setMapperLocations(applicationContext.getResources(classpath*:mapper/*.xml)); return sessionFactory; } }SqlSessionFactoryBean是 MyBatis 和 Spring 之间的核心桥梁它负责把DataSource、mapper.xml 位置、类型别名、插件等组装成一个SqlSessionFactory。classpath*:前缀里的星号很关键单个classpath:只会在第一个匹配的 classpath 根下找classpath*:才会遍历所有 classpath 条目多模块项目里 mapper.xml 分散在不同 jar 时必须用后者。setMapperLocations接收的是Resource[]所以用getResources而不是getResource。3.4 MyBatisScannerConfigMapperScannerConfigurer 扫描 dao 接口Configuration public class MyBatisScannerConfig { Bean public MapperScannerConfigurer MapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer new MapperScannerConfigurer(); // 扫描 com.example 下任意子包的 dao 包 mapperScannerConfigurer.setBasePackage(com.example.*.dao); // 指定使用哪个 SqlSessionFactory多数据源时必须显式指定 mapperScannerConfigurer.setSqlSessionFactoryBeanName(sqlSessionFactory); return mapperScannerConfigurer; } }MapperScannerConfigurer干的事是扫描指定包下的接口为每个接口生成动态代理 Bean 注册到容器这样Autowired UserDao才能注入成功。basePackage支持*通配com.example.*.dao能匹配com.example.base.dao、com.example.user.dao这类结构。setSqlSessionFactoryBeanName用字符串而不是直接注入对象是为了避免和SqlSessionFactoryBean产生循环依赖——这是官方推荐写法也是很多人整合时启动报循环依赖的根因。注意实例特别强调MyBatisScannerConfig和MyBatisConfig不要写在一个类里。原因是MapperScannerConfigurer是BeanDefinitionRegistryPostProcessor执行时机极早如果和SqlSessionFactoryBean同处一个配置类会导致dataSource还没初始化就被引用。3.5 TransactionConfig事务管理器不能省Configuration public class TransactionConfig implements TransactionManagementConfigurer { Autowired private DataSource dataSource; Bean(name transactionManager) Override public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(dataSource); } }实现TransactionManagementConfigurer接口并重写annotationDrivenTransactionManager等于告诉 Spring「这就是默认事务管理器」之后Transactional注解才会生效。返回DataSourceTransactionManager是因为我们只有单一数据源多数据源场景下每个数据源配一个事务管理器再用Transactional(transactionManager xxx)指定。少了这个类Transactional会静默失效——不报错但回滚不生效这是最隐蔽的坑。4. Dao、Service、Controller 三层串起来跑通4.1 Dao 接口与 mapper.xml 的对应关系public interface UserDao { // 方法名必须与 mapper.xml 中 select id... 一致 public ListUser getList(MapString, Object map); }Dao 接口本身不需要任何注解MapperScannerConfigurer会为它生成代理。方法名getList必须和mapper.xml里select idgetList的 id 完全一致命名空间则对应接口全限定名。参数用MapString,Object是为了接收 Controller 传来的任意查询条件动态 SQL 里用#{key}取值。如果参数是单个对象MyBatis 会按属性名匹配多个参数则要用Param注解否则只能按arg0/param1这种默认名取可读性差。4.2 Service 层注入 DaoService public class UserServiceImpl implements UserService { Autowired private UserDao userDao; public Object getList(MapString, Object map) { return userDao.getList(map); } }Service把实现类注册为 BeanAutowired按类型注入UserDao代理。这里没有加Transactional因为纯查询不需要事务如果是写操作方法上要加Transactional并且注意默认只对RuntimeException回滚受检异常要显式写rollbackFor Exception.class。4.3 Controller 暴露 HTTP 接口Controller RequestMapping(value /users) public class UserController { Autowired private UserService userService; RequestMapping(method RequestMethod.GET, produces application/json;charsetUTF-8) ResponseBody public ResponseEntity? list(HttpServletRequest request) { // 把 request 参数统一转成 Map 传给 Service MapString, Object map CommonUtil.getParameterMap(request); return new ResponseEntityObject(userService.getList(map), HttpStatus.OK); } }ControllerResponseBody等价于RestController老版本里常这么写。produces application/json;charsetUTF-8解决返回中文乱码。CommonUtil.getParameterMap是自定义工具把request.getParameterMap()转成普通Map因为原生MapString,String[]在 MyBatis 里取值不方便。访问路径就是localhost:8099/users?id99端口由application.properties里的server.port决定。4.4 启动类扫描配置包ComponentScan(basePackages com.example) SpringBootApplication public class Application extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(Application.class); } public static void main(String[] args) throws Exception { ApplicationContext ctx SpringApplication.run(Application.class, args); // 启动后打印所有 Bean 名方便确认配置类是否被扫描到 String[] beanNames ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { System.out.println(beanName); } } }ComponentScan(basePackages com.example)保证DBConfig、MyBatisConfig、MyBatisScannerConfig、TransactionConfig都被扫描到。继承SpringBootServletInitializer是为了支持打成 war 部署到外部容器。main方法里遍历打印 Bean 名是个实用调试手段启动日志里搜sqlSessionFactory、dataSource、transactionManager能立刻确认装配是否成功比等到请求报错再回头查快得多。5. 整合后的排错清单与进阶调优5.1 四类高频启动/运行报错对照报错信息根因处理方式NoSuchMethodError: org.mybatis.spring.SqlSessionFactoryBeanmybatis 与 mybatis-spring 版本不匹配按官方对应表锁版本3.2.x 配 1.2.xInvalid bound statement (not found)mapper.xml 没被扫描到或 id 不匹配检查classpath*:mapper/*.xml路径与 id 名Transactional不回滚缺TransactionManagementConfigurer或异常类型不符补事务配置类写操作加rollbackFor启动报循环依赖MapperScannerConfigurer与SqlSessionFactoryBean同类拆成两个Configuration类Invalid bound statement是整合后最常遇到的运行期错误八成是 mapper.xml 的namespace写错或者 xml 文件放在了resources下但目录名不是mapper。用classpath*:mapper/*.xml时xml 必须位于某个 classpath 根下的mapper目录src/main/resources/mapper/UserMapper.xml是标准位置。5.2 打开 MyBatis 的 SQL 日志排查慢 SQL 和参数绑定问题第一步是把执行的 SQL 打出来。在application.properties里加# 指定 dao 包下的日志级别为 DEBUGMyBatis 会打印 SQL、参数、结果行数 logging.level.com.example.base.daoDEBUG日志级别设到DEBUG后控制台会输出 Preparing:、 Parameters:、 Total:三段分别对应预编译 SQL、绑定参数、返回行数。如果只看到 Preparing 没有 Parameters说明参数没传进去回头查Map的 key 和#{}里的名字是否一致。生产环境别长期开 DEBUG日志量很大建议只在排查时临时打开。5.3 分页与拦截器的接入位置实例本身没带分页但整合完成后接分页插件是顺理成章的一步。MyBatis 的分页靠Interceptor实现注册位置就在SqlSessionFactoryBean上Bean(name sqlSessionFactory) public SqlSessionFactoryBean sqlSessionFactory(ApplicationContext ctx) throws Exception { SqlSessionFactoryBean sessionFactory new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setMapperLocations(ctx.getResources(classpath*:mapper/*.xml)); // 注册分页插件PageHelper 是常见选择 PageInterceptor pageInterceptor new PageInterceptor(); Properties props new Properties(); props.setProperty(helperDialect, mysql); props.setProperty(reasonable, true); pageInterceptor.setProperties(props); sessionFactory.setPlugins(pageInterceptor); return sessionFactory; }setPlugins接收Interceptor数组可以同时挂分页、SQL 打印、乐观锁等多个拦截器执行顺序按数组顺序。helperDialect指定数据库方言reasonable为 true 时页码越界会自动纠正到合法范围。拦截器本质是 MyBatis 四级插件链上的一环理解它挂在SqlSessionFactoryBean上而不是 Spring 容器里是区分「MyBatis 配置」和「Spring 配置」的关键分界线。本文还有配套的精品资源点击获取
返回列表