系统发展到一定程度就会面临分库分表的问题,我们要考虑的是怎么让系统平滑的从老的表迁移到新的表。通常新表的数据可以通过binlog同步的方式同步老表的数据,在某一个时间点通过开关的方式让读写老表的操作切换到新表。这里就涉及到如何方便的切换新老操作。
加入了新表,必须要有新表的DAO,现在的问题是如何根据开关调用新老DAO来执行操作。先说下目标:
能够方便的实现开关切换。
对现有代码侵入尽量的少。
擦屁股方便,就是稳定迁移之后要能顺利的清理之前的代码。
方案一:在每个调用到的地方利用if判断来决定调用哪个DAO的方法。这个肯定不现实,涉及的代码太多了。而且这样遗留问题比较多,迁移之后需要清理很多垃圾代码。
方案二:写一个代理类,实现DAO接口,根据开关分别调用新DAO或老DAO的相应方法。基本代码量可以接受,对系统侵入性也比较小,原来的代码可以不用动,spring bean里将代理DAO的id设置成原来的DAO id就可以接入系统。迁移之后只要把老DAO和代理DAO的配置删除,将新DAO的id设置为原来的DAO id就可以了。只有一个问题就是代理DAO写的有点恶心,如果接口里的方法不多的话还好,方法很多的话还是需要写很多东西的。而且代码重复比较多各个方法其实就是判断下开关然后调用相应DAO的方法。

这个时候想到了动态代理,这样的话,DAO代理类就可以写的比较简单,基本没有重复的代码。接下来的问题就是怎么让动态代理接入spring 的context。这个可以通过FactoryBean来封装。
首先,实现动态代理的接口InvocationHandler
 

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果开关打开那么调用新的DAO
        if(diamondSwitchConfig.isNewTableSwitch()){
            log.warn("bid_proxy user new table..........................................");
            return method.invoke(bidProxyDAONew, args);
        }
        return method.invoke(proxyBidRepo, args);
    }

这里根据开关让method选择不同的 object来invoke。达到调用不同DAO的目的。然后实现FactoryBean 接口
 

@Override
    public Object getObject() throws Exception {
        Object proxyObj=Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{BidProxyDAO.class}, this);
        return (BidProxyDAO) proxyObj;
    }

    @Override
    public Class getObjectType() {
        return BidProxyDAO.class;
    }

就是返回代理类对象和对象类型,BidProxyDAO 就是DAO接口。附上完整的类

public class ProxyBidDAOProxyFactoryBean implements InvocationHandler, FactoryBean {
    private static Logger log =  LoggerFactory.getLogger(ProxyBidDAOProxyFactoryBean.class);
    /**
     * 新的dao
     */
    @Resource
    private BidProxyDAO bidProxyDAONew;
    /**
     * 老的dao
     */
    @Resource
    private BidProxyDAO proxyBidRepo;

    @Resource
    private DiamondSwitchConfig diamondSwitchConfig;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果开关打开那么调用新的DAO
        if(diamondSwitchConfig.isNewTableSwitch()){
            log.warn("bid_proxy user new table..........................................");
            return method.invoke(bidProxyDAONew, args);
        }
        return method.invoke(proxyBidRepo, args);
    }

    @Override
    public Object getObject() throws Exception {
        Object proxyObj=Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{BidProxyDAO.class}, this);
        return (BidProxyDAO) proxyObj;
    }

    @Override
    public Class getObjectType() {
        return BidProxyDAO.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

然后spring context的配置

<bean id="proxyBidRepo" class="com.taobao.misc.auction.repository.ProxyBidRepo"/> 
<bean id="bidProxyDAONew" class="com.taobao.misc.auction.dao.impl.BidProxyDAOImpl"/>
 <bean id="bidProxyDAO" class="com.taobao.misc.auction.dao.impl.ProxyBidDAOProxyFactoryBean"/>

这样系统原来引用bidProxyDAO 的代码都不需要动就可以实现根据开关来切换新老DAO的实现。等系统上线稳定之后可以将bidProxyDAONew替换成bidProxyDAO,然后删除剩下2个DAO的配置,系统就完全使用新的DAO,而且不用改一行代码。