课程来源
SpringMVC 拦截器 就是高级 filter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package net.yxchen.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class HelloInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("pre handle" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("post handle" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("after completion" ); } }
1 2 3 4 5 6 <mvc:interceptors > <mvc:interceptor > <mvc:mapping path ="/interceptor" /> <bean class ="net.yxchen.interceptor.HelloInterceptor" /> </mvc:interceptor > </mvc:interceptors >
(/interceptor 将页面转发到 success.jsp)
可以看出,顺序是 pre-目标方法-post-加载页面-after。多拦截器就是谁先配谁先执行。谁不放行,以后就都没有了。但是以前放行了的拦截器的 afterCompletion 还会执行。
异常处理 可以直接写一个方法处理掉特定类型的异常:
1 2 3 4 5 @ExceptionHandler(ArithmeticException.class) public String arithmeticHandler () { System.out.println("算数错误" ); return "error" ; }
自然也可以写一个异常处理类
1 2 3 4 5 6 7 8 9 10 @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(ArithmeticException.class) public ModelAndView arithmeticHandler (Exception e) { System.out.println("算数错误" ); ModelAndView mav = new ModelAndView ("error" ); mav.addObject("e" , e); return mav; } }
可以使用 @ResponseStatus 做一个自定义异常状态,类似于404-页面飞了
这种感觉,自己搜。
工作流程 (非原创,来自课程的笔记)
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
不存在:再判断是否配置了mvc:default-servlet-handler:
如果没配置,则控制台报映射查找不到,客户端展示404错误;
如果有配置,则执行目标资源(一般为静态资源,如JSP,HTML);
存在:执行下面流程;
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;
如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
此时将开始执行拦截器的postHandle(…)方法【逆向】;
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图;
在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】;
将渲染结果返回给客户端。
整合问题 通常 Spring 和 SpringMVC 整合的时候会分别写配置文件,并且分容器。所以也不会写出来一个 xml import 另一个 xml 这种鬼东西。
显然不能把 component-scan 这种东西写两遍。我们让 Spring 容器管理业务逻辑组件,用 SpringMVC 容器管理控制器组件。
springmvc.xml
1 2 3 4 <context:component-scan base-package ="net.yxchen" 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.ControllerAdvice" /> </context:component-scan >
spring.xml
1 2 3 4 5 <context:component-scan base-package ="com.atguigu.springmvc" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> <context:exclude-filter type ="annotation" expression ="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan >
在 web.xml 里头用 dispatchservlet 写 springmvc 容器,用下面的写 spring 容器:
1 2 3 4 <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:beans.xml</param-value > </context-param >
两个容器同时存在,spring 容器为父容器,springmvc 容器为子容器。springmvc 能引用 spring 的 bean,反过来不能。
MyBatis MyBatis 是一种持久化层框架(SQL 映射框架)。
使用 jdbc 麻烦又要硬编码 sql 语句。Hibernate 是一种 ORM 框架(对象关系映射),这玩意太强了,居然写 bean 就能解决建表更新等等的糟心事情。它就是个黑箱,不会 sql 也行。但是也是因为黑箱,自己写 sql 也不太方便,所以也不太好。它还是个全映射框架。MyBatis 则是一台能把 sql 写在配置文件里头的半自动洗衣机。
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.5</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.20</version > </dependency >
从文档复制一份全局配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatislearn" /> <property name ="username" value ="root" /> <property name ="password" value ="你的密码" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="EmployeeDao.xml" /> </mappers > </configuration >
1 2 3 4 5 6 7 public class Employee { private Integer id; private String empName; private String email; private Integer gender; }
1 2 3 4 5 6 public interface EmployeeDao { public Employee getEmpById (Integer id) ; public int updateEmployee (Employee employee) ; public int deleteEmployee (Integer id) ; public int insertEmployee (Employee employee) ; }
然后写 sql 映射文件,就相当于接口的实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="net.yxchen.dao.EmployeeDao" > <select id ="getEmpById" resultType ="net.yxchen.bean.Employee" > select * from t_employee where id = #{id} </select > <update id ="updateEmployee" > update t_employee set empName=#{empName}, gender=#{gender}, email=#{email} where id=#{id} </update > <update id ="deleteEmployee" > delete from t_employee where id=#{id} </update > <update id ="insertEmployee" > insert into t_employee (`empName`, `gender`, `email`) values (#{empName}, #{gender}, #{email}) </update > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class EmployeeDaoTest { @Test public void test1 () throws Exception { String resource = "mybatis.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class); System.out.println(employeeDao.getEmpById(1 )); Employee employee = new Employee (null , "poorpool" , "poorpool@yxchen.net" , 0 ); employeeDao.insertEmployee(employee); employeeDao.updateEmployee(new Employee (3 , "poorpool" , "poorpool@yxchen.net" , 1 )); employeeDao.deleteEmployee(3 ); sqlSession.commit(); sqlSession.close(); } }
太方便了 qwq。
全局配置文件 这些标签是有先后的:
可以使用 properties 标签指定外部配置文件。
settings 修改 mybatis 的运行时规则,比如可以自动驼峰(a_column 到 aColumn)。还有延迟加载和按需加载:
1 2 3 4 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings >
typeAliases 给常用的类型起别名。不过 mybatis 给常用类型起好了,我们也没有必要给自己的 bean 起别名。
enviorments 可以配多个环境(然而还是 spring 的强)
mappers 里头的 mapper,可以用 resource 写类路径下的配置文件,可以用 class 指定接口的全类名(然后把配置文件放到同一个包里头),可以用 url 写磁盘或者网络上的配置。可以使用 package 批量注册,但是这时候配置文件要写在包里头。但是把 conf 标为源根以后创建一个同名包会和 src 的同名包合并,所以也不是什么大问题。
sql 映射文件 获得自增主键的值:
1 2 3 <update id ="insertEmployee" useGeneratedKeys ="true" keyProperty ="id" > insert into t_employee (`empName`, `gender`, `email`) values (#{empName}, #{gender}, #{email}) </update >
没有自增也有相应的解决方法。
传参的时候,多个参数只能用 #{1}
或者 #{param1}
这种序号。这是因为 mybatis 将它们封装到了一个 map 中,key 是序号。可以用 @Param 指定参数封装时候的 key。可以传 map。传 pojo 就用属性名做 key。
#{xxx}
里头可以指定 jdbctype,这个好像跟 null 有关。其实 #{xxx}
和 ${xxx}
都可以取值,但是前者是参数预编译,就是参数用?
替代,不会出现 sql 注入问题。后者就是简单拼串。一般都是使用前者,不过表名这种不支持预编译的就用后者就可以了。
花式查询 查询 list 和查询 bean 的配置一模一样,只不过方法的返回值变成了 list。如果查询的是集合,那么 resultType 写的就是集合里的元素的全类名。
可以把查询结果封装到 map 里头,属性是 key,值是 value。
1 2 3 <select id ="getEmpMapById" resultType ="map" > select * from t_employee where id=#{id} </select >
上面的是查询结果一行的。如果多行呢?
1 2 @MapKey("id") public Map<Integer, Employee> getEmpsMap () ;
1 2 3 <select id ="getEmpsMap" resultType ="net.yxchen.bean.Employee" > select * from t_employee </select >
如果名字不一样呢,比如数据库是 youjian,java bean 是 email?可以使用 select 起别名:
1 2 3 <select id ="getEmpById" resultType ="net.yxchen.bean.Employee" > select `id`, `empName`, `gender`, `youjian` email from t_employee where id = #{id} </select >
也可以使用 resultmap 硬点对应:
1 2 3 4 5 6 7 <select id ="getEmpById" resultMap ="qwq" > select * from t_employee where id = #{id} </select > <resultMap id ="qwq" type ="net.yxchen.bean.Employee" > <id property ="id" column ="id" /> <result property ="email" column ="youjian" /> </resultMap >
联合查询 1 2 3 4 5 6 public class Key { private Integer id; private String keyName; private Lock lock; }
1 2 3 public class Lock {private Integer id;private String lockName;
数据库中 t_key 没有 lock,有一个外键 lockId。
如果想级联查询到这个 lock 怎么办?其实能猜出来,用 resultmap 把 lock 的 id map 到 lock.id 就差不多了。
1 2 3 public interface KeyDao { public Key getKeyById (Integer id) ; }
1 2 3 4 5 6 7 8 9 10 11 <select id ="getKeyById" resultMap ="mykey" > select k.id kid, k.keyName, l.id lid, l.lockName from t_key k left join t_lock l on k.lockId = l.id where k.id = #{id}; </select > <resultMap id ="mykey" type ="net.yxchen.bean.Key" > <id property ="id" column ="kid" /> <result property ="keyName" column ="keyName" /> <result property ="lock.id" column ="lid" /> <result property ="lock.lockName" column ="lockName" /> </resultMap >
更好的做法是使用 association,这样更清晰了:
1 2 3 4 5 6 7 8 <resultMap id ="mykey" type ="net.yxchen.bean.Key" > <id property ="id" column ="kid" /> <result property ="keyName" column ="keyName" /> <association property ="lock" javaType ="net.yxchen.bean.Lock" > <id property ="id" column ="lid" /> <result property ="lockName" column ="lockName" /> </association > </resultMap >
如果要查询集合呢?比如 Lock 里头加一个 List<Key> keys
。那就用 collection 呗。
1 2 3 4 5 6 7 8 9 10 11 12 13 <select id ="getLockById" resultMap ="mylock" > select l.id lid, l.lockName, k.id kid, k.keyName from t_lock l left join t_key k on l.id = k.lockId where l.id = #{id}; </select > <resultMap id ="mylock" type ="net.yxchen.bean.Lock" > <id property ="id" column ="lid" /> <result property ="lockName" column ="lockName" /> <collection property ="keys" ofType ="net.yxchen.bean.Key" > <id property ="id" column ="kid" /> <result property ="keyName" column ="keyName" /> </collection > </resultMap >
(不要在意套娃)
如果嫌写 sql 麻烦可以用分步查询:
1 2 3 <select id ="getLockByIdSimple" resultType ="net.yxchen.bean.Lock" > select * from t_lock where id=#{id} </select >
1 2 3 4 5 6 7 8 <select id ="getKeyByIdSimple" resultMap ="mykey2" > select * from t_key where id=#{id} </select > <resultMap id ="mykey2" type ="net.yxchen.bean.Key" > <id property ="id" column ="id" /> <result property ="keyName" column ="keyName" /> <association property ="lock" select ="net.yxchen.dao.LockDao.getLockByIdSimple" column ="lockId" /> </resultMap >
其实也就是把自己钦点变成了再 select 一次,免去了 left join 之忧。但是效率低。