Spring+Ibatis数据库水平分库

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 1.引言    笔者最近在做一个互联网的“类SNS”应用,应用中用户数量巨大(约4000万)左右,因此,简单的使用传统单一数据库存储肯定是不行的。

1.引言
   笔者最近在做一个互联网的“类SNS”应用,应用中用户数量巨大(约4000万)左右,因此,简单的使用传统单一数据库存储肯定是不行的。

   参考了业内广泛使用的分库分表,以及使用DAL数据访问层等的做法,笔者决定使用一种最简单的数据源路由选择方式来解决问题。

   严格的说,目前的实现不能算是一个解决方案,只能是一种思路的简易实现,笔者也仅花了2天时间来完成(其中1.5天是在看资料和Spring/ibatis的源码)。这里也只是为各位看官提供一个思路参考,顺便给自己留个笔记

2.系统的设计前提
   我们的系统使用了16个数据库实例(目前分布在2台物理机器上,后期将根据系统负荷的增加,逐步移库到16台物理机器上)。16个库是根据用户的UserID进行简单的hash分配。这里值得一说的是,我们既然做了这样的横向切分设计,就已经考虑了系统需求的特性,

  • 1.不会发生经常性的跨库访问。
  • 2.主要的业务逻辑都是围绕UserID为核心的,在一个单库事务内即可完成。



   在系统中,我们使用Spring和iBatis。Spring负责数据库的事务管理AOP,以及Bean间的IOC。选择iBatis的最大原因是对Sql的性能优化,以及后期如果有分表要求的时,可以很容易实现对sql表名替换。


3.设计思路
   首先,要说明一下笔者的思路,其实很简单,即“在每次数据库操作前,确定当前要选择的数据库对象”而后就如同访问单库一样的访问当前选中的数据库即可。

   其次,要在每次DB访问前选择数据库,需要明确几个问题,1.iBatis在什么时候从DataSource中取得具体的数据库Connection的,2.对取得的Connection,iBatis是否进行缓存,因为在多库情况下Connection被缓存就意味着无法及时改变数据库链接选择。3.由于我们使用了Spring来管理DB事务,因此必须搞清Spring对DB Connction的开关拦截过程是否会影响多DataSource的情况。

   幸运的是,研究源码的结果发现,iBatis和Spring都是通过标准的DataSource接口来控制
Connection的,这就为我们省去了很多的麻烦,只需要实现一个能够支持多个数据库的DataSource,就能达到我们的目标。

4.代码与实现
多数据库的DataSource实现:MultiDataSource.class

Java代码

  

import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.log4j.Logger; import com.xxx.sql.DataSourceRouter.RouterStrategy; /** * 复合多数据源(Alpha) * @author linliangyi2005@gmail.com * Jul 15, 2010 */ public class MultiDataSource implements DataSource { static Logger logger = Logger.getLogger(MultiDataSource.class); //当前线程对应的实际DataSource private ThreadLocal<DataSource> currentDataSourceHolder = new ThreadLocal<DataSource>(); //使用Key-Value映射的DataSource private Map<String , DataSource> mappedDataSources; //使用横向切分的分布式DataSource private ArrayList<DataSource> clusterDataSources; public MultiDataSource(){ mappedDataSources = new HashMap<String , DataSource>(4); clusterDataSources = new ArrayList<DataSource>(4); } /** * 数据库连接池初始化 * 该方法通常在web 应用启动时调用 */ public void initialMultiDataSource(){ for(DataSource ds : clusterDataSources){ if(ds != null){ Connection conn = null; try { conn = ds.getConnection(); } catch (SQLException e) { e.printStackTrace(); } finally{ if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } } } Collection<DataSource> dsCollection = mappedDataSources.values(); for(DataSource ds : dsCollection){ if(ds != null){ Connection conn = null; try { conn = ds.getConnection(); } catch (SQLException e) { e.printStackTrace(); } finally{ if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } } } } /** * 获取当前线程绑定的DataSource * @return */ public DataSource getCurrentDataSource() { //如果路由策略存在,且更新过,则根据路由算法选择新的DataSource RouterStrategy strategy = DataSourceRouter.currentRouterStrategy.get(); if(strategy == null){ throw new IllegalArgumentException("DataSource RouterStrategy No found."); } if(strategy != null && strategy.isRefresh()){ if(RouterStrategy.SRATEGY_TYPE_MAP.equals(strategy.getType())){ this.choiceMappedDataSources(strategy.getKey()); }else if(RouterStrategy.SRATEGY_TYPE_CLUSTER.equals(strategy.getType())){ this.routeClusterDataSources(strategy.getRouteFactor()); } strategy.setRefresh(false); } return currentDataSourceHolder.get(); } public Map<String, DataSource> getMappedDataSources() { return mappedDataSources; } public void setMappedDataSources(Map<String, DataSource> mappedDataSources) { this.mappedDataSources = mappedDataSources; } public ArrayList<DataSource> getClusterDataSources() { return clusterDataSources; } public void setClusterDataSources(ArrayList<DataSource> clusterDataSources) { this.clusterDataSources = clusterDataSources; } /** * 使用Key选择当前的数据源 * @param key */ public void choiceMappedDataSources(String key){ DataSource ds = this.mappedDataSources.get(key); if(ds == null){ throw new IllegalStateException("No Mapped DataSources Exist!"); } this.currentDataSourceHolder.set(ds); } /** * 使用取模算法,在群集数据源中做路由选择 * @param routeFactor */ public void routeClusterDataSources(int routeFactor){ int size = this.clusterDataSources.size(); if(size == 0){ throw new IllegalStateException("No Cluster DataSources Exist!"); } int choosen = routeFactor % size; DataSource ds = this.clusterDataSources.get(choosen); if(ds == null){ throw new IllegalStateException("Choosen DataSources is null!"); } logger.debug("Choosen DataSource No." + choosen+ " : " + ds.toString()); this.currentDataSourceHolder.set(ds); } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getConnection(); } return null; } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) */ public Connection getConnection(String username, String password) throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getConnection(username , password); } return null; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#getLogWriter() */ public PrintWriter getLogWriter() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getLogWriter(); } return null; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#getLoginTimeout() */ public int getLoginTimeout() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getLoginTimeout(); } return 0; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter) */ public void setLogWriter(PrintWriter out) throws SQLException { if(getCurrentDataSource() != null){ getCurrentDataSource().setLogWriter(out); } } /* (non-Javadoc) * @see javax.sql.CommonDataSource#setLoginTimeout(int) */ public void setLoginTimeout(int seconds) throws SQLException { if(getCurrentDataSource() != null){ getCurrentDataSource().setLoginTimeout(seconds); } } /* (non-Javadoc) * 该接口方法since 1.6 * 不是所有的DataSource都实现有这个方法 * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) */ public boolean isWrapperFor(Class<?> iface) throws SQLException { // if(getCurrentDataSource() != null){ // return getCurrentDataSource().isWrapperFor(iface); // } return false; } /* (non-Javadoc) * 该接口方法since 1.6 * 不是所有的DataSource都实现有这个方法 * @see java.sql.Wrapper#unwrap(java.lang.Class) */ public <T> T unwrap(Class<T> iface) throws SQLException { // if(getCurrentDataSource() != null){ // return getCurrentDataSource().unwrap(iface); // } return null; } import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.log4j.Logger; import com.xxx.sql.DataSourceRouter.RouterStrategy; /** * 复合多数据源(Alpha) * @author linliangyi2005@gmail.com * Jul 15, 2010 */ public class MultiDataSource implements DataSource { static Logger logger = Logger.getLogger(MultiDataSource.class); //当前线程对应的实际DataSource private ThreadLocal<DataSource> currentDataSourceHolder = new ThreadLocal<DataSource>(); //使用Key-Value映射的DataSource private Map<String , DataSource> mappedDataSources; //使用横向切分的分布式DataSource private ArrayList<DataSource> clusterDataSources; public MultiDataSource(){ mappedDataSources = new HashMap<String , DataSource>(4); clusterDataSources = new ArrayList<DataSource>(4); } /** * 数据库连接池初始化 * 该方法通常在web 应用启动时调用 */ public void initialMultiDataSource(){ for(DataSource ds : clusterDataSources){ if(ds != null){ Connection conn = null; try { conn = ds.getConnection(); } catch (SQLException e) { e.printStackTrace(); } finally{ if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } } } Collection<DataSource> dsCollection = mappedDataSources.values(); for(DataSource ds : dsCollection){ if(ds != null){ Connection conn = null; try { conn = ds.getConnection(); } catch (SQLException e) { e.printStackTrace(); } finally{ if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } } } } /** * 获取当前线程绑定的DataSource * @return */ public DataSource getCurrentDataSource() { //如果路由策略存在,且更新过,则根据路由算法选择新的DataSource RouterStrategy strategy = DataSourceRouter.currentRouterStrategy.get(); if(strategy == null){ throw new IllegalArgumentException("DataSource RouterStrategy No found."); } if(strategy != null && strategy.isRefresh()){ if(RouterStrategy.SRATEGY_TYPE_MAP.equals(strategy.getType())){ this.choiceMappedDataSources(strategy.getKey()); }else if(RouterStrategy.SRATEGY_TYPE_CLUSTER.equals(strategy.getType())){ this.routeClusterDataSources(strategy.getRouteFactor()); } strategy.setRefresh(false); } return currentDataSourceHolder.get(); } public Map<String, DataSource> getMappedDataSources() { return mappedDataSources; } public void setMappedDataSources(Map<String, DataSource> mappedDataSources) { this.mappedDataSources = mappedDataSources; } public ArrayList<DataSource> getClusterDataSources() { return clusterDataSources; } public void setClusterDataSources(ArrayList<DataSource> clusterDataSources) { this.clusterDataSources = clusterDataSources; } /** * 使用Key选择当前的数据源 * @param key */ public void choiceMappedDataSources(String key){ DataSource ds = this.mappedDataSources.get(key); if(ds == null){ throw new IllegalStateException("No Mapped DataSources Exist!"); } this.currentDataSourceHolder.set(ds); } /** * 使用取模算法,在群集数据源中做路由选择 * @param routeFactor */ public void routeClusterDataSources(int routeFactor){ int size = this.clusterDataSources.size(); if(size == 0){ throw new IllegalStateException("No Cluster DataSources Exist!"); } int choosen = routeFactor % size; DataSource ds = this.clusterDataSources.get(choosen); if(ds == null){ throw new IllegalStateException("Choosen DataSources is null!"); } logger.debug("Choosen DataSource No." + choosen+ " : " + ds.toString()); this.currentDataSourceHolder.set(ds); } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getConnection(); } return null; } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) */ public Connection getConnection(String username, String password) throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getConnection(username , password); } return null; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#getLogWriter() */ public PrintWriter getLogWriter() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getLogWriter(); } return null; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#getLoginTimeout() */ public int getLoginTimeout() throws SQLException { if(getCurrentDataSource() != null){ return getCurrentDataSource().getLoginTimeout(); } return 0; } /* (non-Javadoc) * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter) */ public void setLogWriter(PrintWriter out) throws SQLException { if(getCurrentDataSource() != null){ getCurrentDataSource().setLogWriter(out); } } /* (non-Javadoc) * @see javax.sql.CommonDataSource#setLoginTimeout(int) */ public void setLoginTimeout(int seconds) throws SQLException { if(getCurrentDataSource() != null){ getCurrentDataSource().setLoginTimeout(seconds); } } /* (non-Javadoc) * 该接口方法since 1.6 * 不是所有的DataSource都实现有这个方法 * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) */ public boolean isWrapperFor(Class<?> iface) throws SQLException { // if(getCurrentDataSource() != null){ // return getCurrentDataSource().isWrapperFor(iface); // } return false; } /* (non-Javadoc) * 该接口方法since 1.6 * 不是所有的DataSource都实现有这个方法 * @see java.sql.Wrapper#unwrap(java.lang.Class) */ public <T> T unwrap(Class<T> iface) throws SQLException { // if(getCurrentDataSource() != null){ // return getCurrentDataSource().unwrap(iface); // } return null; } 这个类实现了DataSource的标准接口,而最核心的部分是getConnection()方法的重载。下面具体阐述:

  • 1.实例变量 clusterDataSources 是一个DataSource 的 ArrayList它存储了多个数据库的DataSource实例,我们使用Spring的IOC功能,将多个DataSource注入到这个list中。
  • 2.实例变量 mappedDataSources 是一个DataSource 的Map,它与clusterDataSources 一样用来存储多个数据库的DataSource实例,不同的是,它可以使用key直接获取DataSource。我们一样会使用Spring的IOC功能,将多个DataSource注入到这个Map中。
  • 3.实例变量currentDataSourceHolder ,他是一个ThreadLocal变量,保存与当前线程相关的且已经取得的DataSource实例。这是为了在同一线程中,多次访问同一数据库时,不需要再重新做路由选择。
  • 4.当外部类调用getConnection()方法时,方法将根据上下文的路由规则,从clusterDataSources 或者 mappedDataSources 选择对应DataSource,并返回其中的Connection。


(PS:关于DataSource的路由选择规则,可以根据应用场景的不同,自行设计。笔者这里提供两种简单的思路,1.根据HashCode,在上述例子中可以是UserId,进行取模运算,来定位数据库。2.根据上下文设置的关键字key,从map中选择映射的DataSource)


5.将MultiDataSource与Spring,iBatis结合
    在完成了上述的编码过程后,就是将这个MultiDataSource与现有Spring和iBatis结合起来配置。

STEP 1。配置多个数据源
笔者这里使用了C3P0作为数据库连接池,这一步和标准的Spring配置一样,唯一不同的是,以前只配置一个,现在要配置多个

Xml代码
<!-- jdbc连接池-1-->  

<bean    id="c3p0_dataSource_1"  class="com.mchange.v2.c3p0.ComboPooledDataSource"   destroy-method="close">      

    <property name="driverClass">      

        <value>${jdbc.driverClass}</value>      

    </property>      

   <property name="jdbcUrl">      

        <value>${mysql.url_1}</value>      

       </property>      

    <property name="user">      

       value>${jdbc.username}</value>      

    </property>      

    <property name="password">      

        <value>${jdbc.password}</value>      

    </property>       

    <!--连接池中保留的最小连接数。-->      

   <property name="minPoolSize">      

           <value>${c3p0.minPoolSize}</value>      

       </property>       

    <!--连接池中保留的最大连接数。Default: 15 -->      

       <property name="maxPoolSize">      

        <value>${c3p0.maxPoolSize}</value>      

    </property>      

    <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->      

      <property name="initialPoolSize">      

       <value>${c3p0.initialPoolSize}</value>      

    </property>    

   <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->      

       <property name="idleConnectionTestPeriod">      

          <value>${c3p0.idleConnectionTestPeriod}</value>      

      </property>      

   </bean>    

      

<!------------- jdbc连接池-2------------------->  

<bean    id="c3p0_dataSource_2"  class="com.mchange.v2.c3p0.ComboPooledDataSource"   destroy-method="close">      

    <property name="driverClass">      

      <value>${jdbc.driverClass}</value>      

    </property>      

   <property name="jdbcUrl">      

       <value>${mysql.url_2}</value>      

       </property>      

    <property name="user">      

       <value>${jdbc.username}</value>      

   </property>      

    <property name="password">      

        <value>${jdbc.password}</value>      

   </property>       

    <!--连接池中保留的最小连接数。-->      

    <property name="minPoolSize">      

           <value>${c3p0.minPoolSize}</value>      

       </property>       

    <!--连接池中保留的最大连接数。Default: 15 -->      

      <property name="maxPoolSize">      

      <value>${c3p0.maxPoolSize}</value>      

   </property>      

    <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->      

      <property name="initialPoolSize">      

     <value>${c3p0.initialPoolSize}</value>      

    </property>    

  <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->      

     <property name="idleConnectionTestPeriod">      

          <value>${c3p0.idleConnectionTestPeriod}</value>      

       </property>      

  </bean>  

  

  <!------------- 更多的链接池配置------------------->  

   ......  

	<!-- jdbc连接池-1-->
	<bean	id="c3p0_dataSource_1"	class="com.mchange.v2.c3p0.ComboPooledDataSource"	destroy-method="close">   
		<property name="driverClass">   
			<value>${jdbc.driverClass}</value>   
		</property>   
		<property name="jdbcUrl">   
			<value>${mysql.url_1}</value>   
        </property>   
		<property name="user">   
			<value>${jdbc.username}</value>   
		</property>   
		<property name="password">   
			<value>${jdbc.password}</value>   
		</property>    
		<!--连接池中保留的最小连接数。-->   
		<property name="minPoolSize">   
            <value>${c3p0.minPoolSize}</value>   
        </property>    
		<!--连接池中保留的最大连接数。Default: 15 -->   
        <property name="maxPoolSize">   
			<value>${c3p0.maxPoolSize}</value>   
		</property>   
		<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->   
        <property name="initialPoolSize">   
			<value>${c3p0.initialPoolSize}</value>   
		</property> 
		<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->   
        <property name="idleConnectionTestPeriod">   
            <value>${c3p0.idleConnectionTestPeriod}</value>   
        </property>   
    </bean> 
    
	<!------------- jdbc连接池-2------------------->
	<bean	id="c3p0_dataSource_2"	class="com.mchange.v2.c3p0.ComboPooledDataSource"	destroy-method="close">   
		<property name="driverClass">   
			<value>${jdbc.driverClass}</value>   
		</property>   
		<property name="jdbcUrl">   
			<value>${mysql.url_2}</value>   
        </property>   
		<property name="user">   
			<value>${jdbc.username}</value>   
		</property>   
		<property name="password">   
			<value>${jdbc.password}</value>   
		</property>    
		<!--连接池中保留的最小连接数。-->   
		<property name="minPoolSize">   
            <value>${c3p0.minPoolSize}</value>   
        </property>    
		<!--连接池中保留的最大连接数。Default: 15 -->   
        <property name="maxPoolSize">   
			<value>${c3p0.maxPoolSize}</value>   
		</property>   
		<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->   
        <property name="initialPoolSize">   
			<value>${c3p0.initialPoolSize}</value>   
		</property> 
		<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->   
        <property name="idleConnectionTestPeriod">   
            <value>${c3p0.idleConnectionTestPeriod}</value>   
        </property>   
    </bean>

    <!------------- 更多的链接池配置------------------->
    ......



STEP 2。将多个数据源都注入到MultiDataSource中

Xml代码

 

  <bean id="multiDataSource" class="com.xxx.sql.MultiDataSource"> <property name="clusterDataSources"> <list> <ref bean="c3p0_dataSource_1" /> <ref bean="c3p0_dataSource_2" /> <ref bean="c3p0_dataSource_3" /> <ref bean="c3p0_dataSource_4" /> <ref bean="c3p0_dataSource_5" /> <ref bean="c3p0_dataSource_6" /> <ref bean="c3p0_dataSource_7" /> <ref bean="c3p0_dataSource_8" /> </list> </property> <property name="mappedDataSources"> <map> <entry key="system" value-ref="c3p0_dataSource_system" /> </map> </property> </bean> <bean id="multiDataSource" class="com.xxx.sql.MultiDataSource"> <property name="clusterDataSources"> <list> <ref bean="c3p0_dataSource_1" /> <ref bean="c3p0_dataSource_2" /> <ref bean="c3p0_dataSource_3" /> <ref bean="c3p0_dataSource_4" /> <ref bean="c3p0_dataSource_5" /> <ref bean="c3p0_dataSource_6" /> <ref bean="c3p0_dataSource_7" /> <ref bean="c3p0_dataSource_8" /> </list> </property> <property name="mappedDataSources"> <map> <entry key="system" value-ref="c3p0_dataSource_system" /> </map> </property> </bean>

 

 



STEP 3。像使用标准的DataSource一样,使用MultiDataSource

Xml代码
<!--  iBatis Client配置 将 MultiDataSource 与iBatis Client 绑定-->  

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  

        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>  

    <property name="dataSource" ref="multiDataSource"></property>  

</bean>  

  

<!-- jdbc事务管理配置 将 MultiDataSource 与事务管理器绑定-->  

<bean id="jdbc_TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  

  <property name="dataSource" ref="multiDataSource"></property>  

</bean>  

	<!--  iBatis Client配置 将 MultiDataSource 与iBatis Client 绑定-->
	<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  		<property name="configLocation" value="classpath:SqlMapConfig.xml"/>
		<property name="dataSource" ref="multiDataSource"></property>
	</bean>
	
	<!-- jdbc事务管理配置 将 MultiDataSource 与事务管理器绑定-->
	<bean id="jdbc_TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="multiDataSource"></property>
	</bean>



至此,我们的程序就可以让Spring来管理多库访问了,但请注意,数据库事务仍然限于单库范围(之前已经说过,这里的应用场景不存在跨库的事务)。


6.Java代码使用例子
首先要说明的是,这里我们只是提供了一个简单的使用范例,在范例中,我们还必须手动的调用API,以确定DataSource的路由规则,在实际的应用中,您可以针对自己的业务特点,对此进行封装,以实现相对透明的路由选择

Java代码
public boolean addUserGameInfo(UserGameInfo userGameInfo){   

    //1.根据UserGameInfo.uid 进行数据源路由选择       DataSourceRouter.setRouterStrategy(   

           RouterStrategy.SRATEGY_TYPE_CLUSTER ,   

           null,   

           userGameInfo.getUid());   

       

    //2.数据库存储   

    try {   

       userGameInfoDAO.insert(userGameInfo);   

       return true;   

    } catch (SQLException e) {   

        e.printStackTrace();   

        logger.debug("Insert UserGameInfo failed. " + userGameInfo.toString());   

    }   

    return false;   

}  

	public boolean addUserGameInfo(UserGameInfo userGameInfo){
		//1.根据UserGameInfo.uid 进行数据源路由选择
		DataSourceRouter.setRouterStrategy(
				RouterStrategy.SRATEGY_TYPE_CLUSTER ,
				null,
				userGameInfo.getUid());
		
		//2.数据库存储
		try {
			userGameInfoDAO.insert(userGameInfo);
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			logger.debug("Insert UserGameInfo failed. " + userGameInfo.toString());
		}
		return false;
	}



OK,我们的多库横向切分的实验可以暂告一个段落。实际上,要实现一个完整的DAL是非常庞大的工程,而对我们推动巨大的,可能只是很小的一个部分,到处都存在着8-2法则,要如何选择,就看各位看官了!!

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
安全 Java 数据库
后端进阶之路——万字总结Spring Security与数据库集成实践(五)
后端进阶之路——万字总结Spring Security与数据库集成实践(五)
|
4月前
|
SQL 监控 druid
p6spy【SpringBoot集成】使用p6spy-spring-boot-starter集成p6spy监控数据库(配置方法举例)
p6spy【SpringBoot集成】使用p6spy-spring-boot-starter集成p6spy监控数据库(配置方法举例)
185 0
|
5月前
|
存储 Java 数据库
java spring boot 数据库密码解密
java spring boot 数据库密码解密
|
5月前
|
存储 Java 数据库连接
Spring Boot 配置主从数据库实现读写分离
Spring Boot 配置主从数据库实现读写分离
173 0
|
3月前
|
SQL Java 数据库连接
(数据库链接池)spring内容复习7月16日笔记
(数据库链接池)spring内容复习7月16日笔记
15 0
|
3月前
|
前端开发 JavaScript Java
基于spring+jsp+mysql实现的Java web论坛系统【源码+数据库+指导运行】
基于spring+jsp+mysql实现的Java web论坛系统【源码+数据库+指导运行】
|
4月前
|
监控 druid Java
SpringBoot 使用【druid-spring-boot-starter】集成 druid 监控数据库
SpringBoot 使用【druid-spring-boot-starter】集成 druid 监控数据库
65 0
|
4月前
|
Java 数据库连接 数据库
Spring Boot整合MyBatis操作mysql数据库实战(附源码 超详细)
Spring Boot整合MyBatis操作mysql数据库实战(附源码 超详细)
143 0
|
4月前
|
Java 关系型数据库 MySQL
Spring Boot使用JdbcTemplate操作mysql数据库实战(附源码 超详细)
Spring Boot使用JdbcTemplate操作mysql数据库实战(附源码 超详细)
40 0
|
5月前
|
中间件 Java 应用服务中间件
重磅!基础+Spring+并发+调优+微服务+数据库+中间件已肝完
在金三银四时也参与过不少面试,2021都说工作不好找,也是对开发人员的要求变高。前段时间自己有整理了一些Java后端开发面试常问的高频考点问题做成一份PDF文档(1000道高频题),同时也整理一些图文解析及笔记,今天在这免费分享给大家,希望大家在即将的十月面试做好复习,长期的积累和短期的突击让自己能找到一个满意的工作!

热门文章

最新文章