SSM(Spring+SpringMVC+Mybatis)框架配置例子
首先从web.xml开始:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!—读取spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- spring的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!—session创建/销毁监听器 -->
<listener>
<listener-class>com.org.xcs.listener.SessionListener</listener-class>
</listener>
<!—字符集过滤器 -->
<filter>
<description>字符集过滤器</description>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>字符集编码</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- druid -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!—将所有.do结尾的url请求交给DispatcherServlet处理 -->
<servlet>
<description>springmvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>springmvc 配置文件</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!—超时配置,单位为分钟 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<!—错误跳转页面 -->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/500.jsp</location>
</error-page>
</web-app>
可以看到,web.xml中引用了两个xml配置文件:
classpath:spring.xml
classpath:springmvc.xml
其中,classpath就是代表web工程编译后的/WEB-INF/classes/ 这个路径(不理解的话,可以将war包放到tomcat中运行后,去工程目录底下查看);在代码工程中,把这两个文件放到资源目录(resources)下就可以了。
接下来,我们看spring.xml的配置:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
">
<importresource="config/*.xml"/>
<!-- 拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mappingpath="/**"/>
<beanclass="com.org.xcs.interceptor.LoginIntercepter"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
可以看到,这里主要做了两件事:一是配置了一个登录拦截器,即用于拦截未登录的请求,强制重定向到登录页面,拦截器内容我也贴出来供大家参考:
LoginIntercepter.java:
package com.visionvera.cms.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public classLoginIntercepter implements HandlerInterceptor {
public voidafterCompletion(HttpServletRequest arg0,
HttpServletResponsearg1, Object arg2, Exception arg3)
throws Exception {
}
public voidpostHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Objectarg2, ModelAndView arg3) throws Exception {
}
public booleanpreHandle(HttpServletRequest request, HttpServletResponse response,
Objectobj) throwsException {
Stringurl = request.getRequestURI().toString();
if(url !=null && !"".equals(url)&& isSkipCheck(url)){
return true;
}else{
Objectuser = request.getSession().getAttribute("login_user");
if(user ==null){
//判断是否为ajax请求
if (request.getHeader("x-requested-with")!=null
&&request.getHeader("x-requested-with")
.equalsIgnoreCase("XMLHttpRequest")){//如果是ajax请求响应头会有x-requested-with
response.setHeader("sessionTimeOut","yes");
returnfalse;
}
response.sendRedirect(request.getContextPath()+"/index/showLogin.do");//如果用户未登录就强行重定向到登录页面
return false;
}
}
return true;
}
private boolean isSkipCheck(Stringurl){
String[]skipArr = {"login"};
for(String skip : skipArr){
if(url.toLowerCase().contains(skip)){
return true;
}
}
return false;
}
}
二是引入(import)了一些配置文件,这些配置文件存放于资源目录下的config目录中,都是xml格式的文件。
这里我是为了细分各种配置,所有拆分成了多个配置文件,大家可以根据实际情况进行整合,也是没有问题的。
config目录下,一共放有5个文件:
annotation.xml
jdbc.xml
mybatis.xml
property.xml
transation.xml
我们来逐个分析这些文件的内容:
annotation.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--spring 扫包 @Service.....-->
<context:component-scanbase-package="com.org.xcs">
<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:annotation-config/>
</beans>
这里定义是扫描Service包,包的位置是在com.org.xcs,并排除了Controller(Controller包的扫描在springmvc.xml中定义)。
接着是jdbc.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- druid -->
<beanid="dataSource"class="com.alibaba.druid.pool.DruidDataSource"
init-method="init"destroy-method="close">
<!-- <property name="driverClass"value="${mysql.driver}"/> -->
<propertyname="url"value="${mysql.url}"></property>
<propertyname="username"value="${mysql.username}"/>
<propertyname="password"value="${mysql.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<propertyname="initialSize"value="1"/>
<propertyname="minIdle"value="1"/>
<propertyname="maxActive"value="20"/>
<!-- 配置获取连接等待超时的时间 -->
<propertyname="maxWait"value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<propertyname="timeBetweenEvictionRunsMillis"value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<propertyname="minEvictableIdleTimeMillis"value="300000"/>
<propertyname="validationQuery"value="SELECT 'x'"/>
<propertyname="testWhileIdle"value="true"/>
<propertyname="testOnBorrow"value="false"/>
<propertyname="testOnReturn"value="false"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<propertyname="poolPreparedStatements"value="true"/>
<propertyname="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
<!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
<propertyname="filters"value="stat"/>
</bean>
</beans>
这里定义了数据源(dataSource),使用阿里巴巴的druid数据库连接池。
然后是mybatis.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--mybatis sessionFactory配置-->
<beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">
<propertyname="dataSource"ref="dataSource"/>
<propertyname="mapperLocations"value="classpath:com/visionvera/cms/dao/*.xml"/>
<propertyname="typeAliasesPackage"value="com.org.xcs.bean"/>
<propertyname="plugins">
<array>
<reflocal="statementHandlerInterceptor"/>
<reflocal="resultSetHandlerInterceptor"/>
</array>
</property>
</bean>
<beanid="statementHandlerInterceptor"class="com.org.xcs.interceptor.StatementHandlerInterceptor"/>
<beanid="resultSetHandlerInterceptor"class="com.org.xcs.interceptor.ResultSetHandlerInterceptor"/>
<!-- 扫包 -->
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
<propertyname="basePackage"value="com.org.xcs.dao"/>
</bean>
<!-- 注册Spring工具类 -->
<beanid="springContextUtil"class="com.org.xcs.commom.SpringContextUtil"></bean>
</beans>
这里主要是数据源的sessionFactory的定义,使用的是mybatis为spring提供的SqlSessionFactoryBean工厂类。我们来重点分析工厂类定义的几个property:
首先是dataSource,这个很明显,定义的是数据源,指向的就是jdbc.xml中定义的那个;
其次是mapperLocations,定义mybatis的Mapper文件的路径,是一些xml文件,mybatis的数据库操作都是定义在这些文件中的;
再次是typeAliasesPackage,Mapper文件关联的数据库实体(entity)类的存放路径;
最后是plugins,这个是mybatis提供的插件,可以定义拦截器用来实现分页。定义多个拦截器的时候,执行顺序从上到下。
两个拦截器的内容如下:
StatementHandlerInterceptor.java:
package com.org.xcs.interceptor;
import java.sql.Connection;
import java.util.Properties;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.org.xcs.commom.ReflectUtil;
@Intercepts({ @Signature(type= StatementHandler.class, method ="prepare", args = { Connection.class }) })
public classStatementHandlerInterceptor implements Interceptor {
private static String MYSQL_DIALECT="com.org.xcs.dao.interceptor.MySqlDialectPPT";
private Loggerlog = LoggerFactory
.getLogger(StatementHandlerInterceptor.class);
public Object intercept(Invocationinvocation)throws Throwable {
RoutingStatementHandlerstatement = (RoutingStatementHandler)invocation
.getTarget();
PreparedStatementHandlerhandler = (PreparedStatementHandler) ReflectUtil
.getFieldValue(statement,"delegate");
RowBoundsrowBounds = (RowBounds) ReflectUtil.getFieldValue(handler,
"rowBounds");
if (rowBounds ==null || rowBounds.getLimit()== RowBounds.NO_ROW_LIMIT) {
returninvocation.proceed();
}
BoundSqlboundSql = statement.getBoundSql();
Stringsql = boundSql.getSql();
MySqlDialectPPTdialect = newMySqlDialectPPT();
sql= dialect.getLimitString(sql, rowBounds.getOffset(),
rowBounds.getLimit());
log.info(sql);
ReflectUtil.setFieldValue(boundSql,"sql",sql);
return invocation.proceed();
}
public Object plugin(Objecttarget) {
return Plugin.wrap(target,this);
}
public voidsetProperties(Properties properties) {
SqlSessionFactoryBeanc;
}
}
ResultSetHandlerInterceptor.java:
packagecom.org.xcs.interceptor;
importjava.sql.Statement;
importjava.util.Properties;
importorg.apache.ibatis.executor.resultset.FastResultSetHandler;
importorg.apache.ibatis.executor.resultset.ResultSetHandler;
importorg.apache.ibatis.plugin.Interceptor;
importorg.apache.ibatis.plugin.Intercepts;
importorg.apache.ibatis.plugin.Invocation;
importorg.apache.ibatis.plugin.Plugin;
importorg.apache.ibatis.plugin.Signature;
importorg.apache.ibatis.session.RowBounds;
importcom.org.xcs.commom.ReflectUtil;
@Intercepts({@Signature(type = ResultSetHandler.class, method ="handleResultSets", args = { Statement.class }) })
publicclass ResultSetHandlerInterceptor implements Interceptor {
public Object intercept(Invocationinvocation) throws Throwable {
FastResultSetHandler resultSet =(FastResultSetHandler) invocation
.getTarget();
RowBounds rowBounds = (RowBounds)ReflectUtil.getFieldValue(resultSet,
"rowBounds");
if (rowBounds.getLimit() > 0
&& rowBounds.getLimit() <RowBounds.NO_ROW_LIMIT) {
ReflectUtil.setFieldValue(resultSet,"rowBounds", new RowBounds());
}
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Propertiesproperties) {
}
}
另外使用到了ReflectUtil.java:
package com.org.xcs.commom;
importjava.lang.reflect.Field;
importjava.lang.reflect.Modifier;
public classReflectUtil {
public static void setFieldValue(Objectobject, String fieldName,Object value){
Field field = getDeclaredField(object,fieldName);
if (field == null)
throw newIllegalArgumentException("Could not find field ["
+ fieldName + "] ontarget [" + object + "]");
makeAccessible(field);
try {
field.set(object, value);
} catch(IllegalAccessException e) {
e.printStackTrace();
}
}
public static Object getFieldValue(Objectobject, String fieldName) {
Field field = getDeclaredField(object,fieldName);
if (field == null)
throw newIllegalArgumentException("Could not find field ["
+ fieldName + "] ontarget [" + object + "]");
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
private static FieldgetDeclaredField(Object object, String filedName) {
for (Class<?> superClass =object.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
returnsuperClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
// Field 不在当前类定义, 继续向上转型
}
}
return null;
}
private static void makeAccessible(Fieldfield) {
if(!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
}
}
以上是分页拦截器所用到的三个类。接下来我们举一个Mapper的例子:
TestDao.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.org.xcs.dao.TestDao">
<selectid="getTimeCount" resultType="ReportVO"parameterType="map">
…<!--省略-->
</select>
<selectid="getMemberCount" resultType="ReportVO"parameterType="map">
…<!--省略-->
</mapper>
可以看到,TestDao.xml的mapper有个namespace属性,这个属性是用于绑定Dao接口的。当你的namespace绑定接口后,你可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句。这里指定为TestDao,我们来看下TestDao.java的内容:
package com.org.xcs.dao;
importjava.util.List;
importjava.util.Map;
import com.org.xcs.bean.ReportVO;
publicinterface MainDao {
List<ReportVO>getTimeCount(Map<String, Object> paramsMap);
List<ReportVO>getMemberCount(Map<String, Object> paramsMap);
}
可以看到,TestDao.java里只定义了接口。通过namespace绑定后,我们通过Service调用Dao接口时,mybatis会自动帮我们执行mapper中定义好的数据库操作语句。
再看property.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 读取jdbc配置 -->
<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<propertyname="locations">
<list>
<!-- jdbc配置 -->
<value>classpath:properties/jdbc.properties</value>
<!-- memcached配置 -->
</list>
</property>
</bean>
</beans>
这个比较简单,就是定义了jdbc.properties的存放路径。
jdbc.properties定义了数据库的地址、用户名密码等信息:
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=UTF-8
mysql.username=test
mysql.password=123456
最后是transation.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- spring 事务 -->
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource"ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driventransaction-manager="transactionManager"/>
</beans>
这个文件定义了数据源使用的事务类型。这里我使用的是spring自带的DataSourceTransactionManager。它会将所有具有@Transactional 注解的bean自动配置为声明式事务支持。
注意:要使事务生效,必须在接口或类的声明处 ,写一个@Transactional,例如:
package com.org.xcs.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.org.xcs.bean.ReportVO;
import com.org.xcs.service.MainService;
@Service
@Transactional
public class MainServiceImpl implements MainService {
…<-- 省略 -->
}
至此,一个完整的SSM框架完成了。其中若有遗漏或错误,欢迎大家批评指正。