Apache Commons-digester是一个非常便捷的XML-Java映射工具包,我们常用它作为规则引擎来使用,其中xml文件用来定义“规则”,通过digester解析之后,将会和javabean对象建立关系。其中struts/spring等第三方项目都使用digester作为xml-java映射工具;digester很好的实现了“xml定义规则”+“java业务处理”的桥接能力;今天我就给大家分享一下如何使用Digester工具设计“数据库分表”策略。
Digester的API非常简单,在此就不再赘言,直接使用实例讲解。
一.XML与规则:
你需要在XML文件中,定义“规则”,其中“规则”就类似于javabean的结构,告诉digester需要实例化什么javabean,不过xml格式必须是结构严谨的。【table-rules.xml】
<?xml version='1.0'?>
<!--
<rule id="this is the rule ID,it is required,cant be null.">
<table>this is table name,such as 'order'.</table>
<delimiter>
as the meaning of label,it's delimiter,'_' is the default value,
you can specify any readable words as delimiter.
</delimiter>
<size>
the total number of table' shards.
such as 64,it means there are 64 table-shards of the table.
</size>
<strategy>
you can see something about RouteStrategyEnum.java.
</strategy>
</rule>
-->
<rules>
<rule id="order">
<table>order</table>
<strategy>hash_code_mod</strategy>
<delimiter>_</delimiter>
<size>256</size>
</rule>
<rule id="order_rule">
<table>rder_rule</table>
<strategy>number_mod</strategy>
<delimiter>_</delimiter>
<size>256</size>
</rule>
</rules>
此XML定义了一个root节点为“rules”,其子节点为“rule”;既然是“数据库分表”,那么每个rule我们可以认为是一种table的声明,其中<table>表示表的实际名称,<strategy>表示此表根据什么策略分表的(比如根据“用户名”hash,根据“订单ID”取模等);<delimiter>表示用于生成表全名时各个参数的“链接符”,比如“_”;<size>表示此table总共有多少个子表,比如256张子表,那么数据将根据“strategy”将数据散列到256张表中,最终子表的实际格式为:<table><delimiter><[size]>,例如“order”表根据“订单id”取模共有256张子表,那么每个子表的名称为order_0 ~ order_255.
二.XML与“规则”模式:【table-rule-pattern.xml】
<?xml version="1.0"?>
<!DOCTYPE digester-rules
PUBLIC "-//Jakarta Apache //DTD digester-rules XML V1.0//EN"
"http://jakarta.apache.org/commons/digester/dtds/digester-rules.dtd">
<digester-rules>
<pattern value="rules">
<pattern value="rule">
<object-create-rule classname="com.test.tableRule.object.TableRule"/>
<set-properties-rule/>
<set-next-rule methodname="put"/>
<pattern value="table">
<call-method-rule methodname="setTable" paramcount="0" paramtypes="java.lang.String"/>
</pattern>
<pattern value="size">
<call-method-rule methodname="setSize" paramcount="0" paramtypes="java.lang.Integer"/>
</pattern>
<pattern value="type">
<call-method-rule methodname="setType" paramcount="0" paramtypes="java.lang.String"/>
</pattern>
<pattern value="delimiter">
<call-method-rule methodname="setDelimiter" paramcount="0" paramtypes="java.lang.String"/>
</pattern>
<pattern value="strategy">
<call-method-rule methodname="setStrategy" paramcount="0" paramtypes="java.lang.String"/>
</pattern>
</pattern>
</pattern>
</digester-rules>
这个文件是告知digester如何解析和映射"table-rules.xml",它的主要作用就是声明XML与javabean映射关系。这个xml中的格式是已经约定的,你不能随意创建自己的节点元素。
1) <pattern>的value属性为需要匹配的table-rules.xml中的元素节点名称,比如“rules”。
2) <object-create-rule>表示“遇到”指定pattern的节点,将会创建一个java对象,此对象的为className类型。被创建的对象,将放在解析stack的顶部。
3) <set-properties-rule>表示“对stack顶”的对象进行“属性赋值”操作,比如<rule>节点使用“set-properties”将会导致“id”属性的setter方法被调用。
4) <set-next-rule>获取将“解析stack顶”的对象(stack.pop()),并执行“top-element”对象的“methodname”指定的方法;(实例中讲解)
5) <call-method-rule>调用“解析stack顶”的对象的指定方法,参数值为当前pattern匹配的节点的值,paramcount为“方法的参数列表的个数”,0表示“只有一个参数”的方法,paramtypes为“方法的参数列表”的类型(","分割),如果paramtypes未声明,将会执行无参的方法。
三.Java实例:
1) 测试类:
public class XmlRuleParseTestMain {
public static void main(String[] args){
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//ָclasspath
Digester digester = DigesterLoader.createDigester(loader.getResource("table-rule-pattern.xml"));
digester.setClassLoader(loader);
final TableRulePool rulePool = TableRulePool.getInstance();
digester.push(rulePool);//top-element
try {
digester.parse(loader.getResource("table-rules.xml"));
}catch(Exception e){
e.printStackTrace();
}
List<TableRule> rules = rulePool.getAllRules();
for(TableRule rule : rules){
System.out.println(rule.getId() + ":" + rule.getTable() + rule.getDelimiter() + rule.getSize());
}
}
}
2) TableRulePool.java:用来保存XML解析的结果,所有的rules列表,它做为digester解析时的“top-element”,当解析table-rules.xml时,遇到“<rule>”节点时,将会创建TableRule对象,并根据<set-next-rule>中指定的put方法,将tableRule实例放入列表中。
/**
* @author liuguanqing
* hold all tablerules,client can get anything from it
*/
public class TableRulePool{
private final Map<String, TableRule> ruleMap = new ConcurrentHashMap<String, TableRule>(32);
private static TableRulePool instance;
private TableRulePool(){};
public void put(TableRule rule) {
if(rule.getId() == null) {
throw new NullPointerException("Rule's ID cannt be null!");
}
if(rule.getDelimiter() == null){
rule.setDelimiter(TableRule.DEFAULT_DELIMITER);
}
if(rule.getStrategy() == null){
rule.setStrategy(TableRule.DEFAULT_STRATEGY);
}
rule.init();
ruleMap.put(rule.getId(), rule);
}
public synchronized static TableRulePool getInstance(){
if(instance == null){
instance = new TableRulePool();
}
return instance;
}
public TableRule getRule(String ruleId){
return ruleMap.get(ruleId);
}
public List<TableRule> getAllRules(){
return new ArrayList<TableRule>(ruleMap.values());
}
}
3) TableRule.java:一个简单的POJO类,在解析table-rules.xml时,任何一个<rule>节点都会被映射成一个TableRule对象;在“规则模式”XML中,<object-create-rule>中指定。为了能够使用<set-properties-rule>,节点的属性(例如:id)需要有setter方法。<call-method-rule>中指定了执行tableRule实例的方法。
/**
* @author liuguanqing
* full name : table + delimiter + size,such as "mall_order_256"
*/
public class TableRule implements Serializable{
/**
*
*/
private static final long serialVersionUID = -5770269844973816977L;
public static final String DEFAULT_DELIMITER = "_";//
public static final String DEFAULT_STRATEGY = RouteStrategyEnum.NUMBER_MOD.getKey();//
private String id;//
//the strategy of data routing. such as "hash","number_mod"
private String strategy;
// prefix of the db-table,not included the "foot_number"
private String table;
private String delimiter;//
private Integer size = 0;//the num of subtables
//key is foot_number of subtable
//value is the fullname of subtable
//such as,0:"order_0",1:"order_1"
//I think it can be better than create a new subtable-name everytime.
private Map<Integer,String> indexes = new HashMap<Integer,String>();
public TableRule(){}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getStrategy() {
return strategy;
}
public void setStrategy(String strategy) {
this.strategy = strategy;
}
/**
* build inner map
*/
public void init(){
for(int i = 0; i < size; i++){
indexes.put(i, table + delimiter + i);
}
}
/**
* route by key,return the fullname of subtable
* @param key
* @return
*/
public String getTable(Object key){
if(key == null){
throw new NullPointerException("route key cant be null1");
}
int index;
if(key instanceof Number){
index = ModStrategy.numberMod((Number)key, this.size);
}else {
index = ModStrategy.hashCodeMod(key.toString(), this.size);
}
return indexes.get(index);
}
}
四.RuleSet:
在上述例子中,我们看到,如果实现XML-Javabean的映射,需要2个xml文件:table-rules.xml定义“规则”列表,table-rule-pattern.xml定义“规则”解析的方式;digester还提供了兼容性的模式,使用java设定“规则”解析的方式,我们可以将不需要table-rule-pattern.xml。
1) 测试类:
public class JavaRuleParseTestMain {
public static void main(String[] args){
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Digester digester = new Digester();
digester.setClassLoader(loader);
final TableRulePool rulePool = TableRulePool.getInstance();
digester.push(rulePool);
digester.addRuleSet(new ConfigRuleSet());
try {
digester.parse(loader.getResource("table-rules.xml"));
}catch(Exception e){
e.printStackTrace();
}
List<TableRule> rules = rulePool.getAllRules();
for(TableRule rule : rules){
System.out.println(rule.getId() + ":" + rule.getTable() + rule.getDelimiter() + rule.getSize());
}
//if you find something about username,you can do such as follow.
String username = "zhangsan";
TableRule orderRule = rulePool.getRule("order");
System.out.println("Full tablename is:" + orderRule.getTable(username));
}
}
2) ConfigRuleSet.java:使用java设定“规则”解析方式,取代table-rule-pattern.xml
public class ConfigRuleSet extends RuleSetBase {
@Override
public void addRuleInstances(Digester digester) {
//digester.push(TableRulePool.getInstance());
digester.addObjectCreate("*/rule", "com.test.tableRule.object.TableRule");
digester.addSetProperties("*/rule");
digester.addSetNext("*/rule", "put");
digester.addCallMethod("*/rule/table", "setTable",0,new String[]{"java.lang.String"});
digester.addCallMethod("*/rule/size", "setSize",0,new String[]{"java.lang.Integer"});
digester.addCallMethod("*/rule/type", "setSize",0,new String[]{"java.lang.String"});
digester.addCallMethod("*/rule/delimiter", "setDelimiter",0,new String[]{"java.lang.String"});
digester.addCallMethod("*/rule/strategy", "setStrategy",0,new String[]{"java.lang.String"});
}
}
可以看出java代码和xml的声明的方式很类似,顺序也需要一样。
--END--