1.數據庫層面的主從配置實現java
2.代碼層面的讀寫分離實現(無需改動現有代碼)mysql
主服務器(Master)將對數據的操做串行記錄到二進制日誌當中(binary log),寫入完畢後主服務器通知數據存儲引擎提交事務,提交好了以後進入第第二步;spring
這裏須要補充,咱們隊數據的操做稱之爲一次二進制的日誌事件,binary log將日誌拷貝到中繼日誌中(relay log)。收線,slave先開啓一個工做線程(IO Thread),IO縣城在master上打開一個鏈接,而後將binary log拷貝到 IO thread中,若是已經讀取到binary log的日誌,就會睡眠並等待binary log產生新的事件,IO縣城將這些事件寫入到relay log中,第二步完成。sql
第三步,slave重作relay log,也就是好比主服務器更新數據,slave尚未,那麼就更新,SQL Thread就是幹這個事情。數據庫
總結:主服務器添加了數據A,操做被記錄到binary log中,從服務器將添加A的操做同步到relay log中,SQL thread將同步的操做執行:向從服務器中插入數據Aapache
1.打開二進制日誌vim
vim /etc/my.cnf服務器
server-id=1session
服務器ID,用於標識
log-bin=master-bin 打開二進制日誌
log-bin-index=master-bin.index 打開二進制日誌索引mybatis
2.重啓數據庫,進入數據庫
show master status;
能夠看到,裏面有個file,使咱們剛纔指定好的master-bin.index的,Position表示開始的位置
3.同時須要新建一個用戶(或者不用也能夠?由於從服務器須要指定一個用戶)
create user repl;
受權
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'從服務器IP地址' IDENTIFIED BY 'mysql';
刷新
flush privileges;
1.打開二進制日誌
vim /etc/my.cnf
2.打開relay log
3.關聯主服務器
change master to master_host='主服務器IP地址',master_port=3306,master_user='repl',master_password='mysql',master_log_file='master-bin.000001',master_log_pos=0;
4.開啓主從跟蹤
start slave;
5.查看從服務器狀態
show slave status \G;
發現報錯:說主從服務器 server id 相同。
解決:
stop slave;
exit;
vim /etc/my.cnf
發現確實id變了。
代碼寫完之後,在src/main/resource裏面的mybatis-config.xml裏面
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 配置全局屬性 --> <settings> <!-- 使用jdbc的getGeneratedKeys獲取數據庫自增主鍵值 --> <setting name="useGeneratedKeys" value="true" /> <!-- 使用列別名替換列名 默認:true --> <setting name="useColumnLabel" value="true" /> <!-- 開啓駝峯命名轉換:Table{create_time} -> Entity{createTime} --> <setting name="mapUnderscoreToCamelCase" value="true" /> <!-- 打印查詢語句 --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <!-- 讀寫分離 --> <plugins> <plugin interceptor="com.wby.o2o.dao.split.DynamicDataSourceInterceptor"> </plugin> </plugins> </configuration>
而後添加日誌代碼,在src/main/resource/spring中的spring-dao.xml
原文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 配置整合mybatis過程 --> <!-- 1.配置數據庫相關參數properties的屬性:${url} --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 2.數據庫鏈接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置鏈接池屬性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- c3p0鏈接池的私有屬性 --> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <!-- 關閉鏈接後不自動commit --> <property name="autoCommitOnClose" value="false" /> <!-- 獲取鏈接超時時間 --> <property name="checkoutTimeout" value="10000" /> <!-- 當獲取鏈接失敗重試次數 --> <property name="acquireRetryAttempts" value="2" /> <!-- 3.配置SqlSessionFactory對象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入數據庫鏈接池 --> <property name="dataSource" ref="dataSource" /> <!-- 配置MyBaties全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 掃描entity包 使用別名 --> <property name="typeAliasesPackage" value="com.wby.o2o.entity" /> <!-- 掃描sql配置文件:mapper須要的xml文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <!-- 4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 給出須要掃描Dao接口包 --> <property name="basePackage" value="com.wby.o2o.dao" /> </bean> </beans>
新配置的文件:主要在鏈接池這塊
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 配置整合mybatis過程 --> <!-- 1.配置數據庫相關參數properties的屬性:${url} --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 2.數據庫鏈接池 --> <bean id="abstractDataSource" abstract="true" destroy-method="close" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- c3p0鏈接池的私有屬性 --> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <!-- 關閉鏈接後不自動commit --> <property name="autoCommitOnClose" value="false" /> <!-- 獲取鏈接超時時間 --> <property name="checkoutTimeout" value="10000" /> <!-- 當獲取鏈接失敗重試次數 --> <property name="acquireRetryAttempts" value="2" /> </bean> <bean id="master" parent="abstractDataSource"> <!-- 配置鏈接池屬性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.mater.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="slave" parent="abstractDataSource"> <!-- 配置鏈接池屬性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.slave.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 配置動態數據源這裏的targetDataSource就是路由數據源所對應的數據名稱 --> <bean id="dynamicDataSource" class="com.wby.o2o.dao.split.DynamicDataSource"> <property name="targetDataSources"> <map> <entry value-ref="master" key="master"></entry> <entry value-ref="slave" key="slave"></entry> </map> </property> </bean> <!-- 由於這些操做是在程序執行以後,在mybatis生成sql語句以後,才決定數據源,所以 引入懶加載 --> <bean id="dataSorce" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> <property name="targetDataSource"> <ref bean="dynamicDataSource"/> </property> </bean> <!-- 3.配置SqlSessionFactory對象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入數據庫鏈接池 --> <property name="dataSource" ref="dataSource" /> <!-- 配置MyBaties全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 掃描entity包 使用別名 --> <property name="typeAliasesPackage" value="com.wby.o2o.entity" /> <!-- 掃描sql配置文件:mapper須要的xml文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <!-- 4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 給出須要掃描Dao接口包 --> <property name="basePackage" value="com.wby.o2o.dao" /> </bean> </beans>
執行測試類
AreaDaoTest
package com.wby.o2o.dao; import static org.junit.Assert.assertEquals; import java.util.List; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import com.wby.o2o.BaseTest; import com.wby.o2o.entity.Area; public class AreaDaoTest extends BaseTest{ @Autowired private AreaDao areaDao; @Test public void testQueryArea(){ List<Area> areaList=areaDao.queryArea(); assertEquals(4, areaList.size()); } }
顯示報錯
是由於DynamicDataSourceInterceptor類沒有添加註解
package com.wby.o2o.dao.split; import java.util.Locale; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionSynchronizationManager; /** * 攔截器。 * 若是是insert,update等寫操做,使用主數據庫 * 若是是query,select等讀操做,使用從數據庫 * @author Administrator * */ //增刪改的操做都封裝在了update裏面,所以update+query就能夠了 @Intercepts({@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}), @Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}) }) public class DynamicDataSourceInterceptor implements Interceptor{ /** * 主要的攔截方法 */ //日誌部分 private static Logger logger =LoggerFactory.getLogger( DynamicDataSourceInterceptor.class); private static final String REGEX=".*insert\\uoo2o.*|.*delete\\u0020.*|.*update\\u0020.*"; @Override public Object intercept(Invocation invocation) throws Throwable { boolean synchronizationAction=TransactionSynchronizationManager.isActualTransactionActive(); Object[] objects=invocation.getArgs(); MappedStatement ms=(MappedStatement) objects[0]; String lookupKey=DynamicDataSourceHolder.DB_MASTER; if (synchronizationAction!=true) { //讀方法 if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) { //selectKey爲自增Id查詢主鍵(SELECT LAST_INSERT_ID())方法 //使用主庫 if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) { lookupKey=DynamicDataSourceHolder.DB_MASTER; }else { BoundSql boundSql=ms.getSqlSource().getBoundSql(objects[1]); String sql=boundSql.getSql() .toLowerCase(Locale.CHINA) .replaceAll("[\\t\\n\\r]", ""); //匹配正則是否是增刪改的 if (sql.matches(REGEX)) { lookupKey=DynamicDataSourceHolder.DB_MASTER; }else { lookupKey=DynamicDataSourceHolder.DB_SLAVE; } } } }else { lookupKey=DynamicDataSourceHolder.DB_MASTER; } logger.debug("設置方法[{}] use[{}] Strategy,SqlCommanType [{}]...", ms.getId(),lookupKey,ms.getSqlCommandType().name()); DynamicDataSourceHolder.setDbType(lookupKey); return invocation.proceed(); } /** * 返回封裝好的對象,或者返回代理對象 * 若是攔截的對象target類型是Executor,就攔截,將intercept給他包裝到 * .wrap(target, this);裏面去。 * 若是不是,直接返回本體 * 爲何攔截Executor類型? * 由於在mybatis中,Executor是用來支持一系列增刪改查操做的。而後經過intercept() * 決定使用哪個數據源 */ public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); }else { return target; } } /** * 類初始化的時候作一些相關的配置。非必要 */ @Override public void setProperties(Properties properties) { // TODO Auto-generated method stub } }