• 【学习】Spring MVC + Spring Data JPA


    Spring MVC

    应用

    Spring MVC和Struts2⼀样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模式的。而这些表现层框架的主要职责就是处理前端HTTP请求。通过⼀套注解,让⼀个简单的 Java 类成为处理请求的控制器,⽽⽆须实现任何接⼝。同时它还⽀持RESTful 编程⻛格的请求。

    Spring MVC 本质可以认为是对servlet的封装,简化了我们servlet的开发
    作⽤:1)接收请求 2)返回响应,跳转⻚⾯

    经典三层和MVC模式

    Spring MVC模式和Servlet模式

    开发流程回顾

    1)配置DispatcherServlet前端控制器
    2)开发处理具体业务逻辑的Handler(@Controller、@RequestMapping)
    3)xml配置⽂件配置controller扫描,配置springmvc三⼤件
    4)将xml⽂件路径告诉springmvc(DispatcherServlet)

    请求处理流程

    前端控制器将任务派发给其他组件执行

    1.⽤户发送请求至前端控制器DispatcherServlet
    2.DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
    3.处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截
    器(如果 有则⽣成)⼀并返回DispatcherServlet
    4.DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
    5.处理器适配器执行Handler
    6.Handler执⾏完成给处理器适配器返回ModelAndView
    7.处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个
    底层对象,包括 Model 和 View
    8.前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。
    9.视图解析器向前端控制器返回View
    10.前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
    11.前端控制器向⽤户响应结果

    九大组件

    HandlerMapping(处理器映射器)

    HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor.
    

    HandlerAdapter(处理器适配器)

    HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请
    求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter的职责。
    

    HandlerExceptionResolver

    HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
    

    ViewResolver

    ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。
    

    RequestToViewNameTranslator

    RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。
    

    LocaleResolver

    ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这
    个组件也是 i18n 的基础。
    

    ThemeResolver

    ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,
    ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。
    

    MultipartResolver

    MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。
    

    FlashMapManager

    FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然可以规避⽤户重新提交订单的问题,但是在这个页面上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。
    
    

    url-pattern配置及原理

    拦截方式

    方式一:带后缀,比如 *.action  *.do  *.aaa
    方式二: /   不会拦截.jsp  但是会拦截.html等静态资源
    		项目中的web.xml继承自tomcat容器中的web.xml,tomcat中的web.xml有DefaultServlet(服务于所有的静          态资源),url-pattern为/ ;项目组的web.xml中也配置了 / ,覆写了web.xml的配置,也就使     			DefaultServlet不生效了,因此也会拦截静态资源
    方式三: /*  拦截所有,包括.jsp
    
    

    解决 / 拦截静态资源

    静态资源配置
    <!--    方案一
            原理:添加该标签配置后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象
                 这个对象对url请求进行筛查,如果是静态资源交由web应用服务器(tomcat)默认的DefaultServlet来处理
    
            缺点:只能将静态资源放在webapp下,不能放在WEB-INF、classpath下;
    -->
        <mvc:default-servlet-handler/>
        
    <!--    方案二 SpringMVC框架自己处理静态资源
            mapping: 约定静态资源的url规则
                指定多个路径   /,classpath:/
            location:指定静态资源存放位置
    
    -->
        <mvc:resources location="classpath:/" mapping="/resources/**" />
    
    

    封装数据

    SpringMVC在handler方法上传入Map、Model和ModelMap参数,并向参数中保存数据(放到请求域),都可以在页面上获取到;

    运行时的具体类型都是BindingAwareModelMap,相当于给BindingAwareModelMap中保存的数据放到请求域中。


    Map jdk接口

    Model spring接口

    ModelMap class,实现Map接口

    BindingAwareModelMap 继承了 ExtendedModelMap;ExtendedModelMap继承了ModelMap,实现了Model接口

    请求参数绑定

    原⽣servlet接收⼀个整型参数:

    String ageStr = request.getParameter("age");
    
    Integer age = Integer.parseInt(ageStr);
    
    
    

    SpringMVC框架对Servlet的封装,简化了servlet的很多操作
    SpringMVC在接收整型参数的时候,直接在Handler⽅法中声明形参即可

    @RequestMapping("xxx")
    public String handle(Integer age) {
    	System.out.println(age);
    }
    
    
    

    参数绑定:取出参数值绑定到handler⽅法的形参上


    SpringMVC接收参数类型

    原生servlet api[HttpServletRequest,HttpServletResponse,HttpSession]支持:直接在handler方法形参中声明使用即可;

    简单数据类型参数(8种基本类型及其包装类):直接在handler方法形参中声明,框架会取出参数值然后绑定到对应参数上,要求形参名和声明的形参名称一致(或者在参数上加上@RequestParam)。

    pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓,但是要求传递的参数名必须和Pojo的属性名保持⼀致。

    pojo包装类型参数[如: Vo],绑定时候直接形参声明即可;传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的方式进⼀步锁定数据。

    日期类型,需要定义一个SpringMVC的类型转换器 ——接口,扩展实现接口,注册实现

    //扩展自定义转换器
    /**
    * ⾃定义类型转换器
    * S:source,源类型
    * T:target:⽬标类型
    */
    public class DateConverter implements Converter<String, Date> {
      @Override
      public Date convert(String source) {
        // 完成字符串向⽇期的转换
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
          Date parse = simpleDateFormat.parse(source);
          return parse;
        } catch (ParseException e) {
        	e.printStackTrace();
        }
        return null;
      }
    }
    
    
    <!-- 注册自定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    	<property name="converters">
         	<set>
          		<bean class="com.converter.DateConverter"></bean>
              	<bean class="其他转换器"></bean>
          	</set>
    	</property>
    </bean>
    
    <mvc:annotation-driven conversion-service="conversionServiceBean" />
    
    
    

    Rest风格请求

    @RequestMapping(value="/test/{id}" ,method= {RequestMethod.GET})

    @PathVariable("id") 从uri中取值 ;

    post 请求乱码: springmvc提供的配置过滤器

    <filter>
    	<filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
          <param-name>encoding</param-name>
          <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
    	<filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    

    针对put、delete等请求方式转换过滤器

    判断请求中有无_method参数,有的话就拦截处理

    <filter>
    	<filter-name>encoding</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>
    
    

    Ajax Json交互

    依赖

    <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.9.8</version>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.8</version>
        </dependency>
    
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>2.9.0</version>
        </dependency>
    
    

    交互:

    前端->后端: ajax发送json格式字符串,后台直接接受为pojo参数(@RequestBody)
    后端->前端: 后台直接返回pojo对象,前端直接接收为json对象或字符串(@ResponseBody : 返回的数据不再走视图解析器流程,而是等同于response直接输出数据)
    
    

    高级技术

    监听器,过滤器,拦截器

    过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理
    监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应用的启动而启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停止而销毁。

    作⽤⼀:做⼀些初始化⼯作,web应⽤中spring容器启动ContextLoaderListener
    作⽤⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等。
    
    

    拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截
    jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)。

    拦截器

    在运⾏程序时,拦截器的执⾏是有⼀定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关。

    单个拦截器

    1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅
    法,否则将不再向下执⾏。
    2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过
    DispatcherServlet向客户端返回响应。
    3)在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法。

    多个拦截器
    preHandle()方法按照配置顺序执行;postHandle()和afterCompletion()方法按照配置反序执行

    multipart形式数据处理

    引入commons-fileupload.jar依赖

    <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.3.1</version>
        </dependency>
    
    

    客户端:form表单 【method=post,enctype=multipart,file组件】

    <!-- method="post"
         enctype="multipart/form-data"
         type="file"
    -->
    <form method="post" enctype="multipart/form-data" action="upload">
    	<input type="file" name="file"/>
    </form>
    
    

    服务端:原先servlet解析文件上传流【springmvc : 重命名(给一个唯一的名字),存储到磁盘(考虑文件目录过多,可以按照日期创建新的文件夹),把文件存储路径更新到数据库】

    @RequestMapping("/handle01")
        public String upload(MultipartFile file, HttpSession session) throws IOException {
            //重命名
            //获取原名称
            String originalFilename = file.getOriginalFilename();
    
            //获取后缀  .jpg
            String ext = originalFilename.substring(originalFilename.lastIndexOf("."), originalFilename.length());
            String newName = UUID.randomUUID() + ext;
            //存储,到指定文件夹
            String realPath = session.getServletContext().getRealPath("/uploads");
            String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            File folder = new File(realPath + "/" + datePath);
            if (! folder.exists()) {
                folder.mkdirs();
            }
    
            file.transferTo(new File(folder,newName));
    
            //路径存库
    
            return "success";
        }
    
    

    springmvc中需要配置文件上传解析器

    <!-- 配置多元素解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置最大文件数量-->
    	<property name="maxUploadSize" value="100"/>
    </bean>
    
    

    异常处理机制

    //注意:写在类中只会对当前类生效
        @ExceptionHandler(ArithmeticException.class)
        public void handleException(ArithmeticException e, HttpServletResponse response){
            //异常处理逻辑
            try{
    
            }catch (Exception exception){
    
            }
        }
    ============================================================
      //可以捕获所有controller的异常
    @ControllerAdvice
    public class GlobalExceptionResolver {
    
        @ExceptionHandler(ArithmeticException.class)
        public ModelAndView handleException(ArithmeticException e, HttpServletResponse response){
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg",e.getMessage());
            modelAndView.setViewName("exception");
    
            return modelAndView;
        }
    }
    
    

    重定向参数传递

    1、【return "redirect:xxx?name=" + name;】 //拼接参数安全性,参数长度都有局限

    2、形参中 【RedirectAttributes redirect

    ​ redirect.addFlashAttribute("name",name)

    ​ return "redirect:xxx";】

    ​ addFlashAttribute方法设置一个flash类型属性,会暂存到session中,在跳转到页面之后该属性销毁;

    自定义MVC框架

    总体流程

    详细流程

    web.xml 配置前端控制器

    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <servlet>
        <servlet-name>mymvc</servlet-name>
        <servlet-class>com.mvcframework.mymvc.MyDispatcherServlet</servlet-class>
        <init-param>
    <!--      配置需要扫描的包的存放文件(初始化参数)-->
          <param-name>scanPackagePropertiesLocation</param-name>
          <param-value>mymvc.properties</param-value>
        </init-param>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>mymvc</servlet-name>
        <url-pattern>/*</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    
    public class MyDispatcherServlet extends HttpServlet {
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            //定义初始化做的事情,然后取具体实现
           //加载配置文件
            String contextConfigLocation = config.getInitParameter("contextConfigLocation");
            doLoadConfig(contextConfigLocation);
    
    
            //扫描相关的类,扫描注解
            doScan(properties.getProperty("scanPackage"));
    
            //初始化相应的bean,维护其依赖关系
            doInstance();
    
            //实现依赖注入
            doAutoWired();
    
            //构造一个handlerMapping,将配置好的url和Method建立映射关系
            initHandlerMapping();
    
            System.out.println("MyMvc 初始化完成");
            //等待请求进入,请求处理
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doPost(req, resp);
        }
    
    }
    
    
    ## mymvc.properties
    scanPackage=com
    
    

    定义注解类

    @Documented
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAutowired {
        String value() default "";
    }
    
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyController {
        String value() default "";
    }
    
    @Documented
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRequestMapping {
        String value() default "";
    }
    
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyService {
        String value() default "";
    }
    
    

    加载配置文件

    private Properties properties = new Properties();
    private void doLoadConfig(String scanPackagePropertiesLocation) {
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(scanPackagePropertiesLocation);
            //将mymvc.properties加载成Properties
            try {
                properties.load(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
    

    包、注解扫描

      private List<String> classNames = new ArrayList<>();
        //scanPackage : com.demo
        private void doPackScan(String scanPackage) {
            //扫描定义的包,将带有特定注解的类的全限定名缓存起来
    
            //  获取classpath在磁盘中的位置
            //  D:/workspace/...(省略)/mymvc/target/classes/
            String classPath = Thread.currentThread().getContextClassLoader().
              getResource("").getPath();
    
            //获取包的路径
            // com/demo
            String packPath = scanPackage.replaceAll("\.", "/");
    
            //拼接获得需要扫描的包的真实路径
            // D:/workspace/...(省略)/mymvc/target/classes/com/demo
            String realPath = classPath + packPath;
            File folder = new File(realPath);
            //获取包下所有文件(夹)
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    //如果包中有目录,则递归
                    // 参数 : com.demo.controller
                    doPackScan(scanPackage + file.getName());
                }else if(file.getName().endsWith(".class")){
                    //找以class结尾的文件,获取它的全限定名称
                    // com.demo.controller.DemoController
                    String className = scanPackage + "." + file.getName().replaceAll(".class","");
                    //将全限定名缓存起来(list)
                    classNames.add(className);
                }
            }
        }
    
    

    创建IoC容器,实例化bean,维护依赖关系

        private Map<String,Object> beanMap = new HashMap<>();
    
        private void doInstance() {
            //从classNames中取全限定名,实例化,根据有无别名、接口等情况生产key
            try{
                for (String className : classNames) {
                    Class<?> aClass = Class.forName(className);
                    String beanName = "";
                    if (aClass.isAnnotationPresent(MyController.class)) {
                        //Controller接口不考虑别名,直接用类名首字母小写作为key
                        String simpleName = aClass.getSimpleName();//DemoController
                        beanName = lowerFirstCase(simpleName);//demoController
                        //实例化,存入IoC容器
                        beanMap.put(beanName,aClass.newInstance());
                    }else if (aClass.isAnnotationPresent(MyService.class)){
                        //如果有别名,beanName就是别名;没有别名就首字母小写作为key
                        String value = aClass.getAnnotation(MyService.class).value();
                        if (!"".equals(value.trim())) {
                            beanName = value.trim();
                            beanMap.put(beanName,aClass.newInstance());
                        }else {
                            String simpleName = aClass.getSimpleName();//DemoController
                            beanName = lowerFirstCase(simpleName);//demoController
                            //实例化,存入IoC容器
                            beanMap.put(beanName,aClass.newInstance());
                        }
    
                        //Service层有方法是通过接口注入的,因此需要维护接口名和实体的关系
                        Class<?>[] interfaces = aClass.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            beanMap.put(anInterface.getName(),aClass.newInstance());
                        }
                    }else{
                        continue;
                    }
    
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    

    完成依赖注入

    private void doAutoWired() {
            //遍历IoC容器,找到字段上有Autowired注解的字段,将属性赋值
            if (beanMap.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> stringObjectEntry : beanMap.entrySet()) {
                //维护的对象(DemoController)
                Object o = stringObjectEntry.getValue();
    
                //获取对象字段
                Field[] declaredFields = o.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
    
                    if (! declaredField.isAnnotationPresent(MyAutowired.class)) {
                        continue;
                    }
    
                    String beanName = declaredField.getDeclaredAnnotation(MyAutowired.class).value();
                    if (!"".equals(beanName.trim())){
                        //如果注解上没有指定beanname,那么取接口名作为beanName
                        beanName = declaredField.getType().getName();
                    }
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(o,beanMap.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
    

    初始化HandlerMapping

    //    Map<String, Method> urlMethodMap = new HashMap<>();
    
        List<Handler> handlers = new ArrayList<>();
    
        private void initHandlerMapping() {
            //读[controller]类/方法 上有无requestMapping注解,以获取uri
            if (beanMap.isEmpty())  return;
    
            for (Map.Entry<String, Object> stringObjectEntry : beanMap.entrySet()) {
                Class<?> aClass = stringObjectEntry.getValue().getClass();
                if (! aClass.isAnnotationPresent(MyController.class)) {
                    continue;
                }
    
                String baseUrl = "";
    
                if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                    baseUrl = aClass.getAnnotation(MyRequestMapping.class).value();
                }
    
                //遍历类中方法
                Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    if (! method.isAnnotationPresent(MyRequestMapping.class)) {
                        continue;
                    }
    
                    String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
    
                    //维护method和url的关系
    //                urlMethodMap.put(baseUrl + methodUrl,method);
                    Handler handler = new Handler(stringObjectEntry.getValue(),
                            method,
                            Pattern.compile(baseUrl+methodUrl));
    
                    //HttpServletRequest request, HttpServletResponse response, String name
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        //如果是HttpServletRequest,HttpServletResponse,存类名
                        if ((parameters[i].getType() == HttpServletRequest.class) ||
                            parameters[i].getType() == HttpServletResponse.class) {
    
                            handler.getArgsIndexMap().put(parameters[i].getType().getSimpleName(),
                                    i);
                        }else{
                            //将形参作为key
                            handler.getArgsIndexMap().put(parameters[i].getName(),i);
                        }
    
                        //存储handler (list)
                    }
                    handlers.add(handler);
                }
    
            }
    
    
        }
    
    ============================================================
    public class Handler {
        //method.invoke(obj,args) 的obj
        private Object controller;
    
        private Method method;
    
        private Pattern pattern;//uri
    
        private Map<String,Integer> argsIndexMap;
    
        public Handler(Object controller, Method method, Pattern pattern) {
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;
            this.argsIndexMap = new HashMap<>();
        }
    
        public Object getController() {
            return controller;
        }
    
        public void setController(Object controller) {
            this.controller = controller;
        }
    
        public Method getMethod() {
            return method;
        }
    
        public void setMethod(Method method) {
            this.method = method;
        }
    
        public Pattern getPattern() {
            return pattern;
        }
    
        public void setPattern(Pattern pattern) {
            this.pattern = pattern;
        }
    
        public Map<String, Integer> getArgsIndexMap() {
            return argsIndexMap;
        }
    
        public void setArgsIndexMap(Map<String, Integer> argsIndexMap) {
            this.argsIndexMap = argsIndexMap;
        }
    }
    
    
    

    处理请求

    @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    //        String requestURI = req.getRequestURI();
    //        //用map存储,缺少执行的类(controller,和参数)
    //        Method method = urlMethodMap.get(requestURI);
    //        method.invoke(obj,args);
            Handler handler = getHandler(req);
            if (handler == null) {
                resp.getWriter().write("404 not found");
                return;
            }
    
            //获取方法参数类型的数组
            Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
    
            //获取请求的参数列表
            Map<String,String[]> parameterMap = req.getParameterMap();
    
            //创建列表,作为method.invoke的参数;长度与参数列表相同
            Object[] args = new Object[parameterTypes.length];
    
            //遍历request中请求参数
            //key: 形参名称  value  参数值
            for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
                String value = StringUtils.join(stringEntry.getValue(),",");
                if (! handler.getArgsIndexMap().containsKey(stringEntry.getKey())) {
                    //判断handler中参数map是否有形参名
                    continue;
                }
    
                Integer paramIndex = handler.getArgsIndexMap().get(stringEntry.getKey());
                args[paramIndex] = value;
            }
    
            //将request,response 赋值
            args[handler.getArgsIndexMap().get(HttpServletRequest.class.getSimpleName())] = req;
            args[handler.getArgsIndexMap().get(HttpServletResponse.class.getSimpleName())] = resp;
    
    
            try {
                handler.getMethod().invoke(handler.getController(),args);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
    
        }
    
        private Handler getHandler(HttpServletRequest req) {
            String requestURI = req.getRequestURI();
            for (Handler handler : handlers) {
                Matcher matcher = handler.getPattern().matcher(requestURI);
                if (!matcher.matches()) {
                    continue;
                }
                return handler;
            }
    
            return null;
        }
    
    
    }
    
    

    流程中遇到的问题及解决

    method.invoke(obj,args);

    原先只存了url和method的对应关系,但是在处理请求时发现需要方法所在类及请求的参数,因此需要把这些关系全部处理起来。

    源码剖析

    SSM

    Mybatis整合Spring

    整合所需 Jar 分析

    Junit测试jar(4.12版本)
    Mybatis的jar(3.4.5)
    Spring相关jar(spring-context、spring-test、spring-jdbc、spring-tx、spring-aop、
    aspectjweaver)
    Mybatis/Spring整合包jar(mybatis-spring-xx.jar)
    Mysql数据库驱动jar
    Druid数据库连接池的jar

    流程

    数据库连接池以及事务管理都交给Spring容器来完成

    <!--包扫描-->
        <context:component-scan base-package="com"/>
        <!--    读取外部资源文件-->
        <context:property-placeholder location="classpath:jdbc.properties/"/>
    
        <!--    数据库连接池以及事务管理都交给Spring容器来完成-->
        <!-- 数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!--    事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <!--    事务管理注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    

    SqlSessionFactory对象应该放到Spring容器中作为单例对象管理

    <!--    SqlSessionFactory对象应该放到Spring容器中作为单例对象管理-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="typeAliasesPackage" value="com.pojo"/>
        </bean>
    
    

    Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象

    <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
        <!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--mapper接⼝包路径配置-->
            <property name="basePackage" value="com.mapper"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
    
    

    注意点:jar包版本依赖,mapper.xml与java编译之后在同个目录(起名用com/mapper,不能是com.mapper

    整合springmvc

    在已有spring+mybatis案例中添加springmvc

    springmvc.xml
    ===========================================================
        <!--包扫描-->
        <context:component-scan base-package="com.controller"/>
    
        <mvc:annotation-driven/>
    ===========================================================
    web.xml
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext*.xml</param-value>
      </context-param>
    <!--spring 启动-->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
    <!--  springmvc 启动-->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    

    service层和dao层是通过spring框架加载的,controller层是通过springmvc加载的,要在controller层注入service对象,因此需要配置监听器

    按层拆分xml

    dao

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
        <!--包扫描-->
        <context:component-scan base-package="com.mapper"/>
        <!--    读取外部资源文件-->
        <context:property-placeholder location="classpath:jdbc.properties/"/>
    
        <!--    数据库连接池以及事务管理都交给Spring容器来完成-->
        <!-- 数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
        <!--    SqlSessionFactory对象应该放到Spring容器中作为单例对象管理-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="typeAliasesPackage" value="com.pojo"/>
        </bean>
    
        <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
        <!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--mapper接⼝包路径配置-->
            <property name="basePackage" value="com.mapper"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
    </beans>
    
    

    service

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
        <!--包扫描-->
        <context:component-scan base-package="com.service"/>
        <!--    事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <!--    事务管理注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    </beans>
    
    

    Spring Data JPA

    Spring Data Jpa 是应⽤于Dao层的⼀个框架,简化数据库开发的,作⽤和Mybatis框架⼀样,但是在使
    ⽤⽅式和底层机制是有所不同的。最明显的⼀个特点,Spring Data Jpa 开发Dao的时候,很多场景我们
    连sql语句都不需要开发。由Spring出品。

    介绍

    Spring Data JPA 是 Spring 基于JPA 规范的基础上封装的⼀套 JPA 应⽤框架,可使开发者⽤极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常⽤功能!学习并使⽤Spring Data JPA 可以极⼤提⾼开发效率。

    使⽤了Spring Data JPA,我们Dao层中只需要写接⼝,不需要写实现类,就⾃动具有了增删改查、分⻚查询等⽅法。使⽤Spring Data JPA 很多场景下不需要我们⾃⼰写sql语句

    JPA规范,Hibernate的关系

    JPA 是⼀套规范,内部是由接⼝和抽象类组成的,Hiberanate 是⼀套成熟的 ORM 框架,⽽且Hiberanate 实现了 JPA 规范,所以可以称 Hiberanate 为 JPA 的⼀种实现⽅式,我们使⽤ JPA 的 API 编程,意味着站在更⾼的⻆度去看待问题(⾯向接⼝编程)。
    Spring Data JPA 是 Spring 提供的⼀套对 JPA 操作更加⾼级的封装,是在 JPA 规范下的专⻔⽤来进⾏数
    据持久化的解决⽅案。

    应用

    开发步骤

    构建⼯程

    创建⼯程导⼊坐标(Java框架于我们⽽⾔就是⼀堆jar)

    <dependencies>
            <!--单元测试jar-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <!--spring-data-jpa 需要引⼊的jar,start-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-jpa</artifactId>
                <version>2.1.8.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.el</groupId>
                <artifactId>javax.el-api</artifactId>
                <version>3.0.1-b04</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.web</groupId>
                <artifactId>javax.el</artifactId>
                <version>2.2.6</version>
            </dependency>
            <!--spring-data-jpa 需要引⼊的jar,end-->
    
            <!--spring 相关jar,start-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.13</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <!--spring对orm框架的⽀持包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <!--spring 相关jar,end-->
    
            <!--hibernate相关jar包,start-->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>5.4.0.Final</version>
            </dependency>
            <!--hibernate对jpa的实现jar-->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-entitymanager</artifactId>
                <version>5.4.0.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>5.4.0.Final</version>
            </dependency>
            <!--hibernate相关jar包,end-->
    
            <!--mysql 数据库驱动jar-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.18</version>
            </dependency>
            <!--druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.21</version>
            </dependency>
            <!--spring-test-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
        </dependencies>
    
    

    配置 Spring 的配置⽂件(配置指定框架执行的细节)

    <!--    引入外部资源文件-->
        <context:property-placeholder location="classpath*:jdbc.properties"/>
        <!--1、创建数据库连接池druid-->
        <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
        <!--2、配置⼀个JPA中⾮常重要的对象,entityManagerFactory
                entityManager类似于mybatis中的SqlSession
                entityManagerFactory类似于Mybatis中的SqlSessionFactory
        -->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <!-- 配置数据源 -->
            <property name="dataSource" ref="dataSource"/>
            <!-- 配置扫描的包路径,dao层扫pojo-->
            <property name="packagesToScan" value="com.pojo"/>
            <!--配置jpa实现类 hibernate        -->
            <property name="persistenceProvider">
                <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
            </property>
            <!--配置jpa方言  具体实现类        -->
            <property name="jpaDialect">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
            </property>
    
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <!--指定数据库类型         -->
                    <property name="database" value="MYSQL"/>
                    <!--是否显示数据库                   -->
                    <property name="showSql" value="true"/>
                    <!-- 指定数据库方言:不同的数据库语法是不一样的            -->
                    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                    <!--数据库表是否自动创建                -->
                    <property name="generateDdl" value="false"/>
                </bean>
            </property>
        </bean>
    
        <!--3、引⽤上⾯创建的entityManagerFactory
                <jpa:repositories> 配置jpa的dao层细节
                base-package:指定dao层接⼝所在包
        -->
        <jpa:repositories base-package="com.dao"
                          entity-manager-factory-ref="entityManagerFactory"
                          transaction-manager-ref="transactionManager"
        />
    
    
        <!--4、事务管理器配置  jpa规范:JpaTransactionManager -->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
    
        <!--5、声明式事务配置-->
            <!--
            <tx:annotation-driven/>
            -->
    
        <!--6、配置spring包扫描-->
        <context:component-scan base-package="com"/>
    
    

    编写实体类 Resume,使⽤ JPA 注解配置映射关系

    /**1.实体类和数据表映射关系
    */
    @Entity
    @Table(name = "t_user")
    public class User{
      
      //2.实体类属性和字段的映射关系
      //标识主键
      @Id
      //生成策略strategy
      //GenerationType.IDENTITY 依赖数据库中自增  Mysql
      //GenerationType.SEQUENCE 依靠序列来产生主键 Oracle
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name = "id")
      private Long id;
      @Column(name = "name")
      private String name;
      @Column(name = "address")
      private String address;
      @Column(name = "phone")
      private String phone;
      
      //setter/getter
    }
    
    

    编写⼀个符合 Spring Data JPA 的 Dao 层接⼝

    /**
     * JpaRepository<操作的实体类类型,主键类型>
     *       封装了CRUD操作
     *     JpaSpecificationExecutor<操作的实体类类型>
     *         封装了复杂查询(分页排序等)
     */
    
    public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
      	//jpql
        @Query("from User where name like '李%'")
        public List<Resume> findAllResumes();
    	//sql
        @Query(value = "select * from t_user where name like '李%'",nativeQuery = true)
        public List<Resume> findAllBySql();
    	//接口命名方式
        public List<Resume> findByAddressLikeOrPhone(String address,String phone);
    }
    
    
    

    操作 ResumeDao 接口对象完成 Dao 层开发

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration( locations = {"classpath:applicationContext.xml"})
    public class JpaTest {
    
        @Autowired
        UserDao userDao;
    
        @Test
        public void test1(){
            /*
            * select user0_.id as id1_0_, user0_.address as address2_0_, user0_.name as name3_0_, user0_.phone as phone4_0_
            * from tb_user user0_
            * */
            List<User> all = userDao.findAll();
            for (User user : all) {
                System.out.println(user);
            }
    
            System.out.println("=================");
            /**
             * select user0_.id as id1_0_0_, user0_.address as address2_0_0_, user0_.name as name3_0_0_, user0_.phone as phone4_0_0_
             * from tb_user user0_
             * where user0_.id=?
             */
            Optional<User> byId = userDao.findById(1L);
            User user = byId.get();
            System.out.println(user);
            System.out.println("===================");
    
            /**
             * select user0_.id as id1_0_, user0_.address as address2_0_, user0_.name as name3_0_, user0_.phone as phone4_0_
             * from tb_user user0_
             * where user0_.address=? and user0_.phone=? and user0_.id=1 and user0_.name=?
             */
            Optional<User> one = userDao.findOne(Example.of(user));
            User user1 = one.get();
            System.out.println(user1);
        }
    
        @Test
        public void test02(){
            /**
             * 有则改,无则增
             * Hibernate: select user0_.id as id1_0_0_, user0_.address as address2_0_0_, user0_.name as name3_0_0_, user0_.phone as phone4_0_0_ from tb_user user0_ where user0_.id=?
             * Hibernate: insert into tb_user (address, name, phone) values (?, ?, ?)
             *
             *Hibernate: select user0_.id as id1_0_0_, user0_.address as address2_0_0_, user0_.name as name3_0_0_, user0_.phone as phone4_0_0_ from tb_user user0_ where user0_.id=?
             * Hibernate: update tb_user set address=?, name=?, phone=? where id=?s
             */
            User user = new User();
            user.setId(5l);
            user.setAddress("shanghai");
            user.setPhone("12312312");
            user.setName("中国人");
            User user1 = userDao.save(user);
    
            System.out.println(user1);
        }
    
        @Test
        public void test03(){
            /**
             * Hibernate: select user0_.id as id1_0_0_, user0_.address as address2_0_0_, user0_.name as name3_0_0_, user0_.phone as phone4_0_0_ from tb_user user0_ where user0_.id=?
             * Hibernate: delete from tb_user where id=?
             */
            userDao.deleteById(5l);
        }
    
        @Test
        public void testJpql(){
            /**jpql
             * Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.name as name3_0_, user0_.phone as phone4_0_
             * from tb_user user0_
             * where user0_.name like '李%'
             */
            List<User> allUsers = userDao.findAllUsers();
            for (User allUser : allUsers) {
                System.out.println(allUser);
            }
    
            System.out.println("===============================");
            /**sql
             *
             * Hibernate: select * from tb_user where name like '李%'
             */
            List<User> allBySql = userDao.findAllBySql();
            for (User user : allBySql) {
                System.out.println(user);
            }
        }
    
        /**
         * 接口命名查询
         * 查询方法名以findBy开头,
         *      属性名首字母大写,
         *             查询方式(模糊查询,等价查询(默认));
         *
         * 多参数And/Or
         */
        @Test
        public void testName(){
            List<User> byAddressLikeOrPhone = userDao.findByAddressLikeOrPhone("上%", "153000000");
            for (User re : byAddressLikeOrPhone) {
                System.out.println(re);
            }
        }
    
        @Test
        public void testSpecification(){
            /**
             * Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.name as name3_0_, user0_.phone as phone4_0_
             * from tb_user user0_
             * where (user0_.address like ?) and user0_.name=?
             */
            Specification<User> userSpecification = new Specification<User>() {
                @Override
                //root 根属性(查询所需要的任何属性都可以从根对象中获取)
                //CriteriaQuery 自定义查询方式
                //CriteriaBuilder 查询构造器,封装了很多的查询条件
                public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    Path name = root.get("name");
                    Path address = root.get("address");
                    Predicate predicate1 = criteriaBuilder.like(address, "上%");
                    Predicate predicate2 = criteriaBuilder.equal(name, "李四");
    
                    Predicate predicate = criteriaBuilder.and(predicate1, predicate2);
    
                    return predicate;
                }
            };
    
            Optional<User> one = userDao.findOne(userSpecification);
            User user = one.get();
            System.out.println(user);
        }
       @Test
        public void sort(){
            Sort sort = new Sort(Sort.Direction.DESC,"id");
            List<Resume> resumes = resumeDao.findAll(sort);
            for (Resume resume : resumes) {
                System.out.println(resume);
            }
        }
    
        @Test
        public void page(){
    //        PageRequest of = PageRequest.of(0, 2);
    
    //        分页且排序
    //        整体先倒序,然后再分页
            PageRequest of = PageRequest.of(0, 2, new Sort(Sort.Direction.DESC, "id"));
            Page<Resume> all = resumeDao.findAll(of);
            for (Resume resume : all) {
                System.out.println(resume);
            }
        }
    }
    
    
    

    接口方法命名规则查询

    查询方法名以findBy开头,属性名首字母大写,查询方式(模糊查询,等价查询(默认));

    多参数And/Or

    例如 findByNameLikeAndAddress("李%","上海")

    动态查询

    把service拿到的条件封装成一个对象传递给dao层,这个对象是Specification

    interface Specification<T>
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
    //root 根属性(查询所需要的任何属性都可以从根对象中获取)
    //CriteriaQuery 自定义查询方式
    //CriteriaBuilder 查询构造器,封装了很多的查询条件
    
    
    /**动态条件封装
    	匿名内部类
    	
    	toPredicate: 动态组装查询条件
    		Root 获取需要查询的对象属性
    		CriteriaBuilder 构建查询条件,内部封装了很多查询条件(模糊查询、精准查询)
    */
    Specification<User> Specification = new Specification<User>() {
                @Override
                //root 根属性(查询所需要的任何属性都可以从根对象中获取)
                //CriteriaQuery 自定义查询方式
                //CriteriaBuilder 查询构造器,封装了很多的查询条件
                public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    Path name = root.get("name");
                    Path address = root.get("address");
                    Predicate predicate1 = criteriaBuilder.like(address, "上%");
                    Predicate predicate2 = criteriaBuilder.equal(name, "李四");
                    Predicate predicate = criteriaBuilder.and(predicate1, predicate2);
                    return predicate;
                }
            };
    
            Optional<User> one = userDao.findOne(Specification)
    
    

    分页,排序

    排序给Sort参数

    Sort(排序方式,哪个字段)
    
    

    分页给Pageable参数

    PageRequest.of(当前页数,每页大小[,排序])
    
    

    源码分析

  • 相关阅读:
    执行器模式设计和使用
    你知道怎么用Idea抽取方法、创建class吗?
    intellij构建多模块项目
    解决IDEA16闪退的问题
    TestNG参数化测试-数据提供程序 @DataProvider方式
    java第三节 面向对象(上)
    java第二节 基本数据类型
    memcache使用方法测试
    mysql Substr与char_length函数的应用
    《web与移动开发》征文活动
  • 原文地址:https://www.cnblogs.com/lxt97/p/13538367.html
Copyright © 2020-2023  润新知