You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by zy...@apache.org on 2023/04/13 12:22:03 UTC

[iotdb] branch rel/1.1 updated: [To rel/1.1][IOTDB-5707] Support CreateTimeSeriesUsingTemplate in Session (#9606)

This is an automated email from the ASF dual-hosted git repository.

zyk pushed a commit to branch rel/1.1
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rel/1.1 by this push:
     new 9c5c1bfda9 [To rel/1.1][IOTDB-5707] Support CreateTimeSeriesUsingTemplate in Session (#9606)
9c5c1bfda9 is described below

commit 9c5c1bfda9a9eed9c90a64a26d09bbea06a4c772
Author: Marcos_Zyk <38...@users.noreply.github.com>
AuthorDate: Thu Apr 13 20:21:54 2023 +0800

    [To rel/1.1][IOTDB-5707] Support CreateTimeSeriesUsingTemplate in Session (#9606)
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |   6 +-
 docs/UserGuide/API/Programming-Java-Native-API.md  |  93 +++++-------
 docs/UserGuide/Operate-Metadata/Template.md        |   6 +-
 .../UserGuide/API/Programming-Java-Native-API.md   |  83 +++++------
 docs/zh/UserGuide/Operate-Metadata/Template.md     |   6 +-
 .../session/it/IoTDBSessionSchemaTemplateIT.java   |  61 ++++++++
 .../java/org/apache/iotdb/isession/ISession.java   |   3 +
 .../apache/iotdb/isession/pool/ISessionPool.java   |   3 +
 .../metadata/visitor/SchemaExecutionVisitor.java   |  20 +++
 .../execution/executor/RegionWriteExecutor.java    |  31 ++++
 .../apache/iotdb/db/mpp/plan/analyze/Analysis.java |  12 ++
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  |  33 ++++
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |   4 +-
 .../db/mpp/plan/parser/StatementGenerator.java     |  13 ++
 .../db/mpp/plan/planner/LogicalPlanVisitor.java    |  16 ++
 .../mpp/plan/planner/plan/node/PlanNodeType.java   |   6 +-
 .../db/mpp/plan/planner/plan/node/PlanVisitor.java |   5 +
 .../node/metedata/write/ActivateTemplateNode.java  |   2 +-
 .../metedata/write/BatchActivateTemplateNode.java  | 166 +++++++++++++++++++++
 .../iotdb/db/mpp/plan/statement/StatementType.java |   4 +-
 .../db/mpp/plan/statement/StatementVisitor.java    |   6 +
 ...nt.java => BatchActivateTemplateStatement.java} |  33 ++--
 .../template/DropSchemaTemplateStatement.java      |  13 +-
 .../service/thrift/impl/ClientRPCServiceImpl.java  |  48 ++++++
 .../java/org/apache/iotdb/session/Session.java     |  14 ++
 .../apache/iotdb/session/SessionConnection.java    |  21 +++
 .../org/apache/iotdb/session/pool/SessionPool.java |  20 +++
 thrift/src/main/thrift/client.thrift               |   7 +
 28 files changed, 600 insertions(+), 135 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index 5f04e5406d..01116c54a1 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -36,7 +36,7 @@ statement
     ;
 
 ddlStatement
-    : createStorageGroup | createTimeseries | createSchemaTemplate | createTimeseriesOfSchemaTemplate
+    : createStorageGroup | createTimeseries | createSchemaTemplate | createTimeseriesUsingSchemaTemplate
     | createFunction | createTrigger | createContinuousQuery
     | alterTimeseries | alterStorageGroup | deleteStorageGroup | deleteTimeseries | deletePartition | deleteTimeseriesOfSchemaTemplate
     | dropFunction | dropTrigger | dropContinuousQuery | dropSchemaTemplate
@@ -113,8 +113,8 @@ templateMeasurementClause
     ;
 
 // Create Timeseries Of Schema Template
-createTimeseriesOfSchemaTemplate
-    : CREATE TIMESERIES OF SCHEMA TEMPLATE ON prefixPath
+createTimeseriesUsingSchemaTemplate
+    : CREATE TIMESERIES (OF | USING) SCHEMA TEMPLATE ON prefixPath
     ;
 
 // Create Function
diff --git a/docs/UserGuide/API/Programming-Java-Native-API.md b/docs/UserGuide/API/Programming-Java-Native-API.md
index 817223155d..4fffd270b0 100644
--- a/docs/UserGuide/API/Programming-Java-Native-API.md
+++ b/docs/UserGuide/API/Programming-Java-Native-API.md
@@ -61,7 +61,7 @@ Here we show the commonly used interfaces and their parameters in the Native API
 
 * Initialize a Session
 
-```java
+``` java
 // use default configuration 
 session = new Session.Builder.build();
 
@@ -95,13 +95,13 @@ Version represents the SQL semantic version used by the client, which is used to
 
 * Open a Session
 
-```java
+``` java
 void open()
 ```
 
 * Open a session, with a parameter to specify whether to enable RPC compression
   
-```java
+``` java
 void open(boolean enableRPCCompression)
 ```
 
@@ -109,7 +109,7 @@ Notice: this RPC compression status of client must comply with that of IoTDB ser
 
 * Close a Session
 
-```java
+``` java
 void close()
 ```
 
@@ -119,13 +119,13 @@ void close()
 
 * CREATE DATABASE
 
-```java
+``` java
 void setStorageGroup(String storageGroupId)    
 ```
 
 * Delete one or several databases
 
-```java
+``` java
 void deleteStorageGroup(String storageGroup)
 void deleteStorageGroups(List<String> storageGroups)
 ```
@@ -134,7 +134,7 @@ void deleteStorageGroups(List<String> storageGroups)
 
 * Create one or multiple timeseries
 
-```java
+``` java
 void createTimeseries(String path, TSDataType dataType,
       TSEncoding encoding, CompressionType compressor, Map<String, String> props,
       Map<String, String> tags, Map<String, String> attributes, String measurementAlias)
@@ -156,14 +156,14 @@ Attention: Alias of measurements are **not supported** currently.
 
 * Delete one or several timeseries
 
-```java
+``` java
 void deleteTimeseries(String path)
 void deleteTimeseries(List<String> paths)
 ```
 
 * Check whether the specific timeseries exists.
 
-```java
+``` java
 boolean checkTimeseriesExists(String path)
 ```
 
@@ -172,7 +172,7 @@ boolean checkTimeseriesExists(String path)
 
 Create a schema template for massive identical devices will help to improve memory performance. You can use Template, InternalNode and MeasurementNode to depict the structure of the template, and use belowed interface to create it inside session.
 
-```java
+``` java
 public void createSchemaTemplate(Template template);
 
 Class Template {
@@ -207,7 +207,7 @@ We strongly suggest you implement templates only with flat-measurement (like obj
 
 A snippet of using above Method and Class:
 
-```java
+``` java
 MeasurementNode nodeX = new MeasurementNode("x", TSDataType.FLOAT, TSEncoding.RLE, CompressionType.SNAPPY);
 MeasurementNode nodeY = new MeasurementNode("y", TSDataType.FLOAT, TSEncoding.RLE, CompressionType.SNAPPY);
 MeasurementNode nodeSpeed = new MeasurementNode("speed", TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY);
@@ -221,25 +221,6 @@ template.addToTemplate(nodeSpeed);
 createSchemaTemplate(flatTemplate);
 ```
 
-You can query measurement inside templates with these APIS:
-
-```java
-// Return the amount of measurements inside a template
-public int countMeasurementsInTemplate(String templateName);
-
-// Return true if path points to a measurement, otherwise returne false
-public boolean isMeasurementInTemplate(String templateName, String path);
-
-// Return true if path exists in template, otherwise return false
-public boolean isPathExistInTemplate(String templateName, String path);
-
-// Return all measurements paths inside template
-public List<String> showMeasurementsInTemplate(String templateName);
-
-// Return all measurements paths under the designated patter inside template
-public List<String> showMeasurementsInTemplate(String templateName, String pattern);
-```
-
 To implement schema template, you can set the measurement template named 'templateName' at path 'prefixPath'.
 
 **Please notice that, we strongly recommend not setting templates on the nodes above the database to accommodate future updates and collaboration between modules.**
@@ -250,13 +231,19 @@ void setSchemaTemplate(String templateName, String prefixPath)
 
 Before setting template, you should firstly create the template using
 
-```java
+``` java
 void createSchemaTemplate(Template template)
 ```
 
+After setting template to a certain path, you can use the template to create timeseries on given device paths through the following interface, or you can write data directly to trigger timeseries auto creation using schema template under target devices. 
+
+``` java
+void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
+```
+
 After setting template to a certain path, you can query for info about template using belowed interface in session:
 
-```java
+``` java
 /** @return All template names. */
 public List<String> showAllTemplates();
 
@@ -269,7 +256,7 @@ public List<String> showPathsTemplateUsingOn(String templateName)
 
 If you are ready to get rid of schema template, you can drop it with belowed interface. Make sure the template to drop has been unset from MTree.
 
-```java
+``` java
 void unsetSchemaTemplate(String prefixPath, String templateName);
 public void dropSchemaTemplate(String templateName);
 ```
@@ -289,7 +276,7 @@ It is recommended to use insertTablet to help improve write efficiency.
   * **Better Write Performance**
   * **Support null values**: fill the null value with any value, and then mark the null value via BitMap
 
-```java
+``` java
 void insertTablet(Tablet tablet)
 
 public class Tablet {
@@ -314,7 +301,7 @@ public class Tablet {
 
 * Insert multiple Tablets
 
-```java
+``` java
 void insertTablets(Map<String, Tablet> tablet)
 ```
 
@@ -331,14 +318,14 @@ void insertTablets(Map<String, Tablet> tablet)
   | DOUBLE     | Double         |
   | TEXT       | String, Binary |
 
-```java
+``` java
 void insertRecord(String deviceId, long time, List<String> measurements,
    List<TSDataType> types, List<Object> values)
 ```
 
 * Insert multiple Records
 
-```java
+``` java
 void insertRecords(List<String> deviceIds, List<Long> times,
     List<List<String>> measurementsList, List<List<TSDataType>> typesList,
     List<List<Object>> valuesList)
@@ -346,7 +333,7 @@ void insertRecords(List<String> deviceIds, List<Long> times,
 * Insert multiple Records that belong to the same device. 
   With type info the server has no need to do type inference, which leads a better performance
 
-```java
+``` java
 void insertRecordsOfOneDevice(String deviceId, List<Long> times,
     List<List<String>> measurementsList, List<List<TSDataType>> typesList,
     List<List<Object>> valuesList)
@@ -358,20 +345,20 @@ When the data is of String type, we can use the following interface to perform t
 
 * Insert a Record, which contains multiple measurement value of a device at a timestamp
 
-```java
+``` java
 void insertRecord(String prefixPath, long time, List<String> measurements, List<String> values)
 ```
 
 * Insert multiple Records
 
-```java
+``` java
 void insertRecords(List<String> deviceIds, List<Long> times, 
    List<List<String>> measurementsList, List<List<String>> valuesList)
 ```
 
 * Insert multiple Records that belong to the same device.
 
-```java
+``` java
 void insertStringRecordsOfOneDevice(String deviceId, List<Long> times,
         List<List<String>> measurementsList, List<List<String>> valuesList)
 ```
@@ -391,7 +378,7 @@ The Insert of aligned timeseries uses interfaces like insertAlignedXXX, and othe
 
 * Delete data before or equal to a timestamp of one or several timeseries
 
-```java
+``` java
 void deleteData(String path, long time)
 void deleteData(List<String> paths, long time)
 ```
@@ -401,14 +388,14 @@ void deleteData(List<String> paths, long time)
 * Time-series raw data query with time range:
   - The specified query time range is a left-closed right-open interval, including the start time but excluding the end time.
 
-```java
+``` java
 SessionDataSet executeRawDataQuery(List<String> paths, long startTime, long endTime);
 ```
 
 * Last query: 
   - Query the last data, whose timestamp is greater than or equal LastTime.
 
-```java
+``` java
 SessionDataSet executeLastDataQuery(List<String> paths, long LastTime);
 ```
 
@@ -416,7 +403,7 @@ SessionDataSet executeLastDataQuery(List<String> paths, long LastTime);
   - Support specified query time range: The specified query time range is a left-closed right-open interval, including the start time but not the end time.
   - Support GROUP BY TIME.
 
-```java
+``` java
 SessionDataSet executeAggregationQuery(List<String> paths, List<Aggregation> aggregations);
 
 SessionDataSet executeAggregationQuery(
@@ -442,13 +429,13 @@ SessionDataSet executeAggregationQuery(
 
 * Execute query statement
 
-```java
+``` java
 SessionDataSet executeQueryStatement(String sql)
 ```
 
 * Execute non query statement
 
-```java
+``` java
 void executeNonQueryStatement(String sql)
 ```
 
@@ -458,7 +445,7 @@ These methods **don't** insert data into database and server just return after a
 
 * Test the network and client cost of insertRecord
 
-```java
+``` java
 void testInsertRecord(String deviceId, long time, List<String> measurements, List<String> values)
 
 void testInsertRecord(String deviceId, long time, List<String> measurements,
@@ -467,7 +454,7 @@ void testInsertRecord(String deviceId, long time, List<String> measurements,
 
 * Test the network and client cost of insertRecords
 
-```java
+``` java
 void testInsertRecords(List<String> deviceIds, List<Long> times,
         List<List<String>> measurementsList, List<List<String>> valuesList)
         
@@ -478,13 +465,13 @@ void testInsertRecords(List<String> deviceIds, List<Long> times,
 
 * Test the network and client cost of insertTablet
 
-```java
+``` java
 void testInsertTablet(Tablet tablet)
 ```
 
 * Test the network and client cost of insertTablets
 
-```java
+``` java
 void testInsertTablets(Map<String, Tablet> tablets)
 ```
 
@@ -579,7 +566,7 @@ APIs in `ClusterInfoService.Client`:
 
 * Get the physical hash ring of the cluster:
 
-```java
+``` java
 list<Node> getRing();
 ```
 
@@ -604,7 +591,7 @@ list<Node> getMetaPartition(1:string path);
 ```
 
 * Get the status (alive or not) of all nodes:
-```java
+``` java
 /**
  * @return key: node, value: live or not
  */
diff --git a/docs/UserGuide/Operate-Metadata/Template.md b/docs/UserGuide/Operate-Metadata/Template.md
index 686156b026..194bb4ecd9 100644
--- a/docs/UserGuide/Operate-Metadata/Template.md
+++ b/docs/UserGuide/Operate-Metadata/Template.md
@@ -70,15 +70,15 @@ After setting the schema template, with the system enabled to auto create schema
 **Attention**: Before inserting data or the system not enabled to auto create schema, timeseries defined by the schema template will not be created. You can use the following SQL statement to create the timeseries or activate the schema template, act before inserting data:
 
 ```shell
-IoTDB> create timeseries of schema template on root.sg1.d1
+IoTDB> create timeseries using schema template on root.sg1.d1
 ```
 
 **Example:** Execute the following statement
 ```shell
 IoTDB> set schema template t1 to root.sg1.d1
 IoTDB> set schema template t2 to root.sg1.d2
-IoTDB> create timeseries of schema template on root.sg1.d1
-IoTDB> create timeseries of schema template on root.sg1.d2
+IoTDB> create timeseries using schema template on root.sg1.d1
+IoTDB> create timeseries using schema template on root.sg1.d2
 ```
 
 Show the time series:
diff --git a/docs/zh/UserGuide/API/Programming-Java-Native-API.md b/docs/zh/UserGuide/API/Programming-Java-Native-API.md
index 6139fc0c71..e3469590de 100644
--- a/docs/zh/UserGuide/API/Programming-Java-Native-API.md
+++ b/docs/zh/UserGuide/API/Programming-Java-Native-API.md
@@ -66,7 +66,7 @@ mvn clean install -pl session -am -Dmaven.test.skip=true
 
 * 初始化 Session
 
-```java
+``` java
 // 全部使用默认配置
 session = new Session.Builder.build();
 
@@ -100,13 +100,13 @@ session =
 
 * 开启 Session
 
-```java
+``` java
 void open()
 ```
 
 * 开启 Session,并决定是否开启 RPC 压缩
 
-```java
+``` java
 void open(boolean enableRPCCompression)
 ```
 
@@ -114,7 +114,7 @@ void open(boolean enableRPCCompression)
 
 * 关闭 Session
 
-```java
+``` java
 void close()
 ```
 
@@ -124,13 +124,13 @@ void close()
 
 * 设置 database
 
-```java
+``` java
 void setStorageGroup(String storageGroupId)
 ```
 
 * 删除单个或多个 database
 
-```java
+``` java
 void deleteStorageGroup(String storageGroup)
 void deleteStorageGroups(List<String> storageGroups)
 ```
@@ -138,7 +138,7 @@ void deleteStorageGroups(List<String> storageGroups)
 
 * 创建单个或多个时间序列
 
-```java
+``` java
 void createTimeseries(String path, TSDataType dataType,
       TSEncoding encoding, CompressionType compressor, Map<String, String> props,
       Map<String, String> tags, Map<String, String> attributes, String measurementAlias)
@@ -161,14 +161,14 @@ void createAlignedTimeseries(String prefixPath, List<String> measurements,
 
 * 删除一个或多个时间序列
 
-```java
+``` java
 void deleteTimeseries(String path)
 void deleteTimeseries(List<String> paths)
 ```
 
 * 检测时间序列是否存在
 
-```java
+``` java
 boolean checkTimeseriesExists(String path)
 ```
 
@@ -176,7 +176,7 @@ boolean checkTimeseriesExists(String path)
 
 * 创建元数据模板,可以通过先后创建 Template、MeasurementNode 的对象,描述模板内物理量结构与类型、编码方式、压缩方式等信息,并通过以下接口创建模板
 
-```java
+``` java
 public void createSchemaTemplate(Template template);
 
 Class Template {
@@ -209,7 +209,7 @@ Class MeasurementNode extends Node {
 
 通过上述类的实例描述模板时,Template 内应当仅能包含单层的 MeasurementNode,具体可以参见如下示例:
 
-```java
+``` java
 MeasurementNode nodeX = new MeasurementNode("x", TSDataType.FLOAT, TSEncoding.RLE, CompressionType.SNAPPY);
 MeasurementNode nodeY = new MeasurementNode("y", TSDataType.FLOAT, TSEncoding.RLE, CompressionType.SNAPPY);
 MeasurementNode nodeSpeed = new MeasurementNode("speed", TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY);
@@ -223,23 +223,10 @@ template.addToTemplate(nodeSpeed);
 createSchemaTemplate(flatTemplate);
 ```
 
-* 对于已经创建的元数据模板,还可以通过以下接口查询模板信息:
-
-```java
-// 查询返回目前模板中所有物理量的数量
-public int countMeasurementsInTemplate(String templateName);
-
-// 检查模板内指定路径是否为物理量
-public boolean isMeasurementInTemplate(String templateName, String path);
-
-// 检查在指定模板内是否存在某路径
-public boolean isPathExistInTemplate(String templateName, String path);
-
-// 返回指定模板内所有物理量的路径
-public List<String> showMeasurementsInTemplate(String templateName);
+* 完成模板挂载操作后,可以通过如下的接口在给定的设备上使用模板注册序列,或者也可以直接向相应的设备写入数据以自动使用模板注册序列。
 
-// 返回指定模板内某前缀路径下的所有物理量的路径
-public List<String> showMeasurementsInTemplate(String templateName, String pattern);
+``` java
+void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
 ```
 
 * 将名为'templateName'的元数据模板挂载到'prefixPath'路径下,在执行这一步之前,你需要创建名为'templateName'的元数据模板
@@ -251,7 +238,7 @@ void setSchemaTemplate(String templateName, String prefixPath)
 
 - 将模板挂载到 MTree 上之后,你可以随时查询所有模板的名称、某模板被设置到 MTree 的所有路径、所有正在使用某模板的所有路径,即如下接口:
 
-```java
+``` java
 /** @return All template names. */
 public List<String> showAllTemplates();
 
@@ -286,7 +273,7 @@ public void dropSchemaTemplate(String templateName);
   * **写入效率高**
   * **支持写入空值**:空值处可以填入任意值,然后通过 BitMap 标记空值
 
-```java
+``` java
 void insertTablet(Tablet tablet)
 
 public class Tablet {
@@ -311,7 +298,7 @@ public class Tablet {
 
 * 插入多个 Tablet
 
-```java
+``` java
 void insertTablets(Map<String, Tablet> tablets)
 ```
 
@@ -328,14 +315,14 @@ void insertTablets(Map<String, Tablet> tablets)
   | DOUBLE     | Double         |
   | TEXT       | String, Binary |
 
-```java
+``` java
 void insertRecord(String prefixPath, long time, List<String> measurements,
    List<TSDataType> types, List<Object> values)
 ```
 
 * 插入多个 Record
 
-```java
+``` java
 void insertRecords(List<String> deviceIds,
         List<Long> times,
         List<List<String>> measurementsList,
@@ -345,7 +332,7 @@ void insertRecords(List<String> deviceIds,
 
 * 插入同属于一个 device 的多个 Record
 
-```java
+``` java
 void insertRecordsOfOneDevice(String deviceId, List<Long> times,
     List<List<String>> measurementsList, List<List<TSDataType>> typesList,
     List<List<Object>> valuesList)
@@ -357,20 +344,20 @@ void insertRecordsOfOneDevice(String deviceId, List<Long> times,
 
 * 插入一个 Record,一个 Record 是一个设备一个时间戳下多个测点的数据
 
-```java
+``` java
 void insertRecord(String prefixPath, long time, List<String> measurements, List<String> values)
 ```
 
 * 插入多个 Record
 
-```java
+``` java
 void insertRecords(List<String> deviceIds, List<Long> times,
    List<List<String>> measurementsList, List<List<String>> valuesList)
 ```
 
 * 插入同属于一个 device 的多个 Record
 
-```java
+``` java
 void insertStringRecordsOfOneDevice(String deviceId, List<Long> times,
     List<List<String>> measurementsList, List<List<String>> valuesList)
 ```
@@ -390,7 +377,7 @@ void insertStringRecordsOfOneDevice(String deviceId, List<Long> times,
 
 * 删除一个或多个时间序列在某个时间点前或这个时间点的数据
 
-```java
+``` java
 void deleteData(String path, long endTime)
 void deleteData(List<String> paths, long endTime)
 ```
@@ -400,14 +387,14 @@ void deleteData(List<String> paths, long endTime)
 * 时间序列原始数据范围查询:
   - 指定的查询时间范围为左闭右开区间,包含开始时间但不包含结束时间。
 
-```java
+``` java
 SessionDataSet executeRawDataQuery(List<String> paths, long startTime, long endTime);
 ```
 
 * 最新点查询:
   - 查询最后一条时间戳大于等于某个时间点的数据。
 
-```java
+``` java
 SessionDataSet executeLastDataQuery(List<String> paths, long lastTime);
 ```
 
@@ -415,7 +402,7 @@ SessionDataSet executeLastDataQuery(List<String> paths, long lastTime);
   - 支持指定查询时间范围。指定的查询时间范围为左闭右开区间,包含开始时间但不包含结束时间。
   - 支持按照时间区间分段查询。
 
-```java
+``` java
 SessionDataSet executeAggregationQuery(List<String> paths, List<Aggregation> aggregations);
 
 SessionDataSet executeAggregationQuery(
@@ -441,13 +428,13 @@ SessionDataSet executeAggregationQuery(
 
 * 执行查询语句
 
-```java
+``` java
 SessionDataSet executeQueryStatement(String sql)
 ```
 
 * 执行非查询语句
 
-```java
+``` java
 void executeNonQueryStatement(String sql)
 ```
 
@@ -457,7 +444,7 @@ void executeNonQueryStatement(String sql)
 
 * 测试 insertRecord
 
-```java
+``` java
 void testInsertRecord(String deviceId, long time, List<String> measurements, List<String> values)
 
 void testInsertRecord(String deviceId, long time, List<String> measurements,
@@ -466,7 +453,7 @@ void testInsertRecord(String deviceId, long time, List<String> measurements,
 
 * 测试 testInsertRecords
 
-```java
+``` java
 void testInsertRecords(List<String> deviceIds, List<Long> times,
         List<List<String>> measurementsList, List<List<String>> valuesList)
 
@@ -477,13 +464,13 @@ void testInsertRecords(List<String> deviceIds, List<Long> times,
 
 * 测试 insertTablet
 
-```java
+``` java
 void testInsertTablet(Tablet tablet)
 ```
 
 * 测试 insertTablets
 
-```java
+``` java
 void testInsertTablets(Map<String, Tablet> tablets)
 ```
 
@@ -568,7 +555,7 @@ API 列表:
 
 * 获取集群中的各个节点的信息(构成哈希环)
 
-```java
+``` java
 list<Node> getRing();
 ```
 
@@ -593,7 +580,7 @@ list<Node> getMetaPartition(1:string path);
 ```
 
 * 获取所有节点的死活状态:
-```java
+``` java
 /**
  * @return key: node, value: live or not
  */
diff --git a/docs/zh/UserGuide/Operate-Metadata/Template.md b/docs/zh/UserGuide/Operate-Metadata/Template.md
index f01f470ee2..6ee5f07143 100644
--- a/docs/zh/UserGuide/Operate-Metadata/Template.md
+++ b/docs/zh/UserGuide/Operate-Metadata/Template.md
@@ -68,15 +68,15 @@ IoTDB> set schema template t1 to root.sg1.d1
 **注意**:在插入数据之前或系统未开启自动注册序列功能,模板定义的时间序列不会被创建。可以使用如下SQL语句在插入数据前创建时间序列即激活模板:
 
 ```shell
-IoTDB> create timeseries of schema template on root.sg1.d1
+IoTDB> create timeseries using schema template on root.sg1.d1
 ```
 
 **示例:** 执行以下语句
 ```shell
 IoTDB> set schema template t1 to root.sg1.d1
 IoTDB> set schema template t2 to root.sg1.d2
-IoTDB> create timeseries of schema template on root.sg1.d1
-IoTDB> create timeseries of schema template on root.sg1.d2
+IoTDB> create timeseries using schema template on root.sg1.d1
+IoTDB> create timeseries using schema template on root.sg1.d2
 ```
 
 查看此时的时间序列:
diff --git a/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBSessionSchemaTemplateIT.java b/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBSessionSchemaTemplateIT.java
index 42a33747a0..f06e9a44de 100644
--- a/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBSessionSchemaTemplateIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/session/it/IoTDBSessionSchemaTemplateIT.java
@@ -224,4 +224,65 @@ public class IoTDBSessionSchemaTemplateIT {
 
     return sessionTemplate;
   }
+
+  @Test
+  public void testBatchActivateTemplate()
+      throws StatementExecutionException, IoTDBConnectionException, IOException {
+    session.createDatabase("root.db");
+
+    Template temp1 = getTemplate("template1");
+    Template temp2 = getTemplate("template2");
+
+    assertEquals("[]", session.showAllTemplates().toString());
+
+    session.createSchemaTemplate(temp1);
+    session.createSchemaTemplate(temp2);
+
+    assertEquals(
+        new HashSet<>(Arrays.asList("template1", "template2")),
+        new HashSet<>(session.showAllTemplates()));
+
+    session.setSchemaTemplate("template1", "root.db.v1");
+    session.setSchemaTemplate("template1", "root.db.v2");
+    session.setSchemaTemplate("template1", "root.db.v3");
+
+    assertEquals(
+        new HashSet<>(Collections.emptyList()),
+        new HashSet<>(session.showPathsTemplateUsingOn("template1")));
+
+    session.setSchemaTemplate("template2", "root.db.v4");
+    session.setSchemaTemplate("template2", "root.db.v5");
+    session.setSchemaTemplate("template2", "root.db.v6");
+
+    assertEquals(
+        new HashSet<>(Arrays.asList("root.db.v4", "root.db.v5", "root.db.v6")),
+        new HashSet<>(session.showPathsTemplateSetOn("template2")));
+
+    session.createTimeseriesUsingSchemaTemplate(Collections.singletonList("root.db.v1.GPS"));
+
+    assertEquals(
+        new HashSet<>(Collections.singletonList("root.db.v1.GPS")),
+        new HashSet<>(session.showPathsTemplateUsingOn("template1")));
+
+    session.createTimeseriesUsingSchemaTemplate(Collections.singletonList("root.db.v5.GPS"));
+
+    assertEquals(
+        new HashSet<>(Collections.singletonList("root.db.v1.GPS")),
+        new HashSet<>(session.showPathsTemplateUsingOn("template1")));
+
+    assertEquals(
+        new HashSet<>(Collections.singletonList("root.db.v5.GPS")),
+        new HashSet<>(session.showPathsTemplateUsingOn("template2")));
+
+    session.createTimeseriesUsingSchemaTemplate(
+        Arrays.asList("root.db.v2.GPS", "root.db.v3.GPS", "root.db.v4.GPS", "root.db.v6.GPS"));
+
+    assertEquals(
+        new HashSet<>(Arrays.asList("root.db.v1.GPS", "root.db.v2.GPS", "root.db.v3.GPS")),
+        new HashSet<>(session.showPathsTemplateUsingOn("template1")));
+
+    assertEquals(
+        new HashSet<>(Arrays.asList("root.db.v4.GPS", "root.db.v5.GPS", "root.db.v6.GPS")),
+        new HashSet<>(session.showPathsTemplateUsingOn("template2")));
+  }
 }
diff --git a/isession/src/main/java/org/apache/iotdb/isession/ISession.java b/isession/src/main/java/org/apache/iotdb/isession/ISession.java
index 92a617b6d8..4095afe368 100644
--- a/isession/src/main/java/org/apache/iotdb/isession/ISession.java
+++ b/isession/src/main/java/org/apache/iotdb/isession/ISession.java
@@ -478,6 +478,9 @@ public interface ISession extends AutoCloseable {
   void dropSchemaTemplate(String templateName)
       throws IoTDBConnectionException, StatementExecutionException;
 
+  void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
+      throws IoTDBConnectionException, StatementExecutionException;
+
   boolean isEnableQueryRedirection();
 
   void setEnableQueryRedirection(boolean enableQueryRedirection);
diff --git a/isession/src/main/java/org/apache/iotdb/isession/pool/ISessionPool.java b/isession/src/main/java/org/apache/iotdb/isession/pool/ISessionPool.java
index ae71cb4132..2f5c017540 100644
--- a/isession/src/main/java/org/apache/iotdb/isession/pool/ISessionPool.java
+++ b/isession/src/main/java/org/apache/iotdb/isession/pool/ISessionPool.java
@@ -394,6 +394,9 @@ public interface ISessionPool {
   void dropSchemaTemplate(String templateName)
       throws StatementExecutionException, IoTDBConnectionException;
 
+  void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
+      throws StatementExecutionException, IoTDBConnectionException;
+
   SessionDataSetWrapper executeQueryStatement(String sql)
       throws IoTDBConnectionException, StatementExecutionException;
 
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/visitor/SchemaExecutionVisitor.java b/server/src/main/java/org/apache/iotdb/db/metadata/visitor/SchemaExecutionVisitor.java
index d553b049a0..d2ca0038b2 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/visitor/SchemaExecutionVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/visitor/SchemaExecutionVisitor.java
@@ -36,6 +36,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanVisitor;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.AlterTimeSeriesNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.BatchActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ConstructSchemaBlackListNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateAlignedTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateMultiTimeSeriesNode;
@@ -326,6 +327,25 @@ public class SchemaExecutionVisitor extends PlanVisitor<TSStatus, ISchemaRegion>
     }
   }
 
+  @Override
+  public TSStatus visitBatchActivateTemplate(
+      BatchActivateTemplateNode node, ISchemaRegion schemaRegion) {
+    for (Map.Entry<PartialPath, Pair<Integer, Integer>> entry :
+        node.getTemplateActivationMap().entrySet()) {
+      Template template = ClusterTemplateManager.getInstance().getTemplate(entry.getValue().left);
+      try {
+        schemaRegion.activateSchemaTemplate(
+            SchemaRegionWritePlanFactory.getActivateTemplateInClusterPlan(
+                entry.getKey(), entry.getValue().right, entry.getValue().left),
+            template);
+      } catch (MetadataException e) {
+        logger.error(e.getMessage(), e);
+        return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
+      }
+    }
+    return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS);
+  }
+
   @Override
   public TSStatus visitInternalBatchActivateTemplate(
       InternalBatchActivateTemplateNode node, ISchemaRegion schemaRegion) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/executor/RegionWriteExecutor.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/executor/RegionWriteExecutor.java
index 9c5917664b..c035edabe5 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/executor/RegionWriteExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/executor/RegionWriteExecutor.java
@@ -45,6 +45,7 @@ import org.apache.iotdb.db.mpp.plan.analyze.schema.SchemaValidator;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanVisitor;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ActivateTemplateNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.BatchActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateAlignedTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateMultiTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateTimeSeriesNode;
@@ -622,6 +623,36 @@ public class RegionWriteExecutor {
       }
     }
 
+    @Override
+    public RegionExecutionResult visitBatchActivateTemplate(
+        BatchActivateTemplateNode node, WritePlanNodeExecutionContext context) {
+      // activate template operation shall be blocked by unset template check
+      context.getRegionWriteValidationRWLock().readLock().lock();
+      try {
+        for (PartialPath devicePath : node.getTemplateActivationMap().keySet()) {
+          Pair<Template, PartialPath> templateSetInfo =
+              ClusterTemplateManager.getInstance().checkTemplateSetInfo(devicePath);
+          if (templateSetInfo == null) {
+            // The activation has already been validated during analyzing.
+            // That means the template is being unset during the activation plan transport.
+            RegionExecutionResult result = new RegionExecutionResult();
+            result.setAccepted(false);
+            String message =
+                String.format(
+                    "Template is being unsetting from path %s. Please try activating later.",
+                    node.getPathSetTemplate(devicePath));
+            result.setMessage(message);
+            result.setStatus(RpcUtils.getStatus(TSStatusCode.METADATA_ERROR, message));
+            return result;
+          }
+        }
+
+        return super.visitBatchActivateTemplate(node, context);
+      } finally {
+        context.getRegionWriteValidationRWLock().readLock().unlock();
+      }
+    }
+
     @Override
     public RegionExecutionResult visitInternalBatchActivateTemplate(
         InternalBatchActivateTemplateNode node, WritePlanNodeExecutionContext context) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index bf15b2a826..e012669ac0 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
@@ -199,6 +199,9 @@ public class Analysis {
   // template and paths set template
   private Pair<Template, List<PartialPath>> templateSetInfo;
 
+  // devicePath -> <template, paths set template>
+  private Map<PartialPath, Pair<Template, PartialPath>> deviceTemplateSetInfoMap;
+
   // potential template used in timeseries query or fetch
   private Map<Integer, Template> relatedTemplateInfo;
 
@@ -494,6 +497,15 @@ public class Analysis {
     this.templateSetInfo = templateSetInfo;
   }
 
+  public Map<PartialPath, Pair<Template, PartialPath>> getDeviceTemplateSetInfoMap() {
+    return deviceTemplateSetInfoMap;
+  }
+
+  public void setDeviceTemplateSetInfoMap(
+      Map<PartialPath, Pair<Template, PartialPath>> deviceTemplateSetInfoMap) {
+    this.deviceTemplateSetInfoMap = deviceTemplateSetInfoMap;
+  }
+
   public Map<Integer, Template> getRelatedTemplateInfo() {
     return relatedTemplateInfo;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index d46dfc0e9b..f9a8c141d0 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
@@ -120,6 +120,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowStorageGroupStatement
 import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowTTLStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowTimeSeriesStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ActivateTemplateStatement;
+import org.apache.iotdb.db.mpp.plan.statement.metadata.template.BatchActivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.CreateSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.SetSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ShowNodesInSchemaTemplateStatement;
@@ -2740,6 +2741,38 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     return analysis;
   }
 
+  @Override
+  public Analysis visitBatchActivateTemplate(
+      BatchActivateTemplateStatement batchActivateTemplateStatement, MPPQueryContext context) {
+    context.setQueryType(QueryType.WRITE);
+    Analysis analysis = new Analysis();
+    analysis.setStatement(batchActivateTemplateStatement);
+
+    Map<PartialPath, Pair<Template, PartialPath>> deviceTemplateSetInfoMap =
+        new HashMap<>(batchActivateTemplateStatement.getPaths().size());
+    for (PartialPath devicePath : batchActivateTemplateStatement.getDevicePathList()) {
+      Pair<Template, PartialPath> templateSetInfo = schemaFetcher.checkTemplateSetInfo(devicePath);
+      if (templateSetInfo == null) {
+        throw new StatementAnalyzeException(
+            new MetadataException(
+                String.format(
+                    "Path [%s] has not been set any template.", devicePath.getFullPath())));
+      }
+      deviceTemplateSetInfoMap.put(devicePath, templateSetInfo);
+    }
+    analysis.setDeviceTemplateSetInfoMap(deviceTemplateSetInfoMap);
+
+    PathPatternTree patternTree = new PathPatternTree();
+    for (PartialPath devicePath : batchActivateTemplateStatement.getDevicePathList()) {
+      patternTree.appendPathPattern(devicePath.concatNode(ONE_LEVEL_PATH_WILDCARD));
+    }
+    SchemaPartition partition = partitionFetcher.getOrCreateSchemaPartition(patternTree);
+
+    analysis.setSchemaPartitionInfo(partition);
+
+    return analysis;
+  }
+
   @Override
   public Analysis visitInternalBatchActivateTemplate(
       InternalBatchActivateTemplateStatement internalBatchActivateTemplateStatement,
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index c5ac196776..3169456672 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -2937,8 +2937,8 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
   }
 
   @Override
-  public Statement visitCreateTimeseriesOfSchemaTemplate(
-      IoTDBSqlParser.CreateTimeseriesOfSchemaTemplateContext ctx) {
+  public Statement visitCreateTimeseriesUsingSchemaTemplate(
+      IoTDBSqlParser.CreateTimeseriesUsingSchemaTemplateContext ctx) {
     ActivateTemplateStatement statement = new ActivateTemplateStatement();
     statement.setPath(parsePrefixPath(ctx.prefixPath()));
     return statement;
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/StatementGenerator.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/StatementGenerator.java
index ccd650336d..728b5b39a9 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/StatementGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/StatementGenerator.java
@@ -54,6 +54,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.CreateTimeSeriesStatement
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DatabaseSchemaStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DeleteStorageGroupStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DeleteTimeSeriesStatement;
+import org.apache.iotdb.db.mpp.plan.statement.metadata.template.BatchActivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.CreateSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.DropSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.SetSchemaTemplateStatement;
@@ -701,6 +702,18 @@ public class StatementGenerator {
     return statement;
   }
 
+  public static BatchActivateTemplateStatement createBatchActivateTemplateStatement(
+      List<String> devicePathStringList) throws IllegalPathException {
+    final long startTime = System.nanoTime();
+    List<PartialPath> devicePathList = new ArrayList<>(devicePathStringList.size());
+    for (String pathString : devicePathStringList) {
+      devicePathList.add(new PartialPath(pathString));
+    }
+    BatchActivateTemplateStatement statement = new BatchActivateTemplateStatement(devicePathList);
+    PERFORMANCE_OVERVIEW_METRICS.recordParseCost(System.nanoTime() - startTime);
+    return statement;
+  }
+
   public static DeleteTimeSeriesStatement createDeleteTimeSeriesStatement(
       List<String> pathPatternStringList) throws IllegalPathException {
     final long startTime = System.nanoTime();
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
index 5e503cb6c6..8895c253c1 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.load.LoadTsFileNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.AlterTimeSeriesNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.BatchActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateAlignedTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateMultiTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateTimeSeriesNode;
@@ -73,6 +74,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowChildPathsStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowDevicesStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowTimeSeriesStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ActivateTemplateStatement;
+import org.apache.iotdb.db.mpp.plan.statement.metadata.template.BatchActivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ShowPathsUsingTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.sys.ShowQueriesStatement;
 import org.apache.iotdb.tsfile.utils.Pair;
@@ -742,6 +744,20 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
         analysis.getTemplateSetInfo().left.getId());
   }
 
+  @Override
+  public PlanNode visitBatchActivateTemplate(
+      BatchActivateTemplateStatement batchActivateTemplateStatement, MPPQueryContext context) {
+    Map<PartialPath, Pair<Integer, Integer>> templateActivationMap = new HashMap<>();
+    for (Map.Entry<PartialPath, Pair<Template, PartialPath>> entry :
+        analysis.getDeviceTemplateSetInfoMap().entrySet()) {
+      templateActivationMap.put(
+          entry.getKey(),
+          new Pair<>(entry.getValue().left.getId(), entry.getValue().right.getNodeLength() - 1));
+    }
+    return new BatchActivateTemplateNode(
+        context.getQueryId().genPlanNodeId(), templateActivationMap);
+  }
+
   @Override
   public PlanNode visitInternalBatchActivateTemplate(
       InternalBatchActivateTemplateStatement internalBatchActivateTemplateStatement,
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanNodeType.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanNodeType.java
index 9b75d9e4a1..11b7b71717 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanNodeType.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanNodeType.java
@@ -36,6 +36,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.read.TimeSeriesCo
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.read.TimeSeriesSchemaScanNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.AlterTimeSeriesNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.BatchActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ConstructSchemaBlackListNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateAlignedTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateMultiTimeSeriesNode;
@@ -165,7 +166,8 @@ public enum PlanNodeType {
   INTERNAL_BATCH_ACTIVATE_TEMPLATE((short) 68),
   INTERNAL_CREATE_MULTI_TIMESERIES((short) 69),
   IDENTITY_SINK((short) 70),
-  SHUFFLE_SINK((short) 71);
+  SHUFFLE_SINK((short) 71),
+  BATCH_ACTIVATE_TEMPLATE((short) 72);
 
   public static final int BYTES = Short.BYTES;
 
@@ -356,6 +358,8 @@ public enum PlanNodeType {
         return IdentitySinkNode.deserialize(buffer);
       case 71:
         return ShuffleSinkNode.deserialize(buffer);
+      case 72:
+        return BatchActivateTemplateNode.deserialize(buffer);
       default:
         throw new IllegalArgumentException("Invalid node type: " + nodeType);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanVisitor.java
index d9430963ff..ab5cce4ebf 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/PlanVisitor.java
@@ -35,6 +35,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.read.TimeSeriesCo
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.read.TimeSeriesSchemaScanNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.AlterTimeSeriesNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.BatchActivateTemplateNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.ConstructSchemaBlackListNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateAlignedTimeSeriesNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write.CreateMultiTimeSeriesNode;
@@ -321,6 +322,10 @@ public abstract class PlanVisitor<R, C> {
     return visitPlan(node, context);
   }
 
+  public R visitBatchActivateTemplate(BatchActivateTemplateNode node, C context) {
+    return visitPlan(node, context);
+  }
+
   public R visitPreDeactivateTemplate(PreDeactivateTemplateNode node, C context) {
     return visitPlan(node, context);
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/ActivateTemplateNode.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/ActivateTemplateNode.java
index 4d7bf8acf5..f941d75075 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/ActivateTemplateNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/ActivateTemplateNode.java
@@ -45,7 +45,7 @@ public class ActivateTemplateNode extends WritePlanNode implements IActivateTemp
   private int templateSetLevel;
   private int templateId;
 
-  private boolean isAligned;
+  private transient boolean isAligned;
 
   private TRegionReplicaSet regionReplicaSet;
 
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/BatchActivateTemplateNode.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/BatchActivateTemplateNode.java
new file mode 100644
index 0000000000..12ecb50c3e
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/write/BatchActivateTemplateNode.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iotdb.db.mpp.plan.planner.plan.node.metedata.write;
+
+import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.path.PathDeserializeUtil;
+import org.apache.iotdb.db.mpp.plan.analyze.Analysis;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanNodeType;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.PlanVisitor;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.WritePlanNode;
+import org.apache.iotdb.tsfile.utils.Pair;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BatchActivateTemplateNode extends WritePlanNode {
+
+  // devicePath -> <templateId, templateSetLevel>
+  private final Map<PartialPath, Pair<Integer, Integer>> templateActivationMap;
+
+  private TRegionReplicaSet regionReplicaSet;
+
+  public BatchActivateTemplateNode(
+      PlanNodeId id, Map<PartialPath, Pair<Integer, Integer>> templateActivationMap) {
+    super(id);
+    this.templateActivationMap = templateActivationMap;
+  }
+
+  private BatchActivateTemplateNode(
+      PlanNodeId id,
+      Map<PartialPath, Pair<Integer, Integer>> templateActivationMap,
+      TRegionReplicaSet regionReplicaSet) {
+    super(id);
+    this.templateActivationMap = templateActivationMap;
+    this.regionReplicaSet = regionReplicaSet;
+  }
+
+  public Map<PartialPath, Pair<Integer, Integer>> getTemplateActivationMap() {
+    return templateActivationMap;
+  }
+
+  public PartialPath getPathSetTemplate(PartialPath devicePath) {
+    return new PartialPath(
+        Arrays.copyOf(devicePath.getNodes(), templateActivationMap.get(devicePath).right + 1));
+  }
+
+  @Override
+  public TRegionReplicaSet getRegionReplicaSet() {
+    return regionReplicaSet;
+  }
+
+  @Override
+  public List<PlanNode> getChildren() {
+    return new ArrayList<>();
+  }
+
+  @Override
+  public void addChild(PlanNode child) {}
+
+  @Override
+  public PlanNode clone() {
+    return new BatchActivateTemplateNode(getPlanNodeId(), templateActivationMap, regionReplicaSet);
+  }
+
+  @Override
+  public int allowedChildCount() {
+    return 0;
+  }
+
+  @Override
+  public List<String> getOutputColumnNames() {
+    return null;
+  }
+
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {
+    PlanNodeType.BATCH_ACTIVATE_TEMPLATE.serialize(byteBuffer);
+
+    int size = templateActivationMap.size();
+    ReadWriteIOUtils.write(size, byteBuffer);
+    for (Map.Entry<PartialPath, Pair<Integer, Integer>> entry : templateActivationMap.entrySet()) {
+      entry.getKey().serialize(byteBuffer);
+      ReadWriteIOUtils.write(entry.getValue().left, byteBuffer);
+      ReadWriteIOUtils.write(entry.getValue().right, byteBuffer);
+    }
+  }
+
+  @Override
+  protected void serializeAttributes(DataOutputStream stream) throws IOException {
+    PlanNodeType.BATCH_ACTIVATE_TEMPLATE.serialize(stream);
+
+    int size = templateActivationMap.size();
+    ReadWriteIOUtils.write(size, stream);
+    for (Map.Entry<PartialPath, Pair<Integer, Integer>> entry : templateActivationMap.entrySet()) {
+      entry.getKey().serialize(stream);
+      ReadWriteIOUtils.write(entry.getValue().left, stream);
+      ReadWriteIOUtils.write(entry.getValue().right, stream);
+    }
+  }
+
+  public static InternalBatchActivateTemplateNode deserialize(ByteBuffer byteBuffer) {
+    int size = ReadWriteIOUtils.readInt(byteBuffer);
+    Map<PartialPath, Pair<Integer, Integer>> templateActivationMap = new HashMap<>(size);
+    for (int i = 0; i < size; i++) {
+      templateActivationMap.put(
+          (PartialPath) PathDeserializeUtil.deserialize(byteBuffer),
+          new Pair<>(ReadWriteIOUtils.readInt(byteBuffer), ReadWriteIOUtils.readInt(byteBuffer)));
+    }
+
+    PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer);
+    return new InternalBatchActivateTemplateNode(planNodeId, templateActivationMap);
+  }
+
+  @Override
+  public List<WritePlanNode> splitByPartition(Analysis analysis) {
+    // gather devices to same target region
+    Map<TRegionReplicaSet, Map<PartialPath, Pair<Integer, Integer>>> splitMap = new HashMap<>();
+    for (Map.Entry<PartialPath, Pair<Integer, Integer>> entry : templateActivationMap.entrySet()) {
+      TRegionReplicaSet regionReplicaSet =
+          analysis.getSchemaPartitionInfo().getSchemaRegionReplicaSet(entry.getKey().getFullPath());
+      splitMap
+          .computeIfAbsent(regionReplicaSet, k -> new HashMap<>())
+          .put(entry.getKey(), entry.getValue());
+    }
+
+    List<WritePlanNode> result = new ArrayList<>();
+    for (Map.Entry<TRegionReplicaSet, Map<PartialPath, Pair<Integer, Integer>>> entry :
+        splitMap.entrySet()) {
+      result.add(new BatchActivateTemplateNode(getPlanNodeId(), entry.getValue(), entry.getKey()));
+    }
+
+    return result;
+  }
+
+  @Override
+  public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+    return visitor.visitBatchActivateTemplate(this, context);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementType.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementType.java
index c4d4fdfeec..5a2c9f15af 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementType.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementType.java
@@ -152,5 +152,7 @@ public enum StatementType {
   DEACTIVATE_TEMPLATE,
 
   INTERNAL_BATCH_ACTIVATE_TEMPLATE,
-  INTERNAL_CREATE_MULTI_TIMESERIES
+  INTERNAL_CREATE_MULTI_TIMESERIES,
+
+  BATCH_ACTIVATE_TEMPLATE
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementVisitor.java
index 9cac1d0646..7d5df6cdd3 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/StatementVisitor.java
@@ -71,6 +71,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowTriggersStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowVariablesStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.UnSetTTLStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ActivateTemplateStatement;
+import org.apache.iotdb.db.mpp.plan.statement.metadata.template.BatchActivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.CreateSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.DeactivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.DropSchemaTemplateStatement;
@@ -384,6 +385,11 @@ public abstract class StatementVisitor<R, C> {
     return visitStatement(activateTemplateStatement, context);
   }
 
+  public R visitBatchActivateTemplate(
+      BatchActivateTemplateStatement batchActivateTemplateStatement, C context) {
+    return visitStatement(batchActivateTemplateStatement, context);
+  }
+
   public R visitShowPathsUsingTemplate(
       ShowPathsUsingTemplateStatement showPathsUsingTemplateStatement, C context) {
     return visitStatement(showPathsUsingTemplateStatement, context);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/BatchActivateTemplateStatement.java
similarity index 60%
copy from server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java
copy to server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/BatchActivateTemplateStatement.java
index 1bf587c1c6..08dd879eec 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/BatchActivateTemplateStatement.java
@@ -19,37 +19,34 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.metadata.template;
 
-import org.apache.iotdb.db.mpp.plan.analyze.QueryType;
-import org.apache.iotdb.db.mpp.plan.statement.IConfigStatement;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.mpp.plan.statement.Statement;
 import org.apache.iotdb.db.mpp.plan.statement.StatementType;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
-import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowStatement;
 
-public class DropSchemaTemplateStatement extends ShowStatement implements IConfigStatement {
+import java.util.List;
 
-  private String templateName;
+public class BatchActivateTemplateStatement extends Statement {
 
-  public DropSchemaTemplateStatement(String templateName) {
+  private final List<PartialPath> devicePathList;
+
+  public BatchActivateTemplateStatement(List<PartialPath> devicePathList) {
     super();
-    this.templateName = templateName;
-    this.statementType = StatementType.DROP_TEMPLATE;
+    this.devicePathList = devicePathList;
+    statementType = StatementType.BATCH_ACTIVATE_TEMPLATE;
   }
 
-  public String getTemplateName() {
-    return templateName;
+  @Override
+  public List<? extends PartialPath> getPaths() {
+    return devicePathList;
   }
 
-  public void setTemplateName(String templateName) {
-    this.templateName = templateName;
+  public List<PartialPath> getDevicePathList() {
+    return devicePathList;
   }
 
   @Override
   public <R, C> R accept(StatementVisitor<R, C> visitor, C context) {
-    return visitor.visitDropSchemaTemplate(this, context);
-  }
-
-  @Override
-  public QueryType getQueryType() {
-    return QueryType.WRITE;
+    return visitor.visitBatchActivateTemplate(this, context);
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java
index 1bf587c1c6..c8f6bfd928 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/template/DropSchemaTemplateStatement.java
@@ -19,13 +19,17 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.metadata.template;
 
+import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.mpp.plan.analyze.QueryType;
 import org.apache.iotdb.db.mpp.plan.statement.IConfigStatement;
+import org.apache.iotdb.db.mpp.plan.statement.Statement;
 import org.apache.iotdb.db.mpp.plan.statement.StatementType;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
-import org.apache.iotdb.db.mpp.plan.statement.metadata.ShowStatement;
 
-public class DropSchemaTemplateStatement extends ShowStatement implements IConfigStatement {
+import java.util.Collections;
+import java.util.List;
+
+public class DropSchemaTemplateStatement extends Statement implements IConfigStatement {
 
   private String templateName;
 
@@ -52,4 +56,9 @@ public class DropSchemaTemplateStatement extends ShowStatement implements IConfi
   public QueryType getQueryType() {
     return QueryType.WRITE;
   }
+
+  @Override
+  public List<? extends PartialPath> getPaths() {
+    return Collections.emptyList();
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/service/thrift/impl/ClientRPCServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/service/thrift/impl/ClientRPCServiceImpl.java
index 5423acac60..2375416f2f 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/thrift/impl/ClientRPCServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/thrift/impl/ClientRPCServiceImpl.java
@@ -57,6 +57,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.CreateTimeSeriesStatement
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DatabaseSchemaStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DeleteStorageGroupStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.DeleteTimeSeriesStatement;
+import org.apache.iotdb.db.mpp.plan.statement.metadata.template.BatchActivateTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.CreateSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.DropSchemaTemplateStatement;
 import org.apache.iotdb.db.mpp.plan.statement.metadata.template.SetSchemaTemplateStatement;
@@ -71,6 +72,7 @@ import org.apache.iotdb.metrics.utils.MetricLevel;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.ServerProperties;
+import org.apache.iotdb.service.rpc.thrift.TCreateTimeseriesUsingSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSAggregationQueryReq;
 import org.apache.iotdb.service.rpc.thrift.TSAppendSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSBackupConfigurationResp;
@@ -1867,6 +1869,52 @@ public class ClientRPCServiceImpl implements IClientRPCServiceWithHandler {
     }
   }
 
+  @Override
+  public TSStatus createTimeseriesUsingSchemaTemplate(TCreateTimeseriesUsingSchemaTemplateReq req)
+      throws TException {
+    try {
+      IClientSession clientSession = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime();
+      if (!SESSION_MANAGER.checkLogin(clientSession)) {
+        return getNotLoggedInStatus();
+      }
+
+      // Step 1: transfer to Statement
+      BatchActivateTemplateStatement statement =
+          StatementGenerator.createBatchActivateTemplateStatement(req.getDevicePathList());
+
+      if (enableAuditLog) {
+        AuditLogger.log(
+            String.format("batch activate schema template %s", req.getDevicePathList()), statement);
+      }
+
+      // permission check
+      TSStatus status = AuthorityChecker.checkAuthority(statement, clientSession);
+      if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+        return status;
+      }
+
+      // Step 2: call the coordinator
+      long queryId = SESSION_MANAGER.requestQueryId();
+      ExecutionResult result =
+          COORDINATOR.execute(
+              statement,
+              queryId,
+              SESSION_MANAGER.getSessionInfo(clientSession),
+              "",
+              partitionFetcher,
+              schemaFetcher);
+
+      return result.status;
+    } catch (IoTDBException e) {
+      return onIoTDBException(e, OperationType.EXECUTE_STATEMENT, e.getErrorCode());
+    } catch (Exception e) {
+      return onNPEOrUnexpectedException(
+          e, OperationType.EXECUTE_STATEMENT, TSStatusCode.EXECUTE_STATEMENT_ERROR);
+    } finally {
+      SESSION_MANAGER.updateIdleTime();
+    }
+  }
+
   @Override
   public TSStatus handshake(TSyncIdentityInfo info) throws TException {
     // TODO(sync): Check permissions here
diff --git a/session/src/main/java/org/apache/iotdb/session/Session.java b/session/src/main/java/org/apache/iotdb/session/Session.java
index 5043ef89e4..ef54c71948 100644
--- a/session/src/main/java/org/apache/iotdb/session/Session.java
+++ b/session/src/main/java/org/apache/iotdb/session/Session.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.NoValidValueException;
 import org.apache.iotdb.rpc.RedirectException;
 import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.service.rpc.thrift.TCreateTimeseriesUsingSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSAppendSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSBackupConfigurationResp;
 import org.apache.iotdb.service.rpc.thrift.TSConnectionInfoResp;
@@ -3339,6 +3340,19 @@ public class Session implements ISession {
     return request;
   }
 
+  /**
+   * Create timeseries represented by schema template under given device paths.
+   *
+   * @param devicePathList the target device paths used for timeseries creation
+   */
+  @Override
+  public void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
+      throws IoTDBConnectionException, StatementExecutionException {
+    TCreateTimeseriesUsingSchemaTemplateReq request = new TCreateTimeseriesUsingSchemaTemplateReq();
+    request.setDevicePathList(devicePathList);
+    defaultSessionConnection.createTimeseriesUsingSchemaTemplate(request);
+  }
+
   /**
    * @param recordsGroup connection to record map
    * @param insertConsumer insert function
diff --git a/session/src/main/java/org/apache/iotdb/session/SessionConnection.java b/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
index dfa77ef892..d5d90c4e23 100644
--- a/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
+++ b/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.rpc.RpcTransportFactory;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.StatementExecutionException;
 import org.apache.iotdb.service.rpc.thrift.IClientRPCService;
+import org.apache.iotdb.service.rpc.thrift.TCreateTimeseriesUsingSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSAggregationQueryReq;
 import org.apache.iotdb.service.rpc.thrift.TSAppendSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSBackupConfigurationResp;
@@ -1057,6 +1058,26 @@ public class SessionConnection {
     }
   }
 
+  protected void createTimeseriesUsingSchemaTemplate(
+      TCreateTimeseriesUsingSchemaTemplateReq request)
+      throws IoTDBConnectionException, StatementExecutionException {
+    request.setSessionId(sessionId);
+    try {
+      RpcUtils.verifySuccess(client.createTimeseriesUsingSchemaTemplate(request));
+    } catch (TException e) {
+      if (reconnect()) {
+        try {
+          request.setSessionId(sessionId);
+          RpcUtils.verifySuccess(client.createTimeseriesUsingSchemaTemplate(request));
+        } catch (TException tException) {
+          throw new IoTDBConnectionException(tException);
+        }
+      } else {
+        throw new IoTDBConnectionException(MSG_RECONNECTION_FAIL);
+      }
+    }
+  }
+
   protected TSBackupConfigurationResp getBackupConfiguration()
       throws IoTDBConnectionException, StatementExecutionException {
     TSBackupConfigurationResp execResp;
diff --git a/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java b/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
index 1f598a06e9..369f723f2d 100644
--- a/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
+++ b/session/src/main/java/org/apache/iotdb/session/pool/SessionPool.java
@@ -2401,6 +2401,26 @@ public class SessionPool implements ISessionPool {
     }
   }
 
+  public void createTimeseriesUsingSchemaTemplate(List<String> devicePathList)
+      throws StatementExecutionException, IoTDBConnectionException {
+    for (int i = 0; i < RETRY; i++) {
+      ISession session = getSession();
+      try {
+        session.createTimeseriesUsingSchemaTemplate(devicePathList);
+        putBack(session);
+        return;
+      } catch (IoTDBConnectionException e) {
+        // TException means the connection is broken, remove it and get a new one.
+        logger.warn(
+            String.format("createTimeseriesOfSchemaTemplate [%s] failed", devicePathList), e);
+        cleanSessionAndMayThrowConnectionException(session, i, e);
+      } catch (StatementExecutionException | RuntimeException e) {
+        putBack(session);
+        throw e;
+      }
+    }
+  }
+
   /**
    * execure query sql users must call closeResultSet(SessionDataSetWrapper) if they do not use the
    * SessionDataSet any more. users do not need to call sessionDataSet.closeOpeationHandler() any
diff --git a/thrift/src/main/thrift/client.thrift b/thrift/src/main/thrift/client.thrift
index 357589db82..e437139369 100644
--- a/thrift/src/main/thrift/client.thrift
+++ b/thrift/src/main/thrift/client.thrift
@@ -429,6 +429,11 @@ struct TSDropSchemaTemplateReq {
   2: required string templateName
 }
 
+struct TCreateTimeseriesUsingSchemaTemplateReq{
+  1: required i64 sessionId
+  2: required list<string> devicePathList
+}
+
 // The sender and receiver need to check some info to confirm validity
 struct TSyncIdentityInfo{
   // Sender needs to tell receiver its identity.
@@ -578,6 +583,8 @@ service IClientRPCService {
 
   common.TSStatus dropSchemaTemplate(1:TSDropSchemaTemplateReq req);
 
+  common.TSStatus createTimeseriesUsingSchemaTemplate(1:TCreateTimeseriesUsingSchemaTemplateReq req);
+
   common.TSStatus handshake(TSyncIdentityInfo info);
 
   common.TSStatus sendPipeData(1:binary buff);