承接上篇文章,数据库已经实现主从复制,接下来我们就需要在java程序中使用spring+mybatis的多数据源特性实现项目的读写分离,提高项目性能。

目的

现在我们有两个数据一样的数据库信息,我们可以在slave数据库上根据我们的需求添加索引提升查询效率,如果项目有增删改的动作,我们就使用master数据库,大部分情况下,我们都是优化项目的查询功能,如果觉得slave数据库加索引后还满足不了,多配置几个slave数据库,并做负载均衡处理,在这里我们使用简单的入门使用。

一、配置数据源

在spring配置文件中配置两个(两个以上,这里我们配置两个,多个数的原理一样)数据库信息,在这里多数据源的动态切换都是通过重载Spring中的AbstractRoutingDataSource类来实现的。


    固定多数据源的动态切换,通过自定义注解实现切换,这样在切换数据源时比较灵活,具体的实现方式如下:

    1、配置多数据源

   

<!--定义数据源1-->
     <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
         <property name="driverClassName" value="com.mysql.jdbc.Driver" />
         <property name="url" value="jdbc:mysql://192.168.1.10:3306/jbpmdb" />
         <property name="username" value="root" />
         <property name="password" value="123456" />
         <!-- 初始化连接大小 -->
         <property name="initialSize" value="0"></property>
         <!-- 连接池最大数量 -->
         <property name="maxActive" value="20"></property>
         <!-- 连接池最大空闲 -->
         <property name="maxIdle" value="20"></property>
         <!-- 连接池最小空闲 -->
         <property name="minIdle" value="1"></property>
         <!-- 获取连接最大等待时间 -->
         <property name="maxWait" value="60000"></property>
     </bean>    <!--定义数据源2-->
     <bean id="salveDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
         <property name="driverClassName" value="com.mysql.jdbc.Driver" />
         <property name="url" value="jdbc:mysql://192.168.1.20:3306/jbpmdb" />
         <property name="username" value="root" />
         <property name="password" value="123456" />
         <!-- 初始化连接大小 -->
         <property name="initialSize" value="0"></property>
         <!-- 连接池最大数量 -->
         <property name="maxActive" value="20"></property>
         <!-- 连接池最大空闲 -->
         <property name="maxIdle" value="20"></property>
         <!-- 连接池最小空闲 -->
         <property name="minIdle" value="1"></property>
         <!-- 获取连接最大等待时间 -->
         <property name="maxWait" value="60000"></property>
     </bean>    <!--动态数据源配置-->
     <bean id="dataSource" class="com.ssm.datasource.DynamicDataSource">
     <!--引入定义好的数据源-->
         <property  name="targetDataSources">
             <map  key-type="java.lang.String">
               <entry key="master" value-ref="masterDataSource" />
               <entry key="salve" value-ref="salveDataSource" />
             </map>
         </property>
     <!--定义默认数据源-->
         <property name="defaultTargetDataSource" ref="masterDataSource" />
     </bean>    <!--spring和mybatis整合-->
     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <property name="dataSource" ref="dataSource" />
         <property name="mapperLocations" value="classpath:mapping/*.xml" />
     </bean>
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
         <property name="basePackage" value="com.ssm.dao" />
         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
     </bean>

通过以上配置我们在项目中就完成了多数据源的配置,简单起见我们就配置两个数据源master(主)数据库,slave(从)数据库。

2、配置注解(@DataSource),用于切换数据源,注解的值只能是上面配置文件中key的值(master、slave)。

@Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface DataSource {
     String value();
 }

注意不要导错包了,都是java中的。

按照上面代码我们就已经准备好最核心的注解,下一步就可以重载Spring中的AbstractRoutingDataSource类来实现在不同业务使用不同的数据源(增删改master、查询salve)。

3、根据spring的切面编程,当使用特定的类(也就是service类)的时候,如果有注解,解释注解内容,并根据注解填写的值(也就是master、slave)来选择不同的数据库连接对象

public class DataSourceAspect {
    /**
     * 定义切面,当调用com.ssm.service下的所有类的所有方法前都会执行beforeInvoke方法
     */
     @Pointcut("execution(* com.ssm.service.*.*(..))")
     public void pointCut(){};    @Before(value = "pointCut()")
     public void beforeInvoke(JoinPoint joinpoint) {
         try {
             String clazzName = joinpoint.getTarget().getClass().getName();
             String methodName = joinpoint.getSignature().getName();
             Class targetClazz = Class.forName(clazzName);
             Method[] methods = targetClazz.getMethods();
             for(Method method : methods) {
                 if(method.getName().equals(methodName)) {
                     // 首先查看方法是否使用注解
                     // 如果使用注解,则获取注解定义的值,并根据注解的值设置访问数据库的key
                     if(method.isAnnotationPresent(DataSource.class)) {
                         DataSource dataSource = method.getAnnotation(DataSource.class);
                         DatasourceHolder.setDataType(dataSource.value());
                     }
                     break;
                 }
             }
         } catch (Exception e) {
             e.printStackTrace();
         }    }
 }

4、定义在使用过程中所用的数据源信息(继承Spring的AbstractRoutingDataSource)

public class DynamicDataSource extends AbstractRoutingDataSource {
     /**
     * 根据DatasourceHolder中DataType的值获取具体的数据源
     */
     @Override
     protected Object determineCurrentLookupKey() {
         return DatasourceHolder.getDataType();
     }
 }

5、现在我们就能开开心心的使用了,祝君愉快!

@Service
 public class IdxServiceImpl implements IIdxSevice {    @Autowired
     private IdxMapper idxMapper;    /**
     * 根据注解的配置,会访问master对应的数据源
     */
     @Override
     @DataSource("master")
     public Map<String,Object> delIdxById(int idxId) {
         return idxMapper.delIdxById(idxId);
     }    /**
     * 根据注解的配置,会访问slave对应的数据源
     */
     @Override
     @DataSource("slave")
     public Map<String, Object> getJobInfo(int dbId) {
         return idxMapper.getJobInfo(dbId);
     }
 }