• SSM框架笔记


    配置

    Project结构

    SpringMVC启用

    Spring MVC配置

    配置具体解释
    * spring_mvc_图解

    spring_mvc_图解


    • ### Spring3.1新特性

    一、Spring2.5之前。我们都是通过实现Controller接口或其实现来定义我们的处理器类。

    二、Spring2.5引入注解式处理器支持,通过@Controller 和 @RequestMapping注解定义我们的处理器类。而且提供了一组强大的注解:
    须要通过处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter来开启支持@Controller 和 @RequestMapping注解的处理器。

    @Controller:用于标识是处理器类。
    
    @RequestMapping:请求到处理器功能方法的映射规则。
    
    @RequestParam:请求參数到处理器功能处理方法的方法參数上的绑定;
    
    @ModelAttribute:请求參数到命令对象的绑定;
    
    @SessionAttributes:用于声明session级别存储的属性。放置在处理器类上,通常列出模型属性(如@ModelAttribute)相应的名称,则这些属性会透明的保存到session中;
    
    @InitBinder:自己定义数据绑定注冊支持。用于将请求參数转换到命令对象属性的相应类型。
    

    三、Spring3.0引入RESTful架构风格支持(通过@PathVariable注解和一些其它特性支持),且又引入了很多其它的注解支持:

    @CookieValue:cookie数据到处理器功能处理方法的方法參数上的绑定;
    
    @RequestHeader:请求头(header)数据到处理器功能处理方法的方法參数上的绑定;
    
    @RequestBody:请求的body体的绑定(通过HttpMessageConverter进行类型转换);
    
    @ResponseBody:处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换)。
    
    @ResponseStatus:定义处理器功能处理方法/异常处理器返回的状态码和原因;
    
    @ExceptionHandler:注解式声明异常处理器;
    
    @PathVariable:请求URI中的模板变量部分到处理器功能处理方法的方法參数上的绑定,从而支持RESTful架构风格的URI;
    

    四、还有比方:

    JSR-303验证框架的无缝支持(通过@Valid注解定义验证元数据);

    使用Spring 3開始的ConversionService进行类型转换(PropertyEditor依旧有效),支持使用@NumberFormat 和 @DateTimeFormat来进行数字和日期的格式化;

    HttpMessageConverter(Http输入/输出转换器,比方JSON、XML等的数据输出转换器);

    ContentNegotiatingViewResolver,内容协商视图解析器,它还是视图解析器,仅仅是它支持依据请求信息将同一模型数据以不同的视图方式展示(如json、xml、html等)。RESTful架构风格中非常重要的概念(同一资源,多种表现形式)。

    Spring 3 引入 一个 mvc XML的命名空间用于支持mvc配置,包含如:

    <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找不到资源错误,)。

    ……等等。

    五、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模式。1、POST时将下一次须要的数据放在FlashMap;2、重定向;3、通过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的差别

    1、Struts2是类级别的拦截。 一个类相应一个request上下文,SpringMVC是方法级别的拦截。一个方法相应一个request上下文。而方法同一时候又跟一个url相应,所以说从架构本身上SpringMVC就easy实现restful url,而struts2的架构实现起来要费劲,由于Struts2中Action的一个方法能够相应一个url,而其类属性却被全部方法共享,这也就无法用注解或其它方式标识其所属方法了。

    2、由上边原因。SpringMVC的方法之间基本上独立的。独享request response数据,请求数据通过參数获取,处理结果通过ModelMap交回给框架。方法之间不共享变量,而Struts2搞的就比較乱,尽管方法之间也是独立的,但其全部Action变量是共享的。这不会影响程序运行,却给我们编码 读程序时带来麻烦,每次来了请求就创建一个Action,一个Action对象相应一个request上下文。

    3、由于Struts2须要针对每一个request进行封装,把request。session等servlet生命周期的变量封装成一个一个Map,供给每一个Action使用。并保证线程安全,所以在原则上。是比較耗费内存的,而SpringMVC的controller则是非线程安全的。

    4、 拦截器实现机制上。Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。

    5、SpringMVC的入口是servlet。而Struts2是filter(这里要指出,filter和servlet是不同的。曾经觉得filter是servlet的一种特殊)。这就导致了二者的机制不同,这里就牵涉到servlet和filter的差别了。

    6、SpringMVC集成了Ajax,使用非常方便。仅仅需一个注解@ResponseBody就能够实现。然后直接返回响应文本就可以,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。

    7、SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比較繁琐。感觉太烦乱。

    8、Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也能够通过不同的文件夹结构和相关配置做到SpringMVC一样的效果,可是须要xml配置的地方不少)。

    9、 设计思想上。Struts2更加符合OOP的编程思想, SpringMVC就比較慎重。在servlet上扩展。

    10、SpringMVC开发效率和性能高于Struts2。
    11、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
    这是破解的问题,须要替换MyEclipseCommonplugins下的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关键字能够保证原子性和可见性,使数据达到线程安全的效果。

    装饰者模式

    设计一个接口。然后装饰类实现后。其它被装饰类将装饰类传入构造函数,在实现接口的函数中调用装饰类的方法,即达到了装饰的目的。

  • 相关阅读:
    两个单链表的第一个公共节点
    对于混沌定义中三个条件的理解
    sort()函数使用详解
    C++优先队列详解
    第一次只出现一个的字符
    丑数
    把数组排成最小的数
    从1到n整数中1的个数
    git相关知识
    文件处理(如果文件存在则追加,不存在则生成多级文件夹以及txt目录)
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7202583.html
Copyright © 2020-2023  润新知