1. 程式人生 > 實用技巧 >【學習】Spring MVC + Spring Data JPA

【學習】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(當前頁數,每頁大小[,排序])

原始碼分析