论坛首页 Java企业应用论坛

动态切换多数据源

浏览 47931 次
该帖已经被评为良好帖
作者 正文
   发表时间:2006-07-22  
一个实现,运行通过,不知道是否有其他问题。
<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
	"http://wwww.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="placeholderConfig"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>springwork/core/aop/targetsource/init.properties</value>
		</property>
	</bean>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url1}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="swappableDataSource"
		class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg>
			<ref local="dataSource"/>
		</constructor-arg>
	</bean>
	<bean id="swappable"
		class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource">
			<ref local="swappableDataSource"/>
		</property>
	</bean>
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource">
			<ref local="swappable"/>
		</property>
		<property name="mappingResources">
			<list>
				<value>springwork/core/aop/targetsource/Cat.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect"> ${hibernate.dialect} </prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.generate_statistics">true</prop>
				<prop key="hibernate.cache.provider_class">
					net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
			</props>
		</property>
	</bean>
	
	<bean id="catDAO" class="springwork.core.aop.targetsource.CatDAO">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>
	

	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>

	<!--
	<bean id="transactionManager"
		class="org.springframework.transaction.jta.JtaTransactionManager">
	</bean>
	-->
	
	<!-- Transaction Interceptor -->
	<bean id="transactionInterceptor"
		class="org.springframework.transaction.interceptor.TransactionInterceptor"
		dependency-check="none">
		<property name="transactionManager">
			<ref bean="transactionManager"/>
		</property>
		<property name="transactionAttributes">
			<props>
				<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
				<prop key="save*">PROPAGATION_REQUIRED</prop>
				<prop key="get*">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>
	<bean id="autoProxyCreator"
		class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
		dependency-check="none">
		<property name="proxyTargetClass">
			<value>true</value>
		</property>
		<property name="interceptorNames">
			<list>
				<value>transactionInterceptor</value>
			</list>
		</property>
		<property name="beanNames">
			<list>
				<value>catDAO</value>
			</list>
		</property>
	</bean>
</beans>



	public void testWithTwoDataSource();
	{
		Cat cat=new Cat("mimi");;
		catDAO.save(cat);;
		Assert.assertNotNull(cat.getId(););;
		Cat cat2=new Cat("xixi");;
		HotSwappableTargetSource swapper=(HotSwappableTargetSource); application.getBean("swappableDataSource");;
		DataSource newDataSource=(DataSource);application.getBean("dataSource1");;
		swapper.swap(newDataSource);;
		catDAO.save(cat2);;
		Assert.assertNotNull(cat2.getId(););;
	}


btw:可以配置多个SessionFactory,使用JTA处理你的事务问题,
         如果你需要跨数据库支持事务的话这样是必须的。
0 请登录后投票
   发表时间:2006-07-22  
TO: knight6892
谢谢你的回覆,我觉得你的方法和代码应该是目前动态数据源在spring中最好的实现了.
不过不知道数据源在切换中性能是否有影响,本来按照我的想法,sessionFactory是有多个,然后动态切换sessionFactory.但也许不可行,目前我使用自己的方法测试过查询数据,没有测试有关事务方面的操作.估计是不行.

接下来使用你的方法,进行测试,稍后会继续发贴.
0 请登录后投票
   发表时间:2006-07-24  
1。在只有一个数据源的时候,使用 spring + hibernate 的情况下,通常在程序中是存在一个 spring 的 beanfactory 的,而且通常这个 beanfactory 还是由 spring 提供的 servlet 自动创建的。通常会在 web 层的 action 里面通过这个 beanfactory 获得 BO,然后调用 BO 来实现相关的功能。

2。在有多个数据源的时候,需要为每个数据源生成一个 beanfactory 。这个目前可能无法通过 spring 提供的 servlet 来自动实现,需要自己手工来操作。这些 beanfactory 使用同样的 spring xml 配置文件,但是使用不同的数据库配置文件。手工操作的代码类似:

ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext( contextFile, false );;
Properties props = new Properties();;
props.load( new FileInputStream( propFile ); );;
PropertyPlaceholderConfigurer configurer = new  PropertyPlaceholderConfigurer();;
configurer.setProperties(  props );;
factory.addBeanFactoryPostProcessor( configurer );;
factory.refresh();;


对应每一个数据源,在程序启动的时候生成一个 factory ,然后可以为每个 factory 和数据源标识对应起来保存在 hashmap 之类的列表中,以方便后面使用。

3。web 层的 action 不能再在 spring 的配置文件中出现了。在 web 层的 action 里面都实现一个 BeanFactoryAware 的 Interface 。

public interface BeanFactoryAware {
  public void setBeanFactory( BeanFactory factory );;
}


在这个 setBeanFactory 函数里面,每个 action 主动从 beanfactory 里面获得自己想要的 BO 。

用户选择的数据源信息可以记录在 session 里面,然后针对 BeanFactoryAware 接口写一个 interceptor ,从 session 里面取得用户的设置,再根据用户的设置取得对应的 BeanFactory ,然后调用 action 的 setBeanFactory 函数。

4。这几个 beanfactory 是相互独立的,不会相互干扰。假设
    数据源 A ---》 bfA
    数据源 B ---》 bfB

   从 bfA 中得到的所有对象都是和数据源 A 相关的,从 bfB 中得到的所有对象都是和数据源 B 相关的。

5。这种做法的缺点是在 action 中,BO 无法再使用 IoC 的方式来注入,而是要自己主动注入。当然,在 setBeanFactory 函数里面,也可以显式地调用 beanfactory 和 this 来做到 IoC 。

补充:写完之后,忽然想到,其实可以在 interceptor 里面来做 IoC 的操作,这个 BeanFactoryAware  可以根本不需要有任何方法,只是作为一个标识接口就可以了。具体还没试过,之前做过多数据源的项目,用的是上面的做法。
0 请登录后投票
   发表时间:2006-11-14  
knight6892 写道
一个实现,运行通过,不知道是否有其他问题。
<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
	"http://wwww.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="placeholderConfig"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>springwork/core/aop/targetsource/init.properties</value>
		</property>
	</bean>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url1}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="swappableDataSource"
		class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg>
			<ref local="dataSource"/>
		</constructor-arg>
	</bean>
	<bean id="swappable"
		class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource">
			<ref local="swappableDataSource"/>
		</property>
	</bean>
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource">
			<ref local="swappable"/>
		</property>
		<property name="mappingResources">
			<list>
				<value>springwork/core/aop/targetsource/Cat.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect"> ${hibernate.dialect} </prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.generate_statistics">true</prop>
				<prop key="hibernate.cache.provider_class">
					net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
			</props>
		</property>
	</bean>
	
	<bean id="catDAO" class="springwork.core.aop.targetsource.CatDAO">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>
	

	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>

	<!--
	<bean id="transactionManager"
		class="org.springframework.transaction.jta.JtaTransactionManager">
	</bean>
	-->
	
	<!-- Transaction Interceptor -->
	<bean id="transactionInterceptor"
		class="org.springframework.transaction.interceptor.TransactionInterceptor"
		dependency-check="none">
		<property name="transactionManager">
			<ref bean="transactionManager"/>
		</property>
		<property name="transactionAttributes">
			<props>
				<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
				<prop key="save*">PROPAGATION_REQUIRED</prop>
				<prop key="get*">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>
	<bean id="autoProxyCreator"
		class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
		dependency-check="none">
		<property name="proxyTargetClass">
			<value>true</value>
		</property>
		<property name="interceptorNames">
			<list>
				<value>transactionInterceptor</value>
			</list>
		</property>
		<property name="beanNames">
			<list>
				<value>catDAO</value>
			</list>
		</property>
	</bean>
</beans>



	public void testWithTwoDataSource()
	{
		Cat cat=new Cat("mimi");
		catDAO.save(cat);
		Assert.assertNotNull(cat.getId());
		Cat cat2=new Cat("xixi");
		HotSwappableTargetSource swapper=(HotSwappableTargetSource) application.getBean("swappableDataSource");
		DataSource newDataSource=(DataSource)application.getBean("dataSource1");
		swapper.swap(newDataSource);
		catDAO.save(cat2);
		Assert.assertNotNull(cat2.getId());
	}


btw:可以配置多个SessionFactory,使用JTA处理你的事务问题,
         如果你需要跨数据库支持事务的话这样是必须的。

数据源用JDNI的话,就不行
0 请登录后投票
   发表时间:2006-11-16  
hibernate-Statistics + MX4J + Spring + JNDI

And you just do nothing for your requirement
0 请登录后投票
   发表时间:2006-12-07  
To knight6892: 请问你的例子中使用同一个SessionFacotry,是否存在二级缓存的问题?
0 请登录后投票
   发表时间:2006-12-07  
ben_nb 写道
knight6892 写道
一个实现,运行通过,不知道是否有其他问题。
<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
	"http://wwww.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="placeholderConfig"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>springwork/core/aop/targetsource/init.properties</value>
		</property>
	</bean>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName">
			<value>${datasource.driverClassName}</value>
		</property>
		<property name="url">
			<value>${datasource.url1}</value>
		</property>
		<property name="username">
			<value>${datasource.username}</value>
		</property>
		<property name="password">
			<value>${datasource.password}</value>
		</property>
	</bean>
	
	<bean id="swappableDataSource"
		class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg>
			<ref local="dataSource"/>
		</constructor-arg>
	</bean>
	<bean id="swappable"
		class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource">
			<ref local="swappableDataSource"/>
		</property>
	</bean>
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource">
			<ref local="swappable"/>
		</property>
		<property name="mappingResources">
			<list>
				<value>springwork/core/aop/targetsource/Cat.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect"> ${hibernate.dialect} </prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.generate_statistics">true</prop>
				<prop key="hibernate.cache.provider_class">
					net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
			</props>
		</property>
	</bean>
	
	<bean id="catDAO" class="springwork.core.aop.targetsource.CatDAO">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>
	

	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory"/>
		</property>
	</bean>

	<!--
	<bean id="transactionManager"
		class="org.springframework.transaction.jta.JtaTransactionManager">
	</bean>
	-->
	
	<!-- Transaction Interceptor -->
	<bean id="transactionInterceptor"
		class="org.springframework.transaction.interceptor.TransactionInterceptor"
		dependency-check="none">
		<property name="transactionManager">
			<ref bean="transactionManager"/>
		</property>
		<property name="transactionAttributes">
			<props>
				<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
				<prop key="save*">PROPAGATION_REQUIRED</prop>
				<prop key="get*">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
	</bean>
	<bean id="autoProxyCreator"
		class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
		dependency-check="none">
		<property name="proxyTargetClass">
			<value>true</value>
		</property>
		<property name="interceptorNames">
			<list>
				<value>transactionInterceptor</value>
			</list>
		</property>
		<property name="beanNames">
			<list>
				<value>catDAO</value>
			</list>
		</property>
	</bean>
</beans>



	public void testWithTwoDataSource()
	{
		Cat cat=new Cat("mimi");
		catDAO.save(cat);
		Assert.assertNotNull(cat.getId());
		Cat cat2=new Cat("xixi");
		HotSwappableTargetSource swapper=(HotSwappableTargetSource) application.getBean("swappableDataSource");
		DataSource newDataSource=(DataSource)application.getBean("dataSource1");
		swapper.swap(newDataSource);
		catDAO.save(cat2);
		Assert.assertNotNull(cat2.getId());
	}


btw:可以配置多个SessionFactory,使用JTA处理你的事务问题,
         如果你需要跨数据库支持事务的话这样是必须的。

数据源用JDNI的话,就不行


可以的,但要强制声明代理接口为 DataSource.
0 请登录后投票
   发表时间:2007-03-10  
还是多数据源的问题
关注这个网站很久了,这里面高人不少。小弟最近碰到个问题,搞了好久也搞不好,所以来请教这里的大侠们。
现在有个网站新闻系统,有4种语言来发布,中、日、韩、英。于是我在后台发布新闻信息的时候,把不同的语言发布到4个数据库里,这4个数据是完全一样的,只是各自记录不同的语言的新闻。
现在的问题是,我需要让来自不同国家的人能同时访问这个网站而互相没有干扰,来自中国的用户,使用的是中文的数据,来自日本的用户,是使用的日文的数据库,也就是说,不同的用户使用自己不同的数据源。
我看到这里已经有人讨论过这样类似的问题,但还是没搞太明白怎么做,主要是不知道如何把不同的数据源放到不同的sessionfacory里,然后让DAO使用使用这些不同的sessionfactory??
我的网站是采用的hibernate+spring,applicationcontext文件配置如下:
<beans>
<!-- Choose the dialect that matches your "dataSource" definition -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
      <value>net.sourceforge.jtds.jdbc.Driver</value>
    </property>
    <property name="url">
      <value>jdbc:jtds:sqlserver://localhost:1433;SelectMethod=cursor;DatabaseName=NPO</value>
    </property>
    <property name="username">
      <value>sa</value>
    </property>
    <property name="password">
      <value>npo1234</value>
    </property>
    <property name="maxActive">
      <value>100</value>
    </property>
    <property name="maxIdle">
      <value>10</value>
    </property>
    <property name="maxWait">
      <value>120000</value>
    </property>
    <property name="defaultAutoCommit">
      <value>true</value>
    </property>
  </bean>
  <bean id="dataSourceko" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
      <value>net.sourceforge.jtds.jdbc.Driver</value>
    </property>
    <property name="url">
      <value>jdbc:jtds:sqlserver://localhost:1433;SelectMethod=cursor;DatabaseName=NPOKR</value>
    </property>
    <property name="username">
      <value>sa</value>
    </property>
    <property name="password">
      <value>npo1234</value>
    </property>
    <property name="maxActive">
      <value>100</value>
    </property>
    <property name="maxIdle">
      <value>10</value>
    </property>
    <property name="maxWait">
      <value>120000</value>
    </property>
    <property name="defaultAutoCommit">
      <value>true</value>
    </property>
  </bean>
    <bean id="dataSourcejp" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
      <value>net.sourceforge.jtds.jdbc.Driver</value>
    </property>
    <property name="url">
      <value>jdbc:jtds:sqlserver://localhost:1433;SelectMethod=cursor;DatabaseName=NPOJP</value>
    </property>
    <property name="username">
      <value>sa</value>
    </property>
    <property name="password">
      <value>npo1234</value>
    </property>
    <property name="maxActive">
      <value>100</value>
    </property>
    <property name="maxIdle">
      <value>10</value>
    </property>
    <property name="maxWait">
      <value>120000</value>
    </property>
    <property name="defaultAutoCommit">
      <value>true</value>
    </property>
  </bean>
    <bean id="dataSourceen" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
      <value>net.sourceforge.jtds.jdbc.Driver</value>
    </property>
    <property name="url">
      <value>jdbc:jtds:sqlserver://localhost:1433;SelectMethod=cursor;DatabaseName=NPOEN</value>
    </property>
    <property name="username">
      <value>sa</value>
    </property>
    <property name="password">
      <value>npo1234</value>
    </property>
    <property name="maxActive">
      <value>100</value>
    </property>
    <property name="maxIdle">
      <value>10</value>
    </property>
    <property name="maxWait">
      <value>120000</value>
    </property>
    <property name="defaultAutoCommit">
      <value>true</value>
    </property>
  </bean>
 
 
      <bean id="swappableDataSource" 
        class="org.springframework.aop.target.HotSwappableTargetSource"> 
        <constructor-arg> 
            <ref local="dataSource"/> 
        </constructor-arg> 
    </bean> 
    <bean id="swappable" 
        class="org.springframework.aop.framework.ProxyFactoryBean"> 
        <property name="targetSource"> 
            <ref local="swappableDataSource"/> 
        </property> 
    </bean>

<bean id="mySessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="swappable"/></property>
<property name="mappingResources">
<list>
<value>jaoso/framework/domain/Role.hbm.xml</value>
<value>jaoso/framework/domain/Right.hbm.xml</value>
<value>jaoso/framework/domain/Account.hbm.xml</value>
<value>jaoso/framework/domain/Person.hbm.xml</value>
<value>jaoso/news/domain/Catalog.hbm.xml</value>
<value>jaoso/news/domain/Article.hbm.xml</value>
<value>jaoso/news/domain/Adv.hbm.xml</value>
<value>jaoso/news/domain/Critic.hbm.xml</value>
<value>jaoso/guestbook/domain/GbCatalog.hbm.xml</value>
<value>jaoso/guestbook/domain/Message.hbm.xml</value>
<value>jaoso/count/domain/Count.hbm.xml</value>
</list>
</property>
</bean>
<!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
<bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean>

<!-- ************************** DAO SETTING *********************************-->
<!-- DAO object: Hibernate implementation -->
<bean id="baseDAO" class="jaoso.framework.dao.hibernate.BaseDAOImpl">
<property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean>

<!-- ************************** Persistent Manager SETTING ******************-->
<bean id="persistentManager" class="jaoso.framework.dao.hibernate.PersistentManagerImpl">
<property name="baseDAO"><ref local="baseDAO"/></property>
</bean>
<!-- ************************** Query Manager SETTING ***********************-->
<bean id="queryManager" class="jaoso.framework.dao.hibernate.QueryManagerImpl">
<property name="baseDAO"><ref local="baseDAO"/></property>
</bean>
</beans>

0 请登录后投票
   发表时间:2007-03-10  
另外有个globe.java文件,加载配置信息:
     */
    private static void init() {

        String path = Global.class.getResource("/spring/applicationContext.xml")
                                  .getPath();
        path = path.substring(1, path.length());

        File file = new File(path);
        File dir = file.getParentFile();
        String[] files = dir.list();

        for (int i = 0, n = files.length; i < n; i++) {

            files[i] = "/spring/" + files[i];
        }

        log.info("Sping starting*******************");
        context = new ClassPathXmlApplicationContext(files);
        log.info("Sping start success*******************");
    }
0 请登录后投票
   发表时间:2007-03-11  
spring2.0.1提供了Dynamic DataSource Routing,http://blog.interface21.com/main/2007/01/23/dynamic-datasource-routing/
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics