静态资源映射
SpringMVC的前端控制器(DispatcherServlet)会拦截请求,配置是“/*”会拦截所有的请求。如果请求的url是”reg.html”,那么前端控制器会去Controller中找url相匹配的handler(处理器),而不是去调度静态页面,所以前端控制器会把静态资源过滤掉。
通过静态资源映射,告诉前端控制不要拦截静态资源。
在spring-mvc.xml中添加配置:
<!-- 静态资源映射 -->
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/**" location="/"/>
中文处理
请求中和响应中常常有中文乱码的问题,springMVC提供了一个专门的字符过滤器,可以处理中文乱码。
我们只需要在web.xml中配置过滤器即可。
<!-- spring的中文过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 默认是utf-8 我们可以通过初始化参数配置 -->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
我们使用的是tomcat7,tomcat8以后,tomcat本身的编码设置就已经是utf-8。tomcat7的编码设置是
iso8859-1。
上述方法为POST请求解决乱码方式,如果是GET请求,中文就会乱码。 所以,发送中文不要使用GET请求。
类型转换器
当前端请求中带有日期格式的参数时:
处理方案:
- 在实体类的属性的上面添加注解,配置日期格式:
@DateTimeFormat(pattern="yyyy-MM-dd")
- 请求中的格式:1999-08-20;用实体类接收参数
- 使用形参接收日期数据时:
public String reg(String name,@DateTimeFormat(pattern="yyyy-MM-dd" Date birth){};
- 如果我们的系统中有大量的日期格式要处理,我们可以配置一个日期格式转换器。
我们自定义一个类型转换器: 实现接口org.springframework.core.convert.converter.Converter
tips:这个接口中的泛型S表示原始类型,T表示目标类型。
public class DateFormatConverter implements Converter<String,Date> {
@Override
public Date convert(String s) {
try{
return new SimpleDateFormat("yyyy-MM-dd").parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
<!-- 开启MVC注解驱动 -->
<mvc:annotation-driven conversion-service="conversionService" />
<!-- 配置各种转换器服务 -->
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.quail.utils.DateFormatConverter"/>
</set>
</property>
</bean>
测试:
@Controller
public class DateFormController {
@RequestMapping("/dateForm")
public void dateForm(String uname,Date date){
System.out.println(uname+":"+date);
}
}
拦截器
拦截器其实就是使用spring的AOP实现了一个请求处理的增强。类似filter
如果要实现拦截器需要实现一个接口:org.springframework.web.servlet.HandlerInterceptor
源码:
public interface HandlerInterceptor {
/*** 在进入请求之前进行预处理(在进入请求之前执行),这个方法可以拦截请求的执行。
* @param request current HTTP request
* @param response current HTTP response
* @param handler 处理器对象
* @return {@code true} 如果返回true,表示放行,否则表示拦截
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//我们可以使用request转发,也可以使用repsonse重定向。
return true;
}
//请求处理之后,但是return之前。 这个方法不能拦截请求,但是可以修改这个请求最终去的方向和数据
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
//如果我们将这里的modelAndView对象的视图名称修改了,那么这个请求最终就会进入我 们修改后的视图。
}
//请求完全结束之后执行,扫尾工作。 释放资源,处理异常。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
}
自定义拦截器:
package com.quail.utils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author quail
* @date 2021/3/25 18:59
* @TODO 自定义拦截器
*/
public class QuailInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行了preHandle拦截:"+handler);
return true;//false阻断,true放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行了postHandle拦截");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("执行了afterCompletion拦截");
}
}
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 要拦截的请求路径 -->
<mvc:mapping path="/*"/>
<!-- 不拦截的请求路径 -->
<mvc:exclude-mapping path="/*.html"/>
<mvc:exclude-mapping path="/css/**"/>
<mvc:exclude-mapping path="/js/**"/>
<mvc:exclude-mapping path="/image/**"/>
<mvc:exclude-mapping path="/doLogin"/> <!-- 配置我们自己的拦截器 -->
<bean class="com.quail.utils.QuailInterceptor"/>
</mvc:interceptor>
<!-- 还可以配置更多的拦截器 -->
<!-- <mvc:interceptor>-->
<!-- <mvc:mapping path=""/>-->
<!-- </mvc:interceptor>-->
</mvc:interceptors>
- 多个拦截器的执行顺序
统一异常处理器
项目中的异常处理?
编译期异常:try。。。catch。。。输出到控制台。
运行时异常:没处理。
异常不能直接“吞掉”。 就是在底层直接输出到控制台。
底层出现的异常,全部打包,并且抛到上层(controller)。
有的企业会有自己的开发API:
①自己的框架(大部分都是从开源框架重构的)
②自己一套自定义的异常。一般自己定义的异常都是运行时异常。
底层的编译期异常,要处理,并且包裹成运行期异常抛出。
spring提供了一个统一的异常处理器。我们需要自己实现接口:org.springframework.web.servlet.HandlerExceptionResolver
package com.quail.utils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author quail
* @date 2021/3/25 19:13
* @TODO
*/
public class QuailExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//记录日志(文件,数据库,邮件)
//发短信,发邮件
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("ex", ex);
return modelAndView;
}
}
我们只要将这个类交个spring管理即可,spring会自动识别。
事务
原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性:持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
事务的隔离级别:
先看几个概念:
脏读:读取到别的事务尚未提交的数据。
不可重复读: 在同一次事务中重复读取的两次数据时不一致的,这种现象都是由于update操作导致的。
幻读: 在同一次事务中,读取的同一批数据的数量不一致。这种现象都是因为insert/delete导致的。
两类事务丢失的问题:
[1]第一类事务丢失,是因为事务提交导致事务丢失。
A查看数据库金额是100,准备添加50进去。最终应该是150.
A 查询完成之后,在没有修改数据之前,B快速的修改了数据,将100修改为150,并且提交了数据。A线程在提交事务之后要保证数据库应该是150。 这样就导致B线程的事务丢失。
[2]第二类事务丢失,正好相反,是因为事务回滚导致事务丢失。
主要原因就是:隔离级别不够。
面试题:什么是事务?
事务就是一组操作。这些操作要么一起成功,要么一起失败。
我们常说的事务就是对数据库的一组操作,要么一起成功,要么回滚。
事务的4中隔离级别:
readuncommit:允许读取未提交的数据,必然会有脏读, 幻读,不可重复的现象。
readcommit:不允许读取未提交的数据,不会有脏读。 已然会有幻读和不可重复读的问题。(Oracle数据库的默认隔离级别)
readrepeatable:可重复,解决了脏读和不可重复读的问题,已然可能产生幻读。(MySQL默认隔离级别)
Serialization:串行化。同步执行。同一时刻,统一数据只有一个事务可以操作。可以解决所有问题。但是效率贼低。一般都不会使用。
编程式事务
public interface PlatformTransactionManager extends TransactionManager {
//得到一个事务状态对象(开启一个事务)
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus var1) throws TransactionException; }
}
Spring实现事务需要实现一个接口:TransactionStatus
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
//是否有保存点
boolean hasSavepoint();
void flush();
//父类的方法
//是否是一个新事务
boolean isNewTransaction();
//是否只回滚
void setRollbackOnly();
boolean isRollbackOnly();
//是否已经完成
boolean isCompleted();
//还有很多其他的属性
}
spring提供了这个事务处理的接口,不同的数据库有不同的事务管理器的实现。
MySQL数据库的事务管理器采用的是org.springframework.jdbc.datasource.DataSourceTransactionManager
在spring的配置文件中添加事务管理器和事务处理模板类配置:
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 编程式事务模板类 这个类是spring提供的 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="timeout" value="6000"/><!-- 超时时间 -->
<property name="readOnly" value="false"/>
<property name="isolationLevel" value="1"/>
<!-- 事务的隔离级别,默认采用当前 使用的数据库的隔离级别 -1表示默认, 1,2 ,4,8 -->
</bean>
实现:
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public int transfer(String fromName, String toName, int money) {
//lamda表达式实现函数式接口
return transactionTemplate.execute((transactionStatus)->{
// 减第一个人的钱
empDao.subtraction(fromName,money);
// 手动出现一个错误
if(1==1)
throw new RuntimeException("故意出错");
// 给第二个人加钱
empDao.addition(toName,money); return 1;
});
}
Spring事务的传播特性
什么是传播特性?
事务的传播特性是说在嵌套的环节中,外层事务和里层事务的处理方案。
7中传播特性:
前提是:methodA调用methodB。传播特性是给methodB的配置。
[1] REQUIRED
必须有事务,如果当前的方法有事务,就支持,如果没有就新建。
methodB必须在事务中执行,如果methodA有事务,methodB就使用methodA的事务。如果methodA没有事务,methodB会自己新建一个事务在事务中执行。
[2]SUPPORTS
支持当前的事务,如果当前没有事务就在非事务状态下执行。
methodA如果有事务,methodB就在事务中执行,methodA没有事务,methodB就在非事务状态下执行。
[3]MANDATORY
要求必须有事务,如果没有就抛出异常。
methodB要求methodA必须有事务,如果没有,就抛出异常。
[4]REQUIRES_NEW
必须新建一个事务,如果当前事务存在,则挂起,如果不存在直接新建。
methodB必须新建事务,如果methodA存在事务,则先将methodA的事务挂起,再新建事务,如果methodA没有事务,则直接新建事务。
[5]NOT_SUPPORTED
必须在非事务状态下运行,如果当前事务存在,则直接挂起。
methodB必须在非事务状态下执行,如果methodA存在事务,直接挂起。
[6]NEVER
绝对不能有事务,如果当前事务存在,直接抛出异常。
methodB必须在非事务状态下执行,如果methodA存在事务,抛出异常。
[7]NESTED
嵌套事务。
<tx:method name="transfer"
isolation="DEFAULT"
read-only="false"
rollback-for="java.lang.Throwable"
timeout="-1"
propagation="REQUIRED"/>
<tx:method name="add"
isolation="DEFAULT"
read-only="false"
propagation="NOT_SUPPORTED"/>