Repository: alibaba/fescar
Pull request #609 (2019-03-15 12:59:40)

resolve conflicts
<!-- Please make sure you have read and understood the contributing guidelines -->

### Ⅰ. Describe what this PR did


### Ⅱ. Does this pull request fix one issue?
<!-- If that, add "fixes #xxx" below in the next line, for example, fixes #97. -->


### Ⅲ. Why don't you add test cases (unit test/integration test)? 


### Ⅳ. Describe how to verify it


### Ⅴ. Special notes for reviews




Patch:
diff --git a/.travis.yml b/.travis.yml
index 12c75db39..0223be4c4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,8 +2,8 @@ language: java
 sudo: false # faster builds
 
 jdk:
-- openjdk11
-- openjdk8
+  - openjdk11
+  - openjdk8
 
 cache:
   directories:
diff --git a/README.md b/README.md
index 45f35b749..b6133fb03 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ For more details about principle and design, please go to [Fescar wiki page](htt
 
 ## Maven dependency
 ```xml
-<fescar.version>0.2.2</fescar.version>
+<fescar.version>0.3.0</fescar.version>
 
 <dependency>
     <groupId>com.alibaba.fescar</groupId>
diff --git a/common/pom.xml b/common/pom.xml
index f6dd854c9..5dabc7270 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-common</artifactId>
diff --git a/common/src/main/java/com/alibaba/fescar/common/thread/RejectedPolicies.java b/common/src/main/java/com/alibaba/fescar/common/thread/RejectedPolicies.java
new file mode 100644
index 000000000..32f661642
--- /dev/null
+++ b/common/src/main/java/com/alibaba/fescar/common/thread/RejectedPolicies.java
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.common.thread;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * Policies for RejectedExecutionHandler
+ *
+ * Created by guoyao on 2019/2/26.
+ */
+public final class RejectedPolicies {
+
+    /**
+     * when rejected happened ,add the new task and run the oldest task
+     *
+     * @return rejected execution handler
+     */
+    public static RejectedExecutionHandler runsOldestTaskPolicy() {
+        return new RejectedExecutionHandler() {
+            @Override
+            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+                if (executor.isShutdown()) {
+                    return;
+                }
+                BlockingQueue<Runnable> workQueue = executor.getQueue();
+                Runnable firstWork = workQueue.poll();
+                boolean newTaskAdd = workQueue.offer(r);
+                if (firstWork != null) {
+                    firstWork.run();
+                }
+                if (!newTaskAdd) {
+                    executor.execute(r);
+                }
+            }
+        };
+    }
+}
diff --git a/common/src/main/java/com/alibaba/fescar/common/util/NetUtil.java b/common/src/main/java/com/alibaba/fescar/common/util/NetUtil.java
index cc0503326..a8d5a38fa 100644
--- a/common/src/main/java/com/alibaba/fescar/common/util/NetUtil.java
+++ b/common/src/main/java/com/alibaba/fescar/common/util/NetUtil.java
@@ -36,7 +36,7 @@ public class NetUtil {
     private static final Logger LOGGER = LoggerFactory.getLogger(NetUtil.class);
     private static final String LOCALHOST = "127.0.0.1";
 
-    private static final String ANYHOST = "0.0.0.0";
+    private static final String ANY_HOST = "0.0.0.0";
 
     private static volatile InetAddress LOCAL_ADDRESS = null;
 
@@ -202,6 +202,7 @@ public class NetUtil {
             return false;
         }
         String name = address.getHostAddress();
-        return (name != null && !ANYHOST.equals(name) && !LOCALHOST.equals(name) && IP_PATTERN.matcher(name).matches());
+        return (name != null && !ANY_HOST.equals(name) && !LOCALHOST.equals(name) && IP_PATTERN.matcher(name)
+            .matches());
     }
 }
diff --git a/common/src/main/java/com/alibaba/fescar/common/util/StringUtils.java b/common/src/main/java/com/alibaba/fescar/common/util/StringUtils.java
index e827d0f0d..f4f20e556 100644
--- a/common/src/main/java/com/alibaba/fescar/common/util/StringUtils.java
+++ b/common/src/main/java/com/alibaba/fescar/common/util/StringUtils.java
@@ -39,7 +39,7 @@ public class StringUtils {
      * @param str the str
      * @return the boolean
      */
-    public static final boolean isEmpty(String str) {
+    public static final boolean isNullOrEmpty(String str) {
         return (str == null) || (str.isEmpty());
     }
 
diff --git a/config/pom.xml b/config/pom.xml
index bdcfb76cd..f97ca0a36 100644
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-config</artifactId>
@@ -57,5 +57,9 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.101tec</groupId>
+            <artifactId>zkclient</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/config/src/main/java/com/alibaba/fescar/config/ConfigType.java b/config/src/main/java/com/alibaba/fescar/config/ConfigType.java
index 1afb4ec95..fc4728f91 100644
--- a/config/src/main/java/com/alibaba/fescar/config/ConfigType.java
+++ b/config/src/main/java/com/alibaba/fescar/config/ConfigType.java
@@ -16,6 +16,7 @@
 
 package com.alibaba.fescar.config;
 
+
 import com.alibaba.fescar.common.exception.NotSupportYetException;
 
 /**
@@ -29,6 +30,10 @@ public enum ConfigType {
      * File config type.
      */
     File,
+    /**
+     * zookeeper config type.
+     */
+    ZK,
     /**
      * Nacos config type.
      */
@@ -45,14 +50,11 @@ public enum ConfigType {
      * @return the type
      */
     public static ConfigType getType(String name) {
-        if (File.name().equalsIgnoreCase(name)) {
-            return File;
-        } else if (Nacos.name().equalsIgnoreCase(name)) {
-            return Nacos;
-        } else if (Apollo.name().equalsIgnoreCase(name)) {
-            return Apollo;
-        } else {
-            throw new NotSupportYetException("unsupport type:" + name);
+        for (ConfigType configType : values()) {
+            if (configType.name().equalsIgnoreCase(name)) {
+                return configType;
+            }
         }
+        throw new IllegalArgumentException("illegal type:" + name);
     }
 }
diff --git a/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java b/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java
index 7e28e8e65..bc5ec4ba1 100644
--- a/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java
+++ b/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java
@@ -46,12 +46,13 @@ public final class ConfigurationFactory {
      */
     public static Configuration getInstance() {
         ConfigType configType = null;
+        String configTypeName = null;
         try {
-            configType = ConfigType.getType(
-                FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
-                    + ConfigurationKeys.FILE_ROOT_TYPE));
+            configTypeName = FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
+                    + ConfigurationKeys.FILE_ROOT_TYPE);
+            configType = ConfigType.getType(configTypeName);
         } catch (Exception exx) {
-            LOGGER.error(exx.getMessage());
+            throw new NotSupportYetException("not support register type: " + configTypeName);
         }
         Configuration configuration;
         switch (configType) {
@@ -76,6 +77,13 @@ public final class ConfigurationFactory {
                 String name = FILE_INSTANCE.getConfig(pathDataId);
                 configuration = new FileConfiguration(name);
                 break;
+            case ZK:
+                try {
+                    configuration = new ZKConfiguration();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                break;
             default:
                 throw new NotSupportYetException("not support register type:" + configType);
         }
diff --git a/config/src/main/java/com/alibaba/fescar/config/ZKConfiguration.java b/config/src/main/java/com/alibaba/fescar/config/ZKConfiguration.java
new file mode 100644
index 000000000..b5a06e9de
--- /dev/null
+++ b/config/src/main/java/com/alibaba/fescar/config/ZKConfiguration.java
@@ -0,0 +1,163 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.config;
+
+import com.alibaba.fescar.common.exception.NotSupportYetException;
+import com.alibaba.fescar.common.util.StringUtils;
+
+import org.I0Itec.zkclient.IZkDataListener;
+import org.I0Itec.zkclient.ZkClient;
+import org.apache.zookeeper.CreateMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.FutureTask;
+
+import static com.alibaba.fescar.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR;
+
+/**
+ * @author crazier.huang
+ * @date 2019/2/18
+ */
+public class ZKConfiguration extends AbstractConfiguration<IZkDataListener> {
+    private final static Logger LOGGER = LoggerFactory.getLogger(ZKConfiguration.class);
+
+    private static final String REGISTRY_TYPE = "zk";
+    private static final String ZK_PATH_SPLIT_CHAR = "/";
+    private static final String FILE_ROOT_CONFIG = "config";
+    private static final String ROOT_PATH = ZK_PATH_SPLIT_CHAR + FILE_ROOT_CONFIG;
+    private static final Configuration FILE_CONFIG = ConfigurationFactory.FILE_INSTANCE;
+    private static final String SERVER_ADDR_KEY = "serverAddr";
+    private static final String SESSION_TIME_OUT_KEY = "session.timeout";
+    private static final String CONNECT_TIME_OUT_KEY = "connect.timeout";
+    private static final int THREAD_POOL_NUM = 5;
+    private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE
+        + FILE_CONFIG_SPLIT_CHAR;
+    private static final ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM,
+        Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
+    private static volatile ZkClient zkClient;
+
+    public ZKConfiguration() {
+        if (zkClient == null) {
+            zkClient = new ZkClient(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY),
+                FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIME_OUT_KEY),
+                FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIME_OUT_KEY));
+            if (!zkClient.exists(ROOT_PATH)) {
+                zkClient.createPersistent(ROOT_PATH, true);
+            }
+        }
+    }
+
+    @Override
+    public String getTypeName() {
+        return REGISTRY_TYPE;
+    }
+
+    @Override
+    public String getConfig(String dataId, String defaultValue, long timeoutMills) {
+        FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
+            public String call() throws Exception {
+                String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
+                String value = zkClient.readData(path);
+                if (StringUtils.isNullOrEmpty(value)) {
+                    return defaultValue;
+                }
+                return value;
+            }
+        });
+        executor.execute(future);
+        try {
+            return future.get(timeoutMills, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            LOGGER.error("getConfig {} is error or timeout,return defaultValue {}", dataId, defaultValue);
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public boolean putConfig(String dataId, String content, long timeoutMills) {
+        FutureTask<Boolean> future = new FutureTask<Boolean>(new Callable<Boolean>() {
+            public Boolean call() throws Exception {
+                String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
+                if (!zkClient.exists(path)) {
+                    zkClient.create(path, content, CreateMode.PERSISTENT);
+                } else {
+                    zkClient.writeData(path, content);
+                }
+                return true;
+            }
+        });
+        executor.execute(future);
+        try {
+            return future.get(timeoutMills, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            LOGGER.warn("putConfig {} : {} is error or timeout", dataId, content);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
+        throw new NotSupportYetException("not support atom operation putConfigIfAbsent");
+    }
+
+    @Override
+    public boolean removeConfig(String dataId, long timeoutMills) {
+        FutureTask<Boolean> future = new FutureTask<Boolean>(new Callable<Boolean>() {
+            public Boolean call() throws Exception {
+                String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
+                return zkClient.delete(path);
+            }
+        });
+        executor.execute(future);
+        try {
+            return future.get(timeoutMills, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            LOGGER.warn("removeConfig {} is error or timeout", dataId);
+            return false;
+        }
+
+    }
+
+    @Override
+    public void addConfigListener(String dataId, IZkDataListener listener) {
+        String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
+        if (zkClient.exists(path)) {
+            zkClient.subscribeDataChanges(path, listener);
+        }
+    }
+
+    @Override
+    public void removeConfigListener(String dataId, IZkDataListener listener) {
+        String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
+        if (zkClient.exists(path)) {
+            zkClient.unsubscribeDataChanges(path, listener);
+        }
+    }
+
+    @Override
+    public List<IZkDataListener> getConfigListeners(String dataId) {
+        throw new NotSupportYetException("not support getConfigListeners");
+    }
+
+}
diff --git a/config/src/main/resources/file.conf b/config/src/main/resources/file.conf
index e0757acc5..7b76b82c2 100644
--- a/config/src/main/resources/file.conf
+++ b/config/src/main/resources/file.conf
@@ -30,7 +30,6 @@ service {
   #disable
   disable = false
 }
-
 client {
   async.commit.buffer.limit = 10000
   lock {
diff --git a/config/src/main/resources/registry.conf b/config/src/main/resources/registry.conf
index c44cb9f7d..fa9a4da9c 100644
--- a/config/src/main/resources/registry.conf
+++ b/config/src/main/resources/registry.conf
@@ -1,5 +1,5 @@
 registry {
-  # file 、nacos 、redis
+  # file 、nacos 、eureka、redis、zk
   type = "file"
 
   nacos {
@@ -7,17 +7,28 @@ registry {
     namespace = "public"
     cluster = "default"
   }
+  eureka {
+    serviceUrl = "http://localhost:1001/eureka"
+    application = "default"
+    weight = "1"
+  }
   redis {
     serverAddr = "localhost:6379"
     db = "0"
   }
+  zk {
+    cluster = "default"
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
 }
 
 config {
-  # file nacos apollo
+  # file、nacos 、apollo、zk
   type = "file"
 
   nacos {
@@ -26,9 +37,14 @@ config {
     cluster = "default"
   }
   apollo {
-      app.id = "fescar-server"
-      apollo.meta = "http://192.168.1.204:8801"
-   }
+    app.id = "fescar-server"
+    apollo.meta = "http://192.168.1.204:8801"
+  }
+  zk {
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
diff --git a/core/pom.xml b/core/pom.xml
index bb8cd4c79..c398d3312 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-core</artifactId>
diff --git a/core/src/main/java/com/alibaba/fescar/core/protocol/Version.java b/core/src/main/java/com/alibaba/fescar/core/protocol/Version.java
index 884f5322b..01b54bf4b 100644
--- a/core/src/main/java/com/alibaba/fescar/core/protocol/Version.java
+++ b/core/src/main/java/com/alibaba/fescar/core/protocol/Version.java
@@ -31,7 +31,7 @@ public class Version {
     /**
      * The constant CURRENT.
      */
-    public static final String CURRENT = "0.3.0";
+    public static final String CURRENT = "0.3.1";
 
     /**
      * The constant VERSION_MAP.
diff --git a/core/src/main/java/com/alibaba/fescar/core/rpc/ChannelManager.java b/core/src/main/java/com/alibaba/fescar/core/rpc/ChannelManager.java
index 758a38602..5263f882d 100644
--- a/core/src/main/java/com/alibaba/fescar/core/rpc/ChannelManager.java
+++ b/core/src/main/java/com/alibaba/fescar/core/rpc/ChannelManager.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentMap;
 
 import com.alibaba.fescar.common.Constants;
 import com.alibaba.fescar.common.exception.FrameworkException;
+import com.alibaba.fescar.common.util.StringUtils;
 import com.alibaba.fescar.core.protocol.IncompatibleVersionException;
 import com.alibaba.fescar.core.protocol.RegisterRMRequest;
 import com.alibaba.fescar.core.protocol.RegisterTMRequest;
@@ -32,7 +33,6 @@ import com.alibaba.fescar.core.protocol.Version;
 import com.alibaba.fescar.core.rpc.netty.NettyPoolKey.TransactionRole;
 
 import io.netty.channel.Channel;
-import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -233,7 +233,7 @@ public class ChannelManager {
     }
 
     private static Set<String> dbKeytoSet(String dbkey) {
-        if (StringUtils.isEmpty(dbkey)) {
+        if (StringUtils.isNullOrEmpty(dbkey)) {
             return null;
         }
         Set<String> set = new HashSet<String>();
diff --git a/core/src/main/java/com/alibaba/fescar/core/rpc/netty/AbstractRpcRemotingServer.java b/core/src/main/java/com/alibaba/fescar/core/rpc/netty/AbstractRpcRemotingServer.java
index 79bb50721..2166db0d9 100644
--- a/core/src/main/java/com/alibaba/fescar/core/rpc/netty/AbstractRpcRemotingServer.java
+++ b/core/src/main/java/com/alibaba/fescar/core/rpc/netty/AbstractRpcRemotingServer.java
@@ -31,6 +31,7 @@ import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.WriteBufferWaterMark;
 import io.netty.channel.epoll.EpollEventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.SocketChannel;
@@ -125,9 +126,9 @@ public abstract class AbstractRpcRemotingServer extends AbstractRpcRemoting impl
             .childOption(ChannelOption.TCP_NODELAY, true)
             .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSendBufSize())
             .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketResvBufSize())
-            .childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK,
-                nettyServerConfig.getWriteBufferHighWaterMark())
-            .childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, nettyServerConfig.getWriteBufferLowWaterMark())
+            .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
+                new WriteBufferWaterMark(nettyServerConfig.getWriteBufferLowWaterMark(),
+                    nettyServerConfig.getWriteBufferHighWaterMark()))
             .localAddress(new InetSocketAddress(listenPort))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
diff --git a/core/src/main/java/com/alibaba/fescar/core/rpc/netty/TmRpcClient.java b/core/src/main/java/com/alibaba/fescar/core/rpc/netty/TmRpcClient.java
index 0c61bef77..610582fed 100644
--- a/core/src/main/java/com/alibaba/fescar/core/rpc/netty/TmRpcClient.java
+++ b/core/src/main/java/com/alibaba/fescar/core/rpc/netty/TmRpcClient.java
@@ -31,7 +31,7 @@ import com.alibaba.fescar.common.XID;
 import com.alibaba.fescar.common.exception.FrameworkErrorCode;
 import com.alibaba.fescar.common.exception.FrameworkException;
 import com.alibaba.fescar.common.thread.NamedThreadFactory;
-import com.alibaba.fescar.common.thread.RejectedPolicys;
+import com.alibaba.fescar.common.thread.RejectedPolicies;
 import com.alibaba.fescar.common.util.NetUtil;
 import com.alibaba.fescar.config.Configuration;
 import com.alibaba.fescar.config.ConfigurationFactory;
@@ -126,7 +126,7 @@ public final class TmRpcClient extends AbstractRpcRemotingClient {
                         new LinkedBlockingQueue(MAX_QUEUE_SIZE),
                         new NamedThreadFactory(nettyClientConfig.getTmDispatchThreadPrefix(),
                             nettyClientConfig.getClientWorkerThreads()),
-                        RejectedPolicys.runsOldestTaskPolicy());
+                        RejectedPolicies.runsOldestTaskPolicy());
                     instance = new TmRpcClient(nettyClientConfig, null, threadPoolExecutor);
                 }
             }
diff --git a/core/src/main/java/com/alibaba/fescar/core/service/ServiceManagerStaticConfigImpl.java b/core/src/main/java/com/alibaba/fescar/core/service/ServiceManagerStaticConfigImpl.java
index b701fc626..45b638152 100644
--- a/core/src/main/java/com/alibaba/fescar/core/service/ServiceManagerStaticConfigImpl.java
+++ b/core/src/main/java/com/alibaba/fescar/core/service/ServiceManagerStaticConfigImpl.java
@@ -63,7 +63,7 @@ public class ServiceManagerStaticConfigImpl implements ServiceManager {
         }
         String rGroupDataId = ConfigurationKeys.SERVICE_PREFIX + rGroup + ConfigurationKeys.GROUPLIST_POSTFIX;
         String serverListConfig = configuration.getConfig(rGroupDataId);
-        if (StringUtils.isEmpty(serverListConfig)) {
+        if (StringUtils.isNullOrEmpty(serverListConfig)) {
             throw new FrameworkException(InvalidConfiguration);
         }
         serverAddresses = serverListConfig.split(SERVER_NODE_SPLIT_CHAR);
diff --git a/discovery/pom.xml b/discovery/pom.xml
index 034830631..0c04563f1 100644
--- a/discovery/pom.xml
+++ b/discovery/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/EurekaRegistryServiceImpl.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/EurekaRegistryServiceImpl.java
index 6adcdb4bd..b4fb24c6b 100644
--- a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/EurekaRegistryServiceImpl.java
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/EurekaRegistryServiceImpl.java
@@ -143,7 +143,7 @@ public class EurekaRegistryServiceImpl implements RegistryService<EurekaEventLis
             });
         }
 
-        return Lists.newArrayList(CLUSTER_ADDRESS_MAP.get(clusterName));
+        return Lists.newArrayList(CLUSTER_ADDRESS_MAP.get(clusterName.toUpperCase()));
     }
 
     private static void refreshCluster() {
@@ -226,7 +226,7 @@ public class EurekaRegistryServiceImpl implements RegistryService<EurekaEventLis
     }
 
     private static String getInstanceId() {
-        return String.format("%s/%s:%d", instanceConfig.getAppname(), instanceConfig.getIpAddress(),
+        return String.format("%s:%s:%d", instanceConfig.getAppname(), instanceConfig.getIpAddress(),
             instanceConfig.getNonSecurePort());
     }
 
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/FileRegistryServiceImpl.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/FileRegistryServiceImpl.java
index 11176f35f..f4160141a 100644
--- a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/FileRegistryServiceImpl.java
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/FileRegistryServiceImpl.java
@@ -20,12 +20,11 @@ import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.alibaba.fescar.common.util.StringUtils;
 import com.alibaba.fescar.config.ConfigChangeListener;
 import com.alibaba.fescar.config.Configuration;
 import com.alibaba.fescar.config.ConfigurationFactory;
 
-import org.apache.commons.lang.StringUtils;
-
 /**
  * The type File registry service.
  *
@@ -85,7 +84,7 @@ public class FileRegistryServiceImpl implements RegistryService<ConfigChangeList
         }
         String endpointStr = CONFIG.getConfig(
             PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + clusterName + POSTFIX_GROUPLIST);
-        if (StringUtils.isEmpty(endpointStr)) {
+        if (StringUtils.isNullOrEmpty(endpointStr)) {
             throw new IllegalArgumentException(clusterName + POSTFIX_GROUPLIST + " is required");
         }
         String[] endpoints = endpointStr.split(ENDPOINT_SPLIT_CHAR);
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RedisRegistryServiceImpl.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RedisRegistryServiceImpl.java
index 31b758d8d..f76af35ad 100644
--- a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RedisRegistryServiceImpl.java
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RedisRegistryServiceImpl.java
@@ -115,7 +115,7 @@ public class RedisRegistryServiceImpl implements RegistryService<RedisListener>
         if (minEvictableIdleTimeMillis > 0) {
             redisConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
         }
-        if (StringUtils.isEmpty(password)) {
+        if (StringUtils.isNullOrEmpty(password)) {
             jedisPool = new JedisPool(redisConfig, host, port, Protocol.DEFAULT_TIMEOUT, null, db);
         } else {
             jedisPool = new JedisPool(redisConfig, host, port, Protocol.DEFAULT_TIMEOUT, password, db);
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryFactory.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryFactory.java
index 4fc60b02a..03bd988dd 100644
--- a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryFactory.java
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryFactory.java
@@ -61,6 +61,9 @@ public class RegistryFactory {
             case File:
                 registryService = FileRegistryServiceImpl.getInstance();
                 break;
+            case ZK:
+                registryService = ZKRegisterServiceImpl.getInstance();
+                break;
             default:
                 throw new NotSupportYetException("not support register type:" + registryType);
 
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryType.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryType.java
index e32bd3214..0c95d608a 100644
--- a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryType.java
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/RegistryType.java
@@ -29,6 +29,10 @@ public enum RegistryType {
      * File registry type.
      */
     File,
+    /**
+     * ZK registry type.
+     */
+    ZK,
     /**
      * Redis registry type.
      */
@@ -57,7 +61,9 @@ public enum RegistryType {
             return Redis;
         } else if (Eureka.name().equalsIgnoreCase(name)) {
             return Eureka;
-        }else {
+        } else if (ZK.name().equalsIgnoreCase(name)) {
+            return ZK;
+        } else {
             throw new NotSupportYetException("unsupport type:" + name);
         }
     }
diff --git a/discovery/src/main/java/com/alibaba/fescar/discovery/registry/ZKRegisterServiceImpl.java b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/ZKRegisterServiceImpl.java
new file mode 100644
index 000000000..2c47c5559
--- /dev/null
+++ b/discovery/src/main/java/com/alibaba/fescar/discovery/registry/ZKRegisterServiceImpl.java
@@ -0,0 +1,217 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.discovery.registry;
+
+import com.alibaba.fescar.common.util.NetUtil;
+import com.alibaba.fescar.config.Configuration;
+import com.alibaba.fescar.config.ConfigurationFactory;
+import com.alibaba.nacos.client.naming.utils.CollectionUtils;
+
+import org.I0Itec.zkclient.IZkChildListener;
+import org.I0Itec.zkclient.ZkClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.alibaba.fescar.common.Constants.IP_PORT_SPLIT_CHAR;
+
+/**
+ * zookeeper path as /registry/zk/
+ *
+ * @author crazier.huang
+ * @date 2019/2/15
+ */
+public class ZKRegisterServiceImpl implements RegistryService<IZkChildListener> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZKRegisterServiceImpl.class);
+
+    private static volatile ZKRegisterServiceImpl instance;
+    private static volatile ZkClient zkClient;
+    private static final Configuration FILE_CONFIG = ConfigurationFactory.FILE_INSTANCE;
+    private static final String ZK_PATH_SPLIT_CHAR = "/";
+    private static final String FILE_ROOT_REGISTRY = "registry";
+    private static final String FILE_CONFIG_SPLIT_CHAR = ".";
+    private static final String REGISTRY_CLUSTER = "cluster";
+    private static final String REGISTRY_TYPE = "zk";
+    private static final String SERVER_ADDR_KEY = "serverAddr";
+    private static final String SESSION_TIME_OUT_KEY = "session.timeout";
+    private static final String CONNECT_TIME_OUT_KEY = "connect.timeout";
+    private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE
+        + FILE_CONFIG_SPLIT_CHAR;
+    private static final String ROOT_PATH = ZK_PATH_SPLIT_CHAR + FILE_ROOT_REGISTRY + ZK_PATH_SPLIT_CHAR + REGISTRY_TYPE
+        + ZK_PATH_SPLIT_CHAR;
+    private static final String ROOT_PATH_WITHOUT_SUFFIX = ZK_PATH_SPLIT_CHAR + FILE_ROOT_REGISTRY + ZK_PATH_SPLIT_CHAR
+        + REGISTRY_TYPE;
+    private static final ConcurrentMap<String, List<InetSocketAddress>> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>();
+    private static final ConcurrentMap<String, List<IZkChildListener>> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>();
+
+    private ZKRegisterServiceImpl() {}
+
+    public static ZKRegisterServiceImpl getInstance() {
+        if (null == instance) {
+            synchronized (ZKRegisterServiceImpl.class) {
+                if (null == instance) {
+                    instance = new ZKRegisterServiceImpl();
+                }
+            }
+        }
+        return instance;
+    }
+
+    @Override
+    public void register(InetSocketAddress address) throws Exception {
+        NetUtil.validAddress(address);
+
+        String path = getRegisterPathByPath(address);
+        getClientInstance().createPersistent(path, true);
+    }
+
+    @Override
+    public void unregister(InetSocketAddress address) throws Exception {
+        NetUtil.validAddress(address);
+
+        String path = getRegisterPathByPath(address);
+        getClientInstance().delete(path);
+    }
+
+    @Override
+    public void subscribe(String cluster, IZkChildListener listener) throws Exception {
+        if (null == cluster) {
+            return;
+        }
+
+        String path = ROOT_PATH + cluster;
+        if (getClientInstance().exists(path)) {
+            getClientInstance().subscribeChildChanges(path, listener);
+            LISTENER_SERVICE_MAP.putIfAbsent(cluster, new ArrayList<>());
+            LISTENER_SERVICE_MAP.get(cluster).add(listener);
+        }
+    }
+
+    @Override
+    public void unsubscribe(String cluster, IZkChildListener listener) throws Exception {
+        if (null == cluster) {
+            return;
+        }
+        String path = ROOT_PATH + cluster;
+        if (getClientInstance().exists(path)) {
+            getClientInstance().unsubscribeChildChanges(path, listener);
+
+            List<IZkChildListener> subscribeList = LISTENER_SERVICE_MAP.get(cluster);
+            if (null != subscribeList) {
+                List<IZkChildListener> newSubscribeList = new ArrayList<>();
+                for (IZkChildListener eventListener : subscribeList) {
+                    if (!eventListener.equals(listener)) {
+                        newSubscribeList.add(eventListener);
+                    }
+                }
+                LISTENER_SERVICE_MAP.put(cluster, newSubscribeList);
+            }
+        }
+
+    }
+
+    /**
+     * @param key the key
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public List<InetSocketAddress> lookup(String key) throws Exception {
+        String clusterName = getServiceGroup(key);
+
+        if (null == clusterName) {
+            return null;
+        }
+
+        Boolean exist = getClientInstance().exists(ROOT_PATH + clusterName);
+        if (!exist) {
+            return null;
+        }
+
+        if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) {
+            List<String> childClusterPath = getClientInstance().getChildren(ROOT_PATH + clusterName);
+            refreshClusterAddressMap(clusterName, childClusterPath);
+            subscribeCluster(clusterName);
+        }
+
+        return CLUSTER_ADDRESS_MAP.get(clusterName);
+    }
+
+    private ZkClient getClientInstance() {
+        if (zkClient == null) {
+            zkClient = new ZkClient(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY),
+                FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIME_OUT_KEY),
+                FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIME_OUT_KEY));
+            if (!zkClient.exists(ROOT_PATH_WITHOUT_SUFFIX)) {
+                zkClient.createPersistent(ROOT_PATH_WITHOUT_SUFFIX, true);
+            }
+        }
+        return zkClient;
+    }
+
+    private void subscribeCluster(String clusterName) throws Exception {
+        subscribe(clusterName, new IZkChildListener() {
+            @Override
+            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
+                String clusterName = parentPath.replace(ROOT_PATH, "");
+                if (CollectionUtils.isEmpty(currentChilds) && CLUSTER_ADDRESS_MAP.get(clusterName) != null) {
+                    CLUSTER_ADDRESS_MAP.remove(clusterName);
+                } else if (!CollectionUtils.isEmpty(currentChilds)) {
+                    refreshClusterAddressMap(clusterName, currentChilds);
+                }
+            }
+        });
+    }
+
+    private void refreshClusterAddressMap(String clusterName, List<String> instances) {
+        List<InetSocketAddress> newAddressList = new ArrayList<>();
+        if (instances == null) {
+            CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);
+            return;
+        }
+        for (String path : instances) {
+            try {
+                String[] ipAndPort = path.split(IP_PORT_SPLIT_CHAR);
+                newAddressList.add(new InetSocketAddress(ipAndPort[0], Integer.parseInt(ipAndPort[1])));
+            } catch (Exception e) {
+                LOGGER.warn("The cluster instance info is error, instance info:{}", path);
+            }
+        }
+        CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);
+    }
+
+    private String getClusterName() {
+        String clusterConfigName = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE + FILE_CONFIG_SPLIT_CHAR
+            + REGISTRY_CLUSTER;
+        return FILE_CONFIG.getConfig(clusterConfigName);
+    }
+
+    private String getServiceGroup(String key) {
+        Configuration configuration = ConfigurationFactory.getInstance();
+        String clusterNameKey = PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + PREFIX_SERVICE_MAPPING + key;
+        return configuration.getConfig(clusterNameKey);
+    }
+
+    private String getRegisterPathByPath(InetSocketAddress address) {
+        return ROOT_PATH + getClusterName() + ZK_PATH_SPLIT_CHAR + NetUtil.toStringAddress(address);
+    }
+}
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 727fd3d55..059d5bbc1 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-distribution</artifactId>
diff --git a/dubbo-alibaba/pom.xml b/dubbo-alibaba/pom.xml
index 62f2224a6..33ef4def1 100644
--- a/dubbo-alibaba/pom.xml
+++ b/dubbo-alibaba/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dubbo/pom.xml b/dubbo/pom.xml
index d925af2a5..26249388a 100644
--- a/dubbo/pom.xml
+++ b/dubbo/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-dubbo</artifactId>
diff --git a/pom.xml b/pom.xml
index 5bcc191ae..41ebed7f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 
     <groupId>com.alibaba.fescar</groupId>
     <artifactId>fescar-all</artifactId>
-    <version>0.3.0</version>
+    <version>0.3.1</version>
     <modules>
         <module>config</module>
         <module>common</module>
@@ -81,7 +81,7 @@
 
         <!-- Maven properties -->
         <maven.test.skip>false</maven.test.skip>
-        <!--<maven.javadoc.skip>true</maven.javadoc.skip>-->
+        <maven.javadoc.skip>true</maven.javadoc.skip>
         <!-- Compiler settings properties -->
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
@@ -121,7 +121,7 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>2.6.3</version>
+            <version>2.23.4</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -322,7 +322,6 @@
                         <version>3.0.0</version>
                         <configuration>
                             <charset>UTF-8</charset>
-                            <failOnError>false</failOnError>
                         </configuration>
                         <executions>
                             <execution>
diff --git a/rm-datasource/pom.xml b/rm-datasource/pom.xml
index 3a85e9f51..095ba27d5 100644
--- a/rm-datasource/pom.xml
+++ b/rm-datasource/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-rm-datasource</artifactId>
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AbstractPreparedStatementProxy.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AbstractPreparedStatementProxy.java
index 26a7bbc4e..a4c509c0f 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AbstractPreparedStatementProxy.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AbstractPreparedStatementProxy.java
@@ -209,6 +209,7 @@ public abstract class AbstractPreparedStatementProxy extends StatementProxy<Prep
 
     }
 
+    @Deprecated
     @Override
     public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
         setParamByIndex(parameterIndex, x);
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionContext.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionContext.java
index 246d97b6b..64465096b 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionContext.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionContext.java
@@ -45,7 +45,7 @@ public class ConnectionContext {
     /**
      * set whether requires global lock in this connection
      *
-     * @param isGlobalLockRequires
+     * @param isGlobalLockRequire
      */
     void setGlobalLockRequire(boolean isGlobalLockRequire) {
         this.isGlobalLockRequire = isGlobalLockRequire;
@@ -181,7 +181,7 @@ public class ConnectionContext {
         if (lockKeysBuffer.isEmpty()) {
             return null;
         }
-        StringBuffer appender = new StringBuffer();
+        StringBuilder appender = new StringBuilder();
         Iterator<String> iterable = lockKeysBuffer.iterator();
         while (iterable.hasNext()) {
             appender.append(iterable.next());
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutor.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutor.java
index ea23a15bf..515542871 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutor.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutor.java
@@ -201,15 +201,13 @@ public abstract class BaseTransactionalExecutor<T, S extends Statement> implemen
         StringBuilder sb = new StringBuilder();
         sb.append(rowsIncludingPK.getTableMeta().getTableName());
         sb.append(":");
-
-        boolean flag = false;
+        int filedSequence = 0;
         for (Field field : rowsIncludingPK.pkRows()) {
-            if (flag) {
+            sb.append(field.getValue());
+            filedSequence++;
+            if (filedSequence < rowsIncludingPK.pkRows().size()) {
                 sb.append(",");
-            } else {
-                flag = true;
             }
-            sb.append(field.getValue());
         }
         return sb.toString();
     }
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/SelectForUpdateExecutor.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/SelectForUpdateExecutor.java
index d6e16be73..3a6784a9a 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/SelectForUpdateExecutor.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/exec/SelectForUpdateExecutor.java
@@ -70,7 +70,7 @@ public class SelectForUpdateExecutor<S extends Statement> extends BaseTransactio
         } else {
             whereCondition = recognizer.getWhereCondition();
         }
-        if (!StringUtils.isEmpty(whereCondition)) {
+        if (!StringUtils.isNullOrEmpty(whereCondition)) {
             selectSQLAppender.append(" WHERE " + whereCondition);
         }
         selectSQLAppender.append(" FOR UPDATE");
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/sql/struct/TableMetaCache.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/sql/struct/TableMetaCache.java
index c33847f3d..fc73ba4ab 100755
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/sql/struct/TableMetaCache.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/sql/struct/TableMetaCache.java
@@ -144,7 +144,7 @@ public class TableMetaCache {
             col.setTableName(tableName);
             col.setColumnName(rs2.getString("COLUMN_NAME"));
             String datatype = rs2.getString("DATA_TYPE");
-            if (StringUtils.equalsIgnoreCase(datatype, "NUMBER")) {
+            if (com.alibaba.druid.util.StringUtils.equalsIgnoreCase(datatype, "NUMBER")) {
                 col.setDataType(java.sql.Types.BIGINT);
             } else if (StringUtils.equalsIgnoreCase(datatype, "VARCHAR2")) {
                 col.setDataType(java.sql.Types.VARCHAR);
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java
index 777b67d18..2590c277a 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java
@@ -16,12 +16,6 @@
 
 package com.alibaba.fescar.rm.datasource.undo;
 
-import java.sql.Blob;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
 import com.alibaba.druid.util.JdbcConstants;
 import com.alibaba.fescar.common.exception.NotSupportYetException;
 import com.alibaba.fescar.common.util.BlobUtils;
@@ -33,6 +27,12 @@ import com.alibaba.fescar.rm.datasource.DataSourceProxy;
 import com.alibaba.fescar.rm.datasource.sql.struct.TableMeta;
 import com.alibaba.fescar.rm.datasource.sql.struct.TableMetaCache;
 
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLIntegrityConstraintViolationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,6 +42,27 @@ import static com.alibaba.fescar.core.exception.TransactionExceptionCode.BranchR
  * The type Undo log manager.
  */
 public final class UndoLogManager {
+    private enum State {
+        /**
+         * This state can be properly rolled back by fescar
+         */
+        Normal(0),
+        /**
+         * This state prevents the branch transaction from inserting undo_log after the global transaction is rolled
+         * back.
+         */
+        GlobalFinished(1);
+
+        private int value;
+
+        State(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
 
     private static final Logger LOGGER = LoggerFactory.getLogger(UndoLogManager.class);
 
@@ -49,12 +70,12 @@ public final class UndoLogManager {
 
     private static String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + "\n" +
         "\t(branch_id, xid, rollback_info, log_status, log_created, log_modified)\n" +
-        "VALUES (?, ?, ?, 0, now(), now())";
+        "VALUES (?, ?, ?, ?, now(), now())";
     private static String DELETE_UNDO_LOG_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + "\n" +
         "\tWHERE branch_id = ? AND xid = ?";
 
     private static String SELECT_UNDO_LOG_SQL = "SELECT * FROM " + UNDO_LOG_TABLE_NAME
-        + " WHERE log_status = 0 AND branch_id = ? AND xid = ? FOR UPDATE";
+        + " WHERE  branch_id = ? AND xid = ? FOR UPDATE";
 
     private UndoLogManager() {
 
@@ -84,25 +105,7 @@ public final class UndoLogManager {
             LOGGER.debug("Flushing UNDO LOG: " + undoLogContent);
         }
 
-        PreparedStatement pst = null;
-        try {
-            pst = cp.getTargetConnection().prepareStatement(INSERT_UNDO_LOG_SQL);
-            pst.setLong(1, branchID);
-            pst.setString(2, xid);
-            pst.setBlob(3, BlobUtils.string2blob(undoLogContent));
-            pst.executeUpdate();
-        } catch (Exception e) {
-            if (e instanceof SQLException) {
-                throw (SQLException)e;
-            } else {
-                throw new SQLException(e);
-            }
-        } finally {
-            if (pst != null) {
-                pst.close();
-            }
-        }
-
+        insertUndoLogWithNormal(xid, branchID, undoLogContent, cp.getTargetConnection());
     }
 
     private static void assertDbSupport(String dbType) {
@@ -115,8 +118,8 @@ public final class UndoLogManager {
      * Undo.
      *
      * @param dataSourceProxy the data source proxy
-     * @param xid             the xid
-     * @param branchId        the branch id
+     * @param xid the xid
+     * @param branchId the branch id
      * @throws TransactionException the transaction exception
      */
     public static void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException {
@@ -125,70 +128,109 @@ public final class UndoLogManager {
         Connection conn = null;
         ResultSet rs = null;
         PreparedStatement selectPST = null;
-        try {
-            conn = dataSourceProxy.getPlainConnection();
-
-            // The entire undo process should run in a local transaction.
-            conn.setAutoCommit(false);
-
-            // Find UNDO LOG
-            selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL);
-            selectPST.setLong(1, branchId);
-            selectPST.setString(2, xid);
-            rs = selectPST.executeQuery();
-
-            while (rs.next()) {
-                Blob b = rs.getBlob("rollback_info");
-                String rollbackInfo = StringUtils.blob2string(b);
-                BranchUndoLog branchUndoLog = UndoLogParserFactory.getInstance().decode(rollbackInfo);
-
-                for (SQLUndoLog sqlUndoLog : branchUndoLog.getSqlUndoLogs()) {
-                    TableMeta tableMeta = TableMetaCache.getTableMeta(dataSourceProxy, sqlUndoLog.getTableName());
-                    sqlUndoLog.setTableMeta(tableMeta);
-                    AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(),
-                        sqlUndoLog);
-                    undoExecutor.executeOn(conn);
-                }
-
-            }
-            deleteUndoLog(xid, branchId, conn);
 
-            conn.commit();
-
-        } catch (Throwable e) {
-            if (conn != null) {
-                try {
-                    conn.rollback();
-                } catch (SQLException rollbackEx) {
-                    LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
-                }
-            }
-            throw new TransactionException(BranchRollbackFailed_Retriable, String.format("%s/%s", branchId, xid), e);
-
-        } finally {
+        for (; ; ) {
             try {
-                if (rs != null) {
-                    rs.close();
+                conn = dataSourceProxy.getPlainConnection();
+
+                // The entire undo process should run in a local transaction.
+                conn.setAutoCommit(false);
+
+                // Find UNDO LOG
+                selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL);
+                selectPST.setLong(1, branchId);
+                selectPST.setString(2, xid);
+                rs = selectPST.executeQuery();
+
+                boolean exists = false;
+                while (rs.next()) {
+                    exists = true;
+
+                    // It is possible that the server repeatedly sends a rollback request to roll back
+                    // the same branch transaction to multiple processes,
+                    // ensuring that only the undo_log in the normal state is processed.
+                    int state = rs.getInt("log_status");
+                    if (!canUndo(state)) {
+                        LOGGER.info("xid {} branch {}, ignore {} undo_log",
+                            xid, branchId, state);
+                        return;
+                    }
+
+                    Blob b = rs.getBlob("rollback_info");
+                    String rollbackInfo = StringUtils.blob2string(b);
+                    BranchUndoLog branchUndoLog = UndoLogParserFactory.getInstance().decode(rollbackInfo);
+
+                    for (SQLUndoLog sqlUndoLog : branchUndoLog.getSqlUndoLogs()) {
+                        TableMeta tableMeta = TableMetaCache.getTableMeta(dataSourceProxy, sqlUndoLog.getTableName());
+                        sqlUndoLog.setTableMeta(tableMeta);
+                        AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(
+                            dataSourceProxy.getDbType(),
+                            sqlUndoLog);
+                        undoExecutor.executeOn(conn);
+                    }
                 }
-                if (selectPST != null) {
-                    selectPST.close();
+
+                // If undo_log exists, it means that the branch transaction has completed the first phase,
+                // we can directly roll back and clean the undo_log
+                // Otherwise, it indicates that there is an exception in the branch transaction,
+                // causing undo_log not to be written to the database.
+                // For example, the business processing timeout, the global transaction is the initiator rolls back.
+                // To ensure data consistency, we can insert an undo_log with GlobalFinished state
+                // to prevent the local transaction of the first phase of other programs from being correctly submitted.
+                // See https://github.com/alibaba/fescar/issues/489
+
+                if (exists) {
+                    deleteUndoLog(xid, branchId, conn);
+                    conn.commit();
+                    LOGGER.info("xid {} branch {}, undo_log deleted with {}",
+                        xid, branchId, State.GlobalFinished.name());
+                } else {
+                    insertUndoLogWithGlobalFinished(xid, branchId, conn);
+                    conn.commit();
+                    LOGGER.info("xid {} branch {}, undo_log added with {}",
+                        xid, branchId, State.GlobalFinished.name());
                 }
+
+                return;
+            } catch (SQLIntegrityConstraintViolationException e) {
+                // Possible undo_log has been inserted into the database by other processes, retrying rollback undo_log
+                LOGGER.info("xid {} branch {}, undo_log inserted, retry rollback",
+                    xid, branchId);
+            } catch (Throwable e) {
                 if (conn != null) {
-                    conn.close();
+                    try {
+                        conn.rollback();
+                    } catch (SQLException rollbackEx) {
+                        LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
+                    }
+                }
+                throw new TransactionException(BranchRollbackFailed_Retriable, String.format("%s/%s", branchId, xid),
+                    e);
+
+            } finally {
+                try {
+                    if (rs != null) {
+                        rs.close();
+                    }
+                    if (selectPST != null) {
+                        selectPST.close();
+                    }
+                    if (conn != null) {
+                        conn.close();
+                    }
+                } catch (SQLException closeEx) {
+                    LOGGER.warn("Failed to close JDBC resource while undo ... ", closeEx);
                 }
-            } catch (SQLException closeEx) {
-                LOGGER.warn("Failed to close JDBC resource while undo ... ", closeEx);
             }
         }
-
     }
 
     /**
      * Delete undo log.
      *
-     * @param xid      the xid
+     * @param xid the xid
      * @param branchId the branch id
-     * @param conn     the conn
+     * @param conn the conn
      * @throws SQLException the sql exception
      */
     public static void deleteUndoLog(String xid, long branchId, Connection conn) throws SQLException {
@@ -197,4 +239,41 @@ public final class UndoLogManager {
         deletePST.setString(2, xid);
         deletePST.executeUpdate();
     }
+
+    private static void insertUndoLogWithNormal(String xid, long branchID,
+                                                String undoLogContent, Connection conn) throws SQLException {
+        insertUndoLog(xid, branchID, undoLogContent, State.Normal, conn);
+    }
+
+    private static void insertUndoLogWithGlobalFinished(String xid, long branchID,
+                                                        Connection conn) throws SQLException {
+        insertUndoLog(xid, branchID, "{}", State.GlobalFinished, conn);
+    }
+
+    private static void insertUndoLog(String xid, long branchID,
+                                      String undoLogContent, State state, Connection conn) throws SQLException {
+        PreparedStatement pst = null;
+        try {
+            pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL);
+            pst.setLong(1, branchID);
+            pst.setString(2, xid);
+            pst.setBlob(3, BlobUtils.string2blob(undoLogContent));
+            pst.setInt(4, state.getValue());
+            pst.executeUpdate();
+        } catch (Exception e) {
+            if (e instanceof SQLException) {
+                throw (SQLException)e;
+            } else {
+                throw new SQLException(e);
+            }
+        } finally {
+            if (pst != null) {
+                pst.close();
+            }
+        }
+    }
+
+    private static boolean canUndo(int state) {
+        return state == State.Normal.getValue();
+    }
 }
diff --git a/server/pom.xml b/server/pom.xml
index f259c057c..eaa734528 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-server</artifactId>
diff --git a/server/src/main/java/com/alibaba/fescar/server/lock/DefaultLockManagerImpl.java b/server/src/main/java/com/alibaba/fescar/server/lock/DefaultLockManagerImpl.java
index 9e2cac25f..833bdaa58 100644
--- a/server/src/main/java/com/alibaba/fescar/server/lock/DefaultLockManagerImpl.java
+++ b/server/src/main/java/com/alibaba/fescar/server/lock/DefaultLockManagerImpl.java
@@ -57,7 +57,7 @@ public class DefaultLockManagerImpl implements LockManager {
         ConcurrentHashMap<Map<String, Long>, Set<String>> bucketHolder = branchSession.getLockHolder();
 
         String lockKey = branchSession.getLockKey();
-        if (StringUtils.isEmpty(lockKey)) {
+        if (StringUtils.isNullOrEmpty(lockKey)) {
             return true;
         }
 
diff --git a/server/src/main/java/com/alibaba/fescar/server/session/FileBasedSessionManager.java b/server/src/main/java/com/alibaba/fescar/server/session/FileBasedSessionManager.java
index 7ebd56987..76eea6b46 100644
--- a/server/src/main/java/com/alibaba/fescar/server/session/FileBasedSessionManager.java
+++ b/server/src/main/java/com/alibaba/fescar/server/session/FileBasedSessionManager.java
@@ -51,6 +51,7 @@ public class FileBasedSessionManager extends AbstractSessionManager implements R
         transactionStoreManager = new FileTransactionStoreManager(sessionStoreFilePath + name, this);
     }
 
+    @Override
     public void reload() {
         restoreSessions();
         washSessions();
diff --git a/server/src/main/resources/registry.conf b/server/src/main/resources/registry.conf
index b70d64e00..fa9a4da9c 100644
--- a/server/src/main/resources/registry.conf
+++ b/server/src/main/resources/registry.conf
@@ -1,5 +1,5 @@
 registry {
-  # file nacos eureka
+  # file 、nacos 、eureka、redis、zk
   type = "file"
 
   nacos {
@@ -9,16 +9,26 @@ registry {
   }
   eureka {
     serviceUrl = "http://localhost:1001/eureka"
-    application = "test"
+    application = "default"
     weight = "1"
   }
+  redis {
+    serverAddr = "localhost:6379"
+    db = "0"
+  }
+  zk {
+    cluster = "default"
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
 }
 
 config {
-  # file nacos
+  # file、nacos 、apollo、zk
   type = "file"
 
   nacos {
@@ -26,6 +36,15 @@ config {
     namespace = "public"
     cluster = "default"
   }
+  apollo {
+    app.id = "fescar-server"
+    apollo.meta = "http://192.168.1.204:8801"
+  }
+  zk {
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
diff --git a/spring/pom.xml b/spring/pom.xml
index da806af48..16f296dd9 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-spring</artifactId>
diff --git a/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java b/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java
index 071ed5d51..205a06ce2 100644
--- a/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java
+++ b/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java
@@ -22,13 +22,13 @@ import java.util.HashSet;
 import java.util.Set;
 
 import com.alibaba.fescar.common.exception.NotSupportYetException;
+import com.alibaba.fescar.common.util.StringUtils;
 import com.alibaba.fescar.config.ConfigurationFactory;
 import com.alibaba.fescar.rm.RMClientAT;
 import com.alibaba.fescar.tm.TMClient;
 import com.alibaba.fescar.tm.api.DefaultFailureHandlerImpl;
 import com.alibaba.fescar.tm.api.FailureHandler;
 
-import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.Advisor;
@@ -146,7 +146,7 @@ public class GlobalTransactionScanner extends AbstractAutoProxyCreator implement
         if (LOGGER.isInfoEnabled()) {
             LOGGER.info("Initializing Global Transaction Clients ... ");
         }
-        if (StringUtils.isEmpty(applicationId) || StringUtils.isEmpty(txServiceGroup)) {
+        if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
             throw new IllegalArgumentException(
                 "applicationId: " + applicationId + ", txServiceGroup: " + txServiceGroup);
         }
diff --git a/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java b/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java
index f5b664b68..d18053000 100644
--- a/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java
+++ b/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java
@@ -105,7 +105,7 @@ public class GlobalTransactionalInterceptor implements MethodInterceptor {
                 @Override
                 public String name() {
                     String name = globalTrxAnno.name();
-                    if (!StringUtils.isEmpty(name)) {
+                    if (!StringUtils.isNullOrEmpty(name)) {
                         return name;
                     }
                     return formatMethod(methodInvocation.getMethod());
diff --git a/tm/pom.xml b/tm/pom.xml
index e8c45c08c..2e4d9d4d6 100644
--- a/tm/pom.xml
+++ b/tm/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-tm</artifactId>
diff --git a/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java b/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java
index 1a83ae089..1340bd08e 100644
--- a/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java
+++ b/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java
@@ -17,69 +17,165 @@
 package com.alibaba.fescar.tm.api;
 
 import com.alibaba.fescar.core.exception.TransactionException;
+import com.alibaba.fescar.tm.api.transaction.TransactionHook;
+import com.alibaba.fescar.tm.api.transaction.TransactionHookManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
 
 /**
  * Template of executing business logic with a global transaction.
  */
 public class TransactionalTemplate {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(TransactionalTemplate.class);
+
+
     /**
      * Execute object.
      *
      * @param business the business
      * @return the object
-     * @throws ExecutionException the execution exception
+     * @throws TransactionalExecutor.ExecutionException the execution exception
      */
     public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
 
         // 1. get or create a transaction
         GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
 
-        // 2. begin transaction
         try {
-            tx.begin(business.timeout(), business.name());
 
-        } catch (TransactionException txe) {
-            throw new TransactionalExecutor.ExecutionException(tx, txe,
-                TransactionalExecutor.Code.BeginFailure);
+            // 2. begin transaction
+            try {
+                triggerBeforeBegin();
+                tx.begin(business.timeout(), business.name());
+                triggerAfterBegin();
+            } catch (TransactionException txe) {
+                throw new TransactionalExecutor.ExecutionException(tx, txe,
+                    TransactionalExecutor.Code.BeginFailure);
 
-        }
+            }
+            Object rs = null;
+            try {
 
-        Object rs = null;
-        try {
+                // Do Your Business
+                rs = business.execute();
 
-            // Do Your Business
-            rs = business.execute();
+            } catch (Throwable ex) {
 
-        } catch (Throwable ex) {
+                // 3. any business exception, rollback.
+                try {
+                    triggerBeforeRollback();
+                    tx.rollback();
+                    triggerAfterRollback();
+                    // 3.1 Successfully rolled back
+                    throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
 
-            // 3. any business exception, rollback.
-            try {
-                tx.rollback();
+                } catch (TransactionException txe) {
+                    // 3.2 Failed to rollback
+                    throw new TransactionalExecutor.ExecutionException(tx, txe,
+                        TransactionalExecutor.Code.RollbackFailure, ex);
 
-                // 3.1 Successfully rolled back
-                throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
+                }
 
+            }
+            // 4. everything is fine, commit.
+            try {
+                triggerBeforeCommit();
+                tx.commit();
+                triggerAfterCommit();
             } catch (TransactionException txe) {
-                // 3.2 Failed to rollback
+                // 4.1 Failed to commit
                 throw new TransactionalExecutor.ExecutionException(tx, txe,
-                    TransactionalExecutor.Code.RollbackFailure, ex);
+                    TransactionalExecutor.Code.CommitFailure);
+            }
+
+            return rs;
+        } finally {
+            //5. clear
+            triggerAfterCompletion();
+            cleanUp();
+        }
+    }
 
+    private void triggerBeforeBegin() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.beforeBegin();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute beforeBegin in hook " + e.getMessage());
             }
+        }
+    }
 
+    private void triggerAfterBegin() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.afterBegin();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterBegin in hook " + e.getMessage());
+            }
         }
+    }
 
-        // 4. everything is fine, commit.
-        try {
-            tx.commit();
+    private void triggerBeforeRollback() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.beforeRollback();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute beforeRollback in hook " + e.getMessage());
+            }
+        }
+    }
+
+    private void triggerAfterRollback() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.afterRollback();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterRollback in hook " + e.getMessage());
+            }
+        }
+    }
+
+    private void triggerBeforeCommit() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.beforeCommit();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute beforeCommit in hook " + e.getMessage());
+            }
+        }
+    }
 
-        } catch (TransactionException txe) {
-            // 4.1 Failed to commit
-            throw new TransactionalExecutor.ExecutionException(tx, txe,
-                TransactionalExecutor.Code.CommitFailure);
+    private void triggerAfterCommit() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.afterCommit();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterCommit in hook " + e.getMessage());
+            }
+        }
+    }
 
+    private void triggerAfterCompletion() {
+        for (TransactionHook hook : getCurrentHooks()) {
+            try {
+                hook.afterCompletion();
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterCompletion in hook " + e.getMessage());
+            }
         }
-        return rs;
+    }
+
+    private void cleanUp() {
+        TransactionHookManager.clear();
+    }
+
+    private List<TransactionHook> getCurrentHooks() {
+        return TransactionHookManager.getHooks();
     }
 
 }
diff --git a/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHook.java b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHook.java
new file mode 100644
index 000000000..5eae2b6b4
--- /dev/null
+++ b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHook.java
@@ -0,0 +1,58 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.tm.api.transaction;
+
+/**
+ * @author guoyao
+ * @date 2019/3/4
+ */
+public interface TransactionHook {
+
+    /**
+     * before tx begin
+     */
+    void beforeBegin();
+
+    /**
+     * after tx begin
+     */
+    void afterBegin();
+
+    /**
+     * before tx commit
+     */
+    void beforeCommit();
+
+    /**
+     * after tx commit
+     */
+    void afterCommit();
+
+    /**
+     * before tx rollback
+     */
+    void beforeRollback();
+
+    /**
+     * after tx rollback
+     */
+    void afterRollback();
+
+    /**
+     * after tx all Completed
+     */
+    void afterCompletion();
+}
diff --git a/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookAdapter.java b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookAdapter.java
new file mode 100644
index 000000000..76a178c2a
--- /dev/null
+++ b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookAdapter.java
@@ -0,0 +1,58 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.tm.api.transaction;
+
+/**
+ * @author guoyao
+ * @date 2019/3/5
+ */
+public class TransactionHookAdapter implements TransactionHook {
+
+    @Override
+    public void beforeBegin() {
+
+    }
+
+    @Override
+    public void afterBegin() {
+
+    }
+
+    @Override
+    public void beforeCommit() {
+
+    }
+
+    @Override
+    public void afterCommit() {
+
+    }
+
+    @Override
+    public void beforeRollback() {
+
+    }
+
+    @Override
+    public void afterRollback() {
+
+    }
+
+    @Override
+    public void afterCompletion() {
+
+    }
+}
diff --git a/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManager.java b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManager.java
new file mode 100644
index 000000000..4811eee87
--- /dev/null
+++ b/tm/src/main/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManager.java
@@ -0,0 +1,67 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.tm.api.transaction;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author guoyao
+ * @date 2019/3/4
+ */
+public final class TransactionHookManager {
+
+    private static final ThreadLocal<List<TransactionHook>> LOCAL_HOOKS = new ThreadLocal<>();
+
+    /**
+     * get the current hooks
+     *
+     * @return
+     * @throws IllegalStateException
+     */
+    public static List<TransactionHook> getHooks() throws IllegalStateException {
+        List<TransactionHook> hooks = LOCAL_HOOKS.get();
+
+        if (hooks == null || hooks.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList(hooks);
+    }
+
+    /**
+     * add new hook
+     *
+     * @param transactionHook
+     */
+    public static void registerHook(TransactionHook transactionHook) {
+        if (transactionHook == null) {
+            throw new NullPointerException("transactionHook must not be null");
+        }
+        List<TransactionHook> transactionHooks = LOCAL_HOOKS.get();
+        if (transactionHooks == null) {
+            LOCAL_HOOKS.set(new ArrayList<>());
+        }
+        LOCAL_HOOKS.get().add(transactionHook);
+    }
+
+    /**
+     * clear hooks
+     */
+    public static void clear() {
+        LOCAL_HOOKS.remove();
+    }
+}


Test patch:
diff --git a/common/src/test/java/com/alibaba/fescar/common/thread/RejectedPoliciesTest.java b/common/src/test/java/com/alibaba/fescar/common/thread/RejectedPoliciesTest.java
new file mode 100644
index 000000000..8f8c84f4c
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/thread/RejectedPoliciesTest.java
@@ -0,0 +1,97 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.common.thread;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Created by guoyao on 2019/2/26.
+ */
+public class RejectedPoliciesTest {
+
+    private final int DEFAULT_CORE_POOL_SIZE = 1;
+    private final int DEFAULT_KEEP_ALIVE_TIME = 10;
+    private final int MAX_QUEUE_SIZE = 1;
+
+    /**
+     * Test runs oldest task policy.
+     *
+     * @throws Exception the exception
+     */
+    @Test
+    public void testRunsOldestTaskPolicy() throws Exception {
+        AtomicInteger atomicInteger = new AtomicInteger();
+        ThreadPoolExecutor poolExecutor =
+            new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_CORE_POOL_SIZE, DEFAULT_KEEP_ALIVE_TIME,
+                TimeUnit.MILLISECONDS,
+                new LinkedBlockingQueue(MAX_QUEUE_SIZE),
+                new NamedThreadFactory("OldestRunsPolicy", DEFAULT_CORE_POOL_SIZE),
+                RejectedPolicies.runsOldestTaskPolicy());
+        CountDownLatch downLatch1 = new CountDownLatch(1);
+        CountDownLatch downLatch2 = new CountDownLatch(1);
+        CountDownLatch downLatch3 = new CountDownLatch(1);
+        //task1
+        poolExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    //wait the oldest task of queue count down
+                    downLatch1.await();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                atomicInteger.getAndAdd(1);
+            }
+        });
+        assertThat(atomicInteger.get()).isEqualTo(0);
+        //task2
+        poolExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                // run second
+                atomicInteger.getAndAdd(2);
+            }
+        });
+        //task3
+        poolExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                downLatch2.countDown();
+                //task3 run
+                atomicInteger.getAndAdd(3);
+                downLatch3.countDown();
+            }
+        });
+        //only the task2 run which is the oldest task of queue
+        assertThat(atomicInteger.get()).isEqualTo(2);
+        downLatch1.countDown();
+        downLatch2.await();
+        //wait task3 run +3
+        downLatch3.await();
+        //run task3
+        assertThat(atomicInteger.get()).isEqualTo(6);
+
+    }
+}
diff --git a/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java b/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
index 23feaa780..bac0fcfe3 100644
--- a/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
+++ b/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
@@ -16,7 +16,6 @@
 
 package com.alibaba.fescar.common.util;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.sql.SQLException;
@@ -40,11 +39,12 @@ public class StringUtilsTest {
      * Test is empty.
      */
     @Test
-    public void testIsEmpty() {
-        assertThat(StringUtils.isEmpty(null)).isTrue();
-        assertThat(StringUtils.isEmpty("abc")).isFalse();
-        assertThat(StringUtils.isEmpty("")).isTrue();
-        assertThat(StringUtils.isEmpty(" ")).isFalse();
+    public void testIsNullOrEmpty() {
+
+        assertThat(StringUtils.isNullOrEmpty(null)).isTrue();
+        assertThat(StringUtils.isNullOrEmpty("abc")).isFalse();
+        assertThat(StringUtils.isNullOrEmpty("")).isTrue();
+        assertThat(StringUtils.isNullOrEmpty(" ")).isFalse();
     }
 
     /**
@@ -80,16 +80,9 @@ public class StringUtilsTest {
      */
     @Test
     @Ignore
-    public void testInputStream2String() {
-        try {
-            InputStream inputStream = StringUtilsTest.class.getClassLoader().getResourceAsStream("test.txt");
-            assertThat(StringUtils.inputStream2String(inputStream)).isEqualTo("abc\n"
-                + ":\"klsdf\n"
-                + "2ks,x:\".,-3sd˚ø≤ø¬≥");
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+    public void testInputStream2String() throws IOException {
+        InputStream inputStream = StringUtilsTest.class.getClassLoader().getResourceAsStream("test.txt");
+        assertThat(StringUtils.inputStream2String(inputStream))
+            .isEqualTo("abc\n" + ":\"klsdf\n" + "2ks,x:\".,-3sd˚ø≤ø¬≥");
     }
 }
diff --git a/config/src/test/java/com/alibaba/fescar/config/ConfigTypeTest.java b/config/src/test/java/com/alibaba/fescar/config/ConfigTypeTest.java
index 57d428475..24e9c76b0 100644
--- a/config/src/test/java/com/alibaba/fescar/config/ConfigTypeTest.java
+++ b/config/src/test/java/com/alibaba/fescar/config/ConfigTypeTest.java
@@ -58,7 +58,7 @@ public class ConfigTypeTest {
     /**
      * Test get type with not support.
      */
-    @Test(expected = NotSupportYetException.class)
+    @Test(expected = IllegalArgumentException.class)
     public void testGetTypeWithNotSupport() {
         ConfigType.getType("none");
     }
diff --git a/config/src/test/java/com/alibaba/fescar/config/ConfigurationFactoryTest.java b/config/src/test/java/com/alibaba/fescar/config/ConfigurationFactoryTest.java
new file mode 100644
index 000000000..1294c25a6
--- /dev/null
+++ b/config/src/test/java/com/alibaba/fescar/config/ConfigurationFactoryTest.java
@@ -0,0 +1,55 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.config;
+
+import com.alibaba.fescar.common.exception.NotSupportYetException;
+import org.junit.Test;
+
+import static com.alibaba.fescar.config.ConfigurationFactory.FILE_INSTANCE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Wu
+ * @date 2019/3/9
+ */
+public class ConfigurationFactoryTest {
+    /**
+     * get config type from registry.conf
+     */
+    private final static ConfigType CONFIG_TYPE = ConfigType.getType(
+            FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
+                    + ConfigurationKeys.FILE_ROOT_TYPE));
+
+    /**
+     * test for getInstance method
+     */
+    @Test
+    public void testGetInstance() {
+        try {
+            Configuration configuration = ConfigurationFactory.getInstance();
+            if (ConfigType.Nacos.equals(CONFIG_TYPE)) {//Nacos case
+                assertThat(configuration).isInstanceOf(NacosConfiguration.class);
+            } else if (ConfigType.Apollo.equals(CONFIG_TYPE)) {//Apollo case
+                assertThat(configuration).isInstanceOf(ApolloConfiguration.class);
+            } else {//File case
+                assertThat(configuration).isInstanceOf(FileConfiguration.class);
+            }
+        } catch (NotSupportYetException e) {
+            //try to get NotSupportYetException
+            assertThat(e).hasMessage("not support register type:" + CONFIG_TYPE);
+        }
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fescar/core/model/BranchStatusTest.java b/core/src/test/java/com/alibaba/fescar/core/model/BranchStatusTest.java
new file mode 100644
index 000000000..705950017
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fescar/core/model/BranchStatusTest.java
@@ -0,0 +1,55 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.core.model;
+
+import com.alibaba.fescar.common.exception.ShouldNeverHappenException;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * A unit test for {@link BranchStatus}
+ * @author Lay
+ * @date 2019/3/6
+ */
+public class BranchStatusTest {
+
+    private static final int REGISTERED_CODE = 1;
+    private static final int NONE = 99;
+
+    @Test
+    public void testGetCode() {
+        int code = BranchStatus.Registered.getCode();
+        Assert.assertEquals(code, REGISTERED_CODE);
+    }
+
+    @Test
+    public void testGetWithByte() {
+        BranchStatus branchStatus = BranchStatus.get((byte)REGISTERED_CODE);
+        Assert.assertEquals(branchStatus, BranchStatus.Registered);
+    }
+
+    @Test
+    public void testGetWithInt() {
+        BranchStatus branchStatus = BranchStatus.get(REGISTERED_CODE);
+        Assert.assertEquals(branchStatus, BranchStatus.Registered);
+    }
+
+    @Test(expected = ShouldNeverHappenException.class)
+    public void testGetException() {
+        BranchStatus.get(NONE);
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fescar/core/model/BranchTypeTest.java b/core/src/test/java/com/alibaba/fescar/core/model/BranchTypeTest.java
new file mode 100644
index 000000000..e97a1faa9
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fescar/core/model/BranchTypeTest.java
@@ -0,0 +1,62 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.core.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * A unit test for {@link BranchType}
+ * @author Lay
+ * @date 2019/3/6
+ */
+public class BranchTypeTest {
+
+    private static final int AT_ORDINAL = 0;
+    private static final int NONE = 99;
+
+    @Test
+    public void testOrdinal() {
+        int ordinal = BranchType.AT.ordinal();
+        Assert.assertEquals(AT_ORDINAL, ordinal);
+    }
+
+    @Test
+    public void testGetWithOrdinal() {
+        BranchType type  = BranchType.get(BranchType.AT.ordinal());
+        Assert.assertEquals(type, BranchType.AT);
+    }
+
+    @Test
+    public void testGetWithByte() {
+        BranchType branchStatus = BranchType.get((byte)AT_ORDINAL);
+        Assert.assertEquals(branchStatus, BranchType.AT);
+    }
+
+    @Test
+    public void testGetWithInt() {
+        BranchType branchStatus = BranchType.get(AT_ORDINAL);
+        Assert.assertEquals(branchStatus, BranchType.AT);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetException() {
+        BranchType.get(NONE);
+    }
+
+
+}
diff --git a/core/src/test/java/com/alibaba/fescar/core/model/GlobalStatusTest.java b/core/src/test/java/com/alibaba/fescar/core/model/GlobalStatusTest.java
new file mode 100644
index 000000000..a0509494c
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fescar/core/model/GlobalStatusTest.java
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.core.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * A unit test for {@link GlobalStatus}
+ * @author Lay
+ * @date 2019/3/6
+ */
+public class GlobalStatusTest {
+    private static final int BEGIN_CODE = 1;
+    private static final int NONE = 99;
+
+    @Test
+    public void testGetCode() {
+        int code = GlobalStatus.Begin.getCode();
+        Assert.assertEquals(code, BEGIN_CODE);
+    }
+
+    @Test
+    public void testGetWithByte() {
+        GlobalStatus branchStatus = GlobalStatus.get((byte)BEGIN_CODE);
+        Assert.assertEquals(branchStatus, GlobalStatus.Begin);
+    }
+
+    @Test
+    public void testGetWithInt() {
+        GlobalStatus branchStatus = GlobalStatus.get(BEGIN_CODE);
+        Assert.assertEquals(branchStatus, GlobalStatus.Begin);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetException() {
+        GlobalStatus.get(NONE);
+    }
+
+}
diff --git a/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutorTest.java b/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutorTest.java
index 108663324..57de4cad9 100644
--- a/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutorTest.java
+++ b/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/exec/BaseTransactionalExecutorTest.java
@@ -17,15 +17,25 @@
 package com.alibaba.fescar.rm.datasource.exec;
 
 import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
 
 import com.alibaba.fescar.rm.GlobalLockTemplate;
 import com.alibaba.fescar.rm.datasource.ConnectionProxy;
 import com.alibaba.fescar.rm.datasource.StatementProxy;
+import com.alibaba.fescar.rm.datasource.sql.struct.Field;
+import com.alibaba.fescar.rm.datasource.sql.struct.TableMeta;
+import com.alibaba.fescar.rm.datasource.sql.struct.TableRecords;
 
 import org.junit.Assert;
 import org.junit.Test;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
 public class BaseTransactionalExecutorTest {
 
     @SuppressWarnings({"rawtypes", "unchecked"})
@@ -77,4 +87,35 @@ public class BaseTransactionalExecutorTest {
 
     }
 
+    @Test
+    public void testBuildLockKey() {
+        //build expect data
+        String tableName = "test_name";
+        String fieldOne = "field_one";
+        String fieldTwo = "field_two";
+        String split1 = ":";
+        String split2 = ",";
+        String buildLockKeyExpect = tableName + split1 + fieldOne + split2 + fieldTwo;
+        // mock field
+        Field field1 = mock(Field.class);
+        when(field1.getValue()).thenReturn(fieldOne);
+        Field field2 = mock(Field.class);
+        when(field2.getValue()).thenReturn(fieldTwo);
+        List<Field> fieldList = new ArrayList<>();
+        fieldList.add(field1);
+        fieldList.add(field2);
+        // mock tableMeta
+        TableMeta tableMeta = mock(TableMeta.class);
+        when(tableMeta.getTableName()).thenReturn(tableName);
+        // mock tableRecords
+        TableRecords tableRecords = mock(TableRecords.class);
+        when(tableRecords.getTableMeta()).thenReturn(tableMeta);
+        when(tableRecords.pkRows()).thenReturn(fieldList);
+        when(tableRecords.size()).thenReturn(fieldList.size());
+        // mock executor
+        BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class);
+        when(executor.buildLockKey(tableRecords)).thenCallRealMethod();
+        assertThat(executor.buildLockKey(tableRecords)).isEqualTo(buildLockKeyExpect);
+    }
+
 }
diff --git a/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessImpl.java b/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessImpl.java
index 00d9978e4..5f87ac928 100644
--- a/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessImpl.java
+++ b/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessImpl.java
@@ -20,12 +20,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class BusinessImpl implements Business {
-    Logger logger = LoggerFactory.getLogger(BusinessImpl.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(BusinessImpl.class);
 
     @Override
     @GlobalTransactional(timeoutMills = 300000, name = "busi-doBiz")
     public String doBiz(String msg) {
-        logger.info("Business doBiz");
+        LOGGER.info("Business doBiz");
         return "hello " + msg;
     }
 }
diff --git a/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessProxy.java b/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessProxy.java
index e4b65e10b..0dcff1140 100644
--- a/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessProxy.java
+++ b/spring/src/test/java/com/alibaba/fescar/spring/annotation/BusinessProxy.java
@@ -19,7 +19,13 @@ package com.alibaba.fescar.spring.annotation;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class BusinessProxy implements InvocationHandler {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(BusinessProxy.class);
+
     private Object proxy;
 
     public BusinessProxy(Object proxy) {
@@ -27,15 +33,20 @@ public class BusinessProxy implements InvocationHandler {
     }
 
     @Override
-    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-        System.out.println("before");
+    public Object invoke(Object proxy, Method method, Object[] args) {
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Before invoking proxy method.");
+        }
         Object result = null;
         try {
             result = method.invoke(this.proxy, args);
         } catch (Exception e) {
-
+            LOGGER.warn("Failed to invoke method {}.", method.getName());
+        }
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("After invoking proxy method.");
         }
-        System.out.println("after");
+
         return result;
     }
 }
diff --git a/spring/src/test/java/com/alibaba/fescar/spring/annotation/MethodDescTest.java b/spring/src/test/java/com/alibaba/fescar/spring/annotation/MethodDescTest.java
new file mode 100644
index 000000000..7ec9b3bb0
--- /dev/null
+++ b/spring/src/test/java/com/alibaba/fescar/spring/annotation/MethodDescTest.java
@@ -0,0 +1,92 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.alibaba.fescar.spring.annotation;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Wu
+ * @date 2019/3/8
+ */
+public class MethodDescTest {
+
+    private static final GlobalTransactionScanner GLOBAL_TRANSACTION_SCANNER = new GlobalTransactionScanner(
+        "global-trans-scanner-test");
+    private static Method method = null;
+    private static GlobalTransactional transactional = null;
+
+    public MethodDescTest() throws NoSuchMethodException {
+        method = MockBusiness.class.getDeclaredMethod("doBiz", String.class);
+        transactional = method.getAnnotation(GlobalTransactional.class);
+    }
+
+    @Test
+    public void testGetTransactionAnnotation()
+        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        MethodDesc methodDesc = getMethodDesc();
+        assertThat(methodDesc.getTransactionAnnotation()).isEqualTo(transactional);
+
+    }
+
+    @Test
+    public void testGetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        MethodDesc methodDesc = getMethodDesc();
+        assertThat(methodDesc.getMethod()).isEqualTo(method);
+    }
+
+    @Test
+    public void testSetTransactionAnnotation()
+        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        MethodDesc methodDesc = getMethodDesc();
+        assertThat(methodDesc.getTransactionAnnotation()).isNotNull();
+        methodDesc.setTransactionAnnotation(null);
+        assertThat(methodDesc.getTransactionAnnotation()).isNull();
+    }
+
+    @Test
+    public void testSetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        MethodDesc methodDesc = getMethodDesc();
+        assertThat(methodDesc.getMethod()).isNotNull();
+        methodDesc.setMethod(null);
+        assertThat(methodDesc.getMethod()).isNull();
+    }
+
+    private MethodDesc getMethodDesc() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        //call the private method
+        Method m = GlobalTransactionScanner.class.getDeclaredMethod("makeMethodDesc", GlobalTransactional.class,
+            Method.class);
+        m.setAccessible(true);
+        return (MethodDesc)m.invoke(GLOBAL_TRANSACTION_SCANNER, transactional, method);
+
+    }
+
+    /**
+     * the type mock business
+     */
+    private static class MockBusiness {
+        @GlobalTransactional(timeoutMills = 300000, name = "busi-doBiz")
+        public String doBiz(String msg) {
+            return "hello " + msg;
+        }
+    }
+
+}
diff --git a/test/pom.xml b/test/pom.xml
index 34223f0eb..952a3d052 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>fescar-all</artifactId>
         <groupId>com.alibaba.fescar</groupId>
-        <version>0.3.0</version>
+        <version>0.3.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fescar-test</artifactId>
diff --git a/test/src/main/java/com/alibaba/fescar/test/LocalTransactionWithGlobalLockDataSourceBasicTest.java b/test/src/main/java/com/alibaba/fescar/test/LocalTransactionWithGlobalLockDataSourceBasicTest.java
index bebcaf18c..ca7c7043b 100644
--- a/test/src/main/java/com/alibaba/fescar/test/LocalTransactionWithGlobalLockDataSourceBasicTest.java
+++ b/test/src/main/java/com/alibaba/fescar/test/LocalTransactionWithGlobalLockDataSourceBasicTest.java
@@ -44,7 +44,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
     private static JdbcTemplate jdbcTemplate;
     private static JdbcTemplate directJdbcTemplate;
 
-    private abstract static class LockConflictExecuteTemplate {
+    private abstract static class AbstractLockConflictExecuteTemplate {
         public void execute() {
             synchronized (LocalTransactionWithGlobalLockDataSourceBasicTest.class) {
                 DataSourceManager.set(new MockDataSourceManager() {
@@ -89,7 +89,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
     @Test
     public void testInsertWithLock() {
         RootContext.bindGlobalLockFlag();
-        new LockConflictExecuteTemplate() {
+        new AbstractLockConflictExecuteTemplate() {
             @Override
             public void doExecute() {
                 jdbcTemplate.update("insert into user0 (id, name, gmt) values (?, ?, ?)",
@@ -111,7 +111,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
     public void testUpdateWithLock() {
         RootContext.bindGlobalLockFlag();
         RootContext.bindGlobalLockFlag();
-        new LockConflictExecuteTemplate() {
+        new AbstractLockConflictExecuteTemplate() {
             @Override
             public void doExecute() {
                 jdbcTemplate.update("update user0 a set a.name = 'yyyy' where a.id = ?", new Object[] {1});
@@ -144,7 +144,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
             new Object[] {1, "xxx", new Date()});
 
         RootContext.bindGlobalLockFlag();
-        new LockConflictExecuteTemplate() {
+        new AbstractLockConflictExecuteTemplate() {
 
             @Override
             public void doExecute() {
@@ -169,7 +169,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
     @Test
     public void testDeleteForLockConflict() {
         RootContext.bindGlobalLockFlag();
-        new LockConflictExecuteTemplate() {
+        new AbstractLockConflictExecuteTemplate() {
 
             @Override
             public void doExecute() {
@@ -193,7 +193,7 @@ public class LocalTransactionWithGlobalLockDataSourceBasicTest {
     @Test
     public void testSelectForUpdateWithLockConflict() {
         RootContext.bindGlobalLockFlag();
-        new LockConflictExecuteTemplate() {
+        new AbstractLockConflictExecuteTemplate() {
 
             @Override
             public void doExecute() {
diff --git a/test/src/main/resources/registry.conf b/test/src/main/resources/registry.conf
index 7304c431d..fa9a4da9c 100644
--- a/test/src/main/resources/registry.conf
+++ b/test/src/main/resources/registry.conf
@@ -1,5 +1,5 @@
 registry {
-  # file nacos
+  # file 、nacos 、eureka、redis、zk
   type = "file"
 
   nacos {
@@ -7,13 +7,28 @@ registry {
     namespace = "public"
     cluster = "default"
   }
+  eureka {
+    serviceUrl = "http://localhost:1001/eureka"
+    application = "default"
+    weight = "1"
+  }
+  redis {
+    serverAddr = "localhost:6379"
+    db = "0"
+  }
+  zk {
+    cluster = "default"
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
 }
 
 config {
-  # file nacos
+  # file、nacos 、apollo、zk
   type = "file"
 
   nacos {
@@ -21,6 +36,15 @@ config {
     namespace = "public"
     cluster = "default"
   }
+  apollo {
+    app.id = "fescar-server"
+    apollo.meta = "http://192.168.1.204:8801"
+  }
+  zk {
+    serverAddr = "127.0.0.1:2181"
+    session.timeout = 6000
+    connect.timeout = 2000
+  }
   file {
     name = "file.conf"
   }
diff --git a/test/src/test/java/com/alibaba/fescar/core/rpc/netty/TmRpcClientTest.java b/test/src/test/java/com/alibaba/fescar/core/rpc/netty/TmRpcClientTest.java
index 8d3359b29..31341e765 100644
--- a/test/src/test/java/com/alibaba/fescar/core/rpc/netty/TmRpcClientTest.java
+++ b/test/src/test/java/com/alibaba/fescar/core/rpc/netty/TmRpcClientTest.java
@@ -41,7 +41,7 @@ public class TmRpcClientTest {
 
     private static final ThreadPoolExecutor
         workingThreads = new ThreadPoolExecutor(100, 500, 500, TimeUnit.SECONDS,
-        new LinkedBlockingQueue(20000), new ThreadPoolExecutor.CallerRunsPolicy());
+                                                new LinkedBlockingQueue(20000), new ThreadPoolExecutor.CallerRunsPolicy());
 
     /**
      * Client rely on server's starting first
@@ -74,7 +74,7 @@ public class TmRpcClientTest {
         Method doConnectMethod = TmRpcClient.class.getDeclaredMethod("doConnect", String.class);
         doConnectMethod.setAccessible(true);
         String serverAddress = "0.0.0.0:8091";
-        Channel channel = (Channel)doConnectMethod.invoke(tmRpcClient, serverAddress);
+        Channel channel = (Channel) doConnectMethod.invoke(tmRpcClient, serverAddress);
         System.out.print("channel = ");
         Assert.assertNotNull(channel);
     }
@@ -138,7 +138,7 @@ public class TmRpcClientTest {
         Method doConnectMethod = TmRpcClient.class.getDeclaredMethod("doConnect", String.class);
         doConnectMethod.setAccessible(true);
         String serverAddress = "0.0.0.0:8091";
-        Channel channel = (Channel)doConnectMethod.invoke(tmRpcClient, serverAddress);
+        Channel channel = (Channel) doConnectMethod.invoke(tmRpcClient, serverAddress);
         System.out.print("channel = " + channel);
         Assert.assertNotNull(channel);
 
@@ -146,12 +146,11 @@ public class TmRpcClientTest {
         request.setTransactionId(123456L);
         request.setLockKey("lock key testSendMsgWithResponse");
         request.setResourceId("resoutceId1");
-        BranchRegisterResponse branchRegisterResponse = (BranchRegisterResponse)tmRpcClient.sendMsgWithResponse(
-            request);
+        BranchRegisterResponse branchRegisterResponse = (BranchRegisterResponse)tmRpcClient.sendMsgWithResponse(request);
         Assert.assertNotNull(branchRegisterResponse);
         //we have not init SessionManager
         Assert.assertEquals(ResultCode.Failed, branchRegisterResponse.getResultCode());
         Assert.assertEquals("RuntimeException[SessionManager is NOT init!]",
-            branchRegisterResponse.getMsg());
+                            branchRegisterResponse.getMsg());
     }
 }
diff --git a/tm/src/test/java/com/alibaba/fescar/tm/api/TransactionTemplateTest.java b/tm/src/test/java/com/alibaba/fescar/tm/api/TransactionTemplateTest.java
new file mode 100644
index 000000000..6b9c1baa1
--- /dev/null
+++ b/tm/src/test/java/com/alibaba/fescar/tm/api/TransactionTemplateTest.java
@@ -0,0 +1,94 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.tm.api;
+
+import com.alibaba.fescar.core.model.GlobalStatus;
+import com.alibaba.fescar.core.model.TransactionManager;
+import com.alibaba.fescar.tm.DefaultTransactionManager;
+import com.alibaba.fescar.tm.api.transaction.TransactionHook;
+import com.alibaba.fescar.tm.api.transaction.TransactionHookManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author guoyao
+ * @date 2019/3/6
+ */
+public class TransactionTemplateTest {
+
+    private static final String DEFAULT_XID = "123456789";
+    private static final String DEFAULT_NAME = "test";
+    private static final int DEFAULT_TIME_OUT = 30000;
+
+    @Before
+    public void init() throws Exception {
+        TransactionManager transactionManager = mock(TransactionManager.class);
+        when(transactionManager.begin(null, null, DEFAULT_NAME, DEFAULT_TIME_OUT)).thenReturn(DEFAULT_XID);
+        when(transactionManager.commit(DEFAULT_XID)).thenReturn(GlobalStatus.Committed);
+        when(transactionManager.rollback(DEFAULT_XID)).thenReturn(GlobalStatus.Rollbacked);
+        when(transactionManager.getStatus(DEFAULT_XID)).thenReturn(GlobalStatus.Begin);
+        DefaultTransactionManager.set(transactionManager);
+    }
+
+    @After
+    public void assertHooks() {
+        assertThat(TransactionHookManager.getHooks()).isEmpty();
+    }
+
+    @Test
+    public void testTransactionCommitHook() throws Exception {
+        TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
+        TransactionalExecutor transactionalExecutor = Mockito.mock(TransactionalExecutor.class);
+        when(transactionalExecutor.name()).thenReturn(DEFAULT_NAME);
+        when(transactionalExecutor.timeout()).thenReturn(DEFAULT_TIME_OUT);
+        TransactionHookManager.registerHook(transactionHook);
+        TransactionalTemplate template = new TransactionalTemplate();
+        template.execute(transactionalExecutor);
+        verify(transactionHook).beforeBegin();
+        verify(transactionHook).afterBegin();
+        verify(transactionHook).beforeCommit();
+        verify(transactionHook).afterCommit();
+        verify(transactionHook).afterCompletion();
+    }
+
+    @Test
+    public void testTransactionRollbackHook() throws Throwable {
+        TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
+        TransactionalExecutor transactionalExecutor = Mockito.mock(TransactionalExecutor.class);
+        when(transactionalExecutor.name()).thenReturn(DEFAULT_NAME);
+        when(transactionalExecutor.timeout()).thenReturn(DEFAULT_TIME_OUT);
+        when(transactionalExecutor.execute()).thenThrow(new RuntimeException());
+        TransactionHookManager.registerHook(transactionHook);
+        TransactionalTemplate template = new TransactionalTemplate();
+        try {
+            template.execute(transactionalExecutor);
+        } catch (Exception e) {
+            //catch rollback exception
+        }
+        verify(transactionHook).beforeBegin();
+        verify(transactionHook).afterBegin();
+        verify(transactionHook).beforeRollback();
+        verify(transactionHook).afterRollback();
+        verify(transactionHook).afterCompletion();
+    }
+
+}
diff --git a/tm/src/test/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManagerTest.java b/tm/src/test/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManagerTest.java
new file mode 100644
index 000000000..4de541262
--- /dev/null
+++ b/tm/src/test/java/com/alibaba/fescar/tm/api/transaction/TransactionHookManagerTest.java
@@ -0,0 +1,61 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.alibaba.fescar.tm.api.transaction;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author guoyao
+ * @date 2019/3/5
+ */
+public class TransactionHookManagerTest {
+
+    @After
+    public void clear() {
+        TransactionHookManager.clear();
+    }
+
+    @Test
+    public void testRegisterHook() {
+        TransactionHookAdapter transactionHookAdapter = new TransactionHookAdapter();
+        TransactionHookManager.registerHook(transactionHookAdapter);
+        List<TransactionHook> hooks = TransactionHookManager.getHooks();
+        assertThat(hooks).isNotEmpty();
+        assertThat(hooks.get(0)).isEqualTo(transactionHookAdapter);
+    }
+
+    @Test
+    public void testGetHooks() {
+        assertThat(TransactionHookManager.getHooks()).isEmpty();
+        TransactionHookManager.registerHook(new TransactionHookAdapter());
+        assertThat(TransactionHookManager.getHooks()).isNotEmpty();
+    }
+
+    @Test
+    public void testClear() {
+        assertThat(TransactionHookManager.getHooks()).isEmpty();
+        TransactionHookManager.registerHook(new TransactionHookAdapter());
+        assertThat(TransactionHookManager.getHooks()).isNotEmpty();
+        TransactionHookManager.clear();
+        assertThat(TransactionHookManager.getHooks()).isEmpty();
+    }
+
+}