- 浏览: 84758 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
wishjlucky:
分析得很详细
ognl.OgnlException: target is null for setProperty(null, "description", [Ljava.l -
onlydo:
只看到发送邮件,收邮件在哪里?
Spring邮件收发
Spring动态切换数据库
首先定义一个AbstractRoutingDataSource,Spring给我们留了这样的接口,让我们方便的定义怎么切换数据源:
public class DynamicDataSource extends AbstractRoutingDataSource { Logger logger = Logger.getAnonymousLogger(); @Override protected Object determineCurrentLookupKey() { String p = "a"; try{ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); p = request.getParameter("dataSource"); }catch(Exception e){ e.printStackTrace(); } if(p==null){ p="a"; } return p; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return logger; } }
可以通过参数来确定到底使用哪个数据源,总的来说,你可以在你自己的AbstractRoutingDataSource中获取到HttpServletRequest,剩下的就是你自己发挥了。代码里对异常进行了catch,因为这里可能会抛No thread-bound request found的异常,具体什么原因不确定,但是等系统跑起来之后(也就是真的从web访问的时候)可以正常运行。
下面是Spring对sessionFactory的配置:
<bean id="faceDataSource" class="test.MyRoutingDataSource"> <property name="targetDataSources"> <map> <entry key="a" value-ref="dataSource1" /> <entry key="b" value-ref="dataSource2" /> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean> <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName1}" /> <property name="jdbcUrl" value="${jdbc.url1}" /> <property name="user" value="${jdbc.username1}" /> <property name="password" value="${jdbc.password1}" /> <property name="preferredTestQuery" value="${preferredTestQuery}" /> <property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}" /> <property name="testConnectionOnCheckout" value="${testConnectionOnCheckout}" /> </bean> <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName2}" /> <property name="jdbcUrl" value="${jdbc.url2}" /> <property name="user" value="${jdbc.username2}" /> <property name="password" value="${jdbc.password2}" /> <property name="preferredTestQuery" value="${preferredTestQuery}" /> <property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}" /> <property name="testConnectionOnCheckout" value="${testConnectionOnCheckout}" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="faceDataSource"/> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <prop key="hibernate.max_fetch_depth">${hibernate.maxFetchDepth}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop> <prop key="hibernate.cache.use_query_cache">${cache.use_query_cache}</prop> <prop key="hibernate.cache.use_second_level_cache">${cache.use_second_level_cache}</prop> <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop> <prop key="hibernate.temp.use_jdbc_metadata_defaults">${hibernate.temp.use_jdbc_metadata_defaults}</prop> </props> </property> <property name="packagesToScan" value="com.novots.bros.*.entity"/> </bean>
到目前为止,在几个静态的数据库之间切换已经没有问题了。
没多久,客户又提出,数据库可能是动态增加进来的。而不是想上面那样固定的几个数据库。
几番查找,找到了一个可以动态增加数据源的东西:
<bean id="dynamicBeanReader" class="cn.chinacti.crm.dynamicdatasource.entity.DynamicBeanReaderImpl" init-method="init"> </bean>此一段,将引出很多故事:
public class DynamicBeanReaderImpl implements DynamicBeanReader,ApplicationContextAware{ private static final Log logger = LogFactory.getLog(DynamicBeanReaderImpl.class);//日记 private ConfigurableApplicationContext applicationContext = null; private XmlBeanDefinitionReader beanDefinitionReader; /*初始化方法*/ public void init(){ beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) applicationContext.getBeanFactory()); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(applicationContext)); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (ConfigurableApplicationContext)applicationContext; } public void loadBean(DynamicBean dynamicBean){ long startTime = System.currentTimeMillis(); String beanName = dynamicBean.getBeanName(); if(applicationContext.containsBean(beanName)){ logger.warn("bean【"+beanName+"】已经加载!"); return; } beanDefinitionReader.loadBeanDefinitions(new DynamicResource(dynamicBean)); logger.info("初始化bean【"+dynamicBean.getBeanName()+"】耗时"+(System.currentTimeMillis()-startTime)+"毫秒。"); } }这里利用ApplicationContextAware创建一个beanDefinitionReader,通过beanDefinitionReader可以动态加载loadbean对象到application context中。beanDefinitionReader需要一个Resource对象:
public class DynamicResource implements Resource { private DynamicBean dynamicBean; public DynamicResource(DynamicBean dynamicBean){ this.dynamicBean = dynamicBean; } /* (non-Javadoc) * @see org.springframework.core.io.InputStreamSource#getInputStream() */ public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(dynamicBean.getXml().getBytes("UTF-8")); }DynamicBean的功能无非是组装bean的xml定义字符串:
/** * 动态bean描述对象 */ public abstract class DynamicBean { protected String beanName; public DynamicBean(String beanName) { this.beanName = beanName; } public String getBeanName() { return beanName; } public void setBeanName(String beanName) { this.beanName = beanName; } /** * 获取bean 的xml描述 * @return */ protected abstract String getBeanXml(); /** * 生成完整的xml字符串 * @return */ public String getXml(){ StringBuffer buf = new StringBuffer(); buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") .append("<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"") .append(" xmlns:p=\"http://www.springframework.org/schema/p\" xmlns:aop=\"http://www.springframework.org/schema/aop\"") .append(" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:jee=\"http://www.springframework.org/schema/jee\"") .append(" xmlns:tx=\"http://www.springframework.org/schema/tx\"") .append(" xsi:schemaLocation=\"") .append(" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd") .append(" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd") .append(" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd") .append(" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd") .append(" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd\">") .append(getBeanXml()) .append("</beans>"); System.out.println(getBeanXml()); return buf.toString(); } }
public class DataSourceDynamicBean extends DynamicBean { private String driverClassName; private String url; private String username; private String password; private String preferredTestQuery; private String idleConnectionTestPeriod; private String testConnectionOnCheckout; private String testConnectionOnCheckin; public DataSourceDynamicBean(String beanName) { super(beanName); } /* (non-Javadoc) * @see org.youi.common.bean.DynamicBean#getBeanXml() */ @Override protected String getBeanXml() { StringBuffer xmlBuf = new StringBuffer(); xmlBuf.append("<bean id=\"").append(beanName).append("\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" destroy-method=\"close\">"); xmlBuf.append("<property name=\"driverClass\" value=\"").append(driverClassName).append("\" />"); xmlBuf.append("<property name=\"jdbcUrl\" value=\"").append(url).append("\"/>"); xmlBuf.append("<property name=\"user\" value=\"").append(username).append("\"/>"); xmlBuf.append("<property name=\"password\" value=\"").append(password).append("\" />"); xmlBuf.append("<property name=\"preferredTestQuery\" value=\"").append(preferredTestQuery).append("\"/>"); xmlBuf.append("<property name=\"idleConnectionTestPeriod\" value=\"").append(idleConnectionTestPeriod).append("\"/>"); xmlBuf.append("<property name=\"testConnectionOnCheckout\" value=\"").append(testConnectionOnCheckout).append("\"/>"); xmlBuf.append("<property name=\"testConnectionOnCheckin\" value=\"").append(testConnectionOnCheckin).append("\"/>"); xmlBuf.append("</bean>"); /*xmlBuf.append("<bean id=\""+beanName+"\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" destroy-method=\"close\"") .append(" p:driverClassName=\""+driverClassName+"\" ") .append(" p:url=\""+url+"\"") .append(" p:username=\""+username+"\"") .append(" p:password=\""+password+"\"/>");*/ return xmlBuf.toString(); } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } 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 getPreferredTestQuery() { return preferredTestQuery; } public void setPreferredTestQuery(String preferredTestQuery) { this.preferredTestQuery = preferredTestQuery; } public String getIdleConnectionTestPeriod() { return idleConnectionTestPeriod; } public void setIdleConnectionTestPeriod(String idleConnectionTestPeriod) { this.idleConnectionTestPeriod = idleConnectionTestPeriod; } public String getTestConnectionOnCheckout() { return testConnectionOnCheckout; } public void setTestConnectionOnCheckout(String testConnectionOnCheckout) { this.testConnectionOnCheckout = testConnectionOnCheckout; } public String getTestConnectionOnCheckin() { return testConnectionOnCheckin; } public void setTestConnectionOnCheckin(String testConnectionOnCheckin) { this.testConnectionOnCheckin = testConnectionOnCheckin; } }这个数据源默认使用的是c3p0,你可以根据自己的喜好修改。
OK,这个故事从这里就到头了。
回到故事的开始,谁来使用这个dynamicBeanReader呢?答案是最开始看到的DynamicDataSource,思路是,我需要用这个reader动态修改这个faceDatasources中的datasource Map,这个Map刚开始可能是空的:
<bean class="cn.chinacti.crm.dynamicdatasource.DynamicDataSource" id="faceDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- <entry value-ref="defaultLocalhost" key="crmDataSource"></entry> --> </map> </property> <property name="dynamicBeanReader" ref="dynamicBeanReader"></property> <property name="defaultTargetDataSource" ref="crmDataSource"></property> <property name="dbName" value="${jdbc.common.dbName}"/> <property name="port" value="${jdbc.common.port}"/> <property name="pwd" value="${jdbc.common.pwd}"/> <property name="userName" value="${jdbc.common.user}"/> <property name="preferredTestQuery" value="${preferredTestQuery}"/> <property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}"/> <property name="testConnectionOnCheckout" value="${testConnectionOnCheckout}"/> <property name="testConnectionOnCheckin" value="${testConnectionOnCheckin}"/> </bean>问题是AbstractRoutingDataSource貌似没有提供直接访问内置datasource map的功能,那么只能通过反射来搞了:
public class DynamicDataSource extends AbstractRoutingDataSource { Logger logger = Logger.getAnonymousLogger(); private DynamicBeanReader dynamicBeanReader; private String dbName; private String port; private String userName; private String pwd; private String preferredTestQuery; private String idleConnectionTestPeriod; private String testConnectionOnCheckout; private String testConnectionOnCheckin; @Override protected Object determineCurrentLookupKey() { String ip = null; String dsName = null; try{ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); ip = (String)request.getSession().getAttribute("ip"); if(ip==null ){ return null; } dsName = createDSNameByIp(ip); ServletContext sc = request.getSession().getServletContext(); DataSource ds = getByDSName(dsName,sc); logger.info("get datasource by name :"+ds); logger.info("ip:"+ip); if(ds==null){//还没创建 ds = createNewDataSource(ip, dbName, port, userName, pwd,sc); addToTargetDataSources(ds,dsName); } }catch(Exception e){ e.printStackTrace(); logger.info(e.getMessage()); } return dsName; } private void addToTargetDataSources(DataSource ds, String dsName) { Class clazz = AbstractRoutingDataSource.class; Field targetDataSourcesField; try { targetDataSourcesField = clazz.getDeclaredField("resolvedDataSources"); targetDataSourcesField.setAccessible(true); Map<Object,Object> targetDataSources = (Map<Object, Object>) targetDataSourcesField.get(this); targetDataSources.put(dsName, ds); targetDataSourcesField.set(this, targetDataSources); } catch (Exception e) { logger.info("修改resolvedDataSources时报错了"); e.printStackTrace(); return; } logger.info("add datasource to resolvedDataSources success."); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return logger; } @Override public Connection getConnection() throws SQLException { Connection conn = super.getConnection(); return conn; } private DataSource createNewDataSource(String ip,String dbName,String port,String user,String pwd,ServletContext sc){ String beanName = createDSNameByIp(ip); DataSourceDynamicBean dataSourceDynamicBean = new DataSourceDynamicBean(beanName); dataSourceDynamicBean.setDriverClassName("com.mysql.jdbc.Driver"); dataSourceDynamicBean.setUrl("jdbc:mysql://"+ip+":"+port+"/"+dbName+"?useUnicode=true&characterEncoding=utf-8"); dataSourceDynamicBean.setUsername(user); dataSourceDynamicBean.setPassword(pwd); dataSourceDynamicBean.setPreferredTestQuery(this.preferredTestQuery); dataSourceDynamicBean.setIdleConnectionTestPeriod(this.idleConnectionTestPeriod); dataSourceDynamicBean.setTestConnectionOnCheckin(this.testConnectionOnCheckin); dataSourceDynamicBean.setTestConnectionOnCheckout(this.testConnectionOnCheckout); dynamicBeanReader.loadBean(dataSourceDynamicBean);//动态记载dataSource logger.info("create datasource:beanName="+beanName+"ip="+ip+""); return (DataSource) getApplicationContext(sc).getBean(beanName); } private String createDSNameByIp(String ip){ return "ds"+ip.replaceAll("\\.", "") ; } private DataSource getByDSName(String dsName,ServletContext sc){ try{ return (DataSource) getApplicationContext(sc).getBean(dsName); }catch(NoSuchBeanDefinitionException e){ return null; } } private ApplicationContext getApplicationContext(ServletContext sc){ return WebApplicationContextUtils.getWebApplicationContext(sc); } public DynamicBeanReader getDynamicBeanReader() { return dynamicBeanReader; } public void setDynamicBeanReader(DynamicBeanReader dynamicBeanReader) { this.dynamicBeanReader = dynamicBeanReader; } public String getDbName() { return dbName; } public void setDbName(String dbName) { this.dbName = dbName; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String getPreferredTestQuery() { return preferredTestQuery; } public void setPreferredTestQuery(String preferredTestQuery) { this.preferredTestQuery = preferredTestQuery; } public String getIdleConnectionTestPeriod() { return idleConnectionTestPeriod; } public void setIdleConnectionTestPeriod(String idleConnectionTestPeriod) { this.idleConnectionTestPeriod = idleConnectionTestPeriod; } public String getTestConnectionOnCheckout() { return testConnectionOnCheckout; } public void setTestConnectionOnCheckout(String testConnectionOnCheckout) { this.testConnectionOnCheckout = testConnectionOnCheckout; } public String getTestConnectionOnCheckin() { return testConnectionOnCheckin; } public void setTestConnectionOnCheckin(String testConnectionOnCheckin) { this.testConnectionOnCheckin = testConnectionOnCheckin; } }原理很简单,AbstractRoutingDataSource本来只是提供了一个口子,说你来决定到底用哪个Key去查找我的Map中的数据源。结果呢,我在查找这个Key的途中,借机使用dynamicBeanReader新建了一个dataSource并成功放到了AbstractRoutingDataSource的datasouce map中(当然,先查查有没有,有就不用创建了)。之后,在返回给AbstractRoutingDataSource一个Key的同时,我确定一定能拿到我想要的dataSource了。
遗留问题:
在系统启动的时候,根本没有web环境,所以根据前台参数来决定用哪一个库无从谈起。并且faceDataSource必须有一个默认数据源,也就是在系统启动的时候,系统要求一定能从faceDataSource中拿到一个确切的数据源。否则就会抛出异常,这和本身的业务逻辑是相违背的。
相关推荐
在Spring框架中,动态切换数据库是一项重要的功能,它允许应用程序根据特定条件或需求在运行时灵活地连接到不同的数据库。这种能力在多租户环境、数据隔离或测试环境中尤其有用。接下来,我们将深入探讨实现这一功能...
总的来说,Spring Cloud多数据源整合和动态切换数据库是一项挑战性的工作,但通过合理的架构设计和Spring框架的特性,可以有效地实现这一功能,为大型分布式系统提供灵活和可扩展的数据访问能力。
总之,Spring的数据库动态切换功能通过抽象化DataSource路由,实现了在运行时根据业务逻辑或环境条件灵活切换数据库的能力。这一特性大大增强了应用程序的灵活性和可扩展性。通过深入学习和实践,你可以将其应用于...
NULL 博文链接:https://qdjinxin.iteye.com/blog/364572
springboot整合redis动态切换每个数据库,
然而,在某些情况下,我们可能需要在运行时根据不同的条件或需求动态地切换数据库,比如在测试环境和生产环境中使用不同的数据库。这个过程就称为“动态切换数据库”。在本文中,我们将深入探讨如何在Java Hibernate...
在Spring MVC和Spring Data JPA框架中,实现多数据库动态切换是一项常见的需求,尤其是在大型分布式系统中。这个压缩包提供了一个完整的示例(demo),帮助开发者理解和应用这一功能。下面将详细讲解如何在Spring...
数据库的动态切换在很多项目当中都有应用,经我查阅了多篇文档,整合思路最终成功实现数据源的动态切换功能,并稳定运行了一段时间未发现异常。 我的数据源切换时根据域名并配合spring来切换的,不同的域名访问...
在进行JEECG切换数据库时,支持多种主流数据库系统,例如ORACLE、MySQL和Microsoft SQL Server等。下面将详细阐述JEECG平台切换数据库的具体操作步骤及其背后涉及的相关知识点。 首先,了解数据库配置文件的重要性...
在Spring Boot应用中,数据库连接动态切换是一种常见的需求,尤其在多租户或者有不同业务场景的系统中。本项目提供了实现这一功能的源代码,允许根据不同的用户操作或HTTP请求来选择不同的数据库,而无需修改...
在开发多租户或多数据库环境的应用系统时,往往需要实现动态切换数据库连接的功能。本文档将详细探讨如何在Spring与Hibernate框架下实现数据库连接的动态切换。该功能允许用户在登录时选择或输入特定的数据库名称,...
"Spring动态切换多数据源Demo"是一个示例项目,它展示了如何在Spring中实现灵活的数据源切换。 首先,我们需要了解Spring中的数据源。在Spring中,数据源(DataSource)是Java中定义的javax.sql.DataSource接口的...
本文将深入探讨“真正意义的Spring动态切换数据源”这一主题,并结合源码进行分析。 动态数据源切换是在多数据库环境下的常见需求,例如在测试和生产环境中使用不同的数据库,或者在微服务架构中,每个服务可能需要...
通过Spring的动态数据源切换,我们可以构建出更加灵活、高效的分布式数据库系统。这种设计模式不仅适用于多主多从架构,也可以应用于其他需要动态选择数据源的场景,如按区域、环境或服务划分的数据源。然而,实施时...
完整的Demo结合了springmvc——mybatis,实现了工具类文件上传下载,结合了Redis的初步使用,并且能使用threadlocal实现数据库动态切换,很适合初建项目做参考,适合初学者使用。
在实际项目中,我们可能需要根据特定的条件动态切换数据库。这时,可以创建一个工具类,提供一个方法接收数据库编号,并返回对应的`RedisTemplate`: ```java @Component public class RedisUtil { @Autowired ...
在IT行业中,构建大型分布式系统时,数据源的动态切换是一项关键能力,它允许系统根据业务需求选择不同的数据库进行操作。本项目“Spring+SpringMvc+MybatisPlus+Aop(自定义注解)动态切换数据源”正是针对这一需求...
在Java的Spring框架中,动态切换数据源是一项重要的功能,尤其在多租户、微服务或者需要根据业务逻辑切换数据库的场景下。本知识点主要围绕如何在Spring中实现数据源的动态切换进行深入探讨。 首先,我们需要理解...
在Spring框架中,动态切换数据源是一项重要的功能,它允许应用程序根据业务需求在多个数据库之间灵活切换。这一特性对于多租户系统、读写分离、分布式数据库等场景尤其有用。以下将详细介绍如何实现Spring的动态数据...
在企业级应用开发中,动态数据源是一种常见需求,它允许程序在运行时根据不同的业务逻辑切换到不同的数据库。在本项目中,我们将探讨如何利用MyBatis与Spring框架实现动态切换数据源的功能。首先,我们需要理解...