- 本文將使用maven與Idea創(chuàng)建一個(gè)Springmvc+Mybatis+Ehcache的空項(xiàng)目
- 本文全部代碼
- mvn命令行創(chuàng)建項(xiàng)目基本結(jié)構(gòu)
mvn archetype:generate -DgroupId=com.mico.emptyspring -DartifactId=emptyspring -DarchetypeArtifactId=maven-archetype-webapp -DinteractivMode=false
- 使用idea打開(kāi)項(xiàng)目
- 修改pom.xml,定義源碼版本和編譯的class版本,否則maven有個(gè)默認(rèn)的編譯版本岸裙,可能較低
<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目錄下創(chuàng)建java文件夾猖败,標(biāo)記為
Source root
Sources -
在java文件夾下創(chuàng)建包名
大概像這樣 添加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版本要求
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>
<!-- 分頁(yè)插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
-
mybatis 和 ehcache 版本要求
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[掃描控制層,配置靜態(tài)資源降允,消息轉(zhuǎn)換器恩闻,視圖解析器]
<?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">
<!-- 配置自動(dòng)掃描的包 -->
<!--<task:annotation-driven/>-->
<mvc:annotation-driven/>
<context:component-scan base-package="com.mico.emptyspring.controller"/>
<!-- 加載properties配置文件 -->
<context:property-placeholder location="classpath:*.properties"/>
<!-- 處理靜態(tài)資源 -->
<!-- <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 方法返回值解析為實(shí)際的物理視圖
如果方法不標(biāo)示為@ResponseBody,將會(huì)走視圖解析器拟糕,也不會(huì)走消息轉(zhuǎn)換器[將對(duì)象轉(zhuǎn)為json字符串]判呕,
@responseBody注解的作用是將controller的方法返回的對(duì)象通過(guò)適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,
寫(xiě)入到response對(duì)象的body區(qū)送滞,通常用來(lái)返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)侠草,需要注意的呢,
在使用此注解之后不會(huì)再走視圖處理器犁嗅,而是直接將數(shù)據(jù)寫(xiě)入到輸入流中边涕,
他的效果等同于通過(guò)response對(duì)象輸出指定格式的數(shù)據(jù)。@ResponseBody都會(huì)在異步獲取數(shù)據(jù)時(shí)使用,
被其標(biāo)注的處理方法返回的數(shù)據(jù)將輸出到相應(yīng)流中,客戶端獲取并顯示數(shù)據(jù)褂微。
-->
<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的整合 -->
<!-- 自動(dòng)掃描 ,忽略@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.數(shù)據(jù)源: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引用數(shù)據(jù)源 Mybatis
定義數(shù)據(jù)源功蜓,同意加載配置 -->
<!--<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自動(dòng)掃描加載Sql映射文件/接口:MapperScannerConfigurer sqlSessionFactory
basePackage:指定sql映射文件/接口所在的包(自動(dòng)掃描) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mico.emptyspring.dao"></property>
<!-- 一直不明白為什么sqlSessionFactoryBeanName要用value而不用ref.
在mybatis-spring1.1.0以前,是通過(guò)<property name="sqlSessionFactory" r
ef="sqlSessionFactory"/>將SqlSessionFactory對(duì)象注入到sqlSessionFactory宠蚂,
這樣做可能會(huì)有一個(gè)問(wèn)題式撼,就是在初始化MyBatis時(shí),jdbc.properties文件還沒(méi)被加載進(jìn)來(lái)求厕,
dataSource的屬性值沒(méi)有被替換著隆,就開(kāi)始構(gòu)造sqlSessionFactory類,屬性值就會(huì)加載失敗呀癣。
在1.1.0以后美浦,MapperScannerConfigure提供了String類型的sqlSessionFactoryBeanName,
這樣將bean name注入到sqlSessionFactoryBeanName项栏,這樣就會(huì)等到spring初始化完成后浦辨,
再構(gòu)建sqlSessionFactory。-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- 4.事務(wù)管理:DataSourceTransactionManager dataSource 引用上面定義好的數(shù)據(jù)源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 5.使用聲明式事務(wù): transaction-manager = "txManager" tx:advice 這種 是用 aop方式管理事物
annotation-driven 這種是注解方式管理事物 第一種方式沼沈,需要在spring配置文件配置一些參數(shù) 第二種方式流酬,需要在 類里 加一些注解進(jìn)行事物管理用一種就行,沒(méi)必須都用 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
- 新增mybatis-config.xml,對(duì)mybatis進(jìn)行配置
<?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 -->
<!-- 全局參數(shù) Mappers文件里可以重寫(xiě)列另,查看UserMapper.xml-->
<settings>
<!-- 配置打印 SQL 到控制臺(tái) -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 全局地開(kāi)啟或關(guān)閉配置文件中的所有映射器已經(jīng)配置的任何緩存康吵。 -->
<setting name="cacheEnabled" value="true"/>
<!-- 全局啟用或禁用延遲加載。當(dāng)禁用時(shí)访递,所有關(guān)聯(lián)對(duì)象都會(huì)即時(shí)加載晦嵌。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 當(dāng)啟用時(shí),有延遲加載屬性的對(duì)象在被調(diào)用時(shí)將會(huì)完全加載任意屬性。否則惭载,每種屬性將會(huì)按需要加載旱函。 -->
<setting name="aggressiveLazyLoading" value="true"/>
<!-- 是否允許單條sql 返回多個(gè)數(shù)據(jù)集 (取決于驅(qū)動(dòng)的兼容性) default:true -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 是否可以使用列的別名 (取決于驅(qū)動(dòng)的兼容性) default:true -->
<setting name="useColumnLabel" value="true"/>
<!-- 允許JDBC 生成主鍵。需要驅(qū)動(dòng)器支持描滔。如果設(shè)為了true棒妨,這個(gè)設(shè)置將強(qiáng)制使用被生成的主鍵,有一些驅(qū)動(dòng)器不兼容不過(guò)仍然可以執(zhí)行含长。 default:false -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 指定 MyBatis 如何自動(dòng)映射 數(shù)據(jù)基表的列 NONE:不隱射 PARTIAL:部分 FULL:全部 -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 這是默認(rèn)的執(zhí)行類型 (SIMPLE: 簡(jiǎn)單券腔; REUSE: 執(zhí)行器可能重復(fù)使用prepared statements語(yǔ)句;BATCH: 執(zhí)行器可以重復(fù)執(zhí)行語(yǔ)句和批量更新) -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 使用駝峰命名法轉(zhuǎn)換字段拘泞。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 設(shè)置本地緩存范圍 session:就會(huì)有數(shù)據(jù)的共享 statement:語(yǔ)句范圍 (這樣就不會(huì)有數(shù)據(jù)的共享 ) defalut:session -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 設(shè)置但JDBC類型為空時(shí),某些驅(qū)動(dòng)程序 要指定值,default:OTHER纷纫,插入空值時(shí)不需要指定類型 -->
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<!--設(shè)置一個(gè)別名 -->
<typeAliases>
<typeAlias type="com.mico.emptyspring.entity.User" alias="User"/>
</typeAliases>
<!-- 啟用pageHelper插件 在代碼中使用
Page page = PageHelper.startPage(pageNum, pageSize, true);在查詢前使用
true表示需要統(tǒng)計(jì)總數(shù),這樣會(huì)多進(jìn)行一次請(qǐng)求select count(0); 省略掉true參數(shù)只返回分頁(yè)數(shù)據(jù)陪腌。
1.統(tǒng)計(jì)總數(shù)辱魁,(將SQL語(yǔ)句變?yōu)?select count(0) from xxx,只對(duì)簡(jiǎn)單SQL語(yǔ)句其效果,復(fù)雜SQL語(yǔ)句需要自己寫(xiě))
Page<?> page = PageHelper.startPage(1,-1);
long count = page.getTotal();
2.分頁(yè)诗鸭,pageNum - 第N頁(yè)染簇, pageSize - 每頁(yè)M條數(shù)
2.1只分頁(yè)不統(tǒng)計(jì)(每次只執(zhí)行分頁(yè)語(yǔ)句)
PageHelper.startPage([pageNum],[pageSize]);
List<?> pagelist = queryForList( xxx.class, "queryAll" , param);
//pagelist就是分頁(yè)之后的結(jié)果
2.2 分頁(yè)并統(tǒng)計(jì)(每次執(zhí)行2條語(yǔ)句,一條select count語(yǔ)句强岸,一條分頁(yè)語(yǔ)句)
適用于查詢分頁(yè)時(shí)數(shù)據(jù)發(fā)生變動(dòng)锻弓,需要將實(shí)時(shí)的變動(dòng)信息反映到分頁(yè)結(jié)果上
Page<?> page = PageHelper.startPage([pageNum],[pageSize],[iscount]);
List<?> pagelist = queryForList( xxx.class , "queryAll" , param);
long count = page.getTotal();
也可以 List<?> pagelist = page.getList(); 獲取分頁(yè)后的結(jié)果集
3.使用PageHelper查全部(不分頁(yè))
PageHelper.startPage(1,0);
List<?> alllist = queryForList( xxx.class , "queryAll" , param);
4.PageHelper的其他API
//獲取orderBy語(yǔ)句
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分頁(yè)合理化,null時(shí)用默認(rèn)配置
Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable);
//isPageSizeZero是否支持PageSize為0,true且pageSize=0時(shí)返回全部結(jié)果蝌箍,false時(shí)分頁(yè),null時(shí)用默認(rèn)配置
Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable, isPageSizeZero);
5.默認(rèn)值
//RowBounds參數(shù)offset作為PageNum使用 - 默認(rèn)不使用
private boolean offsetAsPageNum = false;
//RowBounds是否進(jìn)行count查詢 - 默認(rèn)不查詢
private boolean rowBoundsWithCount = false;
//當(dāng)設(shè)置為true的時(shí)候青灼,如果pagesize設(shè)置為0(或RowBounds的limit=0),就不執(zhí)行分頁(yè)十绑,返回全部結(jié)果
private boolean pageSizeZero = false;
//分頁(yè)合理化
private boolean reasonable = false;
//是否支持接口參數(shù)來(lái)傳遞分頁(yè)參數(shù)聚至,默認(rèn)false
private boolean supportMethodsArguments = false;
6.有一個(gè)安全性問(wèn)題酷勺,需要注意一下本橙,不然可能導(dǎo)致分頁(yè)錯(cuò)亂
什么時(shí)候會(huì)導(dǎo)致不安全的分頁(yè)?
PageHelper 方法使用了靜態(tài)的 ThreadLocal 參數(shù)脆诉,分頁(yè)參數(shù)和線程是綁定的甚亭。
只要你可以保證在 PageHelper 方法調(diào)用后緊跟 MyBatis 查詢方法,這就是安全的击胜。因?yàn)?PageHelper 在 finally 代碼段中自動(dòng)清除了 ThreadLocal 存儲(chǔ)的對(duì)象亏狰。
如果代碼在進(jìn)入 Executor 前發(fā)生異常,就會(huì)導(dǎo)致線程不可用偶摔,這屬于人為的 Bug(例如接口方法和 XML 中的不匹配暇唾,導(dǎo)致找不到 MappedStatement 時(shí)), 這種情況由于線程不可用,也不會(huì)導(dǎo)致 ThreadLocal 參數(shù)被錯(cuò)誤的使用策州。
但是如果你寫(xiě)出下面這樣的代碼瘸味,就是不安全的用法:
PageHelper.startPage(1, 10);
List<Country> list;
if(param1 != null){
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種情況下由于 param1 存在 null 的情況,就會(huì)導(dǎo)致 PageHelper 生產(chǎn)了一個(gè)分頁(yè)參數(shù)够挂,但是沒(méi)有被消費(fèi)旁仿,這個(gè)參數(shù)就會(huì)一直保留在這個(gè)線程上。當(dāng)這個(gè)線程再次被使用時(shí)孽糖,就可能導(dǎo)致不該分頁(yè)的方法去消費(fèi)這個(gè)分頁(yè)參數(shù)枯冈,這就產(chǎn)生了莫名其妙的分頁(yè)。
上面這個(gè)代碼办悟,應(yīng)該寫(xiě)成下面這個(gè)樣子:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種寫(xiě)法就能保證安全尘奏。
如果你對(duì)此不放心,你可以手動(dòng)清理 ThreadLocal 存儲(chǔ)的分頁(yè)參數(shù)誉尖,可以像下面這樣使用:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
try{
list = countryMapper.selectAll();
} finally {
PageHelper.clearPage();
}
} else {
list = new ArrayList<Country>();
}
這么寫(xiě)很不好看罪既,而且沒(méi)有必要。
PageHelper的優(yōu)點(diǎn)是铡恕,分頁(yè)和Mapper.xml完全解耦琢感。實(shí)現(xiàn)方式是以插件的形式,
對(duì)Mybatis執(zhí)行的流程進(jìn)行了強(qiáng)化探熔,添加了總數(shù)count和limit查詢驹针。屬于物理分頁(yè)。
5.0 是用這個(gè)類
com.github.pagehelper.PageInterceptor
因?yàn)镻ageHelper類诀艰,繼承Interceptor
public class PageHelper extends PageMethod implements Dialect
-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--自4.0.0以后的版本已經(jīng)可以自動(dòng)識(shí)別數(shù)據(jù)庫(kù)了柬甥,所以不需要我們?cè)偃ブ付〝?shù)據(jù)庫(kù)-->
<!--<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">
<!-- 啟用緩存注解功能,這個(gè)是必須的其垄,否則注解不會(huì)生效苛蒲,另外,該注解一定要聲明在spring主配置文件中才會(huì)生效 -->
<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>
- 編寫(xiě)ehcache.xml,配置ehcache
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--添加updateCheck="false"臂外,不添加會(huì)報(bào)錯(cuò)-->
<!--[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-->
<!--
屬性說(shuō)明:
? diskStore:指定數(shù)據(jù)在磁盤(pán)中的存儲(chǔ)位置。
? defaultCache:當(dāng)借助CacheManager.add("demoCache")創(chuàng)建Cache時(shí)喇颁,EhCache便會(huì)采用<defalutCache/>指定的的管理策略
以下屬性是必須的:
? maxElementsInMemory - 在內(nèi)存中緩存的element的最大數(shù)目
? maxElementsOnDisk - 在磁盤(pán)上緩存的element的最大數(shù)目漏健,若是0表示無(wú)窮大
? eternal - 設(shè)定緩存的elements是否永遠(yuǎn)不過(guò)期。如果為true橘霎,則緩存的數(shù)據(jù)始終有效蔫浆,
如果為false那么還要根據(jù)timeToIdleSeconds,timeToLiveSeconds判斷
? overflowToDisk - 設(shè)定當(dāng)內(nèi)存緩存溢出的時(shí)候是否將過(guò)期的element緩存到磁盤(pán)上
以下屬性是可選的:
? timeToIdleSeconds - 當(dāng)緩存在EhCache中的數(shù)據(jù)前后兩次訪問(wèn)的時(shí)間超過(guò)timeToIdleSeconds的屬性取值時(shí)姐叁,
這些數(shù)據(jù)便會(huì)刪除瓦盛,默認(rèn)值是0,也就是可閑置時(shí)間無(wú)窮大
? timeToLiveSeconds - 緩存element的有效生命期洗显,默認(rèn)是0.,也就是element存活時(shí)間無(wú)窮大
diskSpoolBufferSizeMB 這個(gè)參數(shù)設(shè)置DiskStore(磁盤(pán)緩存)的緩存區(qū)大小.默認(rèn)是30MB.每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū).
? diskPersistent - 在VM重啟的時(shí)候是否啟用磁盤(pán)保存EhCache中的數(shù)據(jù),默認(rèn)是false原环。
? diskExpiryThreadIntervalSeconds - 磁盤(pán)緩存的清理線程運(yùn)行間隔墙懂,默認(rèn)是120秒。
每個(gè)120s扮念,相應(yīng)的線程會(huì)進(jìn)行一次EhCache中數(shù)據(jù)的清理工作
? memoryStoreEvictionPolicy - 當(dāng)內(nèi)存緩存達(dá)到最大损搬,有新的element加入的時(shí)候辆雾,
移除緩存中element的策略嗤锉。默認(rèn)是LRU(最近最少使用)邮屁,可選的有LFU(最不常使用)和FIFO(先進(jìn)先出)
-->
<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>
- 編寫(xiě)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
- 編寫(xiě)攔截器記錄請(qǐng)求參數(shù)募逞,請(qǐng)求地址箱舞,請(qǐng)求ip卜范,計(jì)算請(qǐng)求處理時(shí)間
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();
}
}
}
- 編寫(xiě)消息轉(zhuǎn)換器[@ResponseBody后才選擇消息轉(zhuǎn)換器]
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;
}
}
- 創(chuàng)建數(shù)據(jù)表
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個(gè)java文件:
mybatis-generator - 創(chuàng)建UserService接口
package com.mico.emptyspring.service;
import com.mico.emptyspring.entity.User;
public interface UserProcessService {
boolean login(User user);
User findByUserId(User u);
}
- 創(chuàng)建UserService實(shí)現(xiàn)
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 會(huì)緩存方法的返回值嘀粱,
*
* @param u
* @return
*
*/
// @Cacheable(value = "baseCache", key = "#u.id")
public User findByUserId(User u) {
User user = userDao.selectByPrimaryKey(u.getId());
return user;
}
}
- 創(chuàng)建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的方法返回的對(duì)象通過(guò)適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后奠骄,
* 寫(xiě)入到response對(duì)象的body區(qū)迁匠,通常用來(lái)返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)剩瓶,需要注意的呢,
* 在使用此注解之后不會(huì)再走視圖處理器城丧,而是直接將數(shù)據(jù)寫(xiě)入到輸入流中延曙,
* 他的效果等同于通過(guò)response對(duì)象輸出指定格式的數(shù)據(jù)。@ResponseBody都會(huì)在異步獲取數(shù)據(jù)時(shí)使用,
* 被其標(biāo)注的處理方法返回的數(shù)據(jù)將輸出到相應(yīng)流中,客戶端獲取并顯示數(shù)據(jù)亡哄。
* @return
*/
@ResponseBody
@RequestMapping("/json")
public User json() {
User user = new User();
user.setId(1);
User userId = userProcessService.findByUserId(user);
return userId;
}
}
- 如何使用ehcache開(kāi)啟mybatis的二級(jí)緩存枝缔?
- 要緩存的entity先implements Serializable
- mybatis注解形式接口,比如@Insert蚊惯,@Selet愿卸,在接口上添加@CacheNamespace注解即可,比如
怎么才說(shuō)明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中如何設(shè)置主鍵自增?
@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);
- 日志中為什么頻繁出現(xiàn)
Creating a new SqlSession
?- Spring與MyBatis整合時(shí)宦焦,MyBatis的一級(jí)緩存在沒(méi)有事務(wù)存在的時(shí)候失效发钝。
- 在未開(kāi)啟事務(wù)的情況之下,每次查詢赶诊,spring都會(huì)關(guān)閉舊的sqlSession而創(chuàng)建新的sqlSession,因此此時(shí)的一級(jí)緩存是沒(méi)有啟作用的;
- 在開(kāi)啟事務(wù)的情況之下笼平,spring使用threadLocal獲取當(dāng)前資源綁定同一個(gè)sqlSession园骆,因此此時(shí)一級(jí)緩存是有效的舔痪。
- 為什么在spring-mvc.xml只掃描控制層,spring-mybatis.xml中排除了控制層锌唾?
只在spring-mybatis.xml中配置
<context:component-scan base-package="com.mico.emptyspring"/>
啟動(dòng)正常锄码,但是任何請(qǐng)求都不會(huì)被攔截夺英,簡(jiǎn)而言之就是@Controller失效只在spring-mvc.xml中配置上述配置
啟動(dòng)正常,請(qǐng)求也正常滋捶,但是事物失效痛悯,也就是不能進(jìn)行回滾在spring-mvc.xml和spring-mybatis.xml中都配置上述信息
啟動(dòng)正常,請(qǐng)求正常重窟,也是事物失效载萌,不能進(jìn)行回滾在spring-mybatis.xml中配置如下
<context:component-scan base-package="com.mico.emptyspring" />
在spring-mvc.xml中配置如下<context:component-scan base-package="com.mico.emptyspring.controller" />
此時(shí)啟動(dòng)正常,請(qǐng)求正常巡扇,事物也正常了扭仁。結(jié)論:
org.springframework.web.servlet.DispatcherServlet
的contextConfigLocation
屬性配置的xml中只需要掃描所有帶@Controller注解的類,在其他配置中可以掃描所有其他帶有注解的類(也可以過(guò)濾掉帶@Controller注解的類)厅翔。