之前有写过在linux上搭建多个mysql实例,然后又写了一篇多个mysql之间如何配置主从,现在终于到了如何使用的时候了,这篇文章就说明了,如何在程序中,而且是在通常的项目结构(基于Spring构建的项目中)中如何使用。
一、说说原理
简单的不能再简单了,配置多个datasource ,不同的方法使用不同的datasource。比如说,来自主库的datasource1,来自从库的datasource2 ,方法method1有数据更改操作(insert, update,delete), 使用datasource1, 方法method2有查询操作(select),使用datasource2。
为什么这么设计呢,因为对于一个项目来时候,绝大部分的数据库操作是查询,而不是更改,而且更改和查询的机制和方式不一样,更改涉及到的是对单表的修改,所的机制,事务的处理,而查询更多的是索引,主键的关联方面。
如果对mysql了解多一点的可能会有新的问题,如果我把主库的存储引擎弄成InnoDB来支持事务,从库用不支持事务的存储引擎来加快查询速度,这不是很好吗?的确很不错,但是有一个问题,如果主库挂了之后,从库需要立即来担任主库的责任的时候,这时候从库不支持事务,部分操作可能会引起问题。所以要视情况来说了,也可以通过多个从库来弥补一下这个问题。
二、Spring如何使用主从
在Spring使用主从主要利用了切面(AOP)和AbstractRoutingDataSource这个抽象类, 流程是这样的,在某一个或者某一类型的类的上加一个切面,当此类方法被调用时,会进入切面,根据方法之前设置的类型,被指向某个切面的datasource实例,这样,当调用mapper的时候就会使用这个datasource来操作数据库了。
做PPT的时候画了个草图,大家凑合看吧。
代码如下
有自定义注解如下
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface DataSource {
String value();
}
实现类方法如下
@Override
@DataSource(value="slave")
public List<Map<String, Object>> getMaterials(String name, String code, String type, String state) {
try {
List<Map<String, Object>> materials = this.materialMapper.getMaterials(null, name, code, type, state);
return materials;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Spring 相关配置如下
<bean id="basic" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close" abstract="true">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- Connection Pooling Info -->
<property name="maxTotal" value="${jdbc.pool.maxActive}" />
<property name="maxIdle" value="${jdbc.pool.maxIdle}" />
<property name="minIdle" value="0" />
<property name="defaultAutoCommit" value="false" />
</bean>
<!-- 从库 -->
<bean id="slaveDataSource" parent="basic">
<property name="url" value="${jdbc.url.second}" />
</bean>
<!-- 主库 -->
<bean id='masterDataSource' parent="basic">
<property name="url" value="${jdbc.url.main}" />
</bean>
<!-- 数据源 -->
<bean id="dataSource" class="com.fantong.common.datasource.DataSourceSwitcher">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource" />
<entry key="slave" value-ref="slaveDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource">
</property>
</bean>
<!-- 配置切面 用于分库 -->
<aop:aspectj-autoproxy />
<bean id="dataSourceAspect" class="com.fantong.common.datasource.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect">
<aop:pointcut id="tx" expression="execution(* *..service.impl.*.*(..))" />
<aop:before pointcut-ref="tx" method="before" />
</aop:aspect>
</aop:config>
相关自定义类
DataSourceAspect
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
public class DataSourceAspect {
public void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<? extends Object> classz = target.getClass();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
DataSourceSwitcherToken.putToken(data.value());
System.out.println(data.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
DataSourceSwitcher
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DataSourceSwitcher extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceSwitcherToken.getToken();
}
}
DataSourceSwitcherToken
public class DataSourceSwitcherToken {
public static final ThreadLocal<String> token = new ThreadLocal<String>();
public static void putToken(String name) {
token.set(name);
}
public static String getToken() {
return token.get();
}
public static void relaxToken() {
token.remove();
}
}