// Path: src/main/java/com/github/mybatis/interceptor/MasterSlaveInterceptor.java
// @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
// public class MasterSlaveInterceptor implements Interceptor {
//   private static final ThreadLocal<Boolean> READ_ONLY_LOCAL = new ThreadLocal<Boolean>();
// 
//   public static boolean isReadOnly() {
//     return Boolean.TRUE.equals(READ_ONLY_LOCAL.get());
//   }
// 
//   @Override
//   public Object intercept(Invocation invocation) throws Throwable {
//     StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//     MappedStatement mappedStatement;
//     if (statementHandler instanceof RoutingStatementHandler) {
//       StatementHandler delegate = ReflectionUtil.getFieldValue(statementHandler, "delegate");
//       mappedStatement = ReflectionUtil.getFieldValue(delegate, "mappedStatement");
//     } else {
//       mappedStatement = ReflectionUtil.getFieldValue(statementHandler, "mappedStatement");
//     }
//     // 读写分离设置使用哪个数据源
//     Boolean readOnly = Boolean.FALSE;
//     if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
//       final String sql = statementHandler.getBoundSql().getSql();
//       String s = sql.toLowerCase();
//       readOnly = !(s.contains("last_insert_id()") || s.contains("row_count()"));
//     }
//     READ_ONLY_LOCAL.set(readOnly);
// 
//     return invocation.proceed();
//   }
// 
//   @Override
//   public Object plugin(Object target) {
//     if (target instanceof StatementHandler) {
//       return Plugin.wrap(target, this);
//     } else {
//       return target;
//     }
//   }
// 
//   @Override
//   public void setProperties(Properties properties) {
// 
//   }
// 
// }


import com.alibaba.druid.pool.DruidDataSource;
import com.github.mybatis.interceptor.MasterSlaveInterceptor;
import com.github.trace.TraceContext;
import com.google.common.base.CharMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
        // predicate: slave is always available
        target = getTargetConnection(method);
      }
    }

    // Target Connection already fetched,
    // or target Connection necessary for current operation ->
    // invoke method on target connection.
    try {
      return method.invoke(target, args);
    } catch (InvocationTargetException ex) {
      throw ex.getTargetException();
    } catch (Exception e) {
      log.error("[{}] method={}", router.getConfigName(), methodName, e);
      throw new RuntimeException("cannot invoke " + methodName, e);
    }
  }

  /**
   * Return whether the proxy currently holds a target Connection.
   */
  private boolean hasTargetConnection() {
    return (this.target != null);
  }

  /**
   * Return the target Connection, fetching it and initializing it if necessary.
   */
  private Connection getTargetConnection(Method operation) throws SQLException {
    if (this.target == null) {
// Path: src/main/java/com/github/mybatis/interceptor/MasterSlaveInterceptor.java
// @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
// public class MasterSlaveInterceptor implements Interceptor {
//   private static final ThreadLocal<Boolean> READ_ONLY_LOCAL = new ThreadLocal<Boolean>();
// 
//   public static boolean isReadOnly() {
//     return Boolean.TRUE.equals(READ_ONLY_LOCAL.get());
//   }
// 
//   @Override
//   public Object intercept(Invocation invocation) throws Throwable {
//     StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//     MappedStatement mappedStatement;
//     if (statementHandler instanceof RoutingStatementHandler) {
//       StatementHandler delegate = ReflectionUtil.getFieldValue(statementHandler, "delegate");
//       mappedStatement = ReflectionUtil.getFieldValue(delegate, "mappedStatement");
//     } else {
//       mappedStatement = ReflectionUtil.getFieldValue(statementHandler, "mappedStatement");
//     }
//     // 读写分离设置使用哪个数据源
//     Boolean readOnly = Boolean.FALSE;
//     if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
//       final String sql = statementHandler.getBoundSql().getSql();
//       String s = sql.toLowerCase();
//       readOnly = !(s.contains("last_insert_id()") || s.contains("row_count()"));
//     }
//     READ_ONLY_LOCAL.set(readOnly);
// 
//     return invocation.proceed();
//   }
// 
//   @Override
//   public Object plugin(Object target) {
//     if (target instanceof StatementHandler) {
//       return Plugin.wrap(target, this);
//     } else {
//       return target;
//     }
//   }
// 
//   @Override
//   public void setProperties(Properties properties) {
// 
//   }
// 
// }


// Path: src/main/java/com/github/mybatis/spring/LazyConnection.java
import com.alibaba.druid.pool.DruidDataSource;
import com.github.mybatis.interceptor.MasterSlaveInterceptor;
import com.github.trace.TraceContext;
import com.google.common.base.CharMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;

        // predicate: slave is always available
        target = getTargetConnection(method);
      }
    }

    // Target Connection already fetched,
    // or target Connection necessary for current operation ->
    // invoke method on target connection.
    try {
      return method.invoke(target, args);
    } catch (InvocationTargetException ex) {
      throw ex.getTargetException();
    } catch (Exception e) {
      log.error("[{}] method={}", router.getConfigName(), methodName, e);
      throw new RuntimeException("cannot invoke " + methodName, e);
    }
  }

  /**
   * Return whether the proxy currently holds a target Connection.
   */
  private boolean hasTargetConnection() {
    return (this.target != null);
  }

  /**
   * Return the target Connection, fetching it and initializing it if necessary.
   */
  private Connection getTargetConnection(Method operation) throws SQLException {
    if (this.target == null) {
      boolean readOnly = MasterSlaveInterceptor.isReadOnly();