SSM
零、Maven
1:构建项目
clean:清除上一次构建生成的内容
compile:编译
compile: 编译主程序
test-compile: 编译单元测试程序
test:执行测试(单元测试用例)
package:打包
install:安装
2:依赖的范围
- compile:默认范围【编译前后都能用】,表示main、test里面都能使用,并且该依赖会打到最终部署的包里面去
- test:测试范围【仅供测试用】,表示可以在test里面使用,但是不能在main里面使用,并且该依赖不会打到最终部署的包里面去
- provided:【部署前用】表示可以在main、test里面都能使用,但是该依赖不会打到最终部署的包里面
- runtime:【写代码不用】表示main、test里面都不能使用,也就是说写代码的时候根本用不到这个东西(编译不需要这个jar),但是运行的时候需要,所以该依赖会达到最终部署的包里面去
依赖范围 | 编译(main) | 测试(test) | 部署 | 示例 |
---|---|---|---|---|
compile 全都有 | √ | √ | √ | log4j |
provided 不部署 | √ | √ | × | servlet-api lombok |
test 仅供测试 | × | √ | × | junit |
runtime 仅供测试和运行 | × | √ | √ | JDBC驱动类 |
3:三种项目关系
-
依赖关系:
- 优点:项目A依赖项目B,避免重复提供
- 缺点:
只有范围是Compile时才能访问
-
继承关系:
- 父项目的依赖,子项目都能用,子项目也可以另外引入其他依赖
- 父项目通过dependencyManagement管理依赖版本
- 父工程中所引入的依赖,子工程也会自动引入(不需要再在子工程中编写dependency)
- 缺点:并列关系,父项目和子项目需要分别手动clean和compile操作
-
聚合关系:分模块开发项目
-
比继承关系更进一步;一个大项目包含多个子项目
-
对父项目clean,compile等,会对所有子项目进行相同命令;
-
基于Maven的继承和聚合,创建一个父工程
- 修改pom.xml指定
打包方式为pom,
pom表示当前工程是聚合工程 - 修改pom.xml,进行依赖的管理 dependencyManagement
- 删除当前工程的src
- 修改pom.xml指定
-
一、Spring
1:IOC和DI
-
容器的分类
普通容器:只提供基本的【存储对象、向外提供对象、删除对象】的功能。例如List、Map、Set、数组
特殊容器:除了提供普通容器能提供的功能之外,还具备【创建对象、管理对象的生命周期、销毁对象、以及执行对象的各种方法】等等功能。例如Tomcat和IOC -
概念1:什么是IOC、为什么需要使用IOC
-
IOC的全称叫做控制反转,将对象的创建、属性赋值等等交由IOC容器完成,使用对象时只需要注入即可。
为什么要使用IOC呢?使用IOC可以带来一系列的好处。
首先,它能够
降低组件之间的耦合性
。通过将对象的创建和管理交给框架,对象不再直接依赖于具体的实现类,而是依赖于接口或抽象类。这样,我们可以更轻松地替换具体的实现类,实现代码的灵活性和可维护性。其次,
方便测试
。由于对象的依赖关系由框架管理,我们可以更容易地在测试时替换依赖项为模拟对象或测试对象。这样,我们可以更方便地编写单元测试和集成测试,并且更容易定位和解决问题。 -
概念2:什么是DI、为什么要使用DI
-
依赖注入是IOC的一种实现方式,用于管理对象之间的依赖关系。在依赖注入中,对象不再负责自行创建或查找它们所依赖的对象,而是通过外部的注入将依赖关系传递给对象。
为什么要使用依赖注入呢?依赖注入可以使得对象之间的依赖关系更加清晰和可控。通过将依赖关系的创建和管理委托给框架,我们可以
减少对象之间的直接耦合[硬编码]
,并且更容易进行组件的替换和扩展。依赖注入还能够提高代码的可测试性
,因为我们可以轻松地替换依赖项为模拟对象,更方便地进行测试。 -
配置文件方式和注解方式进行IOC和依赖注入
-
- 怎么选择到底是使用配置文件方式还是注解方式?
- 框架和第三方组件的类使用 XML 的配置方式【因为不能在别人的代码上加注解】
- 自己写的所有的类使用注解的方式【代码量更少】【耦合度升高,效率高】
-配置项 xml配置 注解配置 定义组件 < bean class = ""/> @Compoment@Controller@Service@Repository 给组件起别名 < bean id = "user" name=""/> @Compoment("user") 依赖注入 < property>或p命名空间 @Autowired按类型注入@Qualifier按名称注入 作用范围 scope属性 @Scope 声明周期 init-method和destroy-method @PostConstruct初始化;@PreDestroy销毁 使用场景 bean来自第三方,使用别人写的类 bean的实体类由自己开发 -
- 怎么使用配置文件方式进行IOC和依赖注入
- 2.1 使用配置文件方式进行IOC
<!--IOC:先引入spring-context依赖;再进行包扫描--> <context:component-scan base-package="com.atguigu"> <!--控制层扫描,只包含Controller注解:include--> </context:component-scan> <context:component-scan base-package="com.atguigu"> <!--业务层包扫描,排除Controller注解:exclude--> </context:component-scan>
-
2.2 使用配置文件方式进行DI
-
2.2.1 property标签【让IOC帮忙创建此类对象时为此类的属性注入值,或简单值,或对象值】
<bean id="userController" class="com.atguigu.controller.UserController"> <!-- property标签就表示调用set方法 name属性: 调用哪个set方法 ref属性: 要注入哪个对象(对应要注入的那个bean对象的id) 使用【value属性】注入简单类型的数据 --> <property name="userService" ref="userService"/> </bean>
-
2.2.2 有参构造
<bean id="userDao" class="com.atguigu.dao.impl.UserDaoImpl"> <!--指定构造函数传入的参数--> <constructor-arg name="username" value="张三"/> </bean>
-
2.2.3 p命名空间
<bean id="userService" class="com.atguigu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/> <!--将property标签融入到bean标签内部-->
-
-
2.3 使用和引用 properties 文件【存放常量:key=value】
引入properties文件
< context:property-placeholder location="classpath:application.properties"/>
读取properties文件中的内容
< constructor-arg name="username" value="${info.username}"/><!--例如在创建DruidDataSource对象的时候需要传入普通参数值--> <context:property-placeholder location="classpath:application.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${datasource.username}"/> <property name="password" value="${datasource.password}"/> <property name="url" value="${datasource.url}"/> <property name="driverClassName" value="${datasource.driverClassName}"/> </bean>
-
-
-
怎么使用注解方式进行IOC和DI
-
3.1 包扫描【对自定义的类组件进行扫描】
-
3.1.1 基础扫描
<context:component-scan base-package="com.atguigu"></context:component-scan>
-
-
3.1.2 指定匹配模式的扫描
- 3.1.3 仅扫描【include-filter】
- 3.1.4 排除【exclude-filter】
-
3.15 指定模式扫描【resource-pattern】
<context:component-scan base-package="com.atguigu" resource-pattern="*Impl.class"> </context:component-scan>
-
3.2 IOC注解
-
Component 【普通组件】
-
Controller 【控制层组件】
-
Service 【业务层组件】
-
Repository
-
给每个组件起别名便于IOC容器根据id进行管理
@Controller("soldierController") public class SoldierController { }
-
-
3.3 DI注解
-
Autowired
- 判断IOC容器中是否有且只有一个要注入的类型的对象
- 如果有且只有一个要注入的类型的对象,那么就将该类型对象进行注入
- 如果IOC容器中没有要注入的类型的对象,那么就报NoSuchBeanDefinitionException
- 如果IOC容器中有多个要注入的类型的对象
- 根据要注入的属性的属性名去匹配对象的id,匹配上哪个就注入哪个,如果一个都匹配不上则报错:expected single matching bean but found 2
- 结合【@Qualifier】指定根据name进行注入
-
Resource
注入对象类型还可以使用@Resource注解:根据name进行自动装配(byName)
-
Value:注入简单类型的值使用@Value注解
@Value("${datasource.username}") private String username; @Value("${datasource.password}") private String password; @Value("${datasource.url}") private String url; @Value("${datasource.driverClassName}") private String driverClassName;
-
-
-
自动装配
什么是自动装配: IOC容器自动调用【set方法】进行依赖注入
自动装配的前提?
- 一定是注入IOC容器中的对象,【不能是注入简单类型的值】
- IOC容器中有且只有一个要注入的类型的对象
- 要装配的属性一定要有set方法
1. 使用标签实现自动装配: <bean id="accountDao" class="com.atguigu.dao.impl.AccountDaoImpl" autowire="byType"> </bean> 2. 使用注解实现自动装配 @Autowired private UserService userService;
-
bean的作用域
单例模式 singleTon:创建后的对象归ioc管理【IOC和DI的设计思想】
多例模式 prototype:创建的对象不归ioc管理【防止并发问题】 -
bean的生命周期
单例:bean在单例模式下,IOC容器启动时解析xml发现该bean标签后,直接创建该bean的对象存入内部map中保存,此后无论调用多少次getBean()获取该bean都是从map中获取该对象返回,一直是一个对象。此对象一直被IOC容器持有,直到容器退出时,随着容器的退出对象被销毁。
多例:bean在多例模式下,IOC容器启动时解析xml发现该bean标签后,只是将该bean进行管理,并不会创建对象,此后每次使用 getBean()获取该bean时,IOC都会重新创建该对象返回,每次都是一个新的对象。这个对象IOC容器并不会持有,何时销毁取决于使用该对象的用户自己什么时候销毁该对象。
-
2:AOP
-
概念1:什么是AOP、为什么要使用AOP
- 为什么要学习AOP?
- 如果没有AOP,我们想要扩展方法的功能,代码就很难满足
开闭原则
,并且会导致代码有大量的冗余 - 如果没有AOP的话,我们想要扩展方法的功能,代码就很难满足
高内聚性
- 使用AOP能够实现在
不修改类的源代码
的基础上扩展类的功能,从而满足代码的开闭原则以及提高方法的内聚性
- 如果没有AOP,我们想要扩展方法的功能,代码就很难满足
- 什么是AOP
面向切面编程,底层使用了【动态代理技术】实现在不修改类的源代码的基础上来扩展类的功能
- 为什么要学习AOP?
-
概念2:什么是动态代理、为什么要使用动态代理、有哪些动态代理技术?
【代理对象:proxyObject】:其实就是将原来的类加上增强的方法合并之后动态生成的类
【被代理对象】:其实就是原来没有被增强的类
- 什么是动态代理:动态代理是一种在运行时生成代理对象的机制,它允许在不修改原始对象的情况下,为原始对象提供额外的功能和行为。动态代理可以用于实现横切关注点(如日志记录、事务管理、权限控制等)和远程调用等场景。
- 动态代理的好处?、
- 隔离关注点【让核心代码专注一件事情】:通过将【横切逻辑】与【核心业务代码】分离,使得核心业务逻辑更加干净、清晰,易于理解和维护。
- 重用性【附加代码各处插入】:可以实现【横切逻辑:附加功能】的重用,避免在每个目标对象中重复编写相同的代码。
- 控制访问【附加验证功能】:通过代理对象可以实现对目标对象的访问控制,例如权限验证、身份验证等。
- 延迟加载:通过代理对象可以实现延迟加载,即在需要时才创建和初始化目标对象,提高系统性能和资源利用率。
- 动态代理技术总共分两种
JDK内置 通过实现原类的接口来扩展功能
的动态代理技术:针对【被代理类的接口】进行动态代理,使用反射【创建接口的实现类】,从而创建【实现类的对象作为代理对象】CDLIB框架:通过继承原类来扩展功能
提供的动态代理技术:针对被代理类,【创建被代理类的子类】,从而创建子类对象作为代理对象
-
AOP中的一些基本概念
-
切面【Aspect】:即【切面类】
@Component //三层架构之外的类都是【组件】 @Aspect //切面类的注解 public class LoggingAspect { //.... }
-
切入点【Pointcut】:即【通知所应用的方法范围】
public class AtguiguPointcut { @Pointcut("execution(* com.atguigu.service.impl.*.*(..))") public void pt1(){} @Pointcut("@annotation(AtguiguCache)") public void cachePt(){} } //在通知注解中使用重用切入点,例如: @Before("com.atguigu.aspect.AtguiguPointcut.pt1()")
-
通知:即【注解】+【增强方法】
-注解类型 形参1 形参2 @Before JoinPoint @AfterReturning JoinPoint Object result @AfterThrowing JoinPoint Throwable @After JoinPoint @Around ProceedingJoinPoint @Component //三层架构之外的类都是【组件】 @Aspect //切面类的注解 public class LoggingAspect { @Before("execution(* com.atguigu.service.impl.*.*(..))") public void printLogBefore(JoinPoint joinPoint){ //before表示在目标方法执行前执行 } @AfterReturning(value = "execution(* com.atguigu.service.impl.*.*(..))" ,returning = "result") public void printLogAfterReturning(JoinPoint joinPoint,Object result){ //AfterReturning表示该方法在目标方法正常执行完毕之后执行 } @AfterThrowing(value = "execution(* com.atguigu.service.impl.*.*(..))" ,throwing = "throwable") public void printLogAfterThrowing(JoinPoint joinPoint,Throwable throwable){ //AfterReturning表示该方法在目标方法抛出异常之后执行 } @After(value = "execution(* com.atguigu.service.impl.*.*(..))") public void printLogAfter(JoinPoint joinPoint){ //AfterReturning表示该方法在目标方法运行完之后执行【无论正常结束还是异常结束】 } //Around环绕通知,在这个方法内部执行【目标方法】 @Around(value = "execution(* com.atguigu.service.impl.*.*(..))") public Object printMethodInvokeTime(ProceedingJoinPoint proceedingJoinPoint){ String className = proceedingJoinPoint.getTarget().getClass().getName(); String methodName = proceedingJoinPoint.getSignature().getName(); //1. 获取目标方法执行之前的总毫秒数 long startTime = System.currentTimeMillis(); try { //2. 执行目标方法 Object result = proceedingJoinPoint.proceed(); return result; } catch (Throwable e) { throw new RuntimeException(e); }finally { //3. 获取目标方法执行之后的总毫秒数 long endTime = System.currentTimeMillis(); //4. 计算执行总时长 System.out.println("[环绕通知]["+className+"]类的["+methodName+"]方法执行的总时长为:"+(endTime - startTime)); } } }
-
目标【被代理类的目标对象】【被代理类的方法是JoinPoint】
//获取目标方法所在类的全限定名 String className = joinPoint.getTarget().getClass().getName(); //获取目标方法的方法名 String methodName = joinPoint.getSignature().getName(); //获取目标方法的参数列表 Object[] args = joinPoint.getArgs();
-
-
AOP的使用
-
-
创建切面
【切面】即【切面类】
@Component 切面类也是普通组件
@Aspect 切面类
-
-
-
使用切入点表达式创建切入点
public class AtguiguPointcut { //定义给所有service包下的所有类的所有方法设为切入点 @Pointcut("execution(* com.atguigu.service.impl.*.*(..))") public void allServiceMethod(){} }
-
-
-
在切面中创建通知,添加通知注解,并且将通知与切入点进行绑定
@Around("com.atguigu.pointcut.AtguiguPointcut.allServiceMethod()") public Object totalTime(ProceedingJoinPoint pjp){ //执行目标方法并返回结果给Object Object result = pjp.proceed(); return result; }
-
-
- 开启AOP
-
配置类中使用注解
@EnableAspectJAutoProxy
-
配置文件中使用标签
< !--在spring-service开启AOP-->
<aop:aspectj-autoproxy />
-
-
AOP底层使用的是哪种动态代理技术?AOP的代理对象到底使用什么类型接收?
- AOP使用了哪种动态代理技术?【两者都有】
- 如果被代理的类实现了接口,那么就使用JDK的动态代理技术创建被代理接口的实现类,从而创建实现类对象作为代理对象【因为如果原类实现了接口则肯定存在接口】
- 如果被代理的类没有实现接口,那么就使用CGLIB的动态代理技术创建被代理类的子类,从而创建子类对象作为代理对象
- AOP代理对象由什么来接收?【肯定是父亲来接收】
- JDK的动态代理对象只能用
被代理的接口
类型接收 - CGLIB的动态代理对象就使用
被代理类
的类型接收
- JDK的动态代理对象只能用
- AOP使用了哪种动态代理技术?【两者都有】
-
通过配置强制让所有的代理都使用CGLIB技术
(1) 如果是配置类方式:@EnableAspectJAutoProxy(proxyTargetClass = true) (2) 如果是配置文件方式:<aop:aspectj-autoproxy proxy-target-class="true"/>
-
静态代理:【把被代理类用接口接收并当做自己的属性的一部分】【ExtensionClassLoader设计类似】
【创建一个代理类,将增强逻辑写在代理类中】
【代理类和被代理类要实现相同的接口】都要实现被代理接口
代理类中持有被代理类的对象
增强逻辑编写在代理类中,但是核心方法必须有被代理类对象执行
//例如:【1】:共同的接口Calculator【2】:被代理类:CalculatorImpl【3】:静态代理类:CalculatorProxy public class CalculatorProxy implements Calculator { //使用共同的接口来接收【被代理类】 private Calculator calculator; }
多个切面嵌套:需要考虑切面的优先级,并且使用
@Order设置优先级,数值越小的优先级越高
2:声明式事务
-
概念1:事务的四大特性
ACID是指原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)
原子性
:一个事务的所有具体操作
应该保持原子性,也就是说是不可分割的,要么同时成功,要么同时失败;MySQL内部是通过undo_log保证事务的原子性一致性
:事务前后的数据状态
要么一起回滚要么一起成功;一致性的话是由业务代码进行保证的隔离性
:多个事务之间是隔离的,不能相互影响;MySQL内部是通过锁机制以及MVCC来保证事务的隔离性。持久性
:一个事务一旦提交,即使此时出现数据库宕机,也不会丢失数据。MySQL的持久性是由redo_log保证持久性
-
概念2:事务的隔离级别
事务的隔离级别指的是多个并发事务之间相互隔离的程度,它定义了一个事务在读取和修改数据时与其他事务的可见性和影响范围。
- Read Uncommitted(读未提交):最低级别的隔离级别。在该级别下,一个事务可以读取到另一个事务未提交的数据变更。这可能导致脏读(Dirty Read),即读取到未提交的、可能会回滚的数据。
- Read Committed(读已提交):大多数数据库系统的默认隔离级别。在该级别下,一个事务只能读取到已经提交的数据变更。这避免了脏读,但可能出现不可重复读(Non-repeatable Read),即在同一事务中多次读取同一数据,得到的结果可能不一致。
- Repeatable Read(可重复读):在该级别下,一个事务在执行期间多次读取同一数据时,保证得到的结果是一致的。即使其他事务对该数据进行了修改,对于当前事务来说也是不可见的。这避免了脏读和不可重复读,但可能出现幻读(Phantom Read),即在同一事务中多次查询,得到的结果集不一致。
- Serializable(串行化):最高级别的隔离级别。在该级别下,事务串行执行,每个事务依次执行,确保了最高的隔离性。它避免了脏读、不可重复读和幻读的问题,但可能导致性能下降,因为事务无法并发执行。
不同的隔离级别在并发环境下提供了不同程度的数据隔离和数据一致性保证。较低的隔离级别通常提供更好的并发性能,但可能引入数据的不一致性问题;较高的隔离级别提供了更强的数据一致性,但可能降低并发性能。
-
概念3:事务的隔离性问题
脏读
:一个事务读
取到了另一个事务未提交的数据
,MySQL设置隔离级别为Read Committed就可以避免脏读不可重复读
:一个事务中多次读
取到的某行
数据的内容不一样
,MySQL设置隔离级别为Repeatable Read就可以避免不可重复读,底层原理使用的是MVCC幻读
:一个事务中多次读
取到的数据的行数不一样
,MySQL设置隔离级别为Serializable就可以避免幻读;我们也可以使用MVCC避免快照读时候的幻读,使用间隙锁避免当前读的幻读
-
概念4:事务的传播性
两个方法嵌套执行的时候,内层方法到底怎么样使用事务(到底要不要与外层方法共用一个事务)
1.
【REQUIRED 需要】
:一般声明在修改数据库的方法中【增删改】
声明当前方法必须运行在事务中;如果外层有事务,则该方法融入到外层事务中运行,如果外层没有事务,B会启动一个新的事务;REQUIRED意思是B事务无论如何都要有事务。@Transactional(propagation = Propagation.REQUIRED)
2.
【SUPPORTS 支持】
:一般声明在查询数据库的方法中【查】
表示当前方法不需要事务上下文,如果外层A有事务,就会融入,如果没有,也不会单独开启事务,即B事务支持A事务;@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
3.【REQUIRED_NEW 需要新事务】:
表示该方法必须运行在它自己的事务中,一个新的事务将启动,如果外层存在一个事务,则在该方法执行期间,外层事务会被挂起;
4.【MANDATORY 强制有事务上下文】:
表示该方法必须在事务中运行,如果外层事务不存在,则抛出一个异常5.【NEVER 不能有事务上下文】:
表示该方法不应该是运行在有事务的上下文中,如果当前正有一个事务在运行,就抛异常;6.【NESTED 嵌套】:
表示如果外层有一个事务,则该方法会嵌套该事务中运行,和REQUIRED的【普通融入】不同,嵌套的事务可以独立于外层事务进行单独地提交和回滚。
如果外层没有事务,则行为和REQUIRED一样,会开启一个新的事务运行; -
声明式事务的使用
-
-
配置
事务管理器
对象- 混合开发方式:在spring-service.xml中配置
< bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> < property name="dataSource" ref="dataSource"/> < /bean>
- 纯注解方式:在配置类中
@Bean public TransactionManager dataSourceTransactionManager(@Autowired DataSource dataSource){ return new DataSourceTransactionManager(dataSource); }
-
-
-
开启事务注解驱动:
用来识别@Transactional注解
(1) 混合开发方式,在spring的配置文件中编写:
<tx:annotation-driven transaction-manager="transactionManager"/>
也可以直接写
<tx:annotation-driven />
自动识别事务管理器(2) 纯注解方式,在spring的配置类中编写:
@EnableTransactionManagement
-
-
- 在需要使用事务的方法上添加@Transactional注解
-
-
Transactional注解的属性
- readOnly属性:如果设置该属性的值为true,表示该事务是只读事务,那么也就表示当前这个事务中不能执行写操作
- timeout属性:设置事务的超时时间,如果达到超时时间还未执行SQL,就会回滚
- rollbackFor属性:设置当前事务遇到什么类型的异常会回滚,默认是遇到RuntimeException或者是Error就回滚
- noRollbackFor属性:设置当前事务遇到什么类型的异常不回滚
- isolation属性:设置当前事务的隔离级别
- propagation属性:设置当前事务的传播性
3:整合单元测试
-
概念1;为什么要整合单元测试:方便直接自动注入对象,然后测试
-
整合单元测试的使用
-
- junit的版本必须大于等于4.12
-
- 引入spring-test的依赖
-
- 在单元测试类上添加俩注解
-
RunWith
@RunWith(SpringJUnit4ClassRunner.class)
-
ContextConfiguration
@ContextConfiguration(locations="配置文件路径"或者classes=配置类)
-
4:纯注解开发
SpringBoot框架是完全舍弃了XML配置
-
- 配置
类上添加
的注解
-
1.1 Configutation注解
@Configuration //告诉框架这个类是配置类
-
1.2 包扫描注解
@ComponentScan("com.atguigu") //包扫描【扫描各个注解组件】
-
1.3 开启AOP的注解
@EnableAspectJAutoProxy
-
1.4 开启声明式事务的注解
@EnableTransactionManagement
-
1.5 引入外部的properties文件的注解
@PropertySource("classpath:application.properties")
-
1.6 导入其它配置类的注解
@Import注解导入其他配置类 @Import({ConfigClass1.class, ConfigClass2.class}) ------------------------------------------------------------------------- @ImportResource注解导入XML配置文件 @ImportResource("classpath:other-config.xml")
- 配置
-
-
针对第三方jar包中的类创建Bean
@Bean //在传入参数的时候可以通过让ioc自动搜索容器内的dataSource对象并装配 public QueryRunner queryRunner(@Autowired DataSource dataSource){ //1. 创建QueryRunner对象 QueryRunner queryRunner = new QueryRunner(dataSource); return queryRunner; }
-
二、Mybatis
0:什么是ORM
ORM,Object-Relationl Mapping,对象关系映射,它的作用是在关系型数据库和对象之间作一个映射
- Object 对应【User】实体类
- Relation 对应【t_users】数据表
- Mapping 对应【UserMapper.xml】映射配置文件:编写SQL语句
1:映射配置文件[XxxMapper.xml]
-
- 放在哪:建议resources/mappers
-
- 叫什么名字:与其对应的接口的名称一致,后缀是.xml
-
-
根标签的namespace的值:与其对应的接口的全限定名一致
<mapper namespace="com.atguigu.mapper.UserMapper"> <!--在mapper标签内些SQL标签语句--> </mapper>
-
-
4.insert、delete、update、select标签
-
4.1 标签的类型与方法的操作对应
-
4.2 标签的id与方法名对应
-
4.3 select标签一定要有
resultType
或者resultMap
属性,表示结果集的封装,增删改不需要封装返回值,不过也会后台传回返回值,表示影响行数;resultType
:select查询到的结果集怎么封装?- 通过resultType属性来指定封装结果集的类型
- 如果查询的是单个数据:则使用简单类型封装
- 如果查询的是一行数据:则使用POJO或者是Map,注意如果是使用POJO封装的话必须保证结果集的字段名与POJO的属性名一致
- 如果查询的是多行数据:则使用List< POJO>或者是List< Map>,
但是要注意resultType的值是POJO的类型或者Map的类型
增删改方法的返回值只能是三种:boolean、int、void
<!--主键回显:新增数据之后,查询新增数据的主键值--> <!--方式一:只适用于数据库自增主键--> <insert id="addAccount" useGeneratedKeys="true" keyProperty="accountId"> INSERT INTO t_account (account_name,money) VALUES (#{account.accountName},#{account.money}) </insert> <!--方式二:--> <insert id="addAccount"> INSERT INTO t_account (account_name,money) VALUES (#{account.accountName},#{account.money}) <selectKey keyProperty="account.accountId" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> </insert>
驼峰映射:配置之后,可以在查询的时候不必再给每个字段单独起别名
类型别名:如果不配置,在指定结果集类型的时候使用的是类型的全限定名,写起来比较麻烦
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--2.1 配置数据源--> <property name="dataSource" ref="dataSource"/> <!--2.2 加载mybatis的全局配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--2.3 设置映射配置文件的路径--> <property name="mapperLocations" value="classpath:mappers/*.xml"/> <!--2.4 类型别名注册--> <property name="typeAliasesPackage" value="com.atguigu.pojo"/> </bean>
-
4.4 标签体中编写SQL,SQL中需要获取方法传入的参数
-
#{}:单个参数:
- 单个简单类型的参数:#{任意字符串}
- 单个对象类型的参数:#{属性名}
- 单个Map类型的参数:#
- 单个参数也可以用@Param给参数起别名,但没必要
-
#{}:多个参数:
- 使用@Param注解给参数取别名
- 如果参数是简单类型:#{别名}
- 如果参数是对象/Map类型:#{别名.属性名}或者#{别名.key}获取
-
${}
{}和${}的区别
-
{}会使用?占位符,然后再设置参数,而${}直接将参数拼接在SQL中
- 有一些场景下
只能使用${},例如表名、或者字段名需要动态传入的时候
-
-
在mybatis中如果我们使用#{}的方式编写的sql时,#{} 对应的变量自动加上单引号 ' '
select * from #{param}
当我们给参数传入值为user时,他的sql是这样的:
select * from 'user' 参数user上会带着单引号,而单引号在mysql中会被识别为字符串,select一个字符串肯定是会报错的。
而如果我们使用${}的方式编写的sql时,${} 是进行sql拼接,${} 对应的变量是不会被加上单引号 ' ' 的。
select * from ${param}
输出的sql为
select * from user
总结:使用#{}方式编写的SQL语句中,会自动为占位符对应的变量添加合适的引号。这是因为#{}会通过预编译的方式将变量值传递给SQL语句,同时会根据变量的类型和上下文自动添加适当的引号。
不过在MySQL数据库执行语句时,对字段添加单引号本就是可行的,例如:
SELECT * FROM users WHERE id = '1'
-
-
-
resultMap
标签 :用于多表查询
-
resultMap属性
- id:唯一标识
- type:映射的实体类的类型
- 一条数据对应返回值是一个对象,多条数据对应返回值List
-
autoMapping:自动映射
- 开启自动映射后,可以将数据库属性名与结果集的驼峰字段名对应,若有不能自动映射的需要使用resultMap子标签中的【id标签】和【result标签】来手动映射对应的字段
-
resultMap子标签
- id标签用来映射主键字段
-
column属性表示要映射的结果集的字段名
- property属性表示要映射到实体类的属性名
- result标签:针对非主键字段进行映射
- column属性表示要映射的结果集的字段名
- property属性表示要映射到实体类的属性名
- assosiation标签:一对一
- javaType:表示关联对象的类型,可以省略
- property:表示关联的这个对象,要赋值给我本身实体类对象的哪个属性
- collection标签:一对多
- ofType表示集合的泛型,可以省略
- property表示关联的这个集合要赋给原本实体类对象的哪个属性
-
resultMap:【一对一】多表查询
一个类中持有另一个类的属性
@Data public class Emp { private Integer id; private String empName; private Double salary; private Integer deptId; /** * 员工所属的部门,一个员工对应一个部门,如果查询员工【附加部门信息】,主体是员工,属于一对一 */ private Dept dept; }
<!-- resultMap是手动映射的类型 id属性:表示resultMap的唯一标识 type属性:表示resultMap映射的类型(结果集最终要封装到哪个类的对象中) autoMapping="true"能够自动映射的字段你就进行自动映射 --> <resultMap id="empWithDeptMap" type="Emp" autoMapping="true"> <!-- id标签用来映射主键字段 column属性表示要映射的结果集的字段名 property属性表示要映射到实体类的属性名 --> <!-- result标签用来映射非主键字段 column属性表示要映射的结果集的字段名 property属性表示要映射到实体类的属性名 --> <!-- association标签表示关联另外一个对象 javaType:表示关联对象的类型,可以省略 property:表示关联的这个对象,要赋值给我本身实体类对象的哪个属性 --> <association property="dept" javaType="Dept" autoMapping="true"> <id column="dept_id" property="id"/> </association> </resultMap> <!-- resultType是自动映射的类型,它要求结果集的字段名与实体类的属性名一致 --> <select id="selectEmpWithDeptById" resultMap="empWithDeptMap"> SELECT e.id,e.emp_name,e.salary,e.dept_id,d.dept_name,d.description FROM t_emp e LEFT JOIN t_dept d ON e.dept_id=d.id WHERE e.id=#{id} </select>
-
resultMap:【一对多】多表查询
如果是一对多,一个类中持有另一个类的集合属性
@Data public class Dept { private Integer id; private String deptName; private String description; /** * 部门与员工的一对多关系,想要查某个部门,连带它下属的所有员工,则需要联表查询,并且是一对多 */ private List<Emp> empList; }
<resultMap id="deptWithEmpListMap" type="Dept"> <!--【一对多】不能使用自动映射,--> <id column="id" property="id"/> <result column="dept_name" property="deptName"/> <result column="description" property="description"/> <!-- collection表示关联一个集合 ofType表示集合的泛型,可以省略 property表示关联的这个集合要赋给原本实体类对象的哪个属性 --> <collection property="empList" ofType="Emp" autoMapping="true"> <id column="empId" property="id"/> <result column="id" property="deptId"/> </collection> </resultMap> <select id="selectDeptById" resultMap="deptWithEmpListMap"> SELECT d.id,d.dept_name,d.description,e.id empId,e.emp_name,e.salary FROM t_dept d LEFT JOIN t_emp e ON e.dept_id=d.id WHERE d.id=#{id} </select>
-
- 动态SQL标签
-
if
if标签往往是配合其他标签来使用的,在test属性后备注上条件,如果符合条件,则添加该语句片段
<if test="money != null and money > 0"> AND money > #{money} </if>
-
where
WHERE标签的作用:
- 如果有条件满足的话,则在第一个条件前加上WHERE关键字
- 可以自动去掉
第一个条件前
的连接符(AND或者OR)
<select id="findAccountListByNameAndMoney" resultType="Account"> SELECT account_id,account_name,money FROM t_account <where> <if test="accountName != null and accountName != ''"> account_name LIKE CONCAT('%',#{accountName},'%') </if> <if test="money != null and money > 0"> AND money > #{money} </if> </where> </select>
-
set
在
修改数据
时,set标签的作用:- 如果有条件满足的话,则在
第一个条件前
加上SET关键字 - 自动去掉条件前的连接符(,)或者去掉最后一个条件后的连接符(,)
- 另外,在set标签中使用if标签可以指定修改字段,而不必修改整条信息
<update id="updateAccount"> UPDATE t_account <set> <if test="accountName != null and accountName != ''"> account_name=#{accountName}, </if> <if test="money != null and money >= 0"> money=#{money} </if> </set> WHERE account_id=#{accountId} </update>
- 如果有条件满足的话,则在
-
trim
set标签和where标签能做的它都能做,通过变化前缀和后缀,更灵活
trim标签的属性:
- prefixOverrides: 去掉所有if标签拼接后的大句子的前缀
- suffixOverrides: 去掉所有if标签拼接后的大句子的后缀
- prefix:添加指定前缀
- suffix:添加指定后缀
针对update修改: <update id="updateAccount"> UPDATE t_account <trim prefix="SET" prefixOverrides="," suffixOverrides=","> <if test="accountName != null and accountName != ''"> account_name=#{accountName}, </if> <if test="money != null and money >= 0"> money=#{money} </if> </trim> WHERE account_id=#{accountId} </update>
针对where条件: <select id="findAccountListByNameAndMoney" resultType="Account"> <include refid="selectFields"></include> <!-- 1. 添加WHERE前缀 2. 去掉AND或者OR前缀 3. 去掉AND或者OR后缀 --> <trim prefix="WHERE" prefixOverrides="AND|OR" suffixOverrides="AND|OR"> <if test="accountName != null and accountName != ''"> AND account_name LIKE CONCAT('%',#{accountName},'%') </if> <if test="money != null and money > 0"> AND money > #{money} </if> </trim> </select>
-
choose、when、otherwise
组合使用,实现类似Java中的swith-case-default选择结构,和【if】作用类似,例如:
<select id="getUser" resultType="User"> SELECT * FROM users <where> <choose> <when test="userId != null"> AND user_id = #{userId} </when> <when test="username != null"> AND username = #{username} </when> <otherwise> AND email = #{email} </otherwise> </choose> </where> </select>
-
each
foreach标签的属性:
- collection表示要遍历的集合【通常是POJO的List集合】
- item表示遍历出来的每一个元素【即每个POJO对象】
- separator表示每个元素之间的分隔符
- open属性: 拼接的字符串的开头
close属性: 拼接的字符串的结尾
<!--插入多条数据时使用--> <insert id="insertBatch"> INSERT INTO t_account (account_name,money) VALUES <foreach collection="accountList" item="account" separator=","> (#{account.accountName},#{account.money}) </foreach> </insert>
<!--返回部分行的记录集合--> <select id="findAccountListByAccountIds" resultType="Account"> <include refid="selectFields"></include> <foreach collection="accountIds" item="id" separator="," open="WHERE account_id IN (" close=")"> #{id} </foreach> </select>
-
sql和include
使用的sql定义的片段可以被其他地方复用,在复用的使用使用【include】标签标注
<!--定义SQL片段:使用sql标签定义id--> <sql id="selectFields"> SELECT account_id,account_name,money FROM t_account </sql> <!--使用include复用指定id的sql语句片段--> <select id="findAccountListByName" resultType="Account"> <include refid="selectFields"></include> <if test="name != null and name != ''"> WHERE account_name LIKE CONCAT('%',#{name},'%') </if> </select>
2:Mybatis与Spring的整合
-
配置SqlSessionFactoryBean
配置Mybatis的SqlSessionFactoryBean需要先获取DataSource对象
<!--1. 配置DataSource--> <context:property-placeholder location="classpath:application.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${datasource.username}"/> <property name="password" value="${datasource.password}"/> <property name="url" value="${datasource.url}"/> <property name="driverClassName" value="${datasource.driverClassName}"/> </bean>
Mybatis主要使用SqlSessionFactoryBean来管理数据库
<!--2.配置SqlSessionFactoryBean--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--2.1 配置数据源--> <property name="dataSource" ref="dataSource"/> <!--2.2 设置映射配置文件的路径--> <property name="mapperLocations" value="classpath:mappers/*.xml"/> <!--2.3 类型别名注册--> <property name="typeAliasesPackage" value="com.atguigu.pojo"/> <!--2.4 驼峰映射--> <property name="configuration"> <bean class="org.apache.ibatis.session.Configuration"> <property name="mapUnderscoreToCamelCase" value="true"/> </bean> </property> </bean>
-
配置MapperScannerConfigurer
用来配置Mapper扫描器,扫描所有的持久层接口
<!--3. 配置MapperScannerConfigurer--> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.atguigu.mapper"/> </bean>
-
spring如何整合第三方框架
FactoryBean机制
- 什么是FactoryBean机制:你配置的是XXXFactoryBean,但实际上IOC容器底层会创建XXXFactory对象放到IOC容器中
- 为什么要使用FactoryBean机制呢:其实是为了将Bean的创建、Bean的各种属性的设置进行封装,主要是为了整合第三方框架
FactoryBean和BeanFactory有什么区别?
- BeanFactory是Spring框架的根接口,用于管理和获取Bean对象。
- FactoryBean是一个特殊的Bean,它是一个工厂类的抽象,用于创建和管理其他Bean对象。
3:Mybatis的缓存(了解)
- 一级缓存
- 二级缓存
4:分页插件的使用
-
开启分页:
PageHelper.startPage(pageNum,pageSize);//在业务层的实现类中开启
-
调用持久层的方法查询列表
-
将第二步查询出来的列表,封装到PageInfo对象中
@Override public PageInfo<User> findPage(Integer pageNum, Integer pageSize) { //1.开启分页 PageHelper.startPage(pageNum, pageSize); //2.查询数据列表 List<User> userList = userMapper.selectAll(); //3返回页面信息对象,并且指明最多显示10个页码 return new PageInfo<>(userList,2); }
-
分页插件需要在spring-persist中的SQLSessionFactoryBean中配置【插件属性】
<!--2. 配置SqlSessionFactoryBean--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 在 plugins 属性中配置 Mybatis 插件 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <props> <!-- 设置 reasonable 为 true 表示将页码进行合理化修正。页码的有效范围:1~总页数 --> <prop key="reasonable">true</prop> <!-- 数据库方言:同样都是 SQL 语句,拿到不同数据库中,在语法上会有差异 --> <!-- 默认情况下,按照 MySQL 作为数据库方言来运行 --> <prop key="helperDialect">mysql</prop> </props> </property> </bean> </array> </property> </bean>
三、SpringMVC
1:注解
-
Controller注解和RestController注解的区别
-
一般在Controller的类上添加@Controller注解即可;
1:当一个视图层类里的所有方法头上都有@ResponseBody注解时,可以将所有的@ResponseBody注解去掉,只需要将@Controller替换为@RestController即可;
2:即:
@ResponseBody + @Controller = @RestController
-
RequestMapping注解
-
作用:将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系,SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求
-
能用在什么地方:Controller类上和类中的方法上
-
有哪些属性(老版)
- value/path:映射的URL路径
- method :请求方式
-
进阶版:把
RequestMapping放在类上
配合使用下面的注解
放在方法上
、、:- GetMapping 【普通方法,转到页面也能用】
- PostMapping【普通方法也能用】
- DeleteMapping
- PutMapping
-
-
RequestParam注解
-
作用
-
用在什么地方
-
有哪些属性
- value:传入的参数名
- required:默认值为true,表示请求参数默认必须提供,如果不提供会报400【数据注入错误】
-
@RequestParam(value = "userName", required = false, defaultValue = "missing")
required 属性设置为 false 表示这个请求参数可有可无
使用 defaultValue 属性给请求参数设置默认值
-
//前端页面: <a th:href="@{/param/one/name/one/value(userName='tom')}">一个名字一个值的情况</a> //postman: http://locahost:8080/user/demoMethod?username=张三疯 @GetMapping("/demoMethod") public String demoMethod(@RequestParam("userName") String userName) { return "逻辑视图"; }
-
-
RequestBody注解
-
- 作用:获取axios穿过来的Json数据
- 用在什么地方:异步请求
-
axios({ url : "/user", method : "POST", data:this.user //需要传入数据的时候传入json对象,区别于JavaWeb阶段的params }).then(response => { console.log(response.data); if (response.data.code == 200) { //增加成功则转到展示页面 location.href = "/userShow.html"; }else { //转发错误页面 location.href = "/error-500.html"; } })
-
后台:肯定是使用对象来接收Json数据
-
@PostMapping public CommonResult addUser(@RequestBody User user){ boolean success = userService.addUser(user); return CommonResult.ok(); }
-
-
Pathvariable注解
-
获取Restful风格的路径参数:
1:先在映射路径获取参数值
2:再在方法形参传入路径参数
例如:
//postman:http://locahost:8080/user/1/2 分页的URL //可以获取多个Pathvariable路径参数 @GetMapping("/{pageNum}/{pageSize}") public String findPage(@PathVariable("pageNum") Integer pageNum,@PathVariable("pageSize") Integer pageSize,Model model){ return "page"; }
-
-
ResponseBody注解 :写在Controller的方法上,表示响应体是Json数据
-
Validate的注解:数据校验时写在Controller方法的形参旁边,校验传来的参数是否符合要求
-
ExceptionHandler注解
-
ControllerAdvice注解
2:配置
-
springmvc配置文件:【spring-web】
-
开启mvc注解驱动:
-
用了mvc:annotation-driven后,默认会帮我们注册类,其中最主要的两个类:RequestMappingHandlerMapping和RequestMappingHandlerAdapter,分别为HandlerMapping的实现类和HandlerAdapter的实现类
HandlerMapping的实现类的作用
实现类RequestMappingHandlerMapping,它会处理@RequestMapping 注解,并将其注册到请求映射表中。HandlerAdapter的实现类的作用
实现类RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
当配置了mvc:annotation-driven/后,Spring就知道了我们启用注解驱动。然后Spring通过context:component-scan标签的配置,会自动为我们将扫描到的@Component,@Controller,@Service,@Repository等注解标记的组件注册到工厂中,来处理我们的请求。
<!--加载mvc注解驱动--> <mvc:annotation-driven />
-
Thymeleaf视图解析器:【spring-web】
-
配置视图解析器之后可以直接返回【逻辑视图】,由视图解析器引擎来解析【动态页面】
【注】:动态页面是位于【WEB-INF】文件夹内的资源
<!--3.配置Thymeleaf的视图解析器--> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="order" value="1"/> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <!--模板类型--> <property name="templateMode" value="HTML5"/> <!--模板的字符编码--> <property name="characterEncoding" value="UTF-8" /> </bean> </property> </bean> </property> </bean>
-
view-controller的配置:【spring-web】
-
通过view-controller标签访问视图:代替之前在html页面需要自己的Controller才能跳转
path属性:表示要匹配的请求路径
view-name属性:表示要显示的【逻辑视图名】:需要走Thymeleaf视图解析器<mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/index.html" view-name="index"/> <mvc:view-controller path="/userAdd.html" view-name="userAdd"/>
如果不需要中间在共享域传递参数等操作,可以直接使用标签跳转页面;
如果需要中间操作则定义Controller方法来转到页面,例如:
//rest风格一般用在增删改查中,对于其他的Controller方法和传统方式一样 @GetMapping("/upload.html")//表示转到upload页面 public String toUploadPage(@RequestParam("id") Integer id,Model model){ model.addAttribute("id",id); return "upload"; }
-
静态资源处理器的配置
-
在 Servlet 容器中,有一个称为 "default servlet" 的特殊 Servlet,它用于处理静态资源请求,例如图片、CSS 文件、JavaScript 文件等。默认情况下,这些静态资源的请求不会经过 Spring MVC 框架的处理流程,而是由容器的默认 Servlet 处理。
通过配置 <mvc:default-servlet-handler />,我们告诉 Spring MVC 框架将【静态资源的请求】交给容器的默认 Servlet 处理。这样可以确保静态资源能够正确地被访问和加载,而不会被 Spring MVC 框架拦截或处理。
<mvc:default-servlet-handler />
-
拦截器的配置
-
客户端=>Filter=>DispatcherServlet=>
Interceptor <=> Controller
可以配置多个拦截器组成拦截器链;
自定义拦截器实现 HandlerInterceptor 接口,实现三个方法;
<!--4. 配置拦截器--> <mvc:interceptors> <mvc:interceptor> <!--拦截路径--> <mvc:mapping path="/**"/> <!--拦截器类1--> <bean class="com.atguigu.interceptor.Demo01Interceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <!--拦截器类--> <bean class="com.atguigu.interceptor.Demo02Interceptor"/> </mvc:interceptor> </mvc:interceptors>
-
文件解析器的配置
-
<!--配置文件上传的解析器--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 由于上传文件的表单请求体编码方式是 multipart/form-data 格式,所以要在解析器中指定字符集 --> <property name="defaultEncoding" value="UTF-8"/> </bean>
-
类型转换器的配置
-
FormattingConversionService 是 Spring 的一个核心概念,它负责在不同类型之间进行格式化和转换操作。例如,将字符串转换为日期对象,或将数字转换为货币格式等;
通过配置 FormattingConversionServiceFactoryBean,我们可以定义和注册自定义的类型转换器、格式化器、解析器等,以满足应用程序特定的格式化和转换需求
<!--注册类型转换器--> <bean id="formattingConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <!-- 在 converters 属性中指定自定义类型转换器 --> <property name="converters"> <set> <bean class="com.atguigu.converter.AddressConverter"/> </set> </property> </bean>
-
-
web.xml
-
DispatcherServlet的配置
-
继承关系:DispatcherServlet ==> FrameworkServlet ==> HttpServletBean
servlet 标签用来告诉Tomcat容器,让其实例化一个Servlet对象
servlet-name 是Servlet对象的名称:此处是分发的Servlet
servlet-class 表名这个Servlet是来自哪个公司的类
init-param 表示
Servlet初始化参数
【此处表示在示例化Servlet对象时加载spring的配置文件】contextConfigLocation
参数指定的Spring配置文件load-on-startup 表示应用程序启动时就对Servlet初始化【调用init方法】
因为Servlet默认只有在第一次访问的时候才调用初始化方法,会使得第一次访问变得非常慢
servlet-mapping 表示在URL访问路径输入什么会到达这个Servlet中【/】
-
<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:spring/spring-web.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>
-
ContextLoaderListener的配置【分布式项目适用】
-
继承关系:ContextLoaderListener =>ContextLoader类 => ServletContextListener接口
listener 标签定义一个监听器接口,使用ContextLoaderListener 实现类来加载Spring上下文
context-param 初始化参数:通过
contextConfigLocation
参数指定的Spring配置文件总结:应用于监听服务器的启动,并且在服务器启动的时候根据初始化参数读取spring的配置文件,从而创建IOC容器
<!--配置ContextLoaderListener监听器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-service.xml</param-value> </context-param>
问题:DispatcherServlet也能够在服务器启动的时候加载Spring的配置文件创建IOC容器,那为什么还需要ContextLoaderListener呢?
1:分布式项目中:
表现层项目使用DispatcherServlet加载配置文件创建IOC容器
业务层、持久层项目使用ContextLoaderListener加载配置文件创建IOC容器2:两个容器的问题:
可能会发生重复创建Bean的问题【include和exclude
】3:两个容器之间是什么关系呢?
父子关系:ContextLoaderListener创建的IOC容器是DispatcherServlet创建的IOC容器的父容器,子容器中能够获取父容器的对象【子.setParent(父)】<!--解决两个容器的重复扫描问题--> <!--spring-web.xml 包扫描只包含Controller--> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan> <!--spring-service.xml 包扫描排除Controller--> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan> <!--持久层因为没有实现类,所以不需要包扫描-->
-
EncodingFilter的配置
-
CharacterEncodingFilter 最终继承
Filter 接口【过滤器接口】
filter表示Filter接口:filter-class 为具体的实现类,由spring框架提供
filter-mapping 表示映射路径:【/*】表示对所有的请求都过滤
<!-- 配置过滤器解决 POST 请求的字符乱码问题 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- encoding参数指定要使用的字符集名称 --> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!-- 请求强制编码 --> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <!-- 响应强制编码 --> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
请求方式转换的过滤器的配置
-
HiddenHttpMethodFilter最终也是继承自Filter接口:在配置文件属于【过滤器链】的一个环节
工作原理是将form表单的【_method】属性值在doFilterInternal方法中转化为标准的Http方法,即GET,、POST、PUT、DELETE等等,然后到Controller中找到对应的方法
需要注意的是,由于doFilterInternal方法
只对method为post的表单
进行过滤<!--请求方式转换的过滤器--> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!--对应前端页面--> <form action="..." method="post"> <input type="hidden" name="_method" value="put" /> ......使用隐藏式标签提交PUT和DELETE请求 </form>
-
3:SpringMVC请求和响应流程
-
请求映射
- DispatcherServlet的配置:用来拦截所有请求,并分发到各个Controller上
- RequestMapping注解:一般写在Controller类上,用来映射路径
- Controller注解或者RestController注解:表示这是一个Controller组件,后者表示这个组件所有的方法都是不走视图解析器,而是传递json数据的。
-
获取请求参数
-
普通类型参数
-
单个参数RequestParam注解
-
@RequestParam适用于单个或少量参数的键值对访问,例如:
http://localhost:8080/user/download?id=/files/pg/uy/b3dafa782ea848bdbc126da272f350d7.png
@Pathvariable 适用于少量路径参数,例如:
http://localhost:8080/user/22 即查询id为22的用户
<form action="/user/upload" method="post" enctype="multipart/form-data"> <input type="hidden" name="id" th:value="${id}"/> <input type="file" name="image"/><br/><br/><br/> <input type="submit" value="上传"/> </form> 相当于请求:http://localhost:8080/user/upload?id=[]&image=[] 此处的image在后台接收的时候直接转成了MultipartFile对象,看不到image是什么值 ------------------------------------------------------------------- //1.少量键值对参数 //两个不同类型的参数不能绑定到一个对象中,只能使用两个RequestParam来接收 @PostMapping("/upload") public String upload(@RequestParam("id") Integer id ,@RequestParam("image") MultipartFile multipartFile) throws IOException { //编写下载逻辑 return "redirect:/user"; } ------------------------------------------------------------------- //2.单个路径参数: @GetMapping("/{id}") public String findUserById(@PathVariable("id") Integer id,Model model){ User user = userService.findById(id); model.addAttribute("user",user); return "userUpdate"; }
-
多个参数(多个键值对:
常见于表单
)- Bean封装,不需要加注解
- Map封装,需要加RequestParam注解
-
-
路径参数
- Pathvariable注解
-
JSON类型参数
- RequestBody注解
-
解决请求参数的中文乱码问题
- EncodingFilter的配置
-
-
类型转换
- NumberFormat注解
- DateTimeFormate注解
- 自定义类型转换器
- 编写自定义类型转换器类:实现Convertor接口
- 配置类型转换器
-
数据校验
- Validated注解加载要进行数据校验的方法的参数前
- 在实体类的属性上添加各种校验注解
-
域对象的使用
- 请求域
- 会话域
- 应用域
-
方法的返回值
-
同步请求
-
逻辑视图
-
return "redirect:/newUrl"; 使用 redirect: 前缀指示重定向
-
redirect:重定向路径
-
return "redirect:/newUrl"; 使用 redirect: 前缀指示重定向
-
forward:转发路径
-
转发是默认行为,因此可以在返回逻辑视图时省略 forward: 前缀
-
ModelAndView对象
-
-
异步请求
- 封装响应数据的对象
- 方法上添加ResponseBody注解,或者类上添加ResponseBody注解,或者类上使用RestController注解
-
-
统一异常处理
- ControllerAdvice注解用在统一异常处理的类上
- ExceptionHandler注解用在统一异常处理的方法上
-
拦截器
- 拦截器与过滤器的相同点
- 拦截器与过滤器的不同点
- 拦截器的使用
- 创建拦截器类,实现HandlerInterceptor接口
- 配置拦截器
-
文件上传和下载
四、补充
一、ApplicationContext-spring.xml配置
属于Spring范畴
ApplicationContext 是 Spring Framework 中的一个接口,用于表示整个 Spring 应用程序的上下文环境。
ApplicationContext 是
核心容器
,在启动 Spring 应用程序时创建的,它会读取配置文件(如 XML、注解等)来实例化和配置应用程序中的对象。它负责依赖注入、生命周期管理、AOP 切面等关键功能,为开发者提供了一个可集中管理和访问各个组件的容器。
WebApplicationContext
是 ApplicationContext 的子接口,它专门用于在 Web 环境中使用。它提供了与 Web 相关的功能和特性,如对 Servlet、Filter、Listener 等 Web 组件的支持、对 Web 上下文的管理、处理 Web 请求等。WebApplicationContext 就是IOC容器,该接口下面有两个两个实现类:
XmlWebApplicationContext
和AnnotationConfigWebApplicationContext
,前者是通过xml文件来创建IOC容器并扫描bean,后者是通过配置类来创建IOC容器再扫描bean【bean就是Component】
ApplicationContext 的主要特点和功能包括:
1:依赖注入(Dependency Injection):通过容器自动将依赖关系注入到组件中,实现解耦和灵活性。
2:面向切面编程(Aspect-Oriented Programming,AOP):提供 AOP 功能,通过横切关注点来实现系统功能的模块化和重用。
3:国际化支持:提供国际化资源的管理和访问,便于多语言应用程序的开发。
4:事件传播:支持应用程序内部事件的发布和监听,实现组件之间的松耦合通信。
5:生命周期管理:管理组件的生命周期,包括初始化、销毁等操作。
6:配置文件管理:读取和解析配置文件,将配置信息应用到应用程序中的对象。
7:拦截器支持:提供拦截器机制,用于处理请求和响应,实现功能的增强和定制。
二、ServletContext-web.xml配置
ServletContext 是 Java Servlet 规范中的一个接口,用于提供 Web 应用程序的运行环境信息和功能。它表示整个 Web 应用程序的上下文,可以在应用程序的各个组件(如 Servlet、过滤器、监听器等)中使用。
ServletContext 接口提供了一系列方法来获取和操作 Web 应用程序的上下文信息,包括:
1:获取应用程序的
初始化参数(通过 web.xml 文件配置 init标签)
。2:获取 Web 应用程序的根目录的真实路径【物理路径】。
3:获取 Web 应用程序的名称。
4:在应用程序范围内存储和获取属性。【共享域】
5:在应用程序范围内发送和接收事件通知。
6:获取与 Web 容器相关的信息,如 Servlet 容器的版本、服务器的名称等。
// 获取ServletContext对象【自动注入】
@Autowired
private ServletContext servletContext;
// 获取ServletContext对象【传统doGet】
ServletContext servletContext = getServletContext();
-----------------------------------------------------------------------
// 获取Web应用程序的初始化参数
String paramValue = servletContext.getInitParameter("paramName");
// 设置和获取共享的属性
servletContext.setAttribute("attributeName", "attributeValue");
Object attributeValue = servletContext.getAttribute("attributeName");
// 获取Web应用程序的真实路径
String realPath = servletContext.getRealPath("/WEB-INF/data/file.txt");
// 获取Web应用程序的上下文路径【/】后面的项目部署别名:/bookstore
String contextPath = servletContext.getContextPath();
// 获取Web应用程序的名称[暂时运行时为null]
String appName = servletContext.getServletContextName();
// 获取MIME类型【媒体文件类型】
String mimeType = servletContext.getMimeType("file.txt");
// 获取服务器信息
String serverInfo = servletContext.getServerInfo();
// 获取指定路径下的资源并转换为输入流
InputStream in = servletContext.getResourceAsStream("/WEB-INF/data/file.txt");
三、依赖和使用场景
依赖名称:artifactId | 依赖所属组织:groupId | 版本:version | 范围:scope | 作用 |
---|---|---|---|---|
spring-webmvc | org.springframework | 5.3.1 | 默认(compile) | 提供了构建Web MVC应用所需的核心功能,包括控制器和处理器映射、视图解析器、拦截器、表单处理和数据绑定、文件上传和下载等 |
四、小细节
Q:@Resource注解和@Qualifier注解有什么区别呢?
A:@Resource 和 @Qualifier 注解都是 Spring 中用于依赖注入的注解,它们都可以用于
指定要注入的 Bean 的名称
,但它们有一些区别:
- @Resource 注解是 JavaEE 标准的注解,而 @Qualifier 注解是 Spring 自带的注解,是 Spring 特有的。
- @Resource 注解默认按名称匹配,并且可以通过
name
属性指定要注入的 Bean 的名称;而 @Qualifier 注解不能按名称匹配,只能通过value
属性指定要注入的 Bean 的名称。- @Resource 注解只能注入其他 Bean,而 @Qualifier 注解既可以注入 Bean,也可以注入其他依赖的值、属性等。
- @Resource 注解不能与 @Autowired 注解一起使用,而@Qualifier 注解可以与 @Autowired 注解一起使用,用于指定要注入的 Bean 的名称(当做一种限定)。
当IOC容器中有多个类型相同的Bean时,使用@Resource注解可以结合name属性或者type属性来指定需要注入的Bean。name属性指定要注入Bean的名称,type属性指定要注入Bean的类型。如果指定name属性,则会根据名称查找对应的Bean进行注入;如果指定type属性,则会根据类型查找对应的Bean进行注入。
使用@Autowired注解时,如果存在多个类型相同的Bean,则需要结合@Qualifier注解一起使用。@Qualifier注解指定需要注入的Bean的名称或者类型。如果指定名称,则会根据名称查找对应的Bean进行注入;如果指定类型,则会根据类型查找对应的Bean进行注入。
因此,可以看出@Resource和@Autowired结合@Qualifier的作用是类似的,但@Resource注解可以更加灵活地指定需要注入的Bean,而@Autowired注解需要结合@Qualifier注解进行使用。
public class UserController {
//
@Resource(name = "userService")
private UserService userService;
@Autowired
@Qualifier("userDao")
private UserDao userDao;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void getUser() {
userService.getUser();
userDao.getUser();
}
}
标签:事务,一篇,对象,IOC,测试,使用,注解,更换,id
From: https://www.cnblogs.com/newleesoo/p/17437414.html