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