- 本文将使用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
-
在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>
-
spring版本和mybatis版本要求
- 添加mybatis 依赖
<!-- 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 版本要求
- 添加mybatis-ehcache依赖
<!-- 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文件:
- 创建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注解即可,比如
怎么才说明hit中了缓存?eg:package com.mico.emptyspring.dao; @CacheNamespace public interface UserMapper { ...... }
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.DispatcherServlet
的contextConfigLocation
属性配置的xml中只需要扫描所有带@Controller注解的类,在其他配置中可以扫描所有其他带有注解的类(也可以过滤掉带@Controller注解的类)。