资讯动态

MyBatis与Spring核心机制深度解析:从XML配置到事务传播的工程真相

发布时间:2026/10/23 16:21:20 来源:尧图企业网站定制
简介本资源是面向高校计算机专业学生与JavaEE初学者的期末复习题库聚焦MyBatis框架核心知识点助力系统梳理ORM原理、SQL映射配置及常见考点。题库覆盖初识MyBatis、核心配置、动态SQL、关联查询等关键章节题型丰富含填空、判断、选择、简答四类并附标准答案与解析特别强化对 / / 元素、mybatis-config.xml结构、resultMap映射机制、SQL注入防范#{}/${}辨析、concat()函数局限性及MyBatis工作流程SqlSessionFactory→SqlSession→Executor→MappedStatement等高频易错点的考查。资源为1个41KB的DOCX文档内容排版清晰、题目编号规范、答案分项标注便于打印复习或碎片化自测。目前已有2646人学习下载是备考JavaEE课程期末考试、夯实MyBatis基础、查漏补缺的高实用性教辅材料。1. 这不是题海战术而是一份能真正帮你把 MyBatis 和 Spring 框架“焊进肌肉记忆”的 JavaEE 期末复习题库你是不是也经历过考前翻了三遍《JavaEE 开发实战》笔记记了二十页结果一看到“resultMap的id和result区别在哪”就卡壳或者对着Pointcut和Around发呆分不清哪个是切点、哪个是增强这不是你学得不认真——而是大多数复习资料把框架讲成了名词解释汇编缺了真实开发中那层“为什么这么设计、不这么写会翻车、改一个参数怎么崩”的实操肌理。这份《JavaEE期末复习题库》不是题海它是一套按真实开发脉络反向拆解的诊断式习题集。它从 MyBatis 删除操作必须事务提交这个血泪经验出发一路覆盖到 Spring MVC 执行流程中HandlerAdapter到底把ModelAndView交给谁不是ViewReslover是DispatcherServlet每道题都对应一个你在写 DAO 层时踩过的坑、在配applicationContext.xml时删错标签导致启动失败的瞬间。填空题里藏着concat()函数防注入的玄学陷阱判断题第2题直击“只加 JAR 包就能跑”这个新手幻觉简答题答案里那句“selectKey的order属性设为BEFORE还是AFTER取决于主键生成策略”就是某次模拟项目X上线前两小时调通 ID 生成逻辑的关键线索。它适合两类人一是临考前想用最少时间抓住主干逻辑的备考者二是刚写完第一个 CRUD 却对SqlSession生命周期一头雾水的入门开发者。题库不教你怎么背它逼你动手去想如果namespace写错了MyBatis 报什么错如果Transactional没加在 Service 层DAO 层的delete()真的会回滚吗2. MyBatis 核心机制落地从 XML 配置到 SQL 执行链路的完整闭环2.1mapper的 namespace 不是摆设它是 MyBatis 查找 SQL 的唯一身份证MyBatis 不是靠文件名找 SQL而是靠mapper标签里的namespace属性。这个值必须全局唯一且要和 DAO 接口的全限定类名完全一致。比如你的接口是com.example.dao.UserDao那么映射文件开头必须写mapper namespacecom.example.dao.UserDao select idfindUserById resultTypecom.example.entity.User SELECT * FROM user WHERE id #{id} /select /mapper注意namespace值必须和接口类名严格匹配包括大小写和包路径。如果写成com.example.dao.userDao小写 d或com.example.dao.UserDaoImpl多写了 ImplMyBatis 在运行时根本找不到这个接口抛出Invalid bound statement (not found)异常——这是初学者最常遇到的“配置没错但死活不执行”的第一大坑。namespace的作用远不止标识。它和select、insert等元素的id属性拼起来构成 MyBatis 内部的 SQL 唯一标识符即statementId。当你在代码里调用sqlSession.selectOne(com.example.dao.UserDao.findUserById, 1)时MyBatis 就是靠这个字符串去缓存里查对应的MappedStatement对象。所以namespace是整个 SQL 映射体系的根漏掉或写错后续所有操作都失去坐标。2.2delete元素背后为什么删除操作必须手动 commit而查询不用填空题第一题问“MyBatis 的删除操作在映射文件中是通过配置【 】元素来实现的”答案是delete。但这只是表象。真正关键的是delete对应的SqlSession.delete()方法本身不触发事务提交它只把 SQL 语句塞进 JDBC 的 PreparedStatement真正的执行和提交由外部控制。看一段典型代码SqlSession sqlSession sqlSessionFactory.openSession(); try { UserDao userDao sqlSession.getMapper(UserDao.class); userDao.deleteUser(1001); // 这行只执行 delete SQL未提交 sqlSession.commit(); // 必须显式调用 commit否则数据不入库 } catch (Exception e) { sqlSession.rollback(); // 出错必须 rollback否则连接可能被污染 } finally { sqlSession.close(); }参数说明sqlSession.openSession()默认开启自动提交autoCommitfalse这意味着所有 DML 操作INSERT/UPDATE/DELETE都处于事务暂挂状态直到你调用commit()或rollback()。而SELECT操作属于 DQL不涉及事务所以sqlSession.selectOne()调用后立即返回结果无需 commit。这是 JDBC 规范决定的MyBatis 只是忠实封装。如果你用的是 Spring 声明式事务Transactional那commit()就由 Spring AOP 代理在方法退出时自动完成但底层逻辑没变——还是那个SqlSession在干活。2.3#{}vs${}模糊查询里藏着的 SQL 注入生死线填空题第3题问“使用 MySQL 中的【 】函数进行字符串拼接可以有效防止模糊查询时可能出现的 SQL 注入问题”标准答案是concat()。但这里有个巨大误导concat()本身并不能防止 SQL 注入它只是 MySQL 提供的一个字符串函数真正防注入的是 MyBatis 的#{}占位符机制。看两个写法对比!-- 危险用 ${} 拼接直接导致 SQL 注入 -- select idsearchUserByName resultTypeUser SELECT * FROM user WHERE name LIKE %${name}% /select !-- 安全用 #{} concat()预编译参数化 -- select idsearchUserByNameSafe resultTypeUser SELECT * FROM user WHERE name LIKE CONCAT(%, #{name}, %) /select当传入参数name admin OR 11时第一种写法会被解析成WHERE name LIKE %admin OR 11%SQL 逻辑被篡改第二种写法中#{name}会被 JDBC 预编译为?实际执行时参数以二进制方式传入CONCAT(%, ?, %)中的?永远是字符串字面量不会参与 SQL 解析。避坑 / 常见问题 / 排查现象 1模糊查询返回空结果但数据库里明明有数据。原因用了${name}但前端传参带了前后空格如 zhang 拼成% zhang %导致匹配失败。解决统一在 Java 层 trim 参数或改用#{name}CONCAT让数据库处理拼接。现象 2CONCAT(%, #{name}, %)在 Oracle 数据库报错。原因CONCAT是 MySQL 特有函数Oracle 用||或CONCAT仅支持两参数。解决用 MyBatis 的bind标签做数据库无关处理bind namelikeName value% name % / where if testlikeName ! nullname LIKE #{likeName}/if /where现象 3select返回null但日志显示 SQL 执行成功。原因resultType写错了类名如resultTypeuser小写而实际类是UserMyBatis 找不到类型处理器。解决检查resultType是否为全限定类名或已注册别名如user别名需在mybatis-config.xml中配置typeAlias aliasuser typecom.example.entity.User/。现象 4update执行后int返回值为 0但数据库记录确实被改了。原因SQL 语句里WHERE条件没匹配到任何行UPDATE影响行为 0MyBatis 如实返回。解决在业务层检查返回值为 0 时抛自定义异常或打告警日志避免“静默失败”。2.4mybatis-config.xml的核心骨架为什么environments和mappers缺一不可mybatis-config.xml是 MyBatis 的总开关它的结构不是随意的。最简可用配置必须包含两个顶级节点environments和mappers。?xml version1.0 encodingUTF-8? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration !-- 1. 环境配置定义数据源和事务管理器 -- environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/test/ property nameusername valueroot/ property namepassword value123456/ /dataSource /environment /environments !-- 2. 映射器加载告诉 MyBatis 去哪找 SQL -- mappers mapper resourcecom/example/dao/UserDao.xml/ /mappers /configurationenvironments节点定义了 MyBatis 连接数据库的“通道”没有它SqlSessionFactoryBuilder构建工厂时会直接抛NullPointerException。mappers则是 SQL 的“地图”它指向所有Mapper.xml文件的位置。如果漏掉mapperssqlSession.getMapper(UserDao.class)会返回null因为 MyBatis 根本不知道你的 SQL 写在哪。参数说明environments的default属性指定默认激活的环境如development可切换为test或productiondataSource的typePOOLED表示使用连接池推荐UNPOOLED每次新建连接性能极差mapper的resource属性是类路径相对地址必须和Mapper.xml在 classpath 下的实际位置一致如src/main/resources/com/example/dao/UserDao.xml。3. Spring Bean 生命周期与依赖注入从 XML 配置到注解驱动的演进真相3.1bean的scope属性singleton 不是“单例”prototype 也不是“每次 new”填空题第4题问“对于【 】作用域的 BeanSpring 只负责创建当容器创建了 Bean 实例后Bean 的实例就交给客户端代码来管理”答案是prototype。但这句话容易引发误解——prototypeBean 真的“每次请求都 new 一个”吗不完全是。看这段配置bean iduserService classcom.example.service.UserService scopeprototype/当你调用applicationContext.getBean(userService)时Spring 确实会每次都 new 一个新实例。但关键在于这个“每次”是指你主动调用getBean()的次数而不是 HTTP 请求次数。如果UserService被注入到一个singleton的 Controller 里Controller public class UserController { Autowired private UserService userService; // 这里注入的 userService 是 singleton 还是 prototype }答案是Controller 初始化时Spring 会调用一次getBean(userService)创建一个 prototype 实例并把它注入进去。之后无论多少次 HTTP 请求Controller 里的userService字段始终是同一个对象。因为prototype的生命周期由容器控制一旦注入完成容器就不再管理它。所以prototype的真实含义是Spring 容器每次调用getBean()时都返回一个新实例但它不保证这个实例在应用中只被用一次。真正需要“每次请求一个新 Bean”的场景要用request或session作用域Web 环境下它们由 Spring Web 容器管理。3.2Autowired的装配逻辑byType 优先但 name 冲突时会 fallback 到 byName填空题第1题问“所谓自动装配就是将一个 Bean 自动的注入到到其他 Bean 的【 】中”答案是Property属性。但Autowired的工作原理比“设个属性值”复杂得多。Spring 默认按类型byType装配。比如Service public class UserServiceImpl implements UserService { ... } Service public class OrderServiceImpl implements OrderService { ... } Controller public class UserController { Autowired private UserService userService; // ✅ 成功找到唯一的 UserService 类型 Bean }但如果存在多个同类型 BeanService(userServiceV1) public class UserServiceImplV1 implements UserService { ... } Service(userServiceV2) public class UserServiceImplV2 implements UserService { ... }此时Autowired private UserService userService;会抛NoUniqueBeanDefinitionException。解决办法有两个加Qualifier(userServiceV1)指定 Bean 名或者在字段上直接用Resource(nameuserServiceV1)JSR-250 标准按 name 查。参数说明Autowired的requiredfalse属性表示该依赖可选即使找不到也不报错字段为 nullQualifier的 value 值必须和Service(xxx)的 name 一致Resource是 Java 标准name属性对应 Bean 的 id/nametype属性对应类型优先级name type。3.3bean的autowire属性XML 时代的手动装配与自动装配之争选择题第1题问“Spring 的bean元素中的 autowire 属性取值不包括以下”选项 D 是byId。这是对的因为autowire只支持no、byName、byType、constructor、autodetect五种没有byId。autowirebyName的意思是Spring 会查找容器中所有 Bean把名字id 或 name和当前 Bean 的属性名相同的那个注入进来。比如bean iduserService classcom.example.service.UserServiceImpl/ bean iduserController classcom.example.controller.UserController autowirebyName/UserController类里必须有一个名为userService的 setter 方法public class UserController { private UserService userService; public void setUserService(UserService userService) { // 方法名必须是 set 首字母大写的属性名 this.userService userService; } }避坑 / 常见问题 / 排查现象 1autowirebyName失效属性仍是 null。原因bean的id和属性名大小写不一致如idUserService大写 U而属性是private UserService userService;小写 u。解决严格遵循 JavaBean 规范id全小写如userService。现象 2autowirebyType找到多个 Bean启动报错。原因容器中有两个UserService实现类且都没指定primarytrue。解决在其中一个bean上加primarytrue或改用Primary注解。现象 3bean配置了init-method但方法没被调用。原因init-method指定的方法名拼写错误或该方法不是 public 无参方法。解决检查方法签名是否为public void init() {}且init-methodinit中的值与方法名完全一致。现象 4Component扫描不到类applicationContext.getBean()报NoSuchBeanDefinitionException。原因ComponentScan的 basePackages 没包含该类所在包或类上没加Component及其衍生注解Service、Repository。解决确认ComponentScan(com.example)覆盖了com.example.service且UserServiceImpl类上有Service。3.4Controller、Service、Repository不只是语义标签更是 AOP 的切面锚点填空题第2题问“Controller通常作用在控制层如 Spring MVC 的【 】”答案是Controller。但这些注解的价值远超“标记位置”。Repository是最关键的——它被 Spring 定义为数据访问异常转换的切面锚点。当你在 DAO 层抛出SQLExceptionSpring 的PersistenceExceptionTranslationPostProcessor会捕获它并转换成 Spring 统一的DataAccessException子类如DuplicateKeyException。这个转换只对加了Repository的类生效。Repository // 必须加否则 SQLException 不会被转换 public class UserDaoImpl implements UserDao { Override public void insertUser(User user) { // JDBC 操作可能抛 SQLException } }Service和Controller则是事务管理的默认切面目标。Transactional注解默认只对public方法生效且要求目标类是 Spring 管理的 Bean即加了Service。如果UserService没加Service你加了Transactional也没用——因为 Spring 无法为它生成代理。所以这些注解是 Spring AOP 的“钩子”。不加Repository你就拿不到 Spring 的异常体系不加ServiceTransactional就是废纸。4. Spring AOP 与 Spring MVC 执行链从切面织入到请求分发的底层穿透4.1Aspect切面的执行顺序为什么Before和After不能替代Around填空题第5题问“AOP 术语中【 】表示 AOP 框架在特定的切入点执行的增强处理”答案是Advice通知。但Advice有五种类型其中Around是最强大也最容易误用的。看一个典型日志切面Aspect Component public class LoggingAspect { Around(annotation(org.springframework.web.bind.annotation.RequestMapping)) public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); Object result joinPoint.proceed(); // ⚠️ 必须调用 proceed()否则目标方法不执行 long end System.currentTimeMillis(); System.out.println(joinPoint.getSignature() took (end - start) ms); return result; } }Around的核心是proceed()方法——它代表目标方法的执行点。如果你忘了调用它或者在proceed()前抛异常目标方法永远不会运行。而Before和After是被动的Before在目标方法前执行After在目标方法后执行无论成功失败但它们无法控制目标方法是否执行、无法修改参数、无法修改返回值。参数说明ProceedingJoinPoint是Around独有的参数类型joinPoint.getSignature()获取方法签名joinPoint.getArgs()获取参数数组Before的JoinPoint参数不能调用proceed()AfterReturning的returning属性可接收返回值如AfterReturning(pointcut ..., returning result)。4.2 Spring MVC 的DispatcherServlet不是“前端控制器”而是整个流程的中央调度器填空题第4题问“Spring MVC 提供了一个前端控制器【 】”答案是DispatcherServlet。但它的角色远不止“拦截请求”。DispatcherServlet是 Spring MVC 的心脏它内部持有一组组件HandlerMapping根据 URL 查找Handler即Controller中的方法HandlerAdapter适配不同类型的Handler如RequestMapping方法、HttpRequestHandlerViewResolver根据逻辑视图名如success解析成真实视图如/WEB-INF/views/success.jspHandlerExceptionResolver统一处理异常LocaleResolver管理国际化。整个流程是DispatcherServlet主导的HTTP Request → DispatcherServlet → HandlerMapping → HandlerAdapter → Handler → ModelAndView → ViewResolver → View → Response关键细节HandlerAdapter并不直接把ModelAndView交给ViewResolver。HandlerAdapter执行完Handler后把ModelAndView返回给DispatcherServlet然后DispatcherServlet再调用ViewResolver。填空题第10章判断题第1题说“HandlerAdapter将ModelAndView对象返回给ViewReslover”这是错的——ViewReslover是笔误正确是ViewResolver且调用方是DispatcherServlet。4.3RequestMapping的 method 属性GET/POST 不是可选而是安全边界填空题第1题问“RequestMapping注解类型用于映射【 】”答案是URL。但RequestMapping的method属性绝不是装饰品。RequestMapping(value /user, method RequestMethod.POST) public String createUser(RequestBody User user) { ... }如果不加method RequestMethod.POST这个方法会响应所有 HTTP 方法GET、POST、PUT、DELETE。这会导致安全风险恶意用户用 GET 访问/user可能触发创建操作语义混乱RESTful 设计中POST /user是创建GET /user是查询混用破坏契约。Spring 4.3 提供了快捷注解PostMapping、GetMapping它们本质就是RequestMapping(method RequestMethod.XXX)的语法糖。避坑 / 常见问题 / 排查现象 1PostMapping方法被 GET 请求触发返回 405 Method Not Allowed。原因浏览器地址栏输入 URL 默认是 GET而方法只允许 POST。解决前端用form methodpost或 AJAX POST 请求后端加GetMapping处理查询。现象 2RequestMapping(/user/{id})的{id}参数取不到PathVariable为 null。原因RequestMapping的value和PathVariable的value不一致如RequestMapping(/user/{userId})但PathVariable(id)。解决确保PathVariable的 name 和 URL 模板中的占位符名完全一致。现象 3RequestBody接收 JSON 报 400 Bad Request。原因缺少EnableWebMvc或WebMvcConfigurationSupport导致HttpMessageConverter未注册。解决在配置类上加EnableWebMvc或确认spring-webmvcJAR 已引入。现象 4DispatcherServlet启动时报No mapping found for HTTP request。原因web.xml中DispatcherServlet的url-pattern配置为/但静态资源js/css也被拦截且没配置mvc:resources。解决在 Spring MVC 配置中加mvc:resources mapping/static/** location/static//或改url-pattern为/app/*。4.4ModelAndView的真相它不是返回值而是 Spring MVC 的内部通信协议填空题第5题问“Controller 执行完成后会返回一个【 】对象”答案是ModelAndView。但现代开发中你几乎不会显式 new 它。// 传统写法不推荐 RequestMapping(/list) public ModelAndView listUsers() { ListUser users userService.findAll(); ModelAndView mav new ModelAndView(userList); // 逻辑视图名 mav.addObject(users, users); // 模型数据 return mav; } // 现代写法推荐 RequestMapping(/list) public String listUsers(Model model) { model.addAttribute(users, userService.findAll()); return userList; // 直接返回逻辑视图名 }ModelAndView是 Spring MVC 1.x 时代的产物它把模型Model和视图View耦合在一个对象里。现在更推荐用Model参数或ModelMap、Map传递数据用String返回视图名。这样更轻量也符合单一职责原则。参数说明Model参数由 Spring 自动注入addAttribute(key, value)等价于model.put(key, value)返回的String是逻辑视图名ViewResolver会根据前缀后缀拼成真实路径如prefix/WEB-INF/views/ userList suffix.jsp→/WEB-INF/views/userList.jsp。5. 动态 SQL 与事务管理MyBatis 与 Spring 协同工作的边界与雷区5.1foreach的 collection 属性为什么 Map 的 key 是 “map”而不是 “params”填空题第2题问“在使用 MyBatis 的foreach时如果传入的参数是多个的时候就需要把它们封装成一个 Map 了这时候 collection 属性值就为 Map 的【 】”答案是键。但这里的“键”指 Map 的 key不是任意字符串。看一个典型场景前端传userId1001和statusactiveDAO 方法需要接收两个参数public ListUser findUsersByStatusAndId(Param(userId) Long userId, Param(status) String status);Param(userId)的作用是告诉 MyBatis把这个参数放到 Map 里key 是userId。所以 MyBatis 内部会构造一个 Map{userId: 1001, status: active}。在 XML 中写foreach时select idfindUsersByStatusAndId resultTypeUser SELECT * FROM user WHERE status #{status} AND id IN foreach itemid collectionuserId open( separator, close) #{id} /foreach /select错collectionuserId是错的。collection的值必须是 Map 的 key而userId是值不是 key。正确写法是collectionlist如果传的是 List或collectionarray如果传的是数组。但这里传的是两个独立参数MyBatis 会把它们包装成一个 Mapkey 是Param指定的值所以collection应该是userId不还是错。真相是foreach的collection属性只适用于参数本身就是集合List/Array/Map的情况。如果参数是多个基本类型应该用Param包装然后在foreach中用collectionparam1指向那个Param的 key。但 MyBatis 3.4 更推荐用Param后在 SQL 中直接引用#{param1}而不是foreach。所以这道题的答案“键”指的是当你手动构造 Map 作为参数时collection的值就是这个 Map 的某个 key。例如MapString, Object params new HashMap(); params.put(idList, Arrays.asList(1001, 1002, 1003)); params.put(status, active); userDao.findUsers(params);XML 中foreach itemid collectionidList open( separator, close) #{id} /foreachcollectionidList—— 这里的idList就是 Map 的 key。5.2set元素的智能逗号消除为什么它比手写 SQL 更安全填空题第5题问“使用set和if元素相结合的方式来组装 update 语句时set元素就会动态前置【 】关键字同时也会消除 SQL 语句中最后一个多余的【 】”答案是SET和逗号。看一个典型更新语句update idupdateUser UPDATE user set if testname ! null and name ! name #{name},/if if testemail ! null and email ! email #{email},/if if testage ! nullage #{age},/if /set WHERE id #{id} /update如果没有set你得手写UPDATE user if testname ! nullname #{name},/if if testemail ! nullemail #{email},/if if testage ! nullage #{age}/if -- 最后一个不能加逗号 WHERE id #{id}这极易出错。set的作用是自动在开头加SET关键字并自动删除内部所有if生成的 SQL 片段末尾的逗号。它等价于trim prefixSET suffixOverrides, if testname ! null and name ! name #{name},/if if testemail ! null and email ! email #{email},/if if testage ! nullage #{age},/if /trimsuffixOverrides,表示删除结尾的逗号prefixSET表示开头加SET。参数说明trim的prefix是前缀如SET、WHEREsuffix是后缀如AND 11prefixOverrides删除开头的指定字符串如AND suffixOverrides删除结尾的指定字符串如,。5.3 Spring 事务传播行为为什么Transactional加在 Service 层DAO 层却不用管填空题第4题问“MyBatis 的更新操作也需要进行事务提交”答案是对。但 MyBatis 本身不管理事务它只提供SqlSession的commit()方法。事务管理是 Spring 的事。Transactional的核心是TransactionInterceptor它在方法执行前后织入事务逻辑。当UserService.updateUser()被调用时UserService.updateUser() → TransactionInterceptor.before() → 开启事务 → UserDao.updateUser() → SqlSession.update() → TransactionInterceptor.afterReturning() → commit()如果UserDao.updateUser()抛异常TransactionInterceptor.afterThrowing()会调用rollback()。关键边界Transactional只对public方法生效且要求调用方和被调用方都是 Spring 代理对象。如果UserService内部直接new UserDaoImpl()事务失效。所以 DAO 层不需要、也不应该加Transactional——它只负责执行 SQL事务由上层 Service 统一控制。避坑 / 常见问题 / 排查现象 1Transactional方法内调用另一个Transactional方法内层事务不生效。原因Spring 事务基于代理内部方法调用是 this.xxx()绕过了代理。解决提取内层逻辑到另一个Service类或用ApplicationContext获取代理对象调用。现象 2Transactional(rollbackFor Exception.class)仍不回滚。原因抛出的是Error如OutOfMemoryError或RuntimeException的子类如NullPointerException而rollbackFor默认只对RuntimeException回滚。解决rollbackFor Exception.class已正确检查是否真的抛出了Exception而非RuntimeException。现象 3同一事务中多次save()只有第一次生效。原因save()方法里用了Modifying但没加Transactional或EntityManager的 flush 模式问题。解决确认所有 DML 操作都在一个Transactional方法内且Modifying配合Query使用。现象 4Transactional在测试类中不生效。原因JUnit 测试类没加 RunWith(Spring本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑