SSM框架筆記

配置

Project結構

SpringMVC啓用

Spring MVC配置

配置具體解釋
* spring_mvc_圖解javascript

spring_mvc_圖解


  • ### Spring3.1新特性

1、Spring2.5以前。咱們都是經過實現Controller接口或其實現來定義咱們的處理器類。html

2、Spring2.5引入註解式處理器支持,經過@Controller 和 @RequestMapping註解定義咱們的處理器類。並且提供了一組強大的註解:
需要經過處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter來開啓支持@Controller 和 @RequestMapping註解的處理器。前端

@Controller:用於標識是處理器類。

@RequestMapping:請求處處理器功能方法的映射規則。

@RequestParam:請求參數處處理器功能處理方法的方法參數上的綁定;

@ModelAttribute:請求參數到命令對象的綁定;

@SessionAttributes:用於聲明session級別存儲的屬性。放置在處理器類上,一般列出模型屬性(如@ModelAttribute)相應的名稱,則這些屬性會透明的保存到session中;

@InitBinder:本身定義數據綁定註冊支持。用於將請求參數轉換到命令對象屬性的相應類型。

3、Spring3.0引入RESTful架構風格支持(經過@PathVariable註解和一些其它特性支持),且又引入了不少其它的註解支持:java

@CookieValue:cookie數據處處理器功能處理方法的方法參數上的綁定;

@RequestHeader:請求頭(header)數據處處理器功能處理方法的方法參數上的綁定;

@RequestBody:請求的body體的綁定(經過HttpMessageConverter進行類型轉換);

@ResponseBody:處理器功能處理方法的返回值做爲響應體(經過HttpMessageConverter進行類型轉換)。

@ResponseStatus:定義處理器功能處理方法/異常處理器返回的狀態碼和緣由;

@ExceptionHandler:註解式聲明異常處理器;

@PathVariable:請求URI中的模板變量部分處處理器功能處理方法的方法參數上的綁定,從而支持RESTful架構風格的URI;

4、還有比方:mysql

JSR-303驗證框架的無縫支持(經過@Valid註解定義驗證元數據);git

使用Spring 3開始的ConversionService進行類型轉換(PropertyEditor依舊有效),支持使用@NumberFormat 和 @DateTimeFormat來進行數字和日期的格式化;github

HttpMessageConverter(Http輸入/輸出轉換器,比方JSON、XML等的數據輸出轉換器);web

ContentNegotiatingViewResolver,內容協商視圖解析器,它仍是視圖解析器,僅僅是它支持依據請求信息將同一模型數據以不一樣的視圖方式展現(如json、xml、html等)。RESTful架構風格中很是重要的概念(同一資源,多種表現形式)。ajax

Spring 3 引入 一個 mvc XML的命名空間用於支持mvc配置,包含如:redis

<mvc:annotation-driven>:

本身主動註冊基於註解風格的處理器需要的DefaultAnnotationHandlerMapping、AnnotationMethodHandlerAdapter

支持Spring3的ConversionService本身主動註冊

支持JSR-303驗證框架的本身主動探測並註冊(僅僅需把JSR-303實現放置到classpath)

本身主動註冊相應的HttpMessageConverter(用於支持@RequestBody 和 @ResponseBody)(如XML輸入輸出轉換器(僅僅需將JAXP實現放置到classpath)、JSON輸入輸出轉換器(僅僅需將Jackson實現放置到classpath))等。

<mvc:interceptors>:註冊本身定義的處理器攔截器;

<mvc:view-controller>:和ParameterizableViewController類似,收到相應請求後直接選擇相應的視圖。

<mvc:resources>:邏輯靜態資源路徑到物理靜態資源路徑的支持;

<mvc:default-servlet-handler>:當在web.xml 中DispatcherServlet使用<url-pattern>/</url-pattern> 映射時,能映射靜態資源(當Spring Web MVC框架沒有處理請求相應的控制器時(如一些靜態資源),轉交給默認的Servlet來響應靜態文件。不然報404找不到資源錯誤,)。

……等等。

5、Spring3.1新特性:

對Servlet 3.0的全面支持。

@EnableWebMvc:用於在基於Java類定義Bean配置中開啓MVC支持。和XML中的<mvc:annotation-driven>功能同樣;

新的@Contoller和@RequestMapping註解支持類:處理器映射RequestMappingHandlerMapping 和 處理器適配器RequestMappingHandlerAdapter組合來取代Spring2.5開始的處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter,提供不少其它的擴展點,它們之間的差異咱們在處理器映射一章介紹。

新的@ExceptionHandler 註解支持類:ExceptionHandlerExceptionResolver來取代Spring3.0的AnnotationMethodHandlerExceptionResolver,在異常處理器一章咱們再具體解說它們的差異。

@RequestMapping的」consumes」 和 「produces」 條件支持:用於支持@RequestBody 和 @ResponseBody,

1 consumes指定請求的內容是什麼類型的內容,即本處理方法消費什麼類型的數據。如consumes=」application/json」表示JSON類型的內容,Spring會依據相應的HttpMessageConverter進行請求內容區數據到@RequestBody註解的命令對象的轉換。

2 produces指定生產什麼類型的內容。如produces=」application/json」表示JSON類型的內容。Spring的依據相應的HttpMessageConverter進行請求內容區數據到@RequestBody註解的命令對象的轉換,Spring會依據相應的HttpMessageConverter進行模型數據(返回值)到JSON響應內容的轉換

3 以上內容。本章第×××節詳述。

URI模板變量加強:URI模板變量可以直接綁定到@ModelAttribute指定的命令對象、@PathVariable方法參數在視圖渲染以前被合併到模型數據中(除JSON序列化、XML混搭場景下)。

@Validated:JSR-303的javax.validation.Valid一種變體(非JSR-303規範定義的,而是Spring本身定義的),用於提供對Spring的驗證器(org.springframework.validation.Validator)支持,需要Hibernate Validator 4.2及更高版本號支持。

@RequestPart:提供對「multipart/form-data」請求的全面支持,支持Servlet 3.0文件上傳(javax.servlet.http.Part)、支持內容的HttpMessageConverter(即依據請求頭的Content-Type,來推斷內容區數據是什麼類型。如JSON、XML。能本身主動轉換爲命令對象),比@RequestParam更強大(僅僅能對請求參數數據綁定。key-alue格式),而@RequestPart支持如JSON、XML內容區數據的綁定;詳見本章的第×××節;

Flash 屬性 和 RedirectAttribute:經過FlashMap存儲一個請求的輸出。當進入還有一個請求時做爲該請求的輸入。典型場景如重定向(POST-REDIRECT-GET模式。一、POST時將下一次需要的數據放在FlashMap;二、重定向;三、經過GET訪問重定向的地址。此時FlashMap會把1放到FlashMap的數據取出放到請求中。並從FlashMap中刪除;從而支持在兩次請求之間保存數據並防止了反覆表單提交)。

Spring Web MVC提供FlashMapManager用於管理FlashMap,默認使用SessionFlashMapManager。即數據默認存儲在session中。

Spring本身主動掃描

<context:component-scan base-package="com.dai.base" />
base-package爲要本身主動掃描的包名,包下的類使用註解來聲明bean。
@Controller控制器Bean
@Service業務層Bean
@Repository相應數據訪問層Bean
@Component所有受Spring 管理組件的通用形式,不推薦使用。

本身主動掃描的控件默認是單例模式。

可以在註解中指定bean名。如@Service(「auth」)。


當不指定bean名時。將依照最小非大寫命名法指定bean名,如AuthService將被命名爲authService。

getBean的方法

使用Spring的IoC後,再也不需要new對象,而是應該使用getBean的方式獲取對象,使用getBean就首先要獲取上下文,在調用其getBean方法。用法:

ApplicationContext ctx = 
new ClassPathXmlApplicationContext("spring-mybatis.xml");
Object obj = (Object)ctx.getBean("object");

固然,這種效率低並且easy出現BUG,正確的姿式應該是建立一個工具類SpringContextsUtil ,經過實現Spring中的ApplicationContextAware接口,在applicationContext.xml中注入bean後Spring會本身主動調用setApplicationContext方法。

此時咱們就可以獲取到Spring context。

public class SpringContextsUtil implements ApplicationContextAware{

private static ApplicationContext applicationContext;    //Spring應用上下文環境 
  /** * 實現ApplicationContextAware接口的回調方法,設置上下文環境 * @param applicationContext * @throws BeansException */
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    SpringContextsUtil.applicationContext = applicationContext;
  }

  /** * @return ApplicationContext */
  public static ApplicationContext getApplicationContext() {
    return applicationContext;
  }

  /** * 獲取對象 * @param name * @return Object 一個以所給名字註冊的bean的實例 * @throws BeansException */
  public static Object getBean(String name) throws BeansException {
    return applicationContext.getBean(name);
  }

  /** * 獲取類型爲requiredType的對象 * 假設bean不能被類型轉換,相應的異常將會被拋出(BeanNotOfRequiredTypeException) * @param name bean註冊名 * @param requiredType 返回對象類型 * @return Object 返回requiredType類型對象 * @throws BeansException */
  public static Object getBean(String name, Class requiredType) throws BeansException {
    return applicationContext.getBean(name, requiredType);
  }

  /** * 假設BeanFactory包含一個與所給名稱匹配的bean定義,則返回true * @param name * @return boolean */
  public static boolean containsBean(String name) {
    return applicationContext.containsBean(name);
  }

  /** * 推斷以給定名字註冊的bean定義是一個singleton仍是一個prototype。 * 假設與給定名字相應的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException) * @param name * @return boolean * @throws NoSuchBeanDefinitionException */
  public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.isSingleton(name);
  }

  /** * @param name * @return Class 註冊對象的類型 * @throws NoSuchBeanDefinitionException */
  public static Class getType(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.getType(name);
  }

  /** * 假設給定的bean名字在bean定義中有別名,則返回這些別名 * @param name * @return * @throws NoSuchBeanDefinitionException */
  public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
    return applicationContext.getAliases(name);
  }
}

此時,假設要使用該類載入springXML的配置,需要在該XML中加入下面這句:
<bean id="springContextsUtil" class="com.dai.pojo.SpringContextUtil" lazy-init="false"></bean>
固然,注意將本身的springXML文件載入到webXML的contextConfigLocation屬性中:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mybatis.xml,
classpath:spring-service.xml</param-value>
</context-param>

SpringMVC與Struts2的差異

一、Struts2是類級別的攔截。 一個類相應一個request上下文,SpringMVC是方法級別的攔截。一個方法相應一個request上下文。而方法同一時候又跟一個url相應,因此說從架構自己上SpringMVC就easy實現restful url,而struts2的架構實現起來要費勁,因爲Struts2中Action的一個方法可以相應一個url,而其類屬性卻被所有方法共享,這也就沒法用註解或其它方式標識其所屬方法了。

二、由上邊緣由。SpringMVC的方法之間基本上獨立的。獨享request response數據,請求數據經過參數獲取,處理結果經過ModelMap交回給框架。方法之間不共享變量,而Struts2搞的就比較亂,儘管方法之間也是獨立的,但其所有Action變量是共享的。這不會影響程序運行,卻給咱們編碼 讀程序時帶來麻煩,每次來了請求就建立一個Action,一個Action對象相應一個request上下文。

三、因爲Struts2需要針對每個request進行封裝,把request。session等servlet生命週期的變量封裝成一個一個Map,供給每個Action使用。並保證線程安全,因此在原則上。是比較耗費內存的,而SpringMVC的controller則是非線程安全的。

四、 攔截器實現機制上。Struts2有以本身的interceptor機制,SpringMVC用的是獨立的AOP方式,這樣致使Struts2的配置文件量仍是比SpringMVC大。

五、SpringMVC的入口是servlet。而Struts2是filter(這裏要指出,filter和servlet是不一樣的。曾經以爲filter是servlet的一種特殊)。這就致使了兩者的機制不一樣,這裏就牽涉到servlet和filter的差異了。

六、SpringMVC集成了Ajax,使用很是方便。僅僅需一個註解@ResponseBody就可以實現。而後直接返回響應文本就能夠,而Struts2攔截器集成了Ajax,在Action中處理時通常必須安裝插件或者本身寫代碼集成進去,使用起來也相對不方便。

七、SpringMVC驗證支持JSR303,處理起來相對更加靈活方便,而Struts2驗證比較繁瑣。感受太煩亂。

八、Spring MVC和Spring是無縫的。從這個項目的管理和安全上也比Struts2高(固然Struts2也可以經過不一樣的文件夾結構和相關配置作到SpringMVC同樣的效果,但是需要xml配置的地方很多)。

九、 設計思想上。Struts2更加符合OOP的編程思想, SpringMVC就比較慎重。在servlet上擴展。

十、SpringMVC開發效率和性能高於Struts2。
十一、SpringMVC可以以爲已經100%零配置。

Log4j

log4j可以經過使用log4j.properties文件來打印日誌以及輸出常見的調試信息。如輸出sql語句:

log4j.rootLogger=DEBUG, Console  

    #Console 
    log4j.appender.Console=org.apache.log4j.ConsoleAppender  
    log4j.appender.Console.layout=org.apache.log4j.PatternLayout  
    log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n  

    log4j.logger.java.sql.ResultSet=INFO  
    log4j.logger.org.apache=INFO  
    log4j.logger.java.sql.Connection=DEBUG  
    log4j.logger.java.sql.Statement=DEBUG  
    log4j.logger.java.sql.PreparedStatement=DEBUG

攔截器與過濾器

過濾器(Filter)與攔截器(Interceptor)是SpringMVC常用的兩種開發模型,運行順序一般是先運行filter,僅僅有限制性過濾器以後才幹夠進入容器運行攔截。


* 過濾器
實現的方式有下面幾類:
(1) 直接實現Filter。這一類過濾器僅僅有CompositeFilter。
(2) 繼承抽象類GenericFilterBean。該類實現了javax.servlet.Filter,這一類的過濾器僅僅有一個,即DelegatingFilterProxy;
(3) 繼承抽象類OncePerRequestFilter,該類爲GenericFilterBean的直接子類。這一類過濾器包含CharacterEncodingFilter、HiddenHttpMethodFilter、HttpPutFormContentFilter、RequestContextFilter和ShallowEtagHeaderFilter;
(4) 繼承抽象類AbstractRequestLoggingFilter,該類爲OncePerRequestFilter的直接子類。這一類過濾器包含CommonsRequestLoggingFilter、Log4jNestedDiagnosticContextFilter和ServletContextRequestLoggingFilter。
\
過濾器放在web資源以前,可以在請求抵達它所應用的web資源(可以是一個Servlet、一個Jsp頁面,甚至是一個HTML頁面)以前截獲進入的請求,並且在它返回到客戶以前截獲輸出請求。

Filter:用來攔截請求。處於client與被請求資源之間,目的是重用代碼。Filter鏈,在web.xml中哪一個先配置,哪一個就先調用。在filter中也可以配置一些初始化參數。


Java中的Filter 並不是一個標準的Servlet 。它不能處理用戶請求,也不能對client生成響應。 主要用於對HttpServletRequest 進行預處理,也可以對HttpServletResponse 進行後處理,是個典型的處理鏈。
Filter 有例如如下幾個用處 :
1)在HttpServletRequest 到達Servlet 以前,攔截客戶的HttpServletRequest 。


2)依據需要檢查HttpServletRequest ,也可以改動HttpServletRequest 頭和數據。
3)在HttpServletResponse 到達client以前,攔截HttpServletResponse 。
4)依據需要檢查HttpServletResponse ,可以改動HttpServletResponse 頭和數據。

首先定義過濾器:

public class LoginFilter extends OncePerRequestFilter{
    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        // 只是濾的uri
        String[] notFilter = new String[] { "login" };

        // 請求的uri
        String uri = request.getRequestURI();

        // 是否過濾
        boolean doFilter = true;
        for (String s : notFilter) {
            if (uri.indexOf(s) != -1) {
                // 假設uri中包含只是濾的uri,則不進行過濾
                doFilter = false;
                break;
            }
        }

        if (doFilter) {
            // 運行過濾
            // 從session中獲取登陸者實體
            Object obj = request.getSession().getAttribute("user");
            if (null == obj) {
                response.sendRedirect("login");
            } else {
                // 假設session中存在登陸者實體,則繼續
                filterChain.doFilter(request, response);
            }
        } else {
            // 假設不運行過濾。則繼續
            filterChain.doFilter(request, response);
        }
    }

}

而後在web.xml中聲明過濾器:
<!-- login filter -->
<filter>
<filter-name>sessionFilter</filter-name>
<filter-class>com.dai.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionFilter<filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
但是注意,因爲過濾器匹配的是所有url,故相應在Spring-MVC中聲明的靜態資源將會出現獲取不到的狀況。這種狀況推薦使用攔截器。

  • 攔截器:
    攔截器

攔截器和過濾器的差異:
  ①攔截器是基於java的反射機制的。而過濾器是基於函數回調。
  ②攔截器不依賴與servlet容器。過濾器依賴與servlet容器。
  ③攔截器僅僅能對action請求起做用,而過濾器則可以對差點兒所有的請求起做用。


  ④攔截器可以訪問action上下文、值棧裏的對象。而過濾器不能訪問。
  ⑤在action的生命週期中。攔截器可以屢次被調用。而過濾器僅僅能在容器初始化時被調用一次。


  ⑥攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,這點很是重要。在攔截器裏注入一個service,可以調用業務邏輯。

文件Upload

上傳方式

SpringMVC後臺獲取上傳文件有兩種方式:

1.經過MultipartFile filename的方式獲取,filename與前端input域的name屬性相同:

public void Upload(@RequestParam(value = " filename", required = false)
            MultipartFile file)

2.經過HttpServletRequest獲取:

public void Upload(HttpServletRequest request,     
            HttpServletResponse response){
MultipartHttpServletRequest multipartRequest = 
        (MultipartHttpServletRequest) request;
MultipartFile file = multipartRequest.getFile(" file ");
}

獲取上傳本地路徑

使用request.getSession().getServletContext().getRealPath("/")就能夠獲取webroot根文件夾的物理路徑。

AJAX處理

SpringMVC中使用ajax技術可以方便地像後臺POST數據,後臺的處理代碼例如如下:

@RequestMapping("/getPerson")
    public void getPerson(String args,PrintWriter pw){
    //args爲前端post過來的參數
        pw.write(data);
        //data爲返回的數據
    }

注意:Firefox中默認的狀況下把datatype用html來解析了。因此使用ajax或者post得到的返回data會報錯object XMLDocument,僅僅要設置一下datatype就可輕鬆攻克了。

  • 響應中文前端亂碼

有時候服務端響應了中文,而ajax返回前端時變成了亂碼。解決問題可以設置response例如如下:

response.setContentType("text/xml;charset=utf-8"); 
response.setHeader("Cache-Control", "no-cache"); 
PrintWriter pw=response.getWriter();

而後用pw輸出返回值到ajax。

  • ajax超時timeout

ajax的timeout僅對網絡請求耗時生效。也就是說,當服務端接受到ajax請求後,自身的sleep或者別的耗時操做將與timeout無關。

Mybatis使用

使用Mybatis逆向生成器生成model,DAO,mapping。

使用註解爲mapper聲明新方法,如:

@Select("select * from user where email=#{email}") User selectByEmail(String email);

Mybatis鏈接池

在處理併發時,常常會出現這種問題:Data source rejected establishment of connection, message from server: 「Too many connections」 。數據庫鏈接實在太多了,固然,你可以改動mysql的最大鏈接數max_connections=3600,默認狀況下使用select VARIABLE_VALUE from information_schema.GLOBAL_VARIABLES where VARIABLE_NAME=’MAX_CONNECTIONS’;可以查看鏈接數應該是151。固然很是easy爆掉。
但是,其實在併發時咱們並不需要這麼多鏈接,過多的鏈接對於服務器也是一種沒必要要的負擔。因爲建立一個Connection的時間甚至超過了運行一句sql的時間。代價過於沉重。這種狀況下咱們就應該考慮使用鏈接池來處理數據庫鏈接,如DBCP,C3P0,BONECP,druid等。固然,mybatis本身也有實現鏈接池。在spring中配置Mybatis例如如下:
mybatis配置
上面destroy-method=」close」的做用是當數據庫鏈接不使用的時候,就把該鏈接又一次放到數據池中,方便下次使用調用。
固然,建議仍是使用第三方鏈接池。如阿里的druid來管理鏈接池。還有自帶的監控界面可以使用。


鏈接池注意下面幾個配置:

defaultAutoCommit:設置從數據源中返回的鏈接是否採用本身主動提交機制,默認值爲 true;
defaultReadOnly:設置數據源是否僅能運行僅僅讀操做, 默認值爲 false;
maxActive:最大鏈接數據庫鏈接數,設置爲0時,表示沒有限制;
maxIdle:最大等待鏈接中的數量,設置爲0時。表示沒有限制;
maxWait:最大等待秒數。單位爲毫秒, 超過期間會報出錯誤信息;
validationQuery:用於驗證鏈接是否成功的查詢SQL語句。SQL語句必須至少要返回一行數據, 如你可以簡單地設置爲:「select count(*) from user」。
removeAbandoned:是否自我中斷。默認是 false ;
removeAbandonedTimeout:幾秒後數據鏈接會本身主動斷開,在removeAbandoned爲true,提供該值。
logAbandoned:是否記錄中斷事件, 默以爲 false。

  • (注意) 有時mybatis會爲每個新獲取的mapper新建一條鏈接,因此壓力測試會爆掉。但是同一個mapper下運行多條sql並不會另外新建鏈接。
    便是說:每次用ApplicationContext ctx=new ClassPathXmlApplicationContext(「application.xml」);都會新建一條鏈接。
    正確的姿式是在controller中ApplicationContext ctx=new ClassPathXmlApplicationContext(「application.xml」);而不是每個方法都new一次。這樣就能正確啓用鏈接池。

Druid鏈接池

Druid是一個阿里開發的JDBC組件。用來替換DBCP和C3P0。

Druid提供了一個高效、功能強大、可擴展性好的數據庫鏈接池。

它可以監控數據庫訪問性能,Druid內置提供了一個功能強大的StatFilter插件,可以具體統計SQL的運行性能,這對於線上分析數據庫訪問性能有幫助。

官方文檔

緩存機制

mybatis提供了一級緩存和二級緩存支持,當Hibernate依據ID訪問數據對象的時候。首先從Session一級緩存中查;查不到。假設配置了二級緩存,那麼從二級緩存中查;假設都查不到,再查詢數據庫。把結果依照ID放入到緩存搜索。刪除、更新、添加數據的時候,同一時候更新緩存。

MyBatis 的緩存採用了delegate機制 及 裝飾器模式設計,當put、get、remove時,當中會通過多層 delegate cache 處理,其Cache類別有:BaseCache(基礎緩存)、EvictionCache(排除算法緩存) 、DecoratorCache(裝飾器緩存):
(1)BaseCache:爲緩存數據終於存儲的處理類。默以爲 PerpetualCache,基於Map存儲;可本身定義存儲處理。如基於EhCache、Memcached等;
(2)EvictionCache:當緩存數量達到必定大小後。將經過算法對緩存數據進行清除。默認採用 Lru 算法(LruCache),提供有 fifo 算法(FifoCache)等;
(3)DecoratorCache:緩存put/get處理先後的裝飾器。如使用 LoggingCache 輸出緩存命中日誌信息、使用 SerializedCache 對 Cache的數據 put或get 進行序列化及反序列化處理、當設置flushInterval(默認1/h)後,則使用 ScheduledCache 對緩存數據進行定時刷新等。

通常緩存框架的數據結構基本上都是 Key-Value 方式存儲,MyBatis 對於其 Key 的生成採取規則爲:[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。

對於併發 Read/Write 時緩存數據的同步問題,MyBatis 默認基於 JDK/concurrent中的ReadWriteLock,使用ReentrantReadWriteLock 的實現,從而經過 Lock 機制防止在併發 Write Cache 過程當中線程安全問題。

運行過程: 運行過程

一級緩存

一級緩存基於 PerpetualCache 的 HashMap 本地緩存,其存儲做用域爲 Session。當 Session flush 或 close 以後,該Session中的所有 Cache 就將清空。一級緩存是默認開啓的。便是基於同一個sqlsession 的查詢語句。在同一個sqlsession下作兩次同樣的查詢並不會運行兩句相同的sql。

二級緩存

二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,不一樣在於其存儲做用域爲 Mapper(Namespace),並且可本身定義存儲源。如 Ehcache、Hazelcast、Redis等。
相同二級緩存也是默認打開,全局開關默認是true。假設它配成false,其他各個Mapper XML文件配成支持cache也沒用。


<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
各個Mapper XML文件。默認是不採用cache。在配置文件加一行就可以支持cache:
<cache /> //type屬性可以本身定義cache來源
注意,此時相應的Model應該實現Serializable接口。若要不序列化,則需要這樣聲明:
<cache readOnly="true"></cache>
常用標籤有:
eviction=」FIFO」 <!--回收策略爲先進先出-->
flushInterval=」60000」 <!--本身主動刷新時間60s-->
size=」512」 <!--最多緩存512個引用對象-->
此時再運行以前運行過的sql會獲得
Cache Hit Ratio [com.dai.DAO.UserMapper]: 0.5
緩存命中的結果,所以將再也不反覆運行sql。

二級緩存的注意事項:
1. 僅僅能在【僅僅有單表操做】的表上使用緩存
不只僅是要保證這個表在整個系統中僅僅有單表操做,並且和該表有關的所有操做必須所有在一個namespace下。
2. 在可以保證查詢遠遠大於insert,update,delete操做的狀況下使用緩存。
這一點不需要多說。所有人都應該清楚。記住,這一點需要保證在1的前提下才幹夠

二級緩存相關知識:
1. 緩存是以namespace爲單位的,不一樣namespace下的操做互不影響。


2. insert,update,delete操做會清空所在namespace下的所有緩存。
3. 一般使用MyBatis Generator生成的代碼中,都是各個表獨立的。每個表都有本身的namespace。

一般狀況下要避免使用二級緩存,緣由例如如下:
假如某個Mapper下有還有一個Mapper的操做,在還有一個Mapper的namespace下刷新了緩存,而在這個Mapper的namespace下緩存依舊有效,則數據出現了不一致性。假設此時該Mapper作了insert,update或delete時,將會出現未知的風險。

Mybatis+Redis

Java中使用Jedis.jar來鏈接和管理Redis。

整合Redis時有可能報錯: nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPool
這是因爲缺乏包:commons-pool的緣故。

可以使用一個鏈接池類來鏈接redis

/** * @類描寫敘述 redis 工具 * @功能名 POJO */ public class RedisUtil { private static JedisPool pool; private static int DBIndex=3; private static String host="127.0.0.1"; private static int port=6379; private static int timeout=60*1000; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(20); config.setMaxWaitMillis((long)1000); config.setTestOnBorrow(false); pool = new JedisPool(config, host, port, timeout);//線程數量限制,IP地址。端口,超時時間 } /** * 注意: * 做爲一個key value存在。很是多開發人員天然的使用set/get方式來使用Redis,實際上這並不是最優化的用法。 * 尤爲在未啓用VM狀況下,Redis所有數據需要放入內存,節約內存尤爲重要。 假如一個key-value單元需要最小佔用512字節,即便僅僅存一個字節也佔了512字節。

這時候就有一個設計模式,可以把key複用,幾個key-value放入一個key中,value再做爲一個set存入, 這樣相同512字節就會存放10-100倍的容量。 用於存儲多個key-value的值,比方可以存儲好多的person Object 樣例:>redis-cli 存儲:redis 127.0.0.1:6379> hset personhash personId personObject 得到:redis 127.0.0.1:6379> hget personhash personId (就可以得到當前personId相應的person對象) * @param key hashset key * @param field 至關於personId * @param value person Object */ public static void hsetItem(String key,String field,byte [] value){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); jedis.hset(key.getBytes(), field.getBytes(), value); } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } } public static byte [] hgetItem(String key,String field){ Jedis jedis=null; byte [] value = null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); value= jedis.hget(key.getBytes(), field.getBytes()); //jedis.hgetAll(key); } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } return value; } /** * @param key * @param value * @param seconds 有效時間 秒爲單位 0爲永久有效 */ public static void setItem(String key ,byte [] value,int seconds){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); if(seconds==0){ jedis.set(key.getBytes(), value); }else{ jedis.setex(key.getBytes(), seconds, value); } } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } } /** * 刪除 * @param keys */ public static void del(String... keys){ Jedis jedis=null; if(keys!=null){ try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); jedis.del(keys); } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } } } /** * 頭部加入元素 * @param key * @param value */ public static void lpushToList(String key,byte[] value){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); jedis.lpush(key.getBytes(), value); } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } } /** * 返回List * @param key * @param value */ public static List<byte[]> lrangeFromList(String key,int start ,int end){ Jedis jedis=null; List<byte[]> list = null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); list = jedis.lrange(key.getBytes(), start, end); } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } return list; } /** * * @param key key * @param member 存儲的value * @param score 排序字段 通常爲objecId */ public static void addItemToSortSet(String key,byte[] member,double score){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); jedis.zadd(key.getBytes(), score, member); } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } } public static void addListToSortSet(String key,List<byte[]> list,List<Double> scores){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.connect(); jedis.select(DBIndex); if(list!=null&& !list.isEmpty()&& scores!=null&& !scores.isEmpty() && list.size()==scores.size()){ for(int i=0;i<list.size();i++){ jedis.zadd(key.getBytes(), scores.get(i), list.get(i)); } } } catch (Exception e) { e.printStackTrace(); }finally{ if(jedis!=null) pool.returnResource(jedis); } } public static byte[] getItem(String key) { Jedis jedis=null; byte[] s=null; try { jedis = pool.getResource(); jedis.select(DBIndex); s = jedis.get(key.getBytes()); return s; } catch (Exception e) { e.printStackTrace(); return s; } finally{ if(jedis!=null) pool.returnResource(jedis); } } public static void delItem(String key) { Jedis jedis=null; try { jedis = pool.getResource(); jedis.select(DBIndex); jedis.del(key.getBytes()); } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } } public static long getIncrement(String key) { Jedis jedis=null; try { jedis = pool.getResource(); jedis.select(DBIndex); return jedis.incr(key); } catch (Exception e) { e.printStackTrace(); return 0L; } finally{ if(jedis!=null) pool.returnResource(jedis); } } public static void getkeys(String pattern){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.select(DBIndex); Set<String> keys = jedis.keys(pattern); for(String b:keys){ System.out.println("keys==> "+b); } } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } } public static String getkey(String key){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.select(DBIndex); return jedis.get(key); } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } return null; } public static void setkey(String key,String value){ Jedis jedis=null; try { jedis = pool.getResource(); jedis.select(DBIndex); jedis.set(key,value); } catch (Exception e) { e.printStackTrace(); } finally{ if(jedis!=null) pool.returnResource(jedis); } } }

部署到tomcat

將導出的war包放到tomcat的webapps文件夾下,運行tomcat就會本身主動解包並代理運行。

導出war包

用myeclipse對project使用export。選擇Java EE下的WAR file,而後選擇導出位置並保存。
* 有時myeclipse 10 導出war包時會出現這種錯誤「security alert:integrity check error」
security alert
這是破解的問題,需要替換MyEclipse\Common\plugins下的com.genuitec.eclipse.export.wizard_9.0.0.me201211011550.jar包爲未破解的原生jar包就能夠。

導出maven依賴jar包

在eclipse中。選擇項目的pom.xml文件,點擊右鍵菜單中的Run As,而後選擇Maven build…選項,在彈出的對話框中的Goals中輸入:dependency:copy-dependencies
maven build
以後Maven所依賴的jar包會導出到target/dependency中。

併發的線程安全處理

因爲SpringMVC的controller並不是線程安全的。因此使用單純的controller + DAO處理併發,經測試是沒法保證線程安全的。


正確的處理併發的姿式例如如下:
(1)在內存中。可以使用Collections.synchronized集合

public static List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());

public void initList() {
        list.add(100);
    }

public Integer getList(){
        return list.get(0);
    }

public void subList(){
        list.set(0, list.get(0)-1);
    }

這樣併發訪問時,不會出現同步錯誤,使用同步鎖使變量變得進程安全。
(2)在@Controller以前添加@Scope(「prototype」),就可以改變單例模式爲原生模式。(經測試,我以爲無效)
(3)使用ThreadLocal

實踐證實,因爲redis的原子性,使用redis+synchronized關鍵字可以保證原子性和可見性,使數據達到線程安全的效果。

裝飾者模式

設計一個接口。而後裝飾類實現後。其它被裝飾類將裝飾類傳入構造函數,在實現接口的函數中調用裝飾類的方法,即達到了裝飾的目的。

相關文章
相關標籤/搜索