項目開發中讀寫的頻率差距很大,所以實現讀寫分離:主庫(master)中非實時讀取的查詢交給負載均衡的從庫(slave),查詢cpu的消耗和寫入的io延時,保證DB系統的健壯性。
最終問題,分佈式事務的線索。最後附源碼[springmvc+spring+mybatis+MySQL]:
注:註釋和部分代碼省略。
1:AbstractRoutingDataSource這個數據源路由類是spring2.0以後增加的,AbstractRoutingDataSource的定義:
/* @since 2.0.1*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {……}
通過實現determineCurrentLookupKey()抽象方法可以實現讀寫數據源的切換。
2:AbstractRoutingDataSource繼承了AbstractDataSource,而AbstractDataSource是DataSource的子類。DataSource是javax.sql的數據源接口,定義:
public interface DataSource extends CommonDataSource,Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
DataSource數據源接口定義了2個方法,都是獲取數據庫連接的。
3:AbstractRoutingDataSource這個數據源路由類對獲取連接的實現:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, DataSource> resolvedDataSources;
private Object defaultTargetDataSource;
private Map<Object, Object> targetDataSources;
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected abstract Object determineCurrentLookupKey();
}
通過自己定義的determineTargetDataSource()方法獲取到數據連接connection。
4:determineTargetDataSource的定義:
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
determineCurrentLookupKey方法返回lookupKey,resolvedDataSources方法就是根據lookupKey從Map中獲得數據源。從3看出resolvedDataSources集合屬性和determineCurrentLookupKey()抽象方法定義。
5:我們自己定義類繼承路由類AbstractRoutingDataSource去實現determineCurrentLookupKey()方法,然後自己來切換字符串告訴this.resolvedDataSources.get(lookupKey);方法這個lookupKey是誰?(也就是這個字符所對應的數據源的key(請看mybatis-context.xml配置40行注入defaultTargetDataSource和targetDataSources的值,通過‘lookupKey’字符串切換,spring注入的數據源對象.
1:通過註解在dao方法上註解,此方法開啓那個數據源:
package com.common.readwriteseparate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
2:自己定義類繼承路由類去實現determineCurrentLookupKey()方法:
package com.common.readwriteseparate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
String dataSouceKey = DynamicDataSourceHolder.getDataSouce();
return dataSouceKey;
}
}
多線程輔助類:
package com.common.readwriteseparate;
public class DynamicDataSourceHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void putDataSource(String name) {
holder.set(name);
}
public static String getDataSouce() {
return holder.get();
}
}
在DynamicDataSource的定義知道,返回的是DynamicDataSourceHolder.getDataSouce()值,我們需要在程序運行時調用DynamicDataSourceHolder.putDataSource()方法,對dataSouceKey數據源key賦值。
package com.common.readwriteseparate;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSourceAspect {
//log
private static final Logger LOG = LoggerFactory.getLogger(DataSourceAspect.class);
public void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
Method methodRefRes = classz[0].getMethod(method, parameterTypes);
if (methodRefRes != null && methodRefRes.isAnnotationPresent(DataSource.class)) {
DataSource data = methodRefRes.getAnnotation(DataSource.class);
DynamicDataSourceHolder.putDataSource(data.value());
LOG.info("\n************************************************\n" + "\t~~~DB:: " + data.value() + "\n************************************************");
}
} catch (Exception e) {
LOG.error("數據源失敗切面獲取異常:" + e.getMessage(), e);
}
}
}
5:先看看配置:
①spring的配置(spring-context.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 使用annotation 自動註冊bean,並檢查@Required,@Autowired的屬性已被注入 -->
<context:component-scan base-package="com.user.service,com.user.dao,com.user.bean,com.user.xml" />
<!-- 引入屬性配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:database.properties" />
</bean>
<!--或 <context:property-placeholder location="classpath*:*.properties" /> -->
</beans>
②:mybatis-context.xml的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-lazy-init="true">
<description>MyBatis的數據庫持久層配置/配置主-從數據源</description>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自動掃描entity目錄, 省掉Configuration.xml裏的手工配置 -->
<property name="mapperLocations" value="classpath*:com/user/xml/*.xml" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<!-- 掃描dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.user.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 配置數據庫註解aop -->
<aop:aspectj-autoproxy />
<bean id="dataSourceAspect" class="com.common.readwriteseparate.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.user.dao.*.*(..))" />
<aop:before pointcut-ref="tx" method="before" />
</aop:aspect>
</aop:config>
<!-- 主-從數據源路由 -->
<bean id="dataSource" class="com.common.readwriteseparate.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write -->
<entry key="master" value-ref="masterDataSource"/>
<!-- read -->
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
</beans>
③:mybatis-config.xml:
<?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>
<typeAliases>
<package name="com.user.bean"/>
</typeAliases>
</configuration>
④:database.properties:
#mysql-Used to verify the effectiveness of the database connection
validationQuery=SELECT 1
jdbc.initialSize=5
jdbc.maxActive=20
jdbc.maxWait=60000
jdbc.poolPreparedStatements=false
jdbc.poolMaximumIdleConnections=0
jdbc.driverClassName=org.gjt.mm.mysql.Driver
#1.tms business. 2.The db level optimization,data concurrency,desirable.
master.jdbc.url=jdbc:mysql://your ip:3306/master?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
slave.jdbc.url=jdbc:mysql://your ip:3306/slave?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc.username=username
jdbc.password=password
⑤:數據源配置:datasource-context.xml:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-lazy-init="true">
<description>配置主-從數據源</description>
<!-- 配置數據源-Master -->
<bean name="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${master.jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="0" />
<!-- 連接池最大使用連接數量 -->
<property name="maxActive" value="20" />
<!-- 連接池最大空閒 error:maxIdle is deprecated -->
<!-- <property name="maxIdle" value="20" /> -->
<!-- 連接池最小空閒 -->
<property name="minIdle" value="0" />
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="60000" />
<!-- <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打開removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分鐘 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 關閉abanded連接時輸出錯誤日誌 -->
<property name="logAbandoned" value="true" />
<!-- 監控數據庫 -->
<!-- <property name="filters" value="stat" /> -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 配置數據源-Slave -->
<bean name="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${slave.jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="0" />
<!-- 連接池最大使用連接數量 -->
<property name="maxActive" value="20" />
<!-- 連接池最大空閒 error:maxIdle is deprecated -->
<!-- <property name="maxIdle" value="20" /> -->
<!-- 連接池最小空閒 -->
<property name="minIdle" value="0" />
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="60000" />
<!-- <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打開removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分鐘 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 關閉abanded連接時輸出錯誤日誌 -->
<property name="logAbandoned" value="true" />
<!-- 監控數據庫 -->
<!-- <property name="filters" value="stat" /> -->
<property name="filters" value="mergeStat" />
</bean>
</beans>
⑥transaction-context.xml事務配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-lazy-init="true">
<description>配置事物</description>
<!-- 註解方式配置事物 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<aop:config>
<aop:pointcut expression="(execution(* com.user.service.*.* (..)))" id="pointcut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
<!-- 事務控制 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="load*" read-only="true" />
<tx:method name="get*" read-only="true" />
<tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="schedule*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="do*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 一個事務涉及一個數據源不能在事務內部去切換數據源成功,所以對多數據源的方法暫不開啓事務~分佈式事務 -->
<!-- <tx:method name="crud*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> -->
<!-- <tx:method name="*" /> -->
</tx:attributes>
</tx:advice>
</beans>
⑦:log4j.properties日誌配置:# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootLogger=info, STDOUT, FILEOUT
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.encoding=UTF-8
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m %n
# FILEOUT is set to be a File appender using a PatternLayout.
log4j.appender.FILEOUT=org.apache.log4j.RollingFileAppender
#tomcat1
#log4j.appender.FILEOUT.File=/home/logs/timespacexstar1/timespacexstar.log
#tomcat2
log4j.appender.FILEOUT.File=/home/logs/mydemoweb/mydemoweb.log
log4j.appender.FILEOUT.MaxFileSize=5MB
log4j.appender.FILEOUT.MaxBackupIndex=10
log4j.appender.FILEOUT.encoding=UTF-8
log4j.appender.FILEOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.FILEOUT.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m %n
# SQL
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.apache=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
6:代碼實現:
①:dao層:
package com.user.dao;
import com.common.readwriteseparate.DataSource;
import com.user.bean.Member;
public interface MemberMapper {
@DataSource("master")
int deleteByPrimaryKey(Integer id);
@DataSource("master")
int insert(Member record);
@DataSource("master")
int insertSelective(Member record);
@DataSource("master")
int updateByPrimaryKeySelective(Member record);
@DataSource("master")
int updateByPrimaryKey(Member record);
@DataSource("slave")
Member selectByPrimaryKey(Integer id);
}
②:業務層代碼:
package com.user.service;
import com.user.bean.Member;
public interface MemberService {
Member crudMember(Member member);
}
實現:
package com.user.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import com.user.bean.Member;
import com.user.dao.MemberMapper;
import com.user.service.MemberService;
@Service("memberService")
public class MemberServiceImpl implements MemberService {
//log
private static final Logger LOG = LoggerFactory.getLogger(MemberServiceImpl.class);
@Autowired
private MemberMapper memeberDao;
@Override
public Member crudMember(Member member) {
Member resMember = null;
try {
resMember = memeberDao.selectByPrimaryKey(member.getId());
LOG.info(member.getId() + "查詢成功~");
memeberDao.insert(member);
LOG.info(member.getUsername() + "註冊成功~");
} catch (Exception e) {
LOG.error("用戶id:"+member.getId()+"保存和查詢失敗~~~" + e.getMessage(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return resMember;
}
}
③:master數據庫沒有id爲2的member註冊,但slave中有一個用戶,先查詢從庫用戶,再插入主庫這個member,不涉及業務僅對分庫測試:
junit測試代碼:
package com.user.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.common.definition.DictionaryDefinition;
import com.user.bean.Member;
import com.user.service.MemberService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-context.xml","classpath:datasource-context.xml",
"classpath:mybatis-context.xml","classpath:transaction-context.xml"})
public class UserTest {
//log
private static final Logger LOG = LoggerFactory.getLogger(UserTest.class);
@Autowired
private MemberService memberService;
/**
* 主從分離測試,非支持事務
* */
@Test
public void crudTraM(){
Member member = new Member();
member.setId(2);
member.setUsername("Tony5");
member.setPassword("12345678");
member.setStatus(DictionaryDefinition.LOGIN_NORMAL);
Member member1 = memberService.crudMember(member);
if(member1 != null){
LOG.info("結果~~:" + member1.getUsername());
}else{
LOG.info("主從事物回滾測試失敗數據爲空");
}
}
}
效果:master插入前:
後:
slave前後不變:
junit代碼測試結果log:
2016-04-11 18:48:32 [ main:2195 ] - [ INFO ]
************************************************
~~~DB:: slave
************************************************
2016-04-11 18:48:32 [ main:2879 ] - [ INFO ] 2查詢成功~
2016-04-11 18:48:32 [ main:2880 ] - [ INFO ]
************************************************
~~~DB:: master
************************************************
2016-04-11 18:48:32 [ main:2977 ] - [ INFO ] Tony5註冊成功~
2016-04-11 18:48:32 [ main:2977 ] - [ INFO ] 結果~~:kekeai
上邊完成了在service層同一方法中數據源的切換,但這個方法必須不支持事務,不然切換失敗!!!
A:spring事務的默然配置:
<tx:method name="*" />
默認意義:
①:事務傳播設置是 REQUIRED
②:隔離級別是數據庫 DEFAULT
③:事務是支持 讀/寫
④:事務超時默認是依賴於事務系統的,或者事務超時沒有被支持。
⑤:任何 RuntimeException 將觸發事務回滾,但是任何 checked Exception 將不觸發事務回滾
<tx:method/> 有關事務自定義的設置:
源代碼:Spring實現數據庫讀寫分離
數據庫sql:db_rw_separate.sql
附錄:
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- Spring監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:*-context.xml</param-value>
</context-param>
<!-- 編碼utf-8 -->
<filter>
<filter-name>charFilter</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>
</filter>
<filter-mapping>
<filter-name>charFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springMvc</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>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置SESSION超時,單位是分鐘 -->
<session-config>
<session-timeout>45</session-timeout>
</session-config>
</web-app>
spring-mvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 自動掃描該包,使SpringMVC認爲包下用了@controller註解的類是控制器 -->
<context:component-scan base-package="com.ourtimes.controller" />
<!--避免IE執行AJAX時,返回JSON出現下載文件 -->
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 啓動SpringMVC的註解功能,完成請求和註解POJO的映射 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJacksonHttpMessageConverter" /> <!-- JSON轉換器 -->
</list>
</property>
</bean>
<!-- 定義跳轉的文件的前後綴 ,視圖模式配置-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 這裏的配置我的理解是自動給後面action的方法return的字符串加上前綴和後綴,變成一個 可用的url地址 -->
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 配置文件上傳,如果沒有使用文件上傳可以不用配置,當然如果不配,那麼配置文件中也不必引入上傳組件包 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 默認編碼 -->
<property name="defaultEncoding" value="utf-8" />
<!-- 文件大小最大值 -->
<property name="maxUploadSize" value="10485760000" />
<!-- 內存中的最大值 -->
<property name="maxInMemorySize" value="40960" />
</bean>
</beans>