Mybatis拦截器实现读写分离的思路和实践
说到这里,我们可以再思考一个问题,一般数据库都是主从,也就是很多都是一主多从,对应的就是一写多读,意思是写的时候写到主库,读的时候可以从从库的任意中读取。因此我们的插件要有写库的数据源已经多个读库的数据源。
上边说了那么多,如果没有读写识别的信号,那说的再多也没有价值。这块我们就需要解析SQL或者解析方法上边的特定注解了。前者为一般模式,后者是灵活配置。当然这块要注意的就是事务了,事务肯定要操作单库,也必然是主库,道理说了挺多哈。我们试着研究一下怎么做吧。
1.首先就是Mybatis插件了,记得之前我们说mybatis有4个阶段(Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler),每个阶段的各个方法都可以被拦截,当然这块拦截器的拦截原理责任链模式,过程还是比较难的。然后通过jdk代理的方式植入到mybatis执行过程中。这块的笔记已经忘的差不多了。再此贴个笔记。
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})})
public class MybatisLanjieqi implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
System.out.println(statementHandler.toString());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
System.out.println("enter the plugin");
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
}
考虑到我们要在代码中灵活决定采用那种类型的数据源,因此我们需要需要将现场的一些东西传过来,比如调用类的信息。当然还有sql的类型什么的。这块我们debug一下。看看我们的StatementHandler中有什么值。
public class MyDataSource extends DruidDataSource {
private static DruidDataSource write;
private static Listreader;
//模拟多库初始化....
public static DruidDataSource getDruidDataSource(String url,String userName,String password) throws SQLException {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
try {
ds.setFilters("stat,mergeStat,slf4j");
} catch (Exception var18) {
}
ds.setMaxActive(50);
ds.setInitialSize(1);
ds.setMinIdle(1);
ds.setMaxWait(60000);
ds.setTimeBetweenEvictionRunsMillis(120000);
ds.setMinEvictableIdleTimeMillis(300000);
ds.setValidationQuery("SELECT 'x'");
ds.setPoolPreparedStatements(true);
ds.setMaxPoolPreparedStatementPerConnectionSize(30);
ds.setTestWhileIdle(true);
ds.setTestOnReturn(false);
ds.setTestOnBorrow(false);
ds.init();
return ds;
}
static {
//初始化write...
try {
write=getDruidDataSource("jdbc:mysql://127.0.0.1:3306/tianjl?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true","root","tianjingle");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//初始化读库
// reader.add(write);
// reader.add(write);
// reader.add(write);
}
public DruidPooledConnection getConnection() throws SQLException {
//这块可以写具体得库选择逻辑,读库随机可以从用random方法。
return write.getConnection();
}
}
"dataO") (name =
public SqlSessionFactoryBean getSqlSessionFactoryOne1() throws Exception {
//xml和实体的映射
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(new MyDataSource());
sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.one");
Resource[] resources = new Resource[]{new ClassPathResource("tian/one/OneMapper.xml")};
sqlSessionFactoryBean.setMapperLocations(resources);
sqlSessionFactoryBean.setPlugins(new MybatisLanjieqi());
return sqlSessionFactoryBean;
}
"dataTwo") (name =
public MapperFactoryBean getSqlSessionFactoryTwo() throws Exception {
//xml和实体的映射
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(new MyDataSource());
sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.two");
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("tian/two/TwoMapper.xml"));
//单个数据源所有的数据库映射
MapperFactoryBean mapperFactoryBean=new MapperFactoryBean();
//设置sqlSessionTemplate,zhuru yong de
mapperFactoryBean.setMapperInterface(TwoMapper.class);
mapperFactoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject());
return mapperFactoryBean;
}
晚安~
评论