├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── assembly │ ├── dev.xml │ └── release.xml ├── bin │ ├── startup.bat │ ├── startup.sh │ └── stop.sh ├── java │ └── com │ │ └── alibaba │ │ └── otter │ │ └── clave │ │ ├── ClaveConfig.java │ │ ├── ClaveLauncher.java │ │ ├── ClaveLocator.java │ │ ├── common │ │ ├── convert │ │ │ ├── ByteArrayConverter.java │ │ │ ├── SqlTimestampConverter.java │ │ │ └── SqlUtils.java │ │ ├── datasource │ │ │ ├── DataMediaSource.java │ │ │ ├── DataMediaType.java │ │ │ ├── DataSourceService.java │ │ │ └── db │ │ │ │ ├── DbDataSourceService.java │ │ │ │ └── DbMediaSource.java │ │ ├── dialect │ │ │ ├── AbstractDbDialect.java │ │ │ ├── AbstractSqlTemplate.java │ │ │ ├── DbDialect.java │ │ │ ├── DbDialectFactory.java │ │ │ ├── DbDialectGenerator.java │ │ │ ├── SqlTemplate.java │ │ │ ├── lob │ │ │ │ ├── AutomaticJdbcExtractor.java │ │ │ │ └── LazyNativeJdbcExtractor.java │ │ │ ├── mysql │ │ │ │ ├── MysqlDialect.java │ │ │ │ └── MysqlSqlTemplate.java │ │ │ └── oracle │ │ │ │ ├── OracleDialect.java │ │ │ │ └── OracleSqlTemplate.java │ │ ├── lifecycle │ │ │ ├── AbstractClaveLifeCycle.java │ │ │ └── ClaveLifeCycle.java │ │ └── meta │ │ │ ├── DdlUtils.java │ │ │ ├── DdlUtilsFilter.java │ │ │ └── TableType.java │ │ ├── exceptions │ │ └── ClaveException.java │ │ ├── model │ │ ├── BatchObject.java │ │ ├── EventColumn.java │ │ ├── EventColumnIndexComparable.java │ │ ├── EventData.java │ │ ├── EventType.java │ │ ├── ObjectData.java │ │ └── RowBatch.java │ │ ├── progress │ │ ├── ClaveBoss.java │ │ ├── ClaveProgress.java │ │ ├── EtlConfig.java │ │ ├── extract │ │ │ └── ClaveExtractor.java │ │ ├── load │ │ │ ├── AbstractLoadContext.java │ │ │ ├── ClaveLoader.java │ │ │ ├── LoadContext.java │ │ │ ├── db │ │ │ │ ├── DataBaseLoader.java │ │ │ │ ├── DbLoadAction.java │ │ │ │ ├── DbLoadContext.java │ │ │ │ ├── DbLoadData.java │ │ │ │ ├── DbLoadDumper.java │ │ │ │ ├── DbLoadMerger.java │ │ │ │ └── inteceptor │ │ │ │ │ ├── LogLoadInterceptor.java │ │ │ │ │ ├── SqlBuilderLoadInterceptor.java │ │ │ │ │ └── operation │ │ │ │ │ ├── AbstractOperationInterceptor.java │ │ │ │ │ ├── ErosaMysqlInterceptor.java │ │ │ │ │ ├── ErosaOracleInterceptor.java │ │ │ │ │ └── OperationInterceptorFactory.java │ │ │ ├── interceptor │ │ │ │ ├── AbstractLoadInterceptor.java │ │ │ │ ├── ChainLoadInterceptor.java │ │ │ │ └── LoadInterceptor.java │ │ │ └── weight │ │ │ │ ├── WeightBarrier.java │ │ │ │ ├── WeightBuckets.java │ │ │ │ └── WeightController.java │ │ ├── select │ │ │ ├── ClaveSelector.java │ │ │ ├── Message.java │ │ │ └── canal │ │ │ │ ├── AbstractCanalSelector.java │ │ │ │ ├── CanalClientSelector.java │ │ │ │ ├── MessageDumper.java │ │ │ │ └── MessageParser.java │ │ └── transform │ │ │ └── ClaveTransform.java │ │ └── utils │ │ ├── ClaveToStringStyle.java │ │ └── spring │ │ ├── PropertyPlaceholderConfigurer.java │ │ └── SocketAddressEditor.java └── resources │ ├── clave.properties │ ├── logback.xml │ └── spring │ └── clave.xml └── test └── java └── com └── alibaba └── otter └── clave └── progress └── select └── CanalClientSelectorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .svn/ 2 | target/ 3 | test-output/ 4 | *.class 5 | .classpath 6 | .project 7 | .settings/ 8 | tmp 9 | temp 10 | *.log 11 | antx.properties 12 | otter.properties 13 | jtester.properties 14 | .idea/ 15 | *.iml 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clave 2 | ===== 3 | 4 | 基于canal的mysql slave实现 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.alibaba.otter 4 | clave 5 | jar 6 | clave module for otter ${project.version} 7 | 1.0.0-SNAPSHOT 8 | https://github.com/agapple/clave 9 | 10 | 11 | org.sonatype.oss 12 | oss-parent 13 | 7 14 | 15 | 16 | 17 | 18 | agapple 19 | http://agapple.iteye.com 20 | jianghang115@gmail.com 21 | 8 22 | 23 | 24 | 25 | 26 | 27 | Apache License, Version 2.0 28 | http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | 31 | 32 | 33 | git@github.com:agapple/clave.git 34 | scm:git:git@github.com:agapple/clave.git 35 | scm:git:git@github.com:agapple/clave.git 36 | 37 | 38 | 39 | 40 | java.net 41 | http://download.java.net/maven/2/ 42 | 43 | true 44 | 45 | 46 | false 47 | 48 | 49 | 50 | alibaba 51 | http://code.alibabatech.com/mvn/releases/ 52 | 53 | true 54 | 55 | 56 | false 57 | 58 | 59 | 60 | 61 | 62 | UTF-8 63 | 64 | true 65 | true 66 | 67 | 1.6 68 | 1.6 69 | UTF-8 70 | 71 | 72 | 73 | 74 | com.alibaba.otter 75 | canal.client 76 | 1.0.2 77 | 78 | 79 | org.springframework 80 | spring 81 | 2.5.6 82 | 83 | 84 | 85 | commons-lang 86 | commons-lang 87 | 2.6 88 | 89 | 90 | commons-io 91 | commons-io 92 | 2.4 93 | 94 | 95 | commons-beanutils 96 | commons-beanutils 97 | 1.8.3 98 | 99 | 100 | mysql 101 | mysql-connector-java 102 | 5.1.12 103 | 104 | 105 | org.apache.ddlutils 106 | ddlutils 107 | 1.0 108 | 109 | 110 | 111 | ch.qos.logback 112 | logback-core 113 | 1.0.6 114 | 115 | 116 | ch.qos.logback 117 | logback-classic 118 | 1.0.6 119 | 120 | 121 | org.slf4j 122 | jcl-over-slf4j 123 | 1.6.0 124 | 125 | 126 | org.slf4j 127 | slf4j-api 128 | 1.6.0 129 | 130 | 131 | 132 | junit 133 | junit 134 | 4.5 135 | test 136 | 137 | 138 | 139 | 140 | 141 | 142 | org.jvnet.wagon-svn 143 | wagon-svn 144 | 1.9 145 | 146 | 147 | org.apache.maven.wagon 148 | wagon-http-shared 149 | 1.0-beta-7 150 | 151 | 152 | 153 | 154 | org.apache.maven.plugins 155 | maven-compiler-plugin 156 | 157 | ${java_source_version} 158 | ${java_target_version} 159 | ${file_encoding} 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-surefire-plugin 165 | 2.5 166 | 167 | 168 | **/*Test.java 169 | 170 | 171 | **/*NoRunTest.java 172 | 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-surefire-plugin 178 | 2.5 179 | 180 | 181 | 235 | 236 | 237 | maven-jar-plugin 238 | 239 | 240 | true 241 | 242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-assembly-plugin 248 | 249 | 2.2.1 250 | 251 | 252 | assemble 253 | 254 | single 255 | 256 | package 257 | 258 | 259 | 260 | false 261 | false 262 | 263 | 264 | 265 | src/main/java 266 | src/test/java 267 | 268 | 269 | src/main/resources 270 | 271 | **/* 272 | 273 | 274 | **/.svn/ 275 | 276 | 277 | 278 | 279 | 280 | src/test/resources 281 | 282 | **/* 283 | 284 | 285 | **/.svn/ 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | dev 294 | 295 | true 296 | 297 | env 298 | !release 299 | 300 | 301 | 302 | 303 | 304 | 305 | maven-assembly-plugin 306 | 307 | 308 | 309 | ${basedir}/src/main/assembly/dev.xml 310 | 311 | clave 312 | ${project.build.directory} 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | release 322 | 323 | 324 | env 325 | release 326 | 327 | 328 | 329 | 330 | 331 | 332 | maven-assembly-plugin 333 | 334 | 335 | 336 | ${basedir}/src/main/assembly/release.xml 337 | 338 | 339 | ${project.artifactId}-${project.version} 340 | 341 | ${project.build.directory} 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | sonatype-nexus-snapshots 352 | Sonatype Nexus Snapshots 353 | https://oss.sonatype.org/content/repositories/snapshots/ 354 | 355 | 356 | sonatype-nexus-staging 357 | Nexus Release Repository 358 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 359 | 360 | 361 | 362 | -------------------------------------------------------------------------------- /src/main/assembly/dev.xml: -------------------------------------------------------------------------------- 1 | 3 | dist 4 | 5 | dir 6 | 7 | false 8 | 9 | 10 | . 11 | / 12 | 13 | README* 14 | 15 | 16 | 17 | ./src/main/bin 18 | bin 19 | 20 | **/* 21 | 22 | 0755 23 | 24 | 25 | ./src/main/resources 26 | /conf 27 | 28 | **/* 29 | 30 | 31 | 32 | target 33 | logs 34 | 35 | **/* 36 | 37 | 38 | 39 | 40 | 41 | lib 42 | 43 | junit:junit 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/assembly/release.xml: -------------------------------------------------------------------------------- 1 | 3 | dist 4 | 5 | tar.gz 6 | 7 | false 8 | 9 | 10 | . 11 | / 12 | 13 | README* 14 | 15 | 16 | 17 | ./src/main/bin 18 | bin 19 | 20 | **/* 21 | 22 | 0755 23 | 24 | 25 | ./src/main/resources 26 | /conf 27 | 28 | **/* 29 | 30 | 31 | 32 | target 33 | logs 34 | 35 | **/* 36 | 37 | 38 | 39 | 40 | 41 | lib 42 | 43 | junit:junit 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/bin/startup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | @if not "%ECHO%" == "" echo %ECHO% 3 | @if "%OS%" == "Windows_NT" setlocal 4 | 5 | set ENV_PATH=.\ 6 | if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% 7 | 8 | set conf_dir=%ENV_PATH%\..\conf 9 | set clave_conf=%conf_dir%\clave.properties 10 | set logback_configurationFile=%conf_dir%\logback.xml 11 | 12 | set CLASSPATH=%conf_dir% 13 | set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% 14 | 15 | set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m 16 | set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 17 | set JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n 18 | set clave_OPTS= -DappName=otter-clave -Dlogback.configurationFile="%logback_configurationFile%" -Dclave.conf="%clave_conf%" 19 | 20 | set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %clave_OPTS% 21 | 22 | java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.clave.ClaveLauncher -------------------------------------------------------------------------------- /src/main/bin/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | current_path=`pwd` 4 | bin_abs_path=$(readlink -f $(dirname $0)) 5 | 6 | base=${bin_abs_path}/.. 7 | clave_conf=$base/conf/clave.properties 8 | logback_configurationFile=$base/conf/logback.xml 9 | export LANG=en_US.UTF-8 10 | export BASE=$base 11 | 12 | if [ -f $base/bin/clave.pid ] ; then 13 | echo "found clave.pid , Please run stop.sh first ,then startup.sh" 2>&2 14 | exit 1 15 | fi 16 | 17 | ## set java path 18 | if [ -z "$JAVA" ] ; then 19 | JAVA=$(which java) 20 | fi 21 | 22 | ALIBABA_JAVA="/usr/alibaba/java/bin/java" 23 | TAOBAO_JAVA="/opt/taobao/java/bin/java" 24 | if [ -z "$JAVA" ]; then 25 | if [ -f $ALIBABA_JAVA ] ; then 26 | JAVA=$ALIBABA_JAVA 27 | elif [ -f $ALIBABA_JAVA ] ; then 28 | JAVA=$TAOBAO_JAVA 29 | else 30 | echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 31 | exit 1 32 | fi 33 | fi 34 | 35 | case "$#" 36 | in 37 | 0 ) 38 | ;; 39 | 1 ) 40 | var=$* 41 | if [ -f $var ] ; then 42 | clave_conf=$var 43 | else 44 | echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN." 45 | exit 46 | fi;; 47 | 2 ) 48 | var=$1 49 | if [ -f $var ] ; then 50 | clave_conf=$var 51 | else 52 | if [ "$1" = "debug" ]; then 53 | DEBUG_PORT=$2 54 | DEBUG_SUSPEND="y" 55 | JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND" 56 | fi 57 | fi;; 58 | * ) 59 | echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN." 60 | exit;; 61 | esac 62 | 63 | str=`file $JAVA_HOME/bin/java | grep 64-bit` 64 | if [ -n "$str" ]; then 65 | JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" 66 | else 67 | JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " 68 | fi 69 | 70 | JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" 71 | clave_OPTS="-DappName=otter-clave -Dlogback.configurationFile=$logback_configurationFile -Dclave.conf=$clave_conf" 72 | 73 | if [ -e $clave_conf -a -e $logback_configurationFile ] 74 | then 75 | 76 | for i in $base/lib/*; 77 | do CLASSPATH=$i:"$CLASSPATH"; 78 | done 79 | CLASSPATH="$base/conf:$CLASSPATH"; 80 | 81 | echo "cd to $bin_abs_path for workaround relative path" 82 | cd $bin_abs_path 83 | 84 | echo LOG CONFIGURATION : $logback_configurationFile 85 | echo clave conf : $clave_conf 86 | echo CLASSPATH :$CLASSPATH 87 | $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $clave_OPTS -classpath .:$CLASSPATH com.alibaba.otter.clave.ClaveLauncher 1>>$base/bin/nohup.out 2>&1 & 88 | echo $! > $base/bin/clave.pid 89 | 90 | echo "cd to $current_path for continue" 91 | cd $current_path 92 | else 93 | echo "clave conf("$clave_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!" 94 | fi -------------------------------------------------------------------------------- /src/main/bin/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cygwin=false; 4 | case "`uname`" in 5 | CYGWIN*) 6 | cygwin=true 7 | ;; 8 | esac 9 | 10 | get_pid() { 11 | STR=$1 12 | PID=$2 13 | if $cygwin; then 14 | JAVA_CMD="$JAVA_HOME\bin\java" 15 | JAVA_CMD=`cygpath --path --unix $JAVA_CMD` 16 | JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` 17 | else 18 | if [ ! -z "$PID" ]; then 19 | JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` 20 | else 21 | JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` 22 | fi 23 | fi 24 | echo $JAVA_PID; 25 | } 26 | 27 | base=`dirname $0`/.. 28 | pid=`cat $base/bin/clave.pid` 29 | if [ "$pid" == "" ] ; then 30 | pid=`get_pid "appName=otter-clave"` 31 | fi 32 | 33 | echo -e "`hostname`: stopping clave $pid ... " 34 | kill $pid 35 | 36 | LOOPS=0 37 | while (true); 38 | do 39 | gpid=`get_pid "appName=otter-clave" "$pid"` 40 | if [ "$gpid" == "" ] ; then 41 | echo "Oook! cost:$LOOPS" 42 | `rm $base/bin/clave.pid` 43 | break; 44 | fi 45 | let LOOPS=LOOPS+1 46 | sleep 1 47 | done -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/ClaveConfig.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave; 2 | 3 | public class ClaveConfig { 4 | 5 | /** 6 | * 在logback的配置文件中定义好进行日志文件输出的键值. 7 | */ 8 | public static String splitLogFileKey = "clave"; 9 | 10 | public static String SYSTEM_SCHEMA = "retl"; 11 | public static String SYSTEM_MARK_TABLE = "retl_mark"; 12 | public static String SYSTEM_BUFFER_TABLE = "retl_buffer"; 13 | public static String SYSTEM_DUAL_TABLE = "xdual"; 14 | public static String SYSTEM_TABLE_MARK_INFO_COLUMN = "server_info"; 15 | public static String SYSTEM_TABLE_MARK_ID_COLUMN = "server_id"; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/ClaveLauncher.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.alibaba.otter.clave.progress.ClaveBoss; 7 | 8 | /** 9 | * 启动一个clave实例 10 | * 11 | * @author jianghang 2012-2-20 下午09:04:09 12 | * @version 1.0.0 13 | */ 14 | public class ClaveLauncher { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(ClaveLauncher.class); 17 | 18 | public static void main(String[] args) { 19 | logger.info("INFO ## load the config"); 20 | ClaveLocator.getApplicationContext(); 21 | logger.info("INFO ## start clave ......"); 22 | ClaveBoss boss = ClaveLocator.getClaveBoss(); 23 | Runtime.getRuntime().addShutdownHook(new Thread() { 24 | 25 | public void run() { 26 | ClaveLocator.close(); 27 | } 28 | 29 | }); 30 | 31 | boss.start(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/ClaveLocator.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.support.ClassPathXmlApplicationContext; 5 | 6 | import com.alibaba.otter.clave.progress.ClaveBoss; 7 | 8 | /** 9 | * spring容器获取 10 | * 11 | * @author jianghang 2012-2-20 下午09:05:59 12 | * @version 1.0.0 13 | */ 14 | public class ClaveLocator { 15 | 16 | private static ApplicationContext context = null; 17 | private static RuntimeException initException = null; 18 | 19 | static { 20 | try { 21 | context = new ClassPathXmlApplicationContext("spring/clave.xml"); 22 | } catch (RuntimeException e) { 23 | throw e; 24 | } 25 | } 26 | 27 | public static ApplicationContext getApplicationContext() { 28 | if (context == null) { 29 | throw initException; 30 | } 31 | 32 | return context; 33 | } 34 | 35 | public static void close() { 36 | ((ClassPathXmlApplicationContext) context).close(); 37 | } 38 | 39 | public static ClaveBoss getClaveBoss() { 40 | return (ClaveBoss) getApplicationContext().getBean("boss"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/convert/ByteArrayConverter.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.convert; 2 | 3 | import org.apache.commons.beanutils.ConversionException; 4 | import org.apache.commons.beanutils.Converter; 5 | import org.apache.commons.beanutils.converters.ArrayConverter; 6 | import org.apache.commons.beanutils.converters.ByteConverter; 7 | 8 | /** 9 | * 需要特殊处理下byte类型数据,先尝试byte[]处理 10 | * 11 | * @author jianghang 2011-12-16 下午04:32:08 12 | * @version 1.0.0 13 | */ 14 | public class ByteArrayConverter implements Converter { 15 | 16 | public static final Converter SQL_BYTES = new ByteArrayConverter(null); 17 | private static final Converter converter = new ArrayConverter(byte[].class, new ByteConverter()); 18 | 19 | protected final Object defaultValue; 20 | protected final boolean useDefault; 21 | 22 | public ByteArrayConverter(){ 23 | this.defaultValue = null; 24 | this.useDefault = false; 25 | } 26 | 27 | public ByteArrayConverter(Object defaultValue){ 28 | this.defaultValue = defaultValue; 29 | this.useDefault = true; 30 | } 31 | 32 | public Object convert(Class type, Object value) { 33 | if (value == null) { 34 | if (useDefault) { 35 | return (defaultValue); 36 | } else { 37 | throw new ConversionException("No value specified"); 38 | } 39 | } 40 | 41 | if (value instanceof byte[]) { 42 | return (value); 43 | } 44 | 45 | try { 46 | return (value.toString()).getBytes("ISO-8859-1"); // 出来的数据为iso-8859-1编码 47 | } catch (Exception e) { 48 | try { 49 | return converter.convert(type, value); // 再尝试一下,按byteConvertor进行转化 50 | } catch (Exception e1) { 51 | throw new ConversionException(e.getMessage(), e1); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/convert/SqlTimestampConverter.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.convert; 2 | 3 | import java.sql.Timestamp; 4 | import java.text.ParseException; 5 | import java.text.ParsePosition; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.Locale; 9 | 10 | import org.apache.commons.beanutils.ConversionException; 11 | import org.apache.commons.beanutils.Converter; 12 | import org.apache.commons.lang.time.DateFormatUtils; 13 | 14 | /** 15 | * 处理时间类型转化, mysql/oracle的时间精度不一致,需要做兼容处理 16 | * 17 | * @author jianghang 2013-3-28 下午11:35:09 18 | * @version 1.0.0 19 | */ 20 | public class SqlTimestampConverter implements Converter { 21 | 22 | /** Field description */ 23 | public static final String[] DATE_FORMATS = new String[] { "yyyy-MM-dd", "HH:mm:ss", "yyyy-MM-dd HH:mm:ss", 24 | "yyyy-MM-dd hh:mm:ss.fffffffff", "EEE MMM dd HH:mm:ss zzz yyyy", 25 | DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), 26 | DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), 27 | DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(), }; 28 | 29 | public static final Converter SQL_TIMESTAMP = new SqlTimestampConverter(null); 30 | 31 | /** 32 | * The default value specified to our Constructor, if any. 33 | */ 34 | private final Object defaultValue; 35 | 36 | /** 37 | * Should we return the default value on conversion errors? 38 | */ 39 | private final boolean useDefault; 40 | 41 | /** 42 | * Create a {@link Converter} that will throw a {@link ConversionException} if a conversion error occurs. 43 | */ 44 | public SqlTimestampConverter(){ 45 | this.defaultValue = null; 46 | this.useDefault = false; 47 | } 48 | 49 | /** 50 | * Create a {@link Converter} that will return the specified default value if a conversion error occurs. 51 | * 52 | * @param defaultValue The default value to be returned 53 | */ 54 | public SqlTimestampConverter(Object defaultValue){ 55 | this.defaultValue = defaultValue; 56 | this.useDefault = true; 57 | } 58 | 59 | /** 60 | * Convert the specified input object into an output object of the specified type. 61 | * 62 | * @param type Data type to which this value should be converted 63 | * @param value The input value to be converted 64 | * @exception ConversionException if conversion cannot be performed successfully 65 | */ 66 | public Object convert(Class type, Object value) { 67 | if (value == null) { 68 | if (useDefault) { 69 | return (defaultValue); 70 | } else { 71 | throw new ConversionException("No value specified"); 72 | } 73 | } 74 | 75 | if (value instanceof java.sql.Date && java.sql.Date.class.equals(type)) { 76 | return value; 77 | } else if (value instanceof java.sql.Time && java.sql.Time.class.equals(type)) { 78 | return value; 79 | } else if (value instanceof java.sql.Timestamp && java.sql.Timestamp.class.equals(type)) { 80 | return value; 81 | } else { 82 | try { 83 | if (java.sql.Date.class.equals(type)) { 84 | return new java.sql.Date(convertTimestamp2TimeMillis(value.toString())); 85 | } else if (java.sql.Time.class.equals(type)) { 86 | return new java.sql.Time(convertTimestamp2TimeMillis(value.toString())); 87 | } else if (java.sql.Timestamp.class.equals(type)) { 88 | return new java.sql.Timestamp(convertTimestamp2TimeMillis(value.toString())); 89 | } else { 90 | return new Timestamp(convertTimestamp2TimeMillis(value.toString())); 91 | } 92 | } catch (Exception e) { 93 | throw new ConversionException("Value format invalid: " + e.getMessage(), e); 94 | } 95 | } 96 | 97 | } 98 | 99 | private Long convertTimestamp2TimeMillis(String input) { 100 | if (input == null) { 101 | return null; 102 | } 103 | 104 | try { 105 | // 先处理Timestamp类型 106 | return java.sql.Timestamp.valueOf(input).getTime(); 107 | } catch (Exception nfe) { 108 | try { 109 | try { 110 | return parseDate(input, DATE_FORMATS, Locale.ENGLISH).getTime(); 111 | } catch (Exception err) { 112 | return parseDate(input, DATE_FORMATS, Locale.getDefault()).getTime(); 113 | } 114 | } catch (Exception err) { 115 | // 最后处理long time的情况 116 | return Long.parseLong(input); 117 | } 118 | } 119 | } 120 | 121 | private Date parseDate(String str, String[] parsePatterns, Locale locale) throws ParseException { 122 | if ((str == null) || (parsePatterns == null)) { 123 | throw new IllegalArgumentException("Date and Patterns must not be null"); 124 | } 125 | 126 | SimpleDateFormat parser = null; 127 | ParsePosition pos = new ParsePosition(0); 128 | 129 | for (int i = 0; i < parsePatterns.length; i++) { 130 | if (i == 0) { 131 | parser = new SimpleDateFormat(parsePatterns[0], locale); 132 | } else { 133 | parser.applyPattern(parsePatterns[i]); 134 | } 135 | pos.setIndex(0); 136 | Date date = parser.parse(str, pos); 137 | if ((date != null) && (pos.getIndex() == str.length())) { 138 | return date; 139 | } 140 | } 141 | 142 | throw new ParseException("Unable to parse the date: " + str, -1); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/datasource/DataMediaSource.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.datasource; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import org.apache.commons.lang.builder.ToStringBuilder; 7 | 8 | import com.alibaba.otter.clave.utils.ClaveToStringStyle; 9 | 10 | /** 11 | * 数据介质源信息描述 12 | * 13 | * @author jianghang 2011-9-2 上午11:28:21 14 | */ 15 | public class DataMediaSource implements Serializable { 16 | 17 | private static final long serialVersionUID = -7653632703273608373L; 18 | private String name; 19 | private DataMediaType type; 20 | private String encode; 21 | private Date gmtCreate; 22 | private Date gmtModified; 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public DataMediaType getType() { 33 | return type; 34 | } 35 | 36 | public void setType(DataMediaType type) { 37 | this.type = type; 38 | } 39 | 40 | public Date getGmtCreate() { 41 | return gmtCreate; 42 | } 43 | 44 | public void setGmtCreate(Date gmtCreate) { 45 | this.gmtCreate = gmtCreate; 46 | } 47 | 48 | public Date getGmtModified() { 49 | return gmtModified; 50 | } 51 | 52 | public void setGmtModified(Date gmtModified) { 53 | this.gmtModified = gmtModified; 54 | } 55 | 56 | public String getEncode() { 57 | return encode; 58 | } 59 | 60 | public void setEncode(String encode) { 61 | this.encode = encode; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | final int prime = 31; 67 | int result = 1; 68 | result = prime * result + ((encode == null) ? 0 : encode.hashCode()); 69 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 70 | result = prime * result + ((type == null) ? 0 : type.hashCode()); 71 | return result; 72 | } 73 | 74 | @Override 75 | public boolean equals(Object obj) { 76 | if (this == obj) return true; 77 | if (obj == null) return false; 78 | if (getClass() != obj.getClass()) return false; 79 | DataMediaSource other = (DataMediaSource) obj; 80 | if (encode == null) { 81 | if (other.encode != null) return false; 82 | } else if (!encode.equals(other.encode)) return false; 83 | if (name == null) { 84 | if (other.name != null) return false; 85 | } else if (!name.equals(other.name)) return false; 86 | if (type != other.type) return false; 87 | return true; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return ToStringBuilder.reflectionToString(this, ClaveToStringStyle.DEFAULT_STYLE); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/datasource/DataMediaType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2004 Alibaba.com All right reserved. This software is the confidential and proprietary information of 3 | * Alibaba.com ("Confidential Information"). You shall not disclose such Confidential Information and shall use it only 4 | * in accordance with the terms of the license agreement you entered into with Alibaba.com. 5 | */ 6 | package com.alibaba.otter.clave.common.datasource; 7 | 8 | /** 9 | * @author jianghang 2011-9-2 上午11:36:21 10 | * @version 1.0.0 11 | */ 12 | public enum DataMediaType { 13 | 14 | /** mysql DB */ 15 | MYSQL, 16 | /** oracle DB */ 17 | ORACLE, 18 | /** cobar */ 19 | COBAR, 20 | /** tddl */ 21 | TDDL, 22 | /** cache */ 23 | MEMCACHE, 24 | /** mq */ 25 | MQ, 26 | /** napoli */ 27 | NAPOLI, 28 | /** diamond push for us */ 29 | DIAMOND_PUSH; 30 | 31 | public boolean isMysql() { 32 | return this.equals(DataMediaType.MYSQL); 33 | } 34 | 35 | public boolean isOracle() { 36 | return this.equals(DataMediaType.ORACLE); 37 | } 38 | 39 | public boolean isTddl() { 40 | return this.equals(DataMediaType.TDDL); 41 | } 42 | 43 | public boolean isCobar() { 44 | return this.equals(DataMediaType.COBAR); 45 | } 46 | 47 | public boolean isMemcache() { 48 | return this.equals(DataMediaType.MEMCACHE); 49 | } 50 | 51 | public boolean isMq() { 52 | return this.equals(DataMediaType.MQ); 53 | } 54 | 55 | public boolean isNapoli() { 56 | return this.equals(DataMediaType.NAPOLI); 57 | } 58 | 59 | public boolean isDiamondPush() { 60 | return this.equals(DataMediaType.DIAMOND_PUSH); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/datasource/DataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.datasource; 2 | 3 | /** 4 | * 数据介质源抽象 5 | * 6 | * @author jianghang 2013-3-28 下午10:57:15 7 | * @version 1.0.0 8 | */ 9 | public interface DataSourceService { 10 | 11 | /** 12 | * 返回操作数据源的句柄 13 | * 14 | * @param 15 | * @param dataMediaId 16 | * @return 17 | */ 18 | T getDataSource(DataMediaSource dataMediaSource); 19 | 20 | /** 21 | * 释放当前数据源. 22 | * 23 | * @param pipeline 24 | */ 25 | void destroy(DataMediaSource dataMediaSource); 26 | 27 | /** 28 | * 释放所有资源 29 | */ 30 | void destroy(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/datasource/db/DbDataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.datasource.db; 2 | 3 | import java.sql.SQLException; 4 | import java.util.Map; 5 | 6 | import javax.sql.DataSource; 7 | 8 | import org.apache.commons.dbcp.BasicDataSource; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.DisposableBean; 13 | import org.springframework.util.Assert; 14 | 15 | import com.alibaba.otter.clave.common.datasource.DataMediaSource; 16 | import com.alibaba.otter.clave.common.datasource.DataMediaType; 17 | import com.alibaba.otter.clave.common.datasource.DataSourceService; 18 | import com.google.common.base.Function; 19 | import com.google.common.collect.MapMaker; 20 | 21 | /** 22 | * 基于数据库的链接实现 23 | * 24 | * @author jianghang 2013-3-28 下午11:02:59 25 | * @version 1.0.0 26 | */ 27 | public class DbDataSourceService implements DataSourceService, DisposableBean { 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(DbDataSourceService.class); 30 | 31 | private int maxWait = 60 * 1000; 32 | 33 | private int minIdle = 0; 34 | 35 | private int initialSize = 0; 36 | 37 | private int maxActive = 32; 38 | 39 | private int maxIdle = 32; 40 | 41 | private int numTestsPerEvictionRun = -1; 42 | 43 | private int timeBetweenEvictionRunsMillis = 60 * 1000; 44 | 45 | private int removeAbandonedTimeout = 5 * 60; 46 | 47 | private int minEvictableIdleTimeMillis = 5 * 60 * 1000; 48 | 49 | private Map dataSources; 50 | 51 | public DbDataSourceService(){ 52 | dataSources = new MapMaker().makeComputingMap(new Function() { 53 | 54 | public DataSource apply(DbMediaSource dbMediaSource) { 55 | return createDataSource(dbMediaSource.getUrl(), dbMediaSource.getUsername(), 56 | dbMediaSource.getPassword(), dbMediaSource.getDriver(), 57 | dbMediaSource.getType(), dbMediaSource.getEncode()); 58 | 59 | } 60 | }); 61 | 62 | } 63 | 64 | public DataSource getDataSource(DataMediaSource dataMediaSource) { 65 | Assert.notNull(dataMediaSource); 66 | return dataSources.get(dataMediaSource); 67 | } 68 | 69 | public void destroy(DataMediaSource dataMediaSource) { 70 | DataSource source = dataSources.remove(dataMediaSource); 71 | if (source != null) { 72 | try { 73 | // fallback for regular destroy 74 | BasicDataSource basicDataSource = (BasicDataSource) source; 75 | basicDataSource.close(); 76 | } catch (SQLException e) { 77 | logger.error("ERROR ## close the datasource has an error", e); 78 | } 79 | } 80 | } 81 | 82 | public void destroy() { 83 | for (DataSource source : dataSources.values()) { 84 | if (source != null) { 85 | try { 86 | // fallback for regular destroy 87 | BasicDataSource basicDataSource = (BasicDataSource) source; 88 | basicDataSource.close(); 89 | } catch (SQLException e) { 90 | logger.error("ERROR ## close the datasource has an error", e); 91 | } 92 | } 93 | } 94 | } 95 | 96 | private DataSource createDataSource(String url, String userName, String password, String driverClassName, 97 | DataMediaType dataMediaType, String encoding) { 98 | 99 | return doCreateDataSource(url, userName, password, driverClassName, dataMediaType, encoding); 100 | } 101 | 102 | @SuppressWarnings("deprecation") 103 | protected DataSource doCreateDataSource(String url, String userName, String password, String driverClassName, 104 | DataMediaType dataMediaType, String encoding) { 105 | BasicDataSource dbcpDs = new BasicDataSource(); 106 | 107 | dbcpDs.setInitialSize(initialSize);// 初始化连接池时创建的连接数 108 | dbcpDs.setMaxActive(maxActive);// 连接池允许的最大并发连接数,值为非正数时表示不限制 109 | dbcpDs.setMaxIdle(maxIdle);// 连接池中的最大空闲连接数,超过时,多余的空闲连接将会被释放,值为负数时表示不限制 110 | dbcpDs.setMinIdle(minIdle);// 连接池中的最小空闲连接数,低于此数值时将会创建所欠缺的连接,值为0时表示不创建 111 | dbcpDs.setMaxWait(maxWait);// 以毫秒表示的当连接池中没有可用连接时等待可用连接返回的时间,超时则抛出异常,值为-1时表示无限等待 112 | dbcpDs.setRemoveAbandoned(true);// 是否清除已经超过removeAbandonedTimeout设置的无效连接 113 | dbcpDs.setLogAbandoned(true);// 当清除无效链接时是否在日志中记录清除信息的标志 114 | dbcpDs.setRemoveAbandonedTimeout(removeAbandonedTimeout); // 以秒表示清除无效链接的时限 115 | dbcpDs.setNumTestsPerEvictionRun(numTestsPerEvictionRun);// 确保连接池中没有已破损的连接 116 | dbcpDs.setTestOnBorrow(false);// 指定连接被调用时是否经过校验 117 | dbcpDs.setTestOnReturn(false);// 指定连接返回到池中时是否经过校验 118 | dbcpDs.setTestWhileIdle(true);// 指定连接进入空闲状态时是否经过空闲对象驱逐进程的校验 119 | dbcpDs.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 以毫秒表示空闲对象驱逐进程由运行状态进入休眠状态的时长,值为非正数时表示不运行任何空闲对象驱逐进程 120 | dbcpDs.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); // 以毫秒表示连接被空闲对象驱逐进程驱逐前在池中保持空闲状态的最小时间 121 | 122 | // 动态的参数 123 | dbcpDs.setDriverClassName(driverClassName); 124 | dbcpDs.setUrl(url); 125 | dbcpDs.setUsername(userName); 126 | dbcpDs.setPassword(password); 127 | 128 | if (dataMediaType.isOracle()) { 129 | dbcpDs.addConnectionProperty("restrictGetTables", "true"); 130 | dbcpDs.setValidationQuery("select 1 from dual"); 131 | } else if (dataMediaType.isMysql()) { 132 | // open the batch mode for mysql since 5.1.8 133 | dbcpDs.addConnectionProperty("useServerPrepStmts", "false"); 134 | dbcpDs.addConnectionProperty("rewriteBatchedStatements", "true"); 135 | if (StringUtils.isNotEmpty(encoding)) { 136 | dbcpDs.addConnectionProperty("characterEncoding", encoding); 137 | } 138 | dbcpDs.setValidationQuery("select 1"); 139 | } else { 140 | logger.error("ERROR ## Unknow database type"); 141 | } 142 | 143 | return dbcpDs; 144 | } 145 | 146 | public void setMaxWait(int maxWait) { 147 | this.maxWait = maxWait; 148 | } 149 | 150 | public void setMinIdle(int minIdle) { 151 | this.minIdle = minIdle; 152 | } 153 | 154 | public void setInitialSize(int initialSize) { 155 | this.initialSize = initialSize; 156 | } 157 | 158 | public void setMaxActive(int maxActive) { 159 | this.maxActive = maxActive; 160 | } 161 | 162 | public void setMaxIdle(int maxIdle) { 163 | this.maxIdle = maxIdle; 164 | } 165 | 166 | public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { 167 | this.numTestsPerEvictionRun = numTestsPerEvictionRun; 168 | } 169 | 170 | public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { 171 | this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; 172 | } 173 | 174 | public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) { 175 | this.removeAbandonedTimeout = removeAbandonedTimeout; 176 | } 177 | 178 | public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) { 179 | this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/datasource/db/DbMediaSource.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.datasource.db; 2 | 3 | import java.util.Properties; 4 | 5 | import com.alibaba.otter.clave.common.datasource.DataMediaSource; 6 | 7 | /** 8 | * 基于db的source信息 9 | * 10 | * @author jianghang 2013-3-28 下午10:55:02 11 | * @version 1.0.0 12 | */ 13 | public class DbMediaSource extends DataMediaSource { 14 | 15 | private static final long serialVersionUID = 2840851954936715456L; 16 | private String url; 17 | private String username; 18 | private String password; 19 | private String driver; 20 | private Properties properties; 21 | 22 | public String getUrl() { 23 | return url; 24 | } 25 | 26 | public void setUrl(String url) { 27 | this.url = url; 28 | } 29 | 30 | public String getUsername() { 31 | return username; 32 | } 33 | 34 | public void setUsername(String username) { 35 | this.username = username; 36 | } 37 | 38 | public String getPassword() { 39 | return password; 40 | } 41 | 42 | public void setPassword(String password) { 43 | this.password = password; 44 | } 45 | 46 | public String getDriver() { 47 | return driver; 48 | } 49 | 50 | public void setDriver(String driver) { 51 | this.driver = driver; 52 | } 53 | 54 | public Properties getProperties() { 55 | return properties; 56 | } 57 | 58 | public void setProperties(Properties properties) { 59 | this.properties = properties; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/AbstractDbDialect.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.apache.commons.lang.StringUtils; 11 | import org.apache.commons.lang.exception.NestableRuntimeException; 12 | import org.apache.ddlutils.model.Table; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.dao.DataAccessException; 16 | import org.springframework.jdbc.core.ConnectionCallback; 17 | import org.springframework.jdbc.core.JdbcTemplate; 18 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 19 | import org.springframework.jdbc.support.lob.LobHandler; 20 | import org.springframework.transaction.TransactionDefinition; 21 | import org.springframework.transaction.support.TransactionTemplate; 22 | import org.springframework.util.Assert; 23 | 24 | import com.alibaba.otter.clave.common.datasource.DataSourceService; 25 | import com.alibaba.otter.clave.common.meta.DdlUtils; 26 | import com.alibaba.otter.clave.common.meta.DdlUtilsFilter; 27 | import com.google.common.base.Function; 28 | import com.google.common.collect.GenericMapMaker; 29 | import com.google.common.collect.MapEvictionListener; 30 | import com.google.common.collect.MapMaker; 31 | 32 | /** 33 | * @author jianghang 2011-10-27 下午01:50:19 34 | * @version 4.0.0 35 | */ 36 | public abstract class AbstractDbDialect implements DbDialect { 37 | 38 | protected static final Logger logger = LoggerFactory.getLogger(AbstractDbDialect.class); 39 | protected int databaseMajorVersion; 40 | protected int databaseMinorVersion; 41 | protected String databaseName; 42 | protected DataSourceService dataSourceService; 43 | protected SqlTemplate sqlTemplate; 44 | protected JdbcTemplate jdbcTemplate; 45 | protected TransactionTemplate transactionTemplate; 46 | protected LobHandler lobHandler; 47 | protected Map, Table> tables; 48 | 49 | public AbstractDbDialect(final JdbcTemplate jdbcTemplate, LobHandler lobHandler){ 50 | this.jdbcTemplate = jdbcTemplate; 51 | this.lobHandler = lobHandler; 52 | // 初始化transction 53 | this.transactionTemplate = new TransactionTemplate(); 54 | transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource())); 55 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 56 | 57 | // 初始化一些数据 58 | jdbcTemplate.execute(new ConnectionCallback() { 59 | 60 | public Object doInConnection(Connection c) throws SQLException, DataAccessException { 61 | DatabaseMetaData meta = c.getMetaData(); 62 | databaseName = meta.getDatabaseProductName(); 63 | databaseMajorVersion = meta.getDatabaseMajorVersion(); 64 | databaseMinorVersion = meta.getDatabaseMinorVersion(); 65 | 66 | return null; 67 | } 68 | }); 69 | 70 | initTables(jdbcTemplate); 71 | } 72 | 73 | public AbstractDbDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler, String name, int majorVersion, 74 | int minorVersion){ 75 | this.jdbcTemplate = jdbcTemplate; 76 | this.lobHandler = lobHandler; 77 | // 初始化transction 78 | this.transactionTemplate = new TransactionTemplate(); 79 | transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource())); 80 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 81 | 82 | this.databaseName = name; 83 | this.databaseMajorVersion = majorVersion; 84 | this.databaseMinorVersion = minorVersion; 85 | 86 | initTables(jdbcTemplate); 87 | } 88 | 89 | public Table findTable(String schema, String table, boolean useCache) { 90 | List key = Arrays.asList(schema, table); 91 | if (useCache == false) { 92 | tables.remove(key); 93 | } 94 | 95 | return tables.get(key); 96 | } 97 | 98 | public Table findTable(String schema, String table) { 99 | return findTable(schema, table, true); 100 | } 101 | 102 | public void reloadTable(String schema, String table) { 103 | if (StringUtils.isNotEmpty(table)) { 104 | tables.remove(Arrays.asList(schema, table)); 105 | } else { 106 | // 如果没有存在表名,则直接清空所有的table,重新加载 107 | tables.clear(); 108 | } 109 | } 110 | 111 | public String getName() { 112 | return databaseName; 113 | } 114 | 115 | public int getMajorVersion() { 116 | return databaseMajorVersion; 117 | } 118 | 119 | @Override 120 | public int getMinorVersion() { 121 | return databaseMinorVersion; 122 | } 123 | 124 | public String getVersion() { 125 | return databaseMajorVersion + "." + databaseMinorVersion; 126 | } 127 | 128 | public LobHandler getLobHandler() { 129 | return lobHandler; 130 | } 131 | 132 | public JdbcTemplate getJdbcTemplate() { 133 | return jdbcTemplate; 134 | } 135 | 136 | public TransactionTemplate getTransactionTemplate() { 137 | return transactionTemplate; 138 | } 139 | 140 | public SqlTemplate getSqlTemplate() { 141 | return sqlTemplate; 142 | } 143 | 144 | public void destory() { 145 | } 146 | 147 | // ================================ helper method ========================== 148 | 149 | private void initTables(final JdbcTemplate jdbcTemplate) { 150 | // soft引用设置,避免内存爆了 151 | GenericMapMaker mapMaker = null; 152 | mapMaker = new MapMaker().softValues().evictionListener(new MapEvictionListener, Table>() { 153 | 154 | public void onEviction(List names, Table table) { 155 | logger.warn("Eviction For Table:" + table); 156 | } 157 | }); 158 | 159 | this.tables = mapMaker.makeComputingMap(new Function, Table>() { 160 | 161 | public Table apply(List names) { 162 | Assert.isTrue(names.size() == 2); 163 | try { 164 | beforeFindTable(jdbcTemplate, names.get(0), names.get(0), names.get(1)); 165 | DdlUtilsFilter filter = getDdlUtilsFilter(jdbcTemplate, names.get(0), names.get(0), names.get(1)); 166 | Table table = DdlUtils.findTable(jdbcTemplate, names.get(0), names.get(0), names.get(1), filter); 167 | afterFindTable(table, jdbcTemplate, names.get(0), names.get(0), names.get(1)); 168 | return table; 169 | } catch (Exception e) { 170 | throw new NestableRuntimeException("find table error : " + names.toString(), e); 171 | } 172 | } 173 | }); 174 | } 175 | 176 | protected DdlUtilsFilter getDdlUtilsFilter(JdbcTemplate jdbcTemplate, String catalogName, String schemaName, 177 | String tableName) { 178 | // we need to return null for backward compatibility 179 | return null; 180 | } 181 | 182 | protected void beforeFindTable(JdbcTemplate jdbcTemplate, String catalogName, String schemaName, String tableName) { 183 | // for subclass to extend 184 | } 185 | 186 | protected void afterFindTable(Table table, JdbcTemplate jdbcTemplate, String catalogName, String schemaName, 187 | String tableName) { 188 | // for subclass to extend 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/AbstractSqlTemplate.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | /** 4 | * 默认的基于标准SQL实现的CRUD sql封装 5 | * 6 | * @author jianghang 2011-10-27 下午01:37:00 7 | * @version 4.0.0 8 | */ 9 | public abstract class AbstractSqlTemplate implements SqlTemplate { 10 | 11 | private static final String DOT = "."; 12 | 13 | public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { 14 | StringBuilder sql = new StringBuilder("select "); 15 | int size = columnNames.length; 16 | for (int i = 0; i < size; i++) { 17 | sql.append(appendEscape(columnNames[i])).append((i + 1 < size) ? " , " : ""); 18 | } 19 | 20 | sql.append(" from ").append(getFullName(schemaName, tableName)).append(" where ( "); 21 | appendColumnEquals(sql, pkNames, "and"); 22 | sql.append(" ) "); 23 | return sql.toString().intern();// 不使用intern,避免方法区内存消耗过多 24 | } 25 | 26 | public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { 27 | StringBuilder sql = new StringBuilder("update " + getFullName(schemaName, tableName) + " set "); 28 | appendColumnEquals(sql, columnNames, ","); 29 | sql.append(" where ("); 30 | appendColumnEquals(sql, pkNames, "and"); 31 | sql.append(")"); 32 | return sql.toString().intern(); // 不使用intern,避免方法区内存消耗过多 33 | } 34 | 35 | public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { 36 | StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); 37 | String[] allColumns = new String[pkNames.length + columnNames.length]; 38 | System.arraycopy(columnNames, 0, allColumns, 0, columnNames.length); 39 | System.arraycopy(pkNames, 0, allColumns, columnNames.length, pkNames.length); 40 | 41 | int size = allColumns.length; 42 | for (int i = 0; i < size; i++) { 43 | sql.append(appendEscape(allColumns[i])).append((i + 1 < size) ? "," : ""); 44 | } 45 | 46 | sql.append(") values ("); 47 | appendColumnQuestions(sql, allColumns); 48 | sql.append(")"); 49 | return sql.toString().intern();// intern优化,避免出现大量相同的字符串 50 | } 51 | 52 | public String getDeleteSql(String schemaName, String tableName, String[] pkNames) { 53 | StringBuilder sql = new StringBuilder("delete from " + getFullName(schemaName, tableName) + " where "); 54 | appendColumnEquals(sql, pkNames, "and"); 55 | return sql.toString().intern();// intern优化,避免出现大量相同的字符串 56 | } 57 | 58 | protected String getFullName(String schemaName, String tableName) { 59 | StringBuilder sb = new StringBuilder(schemaName).append(DOT).append(tableName); 60 | return sb.toString().intern(); 61 | } 62 | 63 | // ================ helper method ============ 64 | 65 | protected String appendEscape(String columnName) { 66 | return columnName; 67 | } 68 | 69 | protected void appendColumnQuestions(StringBuilder sql, String[] columns) { 70 | int size = columns.length; 71 | for (int i = 0; i < size; i++) { 72 | sql.append("?").append((i + 1 < size) ? " , " : ""); 73 | } 74 | } 75 | 76 | protected void appendColumnEquals(StringBuilder sql, String[] columns, String separator) { 77 | int size = columns.length; 78 | for (int i = 0; i < size; i++) { 79 | sql.append(" ").append(appendEscape(columns[i])).append(" = ").append("? "); 80 | if (i != size - 1) { 81 | sql.append(separator); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/DbDialect.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | import org.apache.ddlutils.model.Table; 4 | import org.springframework.jdbc.core.JdbcTemplate; 5 | import org.springframework.jdbc.support.lob.LobHandler; 6 | import org.springframework.transaction.support.TransactionTemplate; 7 | 8 | /** 9 | * 数据库方言定义接口 10 | * 11 | * @author jianghang 2011-10-27 上午11:24:15 12 | * @version 4.0.0 13 | */ 14 | public interface DbDialect { 15 | 16 | public String getName(); 17 | 18 | public String getVersion(); 19 | 20 | public int getMajorVersion(); 21 | 22 | public int getMinorVersion(); 23 | 24 | public String getDefaultSchema(); 25 | 26 | public String getDefaultCatalog(); 27 | 28 | public boolean isCharSpacePadded(); 29 | 30 | public boolean isCharSpaceTrimmed(); 31 | 32 | public boolean isEmptyStringNulled(); 33 | 34 | public boolean isSupportMergeSql(); 35 | 36 | public LobHandler getLobHandler(); 37 | 38 | public JdbcTemplate getJdbcTemplate(); 39 | 40 | public TransactionTemplate getTransactionTemplate(); 41 | 42 | public SqlTemplate getSqlTemplate(); 43 | 44 | public Table findTable(String schema, String table); 45 | 46 | public Table findTable(String schema, String table, boolean useCache); 47 | 48 | public void reloadTable(String schema, String table); 49 | 50 | public void destory(); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/DbDialectFactory.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import java.util.Map; 7 | 8 | import javax.sql.DataSource; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.DisposableBean; 13 | import org.springframework.dao.DataAccessException; 14 | import org.springframework.jdbc.core.ConnectionCallback; 15 | import org.springframework.jdbc.core.JdbcTemplate; 16 | 17 | import com.alibaba.otter.clave.common.datasource.DataSourceService; 18 | import com.alibaba.otter.clave.common.datasource.db.DbMediaSource; 19 | import com.google.common.base.Function; 20 | import com.google.common.collect.MapMaker; 21 | 22 | /** 23 | * @author jianghang 2011-10-27 下午02:12:06 24 | * @version 1.0.0 25 | */ 26 | public class DbDialectFactory implements DisposableBean { 27 | 28 | private static final Logger logger = LoggerFactory.getLogger(DbDialectFactory.class); 29 | private DataSourceService dataSourceService; 30 | private DbDialectGenerator dbDialectGenerator; 31 | 32 | private Map dialects; 33 | 34 | public DbDialectFactory(){ 35 | dialects = new MapMaker().makeComputingMap(new Function() { 36 | 37 | public DbDialect apply(final DbMediaSource source) { 38 | DataSource dataSource = dataSourceService.getDataSource(source); 39 | final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 40 | return (DbDialect) jdbcTemplate.execute(new ConnectionCallback() { 41 | 42 | public Object doInConnection(Connection c) throws SQLException, DataAccessException { 43 | DatabaseMetaData meta = c.getMetaData(); 44 | String databaseName = meta.getDatabaseProductName(); 45 | int databaseMajorVersion = meta.getDatabaseMajorVersion(); 46 | int databaseMinorVersion = meta.getDatabaseMinorVersion(); 47 | DbDialect dialect = dbDialectGenerator.generate(jdbcTemplate, databaseName, 48 | databaseMajorVersion, databaseMinorVersion, 49 | source.getType()); 50 | if (dialect == null) { 51 | throw new UnsupportedOperationException("no dialect for" + databaseName); 52 | } 53 | 54 | if (logger.isInfoEnabled()) { 55 | logger.info(String.format( 56 | "--- DATABASE: %s, SCHEMA: %s ---", 57 | databaseName, 58 | (dialect.getDefaultSchema() == null) ? dialect.getDefaultCatalog() : dialect.getDefaultSchema())); 59 | } 60 | 61 | return dialect; 62 | } 63 | }); 64 | 65 | } 66 | }); 67 | 68 | } 69 | 70 | public DbDialect getDbDialect(DbMediaSource source) { 71 | return dialects.get(source); 72 | } 73 | 74 | public void destory(DbMediaSource source) { 75 | DbDialect dialect = dialects.remove(source); 76 | if (dialect != null) { 77 | dialect.destory(); 78 | } 79 | } 80 | 81 | public void destroy() throws Exception { 82 | for (DbDialect dialect : dialects.values()) { 83 | dialect.destory(); 84 | } 85 | } 86 | 87 | // =============== setter / getter ================= 88 | 89 | public void setDataSourceService(DataSourceService dataSourceService) { 90 | this.dataSourceService = dataSourceService; 91 | } 92 | 93 | public void setDbDialectGenerator(DbDialectGenerator dbDialectGenerator) { 94 | this.dbDialectGenerator = dbDialectGenerator; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/DbDialectGenerator.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.springframework.jdbc.core.JdbcTemplate; 5 | import org.springframework.jdbc.support.lob.LobHandler; 6 | 7 | import com.alibaba.otter.clave.common.datasource.DataMediaType; 8 | import com.alibaba.otter.clave.common.dialect.mysql.MysqlDialect; 9 | import com.alibaba.otter.clave.common.dialect.oracle.OracleDialect; 10 | 11 | /** 12 | * @author jianghang 2013-3-28 下午11:06:18 13 | * @version 1.0.0 14 | */ 15 | public class DbDialectGenerator { 16 | 17 | protected static final String ORACLE = "oracle"; 18 | protected static final String MYSQL = "mysql"; 19 | 20 | protected LobHandler defaultLobHandler; 21 | protected LobHandler oracleLobHandler; 22 | 23 | protected DbDialect generate(JdbcTemplate jdbcTemplate, String databaseName, int databaseMajorVersion, 24 | int databaseMinorVersion, DataMediaType dataMediaType) { 25 | DbDialect dialect = null; 26 | 27 | if (StringUtils.startsWithIgnoreCase(databaseName, ORACLE)) { // for oracle 28 | dialect = new OracleDialect(jdbcTemplate, oracleLobHandler, databaseName, databaseMajorVersion, 29 | databaseMinorVersion); 30 | } else if (StringUtils.startsWithIgnoreCase(databaseName, MYSQL)) { // for mysql 31 | dialect = new MysqlDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion, 32 | databaseMinorVersion); 33 | } 34 | 35 | return dialect; 36 | } 37 | 38 | // ======== setter ========= 39 | public void setDefaultLobHandler(LobHandler defaultLobHandler) { 40 | this.defaultLobHandler = defaultLobHandler; 41 | } 42 | 43 | public void setOracleLobHandler(LobHandler oracleLobHandler) { 44 | this.oracleLobHandler = oracleLobHandler; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/SqlTemplate.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect; 2 | 3 | /** 4 | * sql构造模板操作 5 | * 6 | * @author jianghang 2011-10-27 下午01:31:15 7 | * @version 4.0.0 8 | */ 9 | public interface SqlTemplate { 10 | 11 | public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); 12 | 13 | public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); 14 | 15 | public String getDeleteSql(String schemaName, String tableName, String[] pkNames); 16 | 17 | public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); 18 | 19 | /** 20 | * 获取对应的mergeSql 21 | */ 22 | public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, 23 | String[] viewColumnNames); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/lob/AutomaticJdbcExtractor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.lob; 2 | 3 | import java.sql.CallableStatement; 4 | import java.sql.Connection; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | import java.util.Iterator; 10 | import java.util.Map; 11 | 12 | import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; 13 | 14 | /** 15 | * 根据不同的数据源自动选择对应的NativeJdbcExtractor 16 | * 17 | * @author jianghang 2011-10-27 下午03:35:17 18 | * @version 1.0.0 19 | */ 20 | public class AutomaticJdbcExtractor implements NativeJdbcExtractor { 21 | 22 | private NativeJdbcExtractor defaultJdbcExtractor; 23 | private Map extractors; 24 | private NativeJdbcExtractor jdbcExtractor; 25 | 26 | public AutomaticJdbcExtractor(){ 27 | } 28 | 29 | public boolean isNativeConnectionNecessaryForNativeStatements() { 30 | return true; 31 | } 32 | 33 | public boolean isNativeConnectionNecessaryForNativePreparedStatements() { 34 | return true; 35 | } 36 | 37 | public boolean isNativeConnectionNecessaryForNativeCallableStatements() { 38 | return true; 39 | } 40 | 41 | public Connection getNativeConnection(Connection con) throws SQLException { 42 | return getJdbcExtractor(con).getNativeConnection(con); 43 | } 44 | 45 | private synchronized NativeJdbcExtractor getJdbcExtractor(Object o) { 46 | if (jdbcExtractor == null) { 47 | String objClass = o.getClass().getName(); 48 | Iterator iterator = extractors.keySet().iterator(); 49 | 50 | while (iterator.hasNext()) { 51 | String classPrefix = iterator.next(); 52 | 53 | if (objClass.indexOf(classPrefix) != -1) { 54 | jdbcExtractor = (NativeJdbcExtractor) extractors.get(classPrefix); 55 | 56 | break; 57 | } 58 | } 59 | 60 | if (jdbcExtractor == null) { 61 | jdbcExtractor = defaultJdbcExtractor; 62 | } 63 | } 64 | 65 | return jdbcExtractor; 66 | } 67 | 68 | public Connection getNativeConnectionFromStatement(Statement stmt) throws SQLException { 69 | return getJdbcExtractor(stmt).getNativeConnectionFromStatement(stmt); 70 | } 71 | 72 | public Statement getNativeStatement(Statement stmt) throws SQLException { 73 | return getJdbcExtractor(stmt).getNativeStatement(stmt); 74 | } 75 | 76 | public PreparedStatement getNativePreparedStatement(PreparedStatement ps) throws SQLException { 77 | return getJdbcExtractor(ps).getNativePreparedStatement(ps); 78 | } 79 | 80 | public CallableStatement getNativeCallableStatement(CallableStatement cs) throws SQLException { 81 | return getJdbcExtractor(cs).getNativeCallableStatement(cs); 82 | } 83 | 84 | public ResultSet getNativeResultSet(ResultSet rs) throws SQLException { 85 | return getJdbcExtractor(rs).getNativeResultSet(rs); 86 | } 87 | 88 | public Map getExtractors() { 89 | return extractors; 90 | } 91 | 92 | public void setExtractors(Map extractors) { 93 | this.extractors = extractors; 94 | } 95 | 96 | public NativeJdbcExtractor getDefaultJdbcExtractor() { 97 | return defaultJdbcExtractor; 98 | } 99 | 100 | public void setDefaultJdbcExtractor(NativeJdbcExtractor defaultJdbcExtractor) { 101 | this.defaultJdbcExtractor = defaultJdbcExtractor; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/lob/LazyNativeJdbcExtractor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.lob; 2 | 3 | import java.sql.CallableStatement; 4 | import java.sql.Connection; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | 10 | import org.apache.commons.lang.exception.NestableRuntimeException; 11 | import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; 12 | 13 | /** 14 | * A class to lazily instantiate a native JDBC extractor. 15 | *

16 | * We need to lazily instantiate it because otherwise Spring will construct it for us, and users might get class not 17 | * found errors (eg if they're not using Weblogic and Spring tries to load the WeblogicNativeJdbcExtractor, things get 18 | * ugly). 19 | */ 20 | public class LazyNativeJdbcExtractor implements NativeJdbcExtractor { 21 | 22 | private NativeJdbcExtractor delegatedExtractor; 23 | private Class extractorClass; 24 | 25 | public LazyNativeJdbcExtractor(){ 26 | } 27 | 28 | public void setExtractorClass(Class extractorClass) { 29 | this.extractorClass = extractorClass; 30 | } 31 | 32 | private synchronized NativeJdbcExtractor getDelegatedExtractor() { 33 | try { 34 | if (delegatedExtractor == null) { 35 | delegatedExtractor = (NativeJdbcExtractor) extractorClass.newInstance(); 36 | } 37 | } catch (IllegalAccessException e) { 38 | throw new NestableRuntimeException("Error occurred trying to instantiate a native extractor of type: " 39 | + extractorClass, e); 40 | } catch (InstantiationException e) { 41 | throw new NestableRuntimeException("Error occurred trying to instantiate a native extractor of type: " 42 | + extractorClass, e); 43 | } 44 | 45 | if (delegatedExtractor != null) { 46 | return delegatedExtractor; 47 | } else { 48 | throw new NestableRuntimeException("Error occurred trying to instantiate a native extractor of type: " 49 | + extractorClass); 50 | } 51 | } 52 | 53 | public boolean isNativeConnectionNecessaryForNativeStatements() { 54 | return getDelegatedExtractor().isNativeConnectionNecessaryForNativeStatements(); 55 | } 56 | 57 | public boolean isNativeConnectionNecessaryForNativePreparedStatements() { 58 | return getDelegatedExtractor().isNativeConnectionNecessaryForNativePreparedStatements(); 59 | } 60 | 61 | public boolean isNativeConnectionNecessaryForNativeCallableStatements() { 62 | return getDelegatedExtractor().isNativeConnectionNecessaryForNativeCallableStatements(); 63 | } 64 | 65 | public Connection getNativeConnection(Connection con) throws SQLException { 66 | return getDelegatedExtractor().getNativeConnection(con); 67 | } 68 | 69 | public Connection getNativeConnectionFromStatement(Statement stmt) throws SQLException { 70 | return getDelegatedExtractor().getNativeConnectionFromStatement(stmt); 71 | } 72 | 73 | public Statement getNativeStatement(Statement stmt) throws SQLException { 74 | return getDelegatedExtractor().getNativeStatement(stmt); 75 | } 76 | 77 | public PreparedStatement getNativePreparedStatement(PreparedStatement ps) throws SQLException { 78 | return getDelegatedExtractor().getNativePreparedStatement(ps); 79 | } 80 | 81 | public CallableStatement getNativeCallableStatement(CallableStatement cs) throws SQLException { 82 | return getDelegatedExtractor().getNativeCallableStatement(cs); 83 | } 84 | 85 | public ResultSet getNativeResultSet(ResultSet rs) throws SQLException { 86 | return getDelegatedExtractor().getNativeResultSet(rs); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/mysql/MysqlDialect.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.mysql; 2 | 3 | import org.springframework.jdbc.core.JdbcTemplate; 4 | import org.springframework.jdbc.support.lob.LobHandler; 5 | 6 | import com.alibaba.otter.clave.common.dialect.AbstractDbDialect; 7 | 8 | /** 9 | * 基于mysql的一些特殊处理定义 10 | * 11 | * @author jianghang 2011-10-27 下午01:46:57 12 | * @version 1.0.0 13 | */ 14 | public class MysqlDialect extends AbstractDbDialect { 15 | 16 | public MysqlDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler){ 17 | super(jdbcTemplate, lobHandler); 18 | sqlTemplate = new MysqlSqlTemplate(); 19 | } 20 | 21 | public MysqlDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler, String name, int majorVersion, 22 | int minorVersion){ 23 | super(jdbcTemplate, lobHandler, name, majorVersion, minorVersion); 24 | sqlTemplate = new MysqlSqlTemplate(); 25 | } 26 | 27 | public boolean isCharSpacePadded() { 28 | return false; 29 | } 30 | 31 | public boolean isCharSpaceTrimmed() { 32 | return true; 33 | } 34 | 35 | public boolean isEmptyStringNulled() { 36 | return false; 37 | } 38 | 39 | public boolean isSupportMergeSql() { 40 | return true; 41 | } 42 | 43 | public String getDefaultSchema() { 44 | return null; 45 | } 46 | 47 | public String getDefaultCatalog() { 48 | return (String) jdbcTemplate.queryForObject("select database()", String.class); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/mysql/MysqlSqlTemplate.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.mysql; 2 | 3 | import com.alibaba.otter.clave.common.dialect.AbstractSqlTemplate; 4 | 5 | /** 6 | * mysql sql生成模板 7 | * 8 | * @author jianghang 2011-10-27 下午01:41:20 9 | * @version 1.0.0 10 | */ 11 | public class MysqlSqlTemplate extends AbstractSqlTemplate { 12 | 13 | private static final String ESCAPE = "`"; 14 | 15 | public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, 16 | String[] viewColumnNames) { 17 | StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); 18 | int size = columnNames.length; 19 | for (int i = 0; i < size; i++) { 20 | sql.append(appendEscape(columnNames[i])).append(" , "); 21 | } 22 | size = pkNames.length; 23 | for (int i = 0; i < size; i++) { 24 | sql.append(appendEscape(pkNames[i])).append((i + 1 < size) ? " , " : ""); 25 | } 26 | 27 | sql.append(") values ("); 28 | size = columnNames.length; 29 | for (int i = 0; i < size; i++) { 30 | sql.append("?").append(" , "); 31 | } 32 | size = pkNames.length; 33 | for (int i = 0; i < size; i++) { 34 | sql.append("?").append((i + 1 < size) ? " , " : ""); 35 | } 36 | sql.append(")"); 37 | sql.append(" on duplicate key update "); 38 | 39 | // mysql merge sql匹配了uniqe / primary key时都会执行update,所以需要更新pk信息 40 | size = pkNames.length; 41 | for (int i = 0; i < size; i++) { 42 | sql.append(appendEscape(pkNames[i])).append("=values(").append(appendEscape(pkNames[i])).append(")").append( 43 | " , "); 44 | } 45 | 46 | size = columnNames.length; 47 | for (int i = 0; i < size; i++) { 48 | sql.append(appendEscape(columnNames[i])).append("=values(").append(appendEscape(columnNames[i])).append(")"); 49 | sql.append((i + 1 < size) ? " , " : ""); 50 | } 51 | 52 | return sql.toString().intern();// intern优化,避免出现大量相同的字符串 53 | } 54 | 55 | protected String appendEscape(String columnName) { 56 | return ESCAPE + columnName + ESCAPE; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/oracle/OracleDialect.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.oracle; 2 | 3 | import org.springframework.jdbc.core.JdbcTemplate; 4 | import org.springframework.jdbc.support.lob.LobHandler; 5 | 6 | import com.alibaba.otter.clave.common.dialect.AbstractDbDialect; 7 | 8 | /** 9 | * 基于oracle的一些特殊处理定义 10 | * 11 | * @author jianghang 2011-10-27 下午01:44:46 12 | * @version 1.0.0 13 | */ 14 | public class OracleDialect extends AbstractDbDialect { 15 | 16 | public OracleDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler){ 17 | super(jdbcTemplate, lobHandler); 18 | sqlTemplate = new OracleSqlTemplate(); 19 | } 20 | 21 | public OracleDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler, String name, int majorVersion, 22 | int minorVersion){ 23 | super(jdbcTemplate, lobHandler, name, majorVersion, minorVersion); 24 | sqlTemplate = new OracleSqlTemplate(); 25 | } 26 | 27 | public boolean isCharSpacePadded() { 28 | return true; 29 | } 30 | 31 | public boolean isCharSpaceTrimmed() { 32 | return false; 33 | } 34 | 35 | public boolean isEmptyStringNulled() { 36 | return true; 37 | } 38 | 39 | public boolean storesUpperCaseNamesInCatalog() { 40 | return true; 41 | } 42 | 43 | public boolean isSupportMergeSql() { 44 | return true; 45 | } 46 | 47 | public String getDefaultCatalog() { 48 | return null; 49 | } 50 | 51 | public String getDefaultSchema() { 52 | return (String) jdbcTemplate.queryForObject("SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual", 53 | String.class); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/dialect/oracle/OracleSqlTemplate.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.dialect.oracle; 2 | 3 | import com.alibaba.otter.clave.common.dialect.AbstractSqlTemplate; 4 | 5 | /** 6 | * oracle生成模板 7 | * 8 | * @author jianghang 2011-10-27 下午01:41:34 9 | * @version 1.0.0 10 | */ 11 | public class OracleSqlTemplate extends AbstractSqlTemplate { 12 | 13 | /** 14 | * http://en.wikipedia.org/wiki/Merge_(SQL) 15 | */ 16 | public String getMergeSql(String schemaName, String tableName, String[] keyNames, String[] columnNames, 17 | String[] viewColumnNames) { 18 | final String aliasA = "a"; 19 | final String aliasB = "b"; 20 | StringBuilder sql = new StringBuilder(); 21 | 22 | sql.append("merge /*+ use_nl(a b)*/ into ").append(getFullName(schemaName, tableName)).append(" ").append( 23 | aliasA); 24 | sql.append(" using (select "); 25 | 26 | int size = columnNames.length; 27 | // 构建 (select ? as col1, ? as col2 from dual) 28 | for (int i = 0; i < size; i++) { 29 | sql.append("? as " + columnNames[i]).append(" , "); 30 | } 31 | size = keyNames.length; 32 | for (int i = 0; i < size; i++) { 33 | sql.append("? as " + keyNames[i]).append((i + 1 < size) ? " , " : ""); 34 | } 35 | sql.append(" from dual) ").append(aliasB); 36 | sql.append(" on ("); 37 | 38 | size = keyNames.length; 39 | for (int i = 0; i < size; i++) { 40 | sql.append(aliasA + "." + keyNames[i]).append("=").append(aliasB + "." + keyNames[i]); 41 | sql.append((i + 1 < size) ? " and " : ""); 42 | } 43 | 44 | sql.append(") when matched then update set "); 45 | 46 | size = columnNames.length; 47 | for (int i = 0; i < size; i++) { 48 | sql.append(aliasA + "." + columnNames[i]).append("=").append(aliasB + "." + columnNames[i]); 49 | sql.append((i + 1 < size) ? " , " : ""); 50 | } 51 | 52 | sql.append(" when not matched then insert ("); 53 | size = columnNames.length; 54 | for (int i = 0; i < size; i++) { 55 | sql.append(aliasA + "." + columnNames[i]).append(" , "); 56 | } 57 | size = keyNames.length; 58 | for (int i = 0; i < size; i++) { 59 | sql.append(aliasA + "." + keyNames[i]).append((i + 1 < size) ? " , " : ""); 60 | } 61 | 62 | sql.append(" ) values ("); 63 | size = columnNames.length; 64 | for (int i = 0; i < size; i++) { 65 | sql.append(aliasB + "." + columnNames[i]).append(" , "); 66 | } 67 | size = keyNames.length; 68 | for (int i = 0; i < size; i++) { 69 | sql.append(aliasB + "." + keyNames[i]).append((i + 1 < size) ? " , " : ""); 70 | } 71 | sql.append(" )"); 72 | return sql.toString().intern(); // intern优化,避免出现大量相同的字符串 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/lifecycle/AbstractClaveLifeCycle.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.lifecycle; 2 | 3 | import com.alibaba.otter.clave.exceptions.ClaveException; 4 | 5 | /** 6 | * 基本实现 7 | * 8 | * @author jianghang 2012-7-12 上午10:11:07 9 | * @version 1.0.0 10 | */ 11 | public abstract class AbstractClaveLifeCycle implements ClaveLifeCycle { 12 | 13 | protected volatile boolean running = false; // 是否处于运行中 14 | 15 | public boolean isStart() { 16 | return running; 17 | } 18 | 19 | public void start() { 20 | if (running) { 21 | throw new ClaveException(this.getClass().getName() + " has startup , don't repeat start"); 22 | } 23 | 24 | running = true; 25 | } 26 | 27 | public void stop() { 28 | if (!running) { 29 | throw new ClaveException(this.getClass().getName() + " isn't start , please check"); 30 | } 31 | 32 | running = false; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/lifecycle/ClaveLifeCycle.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.lifecycle; 2 | 3 | /** 4 | * @author jianghang 2012-7-12 上午09:39:33 5 | * @version 1.0.0 6 | */ 7 | public interface ClaveLifeCycle { 8 | 9 | public void start(); 10 | 11 | public void stop(); 12 | 13 | public boolean isStart(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/meta/DdlUtilsFilter.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.meta; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | 8 | /** 9 | * @author zebin.xuzb @ 2012-8-8 10 | * @version 4.1.0 11 | */ 12 | public abstract class DdlUtilsFilter { 13 | 14 | /** 15 | * 返回要获取 {@linkplain DatabaseMetaData} 的 {@linkplain Connection},不能返回null 16 | * 17 | * @param con 18 | * @return 19 | */ 20 | public Connection filterConnection(Connection con) throws Exception { 21 | return con; 22 | } 23 | 24 | /** 25 | * 对 databaseMetaData 做一些过滤,返回 {@linkplain DatabaseMetaData},不能为 null 26 | * 27 | * @param databaseMetaData 28 | * @return 29 | */ 30 | public DatabaseMetaData filterDataBaseMetaData(JdbcTemplate jdbcTemplate, Connection con, 31 | DatabaseMetaData databaseMetaData) throws Exception { 32 | return databaseMetaData; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/common/meta/TableType.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.common.meta; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Locale; 6 | 7 | /** 8 | * An enumeration wrapper around JDBC table types. 9 | */ 10 | public enum TableType { 11 | 12 | /** 13 | * Unknown 14 | */ 15 | unknown, 16 | 17 | /** 18 | * System table 19 | */ 20 | system_table, 21 | 22 | /** 23 | * Global temporary 24 | */ 25 | global_temporary, 26 | 27 | /** 28 | * Local temporary 29 | */ 30 | local_temporary, 31 | 32 | /** 33 | * Table 34 | */ 35 | table, 36 | 37 | /** 38 | * View 39 | */ 40 | view, 41 | 42 | /** 43 | * Alias 44 | */ 45 | alias, 46 | 47 | /** 48 | * Synonym 49 | */ 50 | synonym, ; 51 | 52 | /** 53 | * Converts an array of table types to an array of their corresponding string values. 54 | * 55 | * @param tableTypes Array of table types 56 | * @return Array of string table types 57 | */ 58 | public static String[] toStrings(final TableType[] tableTypes) { 59 | if ((tableTypes == null) || (tableTypes.length == 0)) { 60 | return new String[0]; 61 | } 62 | 63 | final List tableTypeStrings = new ArrayList(tableTypes.length); 64 | 65 | for (final TableType tableType : tableTypes) { 66 | if (tableType != null) { 67 | tableTypeStrings.add(tableType.toString().toUpperCase(Locale.ENGLISH)); 68 | } 69 | } 70 | 71 | return tableTypeStrings.toArray(new String[tableTypeStrings.size()]); 72 | } 73 | 74 | /** 75 | * Converts an array of string table types to an array of their corresponding enumeration values. 76 | * 77 | * @param tableTypeStrings Array of string table types 78 | * @return Array of table types 79 | */ 80 | public static TableType[] valueOf(final String[] tableTypeStrings) { 81 | if ((tableTypeStrings == null) || (tableTypeStrings.length == 0)) { 82 | return new TableType[0]; 83 | } 84 | 85 | final List tableTypes = new ArrayList(tableTypeStrings.length); 86 | 87 | for (final String tableTypeString : tableTypeStrings) { 88 | tableTypes.add(valueOf(tableTypeString.toLowerCase(Locale.ENGLISH))); 89 | } 90 | 91 | return tableTypes.toArray(new TableType[tableTypes.size()]); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/exceptions/ClaveException.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.exceptions; 2 | 3 | import org.apache.commons.lang.exception.NestableRuntimeException; 4 | 5 | public class ClaveException extends NestableRuntimeException { 6 | 7 | private static final long serialVersionUID = -7288830284122672209L; 8 | 9 | private String errorCode; 10 | private String errorDesc; 11 | 12 | public ClaveException(String errorCode) { 13 | super(errorCode); 14 | } 15 | 16 | public ClaveException(String errorCode, Throwable cause) { 17 | super(errorCode, cause); 18 | } 19 | 20 | public ClaveException(String errorCode, String errorDesc) { 21 | super(errorCode + ":" + errorDesc); 22 | } 23 | 24 | public ClaveException(String errorCode, String errorDesc, Throwable cause) { 25 | super(errorCode + ":" + errorDesc, cause); 26 | } 27 | 28 | public ClaveException(Throwable cause) { 29 | super(cause); 30 | } 31 | 32 | public String getErrorCode() { 33 | return errorCode; 34 | } 35 | 36 | public String getErrorDesc() { 37 | return errorDesc; 38 | } 39 | 40 | @Override 41 | public Throwable fillInStackTrace() { 42 | return this; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/BatchObject.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.apache.commons.lang.builder.ToStringBuilder; 6 | 7 | import com.alibaba.otter.clave.utils.ClaveToStringStyle; 8 | 9 | /** 10 | * 数据batch对象 11 | * 12 | * @author jianghang 2013-3-28 下午11:19:13 13 | * @version 1.0.0 14 | */ 15 | public abstract class BatchObject implements Serializable { 16 | 17 | private static final long serialVersionUID = 3211077130963551303L; 18 | 19 | public abstract void merge(T data); 20 | 21 | @Override 22 | public String toString() { 23 | return ToStringBuilder.reflectionToString(this, ClaveToStringStyle.DEFAULT_STYLE); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/EventColumn.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.apache.commons.lang.builder.ToStringBuilder; 6 | 7 | import com.alibaba.otter.clave.utils.ClaveToStringStyle; 8 | 9 | /** 10 | * 描述一个字段信息 11 | * 12 | * @author jianghang 2013-3-28 下午09:15:43 13 | * @version 1.0.0 14 | */ 15 | public class EventColumn implements Serializable { 16 | 17 | private static final long serialVersionUID = 8881024631437131042L; 18 | 19 | private int index; 20 | 21 | private int columnType; 22 | 23 | private String columnName; 24 | 25 | /** 26 | * timestamp,Datetime是一个long型的数字. 27 | */ 28 | private String columnValue; 29 | 30 | private boolean isNull; 31 | 32 | private boolean isKey; 33 | 34 | /** 35 | * 2012.08.09 add by ljh , 新加字段,用于表明是否为真实变更字段,只针对非主键字段有效
36 | * 因为FileResolver/EventProcessor会需要所有字段数据做分析,但又想保留按需字段同步模式 37 | * 38 | *

 39 |      * 1. row模式,所有字段均为updated
 40 |      * 2. field模式,通过store/db反查得到的结果,均为updated
 41 |      * 3. 其余场景,根据判断是否变更过,设置updated数据
 42 |      * 
43 | */ 44 | private boolean isUpdate = true; 45 | 46 | public int getColumnType() { 47 | return columnType; 48 | } 49 | 50 | public void setColumnType(int columnType) { 51 | this.columnType = columnType; 52 | } 53 | 54 | public String getColumnName() { 55 | return columnName; 56 | } 57 | 58 | public void setColumnName(String columnName) { 59 | this.columnName = columnName; 60 | } 61 | 62 | public String getColumnValue() { 63 | if (isNull) { 64 | // 如果为null值,强制设置为null, eromanga主要是走protobuf协议,String值默认为空字符,无法标示为null对象 65 | columnValue = null; 66 | return null; 67 | } else { 68 | return columnValue; 69 | } 70 | } 71 | 72 | public void setColumnValue(String columnValue) { 73 | this.columnValue = columnValue; 74 | } 75 | 76 | public boolean isNull() { 77 | return isNull; 78 | } 79 | 80 | public void setNull(boolean isNull) { 81 | this.isNull = isNull; 82 | } 83 | 84 | public boolean isKey() { 85 | return isKey; 86 | } 87 | 88 | public void setKey(boolean isKey) { 89 | this.isKey = isKey; 90 | } 91 | 92 | public int getIndex() { 93 | return index; 94 | } 95 | 96 | public void setIndex(int index) { 97 | this.index = index; 98 | } 99 | 100 | public boolean isUpdate() { 101 | return isUpdate; 102 | } 103 | 104 | 105 | public void setUpdate(boolean isUpdate) { 106 | this.isUpdate = isUpdate; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | final int prime = 31; 112 | int result = 1; 113 | result = prime * result + ((columnName == null) ? 0 : columnName.hashCode()); 114 | result = prime * result + columnType; 115 | result = prime * result + ((columnValue == null) ? 0 : columnValue.hashCode()); 116 | result = prime * result + index; 117 | result = prime * result + (isKey ? 1231 : 1237); 118 | result = prime * result + (isNull ? 1231 : 1237); 119 | result = prime * result + (isUpdate ? 1231 : 1237); 120 | return result; 121 | } 122 | 123 | @Override 124 | public boolean equals(Object obj) { 125 | if (this == obj) return true; 126 | if (obj == null) return false; 127 | if (getClass() != obj.getClass()) return false; 128 | EventColumn other = (EventColumn) obj; 129 | if (columnName == null) { 130 | if (other.columnName != null) return false; 131 | } else if (!columnName.equals(other.columnName)) return false; 132 | if (columnType != other.columnType) return false; 133 | if (columnValue == null) { 134 | if (other.columnValue != null) return false; 135 | } else if (!columnValue.equals(other.columnValue)) return false; 136 | if (index != other.index) return false; 137 | if (isKey != other.isKey) return false; 138 | if (isNull != other.isNull) return false; 139 | if (isUpdate != other.isUpdate) return false; 140 | return true; 141 | } 142 | 143 | public String toString() { 144 | return ToStringBuilder.reflectionToString(this, ClaveToStringStyle.DEFAULT_STYLE); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/EventColumnIndexComparable.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.util.Comparator; 4 | 5 | /** 6 | * 按照EventColumn的index进行排序 7 | * 8 | * @author jianghang 2013-3-28 下午09:18:28 9 | * @version 1.0.0 10 | */ 11 | public class EventColumnIndexComparable implements Comparator { 12 | 13 | public int compare(EventColumn o1, EventColumn o2) { 14 | if (o1.getIndex() < o2.getIndex()) { 15 | return -1; 16 | } else if (o1.getIndex() == o2.getIndex()) { 17 | return 0; 18 | } else { 19 | return 1; 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/EventData.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang.builder.ToStringBuilder; 7 | 8 | import com.alibaba.otter.clave.utils.ClaveToStringStyle; 9 | 10 | /** 11 | * 描述一个变更事件 12 | * 13 | * @author jianghang 2013-3-28 下午09:17:18 14 | * @version 1.0.0 15 | */ 16 | public class EventData implements ObjectData { 17 | 18 | private static final long serialVersionUID = -7071677425383765372L; 19 | 20 | private String tableName; 21 | 22 | private String schemaName; 23 | 24 | /** 25 | * 变更数据的业务类型(I/U/D/C/A/E),与ErosaProtocol中定义的EventType一致. 26 | */ 27 | private EventType eventType; 28 | 29 | /** 30 | * 变更数据的业务时间. 31 | */ 32 | private long executeTime; 33 | 34 | /** 35 | * 变更前的主键值,如果是insert/delete变更前和变更后的主键值是一样的. 36 | */ 37 | private List oldKeys = new ArrayList(); 38 | 39 | /** 40 | * 变更后的主键值,如果是insert/delete变更前和变更后的主键值是一样的. 41 | */ 42 | private List keys = new ArrayList(); 43 | 44 | private List columns = new ArrayList(); 45 | 46 | // ====================== 运行过程中对数据的附加属性 ============================= 47 | /** 48 | * 预计的size大小,基于binlog event的推算 49 | */ 50 | private long size = 1024; 51 | 52 | /** 53 | * 当eventType = CREATE/ALTER/ERASE时,就是对应的sql语句,否则无效. 54 | */ 55 | private String sql; 56 | 57 | public String getTableName() { 58 | return tableName; 59 | } 60 | 61 | public void setTableName(String tableName) { 62 | this.tableName = tableName; 63 | } 64 | 65 | public String getSchemaName() { 66 | return schemaName; 67 | } 68 | 69 | public void setSchemaName(String schemaName) { 70 | this.schemaName = schemaName; 71 | } 72 | 73 | public EventType getEventType() { 74 | return eventType; 75 | } 76 | 77 | public void setEventType(EventType eventType) { 78 | this.eventType = eventType; 79 | } 80 | 81 | public String getSql() { 82 | return sql; 83 | } 84 | 85 | public void setSql(String sql) { 86 | this.sql = sql; 87 | } 88 | 89 | public long getExecuteTime() { 90 | return executeTime; 91 | } 92 | 93 | public void setExecuteTime(long executeTime) { 94 | this.executeTime = executeTime; 95 | } 96 | 97 | public List getKeys() { 98 | return keys; 99 | } 100 | 101 | public void setKeys(List keys) { 102 | this.keys = keys; 103 | } 104 | 105 | public List getColumns() { 106 | return columns; 107 | } 108 | 109 | public void setColumns(List columns) { 110 | this.columns = columns; 111 | } 112 | 113 | public List getOldKeys() { 114 | return oldKeys; 115 | } 116 | 117 | public void setOldKeys(List oldKeys) { 118 | this.oldKeys = oldKeys; 119 | } 120 | 121 | public long getSize() { 122 | return size; 123 | } 124 | 125 | public void setSize(long size) { 126 | this.size = size; 127 | } 128 | 129 | // ======================== helper method ================= 130 | 131 | /** 132 | * 返回所有待变更的字段 133 | */ 134 | public List getUpdatedColumns() { 135 | List columns = new ArrayList(); 136 | for (EventColumn column : this.columns) { 137 | if (column.isUpdate()) { 138 | columns.add(column); 139 | } 140 | } 141 | 142 | return columns; 143 | } 144 | 145 | public String toString() { 146 | return ToStringBuilder.reflectionToString(this, ClaveToStringStyle.DEFAULT_STYLE); 147 | } 148 | 149 | @Override 150 | public int hashCode() { 151 | final int prime = 31; 152 | int result = 1; 153 | result = prime * result + ((columns == null) ? 0 : columns.hashCode()); 154 | result = prime * result + ((eventType == null) ? 0 : eventType.hashCode()); 155 | result = prime * result + (int) (executeTime ^ (executeTime >>> 32)); 156 | result = prime * result + ((keys == null) ? 0 : keys.hashCode()); 157 | result = prime * result + ((oldKeys == null) ? 0 : oldKeys.hashCode()); 158 | result = prime * result + ((schemaName == null) ? 0 : schemaName.hashCode()); 159 | result = prime * result + ((tableName == null) ? 0 : tableName.hashCode()); 160 | return result; 161 | } 162 | 163 | @Override 164 | public boolean equals(Object obj) { 165 | if (this == obj) return true; 166 | if (obj == null) return false; 167 | if (getClass() != obj.getClass()) return false; 168 | EventData other = (EventData) obj; 169 | if (columns == null) { 170 | if (other.columns != null) return false; 171 | } else if (!columns.equals(other.columns)) return false; 172 | if (eventType != other.eventType) return false; 173 | if (executeTime != other.executeTime) return false; 174 | if (keys == null) { 175 | if (other.keys != null) return false; 176 | } else if (!keys.equals(other.keys)) return false; 177 | if (oldKeys == null) { 178 | if (other.oldKeys != null) return false; 179 | } else if (!oldKeys.equals(other.oldKeys)) return false; 180 | if (schemaName == null) { 181 | if (other.schemaName != null) return false; 182 | } else if (!schemaName.equals(other.schemaName)) return false; 183 | if (tableName == null) { 184 | if (other.tableName != null) return false; 185 | } else if (!tableName.equals(other.tableName)) return false; 186 | return true; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/EventType.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | public enum EventType { 4 | 5 | /** 6 | * Insert row. 7 | */ 8 | INSERT("I"), 9 | 10 | /** 11 | * Update row. 12 | */ 13 | UPDATE("U"), 14 | 15 | /** 16 | * Delete row. 17 | */ 18 | DELETE("D"), 19 | 20 | /** 21 | * Create table. 22 | */ 23 | CREATE("C"), 24 | 25 | /** 26 | * Alter table. 27 | */ 28 | ALTER("A"), 29 | 30 | /** 31 | * Erase table. 32 | */ 33 | ERASE("E"); 34 | 35 | private String value; 36 | 37 | private EventType(String value){ 38 | this.value = value; 39 | } 40 | 41 | public boolean isInsert() { 42 | return this.equals(EventType.INSERT); 43 | } 44 | 45 | public boolean isUpdate() { 46 | return this.equals(EventType.UPDATE); 47 | } 48 | 49 | public boolean isDelete() { 50 | return this.equals(EventType.DELETE); 51 | } 52 | 53 | public boolean isCreate() { 54 | return this.equals(EventType.CREATE); 55 | } 56 | 57 | public boolean isAlter() { 58 | return this.equals(EventType.ALTER); 59 | } 60 | 61 | public boolean isErase() { 62 | return this.equals(EventType.ERASE); 63 | } 64 | 65 | public static EventType valuesOf(String value) { 66 | EventType[] eventTypes = values(); 67 | for (EventType eventType : eventTypes) { 68 | if (eventType.value.equalsIgnoreCase(value)) { 69 | return eventType; 70 | } 71 | } 72 | return null; 73 | } 74 | 75 | public String getValue() { 76 | return value; 77 | } 78 | 79 | public void setValue(String value) { 80 | this.value = value; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/ObjectData.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 抽象的数据对象接口 7 | * 8 | * @author jianghang 2013-3-28 下午11:19:48 9 | * @version 1.0.0 10 | */ 11 | public interface ObjectData extends Serializable { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/model/RowBatch.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * 数据记录集合对象 8 | * 9 | * @author jianghang 2012-10-31 下午05:51:42 10 | * @version 4.1.2 11 | */ 12 | public class RowBatch extends BatchObject { 13 | 14 | private static final long serialVersionUID = -6117067964148581257L; 15 | 16 | private List datas = new LinkedList(); 17 | 18 | public List getDatas() { 19 | return datas; 20 | } 21 | 22 | public void setDatas(List datas) { 23 | this.datas = datas; 24 | } 25 | 26 | public void merge(EventData data) { 27 | this.datas.add(data); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/ClaveBoss.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.atomic.AtomicBoolean; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.alibaba.otter.clave.common.lifecycle.AbstractClaveLifeCycle; 11 | import com.alibaba.otter.clave.model.EventData; 12 | import com.alibaba.otter.clave.model.RowBatch; 13 | import com.alibaba.otter.clave.progress.select.ClaveSelector; 14 | import com.alibaba.otter.clave.progress.select.Message; 15 | 16 | /** 17 | * progress的控制线程 18 | * 19 | * @author jianghang 2013-4-7 下午03:08:48 20 | * @version 1.0.0 21 | */ 22 | public class ClaveBoss extends AbstractClaveLifeCycle { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(ClaveBoss.class); 25 | private ClaveSelector select; 26 | private ClaveProgress progress; 27 | private ExecutorService executor; 28 | 29 | public void start() { 30 | if (!select.isStart()) { 31 | select.start(); 32 | } 33 | 34 | if (!progress.isStart()) { 35 | progress.start(); 36 | } 37 | 38 | executor = Executors.newFixedThreadPool(1); 39 | executor.submit(new Runnable() { 40 | 41 | public void run() { 42 | process(); 43 | } 44 | }); 45 | 46 | super.start(); 47 | } 48 | 49 | public void stop() { 50 | super.stop(); 51 | executor.shutdownNow(); 52 | 53 | if (select.isStart()) { 54 | select.stop(); 55 | } 56 | 57 | if (progress.isStart()) { 58 | progress.stop(); 59 | } 60 | 61 | } 62 | 63 | public void process() { 64 | AtomicBoolean rollback = new AtomicBoolean(true); 65 | while (isStart()) { 66 | try { 67 | if (rollback.compareAndSet(true, false)) { 68 | select.rollback(); 69 | } 70 | 71 | // 获取数据 72 | Message message = select.selector(); 73 | 74 | RowBatch rowBatch = new RowBatch(); 75 | // 进行数据合并 76 | for (EventData data : message.getDatas()) { 77 | rowBatch.merge(data); 78 | } 79 | 80 | boolean result = progress.process(rowBatch); 81 | if (result) { 82 | select.ack(message.getId()); 83 | } else { 84 | select.rollback(message.getId()); 85 | } 86 | } catch (Exception e) { 87 | logger.error("process error!", e); 88 | rollback.compareAndSet(false, true); 89 | try { 90 | Thread.sleep(10 * 1000); 91 | } catch (InterruptedException e1) { 92 | // ignore 93 | } 94 | } 95 | } 96 | } 97 | 98 | public void setSelect(ClaveSelector select) { 99 | this.select = select; 100 | } 101 | 102 | public void setProgress(ClaveProgress progress) { 103 | this.progress = progress; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/ClaveProgress.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress; 2 | 3 | import com.alibaba.otter.clave.common.lifecycle.AbstractClaveLifeCycle; 4 | import com.alibaba.otter.clave.progress.extract.ClaveExtractor; 5 | import com.alibaba.otter.clave.progress.load.ClaveLoader; 6 | import com.alibaba.otter.clave.progress.transform.ClaveTransform; 7 | 8 | /** 9 | * ETL调度器 10 | * 11 | * @author jianghang 2013-4-7 下午03:08:10 12 | * @version 1.0.0 13 | */ 14 | public class ClaveProgress extends AbstractClaveLifeCycle { 15 | 16 | private ClaveExtractor extract; 17 | private ClaveTransform transform; 18 | private ClaveLoader load; 19 | 20 | public void start() { 21 | if (extract != null && !extract.isStart()) { 22 | extract.start(); 23 | } 24 | 25 | if (transform != null && !transform.isStart()) { 26 | transform.start(); 27 | } 28 | 29 | if (load != null && !load.isStart()) { 30 | load.start(); 31 | } 32 | 33 | super.start(); 34 | } 35 | 36 | public void stop() { 37 | super.stop(); 38 | 39 | if (extract != null && extract.isStart()) { 40 | extract.stop(); 41 | } 42 | 43 | if (transform != null && transform.isStart()) { 44 | transform.stop(); 45 | } 46 | 47 | if (load != null && load.isStart()) { 48 | load.stop(); 49 | } 50 | } 51 | 52 | /** 53 | * 处理一批数据,如果处理成功返回true,如果处理失败返回false 54 | * 55 | * @param data 56 | * @return 57 | */ 58 | public boolean process(T data) { 59 | try { 60 | if (extract != null) { 61 | extract.extract(data); 62 | } 63 | 64 | if (transform != null) { 65 | data = transform.transform(data); 66 | } 67 | 68 | if (load != null) { 69 | load.load(data); 70 | } 71 | } catch (Exception e) { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | public void setExtract(ClaveExtractor extract) { 79 | this.extract = extract; 80 | } 81 | 82 | public void setTransform(ClaveTransform transform) { 83 | this.transform = transform; 84 | } 85 | 86 | public void setLoad(ClaveLoader load) { 87 | this.load = load; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/EtlConfig.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress; 2 | 3 | import java.util.List; 4 | 5 | import com.google.common.collect.Lists; 6 | 7 | /** 8 | * 相关etl规则定义 9 | * 10 | * @author jianghang 2013-4-7 下午03:15:14 11 | * @version 1.0.0 12 | */ 13 | public class EtlConfig { 14 | 15 | public static class TablePair { 16 | 17 | private String source; 18 | private String target; 19 | 20 | private List> columnPairs = Lists.newArrayList(); 21 | private ColumnPairMode columnPairMode = ColumnPairMode.INCLUDE; 22 | 23 | private int weight = 1; // 定义数据处理权重 24 | 25 | public String getSource() { 26 | return source; 27 | } 28 | 29 | public void setSource(String source) { 30 | this.source = source; 31 | } 32 | 33 | public String getTarget() { 34 | return target; 35 | } 36 | 37 | public void setTarget(String target) { 38 | this.target = target; 39 | } 40 | 41 | public List> getColumnPairs() { 42 | return columnPairs; 43 | } 44 | 45 | public void setColumnPairs(List> columnPairs) { 46 | this.columnPairs = columnPairs; 47 | } 48 | 49 | public ColumnPairMode getColumnPairMode() { 50 | return columnPairMode; 51 | } 52 | 53 | public void setColumnPairMode(ColumnPairMode columnPairMode) { 54 | this.columnPairMode = columnPairMode; 55 | } 56 | 57 | public int getWeight() { 58 | return weight; 59 | } 60 | 61 | public void setWeight(int weight) { 62 | this.weight = weight; 63 | } 64 | 65 | } 66 | 67 | public enum ColumnPairMode { 68 | INCLUDE, EXCLUDE; 69 | } 70 | 71 | public static class EtlPair { 72 | 73 | private S source; 74 | private T target; 75 | 76 | public S getSource() { 77 | return source; 78 | } 79 | 80 | public void setSource(S source) { 81 | this.source = source; 82 | } 83 | 84 | public T getTarget() { 85 | return target; 86 | } 87 | 88 | public void setTarget(T target) { 89 | this.target = target; 90 | } 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/extract/ClaveExtractor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.extract; 2 | 3 | import com.alibaba.otter.clave.common.lifecycle.ClaveLifeCycle; 4 | 5 | public interface ClaveExtractor extends ClaveLifeCycle { 6 | 7 | public void extract(T data); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/AbstractLoadContext.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load; 2 | 3 | import java.io.Serializable; 4 | import java.util.Collections; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | /** 9 | * 数据处理上下文 10 | * 11 | * @author jianghang 2013-3-28 下午10:36:29 12 | * @version 1.0.0 13 | */ 14 | public abstract class AbstractLoadContext implements LoadContext, Serializable { 15 | 16 | private static final long serialVersionUID = -2052280419851872736L; 17 | protected List prepareDatas; // 准备处理的数据 18 | protected List processedDatas; // 已处理完成的数据 19 | protected List failedDatas; 20 | 21 | public AbstractLoadContext(){ 22 | this.prepareDatas = Collections.synchronizedList(new LinkedList()); 23 | this.processedDatas = Collections.synchronizedList(new LinkedList()); 24 | this.failedDatas = Collections.synchronizedList(new LinkedList()); 25 | } 26 | 27 | public List getPrepareDatas() { 28 | return prepareDatas; 29 | } 30 | 31 | public void setPrepareDatas(List prepareDatas) { 32 | this.prepareDatas = prepareDatas; 33 | } 34 | 35 | public void addProcessData(T processData) { 36 | this.processedDatas.add(processData); 37 | } 38 | 39 | public List getProcessedDatas() { 40 | return processedDatas; 41 | } 42 | 43 | public void setProcessedDatas(List processedDatas) { 44 | this.processedDatas = processedDatas; 45 | } 46 | 47 | public List getFailedDatas() { 48 | return failedDatas; 49 | } 50 | 51 | public void addFailedData(T failedData) { 52 | this.failedDatas.add(failedData); 53 | } 54 | 55 | public void setFailedDatas(List failedDatas) { 56 | this.failedDatas = failedDatas; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/ClaveLoader.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load; 2 | 3 | import com.alibaba.otter.clave.common.lifecycle.ClaveLifeCycle; 4 | 5 | /** 6 | * 数据loader接口 7 | * 8 | * @author jianghang 2013-3-28 下午10:28:45 9 | * @version 1.0.0 10 | */ 11 | public interface ClaveLoader extends ClaveLifeCycle { 12 | 13 | public void load(T data); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/LoadContext.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load; 2 | 3 | /** 4 | * @author jianghang 2013-3-28 下午10:37:00 5 | * @version 1.0.0 6 | */ 7 | public interface LoadContext { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/DataBaseLoader.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db; 2 | 3 | import com.alibaba.otter.clave.common.datasource.db.DbMediaSource; 4 | import com.alibaba.otter.clave.common.dialect.DbDialectFactory; 5 | import com.alibaba.otter.clave.common.lifecycle.AbstractClaveLifeCycle; 6 | import com.alibaba.otter.clave.model.RowBatch; 7 | import com.alibaba.otter.clave.progress.load.ClaveLoader; 8 | import com.alibaba.otter.clave.progress.load.weight.WeightController; 9 | 10 | /** 11 | * 基于数据库load的实现 12 | * 13 | * @author jianghang 2013-4-7 下午03:24:32 14 | * @version 1.0.0 15 | */ 16 | public class DataBaseLoader extends AbstractClaveLifeCycle implements ClaveLoader { 17 | 18 | private DbDialectFactory dbDialectFactory; 19 | private DbMediaSource dataSource; 20 | private DbLoadAction action; 21 | 22 | public void load(RowBatch rowBatch) { 23 | WeightController controller = new WeightController(1); 24 | DbLoadContext context = new DbLoadContext(); 25 | context.setDbDialect(dbDialectFactory.getDbDialect(dataSource)); 26 | action.load(rowBatch, controller, context); 27 | } 28 | 29 | public void setAction(DbLoadAction action) { 30 | this.action = action; 31 | } 32 | 33 | public void setDataSource(DbMediaSource dataSource) { 34 | this.dataSource = dataSource; 35 | } 36 | 37 | public void setDbDialectFactory(DbDialectFactory dbDialectFactory) { 38 | this.dbDialectFactory = dbDialectFactory; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/DbLoadContext.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | import com.alibaba.otter.clave.common.dialect.DbDialect; 8 | import com.alibaba.otter.clave.model.EventData; 9 | import com.alibaba.otter.clave.progress.load.AbstractLoadContext; 10 | import com.google.common.base.Function; 11 | import com.google.common.collect.MapMaker; 12 | 13 | /** 14 | * 数据库load处理上下文 15 | * 16 | * @author jianghang 2011-11-1 上午11:22:45 17 | * @version 4.0.0 18 | */ 19 | public class DbLoadContext extends AbstractLoadContext { 20 | 21 | private static final long serialVersionUID = -4851977997313104740L; 22 | 23 | private DbDialect dbDialect; 24 | private Map, DbLoadCounter> counters; 25 | 26 | public DbLoadContext(){ 27 | super(); 28 | 29 | counters = new MapMaker().makeComputingMap(new Function, DbLoadCounter>() { 30 | 31 | public DbLoadCounter apply(List names) { 32 | return new DbLoadCounter(names.get(0), names.get(1)); 33 | } 34 | }); 35 | } 36 | 37 | public Map, DbLoadCounter> getCounters() { 38 | return counters; 39 | } 40 | 41 | public void setCounters(Map, DbLoadCounter> counters) { 42 | this.counters = counters; 43 | } 44 | 45 | public DbDialect getDbDialect() { 46 | return dbDialect; 47 | } 48 | 49 | public void setDbDialect(DbDialect dbDialect) { 50 | this.dbDialect = dbDialect; 51 | } 52 | 53 | public static class DbLoadCounter { 54 | 55 | private String schema; 56 | private String table; 57 | private AtomicLong rowSize = new AtomicLong(0); 58 | private AtomicLong deleteCount = new AtomicLong(0); 59 | private AtomicLong updateCount = new AtomicLong(0); 60 | private AtomicLong insertCount = new AtomicLong(0); 61 | 62 | public DbLoadCounter(String schema, String table){ 63 | this.schema = schema; 64 | this.table = table; 65 | } 66 | 67 | public String getSchema() { 68 | return schema; 69 | } 70 | 71 | public void setSchema(String schema) { 72 | this.schema = schema; 73 | } 74 | 75 | public String getTable() { 76 | return table; 77 | } 78 | 79 | public void setTable(String table) { 80 | this.table = table; 81 | } 82 | 83 | public AtomicLong getDeleteCount() { 84 | return deleteCount; 85 | } 86 | 87 | public void setDeleteCount(AtomicLong deleteCount) { 88 | this.deleteCount = deleteCount; 89 | } 90 | 91 | public AtomicLong getUpdateCount() { 92 | return updateCount; 93 | } 94 | 95 | public void setUpdateCount(AtomicLong updateCount) { 96 | this.updateCount = updateCount; 97 | } 98 | 99 | public AtomicLong getInsertCount() { 100 | return insertCount; 101 | } 102 | 103 | public void setInsertCount(AtomicLong insertCount) { 104 | this.insertCount = insertCount; 105 | } 106 | 107 | public AtomicLong getRowSize() { 108 | return rowSize; 109 | } 110 | 111 | public void setRowSize(AtomicLong rowSize) { 112 | this.rowSize = rowSize; 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/DbLoadData.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | import com.alibaba.otter.clave.model.EventData; 9 | import com.alibaba.otter.clave.model.EventType; 10 | 11 | /** 12 | * 将同一个weight下的EventData进行数据归类,按表和insert/update/delete类型进行分类 13 | * 14 | *
 15 |  * 归类用途:对insert语句进行batch优化
 16 |  * 1. mysql索引的限制,需要避免insert并发执行
 17 |  * 
18 | * 19 | * @author jianghang 2011-11-9 下午04:28:35 20 | * @version 1.0.0 21 | */ 22 | public class DbLoadData { 23 | 24 | private List tableDatas = new ArrayList(); 25 | 26 | public DbLoadData(){ 27 | // nothing 28 | } 29 | 30 | public DbLoadData(List datas){ 31 | for (EventData data : datas) { 32 | merge(data); 33 | } 34 | } 35 | 36 | public void merge(EventData data) { 37 | TableLoadData tableData = findTableData(data.getSchemaName(), data.getTableName()); 38 | 39 | EventType type = data.getEventType(); 40 | if (type.isInsert()) { 41 | tableData.getInsertDatas().add(data); 42 | } else if (type.isUpdate()) { 43 | tableData.getUpadateDatas().add(data); 44 | } else if (type.isDelete()) { 45 | tableData.getDeleteDatas().add(data); 46 | } 47 | } 48 | 49 | public List getTables() { 50 | return tableDatas; 51 | } 52 | 53 | private synchronized TableLoadData findTableData(String schema, String table) { 54 | for (TableLoadData tableData : tableDatas) { 55 | if (StringUtils.equalsIgnoreCase(schema, tableData.getSchema()) 56 | && StringUtils.equalsIgnoreCase(table, tableData.getTable())) { 57 | return tableData; 58 | } 59 | } 60 | 61 | TableLoadData data = new TableLoadData(schema, table); 62 | tableDatas.add(data); 63 | return data; 64 | } 65 | 66 | /** 67 | * 按table进行分类 68 | */ 69 | public static class TableLoadData { 70 | 71 | private String schema; 72 | private String table; 73 | private List insertDatas = new ArrayList(); 74 | private List upadateDatas = new ArrayList(); 75 | private List deleteDatas = new ArrayList(); 76 | 77 | public TableLoadData(String schema, String table){ 78 | this.schema = schema; 79 | this.table = table; 80 | } 81 | 82 | public List getInsertDatas() { 83 | return insertDatas; 84 | } 85 | 86 | public void setInsertDatas(List insertDatas) { 87 | this.insertDatas = insertDatas; 88 | } 89 | 90 | public List getUpadateDatas() { 91 | return upadateDatas; 92 | } 93 | 94 | public void setUpadateDatas(List upadateDatas) { 95 | this.upadateDatas = upadateDatas; 96 | } 97 | 98 | public List getDeleteDatas() { 99 | return deleteDatas; 100 | } 101 | 102 | public void setDeleteDatas(List deleteDatas) { 103 | this.deleteDatas = deleteDatas; 104 | } 105 | 106 | public String getSchema() { 107 | return schema; 108 | } 109 | 110 | public void setSchema(String schema) { 111 | this.schema = schema; 112 | } 113 | 114 | public String getTable() { 115 | return table; 116 | } 117 | 118 | public void setTable(String table) { 119 | this.table = table; 120 | } 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/DbLoadDumper.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db; 2 | 3 | import java.text.MessageFormat; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | import org.apache.commons.lang.SystemUtils; 8 | import org.springframework.util.CollectionUtils; 9 | 10 | import com.alibaba.otter.clave.model.EventColumn; 11 | import com.alibaba.otter.clave.model.EventData; 12 | 13 | /** 14 | * dump记录 15 | * 16 | * @author jianghang 2011-11-9 下午03:52:26 17 | * @version 1.0.0 18 | */ 19 | public class DbLoadDumper { 20 | 21 | private static final String SEP = SystemUtils.LINE_SEPARATOR; 22 | 23 | private static String context_format = null; 24 | private static String eventData_format = null; 25 | private static int event_default_capacity = 1024; // 预设值StringBuilder,减少扩容影响 26 | 27 | static { 28 | context_format = SEP + "****************************************************" + SEP; 29 | context_format += "* total Data : [{0}] , success Data : [{1}] , failed Data : [{2}] " + SEP; 30 | context_format += "****************************************************" + SEP; 31 | context_format += "* process Data *" + SEP; 32 | context_format += "{3}" + SEP; 33 | context_format += "****************************************************" + SEP; 34 | context_format += "* failed Data *" + SEP; 35 | context_format += "{4}" + SEP; 36 | context_format += "****************************************************" + SEP; 37 | 38 | eventData_format = "-----------------" + SEP; 39 | eventData_format += "- Schema: {0} , Table: {1} " + SEP; 40 | eventData_format += "- EventType : {2} , Time : {3} " + SEP; 41 | eventData_format += "-----------------" + SEP; 42 | eventData_format += "---Pks" + SEP; 43 | eventData_format += "{4}" + SEP; 44 | eventData_format += "---oldPks" + SEP; 45 | eventData_format += "{5}" + SEP; 46 | eventData_format += "---Columns" + SEP; 47 | eventData_format += "{6}" + SEP; 48 | eventData_format += "---Sql" + SEP; 49 | eventData_format += "{7}" + SEP; 50 | } 51 | 52 | public static String dumpContext(DbLoadContext context) { 53 | int successed = context.getProcessedDatas().size(); 54 | int failed = context.getFailedDatas().size(); 55 | int all = context.getPrepareDatas().size(); 56 | return MessageFormat.format(context_format, all, successed, failed, 57 | dumpEventDatas(context.getProcessedDatas()), 58 | dumpEventDatas(context.getFailedDatas())); 59 | } 60 | 61 | public static String dumpEventDatas(List eventDatas) { 62 | if (CollectionUtils.isEmpty(eventDatas)) { 63 | return StringUtils.EMPTY; 64 | } 65 | 66 | // 预先设定容量大小 67 | StringBuilder builder = new StringBuilder(event_default_capacity * eventDatas.size()); 68 | for (EventData data : eventDatas) { 69 | builder.append(dumpEventData(data)); 70 | } 71 | return builder.toString(); 72 | } 73 | 74 | public static String dumpEventData(EventData eventData) { 75 | return MessageFormat.format(eventData_format, eventData.getSchemaName(), eventData.getTableName(), 76 | eventData.getEventType(), String.valueOf(eventData.getExecuteTime()), 77 | dumpEventColumn(eventData.getKeys()), dumpEventColumn(eventData.getOldKeys()), 78 | dumpEventColumn(eventData.getColumns()), "\t" + eventData.getSql()); 79 | } 80 | 81 | private static String dumpEventColumn(List columns) { 82 | StringBuilder builder = new StringBuilder(event_default_capacity); 83 | int size = columns.size(); 84 | for (int i = 0; i < size; i++) { 85 | EventColumn column = columns.get(i); 86 | builder.append("\t").append(column.toString()); 87 | if (i < columns.size() - 1) { 88 | builder.append(SEP); 89 | } 90 | } 91 | return builder.toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/DbLoadMerger.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.LinkedHashMap; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.util.CollectionUtils; 14 | 15 | import com.alibaba.otter.clave.model.EventColumn; 16 | import com.alibaba.otter.clave.model.EventColumnIndexComparable; 17 | import com.alibaba.otter.clave.model.EventData; 18 | import com.alibaba.otter.clave.model.EventType; 19 | 20 | /** 21 | *
 22 |  * 合并相同table的变更记录.
 23 |  * pk相同的多条变更数据合并后的结果是:
 24 |  * 1, I 
 25 |  * 2, U 
 26 |  * 3, D 
 27 |  * 如果有一条I,多条U,merge成I;
 28 |  * 如果有多条U,取最晚的那条;
 29 |  * 
30 | * 31 | * @author jianghang 2013-3-28 下午10:30:52 32 | * @version 1.0.0 33 | */ 34 | public class DbLoadMerger { 35 | 36 | private static final Logger logger = LoggerFactory.getLogger(DbLoadMerger.class); 37 | 38 | /** 39 | * 将一批数据进行根据table+主键信息进行合并,保证一个表的一个pk记录只有一条结果 40 | * 41 | * @param eventDatas 42 | * @return 43 | */ 44 | public static List merge(List eventDatas) { 45 | Map result = new LinkedHashMap(); 46 | for (EventData eventData : eventDatas) { 47 | merge(eventData, result); 48 | } 49 | return new LinkedList(result.values()); 50 | } 51 | 52 | public static void merge(EventData eventData, Map result) { 53 | EventType eventType = eventData.getEventType(); 54 | switch (eventType) { 55 | case INSERT: 56 | mergeInsert(eventData, result); 57 | break; 58 | case UPDATE: 59 | mergeUpdate(eventData, result); 60 | break; 61 | case DELETE: 62 | mergeDelete(eventData, result); 63 | break; 64 | } 65 | } 66 | 67 | private static void mergeInsert(EventData eventData, Map result) { 68 | // insert无主键变更的处理 69 | RowKey rowKey = new RowKey(eventData.getSchemaName(), eventData.getTableName(), eventData.getKeys()); 70 | if (!result.containsKey(rowKey)) { 71 | result.put(rowKey, eventData); 72 | } else { 73 | EventData oldEventData = result.get(rowKey); 74 | eventData.setSize(oldEventData.getSize() + eventData.getSize()); 75 | // 如果上一条变更是delete的,就直接用insert替换 76 | if (oldEventData.getEventType() == EventType.DELETE) { 77 | result.put(rowKey, eventData); 78 | } else if (oldEventData.getEventType() == EventType.UPDATE) { 79 | // insert之前出现了update逻辑上不可能,唯一的可能性主要是Freedom的介入,人为的插入了一条Insert记录 80 | // 不过freedom一般不建议Insert操作,只建议执行update/delete操作. update默认会走merge sql,不存在即插入 81 | logger.warn("update-insert happend. update[{}] , insert[{}]", oldEventData, eventData); 82 | // 如果上一条变更是update的,就用insert替换,并且把上一条存在而这一条不存在的字段值拷贝到这一条中 83 | EventData mergeEventData = replaceColumnValue(eventData, oldEventData); 84 | mergeEventData.getOldKeys().clear();// 清空oldkeys,insert记录不需要 85 | result.put(rowKey, mergeEventData); 86 | } 87 | } 88 | } 89 | 90 | private static void mergeUpdate(EventData eventData, Map result) { 91 | RowKey rowKey = new RowKey(eventData.getSchemaName(), eventData.getTableName(), eventData.getKeys()); 92 | if (!CollectionUtils.isEmpty(eventData.getOldKeys())) {// 存在主键变更 93 | // 需要解决(1->2 , 2->3)级联主键变更的问题 94 | RowKey oldKey = new RowKey(eventData.getSchemaName(), eventData.getTableName(), eventData.getOldKeys()); 95 | if (!result.containsKey(oldKey)) {// 不需要级联 96 | result.put(rowKey, eventData); 97 | } else { 98 | EventData oldEventData = result.get(oldKey); 99 | eventData.setSize(oldEventData.getSize() + eventData.getSize()); 100 | // 如果上一条变更是insert的,就把这一条的eventType改成insert,并且把上一条存在而这一条不存在的字段值拷贝到这一条中 101 | if (oldEventData.getEventType() == EventType.INSERT) { 102 | eventData.setEventType(EventType.INSERT); 103 | // 删除当前变更数据老主键的记录. 104 | result.remove(oldKey); 105 | 106 | EventData mergeEventData = replaceColumnValue(eventData, oldEventData); 107 | mergeEventData.getOldKeys().clear();// 清空oldkeys,insert记录不需要 108 | result.put(rowKey, mergeEventData); 109 | } else if (oldEventData.getEventType() == EventType.UPDATE) { 110 | // 删除当前变更数据老主键的记录. 111 | result.remove(oldKey); 112 | 113 | // 如果上一条变更是update的,把上一条存在而这一条不存在的数据拷贝到这一条中 114 | EventData mergeEventData = replaceColumnValue(eventData, oldEventData); 115 | result.put(rowKey, mergeEventData); 116 | } 117 | } 118 | } else { 119 | if (!result.containsKey(rowKey)) {// 没有主键变更 120 | result.put(rowKey, eventData); 121 | } else { 122 | EventData oldEventData = result.get(rowKey); 123 | // 如果上一条变更是insert的,就把这一条的eventType改成insert,并且把上一条存在而这一条不存在的字段值拷贝到这一条中 124 | if (oldEventData.getEventType() == EventType.INSERT) { 125 | eventData.setEventType(EventType.INSERT); 126 | 127 | EventData mergeEventData = replaceColumnValue(eventData, oldEventData); 128 | result.put(rowKey, mergeEventData); 129 | } else if (oldEventData.getEventType() == EventType.UPDATE) {// 可能存在 1->2 , 2update的问题 130 | 131 | // 如果上一条变更是update的,把上一条存在而这一条不存在的数据拷贝到这一条中 132 | EventData mergeEventData = replaceColumnValue(eventData, oldEventData); 133 | result.put(rowKey, mergeEventData); 134 | } 135 | } 136 | } 137 | } 138 | 139 | private static void mergeDelete(EventData eventData, Map result) { 140 | // 只保留pks,把columns去掉. 以后针对数据仓库可以开放delete columns记录 141 | RowKey rowKey = new RowKey(eventData.getSchemaName(), eventData.getTableName(), eventData.getKeys()); 142 | if (!result.containsKey(rowKey)) { 143 | result.put(rowKey, eventData); 144 | } else { 145 | EventData oldEventData = result.get(rowKey); 146 | eventData.setSize(oldEventData.getSize() + eventData.getSize()); 147 | if (!CollectionUtils.isEmpty(oldEventData.getOldKeys())) {// 存在主键变更 148 | // insert/update -> delete记录组合时,delete的对应的pk为上一条记录的pk 149 | eventData.setKeys(oldEventData.getOldKeys()); 150 | eventData.getOldKeys().clear();// 清除oldKeys 151 | 152 | result.remove(rowKey);// 删除老的对象 153 | result.put(new RowKey(eventData.getSchemaName(), eventData.getTableName(), eventData.getKeys()), 154 | eventData); // key发生变化,需要重新构造一个RowKey 155 | } else { 156 | eventData.getOldKeys().clear();// 清除oldKeys 157 | result.put(rowKey, eventData); 158 | } 159 | 160 | } 161 | } 162 | 163 | /** 164 | * 把old中的值存在而new中不存在的值合并到new中,并且把old中的变更前的主键保存到new中的变更前的主键. 165 | * 166 | * @param newEventData 167 | * @param oldEventData 168 | * @return 169 | */ 170 | private static EventData replaceColumnValue(EventData newEventData, EventData oldEventData) { 171 | List newColumns = newEventData.getColumns(); 172 | List oldColumns = oldEventData.getColumns(); 173 | List temp = new ArrayList(); 174 | for (EventColumn oldColumn : oldColumns) { 175 | boolean contain = false; 176 | for (EventColumn newColumn : newColumns) { 177 | if (oldColumn.getColumnName().equalsIgnoreCase(newColumn.getColumnName())) { 178 | newColumn.setUpdate(newColumn.isUpdate() || oldColumn.isUpdate());// 合并isUpdate字段 179 | contain = true; 180 | } 181 | } 182 | 183 | if (!contain) { 184 | temp.add(oldColumn); 185 | } 186 | } 187 | newColumns.addAll(temp); 188 | Collections.sort(newColumns, new EventColumnIndexComparable()); // 排序 189 | // 把上一次变更的旧主键传递到这次变更的旧主键. 190 | newEventData.setOldKeys(oldEventData.getOldKeys()); 191 | newEventData.setSize(oldEventData.getSize() + newEventData.getSize()); 192 | return newEventData; 193 | } 194 | 195 | public static class RowKey implements Serializable { 196 | 197 | private static final long serialVersionUID = -7369951798499581038L; 198 | private String schemaName; // tableId代表统配符时,需要指定schemaName 199 | private String tableName; // tableId代表统配符时,需要指定tableName 200 | 201 | public RowKey(String schemaName, String tableName, List keys){ 202 | this.schemaName = schemaName; 203 | this.tableName = tableName; 204 | this.keys = keys; 205 | } 206 | 207 | public RowKey(List keys){ 208 | this.keys = keys; 209 | } 210 | 211 | private List keys = new ArrayList(); 212 | 213 | public List getKeys() { 214 | return keys; 215 | } 216 | 217 | public void setKeys(List keys) { 218 | this.keys = keys; 219 | } 220 | 221 | public String getSchemaName() { 222 | return schemaName; 223 | } 224 | 225 | public void setSchemaName(String schemaName) { 226 | this.schemaName = schemaName; 227 | } 228 | 229 | public String getTableName() { 230 | return tableName; 231 | } 232 | 233 | public void setTableName(String tableName) { 234 | this.tableName = tableName; 235 | } 236 | 237 | @Override 238 | public int hashCode() { 239 | final int prime = 31; 240 | int result = 1; 241 | result = prime * result + ((keys == null) ? 0 : keys.hashCode()); 242 | result = prime * result + ((schemaName == null) ? 0 : schemaName.hashCode()); 243 | result = prime * result + ((tableName == null) ? 0 : tableName.hashCode()); 244 | return result; 245 | } 246 | 247 | @Override 248 | public boolean equals(Object obj) { 249 | if (this == obj) { 250 | return true; 251 | } 252 | if (obj == null) { 253 | return false; 254 | } 255 | if (!(obj instanceof RowKey)) { 256 | return false; 257 | } 258 | RowKey other = (RowKey) obj; 259 | if (keys == null) { 260 | if (other.keys != null) { 261 | return false; 262 | } 263 | } else if (!keys.equals(other.keys)) { 264 | return false; 265 | } 266 | if (schemaName == null) { 267 | if (other.schemaName != null) { 268 | return false; 269 | } 270 | } else if (!schemaName.equals(other.schemaName)) { 271 | return false; 272 | } 273 | if (tableName == null) { 274 | if (other.tableName != null) { 275 | return false; 276 | } 277 | } else if (!tableName.equals(other.tableName)) { 278 | return false; 279 | } 280 | return true; 281 | } 282 | 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/LogLoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor; 2 | 3 | import java.text.MessageFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.apache.commons.lang.SystemUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.alibaba.otter.clave.model.EventData; 13 | import com.alibaba.otter.clave.progress.load.db.DbLoadContext; 14 | import com.alibaba.otter.clave.progress.load.db.DbLoadDumper; 15 | import com.alibaba.otter.clave.progress.load.interceptor.AbstractLoadInterceptor; 16 | 17 | /** 18 | * load的日志记录 19 | * 20 | * @author jianghang 2011-11-10 上午11:31:05 21 | * @version 4.0.0 22 | */ 23 | public class LogLoadInterceptor extends AbstractLoadInterceptor { 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(LogLoadInterceptor.class); 26 | private static final String SEP = SystemUtils.LINE_SEPARATOR; 27 | private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss:SSS"; 28 | private int batchSize = 50; 29 | private static String context_format = null; 30 | private boolean dump = true; 31 | 32 | static { 33 | context_format = "* status : {0} , time : {1} *" + SEP; 34 | context_format += "* total Data : [{2}] , success Data : [{3}] , failed Data : [{4}]]" + SEP; 35 | } 36 | 37 | public void commit(DbLoadContext context) { 38 | // 成功时记录一下 39 | if (dump && logger.isInfoEnabled()) { 40 | synchronized (LogLoadInterceptor.class) { 41 | logger.info(SEP + "****************************************************" + SEP); 42 | logger.info(dumpContextInfo("successed", context)); 43 | logger.info("****************************************************" + SEP); 44 | logger.info("* process Data *" + SEP); 45 | logEventDatas(context.getProcessedDatas()); 46 | logger.info("-----------------" + SEP); 47 | logger.info("* failed Data *" + SEP); 48 | logEventDatas(context.getFailedDatas()); 49 | logger.info("****************************************************" + SEP); 50 | } 51 | } 52 | } 53 | 54 | public void error(DbLoadContext context) { 55 | if (dump && logger.isInfoEnabled()) { 56 | synchronized (LogLoadInterceptor.class) { 57 | logger.info(dumpContextInfo("error", context)); 58 | logger.info("* process Data *" + SEP); 59 | logEventDatas(context.getProcessedDatas()); 60 | logger.info("-----------------" + SEP); 61 | logger.info("* failed Data *" + SEP); 62 | logEventDatas(context.getFailedDatas()); 63 | logger.info("****************************************************" + SEP); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 分批输出多个数据 70 | */ 71 | private void logEventDatas(List eventDatas) { 72 | int size = eventDatas.size(); 73 | // 开始输出每条记录 74 | int index = 0; 75 | do { 76 | if (index + batchSize >= size) { 77 | logger.info(DbLoadDumper.dumpEventDatas(eventDatas.subList(index, size))); 78 | } else { 79 | logger.info(DbLoadDumper.dumpEventDatas(eventDatas.subList(index, index + batchSize))); 80 | } 81 | index += batchSize; 82 | } while (index < size); 83 | } 84 | 85 | private String dumpContextInfo(String status, DbLoadContext context) { 86 | int successed = context.getProcessedDatas().size(); 87 | int failed = context.getFailedDatas().size(); 88 | int all = context.getPrepareDatas().size(); 89 | Date now = new Date(); 90 | SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_FORMAT); 91 | return MessageFormat.format(context_format, status, format.format(now), all, successed, failed); 92 | } 93 | 94 | public void setDump(boolean dump) { 95 | this.dump = dump; 96 | } 97 | 98 | public void setBatchSize(int batchSize) { 99 | this.batchSize = batchSize; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/SqlBuilderLoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.util.CollectionUtils; 6 | 7 | import com.alibaba.otter.clave.common.dialect.DbDialect; 8 | import com.alibaba.otter.clave.common.dialect.SqlTemplate; 9 | import com.alibaba.otter.clave.model.EventColumn; 10 | import com.alibaba.otter.clave.model.EventData; 11 | import com.alibaba.otter.clave.model.EventType; 12 | import com.alibaba.otter.clave.progress.load.db.DbLoadContext; 13 | import com.alibaba.otter.clave.progress.load.interceptor.AbstractLoadInterceptor; 14 | 15 | /** 16 | * 计算下最新的sql语句 17 | * 18 | * @author jianghang 2011-12-26 下午12:09:20 19 | * @version 4.0.0 20 | */ 21 | public class SqlBuilderLoadInterceptor extends AbstractLoadInterceptor { 22 | 23 | private boolean rowMode = false; 24 | 25 | public boolean before(DbLoadContext context, EventData currentData) { 26 | // 初步构建sql 27 | DbDialect dbDialect = context.getDbDialect(); 28 | SqlTemplate sqlTemplate = dbDialect.getSqlTemplate(); 29 | EventType type = currentData.getEventType(); 30 | String sql = null; 31 | 32 | // 注意insert/update语句对应的字段数序都是将主键排在后面 33 | if (type.isInsert()) { 34 | if (CollectionUtils.isEmpty(currentData.getColumns())) { // 如果表为全主键,直接进行insert sql 35 | sql = sqlTemplate.getInsertSql(currentData.getSchemaName(), currentData.getTableName(), 36 | buildColumnNames(currentData.getKeys()), 37 | buildColumnNames(currentData.getColumns())); 38 | } else { 39 | sql = sqlTemplate.getMergeSql(currentData.getSchemaName(), currentData.getTableName(), 40 | buildColumnNames(currentData.getKeys()), 41 | buildColumnNames(currentData.getColumns()), new String[] {}); 42 | } 43 | } else if (type.isUpdate()) { 44 | // String[] keyColumns = buildColumnNames(currentData.getKeys()); 45 | // String[] otherColumns = buildColumnNames(currentData.getUpdatedColumns()); 46 | // boolean existOldKeys = false; 47 | // for (String key : keyColumns) { 48 | // // 找一下otherColumns是否有主键,存在就代表有主键变更 49 | // if (ArrayUtils.contains(otherColumns, key)) { 50 | // existOldKeys = true; 51 | // break; 52 | // } 53 | // } 54 | 55 | boolean existOldKeys = !CollectionUtils.isEmpty(currentData.getOldKeys()); 56 | String[] keyColumns = null; 57 | String[] otherColumns = null; 58 | if (existOldKeys) { 59 | // 需要考虑主键变更的场景 60 | // 构造sql如下:update table xxx set pk = newPK where pk = oldPk 61 | keyColumns = buildColumnNames(currentData.getOldKeys()); 62 | otherColumns = buildColumnNames(currentData.getUpdatedColumns(), currentData.getKeys()); 63 | } else { 64 | keyColumns = buildColumnNames(currentData.getKeys()); 65 | otherColumns = buildColumnNames(currentData.getUpdatedColumns()); 66 | } 67 | 68 | if (rowMode && !existOldKeys) {// 如果是行记录,并且不存在主键变更,考虑merge sql 69 | sql = sqlTemplate.getMergeSql(currentData.getSchemaName(), currentData.getTableName(), keyColumns, 70 | otherColumns, new String[] {}); 71 | } else {// 否则进行update sql 72 | sql = sqlTemplate.getUpdateSql(currentData.getSchemaName(), currentData.getTableName(), keyColumns, 73 | otherColumns); 74 | } 75 | } else if (type.isDelete()) { 76 | sql = sqlTemplate.getDeleteSql(currentData.getSchemaName(), currentData.getTableName(), 77 | buildColumnNames(currentData.getKeys())); 78 | } 79 | currentData.setSql(sql); 80 | return false; 81 | } 82 | 83 | private String[] buildColumnNames(List columns) { 84 | String[] result = new String[columns.size()]; 85 | for (int i = 0; i < columns.size(); i++) { 86 | EventColumn column = columns.get(i); 87 | result[i] = column.getColumnName(); 88 | } 89 | return result; 90 | } 91 | 92 | private String[] buildColumnNames(List columns1, List columns2) { 93 | String[] result = new String[columns1.size() + columns2.size()]; 94 | int i = 0; 95 | for (i = 0; i < columns1.size(); i++) { 96 | EventColumn column = columns1.get(i); 97 | result[i] = column.getColumnName(); 98 | } 99 | 100 | for (; i < columns1.size() + columns2.size(); i++) { 101 | EventColumn column = columns2.get(i - columns1.size()); 102 | result[i] = column.getColumnName(); 103 | } 104 | return result; 105 | } 106 | 107 | public void setRowMode(boolean rowMode) { 108 | this.rowMode = rowMode; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/operation/AbstractOperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor.operation; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | import java.text.MessageFormat; 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.jdbc.core.BatchPreparedStatementSetter; 15 | import org.springframework.jdbc.core.JdbcTemplate; 16 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 17 | import org.springframework.transaction.TransactionDefinition; 18 | import org.springframework.transaction.TransactionStatus; 19 | import org.springframework.transaction.support.TransactionCallback; 20 | import org.springframework.transaction.support.TransactionTemplate; 21 | 22 | import com.alibaba.otter.clave.ClaveConfig; 23 | import com.alibaba.otter.clave.common.dialect.DbDialect; 24 | import com.alibaba.otter.clave.model.EventData; 25 | import com.alibaba.otter.clave.progress.load.db.DbLoadContext; 26 | import com.alibaba.otter.clave.progress.load.interceptor.AbstractLoadInterceptor; 27 | 28 | /** 29 | * @author jianghang 2011-10-31 下午02:24:28 30 | * @version 1.0.0 31 | */ 32 | public abstract class AbstractOperationInterceptor extends AbstractLoadInterceptor { 33 | 34 | protected int serverId = -1; 35 | protected final Logger logger = LoggerFactory.getLogger(getClass()); 36 | protected static final int GLOBAL_THREAD_COUNT = 1000; 37 | protected static final int INNER_THREAD_COUNT = 300; 38 | protected static final String checkDataSql = "SELECT COUNT(*) FROM {0} WHERE id BETWEEN 0 AND {1}"; 39 | protected static final String deleteDataSql = "DELETE FROM {0}"; 40 | 41 | protected String updateSql; 42 | protected String clearSql = "UPDATE {0} SET {1} = 0 WHERE id = ? and {1} = ?"; 43 | protected int innerIdCount = INNER_THREAD_COUNT; 44 | protected int globalIdCount = GLOBAL_THREAD_COUNT; 45 | protected Set tableCheckStatus = Collections.synchronizedSet(new HashSet()); 46 | protected AtomicInteger THREAD_COUNTER = new AtomicInteger(0); 47 | protected ThreadLocal threadLocal = new ThreadLocal(); 48 | 49 | protected AbstractOperationInterceptor(String updateSql){ 50 | this.updateSql = updateSql; 51 | } 52 | 53 | private void init(final JdbcTemplate jdbcTemplate, final String markTableName, final String markTableColumn) { 54 | int count = jdbcTemplate.queryForInt(MessageFormat.format(checkDataSql, markTableName, GLOBAL_THREAD_COUNT - 1)); 55 | if (count != GLOBAL_THREAD_COUNT) { 56 | if (logger.isInfoEnabled()) { 57 | logger.info("Interceptor: init " + markTableName + "'s data."); 58 | } 59 | TransactionTemplate transactionTemplate = new TransactionTemplate(); 60 | transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource())); 61 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);// 注意这里强制使用非事务,保证多线程的可见性 62 | transactionTemplate.execute(new TransactionCallback() { 63 | 64 | public Object doInTransaction(TransactionStatus status) { 65 | jdbcTemplate.execute(MessageFormat.format(deleteDataSql, markTableName)); 66 | String batchSql = MessageFormat.format(updateSql, new Object[] { markTableName, markTableColumn }); 67 | jdbcTemplate.batchUpdate(batchSql, new BatchPreparedStatementSetter() { 68 | 69 | public void setValues(PreparedStatement ps, int idx) throws SQLException { 70 | ps.setInt(1, idx); 71 | ps.setInt(2, 0); 72 | } 73 | 74 | public int getBatchSize() { 75 | return GLOBAL_THREAD_COUNT; 76 | } 77 | }); 78 | return null; 79 | } 80 | }); 81 | 82 | if (logger.isInfoEnabled()) { 83 | logger.info("Interceptor: Init EROSA Client Data: " + updateSql); 84 | } 85 | } 86 | 87 | } 88 | 89 | public void transactionBegin(DbLoadContext context, List currentDatas, DbDialect dialect) { 90 | threadLocal.remove();// 进入之前先清理 91 | int threadId = currentId(); 92 | updateMark(context, dialect, threadId, updateSql); 93 | threadLocal.set(threadId); 94 | } 95 | 96 | public void transactionEnd(DbLoadContext context, List currentDatas, DbDialect dialect) { 97 | Integer threadId = threadLocal.get(); 98 | updateMark(context, dialect, threadId, clearSql); 99 | threadLocal.remove(); 100 | } 101 | 102 | /** 103 | * 更新一下事务标记 104 | */ 105 | private void updateMark(DbLoadContext context, DbDialect dialect, int threadId, String sql) { 106 | String markTableName = ClaveConfig.SYSTEM_SCHEMA + "." + ClaveConfig.SYSTEM_MARK_TABLE; 107 | String markTableColumn = ClaveConfig.SYSTEM_TABLE_MARK_ID_COLUMN; 108 | synchronized (dialect.getJdbcTemplate()) { 109 | if (tableCheckStatus.contains(dialect.getJdbcTemplate()) == false) { 110 | init(dialect.getJdbcTemplate(), markTableName, markTableColumn); 111 | tableCheckStatus.add(dialect.getJdbcTemplate()); 112 | } 113 | } 114 | 115 | int affectedCount = dialect.getJdbcTemplate().update( 116 | MessageFormat.format(sql, new Object[] { markTableName, 117 | markTableColumn }), 118 | new Object[] { threadId, serverId }); 119 | if (affectedCount <= 0) { 120 | logger.warn("## update {} failed by [{}]", markTableName, threadId); 121 | } 122 | } 123 | 124 | private int currentId() { 125 | synchronized (this) { 126 | if (THREAD_COUNTER.get() == INNER_THREAD_COUNT) { 127 | THREAD_COUNTER.set(0); 128 | } 129 | 130 | return THREAD_COUNTER.incrementAndGet(); 131 | } 132 | } 133 | 134 | // ========================= setter / getter ======================== 135 | 136 | public void setInnerIdCount(int innerIdCount) { 137 | this.innerIdCount = innerIdCount; 138 | } 139 | 140 | public void setGlobalIdCount(int globalIdCount) { 141 | this.globalIdCount = globalIdCount; 142 | } 143 | 144 | public void setServerId(int serverId) { 145 | this.serverId = serverId; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/operation/ErosaMysqlInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor.operation; 2 | 3 | 4 | /** 5 | * 基于erosa的日志记录 6 | * 7 | * @author jianghang 2011-10-31 下午02:48:22 8 | * @version 4.0.0 9 | */ 10 | public class ErosaMysqlInterceptor extends AbstractOperationInterceptor { 11 | 12 | public static final String mergeofMysqlSql = "INSERT INTO {0} (id, {1}) VALUES (?, ?) ON DUPLICATE KEY UPDATE {1} = VALUES({1})"; 13 | 14 | public ErosaMysqlInterceptor(){ 15 | super(mergeofMysqlSql); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/operation/ErosaOracleInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor.operation; 2 | 3 | 4 | /** 5 | * 基于oracle的数据过滤 6 | * 7 | * @author jianghang 2011-10-31 下午02:51:09 8 | * @version 4.0.0 9 | */ 10 | public class ErosaOracleInterceptor extends AbstractOperationInterceptor { 11 | 12 | public static final String mergeofOracleSql = "merge /*+ use_nl(a b)*/ into {0} a using (select ? as id , ? as {1} from dual) b on (a.id=b.id)" 13 | + " when matched then update set a.{1}=b.{1}" 14 | + " when not matched then insert (a.id , a.{1}) values (b.id , b.{1})"; 15 | 16 | public ErosaOracleInterceptor(){ 17 | super(mergeofOracleSql); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/db/inteceptor/operation/OperationInterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.db.inteceptor.operation; 2 | 3 | import java.util.List; 4 | 5 | import com.alibaba.otter.clave.common.dialect.DbDialect; 6 | import com.alibaba.otter.clave.common.dialect.mysql.MysqlDialect; 7 | import com.alibaba.otter.clave.common.dialect.oracle.OracleDialect; 8 | import com.alibaba.otter.clave.model.EventData; 9 | import com.alibaba.otter.clave.progress.load.db.DbLoadContext; 10 | import com.alibaba.otter.clave.progress.load.interceptor.AbstractLoadInterceptor; 11 | import com.alibaba.otter.clave.progress.load.interceptor.LoadInterceptor; 12 | 13 | /** 14 | * operator的调用工厂 15 | * 16 | * @author jianghang 2011-10-31 下午03:20:16 17 | * @version 1.0.0 18 | */ 19 | public class OperationInterceptorFactory extends AbstractLoadInterceptor { 20 | 21 | private LoadInterceptor[] mysqlInterceptors; 22 | private LoadInterceptor[] oracleInterceptors; 23 | private LoadInterceptor[] empty = new LoadInterceptor[0]; 24 | 25 | public void transactionBegin(DbLoadContext context, List currentDatas, DbDialect dialect) { 26 | LoadInterceptor[] interceptors = getIntercetptor(context, currentDatas); 27 | for (LoadInterceptor interceptor : interceptors) { 28 | interceptor.transactionBegin(context, currentDatas, dialect); 29 | } 30 | } 31 | 32 | public void transactionEnd(DbLoadContext context, List currentDatas, DbDialect dialect) { 33 | LoadInterceptor[] interceptors = getIntercetptor(context, currentDatas); 34 | for (LoadInterceptor interceptor : interceptors) { 35 | interceptor.transactionEnd(context, currentDatas, dialect); 36 | } 37 | } 38 | 39 | private LoadInterceptor[] getIntercetptor(DbLoadContext context, List currentData) { 40 | if (currentData == null || currentData.size() == 0) { 41 | return empty; 42 | } 43 | 44 | DbDialect dbDialect = context.getDbDialect(); 45 | 46 | if (dbDialect instanceof MysqlDialect) { 47 | return mysqlInterceptors; 48 | } else if (dbDialect instanceof OracleDialect) { 49 | return oracleInterceptors; 50 | } else { 51 | return empty; 52 | } 53 | } 54 | 55 | // ===================== setter / getter ========================= 56 | 57 | public void setMysqlInterceptors(LoadInterceptor[] mysqlInterceptors) { 58 | this.mysqlInterceptors = mysqlInterceptors; 59 | } 60 | 61 | public void setOracleInterceptors(LoadInterceptor[] oracleInterceptors) { 62 | this.oracleInterceptors = oracleInterceptors; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/interceptor/AbstractLoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.interceptor; 2 | 3 | import java.util.List; 4 | 5 | import com.alibaba.otter.clave.common.dialect.DbDialect; 6 | 7 | /** 8 | * 提供接口的默认实现 9 | * 10 | * @author jianghang 2011-11-9 下午06:28:14 11 | * @version 4.0.0 12 | */ 13 | public class AbstractLoadInterceptor implements LoadInterceptor { 14 | 15 | public void prepare(L context) { 16 | } 17 | 18 | public boolean before(L context, D currentData) { 19 | return false; 20 | } 21 | 22 | public void transactionBegin(L context, List currentDatas, DbDialect dialect) { 23 | } 24 | 25 | public void transactionEnd(L context, List currentDatas, DbDialect dialect) { 26 | } 27 | 28 | public void after(L context, D currentData) { 29 | 30 | } 31 | 32 | public void commit(L context) { 33 | } 34 | 35 | public void error(L context) { 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/interceptor/ChainLoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.interceptor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.alibaba.otter.clave.common.dialect.DbDialect; 7 | import com.alibaba.otter.clave.model.EventData; 8 | import com.alibaba.otter.clave.progress.load.LoadContext; 9 | 10 | public class ChainLoadInterceptor extends AbstractLoadInterceptor { 11 | 12 | private List interceptors = new ArrayList(); 13 | 14 | public void prepare(LoadContext context) { 15 | if (interceptors == null) { 16 | return; 17 | } 18 | 19 | for (LoadInterceptor interceptor : interceptors) { 20 | interceptor.prepare(context); 21 | } 22 | } 23 | 24 | public boolean before(LoadContext context, EventData currentData) { 25 | if (interceptors == null) { 26 | return false; 27 | } 28 | 29 | boolean result = false; 30 | for (LoadInterceptor interceptor : interceptors) { 31 | result |= interceptor.before(context, currentData); 32 | if (result) {// 出现一个true就退出 33 | return result; 34 | } 35 | } 36 | return result; 37 | } 38 | 39 | public void transactionBegin(LoadContext context, List currentDatas, DbDialect dialect) { 40 | if (interceptors == null) { 41 | return; 42 | } 43 | 44 | for (LoadInterceptor interceptor : interceptors) { 45 | interceptor.transactionBegin(context, currentDatas, dialect); 46 | } 47 | } 48 | 49 | public void transactionEnd(LoadContext context, List currentDatas, DbDialect dialect) { 50 | if (interceptors == null) { 51 | return; 52 | } 53 | 54 | for (LoadInterceptor interceptor : interceptors) { 55 | interceptor.transactionEnd(context, currentDatas, dialect); 56 | } 57 | } 58 | 59 | public void after(LoadContext context, EventData currentData) { 60 | if (interceptors == null) { 61 | return; 62 | } 63 | 64 | for (LoadInterceptor interceptor : interceptors) { 65 | interceptor.after(context, currentData); 66 | } 67 | } 68 | 69 | public void commit(LoadContext context) { 70 | if (interceptors == null) { 71 | return; 72 | } 73 | 74 | for (LoadInterceptor interceptor : interceptors) { 75 | interceptor.commit(context); 76 | } 77 | } 78 | 79 | public void error(LoadContext context) { 80 | if (interceptors == null) { 81 | return; 82 | } 83 | 84 | for (LoadInterceptor interceptor : interceptors) { 85 | interceptor.error(context); 86 | } 87 | } 88 | 89 | public void setInterceptors(List interceptors) { 90 | this.interceptors = interceptors; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/interceptor/LoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.interceptor; 2 | 3 | import java.util.List; 4 | 5 | import com.alibaba.otter.clave.common.dialect.DbDialect; 6 | 7 | public interface LoadInterceptor { 8 | 9 | public void prepare(L context); 10 | 11 | /** 12 | * 返回值代表是否需要过滤该记录,true即为过滤不处理 13 | */ 14 | public boolean before(L context, D currentData); 15 | 16 | public void transactionBegin(L context, List currentDatas, DbDialect dialect); 17 | 18 | public void transactionEnd(L context, List currentDatas, DbDialect dialect); 19 | 20 | public void after(L context, D currentData); 21 | 22 | public void commit(L context); 23 | 24 | public void error(L context); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/weight/WeightBarrier.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.weight; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | import java.util.concurrent.locks.Condition; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | 8 | /** 9 | * 构建基于weight的barrier控制 10 | * 11 | *
 12 |  * 场景:
 13 |  *   多个loader模块会进行并行加载,但每个loader的加载数据的进度统一受到weight的调度,只有当前的weight的所有数据都完成后,不同loader中的下一个weight才允许开始
 14 |  * 
 15 |  * 实现:
 16 |  * 1. 使用AQS构建了一个基于weight的barrier处理,使用一个state进行控制(代表当前运行
 19 |  * 
 20 |  * @author jianghang 2013-3-28 下午10:30:52
 21 |  * @version 1.0.0
 22 |  */
 23 | public class WeightBarrier {
 24 | 
 25 |     private ReentrantLock lock      = new ReentrantLock();
 26 |     private Condition     condition = lock.newCondition();
 27 |     private volatile long threshold;
 28 | 
 29 |     public WeightBarrier(){
 30 |         this(Long.MAX_VALUE);
 31 |     }
 32 | 
 33 |     public WeightBarrier(long weight){
 34 |         this.threshold = weight;
 35 |     }
 36 | 
 37 |     /**
 38 |      * 阻塞等待weight允许执行
 39 |      * 
 40 |      * 
 41 |      * 阻塞返回条件:
 42 |      *  1. 中断事件
 43 |      *  2. 其他线程single()的weight > 当前阻塞等待的weight
 44 |      * 
45 | * 46 | * @throws InterruptedException 47 | */ 48 | public void await(long weight) throws InterruptedException { 49 | try { 50 | lock.lockInterruptibly(); 51 | while (isPermit(weight) == false) { 52 | condition.await(); 53 | } 54 | } finally { 55 | lock.unlock(); 56 | } 57 | } 58 | 59 | /** 60 | * 阻塞等待当前的weight处理,允许设置超时时间 61 | * 62 | *
 63 |      * 阻塞返回条件:
 64 |      *  1. 中断事件
 65 |      *  2. 其他线程single()的weight > 当前阻塞等待的weight
 66 |      *  3. 超时
 67 |      * 
68 | * 69 | * @param timeout 70 | * @param unit 71 | * @throws InterruptedException 72 | * @throws TimeoutException 73 | */ 74 | public void await(long weight, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { 75 | try { 76 | lock.lockInterruptibly(); 77 | while (isPermit(weight) == false) { 78 | condition.await(timeout, unit); 79 | } 80 | } finally { 81 | lock.unlock(); 82 | } 83 | } 84 | 85 | /** 86 | * 重新设置weight信息 87 | * 88 | * @throws InterruptedException 89 | */ 90 | public void single(long weight) throws InterruptedException { 91 | try { 92 | lock.lockInterruptibly(); 93 | threshold = weight; 94 | condition.signalAll(); 95 | } finally { 96 | lock.unlock(); 97 | } 98 | } 99 | 100 | public long state() { 101 | return threshold; 102 | } 103 | 104 | private boolean isPermit(long state) { 105 | return state <= state(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/weight/WeightBuckets.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.weight; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | import com.google.common.base.Function; 8 | import com.google.common.collect.Lists; 9 | 10 | /** 11 | * buckets的集合操作对象 12 | * 13 | * @author jianghang 2013-3-28 下午10:30:52 14 | * @version 1.0.0 15 | */ 16 | public class WeightBuckets { 17 | 18 | private List> buckets = new ArrayList>(); // 对应的桶信息 19 | 20 | /** 21 | * 获取对应的weight的列表,从小到大的排序结果 22 | */ 23 | public synchronized List weights() { 24 | return Lists.transform(buckets, new Function, Long>() { 25 | 26 | public Long apply(WeightBucket input) { 27 | return input.getWeight(); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * 添加一个节点 34 | */ 35 | public synchronized void addItem(long weight, T item) { 36 | WeightBucket bucket = new WeightBucket(weight); 37 | int index = indexedSearch(buckets, bucket); 38 | if (index > buckets.size() - 1) {// 先加一个bucket 39 | bucket.addLastItem(item); 40 | buckets.add(index, bucket); 41 | } else if (buckets.get(index).getWeight() != weight) {// 不匹配的 42 | bucket.addLastItem(item); 43 | buckets.add(index, bucket); 44 | } else { 45 | buckets.get(index).addLastItem(item);// 添加到已有的bucket上 46 | } 47 | 48 | } 49 | 50 | public synchronized List getItems(long weight) { 51 | WeightBucket bucket = new WeightBucket(weight); 52 | int index = indexedSearch(buckets, bucket); 53 | if (index < buckets.size() && index >= 0) { 54 | return buckets.get(index).getBucket(); 55 | } else { 56 | return new LinkedList(); 57 | } 58 | } 59 | 60 | // ========================= helper method ===================== 61 | 62 | private int indexedSearch(List> list, WeightBucket item) { 63 | int i = 0; 64 | for (; i < list.size(); i++) { 65 | Comparable midVal = list.get(i); 66 | int cmp = midVal.compareTo(item); 67 | if (cmp == 0) {// item等于中间值 68 | return i; 69 | } else if (cmp > 0) {// item比中间值小 70 | return i; 71 | } else if (cmp < 0) {// item比中间值大 72 | // next 73 | } 74 | } 75 | 76 | return i; 77 | } 78 | 79 | } 80 | 81 | /** 82 | * 相同weight的item集合对象 83 | * 84 | * @author jianghang 2011-11-1 上午11:09:58 85 | * @version 4.0.0 86 | * @param 87 | */ 88 | class WeightBucket implements Comparable { 89 | 90 | private long weight = -1; 91 | private LinkedList bucket = new LinkedList(); 92 | 93 | public WeightBucket(){ 94 | } 95 | 96 | public WeightBucket(long weight){ 97 | this.weight = weight; 98 | } 99 | 100 | public long getWeight() { 101 | return weight; 102 | } 103 | 104 | public void setWeight(long weight) { 105 | this.weight = weight; 106 | } 107 | 108 | public List getBucket() { 109 | return bucket; 110 | } 111 | 112 | public void setBucket(LinkedList bucket) { 113 | this.bucket = bucket; 114 | } 115 | 116 | public void addFirstItem(T item) { 117 | this.bucket.addFirst(item); 118 | } 119 | 120 | public T getFirstItem() { 121 | return this.bucket.getFirst(); 122 | } 123 | 124 | public void addLastItem(T item) { 125 | this.bucket.addLast(item); 126 | } 127 | 128 | public T getLastItem() { 129 | return this.bucket.getLast(); 130 | } 131 | 132 | public int compareTo(WeightBucket o) { 133 | if (this.getWeight() > o.getWeight()) { 134 | return 1; 135 | } else if (this.getWeight() == o.getWeight()) { 136 | return 0; 137 | } else { 138 | return -1; 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/load/weight/WeightController.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.load.weight; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.PriorityBlockingQueue; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * 权重控制器 12 | * 13 | * @author jianghang 2013-3-28 下午10:30:52 14 | * @version 1.0.0 15 | */ 16 | public class WeightController { 17 | 18 | private AtomicInteger latch; 19 | private WeightBarrier barrier; 20 | private BlockingQueue weights = new PriorityBlockingQueue(); 21 | 22 | public WeightController(int load){ 23 | latch = new AtomicInteger(load); 24 | barrier = new WeightBarrier(Integer.MIN_VALUE); 25 | } 26 | 27 | /** 28 | * 每个loader任务报告启动的第一个任务的weight 29 | * 30 | * @throws InterruptedException 31 | */ 32 | public synchronized void start(List weights) throws InterruptedException { 33 | for (int i = 0; i < weights.size(); i++) { 34 | this.weights.add(weights.get(i)); 35 | } 36 | 37 | int number = latch.decrementAndGet(); 38 | if (number == 0) { 39 | Long initWeight = this.weights.peek(); 40 | if (initWeight != null) { 41 | barrier.single(initWeight); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * 等待自己当前的weight任务可以被执行 48 | * 49 | * @throws InterruptedException 50 | */ 51 | public void await(long weight) throws InterruptedException { 52 | barrier.await(weight); 53 | } 54 | 55 | /** 56 | * 等待自己当前的weight任务可以被执行,带超时控制 57 | * 58 | * @throws InterruptedException 59 | * @throws TimeoutException 60 | */ 61 | public void await(long weight, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { 62 | barrier.await(weight, timeout, unit); 63 | } 64 | 65 | /** 66 | * 通知下一个weight任务可以被执行 67 | * 68 | * @throws InterruptedException 69 | */ 70 | public synchronized void single(long weight) throws InterruptedException { 71 | this.weights.remove(weight); 72 | // 触发下一个可运行的weight 73 | Long nextWeight = this.weights.peek(); 74 | if (nextWeight != null) { 75 | barrier.single(nextWeight); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/select/ClaveSelector.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select; 2 | 3 | import com.alibaba.otter.clave.common.lifecycle.ClaveLifeCycle; 4 | 5 | /** 6 | * 增量数据获取 7 | * 8 | * @author jianghang 2013-3-28 下午09:20:16 9 | * @version 1.0.0 10 | */ 11 | public interface ClaveSelector extends ClaveLifeCycle { 12 | 13 | /** 14 | * 获取一批待处理的数据 15 | */ 16 | public Message selector() throws InterruptedException; 17 | 18 | /** 19 | * 反馈一批数据处理失败,需要下次重新被处理 20 | */ 21 | public void rollback(Long batchId); 22 | 23 | /** 24 | * 反馈所有的batch数据需要被重新处理 25 | */ 26 | public void rollback(); 27 | 28 | /** 29 | * 反馈一批数据处理完成 30 | */ 31 | public void ack(Long batchId); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/select/Message.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** 7 | * 数据对象 8 | * 9 | * @author jianghang 2013-3-28 下午09:21:32 10 | * @version 1.0.0 11 | */ 12 | public class Message implements Serializable { 13 | 14 | private static final long serialVersionUID = 4999493579483771204L; 15 | private Long id; 16 | private List datas; 17 | 18 | public Message(Long id, List datas){ 19 | this.id = id; 20 | this.datas = datas; 21 | } 22 | 23 | public Long getId() { 24 | return id; 25 | } 26 | 27 | public void setId(Long id) { 28 | this.id = id; 29 | } 30 | 31 | public List getDatas() { 32 | return datas; 33 | } 34 | 35 | public void setDatas(List datas) { 36 | this.datas = datas; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/select/canal/AbstractCanalSelector.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select.canal; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import org.apache.commons.lang.SystemUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.slf4j.MDC; 11 | import org.springframework.util.CollectionUtils; 12 | 13 | import com.alibaba.otter.canal.protocol.CanalEntry.Entry; 14 | import com.alibaba.otter.clave.ClaveConfig; 15 | import com.alibaba.otter.clave.model.EventData; 16 | import com.alibaba.otter.clave.progress.select.ClaveSelector; 17 | import com.alibaba.otter.clave.progress.select.Message; 18 | 19 | public abstract class AbstractCanalSelector implements ClaveSelector { 20 | 21 | protected static final Logger logger = LoggerFactory.getLogger(AbstractCanalSelector.class); 22 | protected static final String SEP = SystemUtils.LINE_SEPARATOR; 23 | protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 24 | protected String destination; 25 | protected int logSplitSize = 50; 26 | protected boolean dump = true; 27 | protected boolean dumpDetail = true; 28 | protected MessageParser messageParser; 29 | 30 | protected Message selector(com.alibaba.otter.canal.protocol.Message message) throws InterruptedException { 31 | List eventDatas = messageParser.parse(message.getEntries()); // 过滤事务头/尾和回环数据 32 | Message result = new Message(message.getId(), eventDatas); 33 | 34 | if (dump && logger.isInfoEnabled()) { 35 | String startPosition = null; 36 | String endPosition = null; 37 | if (!CollectionUtils.isEmpty(message.getEntries())) { 38 | startPosition = buildPositionForDump(message.getEntries().get(0)); 39 | endPosition = buildPositionForDump(message.getEntries().get(message.getEntries().size() - 1)); 40 | } 41 | 42 | dumpMessages(result, startPosition, endPosition, message.getEntries().size());// 记录一下,方便追查问题 43 | } 44 | return result; 45 | } 46 | 47 | /** 48 | * 记录一下message对象 49 | */ 50 | protected void dumpMessages(Message message, String startPosition, String endPosition, int total) { 51 | try { 52 | MDC.put(ClaveConfig.splitLogFileKey, destination); 53 | logger.info(SEP + "****************************************************" + SEP); 54 | logger.info(MessageDumper.dumpMessageInfo(message, startPosition, endPosition, total)); 55 | logger.info("****************************************************" + SEP); 56 | if (dumpDetail) {// 判断一下是否需要打印详细信息 57 | dumpEventDatas(message.getDatas()); 58 | logger.info("****************************************************" + SEP); 59 | } 60 | } finally { 61 | MDC.remove(ClaveConfig.splitLogFileKey); 62 | } 63 | } 64 | 65 | /** 66 | * 分批输出多个数据 67 | */ 68 | protected void dumpEventDatas(List eventDatas) { 69 | int size = eventDatas.size(); 70 | // 开始输出每条记录 71 | int index = 0; 72 | do { 73 | if (index + logSplitSize >= size) { 74 | logger.info(MessageDumper.dumpEventDatas(eventDatas.subList(index, size))); 75 | } else { 76 | logger.info(MessageDumper.dumpEventDatas(eventDatas.subList(index, index + logSplitSize))); 77 | } 78 | index += logSplitSize; 79 | } while (index < size); 80 | } 81 | 82 | protected String buildPositionForDump(Entry entry) { 83 | long time = entry.getHeader().getExecuteTime(); 84 | Date date = new Date(time); 85 | SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); 86 | return entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":" 87 | + entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")"; 88 | } 89 | 90 | public void setLogSplitSize(int logSplitSize) { 91 | this.logSplitSize = logSplitSize; 92 | } 93 | 94 | public void setDump(boolean dump) { 95 | this.dump = dump; 96 | } 97 | 98 | public void setDumpDetail(boolean dumpDetail) { 99 | this.dumpDetail = dumpDetail; 100 | } 101 | 102 | public void setMessageParser(MessageParser messageParser) { 103 | this.messageParser = messageParser; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/select/canal/CanalClientSelector.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select.canal; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | import org.springframework.util.CollectionUtils; 8 | 9 | import com.alibaba.otter.canal.client.CanalConnector; 10 | import com.alibaba.otter.canal.client.CanalConnectors; 11 | import com.alibaba.otter.clave.exceptions.ClaveException; 12 | import com.alibaba.otter.clave.model.EventData; 13 | import com.alibaba.otter.clave.progress.select.Message; 14 | 15 | /** 16 | * 基于canal client模式实现数据获取方式 17 | * 18 | * @author jianghang 2013-3-28 下午10:01:19 19 | * @version 1.0.0 20 | */ 21 | public class CanalClientSelector extends AbstractCanalSelector { 22 | 23 | private String destination; 24 | private String zkServers; 25 | private List address; 26 | private String username = ""; 27 | private String password = ""; 28 | private volatile boolean running = false; // 是否处于运行中 29 | 30 | private CanalConnector connector; 31 | private String filter; 32 | private int batchSize = 5000; 33 | 34 | public CanalClientSelector(){ 35 | 36 | } 37 | 38 | public CanalClientSelector(String destination, String zkServers){ 39 | this.destination = destination; 40 | this.zkServers = zkServers; 41 | } 42 | 43 | public CanalClientSelector(String destination, List address){ 44 | this.destination = destination; 45 | this.address = address; 46 | } 47 | 48 | public void start() { 49 | if (StringUtils.isNotEmpty(zkServers)) { 50 | connector = CanalConnectors.newClusterConnector(zkServers, destination, username, password); 51 | } else if (!CollectionUtils.isEmpty(address)) { 52 | connector = CanalConnectors.newClusterConnector(address, destination, username, password); 53 | } else { 54 | throw new ClaveException("no server zkservers or canal server address!"); 55 | } 56 | 57 | connector.connect(); 58 | connector.subscribe(filter); 59 | running = true; 60 | } 61 | 62 | public void stop() { 63 | if (!running) { 64 | return; 65 | } 66 | 67 | connector.disconnect(); 68 | running = false; 69 | } 70 | 71 | public boolean isStart() { 72 | return running; 73 | } 74 | 75 | public Message selector() throws InterruptedException { 76 | com.alibaba.otter.canal.protocol.Message message = null; 77 | while (running) { 78 | message = connector.getWithoutAck(batchSize); 79 | if (message == null || message.getId() == -1L) { // 代表没数据 80 | continue; 81 | } else { 82 | break; 83 | } 84 | } 85 | 86 | if (!running) { 87 | throw new InterruptedException(); 88 | } 89 | 90 | return selector(message); 91 | } 92 | 93 | public void ack(Long batchId) { 94 | connector.ack(batchId); 95 | } 96 | 97 | public void rollback(Long batchId) { 98 | connector.rollback(batchId); 99 | } 100 | 101 | public void rollback() { 102 | connector.rollback(); 103 | } 104 | 105 | // ================= setter / getter ===================== 106 | 107 | public void setDestination(String destination) { 108 | this.destination = destination; 109 | } 110 | 111 | public void setZkServers(String zkServers) { 112 | this.zkServers = zkServers; 113 | } 114 | 115 | public void setAddress(List address) { 116 | this.address = address; 117 | } 118 | 119 | public void setUsername(String username) { 120 | this.username = username; 121 | } 122 | 123 | public void setPassword(String password) { 124 | this.password = password; 125 | } 126 | 127 | public void setFilter(String filter) { 128 | this.filter = filter; 129 | } 130 | 131 | public void setBatchSize(int batchSize) { 132 | this.batchSize = batchSize; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/select/canal/MessageDumper.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select.canal; 2 | 3 | import java.text.MessageFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.apache.commons.lang.StringUtils; 9 | import org.apache.commons.lang.SystemUtils; 10 | import org.springframework.util.CollectionUtils; 11 | 12 | import com.alibaba.otter.clave.model.EventColumn; 13 | import com.alibaba.otter.clave.model.EventData; 14 | import com.alibaba.otter.clave.progress.select.Message; 15 | 16 | /** 17 | * dump记录 18 | * 19 | * @author jianghang 2013-3-28 下午09:57:42 20 | * @version 1.0.0 21 | */ 22 | public class MessageDumper { 23 | 24 | private static final String SEP = SystemUtils.LINE_SEPARATOR; 25 | private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss:SSS"; 26 | private static String context_format = null; 27 | private static String eventData_format = null; 28 | private static int event_default_capacity = 1024; // 预设值StringBuilder,减少扩容影响 29 | 30 | static { 31 | context_format = "* Batch Id: [{0}] ,total : [{1}] , normal : [{2}] , filter :[{3}] , Time : {4}" + SEP; 32 | context_format += "* Start : [{5}] " + SEP; 33 | context_format += "* End : [{6}] " + SEP; 34 | 35 | eventData_format = "-----------------" + SEP; 36 | eventData_format += "- Schema: {0} , Table: {1} " + SEP; 37 | eventData_format += "- Type: {2} , ExecuteTime: {3} " + SEP; 38 | eventData_format += "-----------------" + SEP; 39 | eventData_format += "---START" + SEP; 40 | eventData_format += "---Pks" + SEP; 41 | eventData_format += "{4}" + SEP; 42 | eventData_format += "---oldPks" + SEP; 43 | eventData_format += "{5}" + SEP; 44 | eventData_format += "---Columns" + SEP; 45 | eventData_format += "{6}" + SEP; 46 | eventData_format += "---END" + SEP; 47 | 48 | } 49 | 50 | public static String dumpMessageInfo(Message message, String startPosition, String endPosition, int total) { 51 | Date now = new Date(); 52 | SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_FORMAT); 53 | int normal = message.getDatas().size(); 54 | return MessageFormat.format(context_format, String.valueOf(message.getId()), total, normal, total - normal, 55 | format.format(now), startPosition, endPosition); 56 | } 57 | 58 | public static String dumpEventDatas(List eventDatas) { 59 | if (CollectionUtils.isEmpty(eventDatas)) { 60 | return StringUtils.EMPTY; 61 | } 62 | 63 | // 预先设定容量大小 64 | StringBuilder builder = new StringBuilder(event_default_capacity * eventDatas.size()); 65 | for (EventData data : eventDatas) { 66 | builder.append(dumpEventData(data)); 67 | } 68 | return builder.toString(); 69 | } 70 | 71 | public static String dumpEventData(EventData eventData) { 72 | return MessageFormat.format(eventData_format, eventData.getSchemaName(), eventData.getTableName(), 73 | eventData.getEventType().getValue(), String.valueOf(eventData.getExecuteTime()), 74 | dumpEventColumn(eventData.getKeys()), dumpEventColumn(eventData.getOldKeys()), 75 | dumpEventColumn(eventData.getColumns()), "\t" + eventData.getSql()); 76 | } 77 | 78 | private static String dumpEventColumn(List columns) { 79 | StringBuilder builder = new StringBuilder(event_default_capacity); 80 | int size = columns.size(); 81 | for (int i = 0; i < size; i++) { 82 | EventColumn column = columns.get(i); 83 | builder.append("\t").append(column.toString()); 84 | if (i < columns.size() - 1) { 85 | builder.append(SEP); 86 | } 87 | } 88 | return builder.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/progress/transform/ClaveTransform.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.transform; 2 | 3 | import com.alibaba.otter.clave.common.lifecycle.ClaveLifeCycle; 4 | 5 | public interface ClaveTransform extends ClaveLifeCycle { 6 | 7 | public T transform(T data); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/utils/ClaveToStringStyle.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | import org.apache.commons.lang.builder.ToStringStyle; 7 | 8 | /** 9 | * clave项目内部使用的ToStringStyle 10 | * 11 | *
12 |  * 默认Style输出格式:
13 |  * Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
14 |  * 
15 | * 16 | * @author jianghang 2010-6-18 上午11:35:27 17 | */ 18 | public class ClaveToStringStyle extends ToStringStyle { 19 | 20 | private static final long serialVersionUID = -6568177374288222145L; 21 | 22 | private static final String DEFAULT_TIME = "yyyy-MM-dd HH:mm:ss"; 23 | private static final String DEFAULT_DAY = "yyyy-MM-dd"; 24 | 25 | /** 26 | *
27 |      * 输出格式:
28 |      * Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
29 |      * 
30 | */ 31 | public static final ToStringStyle TIME_STYLE = new ClaveDateStyle(DEFAULT_TIME); 32 | 33 | /** 34 | *
35 |      * 输出格式:
36 |      * Person[name=John Doe,age=33,smoker=false ,day=2010-04-01]
37 |      * 
38 | */ 39 | public static final ToStringStyle DAY_STYLE = new ClaveDateStyle(DEFAULT_DAY); 40 | 41 | /** 42 | *
43 |      * 输出格式:
44 |      * Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
45 |      * 
46 | */ 47 | public static final ToStringStyle DEFAULT_STYLE = ClaveToStringStyle.TIME_STYLE; 48 | 49 | // =========================== 自定义style ============================= 50 | 51 | /** 52 | * 支持日期格式化的ToStringStyle 53 | * 54 | * @author li.jinl 55 | */ 56 | private static class ClaveDateStyle extends ToStringStyle { 57 | 58 | private static final long serialVersionUID = 5208917932254652886L; 59 | 60 | // 日期format格式 61 | private String pattern; 62 | 63 | public ClaveDateStyle(String pattern){ 64 | super(); 65 | this.setUseShortClassName(true); 66 | this.setUseIdentityHashCode(false); 67 | // 设置日期format格式 68 | this.pattern = pattern; 69 | } 70 | 71 | protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { 72 | // 增加自定义的date对象处理 73 | if (value instanceof Date) { 74 | value = new SimpleDateFormat(pattern).format(value); 75 | } 76 | // 后续可以增加其他自定义对象处理 77 | buffer.append(value); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/utils/spring/PropertyPlaceholderConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.utils.spring; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Properties; 6 | 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.context.ResourceLoaderAware; 9 | import org.springframework.core.io.Resource; 10 | import org.springframework.core.io.ResourceLoader; 11 | import org.springframework.util.Assert; 12 | 13 | /** 14 | * 扩展Spring的{@linkplain org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} ,增加默认值的功能。 15 | * 例如:${placeholder:defaultValue},假如placeholder的值不存在,则默认取得 defaultValue。 16 | * 17 | * @author jianghang 2013-1-24 下午03:37:56 18 | * @version 1.0.0 19 | */ 20 | public class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer implements ResourceLoaderAware, InitializingBean { 21 | 22 | private static final String PLACEHOLDER_PREFIX = "${"; 23 | private static final String PLACEHOLDER_SUFFIX = "}"; 24 | private ResourceLoader loader; 25 | private String[] locationNames; 26 | 27 | public PropertyPlaceholderConfigurer(){ 28 | setIgnoreUnresolvablePlaceholders(true); 29 | } 30 | 31 | public void setResourceLoader(ResourceLoader loader) { 32 | this.loader = loader; 33 | } 34 | 35 | public void setLocationNames(String[] locations) { 36 | this.locationNames = locations; 37 | } 38 | 39 | public void afterPropertiesSet() throws Exception { 40 | Assert.notNull(loader, "no resourceLoader"); 41 | 42 | if (locationNames != null) { 43 | for (int i = 0; i < locationNames.length; i++) { 44 | locationNames[i] = resolveSystemPropertyPlaceholders(locationNames[i]); 45 | } 46 | } 47 | 48 | if (locationNames != null) { 49 | List resources = new ArrayList(locationNames.length); 50 | 51 | for (String location : locationNames) { 52 | location = trimToNull(location); 53 | 54 | if (location != null) { 55 | resources.add(loader.getResource(location)); 56 | } 57 | } 58 | 59 | super.setLocations(resources.toArray(new Resource[resources.size()])); 60 | } 61 | } 62 | 63 | private String resolveSystemPropertyPlaceholders(String text) { 64 | StringBuilder buf = new StringBuilder(text); 65 | 66 | for (int startIndex = buf.indexOf(PLACEHOLDER_PREFIX); startIndex >= 0;) { 67 | int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length()); 68 | 69 | if (endIndex != -1) { 70 | String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); 71 | int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length(); 72 | 73 | try { 74 | String value = resolveSystemPropertyPlaceholder(placeholder); 75 | 76 | if (value != null) { 77 | buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), value); 78 | nextIndex = startIndex + value.length(); 79 | } else { 80 | System.err.println("Could not resolve placeholder '" 81 | + placeholder 82 | + "' in [" 83 | + text 84 | + "] as system property: neither system property nor environment variable found"); 85 | } 86 | } catch (Throwable ex) { 87 | System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text 88 | + "] as system property: " + ex); 89 | } 90 | 91 | startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex); 92 | } else { 93 | startIndex = -1; 94 | } 95 | } 96 | 97 | return buf.toString(); 98 | } 99 | 100 | private String resolveSystemPropertyPlaceholder(String placeholder) { 101 | DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); 102 | String value = System.getProperty(dp.placeholder); 103 | 104 | if (value == null) { 105 | value = System.getenv(dp.placeholder); 106 | } 107 | 108 | if (value == null) { 109 | value = dp.defaultValue; 110 | } 111 | 112 | return value; 113 | } 114 | 115 | @Override 116 | protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { 117 | DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); 118 | String value = super.resolvePlaceholder(dp.placeholder, props, systemPropertiesMode); 119 | 120 | if (value == null) { 121 | value = dp.defaultValue; 122 | } 123 | 124 | return trimToEmpty(value); 125 | } 126 | 127 | private static class DefaultablePlaceholder { 128 | 129 | private final String defaultValue; 130 | private final String placeholder; 131 | 132 | public DefaultablePlaceholder(String placeholder){ 133 | int commaIndex = placeholder.indexOf(":"); 134 | String defaultValue = null; 135 | 136 | if (commaIndex >= 0) { 137 | defaultValue = trimToEmpty(placeholder.substring(commaIndex + 1)); 138 | placeholder = trimToEmpty(placeholder.substring(0, commaIndex)); 139 | } 140 | 141 | this.placeholder = placeholder; 142 | this.defaultValue = defaultValue; 143 | } 144 | } 145 | 146 | private String trimToNull(String str) { 147 | if (str == null) { 148 | return null; 149 | } 150 | 151 | String result = str.trim(); 152 | 153 | if (result == null || result.length() == 0) { 154 | return null; 155 | } 156 | 157 | return result; 158 | } 159 | 160 | public static String trimToEmpty(String str) { 161 | if (str == null) { 162 | return ""; 163 | } 164 | 165 | return str.trim(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/otter/clave/utils/spring/SocketAddressEditor.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.utils.spring; 2 | 3 | import java.beans.PropertyEditorSupport; 4 | import java.net.InetSocketAddress; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | import org.springframework.beans.PropertyEditorRegistrar; 8 | import org.springframework.beans.PropertyEditorRegistry; 9 | 10 | public class SocketAddressEditor extends PropertyEditorSupport implements PropertyEditorRegistrar { 11 | 12 | public void registerCustomEditors(PropertyEditorRegistry registry) { 13 | registry.registerCustomEditor(InetSocketAddress.class, this); 14 | } 15 | 16 | public void setAsText(String text) throws IllegalArgumentException { 17 | String[] addresses = StringUtils.split(text, ":"); 18 | if (addresses.length > 0) { 19 | setValue(new InetSocketAddress(addresses[0], Integer.valueOf(addresses[1]))); 20 | } else { 21 | setValue(null); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/clave.properties: -------------------------------------------------------------------------------- 1 | clave.destination=example 2 | clave.zkServers=10.20.144.51:2181 3 | clave.filter=test.ljh_demo 4 | clave.batchSize=100 5 | clave.rowMode=false 6 | clave.poolSize=5 7 | clave.serverId=1 8 | clave.debug=true 9 | clave.skipLoadException=false 10 | clave.db.type=MYSQL 11 | clave.db.url=jdbc:mysql://10.20.144.29:3306 12 | clave.db.driver=com.mysql.jdbc.Driver 13 | #clave.db.driver=oracle.jdbc.driver.OracleDriver 14 | clave.db.username=ottermysql 15 | clave.db.password=ottermysql 16 | clave.db.encode=UTF-8 -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | ../logs/clave.log 12 | 13 | 14 | ../logs/%d{yyyy-MM-dd}/clave-%d{yyyy-MM-dd}-%i.log.gz 15 | 16 | 17 | 512MB 18 | 19 | 60 20 | 21 | 22 | 23 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n 24 | 25 | 26 | 27 | 28 | 29 | ../logs/select.log 30 | 31 | 32 | ../logs/%d{yyyy-MM-dd}/select-%d{yyyy-MM-dd}-%i.log.gz 33 | 34 | 35 | 512MB 36 | 37 | 60 38 | 39 | 40 | %msg 41 | 42 | 43 | 44 | 45 | ../logs/load.log 46 | 47 | 48 | ../logs/%d{yyyy-MM-dd}/load-%d{yyyy-MM-dd}-%i.log.gz 49 | 50 | 51 | 512MB 52 | 53 | 60 54 | 55 | 56 | %msg 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/resources/spring/clave.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | classpath:clave.properties 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/test/java/com/alibaba/otter/clave/progress/select/CanalClientSelectorTest.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.otter.clave.progress.select; 2 | 3 | import org.junit.Test; 4 | 5 | import com.alibaba.otter.clave.model.EventData; 6 | import com.alibaba.otter.clave.progress.select.Message; 7 | import com.alibaba.otter.clave.progress.select.canal.CanalClientSelector; 8 | import com.alibaba.otter.clave.progress.select.canal.MessageParser; 9 | 10 | public class CanalClientSelectorTest { 11 | 12 | @Test 13 | public void testSimple() throws Exception { 14 | CanalClientSelector selector = new CanalClientSelector("example", "10.20.144.51:2181"); 15 | MessageParser messageParser = new MessageParser(); 16 | selector.setMessageParser(messageParser); 17 | selector.setBatchSize(100); 18 | selector.setFilter(""); 19 | selector.start(); 20 | 21 | selector.rollback(); 22 | int totalEmtryCount = 120; 23 | int emptyCount = 0; 24 | while (emptyCount < totalEmtryCount) { 25 | Message message = selector.selector(); 26 | long batchId = message.getId(); 27 | int size = message.getDatas().size(); 28 | if (batchId == -1 || size == 0) { 29 | emptyCount++; 30 | System.out.println("empty count : " + emptyCount); 31 | try { 32 | Thread.sleep(1000); 33 | } catch (InterruptedException e) { 34 | } 35 | } else { 36 | emptyCount = 0; 37 | System.out.printf("message[batchId=%s,size=%s] \n", batchId, size); 38 | } 39 | 40 | selector.ack(batchId); // 提交确认 41 | // connector.rollback(batchId); // 处理失败, 回滚数据 42 | } 43 | 44 | System.out.println("empty too many times, exit"); 45 | selector.stop(); 46 | } 47 | } 48 | --------------------------------------------------------------------------------