(mybatis 版本3.2.*)java
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
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.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;spring
import rml.model.MUser;
import rml.util.ContextHelper;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class TableSplitInterceptor implements Interceptor {
private Log log =LogFactory.getLog(getClass());
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
doSplitTable(metaStatementHandler);
// 傳遞給下一個攔截器處理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標自己,減小目標被代理的次數
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
private void doSplitTable(MetaObject metaStatementHandler) throws ClassNotFoundException{
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
if (originalSql != null && !originalSql.equals("")) {
log.info("分表前的SQL:"+originalSql);
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
String id = mappedStatement.getId();
String className = id.substring(0, id.lastIndexOf("."));
Class<?> classObj = Class.forName(className);
MUser muser = (MUser)metaStatementHandler.getValue("delegate.boundSql.parameterObject");
// 根據配置自動生成分表SQL
TableSplit tableSplit = classObj.getAnnotation(TableSplit.class);
if (tableSplit != null && tableSplit.split()) {
StrategyManager strategyManager = ContextHelper.getStrategyManager();
Strategy strategy=strategyManager.getStrategy(tableSplit.strategy());//獲取分表策略來處理分表
String convertedSql=originalSql.toLowerCase().replaceAll(tableSplit.value(), strategy.convert(tableSplit.value(), muser.getName()));
metaStatementHandler.setValue("delegate.boundSql.sql",convertedSql);
log.info("分表後的SQL:"+convertedSql);
}
}
}
}sql
public interface Strategy {
/**
* 傳入一個須要分表的表名,返回一個處理後的表名
* Strategy必須包含一個無參構造器
* @param tableName
* @return
*/
public String convert(String tableName, String name);
}apache
import java.util.List;
import rml.model.MUser;
import rml.split.TableSplit;
@TableSplit(value="muser", strategy="%5")
public interface MUserMapper {
int deleteByPrimaryKey(String id);spring-mvc
int insert(MUser record);mybatis
int insertSelective(MUser record);mvc
MUser selectByPrimaryKey(String id);app
int updateByPrimaryKeySelective(MUser record);ide
int updateByPrimaryKey(MUser record);
List<MUser> getAll(MUser record);
}測試
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface TableSplit {
//是否分表
public boolean split() default true;
//表名
public String value();
//獲取分表策略
public String strategy();
}
public class NameStrategy implements Strategy{
@Override
public String convert(String tableName,String name) {
StringBuilder sb=new StringBuilder(tableName);
sb.append("_");
sb.append(Integer.parseInt(name)%5);
return sb.toString();
}
}
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class StrategyManager{
private Log log= LogFactory.getLog(StrategyManager.class);
private Map<String,Strategy> strategies = new ConcurrentHashMap<String,Strategy>(10);
public Strategy getStrategy(String key){
return strategies.get(key);
}
public Map<String, Strategy> getStrategies() {
return strategies;
}
public void setStrategies(Map<String, String> strategies) {
for(Entry<String, String> entry : strategies.entrySet()){
try {
this.strategies.put(entry.getKey(),(Strategy)Class.forName(entry.getValue()).newInstance());
} catch (Exception e) {
log.error("實例化策略出錯", e);
}
}
printDebugInfo();
}
private void printDebugInfo(){
StringBuffer msg= new StringBuffer("初始化了"+strategies.size()+"策略");
for(String key: strategies.keySet()){
msg.append("\n");
msg.append(key);
msg.append(" ---> ");
msg.append(strategies.get(key));
}
log.debug(msg.toString());
}
}
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import rml.split.StrategyManager;
@Component
public class ContextHelper implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
ContextHelper.context = context;
}
public static ApplicationContext getApplicationContext() {
return ContextHelper.context;
}
public static <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);
}
public static Object getBean(String name) {
return context.getBean(name);
}
public static <T> T getBean(String name, Class<T> clazz) {
return context.getBean(name, clazz);
}
public static StrategyManager getStrategyManager() {
return context.getBean(StrategyManager.class);
}
}
<!-- 配置分表策略 -->
<bean id="strategyManager" class="rml.split.StrategyManager">
<property name="strategies">
<map>
<entry key="%5" value="rml.split.NameStrategy" />
</map>
</property>
</bean>
<?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>
<plugins>
<plugin interceptor="rml.split.TableSplitInterceptor" />
</plugins>
</configuration>
/**
* 測試分庫分表規則
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring.xml, classpath*:spring-mvc.xml, classpath*:spring-mybatis.xml, classpath*:mybatis-config.xml" })
public class ShardingJdbcMybatisTest {
@Resource
public MUserMapper mUserMapper;
@Test
public void testuserinsert() {
MUser mUser = new MUser();
mUser.setName("5");
mUser.setAge(10);
mUser.setAddress("阿什頓發順豐");
Assert.assertEquals(mUserMapper.insert(mUser)>0, true);
}
}
打印結果
[rml.split.TableSplitInterceptor]分表前的SQL:insert into MUSER (NAME, AGE, ADDRESS) values (?, ?, ?) [org.springframework.beans.factory.support.DefaultListableBeanFactory]Returning cached instance of singleton bean 'strategyManager' [rml.split.TableSplitInterceptor]分表後的SQL:insert into muser_0 (name, age, address) values (?, ?, ?)