介绍
解决数据库读写分离方案有,应用层解决数据库读写分离方案,中间件解决数据库读写分离两种方案。本文只介绍应用层解决读写分离方案。
第一种方案(推荐)
一 、查看AbstractRoutingDataSource类
首先查看一个spring的一个类AbstractRoutingDataSource,查看其属性,注意红框。
二、如何配置多数据源
再查看类中的其它方法:setTargetDataSources与afterPropertiesSet(InitializingBean接口的实现)。
setTargetDataSources方法给targetDataSources属性赋值。
afterPropertiesSet在setTargetDataSources赋值之后执行,给resolvedDataSources赋值。
所以我们如果要配置多个数据源我们就可以在spring配置文件中添加两个数据源,一个读一个写。
<!--配置数据库连接池读-->
<bean id="dataSourceForRead" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--可不写,durid默认自动根据url识别-->
<property name="driverClassName" value="${jdbc.read.driveClassName}" />
<property name="url" value="${jdbc.read.url}" />
<property name="username" value="${jdbc.read.username}" />
<property name="password" value="${jdbc.read.password}" />
<!--配置初始化大小,最小、最大-->
<property name="initialSize" value="1" />
<property name="minIdle" value="3" />
<property name="maxActive" value="20" />
<!--配置获取连接等待超时的时间-->
<property name="maxWait" value="60000" />
<!--配置间隔多久才进行以此检测,检测需要关闭的空闲连接,单位式毫秒-->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!--配置一个连接在池中最小的生存时间,单位是毫秒-->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!--配置监控统计拦截的filters-->
<property name="filters" value="stat" />
</bean>
<!--配置数据库连接池写-->
<bean id="dataSourceForWrite" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--可不写,durid默认自动根据url识别-->
<property name="driverClassName" value="${jdbc.write.driveClassName}" />
<property name="url" value="${jdbc.write.url}" />
<property name="username" value="${jdbc.write.username}" />
<property name="password" value="${jdbc.write.password}" />
<!--配置初始化大小,最小、最大-->
<property name="initialSize" value="1" />
<property name="minIdle" value="3" />
<property name="maxActive" value="20" />
<!--配置获取连接等待超时的时间-->
<property name="maxWait" value="60000" />
<!--配置间隔多久才进行以此检测,检测需要关闭的空闲连接,单位式毫秒-->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!--配置一个连接在池中最小的生存时间,单位是毫秒-->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!--配置监控统计拦截的filters-->
<property name="filters" value="stat" />
</bean>
在AbstractRoutingDataSource实现抽象类的子类中添加两个数据源
<!--自定义存取数据源 《读写锋分离的第二种方式》-->
<bean id="dataSource" class="com.bw.spring.datasource.CustomDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="dataSourceForRead" />
<entry key="slave" value-ref="dataSourceForWrite" />
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceForWrite" />
</bean>
其它配置均和spring配置相同即可。
启动spring完成后,spring会根据配置文件自动在CustomDataSource配置两个数据源。
三、如何选择数据源
查看AbstractRoutingDataSource的getConnection方法
我们在获取数据库链接前都会先执行determineTargetDataSource()方法
determineTargetDataSource又会通过determineCurrentLookupKey方法根据key获取不同的数据源,那么我们的AbstractRoutingDataSource子类就必须实现determineCurrentLookupKey方法,通过此方法帮助我们选择不同的数据源。
设置一个CustomDataSourceHolder设置获取key,需要保证线程安全。
我们自定义一个注解帮助我们在service层切换选择数据源
在定义一个aop类,在方法执行前判断设置哪一种数据源
我们只需要在方法上添加注解即可动态的切换数据源
四、多数据源切换
实际情况下,我们不会是单一的读写数据源,往往是多个读数据源。
直接上代码,我们可以通过设置从库名称为slave1、slave2.。。。。。。.,通过轮询的方式获取从库。
第二种方案
在mapper的dao层加入自定义注解使用不同的数据源。
<bean id="sqlSessionFactoryForRead" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据库连接池-->
<property name="dataSource" ref="dataSourceForRead" />
<!--配置mybatis全局配置文件mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!--扫描model包-->
<property name="typeAliasesPackage" value="com.bw.model" />
<!--扫描sql xml配置文件-->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<!--配置sql sessionFactoryForWrite 《读写分离的第一种方式》-->
<bean id="sqlSessionFactoryForWrite" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据库连接池-->
<property name="dataSource" ref="dataSourceForWrite" />
<!--配置mybatis全局配置文件mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!--扫描model包-->
<property name="typeAliasesPackage" value="com.bw.model" />
<!--扫描sql xml配置文件-->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sql sessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryForRead" />
<!--扫描的dao接口包位置-->
<property name="basePackage" value="com.bw.dao" />
<!--读数据源注解类-->
<property name="annotationClass" value="com.bw.customannotations.DatasourceRead" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sql sessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryForWrite" />
<!--扫描的dao接口包位置-->
<property name="basePackage" value="com.bw.dao" />
<!--写数据源注解类-->
<property name="annotationClass" value="com.bw.customannotations.DatasourceWrite" />
</bean>
<bean id="transactionManagerForRead" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceForRead" />
</bean>
<bean id="transactionManagerForWrite" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceForWrite" />
</bean>