SSM框架——Spring、SpingMVC、Mybatis
Spring核心容器
Spring概述
spring是一款主流的Java开发框架,用于管理项目中所有的对象实例,可以简化开发流程,落实设计模式。
该框架的核心是IOC依赖反转和AOP面向切面编程:
- IOC依赖反转是指把传统过程中代码创建、管理的实例的行为转交给容器,通过容器实现对象实例的装配和管理。容器替开发者屏蔽了大量底层实例的创建、管理等操作,同时通过简单设置即可实现单例等设计模式,提高了开发效率。
直观上体现为,将原本由开发者创建资源的过程反转为资源准备好后自动注入到我们需要的实例中。 - AOP面向切面是指在不修改原代码的基础上实现功能扩展,实现切入点(被增强方法)与通知(增强内容)的管理,更多说明可见SpringAOP面向切面编程。
官网: https://spring.io/
中文网站: https://springframework.org.cn/
Spring框架模块结构图如下:
从文件来说,其核心依赖主要有以下几种:
-
spring-core.jar
提供 IoC 容器的核心功能。 -
spring-beans.jar 管理 Bean 的定义、依赖注入和生命周期。
-
spring-context.jar 提供应用上下文支持,如国际化、事件传播等。
-
spring-aop.jar 支持 AOP(切面编程),基于动态代理实现。
-
spring-expression.jar 提供 SpEL(Spring Expression Language),用于解析表达式。
更多依赖关系可见Spring框架依赖解析。
Maven安装配置
以往我们导入所需jar包都是通过手动下载再添加到库的方式,随着项目的不断增大,该工作会变得更加繁琐,且不同jar包依赖和版本控制的难度会指数上升,所以这里我们学习类似python中pip的包管理工具——Maven。
首先到官网https://maven.apache.org/download.cgi?.下载资源,目前最新的3.9.11版本
首先在IDEA中新建Maven项目
设置中指定maven路径与本地仓库位置
maven相关配置文件都在conf\settings.xml
内,因为国内特殊的网络问题,到中央仓库下载jar包较为不便,所以采用类似python设置镜像源的方法,maven也需要设置镜像仓库,以阿里的为例,将如下标签复制到settings.xml
的mirrors
标签内。
<!-- 阿里云镜像central库 -->
<!-- central Maven中央库(默认仓库),加速访问Java标准库(如commons-*、log4j等),最常用的公共依赖仓库,包含绝大多数开源Java库 -->
<mirror>
<id>aliYunMaven</id>
<name>aliyun maven</name>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<!-- 阿里云镜像public库 -->
<!-- public 同时代理两个仓库的依赖:central + jcenter聚合仓,简化配置,一次引用多个源(但可能增加依赖冲突风险) -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>public</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
本地仓库通过pom.xml
中的<repositoriesy>
标签指定。
<repositories><repository><id>aliyun maven</id> <!-- 与镜像的mirrorOf="aliyun maven"匹配 --><url>https://maven.aliyun.com/repository/central</url></repository></repositories>
随后在右侧maven选项卡中可见该仓库。
配置完成后便可以在自带的配置文件pom.xml
中新增配置,方式为:
<dependencies><!--增加单个依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.0.1</version></dependency></dependencies>
一切顺利的话在输入spring时会自动弹出所有可安装的包,随后自动填充groupid
,最后选择版本列表,使用这种方式管理依赖无需重复复制jar包到每个项目,实现依赖复用的同时也避免收集下载的麻烦。
IOC依赖反转
因为Spring框架主要完成的是对项目对象的管理,所以先创建一个示例类用于后续操作展示:
import java.util.List;public class User {private String name;private List<String> friends;private Home home;public User(String name, ArrayList<String> friends, Home home) {this.name = name;this.friends = friends;this.home = home;}public User() {}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<String> getFriends() {return friends;}public void setFriends(List<String> friends) {this.friends = friends;}public void setHome(Home home) {this.home = home;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", friends=" + friends +", home=" + home +'}';}
}
基于XML文件配置容器对象
首先导入如下库:
项目内创建beans.xml
并导入以下约束:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><!-- 将student的实例配置在spring的容器中 --><!--id属性:组件的唯一标识class属性:类的全类名,spring在底层会通过反射的方式来创建实例--><bean id="user" class="User"><property name="name" value="jack"/></bean>
</beans>
在该xml
文件内,通过bean
标签将对象加入容器,默认调用无参构造方法,并且是单例模式,要使用有参构造可使用<constructor-arg name="" value=""/>
的方式实现,但该形式只能给基本数据类型赋值,并要求参数与构造方法完全对应,要给特殊类型如list
、array
等赋值需额外使用<list>
、<array>
及构造双标签,如果是其他自定义类,需要先加入容器,再使用唯一标识id
传给ref
,实现根据id
注入当前容器内的实例,使用示例如下:
<bean id="home" class="Home"><constructor-arg name="address" value="北京市"/></bean><bean id="user" class="User"><constructor-arg name="name" value="jack"/><constructor-arg name="friends"><list><value>Amy</value><value>Bob</value></list></constructor-arg><constructor-arg name="home" ref="home"/></bean>
出现如下绿色标识说明实例装配成功
Ioc依赖反转是将实例的管理交由容器负责,由原本开发者创建实例转变为将信息注入到容器的实例中,给容器中的对象配置信息要使用<property>
标签进行注入,该方法的本质是调用类的set
方法,所以set
必须在类内声明,配置实例为:
<bean id="user" class="User"><property name="name" value="jack"/><property name="friends" ><list><value>Amy</value><value>Bob</value></list></property></bean>
bean
的生命周期也可以通过标签scope
指定,scope="singleton"
为默认的单例模式,scope="prototype"
表示多例,在web项目中也可以通过request
、session
、global-session
将对象存入指定的域中。
同时可以通过 destroy-method="方法名" init-method=""
分别指定初始化方法和销毁方法,其中初始化方法执行时机晚于构造方法,单例多例模式的具体执行时间不同,多例模式下初始化方法在调用getBean
方法获取实例时调用,单例模式下在创建容器对象时执行。销毁方法在单例场景下通过容器.close()
方法执行,多例模式下不会销毁实例,被垃圾回收机制回收时销毁。
获取容器对象主要有两种方法,从类路径下获取ClassPathXmlApplicationContext(相对路径)
和从硬盘中读取FileSystemXmlApplicationContext(绝对路径)
,再通过getBean(id)
方法获取实例,使用示例如下:
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/xml/beans.xml");User user = (User) context.getBean("user");System.out.println(user);}
}
基于注解配置容器对象
使用注解配置容器对象需要aop
支持,故首先需要导入为库,导入后的库为:
基于注解配置容器对象与xml
方法最大的区别就是类添加到容器及示例创建等操作都通过注释完成,该过程可分为下面几步:
- 新增上下文声明
xmlns:context=""
- 开启包扫描
<context:component-scan base-package="包位置"/>
,用于设置扫描注解范围 - 给类设置注解,实例装配
@Component
,依赖注入@Autowired
,生命周期@PostConstruct
等。
将上节使用的类赋值到新建的springtest
包下,在core.html
文件中检索xmlns:context
,文件可见core.html,新增声明并开启包扫描的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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="springtest"/></beans>
实例装配可使用多个注解完成,多个注解效果相同,相当于在xml
中配置一个Bean
,但针对web应用的不同业务范围,设置多个标识。
- @Component(“id”),把资源交给spring管理
- @Controller,用于表现层的注解
- @Service ,用于业务层的注解
- @Repository,用于持久层的注解
容器扫描指定包下的文件,发现该注解修饰的类则添加到容器内,基于反射创建实例。
依赖注入同样通过注解完成,这些注解只能修饰成员变量,不同场景下的用法如下:
- @Autowired,根据数据类型自动注入,匹配容器中已有的实例,有多个相同类型的实例时需结合@Qualifier使用
- @Qualifier(类名首字母小写/id),该注解不可单独使用,只用于补充标识,通过类名或id指定注入的具体实例
- @Resource(id),根据id注入,或
name=类名首字母小写
,方法由反射机制提供 - @Value(数据),基本数据类型直接注入
修改生命周期有以下几种方法:
- @Scope(),修改类的生命周期,其中参数可以是
singleton
、prototype
和request
、session
、global-session
- @PostConstruct,修饰方法为初始化方法
- @PreDestroy,修饰方法为销毁方法
装配和依赖注入的使用示例如下:
@Component
@Scope("singleton")
public class User {@Value("jack")private String name;private List<String> friends;@Autowired@Qualifier("address")private Home home;
}
基于注解配置容器和XML文件效果一致,方便程度也差不多,实际工程中我们通常对来自第三方的示例使用XML
装配,自行开发的实现类使用注解进行管理。
基于配置类配置容器对象
可以完全脱离XML文件配置容器对象的方法,也是Springboot采用的方法,通过一个核心配置类来管理容器。
实例装配阶段,首先用@Configuration
声明当前类为核心配置类,@Bean
修饰的方法将返回的示例添加到容器,由配置类管理的容器通过AnnotationConfigApplicationContext(配置类.class)
创建,通过getBean("方法名")
取出示例,也可以用注解@Bean(name="别名")
给方法起别名,但起别名后方法名失效。
依赖注入时,基本数据类型直接在装配阶段调用set
方法,引用数据类型需要将数据作为参数传给装配方法,实例必须存储在容器中,本质是Autowired
,有多个实例时通过@Qualifier
指定唯一标识。
实例装配和依赖注入实例代码如下:
@Configuration
public class Main {@Beanpublic Home getHome() {Home home = new Home();home.setAddress("北京市");return home;}@Bean("one")public User user(Home home) {User user = new User();user.setName("John");user.setHome(home);return user;}public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);System.out.println(context);System.out.println(context.getBean("one"));}
}
读取配置文件,使用@PropertySource("classpath:db.properties")
的方式读取配置文件,然后对成员的注入可使用@Value("${关键字}")
的形式实现。
开启扫描包,@ComponentScan("包结构")
可扫描包结构下所有被注解@Component
修饰的实例添加到容器,后续可通过核心容器类getBean
获取。
引入其他核心配置类,@Import({配置类.class})
可引入其他核心配置类,当引入的配置类只有一个时,可省略{}
,继承关系类似子类和父类,引入后可直接使用当前配置类获取父级容器,使用实例如下:
@Import(Main.class)
public class NewConfig {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NewConfig.class);User user =(User)context.getBean("one");System.out.println(user);}
}
AOP面向切面
面向切面是在不修改原代码的基础上实现对功能扩展的具体实践,是动态代理技术的进一步封装,AOP面向切面的相关概念如下:
切面(Aspect)是AOP的核心模块,指的是封装横切关注点的模块化单元。切面通常是一个类,其中包含特定的逻辑,如日志记录、事务管理等功能。切面定义了在何时、何地、以何种方式“切入”到业务代码中。每个切面都可以包含多个切点和通知,以决定切面在应用中的行为方式。
切点(Join Point)
切点(Join Point)指的是程序执行的某个特定点,比如方法的调用、异常的抛出、字段的访问等。在AOP中,切点是潜在的切入位置,表示横切关注点可以在何处插入到应用代码中。虽然理论上任何代码执行点都可以作为切点,但在实际应用中,AOP通常围绕方法调用来定义切点。
AOP框架通常提供切点表达式,用来匹配哪些方法或类需要被增强。例如,可以在某个包下的所有方法执行前插入日志记录,这样的灵活性极大提高了切面的通用性和可维护性。
通知(Advice)定义了切面在切点上的具体行为。通知是AOP的执行部分,表示在匹配的切点上执行的代码逻辑。根据执行时间和方式的不同,通知可以分为以下几种类型:
前置通知(Before Advice):在方法执行之前执行的通知。
后置通知(After Advice):在方法执行之后执行的通知,不管方法是否发生异常。
返回通知(After Returning Advice):在方法正常返回后执行的通知。
异常通知(After Throwing Advice):在方法抛出异常时执行的通知。
环绕通知(Around Advice):在方法执行的前后都执行的通知,允许在方法调用之前和之后都添加
自定义逻辑。这种通知最为灵活,可以完全控制目标方法的执行流程。
目标对象(Target)是应用AOP切面的对象,即包含业务逻辑的实际对象。AOP通过对目标对象的增强来实现切面功能。在AOP中,目标对象是被代理的对象,切面逻辑会在目标对象的方法执行之前、之后或环绕执行。AOP的本质就是在目标对象的基础上动态添加额外的功能,而不修改目标对象本身的代码。
代理(Proxy)是AOP在目标对象上的“包装”,负责实现对目标对象的增强。代理模式在AOP中被广泛应用,通过代理对象间接调用目标对象的方法,并在调用之前或之后插入切面逻辑。
织入(Weaving)是将切面逻辑与目标对象结合的过程,也就是将切面应用到目标对象上,使得增强的代码在目标对象的方法中生效。织入的方式决定了切面是如何“切入”到业务代码中的。
使用aop需先引入aspect.jar
包,创建核心业务类Service
和通知类Advice
,通知类先写四大通知如下:
package AOP;public class Advice {public void before(){System.out.println("前置通知");}public void after(){System.out.println("后置通知");}public void afterReturning(){System.out.println("返回通知");}public void afterThrowing(){System.out.println("异常通知");}public void around(){System.out.println();}
}
基于XML配置AOP
首先在spring官方核心文档core.html内搜索xmlns:aop
查找使用aop的标签声明,将如下结果赋值到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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
配置AOP主要是三步:
- 将通知类加入Spring容器。
- 将目标类加入容器。
- 配置AOP,建立通知与目标类的关系。
添加实例到容器使用bean
标签即可实现,配置aop
则需使用专门的aop标签,通过设置切入点pointcut
与目标方法绑定,该方法可使用匹配规则,如service*
匹配满足方法名前置有service
的所有方法,具体配置方法展示如下:
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="service" class="AOP.Service"/><bean id="advice" class="AOP.Advice"/><aop:config>
<!-- 引入切面,绑定通知 --><aop:aspect ref="advice">
<!-- 绑定通知与增强的方法--><aop:before method="before" pointcut="execution(* AOP.Service.service())"/><aop:after method="after" pointcut="execution(* AOP.Service.service())"/><aop:after-returning method="afterReturning" pointcut="execution(* AOP.Service.service())"/><aop:after-throwing method="afterThrowing" pointcut="execution(* AOP.Service.service())"/></aop:aspect></aop:config>
</beans><!--其中增强方法也可使用如下方法绑定-->
<!--<aop:pointcut id="pt01" expression="execution(* AOP.Service.service())"/>-->
<!--<aop:before method="before" pointcut-ref="pt01"/>-->
此时四种方法会分别在不同场景下触发,其中返回通知和异常通知二者不同时出现,使用简单示例及输出如下:
public class AopTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/xml/beans.xml");Service service = (Service) context.getBean("service");service.service();}
}// 输出如下:
前置通知
核心业务处理
后置通知
返回通知
环绕方法较为特殊,该方法与其他四大方法不能同时出现,也意味着环绕方法可以同时处理这四种情况,该方法借助参数ProceedingJoinPoint
连接点调用增强方法,所有四大通知都在其中的逻辑内完成,同时可借助连接点获取目标方法的参数,使用实例如下:
public void around(ProceedingJoinPoint joinPoint){System.out.println("前置通知触发");// 获取目标方法参数Object[] args = joinPoint.getArgs();try {// 执行目标方法joinPoint.proceed();System.out.println("后置通知");} catch (Throwable e) {System.out.println("异常通知");throw new RuntimeException(e);}finally {System.out.println("返回通知");}
}
通过aop配置即可实现与绑定四个通知相同的效果。
基于注解配置AOP
首先在xml中使用<aop:aspectj-autoproxy/>
开启对目标注解的支持,在xml内开启对context的支持xmlns:context="http://www.springframework.org/schema/context"
并在xsi:schemaLocation
后新增对context
支持的地址http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
,然后再开启包扫描<context:component-scan base-package="AOP"/>
,就可以使用注解对通知类进行配置,使用方法为:
- @Aspect 声明当前类为通知类
- @Pointcut(表达式) 修饰方法为切入点,方法名为切入点id
- @Before(表达式或id) 声明方法为前置通知
- @AfterReturning 声明方法为后置通知,仅在正常执行后返回
- @AfterThrowing 声明方法为异常通知,仅在抛出异常时通知
- @After 声明方法为最终通知,无论是否抛出异常,都会通知
- @Around 声明方法为环绕通知
使用注解配置通知类的使用示例如下:
package AOP;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class Advice {@Pointcut("execution(* AOP.Service.service())")public void pointcut() {}@Before("pointcut()")public void before(){System.out.println("前置通知");}@AfterReturning("pointcut()")public void after(){System.out.println("后置通知");}@AfterThrowing("pointcut()")public void afterReturning(){System.out.println("返回通知");}@After("pointcut()")public void afterThrowing(){System.out.println("异常通知");}// @Around("pointcut()")public void around(ProceedingJoinPoint joinPoint){System.out.println("前置通知触发");// 获取目标方法参数Object[] args = joinPoint.getArgs();try {// 执行目标方法joinPoint.proceed();System.out.println("返回通知");} catch (Throwable e) {System.out.println("异常通知");throw new RuntimeException(e);}finally {System.out.println("后置通知");}}
}
其他补充
Spring整合测试对象
在前面的基础上需要引入额外的四个包,如下图:
首先介绍junit
单元测试框架,以往我们都是在主函数中统一测试,这种方式有时不够灵活,该框架允许我们通过注解@Test
修饰返回值为void
的方法使其单独运行,底层由运行器实现,执行效果如下:
该方法可用于容器的自动执行,首先使用@RunWith(SpringJUnit4ClassRunner.class)
替换运行器为Spring的运行器实例,随后使用@ContextConfiguration(classes=Main.class)
根据核心配置类创建容器对象,或者locations = "/xml/beans.xml"
根据xml
创建容器对象,此时容器就随着运行器的启动而执行,此时的自动注入Autowired
已可用。
SpringMVC——Spring Web框架
Spring MVC概述
参考SpringMVC,MVC是一种架构模式,是落实前后端分离的具体方法,MVC分别代指:
- 模型
Model
,也是逻辑层,用于数据持久化以及处理用户请求; - 视图
View
,用于展示信息,一般为jsp
或html
文件; - 控制器
Controller
,模型与视图的中间层,负责接收视图消息,并交给模型处理,再将模型返回的信息回传给模型。
Spring MVC是Spring框架的一部分,是底层封装了Servlet的表现层框架,可以使用配置文件和注解两套方法实现web交互过程,首先引入相关依赖,maven中引入如下包:
<dependencies><!-- Spring --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.11</version></dependency><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>6.0.11</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>6.0.11</version></dependency><!-- Servlet --><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>6.0.0</version></dependency><!-- JSP --><dependency><groupId>jakarta.servlet.jsp</groupId><artifactId>jakarta.servlet.jsp-api</artifactId><version>3.0.0</version></dependency>
</dependencies>
xml配置视图解析器
如何关联后端代码与前端页面是web工程中很重要的一部分,Servlet底层提供了web.xml
和注解配置两种方法, springmvc也同样支持这两种方法。
首先配置资源调度器DispatcherServlet
,这是框架的核心控制器,负责协调组织项目的不同组件工作,是消息的统一入口,所有请求都先经过该调度器。
配置方法如下:
<web-app><!--SpringMVC前端控制器,本质是一个Servlet,接收所有请求,在容器启动时就会加载--><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><!--自动加载mvc的xml--><param-value>classpath:springmvc.xml</param-value></init-param><!--Tomcat启动级别,1为随容器启动--><load-on-startup>1</load-on-startup></servlet><servlet-mapping><!--绑定处理调度的url,/标识所有包都由该调度器转发--><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
上述配置指定所有请求都使用springmvc.xml
中的规则进行处理,下面具体指定mvc规则,首先要将视图解析器添加到容器,随后设置匹配的前后缀,
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--匹配前缀为jsp文件路径下--><property name="prefix" value="/jsp/"/><!--匹配所有后缀为.jsp--><property name="suffix" value=".jsp"/>
</bean>
上述配置完成后可借助ModelAndView
对象跳转页面,如setViewName("test")
会自动跳转到/jsp/test.jsp
页面。默认执行的是forward
转发方法,要跳转到项目路径以外的资源需使用重定向方法setViewName("redirect:/test.jsp")
,要注意的是使用重定向更改url会使请求内携带的消息失效。
随后在bean.xml
内开启mvc标签识别:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 开启包扫描--><context:component-scan base-package="com.ssm"/><!-- 开启对注解的支持--><mvc:annotation-driven/><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--匹配前缀为jsp文件路径下--><property name="prefix" value="/WEB-INF/jsp/"/><!--匹配所有后缀为.jsp--><property name="suffix" value=".jsp"/></bean><!-- 配置访问指定路径时调用的类--><bean id="/t1" class="com.ssm.Service.Main"/></beans>
有关Javaweb相关知识可见Javaweb开发,以往是通过实现Servlet
接口构建项目,在springmvc框架下,我们改为实现Controller
接口并重写handleRequest
方法,该方法返回ModelAndView
对象,该方法同时持有Model
和View
,方法内可通过addObject("key","value")
添加域对象数据,示例代码如下:
package com.ssm.Service;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class Main implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {System.out.println("access success");ModelAndView mav = new ModelAndView();// 域信息内添加key为msg,value为Hello World的字段mav.addObject("msg", "Hello World");// 自动跳转到/WEB-INF/jsp/index.jspmav.setViewName("index");return mav;}
}
上述配置可以完成基于XML
的路径映射,访问路径/t1
时可有jsp展示带有数据的页面。
注解绑定资源路径
除了实现Controller
重写handleRequest
方法外也可以通过注解绑定路径,首先使用@Controller
将类加入容器,随后使用@RequestMapping()
映射资源路径,该注解可修饰类也可修饰方法, 访问的url
按级别从高到低嵌套,实现类似功能的还有postMapping
和getMapping
分别用于专门处理post和get请求。
使用Mapping
注解修饰的方法返回值如果是字符串String
类型,参数引入Model
由框架自动注入,返回值会被看作视图路径自动跳转,也可在返回值前用forward
和重定向redirect
修饰.
使用@ResponseBody
修饰的方法会将返回值返回给浏览器,如果返回的是集合对象需要借助jackson-databind
包将集合转为json序列,相关方法使用示例代码如下:
@Controller
@RequestMapping("/test")
public class TestController {// 访问url为/test/01@RequestMapping("/01")public ModelAndView test01(){//创建视图模型对象ModelAndView modelAndView = new ModelAndView();//往model中添加数据modelAndView.addObject("data","hello");//设置视图名称modelAndView.setViewName("list");return modelAndView;}@RequestMapping("/02")// 访问test/02自动跳转,model由spring自动注入public String test02(Model model){model.addAttribute("name","Tom");// 自动跳转list.jspreturn "list"; }@RequestMapping("/03")@ResponseBodypublic String test03(){// 访问test/03 页面显示hello worldreturn "hello world";}
Mybatis持久层框架
Mybatis概述
Mybatis是一款持久层框架,免除了几乎所有JDBC代码,包括设置参数和获取结果集的工作,可实现半自动的持久层工作,相比自动生成sql语句的全自动框架Hibernate
,半自动框架允许我们在sql层优化性能,能实现更自由的系统构建。
可到github官网https://github.com/mybatis/mybatis-3或使用maven下载支持:
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version>
</dependency>
官方中文文档:https://mybatis.net.cn/getting-started.html
参考博客:狂神MyBatis
配置方法
Mybatis同样支持xml和注解两种配置方法,Mybatis应用的核心是一个SqlSessionFactory
实例,该实例首先通过SqlSessionFactoryBuilder
获取,SqlSessionFactoryBuilder
通过预先配置的xml文件构建工厂SqlSessionFactory
实例。
Mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置环境,默认选择id为development--><environments default="development">
<!-- 具体环境配置--><environment id="development">
<!-- 开启类型为jdbc的事务--><transactionManager type="JDBC"/>
<!-- 数据源对象,连接池--><dataSource type="POOLED">
<!-- 指定驱动、url、用户名和密码--><property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- xml中&应写为&--><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments>
</configuration>
在该配置中有两点需要进一步解读,事务类型和数据源类型。
事务类型设置
transactionManager
事务管理设置为JDBC
是最简单的提交和回滚机制,仍然需要提交commit
后写入数据库,执行失败时调用rollback
回滚;设置为MANAGED
时,使用外部容器管理事务。
数据源类型
dataSource
有三种类型,UNPOOLED
是每次请求打开连接,使用后关闭,效率较低,适合简单项目;POOLED
使用数据源连接池,与线程池类似,提前创建好一定数量的连接,有请求时从池中获取连接,避免重复创建销毁连接的开销;JNDI
是为了使用外部容器,数据源由外部容器创建,使用JNDI作为标识。
获取核心配置类SqlSessionFactory
的方法
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;public class Main {public static void main(String[] args) {try {InputStream inputStream= Resources.getResourceAsStream("Mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);System.out.println(sqlSessionFactory);} catch (IOException e) {throw new RuntimeException(e);}}
}
要注意的是上述的xml要放到resources
路径下,先将该配置文件读取为流对象,再使用SqlSessionFactoryBuilder
的build
方法获取SqlSessionFactory
实例。
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;public class Main {public static void main(String[] args) {try {// 获取输入流对象,输入来自总配置文件"Mybatis-config.xmlInputStream inputStream= Resources.getResourceAsStream("Mybatis-config.xml");// 使用SqlSessionFactoryBuilder生成工厂实例SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 获取sqlSession实例SqlSession sqlSession=sqlSessionFactory.openSession();} catch (IOException e) {throw new RuntimeException(e);}}
}
使用实例
从进行数据库操作时要确定方法接口,再使用具体的类实现该接口,resource
对应路径下编写映射文件mapper
绑定sql语句与方法,最后在总配置文件中添加映射规则,首先根据数据库表创建对象。
基于该结构封装的类为
public class User {private int id;private String name;private String sex;private String phone;private String hobby;private String description;public User(){}public int getId() {return id;}public void setUid(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby = hobby;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", sex='" + sex + '\'' +", phone='" + phone + '\'' +", hobby='" + hobby + '\'' +", description='" + description + '\'' +'}';}
}
然后写要执行的方法接口,以查询和添加为例,接口代码如下:
import java.util.List;public interface UserDao {List<User> findAll();int insert(User user);
}
再然后在resource
目录下写映射文件,要求映射文件与接口文件类路径一致,且命名一致,如
接口路径:src/main/java/com/UserDao.java
XML 路径:src/main/resources/com/UserDao.xml
项目结构如下图:
映射文件中要匹配接口方法对应要执行的sql语句,同时要声明为映射Mapper
文件,示例如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.Dao.UserDao">
<!-- id为绑定的方法名,返回类型和参数类型分别用全类名指定--><select id="findAll" resultType="com.Dao.User">select * from userinfo</select><insert id="insert" parameterType="com.Dao.User">
<!-- OGNL表达式,与el表达式类似,可直接取到实例内成员-->insert into userinfo (id,name,sex,phone,hobby,description) values (#{id},#{name},#{sex},#{phone},#{hobby},#{description})</insert>
</mapper>
在主配置文件Mybatis-config.xml
中也声明该映射规则,文件中新增如下内容:
<mappers><mapper resource="com/Dao/UserDao.xml"/></mappers>
使用如下代码测试可验证sql语句执行
UserDao userDao=sqlSession.getMapper(UserDao.class);
List<User> users=userDao.findAll();
for(User user:users){System.out.println(user);
}User user=new User();
user.setUid(4);
user.setName("jack");
user.setSex("男");
user.setPhone("18888888888");
user.setHobby("看电影");
user.setDescription("外国人");
// 返回值为影响记录条数
int flag=userDao.insert(user);
// 因为开启了事务管理,所有要提交才能写入数据库
sqlSession.commit();
可见查询结果与数据库更新
也就是说,使用Mybatis的大致过程为:
- 配置核心文件,用于获取数据库连接;
- 编写方法接口,及对应的sql语句,再编写映射文件绑定二者;
- 核心配置文件中增加映射文件;
- 通过工厂生产类获取工厂,再通过工厂获取数据库会话实例;
- 通过数据库会话实例获得接口的实现类;
- 调用接口实现类的方法实现对数据库的操作。
补充说明
底层原理
使用中我们发现,我们只声明了包含方法的接口,而没有写实现类,直接通过xml绑定接口方法与sql语句即可调用,这是因为Mybatis使用动态代理生成了Mapper
代理对象,该对象实现了接口的对应方法。
资源释放
上述代码存在资源管理问题,创建的输入流,工厂和数据库会话资源在程序结束后应该释放,在finally
语句块内添加相关资源的close
方法;并且SqlSessionFactory
是重量级对象,应该用单例模式管理,通常放在类的静态代码块内;使用框架时可以使用如下方式将整个过程实例加入容器,让容器自行管理。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
名称对应
Java中的命名规则是小驼峰,而数据库中要求单词之间以下划线隔开,这种名称不匹配的情况在底层反射中会导致注入错误,Mybatis有一定自动映射机制,但仅能处理简单差异,更健壮的映射需要在方法映射文件中进行处理,添加<resultMap>
标签,指定数据库的列名与实体的属性名,示例如下:
<resultMap id="userDao" type="com.Dao.User">
<!-- column为数据库列名,property为对应属性名-->
<!-- 主键使用id指定--><id column="id" property="uid"></id><result column="ename" property="name"></result></resultMap>
<!-- 指定映射规则,可自动完成转换与映射--><select id="findAll" resultType="com.Dao.User" resultMap="userDao">select id from userinfo</select>
安全问题
使用OGNL(预编译)表达式填充sql语句时,底层使用的PreparedStatement,即预编译的sql指令,可以防止注入攻击,但#{}
修饰的字符会自动在左右添加'
,导致无法被拼接为字符串,这种场景通常出现在模糊查询时,比如下面语句就无法执行:
<select id="findLike" parameterType="String" resultType="com.Dao.User">select * from userinfo where name like '%#{name}%'</select>
将'%#{name}%'
替换为'%${name}%'
即可,但替换语法${}
无法防止注入攻击,所以我们可以使用数据库内部的拼接方法concat
,即将上面的sql语句修改为select * from userinfo WHERE name LIKE CONCAT('%', #{name}, '%')
,也就是尽可能使用#{},而非${}。
关联映射
关系型数据库中的表由主键和外键组织起关系,查询结果转换为Java对象也应该保留该组织关系,根据关系的不同可以划分为一对一、多对一、一对多和多对多,
其中一对一、多对一逻辑相同,是一个或多个表有另一张表的一个记录相关,都是通过主键建立联系。
首先给原有的表结构增加一张地址表,其中的uid标识用户id信息,结构如下:
同时引入新的实体类和根据id查询地址的方法,方法与前面类似,不再赘述,展示配置文件。
<select id="findAddress" parameterType="int" resultType="com.Dao.Address">select * from address where uid=#{id}</select>
同时要求查询展示的User类要聚合Address类,即将其作为一个成员封装,如果有多个成员则封装成list,关键是如何进行两次查询并将查询结果注入到成员中,MyBatis可中通过association
修饰形成嵌套查询,将上一次查询的结果传递给下一次查询,形成语句如下:
<resultMap id="one" type="com.Dao.User"><id column="id" property="uid"/><!-- 通过property指定聚合的成员,javaType指定聚合的全类名 select指定嵌套查询的方法--><!-- 将本次查询结果的id作为参数传给findAddress方法--><association property="address" column="id" javaType="com.Dao.Address" select="findAddress"/></resultMap>
<select id="findAll" resultType="com.Dao.User" resultMap="one">select * from userinfo</select>
也可以使用联合查询join
,用association
将查询结果拼接到聚合类内,示例代码如下:
<!-- 1. 定义包含关联对象的 resultMap -->
<resultMap id="userWithAddressMap" type="com.Dao.User"><!-- 映射User自身的属性 --><id column="user_id" property="uid"/> <!-- user表的id用别名user_id避免冲突 --><result column="user_name" property="name"/> <!-- 假设user表的name字段别名user_name --><!-- 映射关联的Address对象 --><association property="address" javaType="com.Dao.Address"><id column="addr_id" property="id"/> <!-- address表的id别名addr_id --><result column="city" property="city"/> <!-- address表的city字段 --><result column="street" property="street"/> <!-- address表的street字段 --></association>
</resultMap><!-- 2. 一次SQL查询关联所有数据(通过JOIN) -->
<select id="findUserWithAddress" resultMap="userWithAddressMap">SELECT u.id AS user_id, -- 别名区分用户IDu.name AS user_name,a.id AS addr_id, -- 别名区分地址IDa.city,a.streetFROM user uLEFT JOIN address a ON u.id = a.user_id -- 关联条件:用户ID=地址表的user_idWHERE u.id = #{id} -- 按用户ID查询
</select>
最后联合查询的结果会根据列名分别注入主对象User
和关联对象Address
内。
一对多及多对多是指一个或多个表有多个其他表的记录相关联,如一个订单表中的多个商品信息对应,通过中间表建立联系,中间表是两张表的共同信息。
首先多对多关系的主表内要聚合关联对象实体的集合,如private List<Role> roles;
,在resultMap
内使用collection
标签建立关联,使用嵌套查询的方法如下:
<!-- 1. 定义用户的 resultMap,包含角色集合 -->
<resultMap id="userWithRolesMap" type="com.Dao.User"><id column="id" property="id"/><result column="name" property="name"/><!-- 核心:通过<collection>映射角色集合 --><collection property="roles" <!-- User类中的roles属性 -->ofType="com.Dao.Role" <!-- 集合中元素的类型(Role) -->column="id" <!-- 传递的参数:用户id(用于查中间表) -->select="findRolesByUserId" <!-- 查角色的SQL ID -->/>
</resultMap><!-- 2. 查询用户的主SQL -->
<select id="findUserWithRoles" resultMap="userWithRolesMap">SELECT id, name FROM user WHERE id = #{id}
</select><!-- 3. 通过用户id查角色(依赖中间表) -->
<select id="findRolesByUserId" resultType="com.Dao.Role">SELECT r.id, r.role_name FROM role rINNER JOIN user_role ur ON r.id = ur.role_idWHERE ur.user_id = #{userId} <!-- 参数是用户id(来自上面的column="id") -->
</select>
一步到位的联合查询实例为:
<!-- 1. 定义用户的 resultMap,直接映射角色集合 -->
<resultMap id="userWithRolesMap" type="com.Dao.User"><id column="user_id" property="id"/> <!-- 用户id(别名区分) --><result column="user_name" property="name"/> <!-- 用户名 --><!-- 映射角色集合 --><collection property="roles" ofType="com.Dao.Role"><id column="role_id" property="id"/> <!-- 角色id(别名区分) --><result column="role_name" property="roleName"/> <!-- 角色名 --></collection>
</resultMap><!-- 2. 一次JOIN查询所有关联数据 -->
<select id="findUserWithRoles" resultMap="userWithRolesMap">SELECT u.id AS user_id, -- 用户id别名u.name AS user_name,r.id AS role_id, -- 角色id别名(避免与user_id冲突)r.role_nameFROM user uLEFT JOIN user_role ur ON u.id = ur.user_id <!-- 关联中间表 -->LEFT JOIN role r ON ur.role_id = r.id <!-- 关联角色表 -->WHERE u.id = #{id} <!-- 按用户id查询 -->
</select>
关联映射的本质是用于处理主对象和关联对象映射的标签,决定将查询出的数据注入到哪个实例中,同时都支持嵌套查询(第一次结果作为参数传给第二次查询)和联合查询(一次查询出结果,分别注入给不同对象),两个场景一对一和多对多使用的标签只是注入对象的类型不同,association
将数据注入给单个对象,collection
则可以将数据注入给对象集合(单对象vs集合)。
延迟加载
上述所有查询等数据库操作都是立即执行的,在关联场景下,查询主对象时关联对象的所有数据也会立即加载,但有时关联对象的数据我们并不想获取,如果能在需要的适合再去查询加载就可以减少很多不必要的开销,基于这种想法,延迟加载技术诞生了。
延迟加载的核心逻辑是,只有真正需要关联内容时,才执行关联查询并加载数据,但如果每条记录都被访问,那访问订单下的一百个用户可能要执行101次查询,所以这种技术适合偶尔会被查询的记录,一定会被展示的数据还是使用立即加载。
全局下的懒加载可在Mybatis-config.xml
中新增settings
标签,并将lazyLoadingEnabled
的值设置为true
,要注意的是settings
标签应该放在environments
之前,xml文件中的标签顺序应该是properties
→ settings
→ typeAliases
→ typeHandlers
→ objectFactory
→ objectWrapperFactory
→ reflectorFactory
→ plugins
→environments
→ databaseIdProvider
→ mappers
,标签示例如下:
<settings><setting name="lazyLoadingEnabled" value="true"/><!-- 控制懒加载对象属性的加载情况--><!-- true表示任意调用完整加载,false表示每种属性按需加载--><setting name="aggressiveLazyLoading" value="false"/></settings>
局部可以在association
和collection
内的fetchType="lazy"
覆盖全局设置,lazy
表示启动懒加载,eager
表示立即加载。
使用示例如下:
<resultMap id="one" type="com.Dao.User"><id column="id" property="uid"/>
<!-- fetchType启用懒加载--><association property="address" column="id" javaType="com.Dao.Address" select="findAddress" fetchType="lazy"/></resultMap>
缓存机制
Mybatis也提供了查询缓存,用于减轻数据库访问压力,Mybatis设置有两层缓存,其中第一层缓存默认开启,也称本地缓存,是sqlSession级别的缓存,数据库在同一次会话期间查询到的数据会缓存到本地,相同两次查询的内存地址比较也完全一致,关闭sqlSession或数据修改后一级缓存失效。
二级缓存是SqlSessionFactory级别,也称全局缓存,默认关闭,需在settings
设置,将cacheEnabled
设置为true
开启,同时在mapper
中定义缓存。
<settings><setting name="cacheEnabled" value="true"/></settings>
mapper中可手动指定回收策略等相关配置,默认LRU
是最近最少使用,FIFO
先进先出,SOFT
软引用;readOnly
只读通常设置为true
,因为缓存只对查询有效;size
指定应用数目,即最多可以存储多少个对象,更多详细介绍可见Mybatis复习,通常只用默认设置即可,即只需引入<cache/>
。
<cache eviction="LRU" readOnly="true" ></cache>
更细的开启可以在select
等sql语句内将useCache
设置为true
开启缓存。
<select id="findAddress" parameterType="int" resultType="com.Dao.Address" useCache="true">select * from address where uid=#{id}</select>
对象的缓存要基于序列化的转换,所以开启缓存的结果中如果返回有对象,需要实现序列化接口implements Serializable
。
注解开发
通过注解(sql语句)
的形式可以直接实现持久层的操作而无需编写userDao.xml
,要实现字段的映射通过@
使用示例如下:
public interface UserDao {@Select("select * from userinfo")@Results({// 主键映射@Result(column = "id",property = "uid",id = true),@Result(column = "name",property = "name")})List<User> findAll();@Insert("insert into userinfo (id,name,sex,phone,hobby,description) values (#{uid},#{name},#{sex},#{phone},#{hobby},#{description})")int insert(User user);
}
SSM框架
框架概述
SSM框架是Java开发中一套全栈式的解决方案,整合了Spring、Spring MVC和Mybatis,是标准的MVC模式,保留了 Spring 的核心容器能力,又通过 SpringMVC 简化 Web 交互,通过 MyBatis 简化数据库操作,大幅降低了开发成本,对三个组件的分工详细解释如下:
Spring(核心容器)
Spring是整个框架的基础容器,作为web层和持久层的中间层,负责协调二者工作。核心功能围绕IOC(反转控制)和AOP(面向切面)展开,IOC负责管理所有Java对象,通过配置装配实例,降低代码耦合度;AOP负责实现日志记录、事务管理等功能,无需修改代码就能对方法进行增强。
Spring MVC(Web层框架)
Spring MVC专注处理Web层的请求交互,作为业务处理逻辑与用户的中间层,该框架通过注解映射请求到具体方法,将参数绑定到Java对象,处理完业务逻辑后,通过视图解析器返回页面。
MyBatis(持久层框架)
MyBatis是数据访问层框架,用于简化数据库操作。使用注解或配置文件XML将Java方法与sql语句绑定,简化数据库连接,结果集获取等操作,能自动将数据库查询结果转化为Java对象,相比全自动框架,MyBatis 允许开发者直接编写 SQL,便于优化查询性能。
核心配置
使用的相关依赖如下:
<!--pom.xml--><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.test</groupId><artifactId>SSM</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--导入spring相关的坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.2.1.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.1.RELEASE</version></dependency><!--导入mvc相关的--><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.1.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.1.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.1.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.1.RELEASE</version></dependency><!-- jackson依赖 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.11.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.11.3</version></dependency><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version></dependency><!--数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.22</version></dependency><!--mybatis相关--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.0</version></dependency><!--导入数据库连接池的坐标--><dependency><groupId>commons-dbcp</groupId><artifactId>commons-dbcp</artifactId><version>1.4</version></dependency><!--spring整合mybatis的核心jar包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.2</version></dependency></dependencies>
</project>
因为SSM是三个组件的组合,所以配置较为复杂,需要分别配置Spring容器beans.xml
文件、Spring MVC框架的springmvc.xml
、JavaWeb的web.xml
以及MyBatis的mapper.xml
。
MyBatis在使用时会使用反射基于接口创建实例,但我们无法手动获取该实例,所以整合SSM的关键之处就在于如何将MyBatis创建的实例添加到容器中,并在容器中使用。实现方法是借助Mybatis-spring
中间组件,将MyBatis
生成的Mapper
代理对象交给Spring容器管理。
需要做的就是在bean.xml
中使用数据源对象获取Mybatis
的工厂对象,开启扫描器,扫描指定目录下的接口是否创建了实例,创建则加入容器,相关配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 开启包扫描--><context:component-scan base-package="com.ssm"/><!-- 开启对注解的支持--><mvc:annotation-driven/><!--开启路径匹配 --><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--匹配前缀为jsp文件路径下--><property name="prefix" value="/WEB-INF/jsp/"/><!--匹配所有后缀为.jsp--><property name="suffix" value=".jsp"/></bean><!--注册数据源实例--><bean id="basicDataSource" class="org.apache.commons.dbcp.BasicDataSource"><!--注入源数据--><property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=GMT%2B8"></property><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property><property name="username" value="root"></property><property name="password" value="123456"></property></bean><!--注册mybatis的工厂对象--><bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"><!--注入数据源对象--><property name="dataSource" ref="basicDataSource"></property><!--指定映射文件的位置--><property name="mapperLocations" value="classpath:com/mapper/*.xml"></property></bean><!--mybatis的扫描器--><!--扫描持久层包下所有的接口,并且通过反射创建对应的实现类,将代理出来的实现类装配到spring的核心容器中--><bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--指定持久层包的位置--><property name="basePackage" value="com.ssm.mapper"></property></bean></beans>
此时当MyBatis通过动态代理生成了接口的实例时,会将其自动加入容器,要使用的类聚合接口类,再使用@Autowired
自动注入即可获取信息,使用实例如下:
@Controller
@RequestMapping("/user")
public class Service {// 容器自动注入类@Autowiredprivate UserDao userDao;@RequestMapping("/find")@ResponseBodypublic List<User> findAll() {return userDao.findAll();}
}
以Mybaits章节的数据库为例,输出的页面如下:
其他配置
其他配置方法如beans.xml
、web.xml
、springmvc.xml
都与原方法一致,复习一下整体流程。
Spring容器配置
beans.xml
的配置上面已经给出,主要完成的功能是开启包扫描,开启对注解的支持,开启MVC配置,在SSM下需额外配置数据源实例(连接数据库的信息)、注册工厂对象sqlSessionFactoryBean
、扫描持久层包下的接口,将代理类MapperScannerConfigurer
加入容器,需要使用代理类处聚合接口类,直接调用接口的方法即可。
Spring MVC表现层配置
springmvc.xml
本来是可以单独写的,但是因为同属于spring框架下,并且本次示例较为简单,所以就和beans.xml
写在一起,引入标签后仅通过<mvc:annotation-driven/>
开启注解支持,允许通过url访问指定类和方法。
另外就是JavaWeb中的操作,首先在项目结构中添加web模块
然后添加tomcat服务器
MyBatis持久层配置
比较繁琐的就是MyBatis持久层的配置,首先要根据数据库表新建类,用于接收数据,因为继承MyBatis章节,所以直接展示数据库和新建的类:
package com.ssm.mapper;public class User {private int uid;private String name;private String sex;private String phone;private String hobby;private String description;public User(){}public int getId() {return uid;}public void setUid(int uid) {this.uid = uid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby = hobby;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}@Overridepublic String toString() {return "User{" +"uid=" + uid +", name='" + name + '\'' +", sex='" + sex + '\'' +", phone='" + phone + '\'' +", hobby='" + hobby + '\'' +", description='" + description + '\'' +'}';}
}
该类中还聚合了一个持久层操作的接口仅设置一个读取数据的方法,因为使用注解绑定sql语句,所以UserDao.xml
无需配置,否则需要在resource
下新建与对应接口路径完全相同的配置文件,示例如下:
public interface UserDao {@Select("select * from userinfo")@Results({// 主键映射@Result(column = "id",property = "uid",id = true),@Result(column = "name",property = "name")})List<User> findAll();
}
到此SSM项目就可以运行了,整体结构和需要编写的部分如下:
总结
本文从Spring开始,介绍了SpringMVC、MyBatis三种框架,以及最后组合成的全栈框架SSM,各自完成的功能与整体框架的功能总结如下:
Spring(核心容器):以 IOC(控制反转)和 AOP(面向切面编程)为核心思想。IOC 将对象的创建、管理权交给容器,通过依赖注入(DI)降低代码耦合;AOP 通过 “切面(Aspect)、切点(Pointcut)、通知(Advice)” 实现无侵入式功能扩展(如日志、事务),无需修改原有业务代码。
Spring MVC(Web 表现层):底层封装 Servlet,通过DispatcherServlet(前端控制器) 统一接收网络请求,再通过注解(如@Controller、@RequestMapping)或 XML 配置实现 URL 与控制器方法的映射;配合 “视图解析器” 完成 JSP 等动态页面的路径解析,最终实现 “请求接收→业务处理→视图返回” 的 Web 交互流程。
MyBatis(持久层):独立于 Spring 的数据库操作框架,核心是通过Mapper 动态代理生成接口的实现类,自动封装 JDBC 的底层操作(包括数据库连接创建、SQL 执行、结果集与 Java 对象的映射);通过核心配置文件(MyBatis-config.xml)和 Mapper 映射文件(XxxMapper.xml)绑定 SQL 语句与 Java 方法,简化数据库操作。
SSM 整合(全栈协同):通过MyBatis-Spring中间件,将 MyBatis 的关键组件(如 SqlSessionFactory、Mapper 接口代理对象)交给 Spring 容器管理;最终形成 “Spring 管理业务层实例、Spring MVC 处理表现层请求、MyBatis 操作持久层数据” 的分层协作模式,实现从 Web 请求到数据库操作的全流程闭环,构成完整的企业级开发框架。