承接上篇文章,数据库已经实现主从复制,接下来我们就需要在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);
}
}