之前有写过在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(); } }