mybatis-plus+动态数据源+自定义数据源配置+dom解析+自定义参数注入(注解)+AOP切换数据源+druid
项目需求引入租户概念,每个租户一个域名,由于每个租户的用户量比较大,为方便数据管理和减轻数据库压力,要求使用多数据源,根据租户域名路由到对应租户的数据库。在网上看了好多关于多数据源的文章,感觉都不合适。几乎所有的文章都需要在配置文件中配置多个数据源,如果一个租户即有app,又有web,那相同的租户就要配置2个相同的数据源,配置文件会变的很繁琐。于是想把数据源配置文件独立出来,在项目加载数据源的时候自动解析并注入多数据源。
感谢同事架构师萌琪琪!里面一些实现参考了他的多数据源,他用的是AbstractDataSource实现的多数据源路由,不用默认数据源什么的,更接近底层一些。
参考博客:http://icezx.iteye.com/blog/1939586
然后自己整理出一个简单的项目实例,由于还没有实际应用,难免有疏漏或写的不好的地方,欢迎指正。
看下项目结构:
具体的过程我就不多解释了,请参考博客:http://icezx.iteye.com/blog/1939586
项目实例下载地址:点这里!(http://download.youkuaiyun.com/download/kaer_gg/10000306)
简单介绍下实现逻辑和代码吧:
项目启动时通过DynamicDataSource加载数据源,此过程涉及解析自定义的数据库配置文件dataSource.xml。然后controller通过截获请求的url获取用户访问的域名,并通过注解或指定自定义类型对象自动注入到controller参数中,然后通过service切面取得参数做为数据库切换依据进行数据库切换,实现根据不同租户的域名访问项目进行对应租户数据库的访问。
自定义注解:CurrentTenantKey.java
package com.guorucheng.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentTenantKey {
}
controller使用自定义注解或指定参数对象注入:CurrentTenantKeyResolver.java
package com.guorucheng.common.annotation.resolver;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.guorucheng.common.annotation.CurrentTenantKey;
import com.guorucheng.common.entity.TenantKey;
import com.guorucheng.common.utils.StringUtil;
/**
* CurrentTenantKey 注解解析器
* @author GR·cheng
*
*/
public class CurrentTenantKeyResolver implements HandlerMethodArgumentResolver {
//private final Pattern PATTERN = Pattern.compile("^http://((\\w+\\.){1,2}guorucheng\\.com)");
/**
*
* @Title: resolveArgument
* @Description: 将request中的请求参数解析到当前controller参数上
* @param parameter 需要被解析的controller参数,此参数必须先传给{@link #supportsParameter}并返回true
* @param mavContainer 当前request的ModelAndViewContainer
* @param request 当前request
* @param binderFactory 生成{@link WebDataBinder}实例的工厂
* @return 解析后的controller参数
* @throws Exception
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
* @user GR·cheng
* @date 2017年9月6日
*/
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest request,
WebDataBinderFactory binderFactory) throws Exception {
TenantKey tenantKey = new TenantKey();
String url = request.getNativeRequest(HttpServletRequest.class).getServerName().toString();
if (StringUtil.notEmpty(url)) {
tenantKey.setTenantKey(url);
} else {
tenantKey.setTenantKey("www.guorucheng.com");
}
return tenantKey;
}
/**
*
* @Title: supportsParameter
* @Description: 指定参数如果被应用@CurrentTenantKey,则使用该注解。
* @param parameter 当前使用注解的参数
* @return 如果支持当前使用注解的参数,返回true,不支持返回false。如果直接返回true,则表示支持所有参数。
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
* @user GR·cheng
* @date 2017年9月6日
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentTenantKey.class) || parameter.getParameterType() == TenantKey.class;
}
}
切面截获切换依据并设置当前线程数据源:TenantKeyAspect.java
package com.guorucheng.common.aspect;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import com.guorucheng.common.entity.TenantKey;
import com.guorucheng.common.holder.DataSourceHolder;
public class TenantKeyAspect implements MethodBeforeAdvice,AfterReturningAdvice {
/**
*
* @Title: before 执行方法前切换数据源
* @Description:
* @param method 方法名
* @param parameters 参数组
* @param ASP_CLASS 调用的类对象
* @throws Throwable
* @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
* @user GR·cheng
* @date 2017年9月28日
*/
@Override
public void before(Method method, Object[] parameters, Object ASP_CLASS)
throws Throwable {
TenantKey tenantKey = null;
for (Object object : parameters) {//入参查找租户
if (object instanceof TenantKey) {
tenantKey = (TenantKey) object;
break;
}
}
if (tenantKey == null) {//入参无租户,抛出异常
throw new RuntimeException("service层切面未获取到tenantKey");
}
DataSourceHolder.setDataSourceType(tenantKey.getTenantKey());
}
/**
*
* @Title: afterReturning 方法执行return前清除线程数据源
* @Description:
* @param returnObject 调用方法返回的对象
* @param method 方法名
* @param parameters 参数组
* @param ASP_CLASS 调用的类对象
* @throws Throwable
* @see org.springframework.aop.AfterReturningAdvice#afterReturning(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
* @user GR·cheng
* @date 2017年9月28日
*/
@Override
public void afterReturning(Object returnObject, Method method, Object[] parameters, Object ASP_CLASS) throws Throwable {
DataSourceHolder.removeDataSourceType();
}
}
多数据源创建、管理、切换类:DynamicDataSource.java
package com.guorucheng.common.dynamicDataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.guorucheng.common.entity.DataSourceConfig;
import com.guorucheng.common.entity.TenantKey;
import com.guorucheng.common.holder.DataSourceHolder;
import com.guorucheng.common.utils.StringUtil;
import com.guorucheng.common.xmlConf.XmlConfInitStrategy;
/**
* 初始化动态数据源,并根据上下文变量切换数据源
* @author GR·cheng
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware {
private static Logger log = Logger.getLogger(DynamicDataSource.class);
//数据源配置文件名称
private String configName = "";
//bean工厂
private static ApplicationContext context;
//数据源配置列表
private List<DataSourceConfig> configs = null;
//数据源池
public final ConcurrentHashMap<String, DataSource> dataSourcePool = new ConcurrentHashMap<String, DataSource>();
//初始化多数据源
@Override
public void afterPropertiesSet() {
try {
initDataSources();
} catch (Exception e) {
e.printStackTrace();
}
super.afterPropertiesSet();
}
//获取bean工厂
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context = applicationContext;
}
/**
*
*
* @Title: initDataSources
* @Description: 初始化数据源
* @param:
* @return: void
* @user: GR·cheng
*
*/
private void initDataSources() {
configs = XmlConfInitStrategy.init().getInitConfig(configName);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
for (DataSourceConfig config : configs) {
if (!config.checkConfig()) {
throw new RuntimeException("数据库配置文件缺少关键字段值");
}
DataSource dataSource = createDataSource(config);
List<TenantKey> tenantKeys = config.buildTenantKeyList();
for (TenantKey tenantKey : tenantKeys) {
if (dataSourcePool.containsKey(tenantKey)) {
log.info("数据库连接池中已存在数据源:" + tenantKey.getTenantKey());
} else {
beanFactory.registerSingleton(tenantKey.getTenantKey(), dataSource);
dataSourcePool.put(tenantKey.getTenantKey(), dataSource);
}
}
}
this.setTargetDataSources(dataSourcePool);
setDefaultTargetDataSource(dataSourcePool.get("www.guorucheng.com"));
}
/**
*
*
* @Title: createDataSource
* @Description: 创建数据源
* @param: @param config
* @param: @return
* @return: DataSource
* @user: GR·cheng
*
*/
private DataSource createDataSource(DataSourceConfig config) {
if (config == null) {
return null;
}
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(config.getUrl());
dataSource.setUsername(config.getUsername());
dataSource.setPassword(config.getPassword());
String initialSizeStr = config.getInitialSize();
if (StringUtil.notEmpty(initialSizeStr)) {
try {
Integer initialSize = Integer.parseInt(initialSizeStr);
dataSource.setInitialSize(initialSize);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config initialSize invalid");
}
}
String maxActiveStr = config.getMaxActive();
if (StringUtil.notEmpty(maxActiveStr)) {
try {
Integer maxActive = Integer.parseInt(maxActiveStr);
dataSource.setMaxActive(maxActive);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config maxActive invalid");
}
}
String minIdleStr = config.getMinIdle();
if (StringUtil.notEmpty(minIdleStr)) {
try {
Integer minIdle = Integer.parseInt(minIdleStr);
dataSource.setMinIdle(minIdle);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config minIdle invalid");
}
}
String maxWaitStr = config.getMaxWait();
if (StringUtil.notEmpty(maxWaitStr)) {
try {
Long maxWait = Long.parseLong(maxWaitStr);
dataSource.setMaxWait(maxWait);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config maxWait invalid");
}
}
String validationQuery = config.getValidationQuery();
if (StringUtil.notEmpty(validationQuery)) {
dataSource.setValidationQuery(validationQuery);
}
String testOnBorrowStr = config.getTestOnBorrow();
if (StringUtil.notEmpty(testOnBorrowStr)) {
Boolean testOnBorrow = Boolean.parseBoolean(testOnBorrowStr);
dataSource.setTestOnBorrow(testOnBorrow);
}
String testOnReturnStr = config.getTestOnReturn();
if (StringUtil.notEmpty(testOnReturnStr)) {
Boolean testOnReturn = Boolean.parseBoolean(testOnReturnStr);
dataSource.setTestOnReturn(testOnReturn);
}
String testWhileIdleStr = config.getTestWhileIdle();
if (StringUtil.notEmpty(testWhileIdleStr)) {
Boolean testWhileIdle = Boolean.parseBoolean(testWhileIdleStr);
dataSource.setTestWhileIdle(testWhileIdle);
}
String timeBetweenEvictionRunsMillisStr = config.getTimeBetweenEvictionRunsMillis();
if (StringUtil.notEmpty(timeBetweenEvictionRunsMillisStr)) {
try {
Long timeBetweenEvictionRunsMillis = Long.parseLong(timeBetweenEvictionRunsMillisStr);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config timeBetweenEvictionRunsMillis invalid");
}
}
String minEvictableIdleTimeMillisStr = config.getMinEvictableIdleTimeMillis();
if (StringUtil.notEmpty(minEvictableIdleTimeMillisStr)) {
try {
Long minEvictableIdleTimeMIllis = Long.parseLong(minEvictableIdleTimeMillisStr);
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMIllis);
} catch (NumberFormatException e) {
throw new RuntimeException("xml datasource config minEvictableIdleTimeMIllis invalid");
}
}
String removeAbandonedStr = config.getRemoveAbandoned();
if (StringUtil.notEmpty(removeAbandonedStr)) {
Boolean removeAbandoned = Boolean.parseBoolean(removeAbandonedStr);
dataSource.setRemoveAbandoned(removeAbandoned);
}
String removeAbandonedTimeoutStr = config.getRemoveAbandonedTimeout();
if (StringUtil.notEmpty(removeAbandonedTimeoutStr)) {
Integer removeAbandonedTimeout = Integer.parseInt(removeAbandonedTimeoutStr);
dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout);
}
String logAbandonedStr = config.getLogAbandoned();
if (StringUtil.notEmpty(logAbandonedStr)) {
Boolean logAbandoned = Boolean.parseBoolean(logAbandonedStr);
dataSource.setLogAbandoned(logAbandoned);
}
try {
dataSource.init();
} catch (SQLException e) {
throw new RuntimeException("drudDataSource init fail tenant is "+config.getTenantKeyList(),e);
}
return dataSource;
}
//查找当前用户上下文变量中设置的数据源
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSourceType();
}
//设置默认数据源
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
}
//设置数据源集合
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void setTargetDataSources(Map targetDataSources){
super.setTargetDataSources(targetDataSources);
}
public String getConfigName() {
return configName;
}
public void setConfigName(String configName) {
this.configName = configName;
}
}
druid连接池实体类:DataSourceConfig.java
package com.guorucheng.common.entity;
import java.util.ArrayList;
import java.util.List;
import com.guorucheng.common.utils.StringUtil;
public class DataSourceConfig {
/**
* 允许接入的租户域名列表
*/
private List<String> tenantKeyList;
/**
* 数据库地址
*/
private String url;
/**
* 数据库登录名
*/
private String username;
/**
* 数据库登录密码
*/
private String password;
/**
* 初始化连接池大小
*/
private String initialSize;
/**
* 连接池最大使用连接数
*/
private String maxActive;
/**
* 连接池最小空闲数
*/
private String minIdle;
/**
* 获取连接最大等待时间
*/
private String maxWait;
/**
* 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
*/
private String validationQuery;
/**
* 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
*/
private String testOnBorrow;
/**
* 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
*/
private String testOnReturn;
/**
* 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。建议配置为true,不影响性能,并且保证安全性。
*/
private String testWhileIdle;
/**
* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒。
*/
private String timeBetweenEvictionRunsMillis;
/**
* 一个连接在连接池中的最小生存时间,单位毫秒。
*/
private String minEvictableIdleTimeMillis;
/**
* 对于长时间不使用的连接是否强制关闭
*/
private String removeAbandoned;
/**
* 强制关闭长时间不适用的空闲连接的时间
*/
private String removeAbandonedTimeout;
/**
* 是否将关闭当前长时间未用的连接记录日志
*/
private String logAbandoned;
public List<String> getTenantKeyList() {
return tenantKeyList;
}
public void setTenantKeyList(List<String> tenantKeyList) {
this.tenantKeyList = tenantKeyList;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getInitialSize() {
return initialSize;
}
public void setInitialSize(String initialSize) {
this.initialSize = initialSize;
}
public String getMaxActive() {
return maxActive;
}
public void setMaxActive(String maxActive) {
this.maxActive = maxActive;
}
public String getMinIdle() {
return minIdle;
}
public void setMinIdle(String minIdle) {
this.minIdle = minIdle;
}
public String getMaxWait() {
return maxWait;
}
public void setMaxWait(String maxWait) {
this.maxWait = maxWait;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public String getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(String testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public String getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(String testOnReturn) {
this.testOnReturn = testOnReturn;
}
public String getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(String testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public String getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(
String timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public String getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(String minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getRemoveAbandoned() {
return removeAbandoned;
}
public void setRemoveAbandoned(String removeAbandoned) {
this.removeAbandoned = removeAbandoned;
}
public String getRemoveAbandonedTimeout() {
return removeAbandonedTimeout;
}
public void setRemoveAbandonedTimeout(String removeAbandonedTimeout) {
this.removeAbandonedTimeout = removeAbandonedTimeout;
}
public String getLogAbandoned() {
return logAbandoned;
}
public void setLogAbandoned(String logAbandoned) {
this.logAbandoned = logAbandoned;
}
public boolean checkConfig() {
if (tenantKeyList == null || tenantKeyList.size() == 0 || StringUtil.isEmpty(url) || StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {
return false;
}
return true;
}
public List<TenantKey> buildTenantKeyList() {
List<TenantKey> keys = new ArrayList<TenantKey>();
for (String tenantKey : tenantKeyList) {
keys.add(new TenantKey(tenantKey));
}
return keys;
}
}
租户实体类:TenantKey.java
package com.guorucheng.common.entity;
public class TenantKey {
private String tenantKey;
public TenantKey() {
}
public TenantKey(String tenantKey) {
super();
this.tenantKey = tenantKey;
}
public String getTenantKey() {
return tenantKey;
}
public void setTenantKey(String tenantKey) {
this.tenantKey = tenantKey;
}
@Override
public String toString() {
return "TenantKey [tenantKey=" + tenantKey + "]";
}
}
代码生成器:MpGenerator.java
package com.guorucheng.common.generator;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DbType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class MpGenerator {
public static void main(String[] args) {
AutoGenerator ag = new AutoGenerator();
//全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir("D://test-Workspace//GuoRuCheng//src//main//java");
gc.setFileOverride(true);// 是否覆盖文件
gc.setActiveRecord(true);// 开启activeRecord 模式
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(true);// XML columList
gc.setAuthor("GR·cheng");
// 自定义文件命名,注意 %s 会自动填充表实体属性!
// .setMapperName("%sDao")
// .setXmlName("%sDao")
// .setServiceName("MP%sService")
// .setServiceImplName("%sServiceDiy")
// .setControllerName("%sAction")
ag.setGlobalConfig(gc);
//数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/db1?characterEncoding=utf8");
dsc.setUsername("root");
dsc.setPassword("root");
ag.setDataSource(dsc);
//生成策略
StrategyConfig sc = new StrategyConfig();
sc.containsTablePrefix("");
sc.setNaming(NamingStrategy.underline_to_camel);//表名生成策略
ag.setStrategy(sc);
PackageConfig pc = new PackageConfig();
pc.setParent("com.guorucheng");
pc.setEntity("model");
pc.setMapper("dao");
pc.setXml("mapper");
pc.setService("service");
pc.setServiceImpl("service.serviceImpl");
pc.setController("controller");
ag.setPackageInfo(pc);
ag.execute();
}
}
线程数据源管理器:DataSourceHolder.java
package com.guorucheng.common.holder;
/**
* 线程数据源管理器
* @author GR·cheng
*
*/
public class DataSourceHolder {
private static final ThreadLocal<String> DATA_SOURCE_HOLDER = new ThreadLocal<String>();
public static void setDataSourceType(String dataSourceType) {
DATA_SOURCE_HOLDER.set(dataSourceType);
}
public static String getDataSourceType() {
return DATA_SOURCE_HOLDER.get();
}
public static void removeDataSourceType() {
DATA_SOURCE_HOLDER.remove();
}
}
自定义数据库配置文件解析器:XmlConfInitStrategy.java
package com.guorucheng.common.xmlConf;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.guorucheng.common.entity.DataSourceConfig;
import com.guorucheng.common.utils.StringUtil;
/**
* 自定义xml解析
* @author GR·cheng
*
*/
public class XmlConfInitStrategy {
private final Logger LOG = Logger.getLogger(this.getClass());
public static XmlConfInitStrategy xmlConfInitStrategy;
private final String DEFAULT_CONF_FILE = "dataSource-conf.xml";
public static XmlConfInitStrategy init() {
return xmlConfInitStrategy = new XmlConfInitStrategy();
}
public List<DataSourceConfig> getInitConfig(String configName) {
return parseConfigXml(StringUtil.isEmpty(configName)?loadConfig(DEFAULT_CONF_FILE):loadConfig(configName+".xml"));
}
@SuppressWarnings("unchecked")
private List<DataSourceConfig> parseConfigXml(InputStream configXml){
if (configXml == null) {
return null;
}
Document document = null;
SAXReader saxReader = new SAXReader();
List<DataSourceConfig> dataSourceConfigs = new ArrayList<DataSourceConfig>();
try {
document = saxReader.read(configXml);
Element root = document.getRootElement();
List<Element> nodes = root.elements();
for (Element node : nodes) {
DataSourceConfig config = new DataSourceConfig();
String tenantKeys = node.elementTextTrim("tenantKey");
String[] tenantKeyArr = tenantKeys.split(";");
List<String> tenantKeyList = new ArrayList<String>();
for (String tenantKey : tenantKeyArr) {
tenantKeyList.add(tenantKey);
}
config.setTenantKeyList(tenantKeyList);
Element dataSource = node.element("dataSource");
List<Element> dataSources = dataSource.elements();
for (Element element : dataSources) {
String propName = element.getName();
try {
Field field = config.getClass().getDeclaredField(propName);
Method setter = config.getClass().getMethod("set"+propName.substring(0,1).toUpperCase()+propName.substring(1), field.getType());
setter.invoke(config, element.getTextTrim());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
dataSourceConfigs.add(config);
}
} catch (DocumentException e) {
e.printStackTrace();
}
return dataSourceConfigs;
}
/**
* 加载src/main/resources中指定名称的配置文件
* @param configPath 文件名称
* @return 输入流
*/
private InputStream loadConfig(String configName){
InputStream stream = this.getClass().getClassLoader().getResourceAsStream(configName);
if (stream==null) {
LOG.info("dataSource xml is not exist!");
return null;
}
return stream;
}
}
项目中的应用:controller层
/**
*
*
* @Title: findSysUsers
* @Description: 使用自动识别注参并进行数据源切换查询
* @param: @param name
* @param: @param tenantKey
* @param: @return
* @return: List<SysUser>
* @user: GR·cheng
*
*/
@ResponseBody
@RequestMapping("/findSysUsers")
public List<SysUser> findSysUsers(TenantKey tenantKey) {
return userService.findSysUsers(tenantKey);
}
/**
*
*
* @Title: findSysUsers
* @Description: 使用注解注参并进行数据源切换查询
* @param: @param name
* @param: @param tenantKey
* @param: @return
* @return: List<SysUser>
* @user: GR·cheng
*
*/
@ResponseBody
@RequestMapping("/findSysUsers1")
public List<SysUser> findSysUsers1(@CurrentTenantKey TenantKey tenantKey) {
return userService.findSysUsers(tenantKey);
}
service层实现时切换数据源,到这里就不用写代码了,切面类自动就切换数据源了,由于使用了mybatis-plus,使用框架自带的通用dao就好了,非常方便,不用自己再写代码了,dao和mapper都不用写。
package com.guorucheng.service.serviceImpl;
import java.util.List;
import com.guorucheng.model.SysUser;
import com.guorucheng.common.entity.TenantKey;
import com.guorucheng.dao.SysUserMapper;
import com.guorucheng.service.ISysUserService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* 系统用户表 服务实现类
* </p>
*
* @author GR·cheng
* @since 2017-08-31
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
@Autowired
private SysUserMapper userDAO;
@Override
public List<SysUser> findSysUsers(TenantKey tenantKey) {
return userDAO.selectList(null);
}
}
dao:
package com.guorucheng.dao;
import com.guorucheng.model.SysUser;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* <p>
* 系统用户表 Mapper 接口
* </p>
*
* @author GR·cheng
* @since 2017-08-31
*/
public interface SysUserMapper extends BaseMapper<SysUser> {
}
mapper:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.guorucheng.dao.SysUserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.guorucheng.model.SysUser">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
<result column="ctime" property="ctime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, age, ctime
</sql>
</mapper>
接下来是相关配置文件:
spring.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:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- Service包(自动注入) -->
<context:component-scan base-package="com.guorucheng.service"/>
<import resource="classpath:spring-mybatis.xml"/>
</beans>
spring-mybaties.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dynamicDataSource" class="com.guorucheng.common.dynamicDataSource.DynamicDataSource" >
<property name="configName" value="dataSource" /><!-- value值为数据库配置文件xml名称 -->
</bean>
<!-- Spring整合Mybatis,更多查看文档:http://mp.baomidou.com -->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"/>
<!-- 自动扫描Mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/guorucheng/mapper/*.xml"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- <property name="typeAliasesPackage" value="com.guorucheng.model"/> -->
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
</bean>
</array>
</property>
<!-- 全局配置注入 -->
<property name="globalConfig" ref="globalConfig" />
</bean>
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<!--
AUTO->`0`("数据库ID自增")
INPUT->`1`(用户输入ID")
ID_WORKER->`2`("全局唯一ID")
UUID->`3`("全局唯一ID")
-->
<property name="idType" value="2" />
<!--
MYSQL->`mysql`
ORACLE->`oracle`
DB2->`db2`
H2->`h2`
HSQL->`hsql`
SQLITE->`sqlite`
POSTGRE->`postgresql`
SQLSERVER2005->`sqlserver2005`
SQLSERVER->`sqlserver`
-->
<!-- Oracle需要添加该项 -->
<!-- <property name="dbType" value="oracle" /> -->
<!-- 全局表为下划线命名设置 true -->
<!-- <property name="dbColumnUnderline" value="true" /> -->
<!-- <property name="metaObjectHandler">
<bean class="com.ssm.common.MyMetaObjectHandler" />
</property> -->
</bean>
<!-- MyBatis 动态扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.guorucheng.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 配置事务管理 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"/>
</bean>
<!-- 事务管理 属性 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="append*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="edit*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="repair" propagation="REQUIRED"/>
<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
<tx:method name="find*" propagation="REQUIRED" read-only="true"/>
<tx:method name="load*" propagation="REQUIRED" read-only="true"/>
<tx:method name="search*" propagation="REQUIRED" read-only="true"/>
<tx:method name="datagrid*" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<bean id="tenantKeyAspect" class="com.guorucheng.common.aspect.TenantKeyAspect" />
<!-- 配置事务切面和租户切面 -->
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* com.guorucheng.service..*.*(..))"/>
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" order="2"/>
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="tenantKeyAspect" order="1"/><!-- 数据源切换要在事务之前,否则会导致事务无效 -->
</aop:config>
</beans>
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: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.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">
<mvc:default-servlet-handler/>
<!-- Controller包(自动注入) -->
<context:component-scan base-package="com.guorucheng.controller"/>
<mvc:annotation-driven>
<!-- 注册自定义参数解析器 -->
<mvc:argument-resolvers>
<bean class="com.guorucheng.common.annotation.resolver.CurrentTenantKeyResolver" />
</mvc:argument-resolvers>
<!-- FastJson注入 -->
<mvc:message-converters register-defaults="true">
<!-- 避免IE执行AJAX时,返回JSON出现下载文件 -->
<!-- FastJson -->
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- 这里顺序不能反,一定先写text/html,不然ie下出现下载提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="features">
<array value-type="com.alibaba.fastjson.serializer.SerializerFeature">
<!-- 避免循环引用 -->
<value>DisableCircularReferenceDetect</value>
<!-- 是否输出值为null的字段 -->
<value>WriteMapNullValue</value>
<!-- 数值字段如果为null,输出为0,而非null -->
<value>WriteNullNumberAsZero</value>
<!-- 字符类型字段如果为null,输出为"",而非null -->
<value>WriteNullStringAsEmpty</value>
<!-- List字段如果为null,输出为[],而非null -->
<value>WriteNullListAsEmpty</value>
<!-- Boolean字段如果为null,输出为false,而非null -->
<value>WriteNullBooleanAsFalse</value>
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 静态资源配置 -->
<mvc:resources mapping="/resources/**" location="/resources/"/>
<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 上传限制 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 上传文件大小限制为31M,31*1024*1024 -->
<property name="maxUploadSize" value="32505856"/>
</bean>
</beans>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--
| plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
| properties?, settings?,
| typeAliases?, typeHandlers?,
| objectFactory?,objectWrapperFactory?,
| plugins?,
| environments?, databaseIdProvider?, mappers?
|-->
<configuration>
<!--
| 全局配置设置
|
| 可配置选项 默认值, 描述
|
| aggressiveLazyLoading true, 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。
| multipleResultSetsEnabled true, 允许和不允许单条语句返回多个数据集(取决于驱动需求)
| useColumnLabel true, 使用列标签代替列名称。不同的驱动器有不同的作法。参考一下驱动器文档,或者用这两个不同的选项进行测试一下。
| useGeneratedKeys false, 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。
| autoMappingBehavior PARTIAL, 指定MyBatis 是否并且如何来自动映射数据表字段与对象的属性。PARTIAL将只自动映射简单的,没有嵌套的结果。FULL 将自动映射所有复杂的结果。
| defaultExecutorType SIMPLE, 配置和设定执行器,SIMPLE 执行器执行其它语句。REUSE 执行器可能重复使用prepared statements 语句,BATCH执行器可以重复执行语句和批量更新。
| defaultStatementTimeout null, 设置一个时限,以决定让驱动器等待数据库回应的多长时间为超时
| -->
<settings>
<!-- 这个配置使全局的映射器启用或禁用缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="defaultStatementTimeout" value="25000"/>
</settings>
</configuration>
dataSource.xml
<?xml version="1.0" encoding="UTF-8"?>
<tenants>
<tenant>
<!-- 租户,租户域名(项目中必须唯一)-->
<tenantKey>www.guorucheng.com;app.guorucheng.com</tenantKey>
<dataSource>
<url><![CDATA[jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true]]></url><!-- 必须的 -->
<username>root</username><!-- 必须的 -->
<password>root</password><!-- 必须的 -->
<initialSize>0</initialSize><!-- 初始化连接大小 -->
<maxActive>5</maxActive><!-- 连接池最大使用连接数量 -->
<minIdle>0</minIdle><!-- 连接池最小空闲 -->
<maxWait>60000</maxWait><!-- 获取连接最大等待时间 -->
<validationQuery>SELECT 1</validationQuery>
<testOnBorrow>false</testOnBorrow>
<testOnReturn>false</testOnReturn>
<testWhileIdle>true</testWhileIdle>
<timeBetweenEvictionRunsMillis>60000</timeBetweenEvictionRunsMillis><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<minEvictableIdleTimeMillis>25200000</minEvictableIdleTimeMillis><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
</dataSource>
</tenant>
<tenant>
<!-- 租户,租户域名(项目中必须唯一) -->
<tenantKey>beijing.guorucheng.com;app.beijing.guorucheng.com</tenantKey>
<dataSource>
<url><![CDATA[jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true]]></url><!-- 必须的 -->
<username>root</username><!-- 必须的 -->
<password>root</password><!-- 必须的 -->
<initialSize>0</initialSize><!-- 初始化连接大小 -->
<maxActive>5</maxActive><!-- 连接池最大使用连接数量 -->
<minIdle>0</minIdle><!-- 连接池最小空闲 -->
<maxWait>60000</maxWait><!-- 获取连接最大等待时间 -->
<validationQuery>SELECT 1</validationQuery>
<testOnBorrow>false</testOnBorrow>
<testOnReturn>false</testOnReturn>
<testWhileIdle>true</testWhileIdle>
<timeBetweenEvictionRunsMillis>60000</timeBetweenEvictionRunsMillis><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<minEvictableIdleTimeMillis>25200000</minEvictableIdleTimeMillis><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
</dataSource>
</tenant>
</tenants>
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"
id="WebApp_ID" version="3.0" >
<!-- 加载Spring配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- 字符集 过滤器 -->
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring监听器 -->
<listener>
<description>Spring监听器</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 防止Spring内存溢出监听器 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- Spring MVC -->
<servlet>
<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:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Session超时时间 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guorucheng</groupId>
<artifactId>DynamicDataSource</artifactId>
<version>0.0.1-GRC</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.2.5.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<druid.version>1.1.0</druid.version>
<fastjson.version>1.2.8</fastjson.version>
<mybaitsplus.version>2.1-gamma</mybaitsplus.version>
<velocity.version>1.7</velocity.version>
<mysql.version>5.1.38</mysql.version>
<log4j.version>1.2.17</log4j.version>
<slf4j.version>1.7.19</slf4j.version>
<aspectjweaver.version>1.8.8</aspectjweaver.version>
<fileupload.version>1.3.1</fileupload.version>
<jstl.version>1.2</jstl.version>
<dom4j.version>1.6.1</dom4j.version>
<freemarker.version>2.3.26-incubating</freemarker.version>
</properties>
<dependencies>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemarker.version}</version>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<!-- FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${fileupload.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!-- Mybatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybaitsplus.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- Log -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<build>
<finalName>DynamicDataSource</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
下载项目部署和mybatis-plus简介
# Mybatis-Plus DynamicDataSource
运行方法:
1. 建立数据库,导入SQL(在resources的sql中)。
2. 引入本Maven项目,修改数据库配置文件。
3. 添加Web服务器,运行。
4. 修改localhost解析
---127.0.0.1 www.guorucheng.com
---127.0.0.1 app.guorucheng.com
---127.0.0.1 beijing.guorucheng.com
---127.0.0.1 app.beijing.guorucheng.com
5.浏览器地址栏访问:
http://www.guorucheng.com:8080/DynamicDataSource/sysUser/findSysUsers
http://app.guorucheng.com:8080/DynamicDataSource/sysUser/findSysUsers
http://beijing.guorucheng.com:8080/DynamicDataSource/sysUser/findSysUsers
http://app.beijing.guorucheng.com:8080/DynamicDataSource/sysUser/findSysUsers
查看数据库切换效果。
> 为简化开发工作、提高生产率而生
# 简介 | Intro
Mybatis 增强工具包 - 只做增强不做改变,简化`CRUD`操作
# 优点 | Advantages
- **纯正血统**:完全继承原生 `Mybatis` 的所有特性
- **最少依赖**:仅仅依赖`Mybatis`以及`Mybatis-Spring`
- **性能损耗小**:启动即会自动注入基本CURD ,性能无损耗,直接面向对象操作
- **自动热加载**:Mapper对应的xml可以热加载,大大减少重启Web服务器时间,提升开发效率
- **自动生成代码**:包含自动生成代码类以及Maven插件,通过少量配置,即可快速生成Mybatis对应的xml、mapper、entity、service、serviceimpl层代码,减少开发时间
- **自定义操作**:支持自定义Sql注入,实现个性化操作
- **自定义转义规则**:支持数据库关键词(例如:`order`、`key`等)自动转义,支持自定义关键词
- **多种主键策略**:支持多达4种主键策略,可自由配置,若无将会自动填充,更有充满黑科技的`分布式全局唯一ID生成器`
- **无缝分页插件**:基于Mybatis物理分页,无需关心具体操作,等同于编写基本`selectList`查询
- **性能分析**:自带Sql性能分析插件,开发测试时,能有效解决慢查询
- **全局拦截**:提供全表`delete`、`update`操作智能分析阻断
- **避免Sql注入**:内置Sql注入内容剥离器,预防Sql注入攻击
# 文档 | Documentation
[中文](http://mp.baomidou.com/) | [English](http://mp.baomidou.com/en/)
# 原理 | Principle
[Mybatis-Plus 实践及架构原理](http://git.oschina.net/baomidou/mybatis-plus/attach_files)