AI 摘要 文章系统梳理单例模式原理、实现与线程安全方案，对比懒汉、饿汉及静态内部类写法，并指出 Spring MVC Controller 默认单例需关注并发安全。随后完整演示 SSM 整合流程：导入依赖、配置 MyBatis、Spring、Spring MVC、web.xml，搭建 DAO、Service、Controller 三层，实现查询用户表并渲染 JSP，验证框架贯通。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结单例模式单例模式概述单例（Singleton）模式的关键是在系统运行期间，某个类有且只有一个实例，单例模式可以对资源进行重复利用，节约重复创建和销毁的成本，从而降低服务器压力，提高程序效率。单例模式在应用程序的日志服务、配置文件读取、数据库连接池设计、网站计数器等应用程序中广泛使用。静态代码块静态代码块本身不是单例模式的一种实现方式，但它可以在单例模式的实现过程中发挥作用，辅助完成单例模式的相关功能。静态代码块是用 static 关键字修饰的一段代码块，它在类加载时执行且仅执行一次，常用于初始化类的静态成员变量，或执行一些在类初始化阶段就需要完成的一次性操作，如加载配置文件、建立数据库连接等。示例：静态代码块BaseDao 类（cn.duozai.BaseDao）：public class BaseDao { private static final Logger logger = Logger.getLogger(BaseDao.class); /** * 在静态代码块中读取数据库的配置文件 * 类加载的时候只会执行一次 */ static { logger.debug("BaseDao => 读取数据库配置文件"); } /** * 模拟数据库查询 * * @return void */ public void executeQuery() { logger.debug("BaseDao => 查询数据"); } }测试类（test.StaticTest）：public class StaticTest { /** * BaseDao测试 * * @return void */ @Test public void test() { // 第一次调用BaseDao查询数据 BaseDao baseDao1 = new BaseDao(); baseDao1.executeQuery(); // 第二次调用BaseDao查询数据 BaseDao baseDao2 = new BaseDao(); baseDao2.executeQuery(); } }示例效果：单例模式实现单例模式的核心目标是确保一个类在整个应用程序中只有一个实例存在，并提供一个全局访问点来获取这个唯一的实例。实现单例模式，需要满足：一个类只有一个实例：不允许外界随意实例化该类，即需要将构造函数私有化。自行创建实例对象：外界不能随意实例化该类，即需要在该类内部实例化一个实例，且实例化的实例是静态的、私有的。对外提供实例对象：对外提供实例对象的方法应该是公有的、静态的方法，该方法创建或获取本类中的静态私有对象并返回。示例：实现单例模式Singleton 类（cn.duozai.singleton）：/** * 确保该类在整个应用程序中只有一个实例存在 * 并提供一个全局访问点来获取这个唯一的实例 */ public class Singleton { /** * 自行创建实例对象 * 外界不能随意实例化该类，需要在该类内部实例化一个实例 * 实例化的实例是静态的、私有的 */ private static Singleton singleton; /** * 一个类只有一个实例 * 不允许外界随意实例化该类，将构造函数私有化 * * @return null */ private Singleton() { } /** * 对外提供实例对象 * 对外提供实例对象的方法应该是公有的、静态的方法 * 该方法创建或获取本类中的静态私有对象并返回 * * @return Singleton实例 */ public static Singleton getInstance() { if (singleton == null) { // 创建Singleton实例 singleton = new Singleton(); } // 返回Singleton实例 return singleton; } }测试类（test.SingletonTest）：public class SingletonTest { /** * 单例模式测试 * * @return void */ @Test public void test() { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); // Singleton实例全局唯一 System.out.println(singleton1); System.out.println(singleton2); System.out.println(singleton1 == singleton2); } }示例效果：单例懒汉模式懒汉模式即懒加载，即在类加载时不创建其实例，而是被调用的时候再创建实例。懒汉模式是非线程安全的，在并发环境下可能存在严重问题，假设线程 A 和线程 B 几乎同时进入 getInstance 方法，线程 A 和线程 B 都会同时创建实例对象，这就导致了创建出了多个实例对象，违背了单例模式 一个类只有一个实例 的设计初衷。解决懒汉模式的线程安全问题，可以使用 synchronized 关键字将 getInstance 方法定义为同步方法。示例：解决单例懒汉模式存在的问题Singleton 类（cn.duozai.singleton）：public class Singleton { // ... /** * 对外提供实例对象 * 对外提供实例对象的方法应该是公有的、静态的方法 * 该方法创建或获取本类中的静态私有对象并返回 * synchronized：同步方法，保证线程安全 * * @return Singleton实例 */ public static synchronized Singleton getInstance() { // ... } }测试类（test.SingletonTest）：public class SingletonTest { /** * 单例模式测试 * * @return void */ @Test public void test() { // 多线程创建Singleton实例 for (int i = 0; i < 10; i++) { new Thread( () -> { // 获取Singleton实例 Singleton singleton = Singleton.getInstance(); // 多线程的情况下没有处理线程安全问题，有可能会得到多个Singleton实例 System.out.println(singleton); } ).start(); } } }示例效果：通过为 getInstance 方法添加同步关键字 synchronized 的方式可以有效避免线程安全问题，但是这种处理方式效率不高。单例饿汉模式单例模式下解决线程安全问题，可以使用饿汉模式。饿汉模式即在类加载的时候就完成了实例的创建工作，不存在线程安全问题。示例：单例饿汉模式Singleton 类（cn.duozai.singleton）：public class Singleton { /** * 自行创建实例对象 * 外界不能随意实例化该类，需要在该类内部实例化一个实例 * 实例化的实例是静态的、私有的 */ private static Singleton singleton; /** * 静态代码块初始化实例对象 * 在类加载的时候初始化Singleton实例 */ static { singleton = new Singleton(); } /** * 一个类只有一个实例 * 不允许外界随意实例化该类，将构造函数私有化 * * @return null */ private Singleton() { } /** * 对外提供实例对象 * 对外提供实例对象的方法应该是公有的、静态的方法 * 该方法创建或获取本类中的静态私有对象并返回 * * @return Singleton实例 */ public static Singleton getInstance() { // 直接返回Singleton实例 return singleton; } }单例懒饿汉对比单例模式的两种实现方式：懒汉模式：在类加载时暂不创建实例，在调用 getInstance 方法时再创建实例，因此类的加载速度快，运行时获取实例对象的速度相对较慢，具有延迟加载的特性，但是存在线程安全问题。饿汉模式：在类加载时就创建了实例，因此类加载速度慢，运行时获取实例对象的速度快。单例静态内部类单例模式可以结合静态内部类，在实现饿汉模式解决线程安全问题的同时，又能具备懒汉模式的延迟加载的特性，既保证了线程安全，又避免了性能影响。静态内部类是在首次使用该静态内部类的时候才会被加载，其遵循 Java 类加载机制中懒加载的特性。示例：单例静态内部类Singleton 类（cn.duozai.singleton）：public class Singleton { /** * 自行创建实例对象 * 外界不能随意实例化该类，需要在该类内部实例化一个实例 * 实例化的实例是静态的、私有的 * 类初始化的时候不创建Singleton实例 */ private static Singleton singleton; /** * 一个类只有一个实例 * 不允许外界随意实例化该类，将构造函数私有化 * * @return null */ private Singleton() { } /** * 静态内部类 * 在首次使用该静态内部类的时候才会被加载 */ public static class SingletonHelper { // 定义为静态常量 private static final Singleton INSTANCE = new Singleton(); } /** * 对外提供实例对象 * 对外提供实例对象的方法应该是公有的、静态的方法 * 该方法创建或获取本类中的静态私有对象并返回 * * @return Singleton实例 */ public static Singleton getInstance() { // 调用静态内部类，获取Singleton实例 return SingletonHelper.INSTANCE; } }Spring MVC 中的单例Spring MVC 中定义的 Controller 实际上也是 Spring Bean，默认情况下 Spring Bean 的作用域是单例模式。Spring MVC 将 Controller 设计为单例模式，这样的设计就不需要每次访问都创建控制器实例，速度和性能可以更加优秀。在单例模式的 Controller 中声明成员变量，需要注意线程安全问题，单例模式的 Controller 中的成员变量是公共的，会被所有的请求共享。SSM 框架整合SSM 概述SSM 一般是指将 Spring、SpringMVC、MyBatis 这三个主流的 Java 开发框架整合在一起，用于构建高效、灵活且易于维护的企业级 Java Web 应用程序。SSM 整合中各个框架的角色：Spring：在整个应用中负责对象的创建、配置以及管理它们的生命周期等工作，为其他框架的集成提供基础支撑。Spring MVC：主要处理用户与服务器之间的交互，接收来自客户端浏览器的 HTTP 请求，根据配置的映射规则找到对应的 Controller 层方法进行处理，然后返回响应结果给客户端。MyBatis：负责与数据库进行交互，在应用程序中执行对数据库的各种操作。SSM 整合的优势：提升开发效率：整合后可利用各框架成熟功能，如 Spring 依赖注入、SpringMVC 便捷请求处理、MyBatis 高效数据库访问，避免重复造轮子，开发者能专注业务逻辑，缩短开发周期。便于维护与扩展：基于清晰的分层架构，各层职责明确，修改代码时定位清晰，影响范围小，且方便替换组件或添加新功能，可灵活应对技术升级与业务变化需求。优化团队协作：不同开发人员可依据专长分别负责 SpringMVC、Spring、MyBatis 相关部分，并行开展工作，降低学习成本，提高协作效率。增强系统性能：各框架自带的性能优化机制能协同发挥作用，保障系统高效运行，提升整体执行效率与数据准确性。SSM 整合SSM 的整合步骤：导入 JAR 依赖包：Spring 依赖、MyBatis 依赖、Spring 与 MyBatis 整合依赖、Spring MVC 依赖、JSP 依赖。编写 MyBatis 核心配置文件：配置运行时行为（日志实现、自动映射级别等）、配置 SQL 映射文件。编写 Spring 核心配置文件：配置数据源、配置 SqlSessionFactoryBean 工厂、配置 MapperScannerConfigurer 注入映射器、配置事务管理器、开启 Ioc、AOP、事务注解。编写 Spring MVC 核心配置文件：开启 MVC 注解、配置视图解析器。编写 Web 应用配置文件：配置 Spring MVC 前端控制器、配置 Spring 监听器。编写三层架构：DAO、Service、Controller。示例：SSM 整合SQL 脚本：SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_address -- ---------------------------- DROP TABLE IF EXISTS `t_address`; CREATE TABLE `t_address` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `contact` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人姓名', `addressDesc` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '收货地址明细', `postCode` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮编', `tel` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人电话', `userId` bigint DEFAULT NULL COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='地址表'; -- ---------------------------- -- Records of t_address -- ---------------------------- BEGIN; INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (1, '张三', '福建省泉州市安溪县宝龙城市广场', '362400', '13275000001', 1); INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (2, '张三', '福建省厦门市集美区软件园3期', '361000', '13275000002', 1); COMMIT; -- ---------------------------- -- Table structure for t_sys_role -- ---------------------------- DROP TABLE IF EXISTS `t_sys_role`; CREATE TABLE `t_sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `code` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色编码', `roleName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'; -- ---------------------------- -- Records of t_sys_role -- ---------------------------- BEGIN; INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (1, 'SYS_ADMIN', '管理员'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (2, 'SYS_MANAGER', '店长'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (3, 'SYS_EMPLOYEE', '店员'); COMMIT; -- ---------------------------- -- Table structure for t_sys_user -- ---------------------------- DROP TABLE IF EXISTS `t_sys_user`; CREATE TABLE `t_sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `account` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '账号', `realName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '真实姓名', `password` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码', `sex` int DEFAULT NULL COMMENT '性别，1女/2男', `birthday` date DEFAULT NULL COMMENT '出生日期', `phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码', `roleId` bigint DEFAULT NULL COMMENT '用户角色id', `certFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '证件照片', `avatarFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工作证照片', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; -- ---------------------------- -- Records of t_sys_user -- ---------------------------- BEGIN; INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (1, 'admin', 'admin', '0000000', 1, '1980-11-01', '13888888888', 1, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (2, 'limingxing', '李明星', '0000000', 2, '1983-12-10', '13188888888', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (3, 'wanglulu', '王璐璐', '0000000', 2, '1984-06-05', '15766666666', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (4, 'hauzhiqing', '华志强', '0000000', 1, '1983-06-15', '15866666666', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (5, 'huangweihua', '黄卫华', '0000000', 2, '1982-12-31', '13800099999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (6, 'zhaogang', '赵刚', '0000000', 2, '1980-01-01', '15266655555', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (7, 'liuyang', '刘阳', '0000000', 2, '1978-03-12', '17099999999', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (8, 'lijiangtao', '李江涛', '0000000', 1, '1983-12-10', '13277777777', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (9, 'liuzhong', '刘忠', '0000000', 2, '1981-11-04', '15388888888', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (10, 'zhangfeng', '张峰', '0000000', 2, '1980-01-01', '19288899999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (11, 'songke', '宋科', '0000000', 1, '1983-10-10', '18900000000', 2, NULL, NULL); COMMIT; SET FOREIGN_KEY_CHECKS = 1;添加 Web 框架支持、导入相关依赖 JAR 文件，并添加为库。示例效果：Log4J 配置文件（classpath:log4j.properties）：log4j.rootLogger=debug,CONSOLE ################## # Console Appender ################## log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=debug log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%nMyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <settings> <setting name="logImpl" value="LOG4J"/> <setting name="autoMappingBehavior" value="FULL"/> </settings> <mappers> <package name="cn.duozai.dao"/> </mappers> </configuration>数据库配置文件（classpath:database.properties）：# 数据库驱动 db_driver=com.mysql.cj.jdbc.Driver # 数据库地址 db_url=jdbc:mysql://localhost:3306/cvs_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai # 数据库用户名 db_username=root # 数据库密码 db_password=123456.. # 连接池最小连接数 dbcp_minIdle=45 # 连接池最大连接数 dbcp_maxActive=100 # 连接池最大空闲连接数 dbcp_maxIdle=50 # 连接池初始化连接数 dbcp_initialSize=5 # 连接池最大等待时间 dbcp_maxWait=100 # 连接池是否开启无用连接回收 dbcp_removeAbandoned=true # 连接池无用连接回收时间 dbcp_removeAbandonedTimeout=180Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="cn.duozai"/> <aop:aspectj-autoproxy/> <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="location" value="classpath:database.properties"/> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${db_driver}"/> <property name="url" value="${db_url}"/> <property name="username" value="${db_username}"/> <property name="password" value="${db_password}"/> <property name="initialSize" value="${dbcp_initialSize}"/> <property name="maxActive" value="${dbcp_maxActive}"/> <property name="maxIdle" value="${dbcp_maxIdle}"/> <property name="minIdle" value="${dbcp_minIdle}"/> <property name="maxWait" value="${dbcp_maxWait}"/> <property name="removeAbandonedTimeout" value="${dbcp_removeAbandonedTimeout}"/> <property name="removeAbandoned" value="${dbcp_removeAbandoned}"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="validationQuery" value="select 1"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="numTestsPerEvictionRun" value="${dbcp_maxActive}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.duozai.dao"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>Spring MVC 核心配置文件（classpath:springmvc-servlet.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:component-scan base-package="cn.duozai"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>Web 应用配置文件（/web/WEB-INF/web.xml）：<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { /** * 查询用户表记录数 * * @return 用户表记录总数 */ int count(); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8" ?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT COUNT(1) FROM t_sys_user </mapper>用户表业务逻辑接口（cn.duozai.dao.SysUserService）：public interface SysUserService { /** * 查询用户表记录数 * * @return 用户表记录总数 */ int count(); }用户表业务逻辑接口实现类（cn.duozai.dao.SysUserServiceImpl）：@Service // 标记Service实现类为Bean对象，交给Spring管理 public class SysUserServiceImpl implements SysUserService { @Resource // Service层调用Dao层，注入Dao层对象 SysUserMapper sysUserMapper; /** * 查询用户表记录数 * * @return 用户表记录总数 */ @Override public int count() { return sysUserMapper.count(); } }首页控制器（cn.duozai.controller.IndexController）：@Controller // 标记控制器类为Bean对象，交给Spring管理 public class IndexController { @Resource // 控制器调用Service层，注入Service层对象 SysUserService sysUserService; /** * 处理/index请求 * * @param model 模型对象 * @return 视图页面文件名称 */ @GetMapping("/index") public String index(Model model) { // 调用Service层查询数据 int count = sysUserService.count(); // 将查询结果添加到模型对象中 model.addAttribute("count", count); // 返回视图页面名称 return "index"; } }首页视图页面（/web/WEB-INF/jsp/index.jsp）：<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title 用户表记录数：${count} 示例效果：
AI 摘要 文章系统梳理 Spring MVC 入门要点：回顾 MVC 与 JSP Model 2 思想；示范 XML 与注解两种配置方式搭建 Hello 工程；详解 DispatcherServlet 请求流转机制；演示 @RequestMapping、@RequestParam、Model/ModelAndView/Map 出参及中文乱码处理；配置 InternalResourceViewResolver 简化视图路径，为后续 SSM 整合奠定扎实基础。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结MVC 模式MVC 概述MVC 模式即 Model-View-Controller（模型-视图-控制器）模式，是开发 Web 应用程序时常用的一种代码分层方式。MVC 模式的三层：模型（Model）：负责处理业务逻辑和数据，对应 Java Bean，即 POJO 实体、DAO 层、Service 层。视图（View）：负责处理数据并将数据呈现给用户，对应 JSP、HTML。控制器（Controller）：负责接收并转发请求，对应 Servlet。JSP Model 2JSP + Servlet + Java Bean 的模式是典型的 JSP Model 2 模式。JSP Model 1早期的 JSP Model 1 模式中，缺少 Servlet，只由 JSP + Java Bean 组成，JSP 代替 Servlet 接受请求并处理数据，再将处理结果返回给前端。在 JSP Model 1 模式中，JSP 身兼多职，既要负责视图层的数据展示，又要负责业务流程控制，结构混乱，因此更推荐 JSP Model 2 模式。MVC 总结MVC 模式的处理过程：视图层提供交互页面，并发送用户输入的请求给控制器层。控制器层接收用户的请求，并决定该调用的模型，调用对应的 Service 层、DAO 层操作数据。模型层根据用户请求进行相应的业务逻辑处理，并返回处理结果。控制器层根据返回的处理结果，通过视图层展示结果给用户。MVC 模式的优点：MVC 三个模块相互独立，松耦合架构。多视图共享一个模型，大大提高代码的可重用性。控制器提高了应用程序的灵活性和可配置性。有利于软件工程化管理。MVC 模式的缺点：增加了系统结构和实现的复杂性，不适合小型规模的项目。视图层与模型之间需要控制器做中间的连接控制，所以效率较低。常见的 MVC 框架：Struts 1.0、Struts 2.0、Spring MVC 等。搭建 Spring MVC 环境Spring MVC 概述Spring MVC 是 Spring 提供的一个基于 MVC 模式设计的 Web 层框架。Spring MVC 是基于 Servlet 实现的，使用了 Controller 代替掉 JSP Model 2 模型中的 Servlet，实现了更优雅、更高效的 JSP Model 2 的模型。Spring MVC 的优点：角色划分清晰：Model、View、Controller 各司其职，各负其责。配置功能灵活：基于 Spring，可以使用 XML 或注解的方式开发 Spring MVC，使用 Spring IoC/AOP 特性解决问题。功能丰富：提供了 Web 应用开发的一整套流程，包含功能各异的技术工具，可以方便地配合使用。可灵活选择视图：可以根据项目实际需求选择 JSP、HTML、Velocity 等视图技术。支持国际化：提供国际化支持，可以轻松地实现不同语言之间的切换。第一个 Spring MVC 程序搭建 Spring MVC 开发环境的步骤：新建项目，并添加 Web 框架支持。导入 JAR 依赖包：Spring、Spring MVC、JSP。编写 Spring MVC 核心配置文件 springmvc-servlet.xml。编写 Web 应用配置文件 web.xml。编写 Spring MVC Controller 类。编写 JSP 视图页面。使用 Tomcat 运行 Spring MVC 程序。示例：第一个 Spring MVC 程序添加 Web 框架支持、导入相关依赖 JAR 文件，并添加为库。示例效果：Log4J 配置文件（classpath:log4j.properties）：log4j.rootLogger=debug,CONSOLE ################## # Console Appender ################## log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=debug log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%nSpring MVC 核心配置文件（classpath:springmvc-servlet.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> </beans>Web 应用配置文件（/web/WEB-INF/web.xml）：<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>Hello 控制器（cn.duozai.controller.HelloController）：/** * 控制器类继承AbstractController类，并重写handleRequestInternal方法 */ public class HelloController extends AbstractController { /** * 处理Web请求 * * @param httpServletRequest 请求对象 * @param httpServletResponse 响应对象 * @return 模型视图对象 */ @Override protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { System.out.println("Spring MVC框架搭建成功啦"); // 提供一个视图页面给用户 return new ModelAndView("/WEB-INF/jsp/hello.jsp"); } }Hello 视图页面（/web/WEB-INF/jsp/hello.jsp）：<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title Spring MVC开发环境搭建成功 Spring MVC 核心配置文件（classpath:springmvc-servlet.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean name="/hello" class="cn.duozai.controller.HelloController"/> </beans>添加 Tomcat 服务器：示例效果：使用注解开发 Spring MVC在 Spring MVC 中，可以使用注解的方式进行开发。Spring MVC 的基本注解：名称描述@Controller标记控制器类@RequestMapping指定方法的请求路径d示例：使用注解开发 Spring MVCSpring MVC 核心配置文件（classpath:springmvc-servlet.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:component-scan base-package="cn.duozai"/> </beans>Hello 控制器（cn.duozai.controller.HelloController）：/** * @Controller注解：标记为控制器Bean对象，即将该类交给Spring管理 */ @Controller public class HelloController { /** * 处理/hello请求 * @RequestMapping注解：指定请求信息映射 * value属性：指定请求路径 * * @return 模型视图对象 */ @RequestMapping(value = "/hello") public ModelAndView hello() { System.out.println("Spring MVC框架搭建成功啦"); // 提供一个视图页面给用户 return new ModelAndView("/WEB-INF/jsp/hello.jsp"); } }示例效果：Spring MVC 流程梳理Spring MVC 请求处理流程图：Spring MVC 请求处理流程分析：用户发送请求（/hello）到 Web 服务器（Tomcat）。在 Web 服务器中，配置了 Spring MVC 的前端控制器 DispatcherServlet，前端控制器会接收到用户请求。前端控制器根据用户的请求信息（/hello），找到对应的页面控制器/方法（HelloController-hello 方法），并执行处理请求的方法。页面控制器/方法调用 Service 层、DAO 层操作数据，数据操作结果返回给控制器。页面控制器/方法将数据操作结果、视图页面路径保存在模型视图对象 ModelAndView 中，并将模型视图对象返回给前端控制器。前端控制器获取到模型视图对象后，根据对应的视图页面（hello.jsp）渲染视图页面。视图页面渲染完毕后，前端控制器将视图页面响应展示给用户。Spring MVC 体系结构图：Spring MVC 体系结构分析：用户发送请求到服务器，前端控制器（DispatcherServlet）接收到用户的请求。前端控制器（DispatcherServlet）通过处理器映射（HandlerMapping）来根据请求信息找到处理该请求的处理器（Handler），即控制器中的处理请求的方法。前端控制器（DispatcherServlet）通过处理器适配器（HandlerAdapter）调用实际处理请求的处理器（Handler）。在处理请求的过程中，处理器（Handler）可能会使用消息处理器（HttpMessageConverter）来处理 HTTP 消息的转换，包括数据绑定、数据转换、数据格式化和数据验证。处理器（Handler）处理完请求后，会把数据和视图信息保存到模型视图对象（ModelAndView）中，并将模型视图对象（ModelAndView）返回给前端控制器（DispatcherServlet）。前端控制器（DispatcherServlet）使用视图解析器（ViewResolver）来解析模型视图对象（ModelAndView）中的视图信息，找到对应的视图页面（View）。前端控制器（DispatcherServlet）将模型数据（Model）渲染到视图页面（View）中，并将视图页面返回给用户。前后端数据交互@RequestMapping 注解处理映射Spring MVC 提供了 @RequestMapping 注解，用于处理映射关系。@RequestMapping 注解的基本属性：名称描述value给方法绑定的请求路径method限定 HTTP 请求方式使用 @RequestMapping 注解处理映射时，需要注意：@RequestMapping 注解可以加在控制器上，也可以加在控制器中的方法上。@RequestMapping 注解映射请求信息后，通过类定义的请求信息 + 方法定义的请求信息来进行访问。@RequestMapping 注解映射的请求信息（请求方式 + 请求路径）必须保证全局唯一。@RequestMapping 注解可以为同一个方法映射多个请求路径。通常会为控制器的 @RequestMapping 注解定义不同的值，以便于区分请求，不易出错，通过请求 URL 就可以明确看出是属于哪一个业务模块下的功能。@RequestMapping 注解的 value 属性用于将不同的请求路径映射到对应的控制器方法上，即给控制器方法绑定请求路径，当用户访问该路径时，就会执行这个控制器方法。示例：使用 @RequestMapping 注解定义请求路径Hello 控制器（cn.duozai.controller.HelloController）：/** * @Controller注解：标记为控制器Bean对象，即将该类交给Spring管理 * @RequestMapping注解：为控制器中的所有方法添加统一的请求路径前缀 * 访问控制器中的方法时，使用类请求路径+方法请求路径进行访问 * 请求路径在整个项目中必须保证唯一性 */ @Controller @RequestMapping("/admin") public class HelloController { /** * 处理/hello请求 * @RequestMapping注解：指定请求信息映射 * value属性：指定请求路径 * * @return 模型视图对象 */ @RequestMapping(value = "/hello") public ModelAndView hello() { // ... } /** * 处理/h1和/h2请求 * * @return 模型视图对象 */ @RequestMapping(value = {"/h1", "h2"}) public ModelAndView hello2() { // ... } }示例效果：@RequestMapping 注解可以通过 method 属性为方法指定 RequestMethod 枚举类中定义的请求方式。RequestMethod 枚举类中的请求方式：名称描述GET限定方法只能通过 GET 方式进行访问，常用于查询功能POST限定方法只能通过 POST 方式进行访问，常用于新增功能PUT限定方法只能通过 PUT 方式进行访问，常用于修改功能DELETE限定方法只能通过 DELETE 方式进行访问，常用于删除功能示例：使用 @RequestMapping 注解定义请求方式Hello 控制器（cn.duozai.controller.HelloController）：@Controller @RequestMapping("/admin") public class HelloController { /** * 处理/hello请求 * @RequestMapping注解：指定请求信息映射 * value属性：指定请求路径 * method属性：指定请求方式，RequestMethod枚举 * * @return 模型视图对象 */ @RequestMapping(value = "/hello", method = RequestMethod.GET) public ModelAndView hello() { // ... } /** * 处理/h1和/h2请求 * * @return 模型视图对象 */ @RequestMapping(value = {"/h1", "h2"}, method = RequestMethod.POST) public ModelAndView hello2() { // ... } }示例效果：@RequestMapping 注解有衍生注解，可以更便捷地为方法指定请求方式。@RequestMapping 的衍生注解：名称描述@GetMapping同 RequestMethod.GET@PostMapping同 RequestMethod.POST@PutMapping同 RequestMethod.PUT@DeleteMapping同 RequestMethod.DELETE示例：使用 @RequestMapping 的衍生注解注解定义请求方式Hello 控制器（cn.duozai.controller.HelloController）：@Controller @RequestMapping("/admin") public class HelloController { /** * 处理/hello请求 * * @return 模型视图对象 */ @GetMapping(value = "/hello") public ModelAndView hello() { // ... } /** * 处理/h1和/h2请求 * * @return 模型视图对象 */ @PostMapping(value = {"/h1", "h2"}) public ModelAndView hello2() { // ... } }@RequestParam 注解处理入参Spring MVC 提供了 @RequestParam 注解，用于接收视图页面传递给方法的参数。@RequestParam 注解的基本属性：名称描述name视图页面传递给方法的参数名称required参数是否必须，默认为 true，若请求中不包含该参数，则抛出异常defaultValue当参数不传或传入空值时，为参数赋予默认值控制器方法要接收视图页面传递的参数，只需要在方法中定义传入的参数，并在该参数前添加 @RequestParam 注解。示例：使用 @RequestParam 注解接收参数首页视图页面（/web/WEB-INF/jsp/index.jsp）：<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title 请输入姓名： 请输入年龄： Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { private static final Logger logger = Logger.getLogger(HelloController.class); /** * 提供表单 * * @return 模型视图对象 */ @GetMapping("/index") public ModelAndView index() { return new ModelAndView("/WEB-INF/jsp/index.jsp"); } /** * 处理表单请求 * @RequestParam注解：接收表单传递过来的参数 * name属性：对应表单元素的name属性，即参数名称 * * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 模型视图对象 */ @PostMapping("/hello") public ModelAndView hello(@RequestParam(name = "name") String name, @RequestParam(name = "age") Integer age) { logger.debug("视图页面表单传递的姓名 => " + name); logger.debug("视图页面表单传递的年龄 => " + age); // 暂时不返回视图页面 return null; } }示例效果：默认情况下，被 @RequestParam 注解标记的参数是必传的，可以通过 @RequestParam 注解的 required 属性设置为非必传，使用 defaultValue 属性设置参数的默认值。示例：使用 @RequestParam 为参数设置默认值Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { private static final Logger logger = Logger.getLogger(HelloController.class); /** * 提供表单 * * @return 模型视图对象 */ @GetMapping("/index") public ModelAndView index() { // ... } /** * 处理表单请求 * @RequestParam注解：接收表单传递过来的参数 * name属性：对应表单元素的name属性，即参数名称 * required属性：参数是否必须传递 * defaultValue属性：参数默认值 * * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 模型视图对象 */ @PostMapping("/hello") public ModelAndView hello(@RequestParam(name = "name") String name, @RequestParam(name = "age", required = false, defaultValue = "18") Integer age) { // ... } }示例效果：解决入参中文乱码问题Spring MVC 控制器方法入参时，会出现接收中文参数乱码问题，可以在 Web 应用配置文件中增加 String 字符编码过滤器并配置字符编码为 UTF-8 来解决中文乱码问题。示例：解决入参中文乱码问题Web 应用配置文件（/web/WEB-INF/web.xml）：<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>示例效果：ModelAndView 对象处理出参Spring MVC 中将数据传递给视图页面，可以使用 ModelAndView 对象处理出参。ModelAndView 对象即模型视图对象，其既可以包含视图信息，也可以包含模型数据。ModelAndView 对象的基本方法：名称描述ModelAndView addObject(String k, Object v)将数据以 k-v 的形式添加到模型视图对象中数据可以是基本数据类型、复杂数据类型等在 JSP 视图页面中通过 EL 表达式来取值void setViewName(String viewName)设置视图页面的路径示例：使用 ModelAndView 对象处理出参Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { // ... /** * 处理表单请求 * * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 模型视图对象 */ @PostMapping("/hello") public ModelAndView hello(@RequestParam(name = "name") String name, @RequestParam(name = "age",required = false, defaultValue = "18") Integer age) { logger.debug("视图页面表单传递的姓名 => " + name); logger.debug("视图页面表单传递的年龄 => " + age); ModelAndView mv = new ModelAndView(); // 将数据添加到ModelAndView模型视图对象中 // 在JSP页面中就可以通过EL表达式获取数据 mv.addObject("name", name); mv.addObject("age", age); // 设置视图页面文件路径 mv.setViewName("/WEB-INF/jsp/hello.jsp"); // 返回模型视图对象 return mv; } }Hello 视图页面（/web/WEB-INF/jsp/hello.jsp）：<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title Hello！ 您的姓名：${name} 您的年龄：${age} 示例效果：Model 对象处理出参Spring MVC 中将数据传递给视图页面，可以使用 Model 对象处理出参。Model 对象即模型对象，控制器方法可以接收一个 Model 类型的参数，用于存放模型数据，控制器方法不再返回 ModelAndView，直接返回视图页面的路径字符串。Model 对象的基本方法：名称描述void addAttribute(String k, Object v)将数据以 k-v 的形式添加到模型对象中数据可以是基本数据类型、复杂数据类型等在 JSP 视图页面中通过 EL 表达式来取值使用 Model 对象处理出参，是 Spring MVC 的标准用法。示例：使用 Model 对象处理出参用户实体类（cn.duozai.entity.User）：public class User { /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; /** * 网址 */ private String website; // ... }Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { // ... /** * 处理表单请求 * * @param model Model模型对象 * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 视图页面文件路径 */ @PostMapping("/hello") public String hello(Model model, @RequestParam(name = "name") String name, @RequestParam(name = "age",required = false, defaultValue = "18") Integer age) { logger.debug("视图页面表单传递的姓名 => " + name); logger.debug("视图页面表单传递的年龄 => " + age); // 实例化User对象 User user = new User(); user.setName(name); user.setAge(age); user.setWebsite("http://www.duozai.cn"); // 将数据添加到Model模型对象中 model.addAttribute("userObject", user); // 返回视图页面文件路径 return "/WEB-INF/jsp/hello.jsp"; } }Hello 视图页面（/web/WEB-INF/jsp/hello.jsp）：<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title Hello！ 您的姓名：${userObject.name} 您的年龄：${userObject.age} 您的网址：${userObject.website} 示例效果：Map 对象处理出参Spring MVC 中将数据传递给视图页面，可以使用 Map 对象处理出参，使用方式同 Model 对象。示例：使用 Map 对象处理出参Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { // ... /** * 处理表单请求 * * @param mapModel Map对象 * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 视图页面文件路径 */ @PostMapping("/hello") public String hello(Map<String, Object> mapModel, @RequestParam(name = "name") String name, @RequestParam(name = "age",required = false, defaultValue = "18") Integer age) { logger.debug("视图页面表单传递的姓名 => " + name); logger.debug("视图页面表单传递的年龄 => " + age); // 实例化User对象 User user = new User(); user.setName(name); user.setAge(age); user.setWebsite("http://www.duozai.cn"); // 将数据添加到Map对象中 mapModel.put("userObject", user); // 返回视图页面文件路径 return "/WEB-INF/jsp/hello.jsp"; } }视图解析器视图解析器概述Spring MVC 视图解析器（View Resolver）负责将控制器方法返回的视图页面的路径解析为实际要呈现给客户端的物理视图，如一个 JSP 页面、HTML 页面等。Spring MVC 常见的视图解析器：InternalResourceViewResolver：处理 JSP 视图的经典视图解析器，它会将视图名称解析为相对于特定前缀和后缀的 JSP 文件路径。ThymeleafViewResolver：用于和 Thymeleaf 模板引擎配合使用。FreeMarkerViewResolver：用于和 FreeMarker 模板引擎配合使用。借助 InternalResourceViewResolver 视图解析器，可以在 Spring MVC 核心配置文件中配置视图文件路径前缀为“/WEB-INF/jsp/”，后缀为“.jsp”，当控制器返回“index” 时，视图文件路径就会被解析成“/WEB-INF/jsp/index.jsp”。示例：视图解析器Spring MVC 核心配置文件（classpath:springmvc-servlet.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:component-scan base-package="cn.duozai"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>Hello 控制器（cn.duozai.controller.HelloController）：@Controller public class HelloController { private static final Logger logger = Logger.getLogger(HelloController.class); /** * 提供一个表单 * * @return 视图页面文件名称 */ @GetMapping("/index") public String index() { // 返回视图页面文件名称 // 视图解析器拼接的视图路径为：/WEB-INF/jsp/index.jsp return "index"; } /** * 处理表单请求 * * @param model Model模型对象 * @param name 视图页面表单传递的name参数 * @param age 视图页面表单传递的age参数 * @return 视图页面文件名称 */ @PostMapping("/hello") public String hello(Model model, @RequestParam(name = "name") String name, @RequestParam(name = "age",required = false, defaultValue = "18") Integer age) { // ... // 返回视图页面文件名称 return "hello"; } }示例效果：
AI 摘要 文章系统讲解 Spring 与 MyBatis 的轻量级整合方案：先以 IoC 接管 MyBatis 生命周期，通过 SqlSessionFactoryBean、SqlSessionTemplate 实现 DAO；再用 MapperScannerConfigurer 自动扫描接口生成实现，简化编码。随后引入声明式事务，对比编程式痛点，给出 XML 及 @Transactional 注解两种配置，演示传播行为、隔离级别、回滚规则等核心属性，并附完整用户表增删改查示例与测试，帮助读者快速构建低耦合、高可维护的 SSM 企业应用。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结基本整合方式整合思路梳理Spring 整合其他框架的本质其实就是把其他框架交给 Spring 管理，Spring 通过 IoC、AOP 等机制实现与其他框架的连接，最终建立一个低耦合的应用架构。MyBatis 三大核心接口和类的关系：使用 Spring 整合 MyBatis 时，以上流程可以全部移交给 Spring 来处理，即读取配置文件、组件的创建、组件之间的依赖关系以及整个框架的生命周期都由 Spring 容器统一管理。初步整合方式Spring 和 MyBatis 的整合步骤：导入 JAR 依赖包：Spring IoC/AOP 依赖、事务依赖、dbcp 数据源依赖、MyBatis 依赖、Spring 与 MyBatis 整合依赖。编写配置文件：MyBatis 核心配置文件、Spring 核心配置文件。通过 Spring 配置文件配置数据源，创建数据源对象。通过 Spring 配置文件配置 SqlSessionFactory，创建 SqlSessionFactory 对象。通过 Spring 配置文件配置 SqlSession，创建 SqlSession 对象。通过 SqlSession 操作数据库。示例：Spring 与 MyBatis 初步整合SQL 脚本：SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_address -- ---------------------------- DROP TABLE IF EXISTS `t_address`; CREATE TABLE `t_address` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `contact` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人姓名', `addressDesc` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '收货地址明细', `postCode` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮编', `tel` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人电话', `userId` bigint DEFAULT NULL COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='地址表'; -- ---------------------------- -- Records of t_address -- ---------------------------- BEGIN; INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (1, '张三', '福建省泉州市安溪县宝龙城市广场', '362400', '13275000001', 1); INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (2, '张三', '福建省厦门市集美区软件园3期', '361000', '13275000002', 1); COMMIT; -- ---------------------------- -- Table structure for t_sys_role -- ---------------------------- DROP TABLE IF EXISTS `t_sys_role`; CREATE TABLE `t_sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `code` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色编码', `roleName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'; -- ---------------------------- -- Records of t_sys_role -- ---------------------------- BEGIN; INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (1, 'SYS_ADMIN', '管理员'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (2, 'SYS_MANAGER', '店长'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (3, 'SYS_EMPLOYEE', '店员'); COMMIT; -- ---------------------------- -- Table structure for t_sys_user -- ---------------------------- DROP TABLE IF EXISTS `t_sys_user`; CREATE TABLE `t_sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `account` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '账号', `realName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '真实姓名', `password` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码', `sex` int DEFAULT NULL COMMENT '性别，1女/2男', `birthday` date DEFAULT NULL COMMENT '出生日期', `phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码', `roleId` bigint DEFAULT NULL COMMENT '用户角色id', `certFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '证件照片', `avatarFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工作证照片', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; -- ---------------------------- -- Records of t_sys_user -- ---------------------------- BEGIN; INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (1, 'admin', 'admin', '0000000', 1, '1980-11-01', '13888888888', 1, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (2, 'limingxing', '李明星', '0000000', 2, '1983-12-10', '13188888888', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (3, 'wanglulu', '王璐璐', '0000000', 2, '1984-06-05', '15766666666', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (4, 'hauzhiqing', '华志强', '0000000', 1, '1983-06-15', '15866666666', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (5, 'huangweihua', '黄卫华', '0000000', 2, '1982-12-31', '13800099999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (6, 'zhaogang', '赵刚', '0000000', 2, '1980-01-01', '15266655555', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (7, 'liuyang', '刘阳', '0000000', 2, '1978-03-12', '17099999999', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (8, 'lijiangtao', '李江涛', '0000000', 1, '1983-12-10', '13277777777', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (9, 'liuzhong', '刘忠', '0000000', 2, '1981-11-04', '15388888888', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (10, 'zhangfeng', '张峰', '0000000', 2, '1980-01-01', '19288899999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (11, 'songke', '宋科', '0000000', 1, '1983-10-10', '18900000000', 2, NULL, NULL); COMMIT; SET FOREIGN_KEY_CHECKS = 1;导入相关依赖 JAR 文件，并添加为库。示例效果：用户表实体类（cn.duozai.entity.SysUser）：public class SysUser implements Serializable { /** * 主键id */ private Integer id; /** * 账号 */ private String account; /** * 真实姓名 */ private String realName; /** * 密码 */ private String password; /** * 性别，1女/2男 */ private Integer sex; /** * 出生日期 */ private Date birthday; /** * 手机号码 */ private String phone; /** * 用户角色id */ private Integer roleId; /** * 证件照片 */ private String certFile; /** * 工作证照片 */ private String avatarFile; // ... }用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { /** * 查询用户列表 * * @return 用户列表 */ List<SysUser> getUserList(); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user </mapper>Log4J 配置文件（classpath:log4j.properties）：log4j.rootLogger=debug,CONSOLE ################## # Console Appender ################## log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=debug log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%nMyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <settings> <setting name="logImpl" value="LOG4J"/> <setting name="autoMappingBehavior" value="FULL"/> </settings> <typeAliases> <package name="cn.duozai.entity"/> </typeAliases> <mappers> <package name="cn.duozai.dao"/> </mappers> </configuration>Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="cn.duozai"/> <aop:aspectj-autoproxy/> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/cvs_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456.."/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>用户表数据访问接口实现类（cn.duozai.dao.SysUserMapperImpl）：@Repository public class SysUserMapperImpl implements SysUserMapper { /** * 从Spring容器中注入SqlSessionTemplate */ @Resource(name = "sqlSessionTemplate") SqlSessionTemplate sqlSession; /** * 查询用户列表 * * @return 用户列表 */ @Override public List<SysUser> getUserList() { // 通过SqlSessionTemplate调用Mapper接口，实现数据库操作 return sqlSession.getMapper(SysUserMapper.class).getUserList(); } }用户表业务逻辑接口（cn.duozai.dao.SysUserService）：public interface SysUserService { /** * 查询用户列表 * * @return 用户列表 */ List<SysUser> getUserList(); }用户表业务逻辑接口实现类（cn.duozai.dao.SysUserServiceImpl）：@Service public class SysUserServiceImpl implements SysUserService { /** * 从Spring容器中注入依赖的Dao层对象 */ @Resource SysUserMapper sysUserMapper; /** * 查询用户列表 * * @return 用户列表 */ @Override public List<SysUser> getUserList() { // Service层调用Dao层操作数据库 return sysUserMapper.getUserList(); } }测试类（test.SmTest）：public class SmTest { private static final Logger logger = Logger.getLogger(SmTest.class); /** * 查询用户列表 * * @return void */ @Test public void getUserList() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SysUserService sysUserService = (SysUserService) context.getBean("sysUserServiceImpl"); List<SysUser> sysUserList = sysUserService.getUserList(); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } } }示例效果：映射器整合方式mybatis-spring 提供了 MapperScannerConfigurer，可以扫描指定 Dao 包中的接口和 SQL 映射文件，将其注册为映射器，快速生成 Dao 层接口实现类，简化编码。示例：使用 MapperScannerConfigurer 注入映射器Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.duozai.dao"/> </bean> </beans>示例效果：声明式事务MySQL 事务概述事务是一组数据库操作的逻辑单元，这些操作要么全部成功执行，要么全部不执行，以确保数据的一致性和完整性。事务场景：你去银行转账，从一个账户扣款和在另一个账户收款这两个操作应该作为一个整体来完成，要么都成功（转账成功），要么都不发生（转账失败），不能出现钱从一个账户扣了，但另一个账户没收到的情况，这就是事务要保证的效果。事务的 ACID 特性：原子性（Atomicity）：事务中的所有操作是一个不可分割的整体，就像一个原子一样，要么所有操作都成功完成，事务提交；要么所有操作都失败回滚，回到事务开始前的状态。一致性（Consistency）：事务执行前后，数据库的状态必须保持一致，数据库要满足所有既定的约束条件，如数据类型约束、唯一性约束、外键约束等。隔离性（Isolation）：多个并发执行的事务之间应该相互隔离，互不干扰，隔离性可以防止事务之间出现数据不一致的问题。持久性（Durability）：一旦事务提交成功，其对数据库所做的修改就应该是永久性的，即使后续系统出现故障，如断电、服务器崩溃等，这些修改也不会丢失。并发场景下，事务引发的问题：脏读（Dirty Read）：当一个事务正在访问数据并且对数据进行了修改，此时还未提交到数据库中，这时另一个事务也访问并使用了这个数据，由于上个事务还未提交，此时读到的就是“脏数据”，根据“脏数据”所做的操作可能是不正确的。丢失修改（Lost to Modify）：当两个或多个事务同时修改同一数据时，后提交的事务可能会覆盖前一个事务所做的修改，导致前一个事务的更新被丢失，这可能会导致数据不一致性或意外的结果。死锁（Deadlock）：两个或多个事务相互等待对方释放资源，导致系统无法继续执行。不可重复读（Unrepeatable read)：一个事务在读取某个数据后，另一个事务修改了该数据并提交。当第一个事务再次读取同一数据时，得到的结果与之前不一致。幻读（Phantom read）：幻读与不可重复读类似，它发生在一个事务读取了几行数据，接着另一个并发事务插入了一些数据时，在随后的查询中，第一个事务就会发现一些原本不存在的记录。声明式事务概述在传统方案下，通过在业务方法中硬编码的方式进行事务控制，事务相关代码分散在业务方法中难以重用，复杂事务的编码难度高，增加了开发难度。Spring 提供了基于 AOP 实现的声明式事务处理机制，所有的工作均在配置文件中完成，将事务处理相关工作与业务代码分离开，极大地降低了开发和维护难度。配置声明式事务一般事务要求在做增删改操作时，如果产生异常，应该做事务回滚。在未配置声明式事务的情况下执行增删改操作，即使产生异常，也不会进行事务回滚，数据的增删改依然生效。示例：未配置声明式事务时存在的问题用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户id修改密码 * * @param id 用户id * @param newPassword 新密码 * @return SQL语句影响行数 */ int updatePassword(@Param("userId") Integer id, @Param("newPassword") String newPassword); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <update id="updatePassword"> UPDATE t_sys_user SET password = #{newPassword} WHERE id = #{userId} </update> </mapper>用户表业务逻辑接口（cn.duozai.dao.SysUserService）：public interface SysUserService { // ... /** * 根据用户id修改密码 * * @param id 用户id * @param newPassword 新密码 * @return 修改结果 */ boolean updatePassword(Integer id, String newPassword); }用户表业务逻辑接口实现类（cn.duozai.dao.SysUserServiceImpl）：@Service public class SysUserServiceImpl implements SysUserService { @Resource SysUserMapper sysUserMapper; // ... /** * 根据用户id修改密码 * * @param id 用户id * @param newPassword 新密码 * @return 修改结果 */ @Override public boolean updatePassword(Integer id, String newPassword) { int result = sysUserMapper.updatePassword(id, newPassword); System.out.println(1/0); // 抛出异常，应该做事务回滚 // 如果没有配置声明式事务，则事务不会回滚 return result > 0; } }测试类（test.SmTest）：public class SmTest { // ... /** * 根据用户id修改密码 * * @return void */ @Test public void updatePassword() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SysUserService sysUserService = (SysUserService) context.getBean("sysUserServiceImpl"); boolean result = sysUserService.updatePassword(1, "000111"); if(result) { logger.debug("修改成功"); } else { logger.debug("修改失败"); } } }示例效果：声明式事务关注的核心问题是对哪些方法，采取什么样的事务策略。配置声明式事务的基本属性：名称描述propagation事务传播机制，默认值为 REQUIRED如果当前存在事务，那么该方法将会在该事务中运行如果当前没有事务，那么它会启动一个新的事务isolation事务隔离级别，默认值为 DEFAULT，使用数据库默认的事务隔离级别timeout事务超时时间，单位为秒，超过给定时间自动回滚read-only事务是否为只读，默认为 false对于只执行查询功能的事务，设置为 true 能提高事务处理性能rollback-for设定能够触发事务回滚的异常类型no-rollback-for设定不触发回滚的异常类型示例：使用 XML 配置声明式事务Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务配置2-设置事务属性 <tx:advice>元素节点：设置事务属性 id属性：事务属性配置名称 transaction-manager属性：关联的事务管理器，默认值为transactionManager --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- <tx:attributes>元素节点：设置一组事务属性 <tx:method>元素节点：针对某一些方法设置事务属性 name属性：匹配方法名称 propagation属性：事务传播属性，一般设置为REQUIRED isolation属性：事务隔离级别 --> <tx:attributes> <tx:method name="update*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pointcut" expression="execution(* cn.duozai.service..*.*(..))"/> <!-- <aop:advisor>标签：定义事务切面 advice-ref属性：关联事务属性配置名称 pointcut-ref属性：关联切入点表达式 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/> </aop:config> </beans>示例效果：使用注解实现声明式事务Spring 支持使用注解的方式结合少量配置来实现声明式事务。示例：使用注解配置声明式事务Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>用户表业务逻辑接口实现类（cn.duozai.dao.SysUserServiceImpl）：/** * @Transactional注解：开启声明式事务 */ @Transactional @Service public class SysUserServiceImpl implements SysUserService { // ... }
AI 摘要 文章系统梳理 SSM 框架中 IoC 与 AOP 的扩展用法：先以 XML 方式演示异常抛出、最终、环绕等五种增强，并对比前置、后置场景；随后展开构造注入、p 命名空间、集合等依赖注入技巧，比较 setter 与构造优劣；继而用 @Component、@Service、@Autowired、@Resource 等注解简化 IoC，结合 @Aspect、@Around 等注解实现零配置 AOP，给出完整示例与最佳实践，帮助开发者在企业轻量级项目中灵活选用配置方式。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结增强类型扩展异常抛出增强异常抛出增强是指当目标方法抛出异常时进行织入操作的一种增强方式，常用于为项目提供统一的异常处理功能，具有可灵活插拔的特点。示例：异常抛出增强用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：public class UserServiceImpl implements UserService { // ... /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 int result = userDao.save(); // 模拟产生异常 System.out.println(1/0); return result > 0; } }日志增强处理类（cn.duozai.aop.UserServiceLogger）：public class UserServiceLogger { // ... /** * 异常抛出增强：方法抛出异常时做增强 * * @param jp 连接点对象 * @param ex 具体异常对象 * @return void */ public void afterThrowing(JoinPoint jp, Exception ex) { logger.debug(jp.getSignature().getName() + "方法抛出异常: " + ex.getMessage()); } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config> <aop:aspect ref="userServiceLogger"> <!-- <aop:after-throwing>标签：定义异常抛出增强，在方法抛出异常时做额外操作 throwing属性：指定为获取异常对象并传递给增强方法中的ex参数 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> </aop:config> </beans>示例效果：最终增强最终增强是指无论目标对象的方法正常运行还是抛出异常，该增强处理都会被执行的一种增强方式，与 Java 中的 finally 代码块的作用相似，常用于释放资源等功能，具有可灵活插拔的特点。示例：最终增强日志增强处理类（cn.duozai.aop.UserServiceLogger）：public class UserServiceLogger { // ... /** * 最终增强：方法执行完毕，无论是否抛出异常，都会执行增强操作 * * @param jp 连接点对象 * @return void */ public void after(JoinPoint jp) { logger.debug(jp.getSignature().getName() + "方法执行结束"); } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config> <aop:aspect ref="userServiceLogger"> <!-- <aop:after>标签：定义最终增强，无论方法是否抛出异常，都会执行增强方法 --> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>示例效果：环绕增强环绕增强是指在目标对象方法前后都可以进行织入的一种增强处理方式，在环绕增强中，可以获取或修改目标方法的参数、返回值，可以对它进行异常处理，甚至可以决定目标方法是否执行。示例：环绕增强日志增强处理类（cn.duozai.aop.UserServiceLogger）：public class UserServiceLogger { // ... /** * 环绕增强 * * @param jp 连接点对象，是JoinPoint的子接口 * @return 方法返回值对象 */ public Object around(ProceedingJoinPoint jp) { logger.debug(jp.getSignature().getName() + "方法开始执行"); Object result = null; try { // 调用真正的目标方法 result = jp.proceed(); logger.debug("方法执行后，返回：" + result); } catch (Throwable ex) { logger.debug(jp.getSignature().getName() + "方法发生异常: " + ex.getMessage()); } finally { logger.debug(jp.getSignature().getName() + "方法执行结束"); } return result; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config> <aop:aspect ref="userServiceLogger"> <!-- <aop:around>标签：定义环绕增强 --> <aop:around method="around" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>示例效果：增强总结前置增强（before）：作用时机：在目标方法执行之前执行增强逻辑。使用场景：记录方法开始执行的相关信息，如记录开始时间、传入的参数情况等。后置增强（afterReturning）：作用时机：在目标方法正常执行完成后（没有抛出异常的情况下）执行增强逻辑。使用场景：对方法返回结果进行加工、做一些补充的日志记录用于记录方法成功执行后的状态，便于后续的分析排查问题等。异常抛出增强（afterThrowing）：作用时机：当目标方法执行过程中抛出异常时被触发。使用场景：统一的异常处理机制，避免异常直接抛给上层调用者导致系统不稳定，也方便对异常进行归类、记录和按特定规则反馈异常相关提示等。最终增强（after）：作用时机：不管目标方法是正常执行完毕还是抛出异常，最终增强里的逻辑都会执行。使用场景：资源清理工作，如关闭文件流、释放数据库连接、清理一些临时占用的内存缓存等。环绕增强（around）：作用时机：在目标方法执行前、执行时、执行后都进行相关逻辑控制，相当于把前置、后置等增强的功能融合在了一起，并且可以灵活决定目标方法是否真的要执行。使用场景：对方法执行的整个流程进行全面控制，常用于需要精细控制目标方法执行过程、进行复杂的事务管理（比如决定事务的提交、回滚时机等）以及整合多个不同阶段的处理逻辑的场景。依赖注入方式扩展构造注入构造注入是指 Spring 通过构造方法为属性赋值的一种注入方式，可以在对象初始化时为属性赋值，具有良好的实效性。构造注入的基本用法：<bean id="..." class="..."> <constructor-arg name="构造方法中的参数名称" ref="要注入的依赖对象" /> <constructor-arg name="构造方法中的参数名称" value="要注入的值" /> </bean>构造注入能保证依赖不可变、方便依赖关系初始化及利于单元测试，但是构造函数参数多易使代码臃肿，处理可选依赖不太便捷。示例：构造注入用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：public class UserServiceImpl implements UserService { /** * 用户模块数据访问层对象，通过Spring进行注入 */ UserDao userDao; /** * 设置用户模块数据访问层对象 * 借助set方法来设置依赖对象：setter注入/构造注入 * * @param userDao 用户模块数据访问层对象 * @return void */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** * 构造函数 * 借助构造方法来设置依赖对象：构造注入 * * @param userDao 用户模块数据访问层对象 * @return null */ public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 int result = userDao.save(); return result > 0; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="cn.duozai.dao.UserDaoImpl"/> <bean id="userService" class="cn.duozai.service.UserServiceImpl"> <!-- <property>标签：依赖set方法进行注入，即set注入/设值注入 --> <!-- <property name="userDao" ref="userDao"/> --> <!-- <constructor-arg>：构造函数注入 name属性：构造函数的参数名 ref属性：引用spring容器中bean对象 value属性：构造函数的参数值 --> <constructor-arg name="userDao" ref="userDao"/> </bean> </beans>示例效果：p 命名空间注入使用 Spring 做属性注入时，需要使用 property 标签，使用 p 命名空间，可以简化 property 标签，但其本质上还是 setter 注入。p 命名空间的基本用法：<bean id="..." class="..." p:属性="要注入的值"/> <bean id="..." class="..." p:属性-ref="要注入的依赖对象"/>在 Spring 配置文件中使用 p 命名空间，需要先添加 p 命名空间的声明。p 命名空间的声明：xmlns:p="http://www.springframework.org/schema/p"p 命名空间的特点是使用属性而不是子元素的形式配置 Bean 的属性。示例：p 命名空间注入Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="cn.duozai.dao.UserDaoImpl"/> <bean id="userService" class="cn.duozai.service.UserServiceImpl" p:userDao-ref="userDao"/> </beans>不同数据类型的注入Spring 注入基本数据类型、字符串的基本用法：<bean id="..." class="..."> <property name="..."> <value></value> </property> <property name="..." value=""/> </bean>Spring 注入对象的基本用法：<bean id="..." class="..."> <property name="..." ref="..."/> </bean>Spring 注入空字符串的基本用法：<bean id="..." class="..."> <property name="..."> <value></value> </property> </bean>Spring 注入 null 值的基本用法：<bean id="..." class="..."> <property name="..."> <null/> </property> </bean>Spring 注入 List 的基本用法：<bean id="..." class="..."> <property name="..."> <value>...</value> <value>...</value> </property> </bean>Spring 注入 Set 的基本用法：<bean id="..." class="..."> <property name="..."> <set> <value>...</value> <value>...</value> </set> </property> </bean>Spring 注入 Map 的基本用法：<bean id="..." class="..."> <property name="..."> <entry key="..." value="..."/> <entry key="..." value="..."/> </property> </bean>Spring 注入 Properties 的基本用法：<bean id="..." class="..."> <props name="..."> <prop key="..." value="..."/> <prop key="..." value="..."/> </property> </bean>注入总结setter 设值注入：原理：依赖对象通过类的 Setter 方法注入到目标对象中。优点：可选依赖配置灵活，代码可读性较好。缺点：可能导致对象处于不一致状态，不利于对象的不可变特性维护。构造注入：原理：通过类的构造方法来注入依赖。优点：依赖不可变，在对象构造完成后所有必需的依赖都已经被完全初始化。缺点：构造方法参数过多时变得复杂，不够灵活，不适合可选依赖场景。p 命名空间注入：原理：本质上还是 setter 设值注入，使用属性而不是子元素的形式配置 Bean 的属性。在 Spring 配置文件中使用 p 命名空间，需要先添加 p 命名空间的声明。IoC 注解实现使用 Spring 注解实现 IoC当项目越来越庞大时，使用 XML 配置 IoC，需要编写大量代码，不利于项目维护，从 Spring 2.0 开始引入注解的配置方式来优化 XML 配置。使用 Spring 注解实现 IoC，需要依赖 Spring AOP，故使用 Spring 注解时，需要同时导入 IoC 和 AOP 相关依赖和命名空间，并开启 Spring 注解扫包，才能使用注解实现 IoC。Spring IoC 的基本注解：名称描述@Component将一个类定义为 Spring 的 Bean 组件，与 XML 配置中的 bean 标签作用相同@Repository作用同 @Component，用于标注 DAO 类@Service作用同 @Component，用于标注 Service 类@Controller作用同 @Component，用于标注 Controller 类@Autowired根据对象类型注入 Bean 对象@Qualifier与 @Autowired 组合使用，根据 Bean 对象名称注入 Bean 对象示例：使用 Spring 注解实现 IoC用户模块数据访问接口实现类（cn.duozai.dao.UserDaoImpl）：/** * @Repository注解：标记为Bean对象，即将该类交给Spring管理 * value属性：Bean对象的名称，默认为类名首字母小写 * 相当于 <bean id="userDao" class="cn.duozai.dao.UserDaoImpl"> */ @Repository public class UserDaoImpl implements UserDao { private static final Logger logger = Logger.getLogger(UserDaoImpl.class); /** * 保存用户信息 * * @return SQL语句影响行数 */ @Override public int save() { logger.debug("保存用户信息到数据库"); return 1; } }用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：/** * @Service注解：标记为Bean对象，即将该类交给Spring管理 */ public class UserServiceImpl implements UserService { /** * Service定义对于DAO层的引用，从Spring中注入DAO * @Autowired注解：注入类型为UserDao的Bean对象 */ @Autowired UserDao userDao; /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 int result = userDao.save(); return result > 0; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="cn.duozai"/> </beans>测试类（test.SpringTest）：public class SpringTest { /** * Spring测试 * * @return void */ @Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 使用@Service注解标记UserServiceImpl时，没有为其取名，其默认为类名首字母小写 UserService userService = (UserService) applicationContext.getBean("userServiceImpl"); userService.save(); } }示例效果：@Autowired 注解默认会根据对象的类型来注入对应的 Bean 对象，当出现相同类型的 Bean 对象时，就会报错，可以结合 @Qualifier 注解根据 Bean 的名称进行注入。示例：使用 Autowired + Qualifier 实现 IoC用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：@Service public class UserServiceImpl implements UserService { /** * Service定义对于DAO层的引用，从Spring中注入DAO * @Autowired注解：注入类型为UserDao的Bean对象 * @Qualifier注解：注入名称为userDao的Bean对象 */ @Autowired @Qualifier("userDao") UserDao userDao; // ... }使用 Java 标准注解完成装配Spring 支持使用 Java 标准注解 @Resource 进行Bean 对象的注入。@Resource 注解默认根据对象的名称进行 Bean 对象注入，当通过对象名称无法找到 Bean 对象时，会自动按照对象的类型进行 Bean 对象注入。示例：使用 Java 标准注解完成装配用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：@Service public class UserServiceImpl implements UserService { /** * Service定义对于DAO层的引用，从Spring中注入DAO * @Resource注解：先根据名称注入，后根据类型注入 */ @Resource UserDao userDao; // ... }装配注解总结@Autowired：提供者：Spring 提供的注解。作用：根据类型自动寻找匹配的 Bean 进行注入，如果在容器中有多个同一类型的 Bean 时，可以结合 @Qualifier 注解来指定具体要注入的 Bean 名称。IntelliJ IDEA 中不推荐使用 @Autowired 注解进行装配。@Resource：提供者：Java EE 的标准注解（javax.annotation.Resource）。作用：既可以按照名称进行注入（默认先按名称查找，如果没找到再按类型查找），也可以按照类型进行注入。@Inject：提供者：Java 依赖注入规范（JSR 330）中的注解。作用：与 @Autowired 类似，主要依据类型来注入 Bean。AOP 注解实现AspectJ 概述AspectJ 是一个面向切面的框架，它扩展了 Java 语言，定义了 AOP 语法，能够在编译期提供代码的织入。@AspectJ 是 AspectJ 5 新增的功能，使用 JDK 5.0 注解技术和正规的 AspectJ 切点表达式语言描述切面。Spring 通过集成 AspectJ 框架实现了以注解的方式定义切面，使得配置文件的代码大大减少。使用 Spring 注解标注切面使用 Spring 注解实现 AOP，需要同时导入 IoC 和 AOP 相关依赖和命名空间，并开启 Spring 注解扫包，才能使用注解实现AOP。Spring AOP 的基本注解：名称描述@Aspect将一个类标记为切面，里面包含了一些针对其他类的横切关注点的处理逻辑@Before定义前置增强方法@AfterReturning定义后置增强方法@AfterThrowing定义异常抛出增强方法@After定义最终增强方法@Around定义环绕增强方法示例：使用 Spring 注解标注切面用户模块数据访问接口实现类（cn.duozai.dao.UserDaoImpl）：@Repository public class UserDaoImpl implements UserDao { private static final Logger logger = Logger.getLogger(UserDaoImpl.class); /** * 保存用户信息 * * @return SQL语句影响行数 */ @Override public int save() { logger.debug("保存用户信息到数据库"); return 1; } }用户模块业务逻辑接口实现类（cn.duozai.dao.UserServiceImpl）：@Service public class UserServiceImpl implements UserService { @Resource UserDao userDao; /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 int result = userDao.save(); return result > 0; } }日志增强处理类（cn.duozai.aop.UserServiceLogger）：/** * @Aspect注解：标记为切面类 = 切入点+增强处理 * @Component注解：标记为Bean对象 */ @Aspect @Component public class UserServiceLogger { private static final Logger logger = Logger.getLogger(UserServiceLogger.class); /** * 环绕增强 * * @param jp 连接点对象，是JoinPoint的子接口 * @return 方法返回值对象 */ @Around("execution(boolean save())") // 标记环绕增强 public Object around(ProceedingJoinPoint jp) { logger.debug(jp.getSignature().getName() + "方法开始执行"); Object result = null; try { // 调用真正的目标方法 result = jp.proceed(); logger.debug("方法执行后，返回：" + result); } catch (Throwable ex) { logger.debug(jp.getSignature().getName() + "方法发生异常: " + ex.getMessage()); } finally { logger.debug(jp.getSignature().getName() + "方法执行结束"); } return result; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="cn.duozai"/> <aop:aspectj-autoproxy/> </beans>示例效果：
AI 摘要 文章系统梳理 Spring 轻量级企业框架的诞生背景、模块体系与两大核心技术：IoC 通过依赖注入与工厂解耦，实现 Bean 生命周期托管；AOP 借切面、切入点、通知等概念，把日志、事务等横切逻辑动态织入业务对象，全程以 XML 配置示例演示从 HelloWorld 到打印机装配再到日志增强的完整流程，为后续深入 SSM 开发奠定理论与实战基础。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结Spring 概述Spring 历史由来Spring 是一个轻量级的企业级应用框架，兴起于 2003 年，当时流行的传统 JavaEE 框架均为过于臃肿的重量级架构体系，其开发效率、开发难度和实际性能都不能满足人们的需求。Spring 中文含义为“春天”，其诞生给人一种格外清新的感觉，蕴含着勃勃生机。Spring 最初由 Rod Johnson 在《Expert One-on-One Java EE 设计与开发》一书中的部分理念衍生而来的，在书中，他对 Java EE 技术的日益臃肿和低效提出了质疑，并提出了 Interface 21，即 Spring 的雏形。2003 年 2 月，Spring 正式成为一个开源项目，并发布于 SourceForge 中。Spring 文档：https://www.spring.io/Spring 是企业应用开发的一站式选择，贯穿于表现层、业务层、持久层。Spring 的优点：低侵入式设计：Spring 通过依赖注入、面向接口编程等方式管理对象关系，业务代码无需大量耦合特定 Spring 框架代码，改动实现细节时对依赖它的其他组件影响小。独立于各种应用服务器：Spring 基于自身的轻量级容器和组件化架构，能在多种应用服务器（如 Tomcat、Jetty 等）上运行，无需依赖特定应用服务器的独有特性来实现核心功能。依赖注入特性将组件关系透明化，降低耦合度。面向切面编程特性允许将通用任务进行集中式处理。与第三方框架的良好整合：Spring 像是一个超级黏合平台，能方便且灵活地与各类如 MyBatis、Hibernate 等第三方框架协同工作，实现功能互补、优势叠加。Spring 体系结构Spring 由大约 20 个功能模块组成。Spring 核心容器 Core Container：Beans 模块：负责创建和管理对象（即 Java Bean）。Core 模块：Spring 的核心基础，提供了一些最基本的功能和工具，如对资源的访问、读取配置文件、处理一些基础的类型转换、事件传播等功能。Context 模块：Spring 的大管家，提供提供应用上下文环境、负责管理 Bean 的生命周期等。SpEL 模块（Spring Expression Language）：专门用来在 Spring 里写表达式的语言。数据访问与集成 Data Access and Integration：JDBC 模块：简化了使用 JDBC 进行数据库访问的操作，让开发人员能更便捷地执行 SQL 语句、处理结果集以及管理数据库连接等相关事宜。ORM 模块：用于无缝衔接 Spring 框架与各类 ORM 工具（如 MyBatis 等），使在 Spring 应用中利用 ORM 技术进行对象关系映射及数据操作更加方便、高效。OXM 模块：用于在 Spring 框架中方便地实现对象与 XML 数据之间的相互转换以及对 XML 相关处理进行简化和规范化操作。JMS 模块：便于在 Spring 应用中实现与消息队列系统进行高效的消息发送、接收以及对 JMS（Java Message System）相关操作的管理和集成。Transactions：用于在 Spring 应用中方便地管理和控制数据库事务，提供了声明式和编程式两种方式来确保数据操作的一致性、隔离性等事务特性。Spring Web：Web 模块：为 Spring 应用提供了处理 Web 请求与响应、实现 Web 应用相关功能（如路由、视图处理等）的能力，以便构建功能完备的 Web 应用程序。Servlet 模块：用于让 Spring 应用更好地与 Servlet 容器（如 Tomcat 等）协同工作，方便在基于 Servlet 技术的 Web 应用中实现请求处理、资源调度等功能。WebSocket 模块：让 Spring 应用便捷地实现与浏览器等客户端间实时、双向的 WebSocket 通信功能，用于高效的数据交互与实时信息传递。Portlet 模块：助力在 Spring 应用中方便地开发、部署和管理基于 Portlet 规范的 Web 组件，以实现可嵌入到门户页面（Portal）中的特定功能。Spring 其他：AOP：用于在不修改原始代码的基础上，通过定义切面实现对业务逻辑中横切关注点（如日志记录、事务管理等）的统一处理，增强代码的可维护性和功能扩展性。Aspects：在 Spring 框架中用于方便地实现基于 AspectJ 的 AOP（面向切面编程）功能，辅助定义切面、切点及增强处理等以处理横切关注点。Instrumentation：用于在 Spring 应用中实现对 Java 字节码的动态修改、监测等操作，以便更好地进行性能分析、运行时监控等。Messaging：用于让 Spring 应用方便地进行消息的发送、接收以及对消息传递相关流程（如队列管理等）进行有效管理与集成。Test：用于在 Spring 应用中便捷地开展各类测试工作，包括对 Bean、业务逻辑及 Web 请求处理等方面的测试，简化测试流程并提供相关测试工具。Spring 核心Spring 设计理念：面向 Bean 的编程，Bean 即被 Spring 容器所管理的 Java 对象。Spring 两大核心技术：控制反转 IoC 和依赖注入 DI。面向切面编程 AOP。Spring IoCIoC 概述IoC 全称 Inversion of Control，即控制反转，意为把对资源的控制权反转过来。IoC 不是一项开发技术，也不是具体功能，而是面向对象编程中的一种设计理念。DI 全称 Dependency Injection，即依赖注入，表示将依赖的对象注入需要的类中，是 IoC 设计思想的一种具体实现方式。控制反转初体验用户模块业务逻辑层调用数据访问层，在用户模块业务逻辑层中要定义一个数据访问层对象，模块之间的耦合度较高，可以使用工厂家族（简单工厂或工厂方法）解决业务逻辑层和数据访问层之间的耦合性问题。示例：控制反转初体验用户模块数据访问接口（cn.duozai.dao.UserDao）：public interface UserDao { /** * 保存用户信息 * * @return SQL语句影响行数 */ int save(); }用户模块数据访问接口实现类（cn.duozai.dao.UserDaoImpl）：public class UserDaoImpl implements UserDao { /** * 保存用户信息 * * @return SQL语句影响行数 */ @Override public int save() { System.out.println("保存用户信息到数据库"); return 1; } }用户模块数据访问层工厂（cn.duozai.factory.UserDaoFactory）：public class UserDaoFactory { /** * 负责创建用户DAO实例 * * @return 用户模块数据访问层实现类 */ public static UserDao getInstance() { return new UserDaoImpl(); } }用户模块业务逻辑接口（cn.duozai.service.UserService）：public interface UserService { /** * 保存用户信息 * * @return 保存结果 */ boolean save(); }用户模块业务逻辑接口实现类（cn.duozai.service.UserServiceImpl）：public class UserServiceImpl implements UserService { // 实例化依赖的UserDao（自己实例化） // UserDao userDao = new UserDaoImpl(); // 实例化依赖的UserDao（通过工厂获取，实例化UserDao的权限交给工厂） UserDao userDao = UserDaoFactory.getInstance(); /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 int result = userDao.save(); return result > 0; } }用户模块业务逻辑层调用数据访问层，原本需要在业务逻辑层中实例化数据访问层对象，现在对于数据访问层实例化的工作，交给了工厂，即业务逻辑层获取依赖对象时的“控制权/实例化权”发生了反转，这就是所谓的控制反转。第一个 Spring IoC 程序实现 Spring IoC 的基本步骤：导入 JAR 依赖包：Spring 相关 JAR 文件。创建项目，并将所需的 JAR 文件添加为库。编写相关类。编写 Spring 核心配置文件 applicationContext.xml。编写测试类、测试方法。示例：第一个 Spring IoC 程序导入相关依赖 JAR 文件，并添加为库。示例效果：Log4J 配置文件（classpath:log4j.properties）：log4j.rootLogger=debug,CONSOLE ################## # Console Appender ################## log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=debug log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%nHelloSpring 实体类（cn.duozai.HelloSpring）：public class HelloSpring { private static final Logger logger= Logger.getLogger(HelloSpring.class); /** * hello属性 */ private String hello; /** * 输出方法 */ public void print() { logger.debug("Spring说：" + hello); } public String getHello() { return hello; } public void setHello(String hello) { this.hello = hello; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 一般情况下：类型 对象名 = new 类型(); 控制反转：通过<bean>标签声明需要Spring创建的实例，让Spring实例化对象 class属性：指定该对象实例的类型 id属性：该对象实例的名称 --> <bean id="helloSpring" class="cn.duozai.HelloSpring"> <!-- 实例化的过程中，给helloSpring对象设置name属性，即注入 <property>标签：调用set方法设置对象中的属性 name属性：对应的set方法，setXxx value属性：要设置的值 --> <property name="hello" value="反转的人生如此惊艳"/> </bean> </beans>测试类（test.SpringTest）：public class SpringTest { /** * Spring测试 * * @return void */ @Test publicvoidtest() { // 1、读取Spring配置文件，实例化Spring上下文对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2、根据bean-id获取Bean对象实例 HelloSpring helloSpring = (HelloSpring) applicationContext.getBean("helloSpring"); // 3、执行print方法 helloSpring.print(); } }示例效果：Spring 会根据配置文件中的信息，自动创建对应的实例对象，并通过 setter 方法为其属性进行赋值，实例的属性值将不再由程序中的代码来主动创建和管理，改为被动接收 Spring 的注入，这就是 IoC/DI 的一种具体实现。使用 Spring IoC 实现 Java Bean 注入使用 Spring IoC，可以实现 Java Bean 的注入。示例：使用 Spring IoC 实现 Java Bean 注入纸张接口（cn.duozai.Paper）：public interface Paper { /** * 获取纸张类型 * * @return 纸张类型 */ String getPaper(); }A4 纸张实现类（cn.duozai.A4Paper）：public class A4Paper implements Paper{ /** * 获取纸张类型 * * @return 纸张类型 */ @Override public String getPaper() { return "A4纸"; } }B5 纸张实现类（cn.duozai.B5Paper）：public class B5Paper implements Paper { /** * 获取纸张类型 * * @return 纸张类型 */ @Override public String getPaper() { return "B5纸"; } }墨盒接口（cn.duozai.Ink）：public interface Ink { /** * 获取墨盒颜色 * * @return 墨盒颜色 */ String getColor(); }彩色墨盒实现类（cn.duozai.ColorInk）：public class ColorInk implements Ink { /** * 获取墨盒颜色 * * @return 墨盒颜色 */ @Override public String getColor() { return "彩色"; } }灰色墨盒实现类（cn.duozai.GrayInk）：public class GrayInk implements Ink { /** * 获取墨盒颜色 * * @return 墨盒颜色 */ @Override public String getColor() { return "灰色"; } }打印机类（cn.duozai.Printer）：public class Printer { private static final Logger logger = Logger.getLogger(Printer.class); /** * 墨盒对象 */ private Ink ink; /** * 设置打印机使用的墨盒 * * @param ink 具体的墨盒实现类对象 * @return void */ public void setInk(Ink ink) { this.ink = ink; } /** * 纸张对象 */ private Paper paper; /** * 设置打印机使用的纸张 * * @param paper 具体的纸张实现类对象 * @return void */ public void setPaper(Paper paper) { this.paper = paper; } /** * 打印所使用的墨盒和纸张信息 */ public void print() { logger.debug("墨盒：" + ink.getColor()); logger.debug("纸张：" + paper.getPaper()); } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a4Paper" class="cn.duozai.A4Paper"/> <bean id="b5Paper" class="cn.duozai.B5Paper"/> <bean id="colorInk" class="cn.duozai.ColorInk"/> <bean id="grayInk" class="cn.duozai.GrayInk"/> <bean id="printer" class="cn.duozai.Printer"> <property name="ink" ref="colorInk"/> <property name="paper" ref="a4Paper"/> </bean> </beans>测试类（test.SpringTest）：public class SpringTest { /** * Spring测试 * * @return void */ @Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Printer printer = (Printer) applicationContext.getBean("printer"); printer.print(); } }示例效果：Spring 使用 DI 的方式实现了 Java 类之间的解耦合，使 Bean 与 Bean 之间不再产生直接的依赖关系，而是通过 Spring 统一建立桥梁，Bean 中只声明需要依赖的接口类型，最终注入的具体实现类则由 Spring 灵活配置，这也就是“面向接口编程”。Spring AOPAOP 概述面向切面编程（AOP，Aspect Oriented Programming）是软件编程思想发展到一定阶段的产物，是对面向对象编程（OOP，Object Oriented Programming）的有益补充。AOP 一般适用于具有横切逻辑的场合，如访问控制、事务管理、日志记录、性能监测等。public class UserServiceImpl implements UserService { private static final Logger log = Logger.getLogger(UserServiceImpl.class); public boolean save(User user){ log.info("添加用户" + user.getUsername()); //记录日志 SqlSession sqlSession = null; boolean flag = false; try { //异常处理 sqlSession = MyBatisUtil.createSqlSession(); if(sqlSession.getMapper(userMapper.class).add(user) > 0) { flag = true; } sqlSession.commit(); //事务控制 log.info("成功添加用户" + user.getUsername()); //记录日志 } catch (Exception e){ log.error("添加用户" + user.getUsername() + " 失败", e); //记录日志 sqlSession.rollback(); //事务控制 flag = false; } finally { MyBatisUtil.closeSqlSession(sqlSession); } return flag; } }在业务系统中，总有一些散落、渗透到系统各处且不得不处理等事情，如日志、异常处理、事务控制等，这些穿插在既定业务中的操作就是所谓的“横切逻辑”，也就是所谓的切面。这一些“横切逻辑”操作，一般会抽取出来，放在一个公共的类或方法中去处理，以实现业务系统和横切逻辑的解耦。面向切面编程的目标就是在不改变原有的程序的基础上为代码片段增加新的功能，对其进行增强处理，其设计思想来源于代理模式。AOP 中的基本概念：面向切面编程（AOP）：给程序添加一些额外功能的特殊方式，不用改动原来主要业务代码的结构，就能在合适的地方悄悄“插入”日志记录、权限验证、性能统计之类的通用功能。切面（Aspect）：把一些分散在程序各处、但功能类似的通用操作（比如多处的日志记录代码）集中起来管理，规定好在哪些业务流程节点（比如方法调用前、调用后等）要做这些通用操作，就好像是一个关于这些通用功能怎么执行的整体规划。切面包含增强处理和切入点。连接点（Join Point）：程序运行过程中，那些可以插入切面相关逻辑的具体时刻或动作，比如一个方法被调用、一个对象被创建、属性被访问等，就像是一些可供“下手”添加额外功能的具体时机。切入点（Pointcut）：从众多连接点里选出来的特定部分，明确指出到底在哪些具体的连接点上要应用切面里的逻辑，就好比是从很多可添加额外功能的时机里，挑出符合特定条件的那些时机。切入点一定是连接点，但连接点不一定是切入点。通知/增强处理（Advice）：切面里实际要执行的代码内容，也就是具体要做的通用功能操作，比如在方法调用前后记录日志等。目标对象（Target Object）：即原本的业务对象，切面相关逻辑要作用在它们上面，就像是要接受这些额外功能“装饰”的原始对象。织入（Weaving）：把切面里的增强处理按照切入点规定的地方，插入到目标对象的执行流程中去的过程，就像是把额外功能的操作按照选定的时机，准确“嵌入”到原始业务对象的运行过程中。AOP 代理（AOP Proxy）：由 AOP 框架所创建的对象，实现执行增强处理方法等功能，就像是接受了这些额外功能“装饰”之后的对象。第一个 Spring AOP 程序实现 Spring AOP 的基本步骤：导入 JAR 依赖包：Spring 相关 JAR 文件、Spring AOP 相关 JAR 文件。编写增强处理类。编写 Spring 核心配置文件 applicationContext.xml。编写测试类、测试方法。示例：第一个 Spring AOP 程序导入相关依赖 JAR 文件，并添加为库。示例效果：用户模块数据访问接口（cn.duozai.dao.UserDao）：public interface UserDao { /** * 保存用户信息 * * @return SQL语句影响行数 */ int save(); }用户模块数据访问接口实现类（cn.duozai.dao.UserDaoImpl）：public class UserDaoImpl implements UserDao { private static final Logger logger = Logger.getLogger(UserDaoImpl.class); /** * 保存用户信息 * * @return SQL语句影响行数 */ @Override public int save() { logger.debug("保存用户信息到数据库"); return 1; } }用户模块业务逻辑接口（cn.duozai.service.UserService）：public interface UserService { /** * 保存用户信息 * * @return 保存结果 */ boolean save(); }用户模块业务逻辑接口实现类（cn.duozai.service.UserServiceImpl）：public class UserServiceImpl implements UserService { /** * 用户模块数据访问层对象，通过Spring进行注入 */ UserDao userDao; /** * 设置用户模块数据访问层对象 * * @param userDao 用户模块数据访问层对象 * @return void */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** * 保存用户信息 * * @return 保存结果 */ @Override public boolean save() { // 调用userDao的方法保存用户信息 intresult = userDao.save(); return result > 0; } }Spring 核心配置文件（classpath:applicationContext.xml）：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="cn.duozai.dao.UserDaoImpl"/> <bean id="userService" class="cn.duozai.service.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> </beans>测试类（test.SpringTest）：public class SpringTest { /** * Spring测试 * * @return void */ @Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save(); } }示例效果：日志增强处理类（cn.duozai.aop.UserServiceLogger）：/** * 在增强处理类中主要定义增强方法，即要额外做的事情 */ public class UserServiceLogger { private static final Logger logger = Logger.getLogger(UserServiceLogger.class); /** * 前置增强：在方法执行之前要做的事情 * * @param jp 连接点方法对象 * @return void */ public void before(JoinPoint jp) { logger.debug("开始调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法"); } /** * 后置增强：方法执行之后做增强 * * @param jp 连接点方法对象 * @param result 原方法的返回值 * @return void */ public void afterReturning(JoinPoint jp, Object result) { logger.debug("方法执行后，返回：" + result); } }Spring 核心配置文件（classpath:applicationContext.xml）：<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userServiceLogger" class="cn.duozai.aop.UserServiceLogger"/> <!-- <aop:config>标签：配置Spring AOP --> <aop:config> <!-- <aop:pointcut>标签：定义切入点，指定让哪些方法做增强操作 expression：使用execution指定切入点表达式 --> <aop:pointcut id="pointcut" expression="execution(boolean save())"/> <!-- <aop:aspect>标签：配置切面 ref属性：引用包含增强方法的Bean，即增强处理类对象 切面 = 切入点 + 增强处理 --> <aop:aspect ref="userServiceLogger"> <!-- <aop:before>标签：定义前置增强，在方法开始执行前做额外操作 method属性：指定增强方法 pointcut-ref属性：引用切入点 告知程序pointcut-ref方法在执行之前先执行userServiceLogger中的before方法 --> <aop:before method="before" pointcut-ref="pointcut"/> <!-- <aop:after-returning>标签：定义后置增强，在方法正常执行结束时做额外操作 returning属性：指定为获取原有方法的返回值并传递给增强方法中的result参数 --> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> </aop:aspect> </aop:config> </beans>示例效果：
AI 摘要 文章系统讲解 MyBatis 动态 SQL 核心标签：if、where、choose-when-otherwise、foreach、set、trim 及分页实现。通过多条件查询、集合参数、部分字段更新等场景，演示如何借助标签智能拼接 SQL，避免空值导致语法错误，提升代码健壮性与可维护性。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结动态 SQL 处理简单的多参数查询if 标签通常情况下在实现多条件查询时，需要实现动态 SQL 的构建，如果没有实现动态 SQL 的构建，可能会出现 SQL 语句错误的问题。示例：多条件查询可能存在的问题用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据条件（Params）查询用户列表 * * @param realName 真实姓名 * @param roleId 用户角色id * @return 用户列表 */ List<SysUser> getUserListByParams(@Param("realName") String realName, @Param("rId") Integer roleId); // ... }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{rId} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（Params）查询用户列表 * * @return void */ @Test public void getUserListByParams() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 只查询真实姓名包含“刘”的用户，不根据用户角色id查询 List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByParams("刘", null); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } // ... }示例效果：查询条件不符合要求的，不应该拼接到 SQL 语句中。在 MyBatis 中，if 标签用于实现 SQL 语句的动态拼接功能，使用 if 标签可以有效地避免多条件查询拼接 SQL 语句时存在的隐患，保证了业务的正确性。示例：if 标签用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE <if test="realName != null"> realName LIKE CONCAT('%', #{realName}, '%') </if> <if test="rId != null"> AND roleId = #{rId} </if> </mapper>示例效果：where 标签MyBatis 提供了 where 标签，用于对 SQL 语句中的 where 关键字进行简化处理，直接替换 SQL 语句中的 where 关键字，可以智能地处理其内部多余的 and、or 等关键字，避免多余字符带来的语法错误。示例：if + where 标签测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（Params）查询用户列表 * * @return void */ @Test public void getUserListByParams() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 只根据用户角色id查询，不根据真实姓名查询 List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByParams(null, 2); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } // ... }示例效果：用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user <where> <if test="realName != null"> AND realName LIKE CONCAT('%', #{realName}, '%') </if> <if test="rId != null"> AND roleId = #{rId} </if> </where> </mapper>示例效果：choose + when + otherwise 标签choose 是一个组合标签，通常与 when、otherwise 标签配合使用。choose 标签的基本用法：<choose> <when test="判断条件"> ... </when> <when test="判断条件"> ... </when> ... <otherwise> ... </otherwise> </choose>choose、when、otherwise 实现了类似于 Java 中 switch 语句的功能。示例：choose + when + otherwise 标签用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据条件（Params）查询用户列表，三选一 * * @param account 账号 * @param realName 真实姓名 * @param roleId 用户角色id * @return 用户列表 */ List<SysUser> getUserListByChoose(@Param("account") String account, @Param("realName") String realName, @Param("rId") Integer roleId); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user <where> <choose> <when test="account != null"> AND account = #{account} </when> <when test="realName != null"> AND realName LIKE CONCAT('%', #{realName}, '%') </when> <otherwise> AND roleId = #{rId} </otherwise> </choose> </where> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（Params）查询用户列表，三选一 * * @return void */ @Test public void getUserListByChoose() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByChoose("limingxing", null, 2); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId() + " account => " + sysUser.getAccount()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：动态 SQL 处理集合参数foreach 标签foreach 标签用于在 SQL 语句中对集合进行迭代操作，更方便地构建出包含动态数量参数的 SQL 语句。foreach 标签的基本用法：<foreach collection="参数名称" item="元素别名" open="起始位置的拼接字符" close="结束位置的拼接字符" separator="元素之间的连接符"> ...#{元素别名} </foreach>使用 foreach 标签处理数组类型参数使用 foreach 标签可以遍历数组类型的参数，该参数在 foreach 标签中的参数名称默认为 array，可以使用 @Param 注解为参数取别名。示例：使用 foreach 标签处理数组类型参数用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户角色id数组查询用户列表 * * @param roleIds 用户角色id数组 * @return 用户列表 */ List<SysUser> getUserListByRoleIdArray(@Param("roleIdsArray") Integer[] roleIds); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE roleId in <foreach collection="roleIdsArray" item="item" open="(" separator="," close=")"> #{item} </foreach> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户角色id数组查询用户列表 * * @return void */ @Test public void getUserListByRoleIdArray() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); Integer[] roleIds = {1,2}; List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByRoleIdArray(roleIds); for (SysUser sysUser : sysUserList) { System.out.println("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：使用 foreach 标签处理 List 类型参数使用 foreach 标签可以遍历 List 类型的参数，该参数在 foreach 标签中的参数名称默认为 list，可以使用 @Param 注解为参数取别名。示例：使用 foreach 标签处理 List 类型参数用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户角色id列表查询用户列表 * * @param roleIds 用户角色id列表 * @return 用户列表 */ List<SysUser> getUserListByRoleIdList(List<Integer> roleIds); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE roleId in <foreach collection="list" item="item" open="(" separator="," close=")"> #{item} </foreach> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户角色id列表查询用户列表 * * @return void */ @Test public void getUserListByRoleIdList() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<Integer> roleIds = new ArrayList<>(); roleIds.add(1); roleIds.add(2); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByRoleIdList(roleIds); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：动态 SQL 处理更新功能set 标签通常情况下在实现修改功能时，需要实现动态 SQL 的构建，如果没有实现动态 SQL 的构建，可能会出现 SQL 语句错误的问题。示例：修改操作可能存在的问题用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 修改用户信息 * * @param sysUser 用户表实体类对象 * @return SQL语句影响行数 */ int updateSysUser(SysUser sysUser); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser"> UPDATE t_sys_user SET realName = #{realName}, phone = #{phone} WHERE id = #{id} </update> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 修改用户信息 * * @return void */ @Test public void updateSysUser() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 将要修改的用户信息保存到实体类中 // 根据用户id修改真实姓名 SysUser sysUser = new SysUser(); sysUser.setId(2); sysUser.setRealName("明星"); int result = sqlSession.getMapper(SysUserMapper.class) .updateSysUser(sysUser); MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：只对部分属性的值进行修改时，没有做修改的属性不应该拼接到 SQL 语句中。在 MyBatis 中，可以使用 set + if 标签实现 SQL 修改语句的动态拼接功能，set 标签直接替换 SQL 语句中的 set 关键字，可以智能地处理其内部多余的字符，避免多余字符带来的语法错误。示例：if + set 标签用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser"> UPDATE t_sys_user <set> <if test="realName != null"> realName = #{realName}, </if> <if test="phone != null"> phone = #{phone}, </if> </set> WHERE id = #{id} </update> </mapper>示例效果：动态 SQL 扩展trim 标签trim 标签可以实现类似 where、set 标签的功能，用于自动拼接或忽略 SQL 语句中的关键字。trim 标签的基本用法：<trim prefix="要拼接的前缀" suffix="要拼接的后缀" prefixOverrides="要忽略的前缀，用|隔开" suffixOverrides="要忽略的后缀，用|隔开"> ... </trim>trim 标签高度灵活，通用性强，可以根据实际业务逻辑精确控制要添加或去除的内容。示例：trim 标签用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 修改用户信息 * * @param sysUser 用户表实体类对象 * @return SQL语句影响行数 */ int updateSysUser(SysUser sysUser); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser"> UPDATE t_sys_user <trim prefix="set" suffixOverrides=","> <if test="realName != null"> realName = #{realName}, </if> <if test="phone != null"> phone = #{phone}, </if> </trim> WHERE id = #{id} </update> </mapper>分页实现MyBatis 并没有对分页功能做过多的处理，而是使用数据库自带的分页功能。MySQL 实现分页的方式：使用 LIMIT m, n 语法，其中 m = (页码 - 1) * 页大小，n = 页大小。使用 LIMIT a OFFSET b 语法，其中 a = 页大小，b = (页码 - 1) * 页大小。示例：分页查询用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 分页查询用户列表 * * @param m (页码-1)*页大小 * @param n 页大小 * @return 用户列表 */ List<SysUser> getUserListByPage(@Param("m") Integer m, @Param("n") Integer n); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user LIMIT #{m}, #{n} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 分页查询用户列表 * * @return void */ @Test public void getUserListByPage() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 定义页码和页大小 int pageIndex = 2; int pageSize = 5; // 计算 LIMIT m, n int m = (pageIndex-1)*pageSize; List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByPage(m, pageSize); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：
AI 摘要 文章系统梳理 MyBatis SQL 映射文件结构与最佳实践，围绕条件查询、结果映射、增删改操作及缓存机制展开。详述单参数、实体、Map、多参数四种入参方式，对比 resultType 自动映射与 resultMap 手动映射，演示 association、collection 嵌套处理一对一、一对多关系，给出驼峰转换与自动映射级别配置，总结 insert、update、delete 写法及事务控制要点，并说明一级、二级缓存原理与局限性，为 SSM 项目提供完整持久层参考。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结MyBatis 映射基础SQL 映射文件概述MyBatis 真正的特色在于 SQL 映射，功能强大，使用简单。SQL 映射文件中的顶级元素：名称描述mapperSQL 映射文件的根元素cache配置给定命名空间的缓存cache-ref从其他命名空间引用缓存配置resultMap用来描述数据库结果集和对象的对应关系sql可以重用的 SQL 块，也可以被其他语句引用insert映射插入语句update映射更新语句delete映射删除语句select映射查询语句编写 SQL 映射文件的注意事项：SQL 映射文件与该 Mapper 接口同名（实体类名 + Mapper），并放置在同一包路径下。以映射的 Mapper 接口的完全限定类名（包路径 + 接口名）作为 SQL 映射文件 mapper 元素的 namespace 属性。接口中的方法名与 SQL 映射文件中映射 SQL 语句的元素的 id 属性一一对应。MyBatis 条件查询实现单一条件查询在 MyBatis 中实现条件查询，如果仅传入一个基本数据类型或其包装类型或一个 String 类型的参数，MyBatis 框架的处理方式非常简单。select 元素实现单一条件查询的基本用法：// Mapper接口定义方法 ... 方法名(参数类型 参数名); ... WHERE xx = #{参数名} 使用 select 元素实现单一条件查询的注意事项：parameterType 属性表示 SQL 语句传入的参数类型，可以使用全限定名或别名。parameterType 属性指定参数类型为 string，是 java.lang.String 的别名，是 MyBatis 内建的类型别名。使用 #{...} 获取参数值时，MyBatis 会将其转换成 PreparedStatement 参数，并为其赋值。示例：单一条件查询用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据真实姓名模糊查询用户列表 * * @param realName 真实姓名 * @return 用户列表 */ List<SysUser> getUserListByRealName(String realName); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE realName LIKE CONCAT('%', #{realName}, '%') </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据真实姓名模糊查询用户列表 * @return void */ @Test public void getUserListByRealName() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByRealName("张"); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：使用 Java 对象入参实现多条件查询在 MyBatis 中实现多条件查询，可以将查询参数保存在实体类中，传入方法实现多条件查询。select 元素使用 Java 对象入参实现多条件查询的基本用法：// Mapper接口定义方法 ... 方法名(实体类类型 对象名); ... WHERE xx = #{实体类中的属性名} 使用 Java 对象入参，灵活性欠佳，参数受对象结构即实体类属性的限制。示例：使用 Java 对象入参实现多条件查询用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据条件（SysUser）查询用户列表 * * @param sysUser 用户表实体类对象 * @return 用户列表 */ List<SysUser> getUserListBySysUser(SysUser sysUser); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{roleId} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（SysUser）查询用户列表 * * @return void */ @Test public void getUserListBySysUser() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 封装SysUser查询条件 SysUser searchSysUser = new SysUser(); searchSysUser.setRealName("刘"); searchSysUser.setRoleId(2); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListBySysUser(searchSysUser); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：使用 Map 对象入参实现多条件查询在 MyBatis 中实现多条件查询，可以将查询参数保存在 Map 对象中，传入方法实现多条件查询。select 元素使用 Map 对象入参实现多条件查询的基本用法：// Mapper接口定义方法 ... 方法名(Map 对象名); ... WHERE xx = #{map-key} 使用 Map 对象入参，不受实体类设计的限制，可以灵活地传递所需要的参数。示例：使用 Map 对象入参实现多条件查询用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据条件（Map）查询用户列表 * * @param sysUserMap Map对象 * @return 用户列表 */ List<SysUser> getUserListByMap(Map<String, Object> sysUserMap); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE realName LIKE CONCAT('%', #{rName}, '%') AND roleId = #{rId} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（Map）查询用户列表 * * @return void */ @Test public void getUserListByMap() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 封装SysUserMap查询条件 Map<String, Object> sysUserMap = new HashMap<>(); sysUserMap.put("rName", "刘"); sysUserMap.put("rId", 2); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByMap(sysUserMap); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：使用多参数入参实现多条件查询MyBatis 允许直接对方法传入多个参数，以实现多参数条件查询。select 元素使用多参数入参实现多条件查询的基本用法：// Mapper接口定义方法 ... 方法名(@Param("参数名1") 参数1, @Param("参数名2") 参数2, ...); ... WHERE xx = #{@Param注解标记的参数名} 多参数入参，适合传递的参数的数量较少的简单场景。使用 select 元素 + 多参数入参实现多条件查询的注意事项：在 MyBatis 底层实现中，默认会把传入的参数转换成 Map 格式，且使用 param + 参数在列表中的位置作为 map-key，如 param1、param2。通常情况下，在方法中传入多个参数时，会使用 @Param 注解为参数命名。多参数入参时，select 元素不需要指定 parameterType 属性。示例：使用多参数入参实现多条件查询用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据条件（Params）查询用户列表 * * @param realName 真实姓名 * @param roleId 用户角色id * @return 用户列表 */ List<SysUser> getUserListByParams(@Param("realName") String realName, @Param("rId") Integer roleId); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{rId} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据条件（Params）查询用户列表 * * @return void */ @Test public void getUserListByParams() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByParams("刘", 2); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：MyBatis 结果映射resultType 自动映射在 select 元素中，resultType 属性用于指定查询结果映射的类型。resultType 属性自动映射功能允许将数据库查询结果自动映射到指定的 Java 对象类型。当设置了 resultType 属性后，MyBatis 会根据数据库表的字段名和 Java 对象的属性名进行自动匹配映射，如果字段名和属性名一致，或者可以通过一定的命名规则进行匹配，MyBatis 会自动将数据库中的数据填充到对应的 Java 对象属性中。示例：联表查询 resultType 自动映射用户表实体类（cn.duozai.entity.SysUser）：public class SysUser implements Serializable { // ... /** * 用户角色id */ private Integer roleId; /** * 用户角色名称 */ private String userRoleName; // ... }用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 查询用户列表，包含用户角色名称 * * @return 用户列表 */ List<SysUser> getUserListWithRoleName(); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT u.*, r.roleName FROM t_sys_user AS u, t_sys_role AS r WHERE u.roleId = r.id </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 查询用户列表，包含用户角色名称 * * @return void */ @Test public void getUserListWithRoleName() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListWithRoleName(); for (SysUser sysUser : sysUserList) { // realName能自动映射，实体类属性名与数据库字段名一致 // userRoleName不能自动映射，实体类属性名（userRoleName）与数据库字段名（roleName）不一致 logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为：" + sysUser.getUserRoleName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：resultMap 手动映射在 MyBatis 中，可以使用 resultMap 元素定义结果映射，对名称不同的结果集字段和实体类属性进行手动映射关系的绑定。resultMap 元素的基本用法：<resultMap id="映射规则名称" type="映射的结果类型"> <id column="数据库字段名" property="实体类属性名" /> <result column="数据库字段名" property="实体类属性名" /> </resultMap> ... 使用 resultMap 元素实现手动映射的注意事项：select 元素使用 resultMap 属性指定手动映射规则时，不能再使用 resultType 属性。使用 resultMap 元素进行手动映射，没有指定某些数据库字段和实体类属性的映射关系时，只要数据库字段名和实体类属性名一致，也会进行自动映射。示例：联表查询 resultMap 手动映射用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <!-- <resultMap>标签：手动指定字段和属性之间的映射关系规则 id属性：映射规则的名称 type属性：将查询结果映射成哪一个Java实体类 --> <resultMap id="userWithRoleName" type="cn.duozai.entity.SysUser"> <!-- <id>标签：映射主键字段 <result>标签：映射普通字段 property属性：实体类中的属性名 column属性：数据库中的字段名 --> <id column="id" property="id"/> <result column="roleName" property="userRoleName"/> </resultMap> SELECT u.*, r.roleName FROM t_sys_user AS u, t_sys_role AS r WHERE u.roleId = r.id </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 查询用户列表，包含用户角色名称 * * @return void */ @Test public void getUserListWithRoleName() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListWithRoleName(); for (SysUser sysUser : sysUserList) { // realName虽然没有手动绑定映射关系，但实体类属性名与数据库字段名一致，它也能自动映射 // 指定了手动映射规则，实体类属性名（userRoleName）与数据库字段名（roleName）绑定了关系 logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为：" + sysUser.getUserRoleName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：联表查询概述使用联表查询时，结果集中往往会包含与多个实体类相关的信息（在一方包含另一方的引用），此时即可使用嵌套结果映射解决数据封装问题。联表查询的情况：一对一：两个表中的记录之间存在一一对应的关系，如一个用户对应一个角色。一对多：一个实体可以与多个其他实体相关联，如一个用户有多个收货地址。多对一：多个实体可以与多个其他实体相关联，如一个学生可以选择多门课程，一门课程也可以有多个学生选修。使用 association 元素实现一对一嵌套结果映射实现一对一联表查询，可以在两个有一对一关系的实体类里，一方通过属性关联另一方，同时使用 association 元素在 SQL 映射文件中来描述一对一嵌套结果映射的关系，以实现联表查询时数据的正确整合与获取。association 元素用于处理一对一联表关系映射。association 元素的基本用法：<resultMap id="..." type="..."> <association property="关联的对象属性名" javaType="关联的对象类型"> <id column="..." property="..." /> <result column="..." property="..." /> </association> </resultMap>使用 resultMap 元素 + association 元素实现一对一嵌套结果映射的注意事项：因为结果映射的需要，要确保查询字段时所有的字段都是唯一的。association 元素可以其结合 resultMap 属性实现映射规则的复用。示例：联表查询一对一嵌套结果手动映射角色表实体类（cn.duozai.entity.SysRole）：public class SysRole implements Serializable { /** * 主键id */ private Integer id; /** * 角色编码 */ private String code; /** * 角色名称 */ private String roleName; // ... }用户表实体类（cn.duozai.entity.SysUser）：public class SysUser implements Serializable { // ... /** * 用户角色id */ private Integer roleId; /** * 用户角色名称 */ // private String userRoleName; /** * 关联用户角色表实体类对象 */ private SysRole sysRole; // ... }用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户角色id查询用户列表，包含角色信息 * * @param roleId 用户角色id * @return 用户列表 */ List<SysUser> getUserListByRoleId(@Param("roleId") Integer roleId); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <resultMap id="userWithRole" type="cn.duozai.entity.SysUser"> <id property="id" column="id"/> <result property="realName" column="realName"/> <!-- 在SysUser中嵌套了SysRole，需要把角色信息映射到SysUser中的SysRole <association>标签：实现一对一关系映射 property属性：SysUser中嵌套的SysRole对象的属性名 javaType属性：SysUser中嵌套的SysRole对象的类型 --> <association property="sysRole" javaType="cn.duozai.entity.SysRole"> <id property="id" column="rid"/> <result property="roleName" column="roleName"/> <result property="code" column="code"/> </association> </resultMap> SELECT u.*, r.id AS rid, r.`code`, r.roleName FROM t_sys_user AS u, t_sys_role AS r WHERE u.roleId = #{roleId} AND u.roleId = r.id </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户角色id查询用户列表，包含角色信息 * * @return void */ @Test public void getUserListByRoleId() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class) .getUserListByRoleId(2); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为：" + sysUser.getSysRole().getRoleName()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：使用 collection 实现一对多嵌套结果映射实现一对多联表查询，可以在在一方的实体类里可通过集合属性（如 List、Set 等）来关联多方的多个对象，同时使用 collection 元素在 SQL 映射文件中来描述一对多嵌套结果映射的关系，以实现联表查询时能正确地将数据获取到对应的实体类对象及其关联的集合对象中。collection 元素用于处理一对多或多对多联表关系映射。collection 元素的基本用法：<resultMap id="..." type="..."> <collection property="关联的集合属性名" ofType="关联的集合对象中的元素类型"> <id column="..." property="..." /> <result column="..." property="..." /> </collection> </resultMap>使用 resultMap 元素 + collection 元素实现一对一嵌套结果映射的注意事项：因为结果映射的需要，要确保查询字段时所有的字段都是唯一的。collection 元素可以其结合 resultMap 属性实现映射规则的复用。示例：联表查询一对多嵌套结果手动映射地址表实体类（cn.duozai.entity.Address）：public class Address implements Serializable { /** * 主键id */ private Integer id; /** * 联系人姓名 */ private String contact; /** * 收货地址明细 */ private String addressDesc; /** * 邮编 */ private String postCode; /** * 联系人电话 */ private String tel; /** * 用户id */ private Integer userId; // ... }用户表实体类（cn.duozai.entity.SysUser）：public class SysUser implements Serializable { // ... /** * 关联用户地址列表 */ private List<Address> addressList; // ... }用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户id查询用户对象，包含地址列表 * * @param id 用户id * @return 用户表实体类对象 */ SysUser getUserById(@Param("userId") Integer id); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <resultMap id="userWithAddress" type="cn.duozai.entity.SysUser"> <id property="id" column="id"/> <result property="realName" column="realName"/> <!-- <collection>标签：实现一对多关系映射 property属性：SysUser中嵌套的地址列表集合的属性名 ofType属性：SysUser中嵌套的地址列表集合中的元素类型 --> <collection property="addressList" ofType="cn.duozai.entity.Address"> <id property="id" column="aid"/> <result property="addressDesc" column="addressDesc"/> <result property="tel" column="tel"/> </collection> </resultMap> SELECT u.*, a.id AS aid, a.contact, a.addressDesc, a.postCode, a.tel FROM t_sys_user AS u, t_address AS a WHERE u.id = a.userId AND u.id = #{userId} </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户id查询用户对象，包含地址列表 * * @return void */ @Test public void getUserById() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); SysUser sysUser = sqlSession.getMapper(SysUserMapper.class) .getUserById(1); logger.debug("SysUser => " + sysUser.getRealName()); for (Address address : sysUser.getAddressList()) { logger.debug("Address => " + address.getAddressDesc()); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：resultType 与 resultMap 总结在 select 元素中，resultType 属性直接表示返回类型，适用于比较简单直接的数据封装场景。而 resultMap 属性是对外部 resultMap 的引用，能够处理结果集字段名与实体类属性名不一致、或者需要对连接查询结果使用嵌套映射等较为复杂的问题。二者本质上都是基于 Map 数据结构，不能同时使用。自动映射行为MyBatis 自动映射的情况：单表查询使用 resultType 时，数据库字段名与实体类属性名一致，可以自动映射。单表查询使用 resultMap 时，可以实现手动映射关系。resultMap 中未定义的映射关系，只要数据库字段名与实体类属性名一致，也可以自动映射。多表查询使用 resultMap+association/collection 嵌套映射时，默认不会对嵌套映射的 resultMap 进行自动映射。resultMap 中未定义的映射关系，即使数据库字段名与实体类属性名一致，也不会自动映射。此时可以配置 MyBatis 自动映射级别实现自动映射。MyBatis 自动映射级别（autoMappingBehavior）：自动映射行为resultTyperesultMapresultMap+association/collectionNONE失效手动映射手动映射PARTIAL（默认）自动映射自动映射手动映射FULL自动映射自动映射自动映射MyBatis 自动映射的前提：数据库字段名与实体类属性名一致。示例：自动映射级别用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户角色id查询用户列表，包含角色信息 * * @param roleId 用户角色id * @return 用户列表 */ List<SysUser> getUserListByRoleId(@Param("roleId") Integer roleId); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <resultMap id="userWithRole" type="cn.duozai.entity.SysUser"> <id property="id" column="id"/> <!-- <result property="realName" column="realName"/> --> <association property="sysRole" javaType="cn.duozai.entity.SysRole"> <id property="id" column="rid"/> <result property="roleName" column="roleName"/> <result property="code" column="code"/> </association> </resultMap> SELECT u.*, r.id AS rid, r.`code`, r.roleName FROM t_sys_user AS u, t_sys_role AS r WHERE u.roleId = #{roleId} AND u.roleId = r.id </mapper>示例效果：MyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <settings> <setting name="autoMappingBehavior" value="FULL"/> </settings> </configuration>示例效果：自动映射的特殊情况一般情况下，数据库字段名以下划线命名（xx_xx），而实体类中的属性名以驼峰命名（xxXx），此时数据库字段名与实体类中的属性名不一致。在 MyBatis 中，可以配置驼峰命名自动转换（mapUnderscoreToCamelCase），以实现自动映射。示例：自动映射的特殊情况MyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>MyBatis 增删改操作增删改操作概述在使用 MyBatis 增删改元素执行 SQL 语句时，需要注意：对于增删改操作，数据库本身默认返回执行 SQL 语句所影响的行数，因此 Mapper 接口的返回值一般设置为 int 类型。映射增删改 SQL 语句的元素没有 resultType/resultMap 属性。执行增删改操作时，需要注意数据库事务控制。一般情况下，参数数量较多时最好封装成对象入参，特别是常规的添加和修改操作，字段较多，封装对象比较方便。参数量较少时，可以直接多参数入参。无论是多参数入参还是单参数入参，都建议使用 @Param 注解对参数合理命名。执行 insert 语句在 MyBatis 中，insert 元素用于定义新增语句。示例：执行 insert 语句用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 添加用户 * * @param sysUser 用户表实体类对象 * @return SQL语句影响行数 */ int add(SysUser sysUser); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <insert id="add" parameterType="cn.duozai.entity.SysUser"> INSERT INTO t_sys_user(account, realName, password, sex, birthday, phone, address, roleId) VALUES(#{account}, #{realName}, #{password}, #{sex}, #{birthday}, #{phone}, #{address}, #{roleId}) </insert> </mapper>MyBatis 工具（cn.duozai.utils.MyBatisUtil）：public class MyBatisUtil { // ... /** * 创建并返回SqlSession实例 * * @return SqlSession实例 */ public static SqlSession createSqlSession() { // 关闭自动提交，开启事务控制 return sqlSessionFactory.openSession(false); } // ... }测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 添加用户 * * @return void */ @Test public void add() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); SysUser sysUser = new SysUser(); sysUser.setAccount("addtest1"); sysUser.setRealName("添加测试"); int result = sqlSession.getMapper(SysUserMapper.class).add(sysUser); if(result == 1) { logger.debug("用户" + sysUser.getRealName() + "添加成功"); } else { logger.debug("用户" + sysUser.getRealName() + "添加失败"); } // sqlSessionFactory.openSession(false) 时需要手动提交事务 // 将openSession的参数设置为true，则不需要手动提交事务，其自动提交事务 sqlSession.commit(); MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：执行 update 语句在 MyBatis 中，update 元素用于定义修改语句。示例：执行 update 语句用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户id修改密码 * * @param id 用户id * @param newPassword 新密码 * @return SQL语句影响行数 */ int updatePassword(@Param("userId") Integer id, @Param("newPassword") String newPassword); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <update id="updatePassword"> UPDATE t_sys_user SET password = #{newPassword} WHERE id = #{userId} </update> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户id修改密码 * * @return void */ @Test public void updatePassword() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); int result = sqlSession.getMapper(SysUserMapper.class) .updatePassword(12, "888999"); if(result == 1) { logger.debug("用户密码修改成功"); } else { logger.debug("用户密码修改失败"); } // MyBatisUtil已开启自动提交事务 // sqlSessionFactory.openSession(true); // sqlSession.commit(); MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：执行 delete 语句在 MyBatis 中，delete 元素用于定义删除语句。示例：执行 delete 语句用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 根据用户id删除用户 * * @param id 用户id * @return SQL语句影响行数 */ int deleteUserById(@Param("userId") Integer id); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> <delete id="deleteUserById"> DELETE FROM t_sys_user WHERE id = #{userId} </delete> </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 根据用户id删除用户 * * @return void */ @Test public void deleteUserById() { SqlSession sqlSession = MyBatisUtil.createSqlSession(); int result = sqlSession.getMapper(SysUserMapper.class) .deleteUserById(12); if(result == 1) { logger.debug("用户删除成功"); } else { logger.debug("用户删除失败"); } MyBatisUtil.closeSqlSession(sqlSession); } }示例效果：MyBatis 缓存MyBatis 缓存概述MyBatis 的缓存分为两个级别：一级缓存和二级缓存。MyBatis 一级缓存：一级缓存是基于 PerpetualCache 的 HashMap 本地缓存，默认是 SqlSession 级别的缓存。一级缓存在 SqlSession 的一个生命周期内有效，当 SqlSession 关闭后，该 SqlSession 中的所有一级缓存会被清空。一级缓存默认是开启的。MyBatis 二级缓存：二级缓存是 SqlSessionFactory 级别的缓存，缓存中的数据可以被所有的 SqlSession 共享。二级缓存默认是关闭的，使用 MyBatis 二级缓存，需要在 MyBatis 核心配置中全局开启二级缓存（cacheEnabled），并在 SQL 映射文件中配置缓存。MyBatis 缓存在数据量达到一定规模时，其无法满足要求。MyBatis 的核心是 SQL 管理，缓存并不是 MyBatis 所擅长的，实现缓存机制可以采用 Redis、MongoDB、Memcached、OSCache 等专业缓存服务器。
AI 摘要 文章系统梳理 MyBatis 入门要点：先阐释框架与 ORM 概念，对比主流持久层方案；继而以 step-by-step 方式演示环境搭建、配置、实体与 Mapper 编写、SqlSession 使用及工具类封装；最后解析核心配置文件各元素作用与最佳实践，并总结 MyBatis 优缺点及适用场景，为后续 SSM 整合奠定扎实基础。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结框架技术概述框架概念框架（Framework）是一种经过检验的、按一定规则组织的一组组件、提供可重用的公共结构、具有特定功能的半成品。框架针对特定的功能或架构需要，对基础代码进行了封装并提供相应的 API，不同的框架能够提供不同的功能，开发者需要在框架的基础上，加入具体的业务逻辑，开发出一套自己的应用。使用框架技术的优势：不用再考虑公共问题，这些问题框架已经做好了实现。专心在业务逻辑的实现上，提升开发效率。项目结构统一，易于学习、维护。新手也可写出好程序。主流框架技术简介MyBatis：基于 ORM 的优秀的 DAO 层持久化框架，支持动态 SQL 等特性，小巧易学。Spring：开源、轻量级的企业级应用框架，覆盖了 Java EE 技术的方方面面，可以很方便的与其他框架集成使用，使得开发过程更加快捷高效。Spring MVC：基于 Servlet API 构建的 Web 框架，是目前最主要的 MVC 框架，具有高度可配置性，使用灵活。Hibernate：优秀的 DAO 层持久化框架，使用面向对象的方式操作数据库，而不需要直接编写 SQL 查询语句Struts2：基于 Web 的 MVC 框架，支持拦截器、OGNL 表达式等特性。数据持久化及 ORM持久化持久化是程序数据在瞬时状态和持久状态间转换的过程。ORMORM（Object/Relational Mapping，对象/关系映射）是一种在关系型数据库和面向对象编程语言之间进行数据转换的编程技术。ORM 的作用：编写程序的时候，以面向对象的方式处理数据。保存数据的时候，却以关系型数据库的方式存储。ORM 的解决方案：在持久化对象上执行基本的增、删、改、查操作。对持久化对象提供一种查询语言或者 API。对象关系映射工具。提供与事务对象交互、执行检查、延迟加载以及其他优化功能。使用 MyBatis 实现数据库操作MyBatis 概述MyBatis 的前身叫做 iBatis，是 Apache 的一个开源的项目。MyBatis 文档：https://mybatis.net.cn/MyBatis 简介：MyBatis 是一款优秀的持久层框架，它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。通过 XML 或注解可以将特定的 SQL 语句与 Java 实体类关联起来，借助 MyBatis 提供的 API 执行 SQL 语句并完成数据在两种模型之间的转换。是一种半自动化的 ORM 实现。MyBatis 初体验搭建 MyBatis 开发环境的步骤：导入 JAR 依赖包：MySQL 驱动、MyBatis、Log4J。编写 MyBatis 核心配置文件 mybatis-config.xml。编写数据库表所对应的实体类。编写数据访问层 Mapper 接口。编写 SQL 映射文件。编写测试类、测试方法。示例：搭建 MyBatis 开发环境SQL 脚本：SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_address -- ---------------------------- DROP TABLE IF EXISTS `t_address`; CREATE TABLE `t_address` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `contact` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人姓名', `addressDesc` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '收货地址明细', `postCode` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮编', `tel` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人电话', `userId` bigint DEFAULT NULL COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='地址表'; -- ---------------------------- -- Records of t_address -- ---------------------------- BEGIN; INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (1, '张三', '福建省泉州市安溪县宝龙城市广场', '362400', '13275000001', 1); INSERT INTO `t_address` (`id`, `contact`, `addressDesc`, `postCode`, `tel`, `userId`) VALUES (2, '张三', '福建省厦门市集美区软件园3期', '361000', '13275000002', 1); COMMIT; -- ---------------------------- -- Table structure for t_sys_role -- ---------------------------- DROP TABLE IF EXISTS `t_sys_role`; CREATE TABLE `t_sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `code` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色编码', `roleName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'; -- ---------------------------- -- Records of t_sys_role -- ---------------------------- BEGIN; INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (1, 'SYS_ADMIN', '管理员'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (2, 'SYS_MANAGER', '店长'); INSERT INTO `t_sys_role` (`id`, `code`, `roleName`) VALUES (3, 'SYS_EMPLOYEE', '店员'); COMMIT; -- ---------------------------- -- Table structure for t_sys_user -- ---------------------------- DROP TABLE IF EXISTS `t_sys_user`; CREATE TABLE `t_sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `account` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '账号', `realName` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '真实姓名', `password` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码', `sex` int DEFAULT NULL COMMENT '性别，1女/2男', `birthday` date DEFAULT NULL COMMENT '出生日期', `phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码', `roleId` bigint DEFAULT NULL COMMENT '用户角色id', `certFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '证件照片', `avatarFile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工作证照片', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; -- ---------------------------- -- Records of t_sys_user -- ---------------------------- BEGIN; INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (1, 'admin', 'admin', '0000000', 1, '1980-11-01', '13888888888', 1, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (2, 'limingxing', '李明星', '0000000', 2, '1983-12-10', '13188888888', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (3, 'wanglulu', '王璐璐', '0000000', 2, '1984-06-05', '15766666666', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (4, 'hauzhiqing', '华志强', '0000000', 1, '1983-06-15', '15866666666', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (5, 'huangweihua', '黄卫华', '0000000', 2, '1982-12-31', '13800099999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (6, 'zhaogang', '赵刚', '0000000', 2, '1980-01-01', '15266655555', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (7, 'liuyang', '刘阳', '0000000', 2, '1978-03-12', '17099999999', 2, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (8, 'lijiangtao', '李江涛', '0000000', 1, '1983-12-10', '13277777777', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (9, 'liuzhong', '刘忠', '0000000', 2, '1981-11-04', '15388888888', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (10, 'zhangfeng', '张峰', '0000000', 2, '1980-01-01', '19288899999', 3, NULL, NULL); INSERT INTO `t_sys_user` (`id`, `account`, `realName`, `password`, `sex`, `birthday`, `phone`, `roleId`, `certFile`, `avatarFile`) VALUES (11, 'songke', '宋科', '0000000', 1, '1983-10-10', '18900000000', 2, NULL, NULL); COMMIT; SET FOREIGN_KEY_CHECKS = 1;示例效果：导入相关依赖 JAR 文件，并添加为库。示例效果：Log4J 配置文件（classpath:log4j.properties）：log4j.rootLogger=debug,CONSOLE ################## # Console Appender ################## log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=debug log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n数据库配置文件（classpath:database.properties）：db_driver=com.mysql.cj.jdbc.Driver db_url=jdbc:mysql://127.0.0.1:3306/cvs_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai db_user=root db_password=123456..MyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <properties resource="database.properties"/> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${db_driver}"/> <property name="url" value="${db_url}"/> <property name="username" value="${db_username}"/> <property name="password" value="${db_password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="cn/duozai/dao/SysUserMapper.xml"/> </mappers> </configuration>用户表实体类（cn.duozai.entity.SysUser）：public class SysUser implements Serializable { /** * 主键id */ private Integer id; /** * 账号 */ private String account; /** * 真实姓名 */ private String realName; /** * 密码 */ private String password; /** * 性别，1女/2男 */ private Integer sex; /** * 出生日期 */ private Date birthday; /** * 手机号码 */ private String phone; /** * 用户角色id */ private Integer roleId; /** * 证件照片 */ private String certFile; /** * 工作证照片 */ private String avatarFile; // ... }用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { /** * 查询用户表记录数 * * @return 用户表记录总数 */ int count(); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <!-- <mapper>标签：根标签 namespace属性：SQL映射文件的命名空间，保证全局唯一 namespace一般对应DAO接口的全限定名，即包+接口名 --> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT COUNT(1) FROM t_sys_user </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { private static final Logger logger = Logger.getLogger(SysUserMapperTest.class); /** * 查询用户表记录数 * * @return void */ @Test public void count() throws IOException { // 1、读取MyBatis核心配置文件的输入流 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // 2、使用SqlSessionFactoryBuilder构建SqlSessionFactory工厂实例 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); // 3、创建SqlSession会话实例 SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、通过字符串关键字标识执行SQL：namespace+id // int count = sqlSession.selectOne("cn.duozai.dao.SysUserMapper.count"); // 5、构建Mapper接口执行SQL：创建SysUserMapper接口实例，调用方法执行SQL int count = sqlSession.getMapper(SysUserMapper.class).count(); // 6、调试输出执行结果 logger.debug("count => " + count); // 7、关闭SqlSession会话实例 sqlSession.close(); } }示例效果：搭建 MyBatis 开发环境的注意事项：一般情况下，项目的配置文件放在 resources 目录下，并将该目录标记为资源根目录。MyBatis 的核心配置文件默认叫做 configuration.xml，为了与其他框架的配置文件进行区分，习惯将 MyBatis 的核心配置文件命名为 mybatis-config.xml。一般情况下，实体类中的属性名要和数据库表中的字段名一致。Mapper 接口定义的操作一般针对特定的实体类，所以习惯上采用实体类名 + Mapper 的规则进行命名。SQL 映射文件的命名与其对应的 Mapper 接口的命名一致，且通常情况下与其对应的 Mapper 接口放在同一包路径下。对于返回值类型为集合的方法，其 SQL 映射文件中的 resultType 属性指定的是集合中元素的类型，而不是集合本身的类型。示例：查询数据列表用户表数据访问接口（cn.duozai.dao.SysUserMapper）：public interface SysUserMapper { // ... /** * 查询用户列表 * * @return 用户列表 */ List<SysUser> getUserList(); }用户表数据访问接口 SQL 映射文件（cn/duozai/dao/SysUserMapper.xml）：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cn.duozai.dao.SysUserMapper"> SELECT * FROM t_sys_user </mapper>测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 查询用户列表 * * @return void */ @Test public void getUserList() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = sqlSessionFactory.openSession(); List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class).getUserList(); for (SysUser sysUser : sysUserList) { logger.debug("SysUser => " + sysUser.getRealName()); } sqlSession.close(); } }示例效果：MyBatis 优缺点及其适用场景MyBatis 的优点：与 JDBC 相比，使用 MyBatis 操作数据库，减少了 50% 以上的代码量。MyBatis 是最简单的持久化框架，小巧并简单易学，能够快速上手应用。在 MyBatis 中，SQL 代码从程序代码中彻底分离，通过 XML 文件进行维护，便于管理和优化。MyBatis 提供了 XML 标签，支持编写动态 SQL。MyBatis 提供了映射标签，支持对象与数据库的 ORM 字段映射。MyBatis 的缺点：SQL 语句编写工作量大，对开发人员有一定要求。原生 SQL 语句依赖特定的数据库产品，数据库移植性差。MyBatis 专注于 SQL 本身，是一个足够灵活的 DAO 层解决方案，适用于性能要求较高或者需求多变的互联网项目。MyBatis 核心对象MyBatis 核心对象概述MyBatis 的核心对象包括核心接口和类。SqlSessionFactory 实例是 MyBatis 应用程序的核心。SqlSessionFactory 实例由 SqlSessionFactoryBuilder 对象根据 XML 配置文件进行构建。通过 SqlSessionFactory 实例可以获取 SqlSession 实例。SqlSession 实例包含执行 SQL 操作的方法，可以通过该实例执行已映射的 SQL 语句。MyBatis 三大核心接口和类的关系：SqlSessionFactoryBuilderSqlSessionFactoryBuilder 提供了多种 build 方法的重载，负责根据配置信息构建 SqlSessionFactory 实例。创建 SqlSessionFactory 实例的基本方法：InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);一旦创建了 SqlSessionFactory 实例，就不再需要 SqlSessionFactoryBuilder，因此 SqlSessionFactoryBuilder 的最佳作用域范围是存在于方法体内，即为局部变量，用完即销毁。SqlSessionFactorySqlSessionFactory 是每个 MyBatis 应用的核心，其作用是创建 SqlSession 实例。创建 SqlSession 实例的基本方法：SqlSession sqlSession = sqlSessionFactory.openSession();openSession 方法的基本参数：名称描述autoCommit控制是否开启自动提交事务，默认值为 false在应用运行过程中，不建议反复销毁和创建 SqlSessionFactory 实例，SqlSessionFactory 实例一旦创建就应该在应用的运行周期中始终存在，存在于整个应用运行时，并且同时只存在一个对象实例（单例），因此 SqlSessionFactory 的最佳作用域范围是应用的全局作用域。示例：MyBatisUtilMyBatis 工具类（cn.duozai.utils.MyBatisUtil）：public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; /** * 使用静态代码块唯一一次创建SqlSessionFactory实例 * 静态代码块在类被加载时就会执行，且只执行一次 * 无论创建多少个该类的对象，静态代码块都不会再次执行 */ static { try { // 读取MyBatis核心配置文件的输入流 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // 使用SqlSessionFactoryBuilder构建SqlSessionFactory工厂实例 sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { e.printStackTrace(); } } /** * 创建并返回SqlSession实例 * * @return SqlSession实例 */ public static SqlSession createSqlSession() { return sqlSessionFactory.openSession(); } /** * 关闭SqlSession会话 * * @param sqlSession SqlSession实例 */ public static void closeSqlSession(SqlSession sqlSession) { if(sqlSession != null) { sqlSession.close(); } } }测试类（test.SysUserMapperTest）：public class SysUserMapperTest { // ... /** * 查询用户表记录数-使用MyBatisUtil * * @return void */ @Test public void utilTest() { // 1、调用MyBatisUtil，创建SqlSession会话 SqlSession sqlSession = MyBatisUtil.createSqlSession(); // 2、执行SQL int count = sqlSession.getMapper(SysUserMapper.class).count(); // 3、调试输出执行结果 logger.debug("count => " + count); // 4、关闭SqlSession MyBatisUtil.closeSqlSession(sqlSession); } }SqlSessionSqlSession 表示一次数据库会话，包含了执行 SQL 所需的所有方法，非线程安全，不能共享，其对应一次数据库会话，会话结束必须关闭，因此 SqlSession 的最佳作用域范围是方法作用域内。使用 SqlSession 执行 SQL 语句的方式：通过 namespace + id 字符串运行映射的 SQL 语句。基于 Mapper 接口方式执行 SQL 语句。MyBatis 核心配置文件MyBatis 核心配置文件概述MyBatis 的核心配置文件（classpath:mybatis-config.xml）中设置的是 MyBatis 框架的全局信息，包括数据库连接及事务信息、MyBatis 运行所需的各种特性等。MyBatis 核心配置文件中的元素必须遵循固定的先后顺序。- configuration - properties - settings - typeAliases - typeHandlers - objectFactory - plugins - environments - environment - transactionManager - dataSource - databaseIdProvider - mappersproperties 元素properties 元素描述的是一些外部的、可替代的属性。properties 元素的基本用法： <properties resource="外部文件路径"/> <properties> <property name="键" value="值"/> </properties> <!-- 3、同时使用这两种配置方式，外部属性文件>property子节点 --> <properties resource="外部文件路径"> <property name="键" value="值"/> </properties>settings 元素settings 元素中设置的选项可以影响 MyBatis 框架的运行时行为，主要是 MyBatis 的一些全局配置属性的设置。settings 元素支持的基本选项：名称描述cacheEnabled指定是否全局开启二级缓存autoMappingBehavior指定 MyBatis 自动映射级别取值为 NONE/PARTIAL/FULL，默认值为 PARTIALlogImpl指定 MyBatis 使用的日志实现取值为 SLF4J/LOG4J 等mapUnderscoreToCamelCase是否开启驼峰命名自动映射，默认值为 falsesettings 元素的基本用法：<settings> <setting name="键" value="值"></setting> </settings>typeAliases 元素typeAliases 元素可以为 Java 类型配置一个简短的别名，以取代完全限定类名，简化开发。typeAliases 元素的基本用法： <typeAliases> <typeAlias alias="别名" type="完全限定类名"/> </typeAliases> <typeAliases> <package name="包名"/> </typeAliases>MyBatis 中定义的别名在 SQL 映射文件中使用时不区分大小写。environments 元素environments 元素可以针对开发、测试、生产部署等不同的需求，为 MyBatis 配置多套运行环境，可以在运行项目时灵活选择。environments 元素的基本用法：<environments default="当前使用的环境ID"> <environment id="环境ID"> <transactionManager type="事务类型"/> <dataSource type="数据源类型"> <property name="driver" value="数据库驱动"/> </dataSource> <environment> <environment id="环境ID"> <environment> </environments>使用 environments 元素的注意事项：通过设置 environments 节点的 default 属性来指定默认的运行环境 id，必须通过 default 属性指定其中一个为默认运行环境。environment 节点配置用来配置一个具体的环境信息，通过设置 id 属性来指定环境的唯一标识。transactionManager 元素通过设置 type 属性来配置事务管理器，可选 JDBC（使用原生 JDBC 来管理事务）、MANAGED（托管给外部容器来管理事务）。dataSource 元素通过 type 属性来配置数据源类型，可选 UNPOOLED（非池化数据源）、POOLED（池化数据源）、JNDI（Java 命名和目录接口数据源）。mappers 元素mappers 元素通知 MyBatis 查找定义了 SQL 语句的 SQL 映射文件的路径，MyBatis 会查找指定包路径下的所有 Mapper 接口，同时加载相同路径下的同名 SQL 映射文件。mappers 元素的基本用法： <mappers> <mapper resource="相对于类路径的资源引用"/> </mappers> <mappers> <mapper class="Mapper接口完全限定类名"/> </mappers> <mappers> <package name="Mapper包路径"/> </mappers>mappers 元素告诉 MyBatis 应该到哪里去找 SQL 映射文件，如果没有合理地配置 mappers 元素，MyBatis 会找不到 SQL 映射文件，无法对数据进行操作。示例：MyBatis 核心配置文件扩展MyBatis 核心配置文件（classpath:mybatis-config.xml）：<?xml version="1.0" encoding="UTF-8"?> <configuration> <properties resource="database.properties"/> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <!-- <typeAlias type="cn.duozai.entity.SysUser" alias="sysuser"/> --> <package name="cn.duozai.entity"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${db_username}"/> <property name="password" value="${db_password}"/> </dataSource> </environment> </environments> <mappers> <!-- <mapper resource="cn/duozai/dao/SysUserMapper.xml"/> --> <package name="cn.duozai.dao"/> </mappers> </configuration>