Java Maven+Idea 构建SpringMvc+Mybatis项目

  • 本文将使用maven与Idea创建一个Springmvc+Mybatis+Ehcache的空项目
  • 本文全部代码
  • mvn命令行创建项目基本结构
mvn archetype:generate -DgroupId=com.mico.emptyspring -DartifactId=emptyspring -DarchetypeArtifactId=maven-archetype-webapp -DinteractivMode=false
  • 使用idea打开项目
  • 修改pom.xml,定义源码版本和编译的class版本,否则maven有个默认的编译版本,可能较低
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
</properties>
  ......
<build>
        <finalName>emptyspring</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
</build>
  • src/main目录下创建java文件夹,标记为Source root

    Sources

  • 在java文件夹下创建包名


    大概像这样
  • 添加spring依赖到pom.xml

        <!-- 基本包 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
        </dependency>

        <!-- 文件上传解析 -->
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

        <!--JDBC-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.40</version>
        </dependency>



        <!-- jackson -->
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.7</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.7</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.7</version>
        </dependency>



        <!-- 日志文件管理包 -->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.22</version>
        </dependency>
        <!--slf4j-log4j12 代表绑定的是log4j 1.2版本-->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.7.22</version>
        </dependency>
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
        </dependency>



         <!--核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--webmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!-- context-support EhCacheManagerFactoryBean 在这里 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--事物-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--orm-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- mybatis -->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!-- 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.8</version>
        </dependency>
        <!-- mybatis ehcache -->
        <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.1.0</version>
        </dependency>
  • 编辑web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:spring-*.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>characterEncodingFilter</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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
  • 在resource文件夹下新加spring-mvc.xml[扫描控制层,配置静态资源,消息转换器,视图解析器]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns="http://www.springframework.org/schema/beans"
       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/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置自动扫描的包 -->
    <!--<task:annotation-driven/>-->
    <mvc:annotation-driven/>
    <context:component-scan base-package="com.mico.emptyspring.controller"/>


    <!-- 加载properties配置文件 -->
    <context:property-placeholder location="classpath:*.properties"/>


    <!-- 处理静态资源 -->
    <!-- <mvc:default-servlet-handler/>  -->
    <!--<mvc:resources mapping="/*.jsp" location="/"/>-->
    <!--<mvc:resources mapping="/*.html" location="/"/>-->
    <!--<mvc:resources mapping="/*.ico" location="/"/>-->
    <mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/css/**" location="/css/"/>

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/static/**"/>
            <mvc:exclude-mapping path="/*.html"/>
            <mvc:exclude-mapping path="/favicon.ico"/>
            <bean class="com.mico.emptyspring.interceptor.CommonInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.mico.emptyspring.convert.MessageConverter"></bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <!-- set the max upload size100MB -->
        <property name="maxUploadSize">
            <value>104857600</value>
        </property>
        <property name="maxInMemorySize">
            <value>4096</value>
        </property>
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>

     <!--配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图

        如果方法不标示为@ResponseBody,将会走视图解析器,也不会走消息转换器[将对象转为json字符串],

        @responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,
        写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,
        在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,
        他的效果等同于通过response对象输出指定格式的数据。@ResponseBody都会在异步获取数据时使用,
        被其标注的处理方法返回的数据将输出到相应流中,客户端获取并显示数据。
     -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>
  • 新增spring-mybatis.xml 整合spirng和mybatis
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns="http://www.springframework.org/schema/beans"
       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-4.0.xsd">

    <!-- Mybatis 和 Spring的整合 -->

    <!-- 自动扫描 ,忽略@Controller注解的类-->
    <context:component-scan base-package="com.mico.emptyspring">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"></context:exclude-filter>
    </context:component-scan>

    <!-- 1.数据源:DriverManagerDataSource -->
    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true"></property>
        <property name="username" value="root"></property>
        <property name="password" value="micocube"></property>
    </bean>

    <!-- 2.Mybatis 的 SqlSession的工厂:SqlSessionFactoryBean dataSource引用数据源 Mybatis
        定义数据源,同意加载配置 -->
    <!--<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
    <!--<property name="dataSource" ref="dataSource"></property>-->
    <!--<property name="configLocation" value="classpath:mybatis-config.xml"></property>-->
    <!--</bean>-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--<property name="mapperLocations" value="classpath:mappers/*.xml"/>-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- <property name="typeAliasesPackage" value="com.tiantian.ckeditor.model" /> -->
    </bean>


    <!-- 3. Mybatis自动扫描加载Sql映射文件/接口:MapperScannerConfigurer sqlSessionFactory
        basePackage:指定sql映射文件/接口所在的包(自动扫描) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.mico.emptyspring.dao"></property>
        <!-- 一直不明白为什么sqlSessionFactoryBeanName要用value而不用ref.
        在mybatis-spring1.1.0以前,是通过<property name="sqlSessionFactory" r
        ef="sqlSessionFactory"/>将SqlSessionFactory对象注入到sqlSessionFactory,
        这样做可能会有一个问题,就是在初始化MyBatis时,jdbc.properties文件还没被加载进来,
        dataSource的属性值没有被替换,就开始构造sqlSessionFactory类,属性值就会加载失败。
        在1.1.0以后,MapperScannerConfigure提供了String类型的sqlSessionFactoryBeanName,
        这样将bean name注入到sqlSessionFactoryBeanName,这样就会等到spring初始化完成后,
        再构建sqlSessionFactory。-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>


    <!-- 4.事务管理:DataSourceTransactionManager dataSource 引用上面定义好的数据源 -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!-- 5.使用声明式事务: transaction-manager = "txManager" tx:advice 这种 是用 aop方式管理事物
        annotation-driven 这种是注解方式管理事物 第一种方式,需要在spring配置文件配置一些参数 第二种方式,需要在 类里 加一些注解进行事物管理用一种就行,没必须都用 -->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>
  • 新增mybatis-config.xml,对mybatis进行配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- mybatis 配置文件文档 http://www.mybatis.org/mybatis-3/zh/configuration.html -->

    <!-- 全局参数 Mappers文件里可以重写,查看UserMapper.xml-->
    <settings>
        <!-- 配置打印 SQL 到控制台 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
        <setting name="aggressiveLazyLoading" value="true"/>
        <!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
        <setting name="useColumnLabel" value="true"/>
        <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
        <setting name="useGeneratedKeys" value="true"/>
        <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分  FULL:全部  -->
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
        <setting name="localCacheScope" value="SESSION"/>
        <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>


    <!--设置一个别名 -->
    <typeAliases>
        <typeAlias type="com.mico.emptyspring.entity.User" alias="User"/>
    </typeAliases>

    <!-- 启用pageHelper插件 在代码中使用
    Page page = PageHelper.startPage(pageNum, pageSize, true);在查询前使用
    true表示需要统计总数,这样会多进行一次请求select count(0); 省略掉true参数只返回分页数据。

    1.统计总数,(将SQL语句变为 select count(0) from xxx,只对简单SQL语句其效果,复杂SQL语句需要自己写)
        Page<?> page = PageHelper.startPage(1,-1);
        long count = page.getTotal();


    2.分页,pageNum - 第N页, pageSize - 每页M条数
        2.1只分页不统计(每次只执行分页语句)
            PageHelper.startPage([pageNum],[pageSize]);
            List<?> pagelist = queryForList( xxx.class, "queryAll" , param);
            //pagelist就是分页之后的结果
        2.2 分页并统计(每次执行2条语句,一条select count语句,一条分页语句)
            适用于查询分页时数据发生变动,需要将实时的变动信息反映到分页结果上
            Page<?> page = PageHelper.startPage([pageNum],[pageSize],[iscount]);
            List<?> pagelist = queryForList( xxx.class , "queryAll" , param);
            long count = page.getTotal();
            也可以 List<?> pagelist = page.getList();  获取分页后的结果集


    3.使用PageHelper查全部(不分页)
        PageHelper.startPage(1,0);
        List<?> alllist = queryForList( xxx.class , "queryAll" , param);

    4.PageHelper的其他API
        //获取orderBy语句
        String orderBy = PageHelper.getOrderBy();
        Page<?> page = PageHelper.startPage(Object params);
        Page<?> page = PageHelper.startPage(int pageNum, int pageSize);
        Page<?> page = PageHelper.startPage(int pageNum, int pageSize, boolean isCount);
        Page<?> page = PageHelper.startPage(pageNum, pageSize, orderBy);
        //isReasonable分页合理化,null时用默认配置
        Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable);
        //isPageSizeZero是否支持PageSize为0,true且pageSize=0时返回全部结果,false时分页,null时用默认配置
        Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable, isPageSizeZero);

    5.默认值
        //RowBounds参数offset作为PageNum使用 - 默认不使用
        private boolean offsetAsPageNum = false;
        //RowBounds是否进行count查询 - 默认不查询
        private boolean rowBoundsWithCount = false;
        //当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
        private boolean pageSizeZero = false;
        //分页合理化
        private boolean reasonable = false;
        //是否支持接口参数来传递分页参数,默认false
        private boolean supportMethodsArguments = false;

    6.有一个安全性问题,需要注意一下,不然可能导致分页错乱
        什么时候会导致不安全的分页?
        PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。
        只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。
        如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。
        但是如果你写出下面这样的代码,就是不安全的用法:

        PageHelper.startPage(1, 10);
        List<Country> list;
        if(param1 != null){
            list = countryMapper.selectIf(param1);
        } else {
            list = new ArrayList<Country>();
        }

        这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
        上面这个代码,应该写成下面这个样子:

        List<Country> list;
        if(param1 != null){
            PageHelper.startPage(1, 10);
            list = countryMapper.selectIf(param1);
        } else {
            list = new ArrayList<Country>();
        }

        这种写法就能保证安全。
        如果你对此不放心,你可以手动清理 ThreadLocal 存储的分页参数,可以像下面这样使用:

        List<Country> list;
        if(param1 != null){
            PageHelper.startPage(1, 10);
            try{
                list = countryMapper.selectAll();
            } finally {
                PageHelper.clearPage();
            }
        } else {
            list = new ArrayList<Country>();
        }

        这么写很不好看,而且没有必要。

    PageHelper的优点是,分页和Mapper.xml完全解耦。实现方式是以插件的形式,
    对Mybatis执行的流程进行了强化,添加了总数count和limit查询。属于物理分页。
    5.0 是用这个类
    com.github.pagehelper.PageInterceptor
    因为PageHelper类,继承Interceptor
    public class PageHelper extends PageMethod implements Dialect
    -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--自4.0.0以后的版本已经可以自动识别数据库了,所以不需要我们再去指定数据库-->
            <!--<property name="dialect" value="mysql"/>-->
            <!--<property name="offsetAsPageNum" value="false"/>-->
            <!--<property name="rowBoundsWithCount" value="false"/>-->
            <!--<property name="pageSizeZero" value="true"/>-->
            <!--<property name="reasonable" value="false"/>-->
            <!--<property name="supportMethodsArguments" value="false"/>-->
            <!--<property name="returnPageInfo" value="none"/>-->
        </plugin>
    </plugins>

    <mappers>
        <package name="com.mico.emptyspring.dao"></package>
    </mappers>
</configuration>
  • 新增spring-ehcache.xml,整合spring和ehcache
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd">


    <!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
    <cache:annotation-driven cache-manager="ehcacheManager"/>
    <!-- cacheManager工厂类,指定ehcache.xml的位置 -->
    <bean id="ehcacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
    </bean>
    <!-- 声明cacheManager -->
    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManagerFactory"/>
    </bean>
</beans>
  • 编写ehcache.xml,配置ehcache
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!--添加updateCheck="false",不添加会报错-->
    <!--[DEBUG-console] 2019/01/28,15:08:07.253|Update check failed:-->
    <!--java.io.IOException: Server returned HTTP response code: 403 for URL: http://www.terracotta.org/kit/reflector?pageID=update.properties&kitID=ehcache.default&id=-1407972861&os-name=Mac+OS+X&jvm-name=Java+HotSpot%28TM%29+64-Bit+Server+VM&jvm-version=1.8.0_131&platform=x86_64&tc-version=2.6.11&tc-product=Ehcache+Core+2.6.11&source=Ehcache+Core&uptime-secs=1&patch=UNKNOWN-->


    <!--
     属性说明:
         diskStore:指定数据在磁盘中的存储位置。
         defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
        以下属性是必须的:
             maxElementsInMemory - 在内存中缓存的element的最大数目
             maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
             eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,
                    如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
             overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
            以下属性是可选的:
             timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,
                这些数据便会删除,默认值是0,也就是可闲置时间无穷大
             timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
            diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
             diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
             diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。
                每个120s,相应的线程会进行一次EhCache中数据的清理工作
             memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候,
                移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
     -->
    <diskStore path="/data/ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>

    <cache name="baseCache" eternal="true" maxElementsInMemory="1000" maxElementsOnDisk="10000"
           overflowToDisk="true" diskPersistent="false" timeToIdleSeconds="0"
           timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/>

</ehcache>
  • 编写log4j.properties,日志配置文件
log4j.rootLogger=DEBUG,file,stdout
#log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
#log4j.appender.stdout.layout.ConversionPattern=[%p-server] %d{yyyy/MM/dd,HH:mm:ss.SSS}|%m%n
#log4j.appender.stdout.Threshold=debug
### 输出到日志文件 ###
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=/data/www/file/logs/mico/server.log
log4j.appender.file.Append=true
log4j.appender.file.com.coding=INFO
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p-server] %d{yyyy/MM/dd,HH:mm:ss.SSS}|%m%n
#
#log4j.logger.java.sql.Statement = debug
#log4j.logger.java.sql.PreparedStatement = debug
#log4j.logger.java.sql.ResultSet =debug
#log4j.logger.java.sql.Connection =debug
#log4j.logger.com.ibatis =debug
#
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.java.sql.Statement = info
#log4j.appender.stdout.java.sql.PreparedStatement = debug
#log4j.appender.stdout.java.sql.ResultSet =debug
#log4j.appender.stdout.java.sql.Connection =debug
log4j.appender.stdout.com.coding=debug
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p-console] %d{yyyy/MM/dd,HH:mm:ss.SSS}|%m%n
  • 编写拦截器记录请求参数,请求地址,请求ip,计算请求处理时间
package com.mico.emptyspring.interceptor;


import com.mico.emptyspring.http.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

public class CommonInterceptor implements HandlerInterceptor {

    private static Logger log = LoggerFactory.getLogger(CommonInterceptor.class);

    public CommonInterceptor() {
        super();
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setHeader("Access-Control-Allow-Origin", "*");
        request.setAttribute(SpringMVCConstant.PROCESS_START, System.currentTimeMillis());
        Map params = new HashMap();
        request.getParameterMap().keySet().forEach(key -> {
            params.put(key, request.getParameterMap().get(key));
        });
        request.setAttribute(SpringMVCConstant.REQUEST_PARAMS, params);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
        if (exception == null) {
            LogContent lc = new LogContent(
                    System.currentTimeMillis() - (Long) request.getAttribute(SpringMVCConstant.PROCESS_START) + "ms",
                    request.getAttribute(SpringMVCConstant.REQUEST_PARAMS),
                    request.getAttribute(SpringMVCConstant.RESULT_FOR_LOG),
                    request.getServletPath(),
                    SpringMVCContext.getIp(request));
            String logContent = GlobalObject.getJsonMapper().writeValueAsString(lc);
            log.info(logContent);
        } else {
            LogContentWithException lc = new LogContentWithException(
                    System.currentTimeMillis() - (Long) request.getAttribute(SpringMVCConstant.PROCESS_START) + "ms",
                    request.getAttribute(SpringMVCConstant.REQUEST_PARAMS),
                    request.getAttribute(SpringMVCConstant.RESULT_FOR_LOG),
                    request.getServletPath(),
                    SpringMVCContext.getIp(request),
                    exception);
            String logContent = GlobalObject.getJsonMapper().writeValueAsString(lc);
            log.error(logContent, exception);

            GlobalObject.getJsonMapper().writeValue(response.getOutputStream(), new HttpResult(HttpStatus.SERVER_FAIL));
            response.getOutputStream().close();
        }
    }


    private class LogContent {
        public String consumed;
        public Object params;
        public Object result;
        public String url;
        public String ip;

        public LogContent(String consumed, Object params, Object result, String url, String ip) {
            this.consumed = consumed;
            this.params = params;
            this.result = result;
            this.url = url;
            this.ip = ip;
        }
    }

    private class LogContentWithException extends LogContent {
        public String throwable;

        public LogContentWithException(String consumed, Object params, Object result, String url, String ip, Exception throwable) {
            super(consumed, params, result, url, ip);
            this.throwable = throwable.toString();
        }
    }


}

  • 编写消息转换器[@ResponseBody后才选择消息转换器]
package com.mico.emptyspring.convert;

import com.mico.emptyspring.http.GlobalObject;
import com.mico.emptyspring.http.SpringMVCConstant;
import com.mico.emptyspring.http.SpringMVCContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * @auther coding on 2018/3/9.
 * email: ldscube@gmail.com
 */
public class MessageConverter extends AbstractHttpMessageConverter {
    private static final Log log = LogFactory.getLog(MessageConverter.class);
    private List supportedMediaTypes = Collections.singletonList(MediaType.APPLICATION_JSON_UTF8);


    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return supportedMediaTypes;
    }

    @Override
    protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }


    @Override
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        log.info("writeInternal:"+o.toString());
        SpringMVCContext.getRequest().setAttribute(SpringMVCConstant.RESULT_FOR_LOG, o);
        GlobalObject.getJsonMapper().writeValue(httpOutputMessage.getBody(), o);
    }

    @Override
    protected boolean supports(Class aClass) {
        return true;
    }

}
  • 创建数据表
create table role
(
  id int auto_increment
    primary key,
  name varchar(255) null
)
;

create table user
(
  id int auto_increment
    primary key,
  username varchar(20) null,
  password varchar(526) null
)
;

create table user_roles
(
  user_id int not null,
  roles_id int not null
)
;

create index FK55itppkw3i07do3h7qoclqd4k
  on user_roles (user_id)
;

create index FKj9553ass9uctjrmh0gkqsmv0d
  on user_roles (roles_id)
;


INSERT INTO test.role (id,name) VALUES (1,'User');
INSERT INTO test.user (id,username, password) VALUES (1,'root', '$2a$10$TeayMIrpuDwrpLHL5QsNpOcPeE/Kx3c4UYbi4NQzNkfKgf9YtL6F2');
INSERT INTO test.user_roles (user_id, roles_id) VALUES (1, 1);
java -jar ./src/main/resources/utils/mybatis-generator-core-1.3.7.jar -configfile ./src/main/resources/mybatis-generator.xml -overwrite
  • 生成的UserMapper如下所示
package com.mico.emptyspring.dao;

import com.mico.emptyspring.entity.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.type.JdbcType;

import java.util.List;

public interface UserMapper {
    @Delete({
            "delete from user",
            "where id = #{id,jdbcType=INTEGER}"
    })
    int deleteByPrimaryKey(Integer id);

    @Insert({
            "insert into user (id, username, ",
            "password)",
            "values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, ",
            "#{password,jdbcType=VARCHAR})"
    })
    int insert(User record);

    @Select({
            "select",
            "id, username, password",
            "from user",
            "where id = #{id,jdbcType=INTEGER}"
    })
    @Results({
            @Result(column = "id", property = "id", jdbcType = JdbcType.INTEGER, id = true),
            @Result(column = "username", property = "username", jdbcType = JdbcType.VARCHAR),
            @Result(column = "password", property = "password", jdbcType = JdbcType.VARCHAR)
    })
    User selectByPrimaryKey(Integer id);

    @Select({
            "select",
            "id, username, password",
            "from user"
    })
    @Results({
            @Result(column = "id", property = "id", jdbcType = JdbcType.INTEGER, id = true),
            @Result(column = "username", property = "username", jdbcType = JdbcType.VARCHAR),
            @Result(column = "password", property = "password", jdbcType = JdbcType.VARCHAR)
    })
    List<User> selectAll();
    @Update({
            "update user",
            "set username = #{username,jdbcType=VARCHAR},",
            "password = #{password,jdbcType=VARCHAR}",
            "where id = #{id,jdbcType=INTEGER}"
    })
    int updateByPrimaryKey(User record);
}
  • 该操作生成6个java文件:


    mybatis-generator
  • 创建UserService接口
package com.mico.emptyspring.service;


import com.mico.emptyspring.entity.User;

public interface UserProcessService {
     boolean login(User user);
     User findByUserId(User u);
}

  • 创建UserService实现
package com.mico.emptyspring.service;

import com.mico.emptyspring.dao.UserMapper;
import com.mico.emptyspring.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
@Transactional(rollbackFor = Exception.class)
public class UserProcessServiceImpl implements UserProcessService {

    @Autowired
    private UserMapper userDao;


    public boolean login(User userParam) {
//      Page page = PageHelper.startPage(pageNum, pageSize, true);
        User user = findByUserId(userParam);
        boolean loginSuccess = user == null ? false : true;
        return loginSuccess;
    }

    /**
     * Cacheable 会缓存方法的返回值,
     *
     * @param u
     * @return
     *
     */

//    @Cacheable(value = "baseCache", key = "#u.id")
    public User findByUserId(User u) {
        User user = userDao.selectByPrimaryKey(u.getId());
        return user;
    }


}

  • 创建UserProcessController控制器
package com.mico.emptyspring.controller;


import com.mico.emptyspring.entity.User;
import com.mico.emptyspring.service.UserProcessService;
import com.mico.emptyspring.utils.EhcacheUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;


@Controller
@RequestMapping("/user")
public class UserProcessController {

    @Autowired
    private UserProcessService userProcessService;
    @Resource
    private EhCacheCacheManager ehcacheManager;

    @RequestMapping("/index")
    public String index() {
        User user = new User();
        user.setId(1);
        userProcessService.findByUserId(user);
        EhcacheUtils.listAllCache(ehcacheManager);
        return "inner";
    }

    /**
     *
     * @responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,
     *  写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,
     *  在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,
     *  他的效果等同于通过response对象输出指定格式的数据。@ResponseBody都会在异步获取数据时使用,
     *  被其标注的处理方法返回的数据将输出到相应流中,客户端获取并显示数据。
     * @return
     */
    @ResponseBody
    @RequestMapping("/json")
    public User json() {
        User user = new User();
        user.setId(1);
        User userId = userProcessService.findByUserId(user);
        return userId;
    }
}

  • 如何使用ehcache开启mybatis的二级缓存?
    • 要缓存的entity先implements Serializable
    • mybatis注解形式接口,比如@Insert,@Selet,在接口上添加@CacheNamespace注解即可,比如
      package com.mico.emptyspring.dao;
    
      @CacheNamespace
      public interface UserMapper {
         ......
      }
    
    怎么才说明hit中了缓存?eg:
    Cache Hit Ratio [com.mico.emptyspring.dao.UserMapper]: 0.5
    • mybatis接口+mapper.xml文件,在mapper.xml文件中,添加配置,官方文档
    <mapper namespace="org.acme.FooMapper">
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    ...
    </mapper>
    
  • entity中如何设置主键自增?
        @Options(useGeneratedKeys = true,keyProperty = "id")
        @Insert({
                "insert into user (id, username, ",
                "password)",
                "values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, ",
                "#{password,jdbcType=VARCHAR})"
        })
        int insert(User record);
  • 日志中为什么频繁出现Creating a new SqlSession?
    • Spring与MyBatis整合时,MyBatis的一级缓存在没有事务存在的时候失效。
    • 在未开启事务的情况之下,每次查询,spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时的一级缓存是没有启作用的;
    • 在开启事务的情况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的。
  • 为什么在spring-mvc.xml只扫描控制层,spring-mybatis.xml中排除了控制层?
    • 只在spring-mybatis.xml中配置<context:component-scan base-package="com.mico.emptyspring"/>
      启动正常,但是任何请求都不会被拦截,简而言之就是@Controller失效

    • 只在spring-mvc.xml中配置上述配置
      启动正常,请求也正常,但是事物失效,也就是不能进行回滚

    • 在spring-mvc.xml和spring-mybatis.xml中都配置上述信息
      启动正常,请求正常,也是事物失效,不能进行回滚

    • 在spring-mybatis.xml中配置如下<context:component-scan base-package="com.mico.emptyspring" />在spring-mvc.xml中配置如下<context:component-scan base-package="com.mico.emptyspring.controller" />此时启动正常,请求正常,事物也正常了。

    • 结论:org.springframework.web.servlet.DispatcherServletcontextConfigLocation属性配置的xml中只需要扫描所有带@Controller注解的类,在其他配置中可以扫描所有其他带有注解的类(也可以过滤掉带@Controller注解的类)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,498评论 0 4
  • 1 Mybatis入门 1.1 单独使用jdbc编程问题总结 1.1.1 jdbc程序 上边使...
    哇哈哈E阅读 3,303评论 0 38
  • MyBatis 理论篇 [TOC] 什么是MyBatis  MyBatis是支持普通SQL查询,存储过程和高级映射...
    有_味阅读 2,892评论 0 26
  • 斗彩始于明宣德,但实物罕见。成化时期的斗彩最受推崇,明清文献中也称之为成“窑彩”或 "青花间装五色"。传世成化斗彩...
    荒唐忆梦阅读 583评论 0 0
  • 一转眼,进入幼儿园工作已经三个星期了,这段时间里都在忙于应对各种突发事件,今天要做这个了明天要交那个了,这个必须做...
    零度清爽阅读 134评论 0 0