【学习】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(当前页数,每页大小[,排序])

源码分析

原文地址:https://www.cnblogs.com/lxt97/p/13538367.html