一、前言
在 Spring 框架和 Spring Boot 中,最常使用的 Web 技術就是 Spring MVC。
二、工程層次結構
在 cmd 中使用 tree /f 命令查看項目結構關系,如下:
├─spring-boot-hello │ │ │ ├─src │ │ ├─main │ │ │ ├─java │ │ │ │ └─com │ │ │ │ └─light │ │ │ │ └─springboot │ │ │ │ │ SpringbootApplication.java │ │ │ │ ├─conf │ │ │ │ ├─controller │ │ │ │ ├─dao │ │ │ │ │ └─impl │ │ │ │ ├─entity │ │ │ │ ├─service │ │ │ │ │ └─impl │ │ │ │ └─util │ │ │ │ SpringUtil.java │ │ │ │ │ │ │ └─resources │ │ │ │ application.properties │ │ │ │ │ │ │ ├─static │ │ │ │ ├─css │ │ │ │ ├─image │ │ │ │ └─js │ │ │ └─templates │ │ │ │ │ └─test │ │ ├─java │ │ └─resources
Java 包名結構一般為:
controller:通常包含項目中 MVC 的控制層,通常使用到 @Controller 注解;
service:通常包含業務層,impl 為其實現類,通常使用到 @Service 注解;
dao:通常包含持久層,impl 為其實現類,通常使用到 @Repository 注解;
entity:通常包含業務實體模型;
conf:通常包含一些配置類,通常使用到 @Configuration 和 @Bean 注解;
util:通常包含項目中的一些工具類。
Web 層結構一般為:
resource/templates:一般包含模板文件;
resource/static:一般包含模板文件使用到的靜態資源文件,如 js、css、圖片等,粉筆額存放到對應的目錄中。
三、模板引擎
Spring Boot支持的模板類型有:Thymeleaf、FreeMarker、Velocity、Groovy 和 Mustache 等,Spring Boot 建議使用這些模板引擎,避免使用 JSP,若一定要使用 JSP 將無法實現 Spring Boot 的多種特性。此處分別介紹 Thymeleaf 和 freemarker 的整合,Spring Boot 支持同時使用多種模板引擎,通常情況下,Spring Boot 會依次檢查每種模板引擎是否能處理指定的試圖名。
3.1 整合 Thymeleaf
在 pom 文件中添加對 Thymeleaf 模板的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
在 application.properties 中添加如下內容:
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
其中 spring.thymeleaf.cache 在開發中設置為 false,表示關閉緩存,當開發人員修改模板時,可以及時生效,看到效果,在正式環境中將其值設置為 true。可以只設置第一項,其他均為默認值。
編寫 ThymeleafController.java,如下:
package com.light.springboot.controller; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping(value="/thymeleafController") public class ThymeleafController { @RequestMapping(value="/hello1/{id}") public String order(@PathVariable(value = "id",required = false) String id, @RequestParam(value = "action",required = false) String action, Map<String,Object> map) { System.out.println("id:" + id); System.out.println("action:" + action); map.put("msg", "Thymeleaf"); return "hello1"; } }
在 resources/templates 目錄下創建名為 hello1.html 的文件,內容如下:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Spring Boot Thymeleaf</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h2 th:text="${msg}"></h2> </body> </html>
頁面展示的效果如下:
3.2 整合 freemarker
在 pom 文件中添加對 freemarker 模板的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
在 application.properties 中添加如下內容:
spring.freemarker.cache=false
spring.freemarker.allow-request-override=false spring.freemarker.check-template-location=true spring.freemarker.charset=UTF-8 spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=false spring.freemarker.expose-session-attributes=false spring.freemarker.expose-spring-macro-helpers=false spring.freemarker.prefix=classpath:/templates/ spring.freemarker.suffix=.ftl
同理,其中 spring.freemarker.cache 在開發中設置為 false,表示關閉緩存,當開發人員修改模板時,可以及時生效,看到效果,在正式環境中將其值設置為 true。可以只設置第一項,其他均為默認值。
編寫 FreemarkerController.java 如下:
package com.light.springboot.controller; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping(value="/freemarkerController") public class FreemarkerController { @RequestMapping(value="/hello2/{id}") public String order1(@PathVariable(value = "id",required = false) String id, @RequestParam(value = "action",required = false) String action, Map<String,Object> map) { map.put("msg", "Freemarker"); return "hello2"; } }
在 resources/templates 目錄下創建名為 hello2.ftl 的文件,ftl 是 freemarker 默認的模板文件后綴,內容如下:
<!DOCTYPE html> <html> <head> <title>Spring Boot Freemarker</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h2>${msg}</h2> </body> </html>
頁面展示的效果如下:
四、常用注解
以前使用 Spring 的時候,都是在 xml 中配置一個個的 Bean,自從出現了注解之后,注解取代傳統的 xml 配置,成為主流的配置方式:
4.1 常用聲明 Bean 的注解
Spring 提供了多個注解聲明 Bean 為 Spring 管理的 Bean,注解不同代表的含義不一樣,但是對於 Spring IOC 容器來說,都是 Sping 管理的 Bean。
@Controller:聲明此類是一個 MVC 類,通常與 @RequestMapping 一起使用;
@Service:聲明此類是一個業務處理類,通常與 @Transactional 一起配合使用;
@Repository:聲明此類是一個數據庫或者其他 NoSQL 訪問類;
@RestController:用於 REST 服務,相當於 @Controller 和 @ResponseBody 同時使用;
@Component:聲明此類是一個 Spring 管理的類,由於不屬於具體的某一個層面,所有通常用於無法用上述注解描述的 Spring 管理類;
@Configuration:聲明此類是一個配置類,也被稱為 Java Config,以前通常是在 XML 中配置,現在使用 Java 編寫配置,通常與注解 @Bean 配合使用;
@Bean:作用在方法上,聲明該方法執行的返回結果是一個 Spring 容器管理的 Bean,包括 @PostConstruct 注解和 @preDestroy 注解;
@Value:為了簡化讀取 properties 文件中的配置值,spring支持 @Value("${key}") 注解的方式來獲取,這種方式大大簡化了項目配置,提高業務中的靈活性@Value("${key}")。
Spring 有兩種方式來引用容器管理的 Bean,一種是根據名字 byName,另一種是根據類型 byType:
@Resource:該注解由 JavaEE 提供,默認按照名稱進行匹配,其中可以指定引用的 Bean 名稱。
@Autowired:該注解由 spring 提供,默認按照類型進行匹配,也可以配合 @Qualifier 屬性來按名稱匹配。
4.2 常用方法注解及參數
@RequestMapping:既可以作用在方法上,默認返回視圖的名稱,也可以作用在類上,按照 HTTP method 的不同,簡化后又可分為:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 和 @PatchMapping;
@ResponseBody:直接將返回的對象輸出到客戶端,如果是字符串,則直接返回;如果不是,Spring Boot 默認使用 Jackson 序列化成 JSON 字符串后輸出;
@PathVariable:可以將 URL 中的值映射到方法參數中;
@RequestParam:對應於 HTTP 請求的參數,自動轉化為參數對應的類型;
@RequestBody:Controller 方法帶有@RequestBody 注解的參數,意味着請求的 HTTP 消息體的內容是一個 JSON,需要轉化為注解指定的參數類型。Spring Boot 默認使用 Jackson 來處理反序列化工作;
@InitBinder:用在方法上,說明這個方法會注冊多個轉化器,用來個性化地將 HTTP 請求參數轉化成對應的 Java 對象,如轉化為日期類型、浮點類型和 JavaBean 等;
@ModelAttribute:通常作用在 Controller 的某個方法上,此方法會首先被調用,並將方法結果作為 Model 的屬性,然后再調用對應的 Controller 處理方法;
Model:Spring 中通用的 MVC 模型,也可以使用 Map 和 ModelMap 作為渲染視圖的模型;
ModelAndView:包含了模型和視圖路徑的對象,既可以通過方法聲明,也可以在方法中構造。
五、JavaBean 接收 HTTP 參數
Spring 支持按照前綴自動映射到不同的對象上。比如用戶提交了訂單信息,訂單信息包含了多個訂單明細。
package com.light.springboot.entity; import java.util.List; public class OrderPostForm { private String name; private Order order; private List<OrderDetail> details; // 省略 getter 和 setter 方法 }
同理創建 Order 模型類和 OrderDetail 模型類。
以 name 為屬性值的 HTTP 參數將直接映射到 OrderPostForm 類的 name 屬性上;
以 order 為前綴的 HTTP 參數將映射到 OrderPostForm 類的 order 屬性上;
以 details 屬性為前綴的 HTTP 參數將映射到 OrderPostForm 的 details 屬性上。
表單頁面設計如下:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Insert title here</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <form action="/thymeleafController/saveorder" method="post"> 名稱:<input name="name" /> <p></p> 訂單名稱:<input name="order.name" /> <p></p> 訂單明細1:<input name="details[0].name" /> <br /> 訂單明細2:<input name="details[1].name" /> <p></p> <input type="submit" value="Submit" /> </form> </body> </html>
則 Controller 的方法可以正常處理這個請求中的所有參數:
@RequestMapping(value="/saveorder") @ResponseBody public String saveOrder(OrderPostForm form) { System.out.println("form.name:" + form.getName()); System.out.println("form.order.name:" + form.getOrder().getName()); System.out.println("form.getDetails().size():" + form.getDetails().size()); System.out.println("form.details.name:" + form.getDetails().get(0).getName()); System.out.println("form.details.name:" + form.getDetails().get(1).getName()); return "success"; }
簡單描述上面的 Spring 將 HTTP 參數映射到 JavaBean 的規則:
name:OrderPostForm 類對象 form 的 name 屬性;
order.name:Order 類對象 order 的 name 屬性,由於 order 對象也是 OrderPostForm 的一個屬性,所以也可以說是 OrderPostForm 類對象 form 的 order 屬性的 name 屬性;
details[0].name/details[1].name:List<OrderDetail> 類對象 details 的 name 屬性,由於 details 對象也是 OrderPostForm 的一個屬性,所以也可以說是 OrderPostForm 類對象 form 的 details 屬性的 name 屬性,要求 details 是個數組或者 List(不能是 Set,因為 Set 為無序集合,不能用索引取值),details[0] 表示 details 屬性的第一個元素。
六、文件上傳和下載
通過 MultipartFile 來處理文件上傳,編寫用戶上傳文件的模型類 UserFile,用於封裝數據:
package com.light.springboot.entity; public class UserFile { private String name; private String path; public UserFile(String name, String path) { this.name = name; this.path = path; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } }
編寫文件上傳和下載的 Controller:
package com.light.springboot.controller; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.light.springboot.entity.UserFile; /** * 文件上傳和下載 * @author Administrator * */ @Controller @RequestMapping("/fileController") public class FileController { private String path = "G:\\"; /** * 進入到文件上傳頁面 * @return */ @GetMapping public String file() {//不配置路徑映射的話,表示此 fileController下的默認頁面 return "upload_file"; } /** * 單文件上傳 * @param file * @return * @throws Exception */ @PostMapping("/uploadFile") @ResponseBody public UserFile upload(MultipartFile file) throws Exception { if(!file.isEmpty()) {//判斷上傳內容為空,或者沒有上傳文件 System.out.println(file.getName());//表單參數name System.out.println(file.getOriginalFilename());//獲取上傳的文件名稱 System.out.println(file.getSize());//文件上傳的大小 byte[] fileByte = file.getBytes();//獲取上傳文件的內容,轉換為字節數組 System.out.println(fileByte.length);//文件上傳的字節數 File localFile = new File(path, file.getOriginalFilename()); // 保存上傳文件到目標文件系統 file.transferTo(localFile); return new UserFile(file.getName(), localFile.getAbsolutePath()); } return null; } /** * 文件下載 * @param name * @param request * @param response */ @GetMapping("/{name}") @ResponseBody public void download(@PathVariable(value = "name",required = false) String name, HttpServletRequest request, HttpServletResponse response) { InputStream inputStream = null; OutputStream outputStream = null; File file = null; try { file = new File(path, name + ".txt"); inputStream = new FileInputStream(file); outputStream = response.getOutputStream(); // 設置response的Header response.addHeader("Content-Disposition", "attachment;filename=" + name + ".txt"); response.addHeader("Content-Length", "" + file.length()); response.setContentType("application/x-download"); // 通過IOUtil對接輸入輸出流,實現文件下載 IOUtils.copy(inputStream, outputStream); } catch (Exception e) { e.printStackTrace(); } } }
設計的頁面上傳模板如下:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Insert title here</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>文件上傳</h1> <form method="post" action="/fileController/uploadFile" enctype="multipart/form-data"> 選擇一個文件: <input type="file" name="file" /> <br/><br/> <input type="submit" value="上傳" /> </form> </body> </html>
注意:頁面 input 中的 name 屬性 file 對應於 upload(MultipartFile file)
進入上傳頁面:
上傳結果頁面:
下載結果頁面:
如果同時上傳多個文件,則使用 MultipartFile 數組類來接收多個文件上傳:
/** * 多文件上傳 * @param files * @return * @throws Exception */ @PostMapping("/uploadFiles") @ResponseBody public UserFile uploadFiles(MultipartFile[] files) throws Exception { // 處理多文件上傳 return null; }
同理注意:頁面 input 中的 name 屬性 files 應該對應於 upload(MultipartFile[] files)
可以通過配置 application.properties 對 Spring Boot 上傳的文件進行限定,默認如下配置:
# MULTIPART (MultipartProperties) spring.http.multipart.enabled=false spring.http.multipart.file-size-threshold=0 spring.http.multipart.location= spring.http.multipart.max-file-size=1MB spring.http.multipart.max-request-size=10MB spring.http.multipart.resolve-lazily=false
參數 enable 默認為 true,即允許上傳附件,file-size-threshould 限定了當上傳的文件超過一定長度時,就先寫到臨時文件里。這有助於上傳文件不占用過多的內存,單位是 MB 或者 KB,默認是0,即不限定閾值。location 指的是臨時文件的存放目錄,如果不設定,則是 Web 服務器提供的一個臨時目錄。max-file-size 屬性指定了單個文件的最大長度,默認是 1MB,max-request-size 屬性說明單次 HTTP 請求上傳的最大長度,默認是 10MB。resolve-lazily 表示當文件和參數被訪問的時候再解析成文件。
七、驗證框架
Spring Boot 支持 JSR-303、Bean 驗證框架,默認實現用的是 Hibernate validator。JSR-303 是 Java 標准的驗證框架,已有的實現有 Hibernate validator。JSR-303 定義了一系列注解用來驗證 Bean 的屬性,常用的有如下幾種:
空檢查:
@Null:驗證對象是否為空;
@NotNull:驗證對象不為空;
@NotBlank:驗證字符串不為空或者不是空字符串,比如""和“ ”都會驗證失敗;
@NotEmpty:驗證對象不為 null,或者集合不為空。
長度檢查:
@Size(min=,max=):驗證對象長度,可支持字符串,集合;
@Length:字符串長度。
數值檢測:
@Min:驗證數字是否大於等於指定的值;
@Max:驗證數字是否大於等於指定的值;
@Digits:驗證數字是否符合指定格式,如@Digits(integer=9,fraction=2);
@Range:驗證數字是否在指定的范圍內,如@Range(min=1,max=1000);
其他:
@Email:驗證是否為郵件格式,為 null 則不做校驗;
@Pattern:驗證 String 對象是否符合正則表達式的規則。
在 Controller 中,只需要給方法參數加上 @Validated 即可觸發一次校驗。也可以通過自定義注解的方式來實現自定義校驗。
八、 Redirect 和 Forward
此處的 Redirect 和 Forward 的功能與傳統的 Java Web 中的功能一樣,只是表現形式不一樣。通常而言,Controller 都會返回一個視圖名稱,比如 ftl 結尾的視圖交給 Freemarker 模板引擎渲染,有些情況下,Controller 會返回客戶端一個 HTTP Redirect 重定向請求,希望客戶端按照指定地址重新發起一次請求,比如客戶登錄成功后,重定向到后台系統首頁。再比如客戶端通過 POST 請求提交了一個訂單,可以返回一個重定向請求到此訂單明細的請求地址。這樣做的好處是,如果用戶再次刷新頁面,則訪問的是訂單詳情地址,而不會再次提交訂單。
8.1 Redirect
@ResponseBody public String saveOrder(OrderPostForm form) { // save order return "redirect:/order/detail?orderId=1001"; }
也可以在 ModelAndView 中設置帶有 “redirect:” 前綴的 URI:
ModelAndView view = new ModelAndView("redirect:/order/detail?orderId=1001");
或者直接使用 RedirectView 類:
RedirectView view = new RedirectView("/order/detail?orderId=1001");
8.2 Forward
Spring MVC 也支持 forward 前綴,用來在 Controller 執行完畢后,再執行另外一個 Controller 的方法:
@RequestMapping("/bbs") public String index(){ // forward 到 module 方法 return "forward:/bbs/module/1-1" } @RequestMapping("/bbs/module/{type}-{page}") public ModelAndView module(){ // some code }
備注:
傳統 Java Web 中,
Redirect 的形式為:response.sendRedirect("資源的URL");//請求重定向到另外的資源。
Forward 的形式為:request.getRequestDispatcher("資源的URL").forward(request,response);//獲取請求轉發器對象,該轉發器的指向通過 getRequestDisPatcher() 的參數設置,然后調用 forward() 方法,轉發請求。
九、@Service 和 @Transactional
在 Spring Boot 中,Controller 調用業務邏輯處理交給了被 @Service 注解的類,這也是個普通的 JavaBean,Controller 中可以自動注入這個種 Bean,並調用其方法完成主要的業務邏輯,正如 Controller 注解經常和 @RequestMapping 搭配使用一樣,@Service 和 @Transactional 配合使用。
一般在開發中,會定義相關的業務接口,然后實現此接口,並在實現類中添加 @Service 來讓 Spring Boot 掃描到,同時搭配上 @Transactonal,Spring Boot 會對這樣的 Bean 進行事務增強。如下:
package com.light.springboot.service.impl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.light.springboot.service.DataService; @Service @Transactional public class DataServiceImpl implements DataService { @Override public void hello() { System.out.println("say Hello"); } }
@Transactional 可以作用在類上,這樣,所有的方法都會參與事務管理。也可以放到方法上。
事務對於系統的重要性不言而喻,針對事務,基本的概念和 ACID 四個特性在這里不做贅述,其他需要了解內容有:
9.1 事務主要分為兩種管理方式:
編程式事務:編程式事務指的是通過編碼方式實現事務,即類似於 JDBC 編程實現事務管理。管理使用 TransactionTemplate 或者直接使用底層的 PlatformTransactionManager,對於編程式事務管理,Spring推薦使用 TransactionTemplate。
聲明式事務:聲明式事務建立在 AOP 之上,其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於 @Transactional 注解的方式),便可以將事務規則應用到業務邏輯中。顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。
對比分析:聲明式事務管理使業務代碼,一個普通的 POJO 對象,只要加上注解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立為方法等。
Spring 事務的七種傳播屬性和四種隔離級別
9.2 事務傳播行為:所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量。
傳播行為 | 描述 |
TransactionDefinition.PROPAGATION_REQUIRED | 如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。該設置最常用。 |
TransactionDefinition.PROPAGATION_REQUIRES_NEW | 創建一個新的事務,如果當前存在事務,則把當前事務掛起 |
TransactionDefinition.PROPAGATION_SUPPORTS | 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事務方式運行,如果當前存在事務,則把當前事務掛起 |
TransactionDefinition.PROPAGATION_NEVER | 以非事務方式運行,如果當前存在事務,則拋出異常 |
TransactionDefinition.PROPAGATION_MANDATORY | 如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常 |
TransactionDefinition.PROPAGATION_NESTED | 如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED |
9.3 隔離級別:代表並發事務的隔離程度,也是通過 TransactionDefinition 接口定義的。
隔離級別 | 描述 |
TransactionDefinition.ISOLATION_DEFAULT | 這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED | 該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀和不可重復讀,因此很少使用該隔離級別 |
TransactionDefinition.ISOLATION_READ_COMMITTED | 該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值 |
TransactionDefinition.ISOLATION_REPEATABLE_READ | 該隔離級別表示一個事務在整個過程中可以多次重復執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止臟讀和不可重復讀。 |
TransactionDefinition.ISOLATION_SERIALIZABLE | 所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。 |
常用數據庫默認事務隔離級別:
MySQL:默認為 REPEATABLE_READ
SQLServer:默認為 READ_COMMITTED
Oracle:默認為 READ_COMMITTED
Xml 配置:
<tx:method name="get*" propagation="REQUIRED" isolation="DEFAULT" read-only="true" />
注解方式:
默認的事務傳播行為和隔離級別:
@Transactional(propagation=Propagation.REQUIRED)
大多數主流數據庫的默認事務等級:isolation=Isolation.READ_COMMITTED
@Transactional(isolation=Isolation.READ_COMMITTED)
十、Jackson 技術
在 MVC 框架中,Spring Boot 內置了Jackson 來完成 JSON de 序列化和反序列化,國內阿里巴巴提供的 Fastjson 也是個不錯的工具。總的來說,Jackson 功能非常強大,既能滿足簡單的序列化和反序列化操作,也能實現復雜的、個性化的序列化和反序列化操作。到目前為止,Jackson 的序列化和反序列化性能都非常優秀,已經是國內外大部分 JSON 相關編程的首選工具。
Jackson 是一個流行的高性能 JavaBean 到 JSON 的綁定工具,Jackson 使用 ObjectMapper 類將 POJO 對象序列化成 JSON 字符串,也能將 JSON 字符串反序列化成 POJO 對象。實際上,Jackson 支持三種層次的序列化和反序列化方式,最常用的一種是采用 DataBind 方式,將 POJO 序列化成 JSON,或者將 JSON 反序列化到 POJO,這是最直接和最簡單的一種方式,不過有時候需要借助 Jackson 的注解或者上述序列化實現類來個性化序列化和反序列化操作。
注意:JSON 規范要求 Key 是字符串,且用雙引號,盡管很多工具都可以用單引號甚至不用也能識別,但還是建議遵照 JSON 的規范。Jackson從2.0開始改用新的包名 fasterxml,1.x版本的包名是codehaus。除了包名不同,他們的 Maven artifact id 也不同。1.x 版本現在只提供bug-fix,而2.x版本還在不斷開發和發布中。如果是新項目,建議直接用 2x,即 fasterxml jackson。
10.1 ObjectMapper 類是 Jackson 庫的主要類,它能夠實現 Java 對象與 JSON 數據之間的序列化和反序列化
UserEntity 模型類:
package com.light.springboot.entity; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonView; //@JsonIgnoreProperties({"id","name"}) public class UserEntity { private Map<String, Object> map = new HashMap<String, Object>(); private Long id; private String name; private int age; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", locale = "zh" , timezone="GMT+8") private Date createDate; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @JsonAnySetter public void other(String property, Object value){ map.put(property, value); System.out.println(property + ":" + map.get(property)); } @JsonAnyGetter public Map<String, Object> getOtherProperties(){ return map; } @Override public String toString() { return "UserEntity [id=" + id + ", name=" + name + ", age=" + age + ", createDate=" + createDate + "]"; } }
JacksonUtil 工具類實現序列化和反序列化:
package com.light.springboot.util; import java.io.IOException; import java.util.Date; import java.util.List; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * Jackson從2.0開始改用新的包名fasterxml,1.x版本的包名是codehaus。除了包名不同,他們的Maven artifact id也不同。 * 1.x版本現在只提供bug-fix,而2.x版本還在不斷開發和發布中。如果是新項目,建議直接用2x,即fasterxml jackson * @author Administrator * jackson-core:核心包 * jackson-annotations:注解包 * jackson-databind:數據綁定包 */ public class JacksonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); /** * JSON字符串反序列化成Java對象 * @param json * @return */ public static UserEntity databind(String json){ UserEntity user = null; try { user = objectMapper.readValue(json, UserEntity.class); } catch (JsonParseException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return user; } /** * Java對象序列化成JSON字符串 * @param user * @return */ public static String custom(UserEntity user){ String json = null; try { json = objectMapper.writeValueAsString(user); } catch (JsonGenerationException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return json; } /** * Jackson提供了JavaType,用來指明集合類型和泛型 * @param collectonClass * @param elementClasses * @return */ public static JavaType getCollectionType(Class<?> collectonClass, Class<?>... elementClasses){ return objectMapper.getTypeFactory().constructParametricType(collectonClass, elementClasses); } /** * 集合的反序列化 * @param jsonArray * @return */ public static List<UserEntity> databinds(String jsonArray){ JavaType type = getCollectionType(List.class, UserEntity.class); List<UserEntity> list = null; try { list = objectMapper.readValue(jsonArray, type); } catch (JsonParseException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return list; } public static void main(String[] args) throws JsonProcessingException { //JSON字符串反序列化成Java對象 String json = "{\"name\":\"Rain\",\"id\":4405,\"age\":28,\"createDate\":\"2018-01-25 12:17:22\",\"phone\":\"13891701101\"}"; UserEntity user = databind(json); System.out.println(user.toString()); //Java對象序列化成JSON字符串 //UserEntity user = new UserEntity(); user.setId(10086L); user.setName("Bob"); user.setAge(18); user.setCreateDate(new Date()); System.out.println(custom(user)); //集合的反序列化和序列化 String jsonArray = "[{\"name\":\"Rain\",\"id\":4405,\"age\":28,\"createDate\":\"2018-01-25 12:17:22\",\"phone\":\"13891701102\"}," + "{\"name\":\"Jim\",\"id\":4406,\"age\":26,\"createDate\":\"2018-01-25 12:17:22\",\"phone\":\"13891701103\"}]"; List<UserEntity> userList = databinds(jsonArray); for (UserEntity userEntity : userList) { System.out.println(userEntity.toString()); } //集合的序列化 System.out.println(objectMapper.writeValueAsString(userList)); } }
10.2 Jackson 常用注解
Jackson 包含了很多注解,用來個性化序列化和反序列化操作,主要包含如下注解。
@JsonProperty,作用在屬性上,用來為 JSON Key 指定一個別名;
@JsonIgnore,作用在屬性上,用來忽略此屬性;
@JsonIgnoreProperties,忽略一組屬性,作用在類上,比如 @JsonIgnorePropertiess({"id","phone"});
@JsonAnySetter,標記在某個方法上,此方法接收 Key、Value,用於 Jackson 在反序列化過程中,未找到的對應屬性都調用此方法。通常這個方法用一個 Map 來實現;
@JsonAnyGetter,此注解標注在一個返回 Map 的方法上,Jackson 會取出 Map中的每一個值進行序列化;
@JsonFormat,用於日期的格式化,如:
@JsonFormat(pattern="yyyy-MM-dd HH-mm-ss") private Date createDate;
@JsonNaming,用於指定一個命名策略,作用於類或者屬性上,類似 @JsonProperty,但是會自動命名。Jackson 自帶了多種命名策略,你也可以實現自己的命名策略,比如輸入的 Key 由 Java 命名方式轉換為下划線命名方式:userName 轉換為 user-name;
@JsonSerialize,指定一個實現類來自定義序列化。類必須實現 JsonSerializer 接口;
@JsonDeserialize,用戶自定義反序列化,同 @JsonSerialize,類需要實現 JsonDeserialize 接口;
@JsonView,作用在類或者屬性上,用來定義一個序列化組。Spring MVC 的 Controller 方法可以使用同樣的 @JsonView 來序列化屬於這一組的配置。