diff --git a/TOC.md b/TOC.md index 6ea36a485dfda..25a5e1e982c46 100644 --- a/TOC.md +++ b/TOC.md @@ -19,7 +19,7 @@ - Develop - [Overview](/develop/dev-guide-overview.md) - Quick Start - - [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md) + - [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md) - [CRUD SQL in TiDB](/develop/dev-guide-tidb-crud-sql.md) - Example Applications - [Golang](/develop/dev-guide-sample-application-golang.md) diff --git a/_docHome.md b/_docHome.md index 3d9a36079c623..665d6764677c5 100644 --- a/_docHome.md +++ b/_docHome.md @@ -49,7 +49,7 @@ Get the power of a cloud-native, distributed SQL database built for real-time an --> -TiDB is an open-source distributed SQL database that supports Hybrid Transactional and Analytical Processing (HTAP) workloads. It is MySQL compatible and features horizontal scalability, strong consistency, and high availability. You can deploy TiDB on premises or in the cloud. +TiDB is an open-source distributed SQL database that supports Hybrid Transactional and Analytical Processing (HTAP) workloads. It is MySQL compatible and features horizontal scalability, strong consistency, and high availability. You can deploy TiDB in a self-hosted environment or in the cloud. diff --git a/br/backup-and-restore-overview.md b/br/backup-and-restore-overview.md index 82041e0d1498a..85807ea6cce49 100644 --- a/br/backup-and-restore-overview.md +++ b/br/backup-and-restore-overview.md @@ -44,7 +44,7 @@ Backup storage and network configuration: ## Use backup and restore -The way to use BR varies with the deployment method of TiDB. This document introduces how to use the br command-line tool to back up and restore TiDB cluster data in an on-premise deployment. +The way to use BR varies with the deployment method of TiDB. This document introduces how to use the br command-line tool to back up and restore TiDB cluster data in a self-hosted environment. For information about how to use this feature in other deployment scenarios, see the following documents: diff --git a/br/br-pitr-guide.md b/br/br-pitr-guide.md index 90c6fc6e46a55..903f2ff625723 100644 --- a/br/br-pitr-guide.md +++ b/br/br-pitr-guide.md @@ -123,7 +123,7 @@ Testing scenario 1 (on [TiDB Cloud](https://tidbcloud.com)): - New log data created in the cluster: 10 GB/h - Write (INSERT/UPDATE/DELETE) QPS: 10,000 -Testing scenario 2 (on-premises): +Testing scenario 2 (on TiDB Self-Hosted): - The number of TiKV nodes (8 core, 64 GB memory): 6 - The number of Regions: 50,000 diff --git a/clinic/clinic-introduction.md b/clinic/clinic-introduction.md index d355a71dde8eb..b3b977ac829d0 100644 --- a/clinic/clinic-introduction.md +++ b/clinic/clinic-introduction.md @@ -74,7 +74,7 @@ First, Diag gets cluster topology information from the deployment tool TiUP (tiu ## Next step -- Use PingCAP Clinic in an on-premise environment +- Use PingCAP Clinic in a self-hosted environment - [Quick Start with PingCAP Clinic](/clinic/quick-start-with-clinic.md) - [Troubleshoot Clusters using PingCAP Clinic](/clinic/clinic-user-guide-for-tiup.md) - [PingCAP Clinic Diagnostic Data](/clinic/clinic-data-instruction-for-tiup.md) diff --git a/clinic/clinic-user-guide-for-tiup.md b/clinic/clinic-user-guide-for-tiup.md index 7652a5ffffea5..7ac8b6f56205e 100644 --- a/clinic/clinic-user-guide-for-tiup.md +++ b/clinic/clinic-user-guide-for-tiup.md @@ -9,7 +9,7 @@ For TiDB clusters and DM clusters deployed using TiUP, you can use PingCAP Clini > **Note:** > -> - This document **only** applies to clusters deployed using TiUP in an on-premises environment. For clusters deployed using TiDB Operator on Kubernetes, see [PingCAP Clinic for TiDB Operator environments](https://docs.pingcap.com/tidb-in-kubernetes/stable/clinic-user-guide). +> - This document **only** applies to clusters deployed using TiUP in a self-hosted environment. For clusters deployed using TiDB Operator on Kubernetes, see [PingCAP Clinic for TiDB Operator environments](https://docs.pingcap.com/tidb-in-kubernetes/stable/clinic-user-guide). > > - PingCAP Clinic **does not support** collecting data from clusters deployed using TiDB Ansible. diff --git a/develop/dev-guide-aws-appflow-integration.md b/develop/dev-guide-aws-appflow-integration.md index 608a3c30781ba..1b2ff2a604b4c 100644 --- a/develop/dev-guide-aws-appflow-integration.md +++ b/develop/dev-guide-aws-appflow-integration.md @@ -7,9 +7,9 @@ summary: Introduce how to integrate TiDB with Amazon AppFlow step by step. [Amazon AppFlow](https://aws.amazon.com/appflow/) is a fully managed API integration service that you use to connect your software as a service (SaaS) applications to AWS services, and securely transfer data. With Amazon AppFlow, you can import and export data from and to TiDB into many types of data providers, such as Salesforce, Amazon S3, LinkedIn, and GitHub. For more information, see [Supported source and destination applications](https://docs.aws.amazon.com/appflow/latest/userguide/app-specific.html) in AWS documentation. -This document describes how to integrate TiDB with Amazon AppFlow and takes integrating a TiDB Cloud Serverless Tier cluster as an example. +This document describes how to integrate TiDB with Amazon AppFlow and takes integrating a TiDB Serverless cluster as an example. -If you do not have a TiDB cluster, you can create a [Serverless Tier](https://tidbcloud.com/console/clusters) cluster, which is free and can be created in approximately 30 seconds. +If you do not have a TiDB cluster, you can create a [TiDB Serverless](https://tidbcloud.com/console/clusters) cluster, which is free and can be created in approximately 30 seconds. ## Prerequisites @@ -66,7 +66,7 @@ git clone https://github.com/pingcap-inc/tidb-appflow-integration > > - The `--guided` option uses prompts to guide you through the deployment. Your input will be stored in a configuration file, which is `samconfig.toml` by default. > - `stack_name` specifies the name of AWS Lambda that you are deploying. - > - This prompted guide uses AWS as the cloud provider of TiDB Cloud Serverless Tier. To use Amazon S3 as the source or destination, you need to set the `region` of AWS Lambda as the same as that of Amazon S3. + > - This prompted guide uses AWS as the cloud provider of TiDB Serverless. To use Amazon S3 as the source or destination, you need to set the `region` of AWS Lambda as the same as that of Amazon S3. > - If you have already run `sam deploy --guided` before, you can just run `sam deploy` instead, and SAM CLI will use the configuration file `samconfig.toml` to simplify the interaction. If you see a similar output as follows, this Lambda is successfully deployed. @@ -148,7 +148,7 @@ Choose the **Source details** and **Destination details**. TiDB connector can be ``` 5. After the `sf_account` table is created, click **Connect**. A connection dialog is displayed. -6. In the **Connect to TiDB-Connector** dialog, enter the connection properties of the TiDB cluster. If you use a TiDB Cloud Serverless Tier cluster, you need to set the **TLS** option to `Yes`, which lets the TiDB connector use the TLS connection. Then, click **Connect**. +6. In the **Connect to TiDB-Connector** dialog, enter the connection properties of the TiDB cluster. If you use a TiDB Serverless cluster, you need to set the **TLS** option to `Yes`, which lets the TiDB connector use the TLS connection. Then, click **Connect**. ![tidb connection message](/media/develop/aws-appflow-step-tidb-connection-message.png) @@ -244,5 +244,5 @@ test> SELECT * FROM sf_account; - If anything goes wrong, you can navigate to the [CloudWatch](https://console.aws.amazon.com/cloudwatch/home) page on the AWS Management Console to get logs. - The steps in this document are based on [Building custom connectors using the Amazon AppFlow Custom Connector SDK](https://aws.amazon.com/blogs/compute/building-custom-connectors-using-the-amazon-appflow-custom-connector-sdk/). -- [TiDB Cloud Serverless Tier](https://docs.pingcap.com/tidbcloud/select-cluster-tier#serverless-tier-beta) is **NOT** a production environment. +- [TiDB Serverless](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta) is **NOT** a production environment. - To prevent excessive length, the examples in this document only show the `Insert` strategy, but `Update` and `Upsert` strategies are also tested and can be used. \ No newline at end of file diff --git a/develop/dev-guide-build-cluster-in-cloud.md b/develop/dev-guide-build-cluster-in-cloud.md index e71393be66a30..3c5d7425c52dd 100644 --- a/develop/dev-guide-build-cluster-in-cloud.md +++ b/develop/dev-guide-build-cluster-in-cloud.md @@ -1,15 +1,15 @@ --- -title: Build a TiDB Cluster in TiDB Cloud (Serverless Tier) -summary: Learn how to build a TiDB cluster in TiDB Cloud (Serverless Tier) and connect to a TiDB Cloud cluster. +title: Build a TiDB Serverless Cluster +summary: Learn how to build a TiDB Serverless cluster in TiDB Cloud and connect to it. --- -# Build a TiDB Cluster in TiDB Cloud (Serverless Tier) +# Build a TiDB Serverless Cluster -This document walks you through the quickest way to get started with TiDB. You will use [TiDB Cloud](https://en.pingcap.com/tidb-cloud) to create a Serverless Tier cluster, connect to it, and run a sample application on it. +This document walks you through the quickest way to get started with TiDB. You will use [TiDB Cloud](https://en.pingcap.com/tidb-cloud) to create a TiDB Serverless cluster, connect to it, and run a sample application on it. If you need to run TiDB on your local machine, see [Starting TiDB Locally](/quick-start-with-tidb.md). @@ -21,7 +21,7 @@ This document walks you through the quickest way to get started with TiDB Cloud. -## Step 1. Create a Serverless Tier cluster +## Step 1. Create a TiDB Serverless cluster 1. If you do not have a TiDB Cloud account, click [here](https://tidbcloud.com/free-trial) to sign up for an account. @@ -29,9 +29,9 @@ This document walks you through the quickest way to get started with TiDB Cloud. 3. On the [**Clusters**](https://tidbcloud.com/console/clusters) page, click **Create Cluster**. -4. On the **Create Cluster** page, **Serverless Tier** is selected by default. Update the default cluster name if necessary, and then select the region where you want to create your cluster. +4. On the **Create Cluster** page, **Serverless** is selected by default. Update the default cluster name if necessary, and then select the region where you want to create your cluster. -5. Click **Create** to create a Serverless Tier cluster. +5. Click **Create** to create a TiDB Serverless cluster. Your TiDB Cloud cluster will be created in approximately 30 seconds. @@ -45,7 +45,7 @@ This document walks you through the quickest way to get started with TiDB Cloud. > **Note:** > -> For [Serverless Tier clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#serverless-tier), when you connect to your cluster, you must include the prefix for your cluster in the user name and wrap the name with quotation marks. For more information, see [User name prefix](https://docs.pingcap.com/tidbcloud/select-cluster-tier#user-name-prefix). +> For [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta), when you connect to your cluster, you must include the prefix for your cluster in the user name and wrap the name with quotation marks. For more information, see [User name prefix](https://docs.pingcap.com/tidbcloud/select-cluster-tier#user-name-prefix). @@ -53,7 +53,7 @@ This document walks you through the quickest way to get started with TiDB Cloud. > **Note:** > -> For [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta), when you connect to your cluster, you must include the prefix for your cluster in the user name and wrap the name with quotation marks. For more information, see [User name prefix](/tidb-cloud/select-cluster-tier.md#user-name-prefix). +> For [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta), when you connect to your cluster, you must include the prefix for your cluster in the user name and wrap the name with quotation marks. For more information, see [User name prefix](/tidb-cloud/select-cluster-tier.md#user-name-prefix). @@ -130,7 +130,7 @@ mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1 -2. Run the connection string obtained in [Step 1](#step-1-create-a-serverless-tier-cluster). +2. Run the connection string obtained in [Step 1](#step-1-create-a-tidb-serverless-cluster). {{< copyable "shell-regular" >}} @@ -142,8 +142,8 @@ mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1 > **Note:** > -> - When you connect to a Serverless Tier cluster, you must [use the TLS connection](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters). -> - If you encounter problems when connecting to a Serverless Tier cluster, you can read [Secure Connections to Serverless Tier Clusters](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters) for more information. +> - When you connect to a TiDB Serverless cluster, you must [use the TLS connection](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters). +> - If you encounter problems when connecting to a TiDB Serverless cluster, you can read [Secure Connections to TiDB Serverless Clusters](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters) for more information. @@ -151,8 +151,8 @@ mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1 > **Note:** > -> - When you connect to a Serverless Tier cluster, you must [use the TLS connection](/tidb-cloud/secure-connections-to-serverless-tier-clusters.md). -> - If you encounter problems when connecting to a Serverless Tier cluster, you can read [Secure Connections to Serverless Tier Clusters](/tidb-cloud/secure-connections-to-serverless-tier-clusters.md) for more information. +> - When you connect to a TiDB Serverless cluster, you must [use the TLS connection](/tidb-cloud/secure-connections-to-serverless-tier-clusters.md). +> - If you encounter problems when connecting to a TiDB Serverless cluster, you can read [Secure Connections to TiDB Serverless Clusters](/tidb-cloud/secure-connections-to-serverless-tier-clusters.md) for more information. diff --git a/develop/dev-guide-create-database.md b/develop/dev-guide-create-database.md index d79fbbb44462f..c9ef93d081af3 100644 --- a/develop/dev-guide-create-database.md +++ b/develop/dev-guide-create-database.md @@ -11,7 +11,7 @@ This document describes how to create a database using SQL and various programmi Before creating a database, do the following: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md). +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md). - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md). ## What is database diff --git a/develop/dev-guide-create-secondary-indexes.md b/develop/dev-guide-create-secondary-indexes.md index 486cc168af422..0e57fd3c6f4a9 100644 --- a/develop/dev-guide-create-secondary-indexes.md +++ b/develop/dev-guide-create-secondary-indexes.md @@ -11,7 +11,7 @@ This document describes how to create a secondary index using SQL and various pr Before creating a secondary index, do the following: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md). +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md). - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md). - [Create a Database](/develop/dev-guide-create-database.md). - [Create a Table](/develop/dev-guide-create-table.md). diff --git a/develop/dev-guide-create-table.md b/develop/dev-guide-create-table.md index 4fc1bc58ba275..1ad1b6dbf7ff4 100644 --- a/develop/dev-guide-create-table.md +++ b/develop/dev-guide-create-table.md @@ -11,7 +11,7 @@ This document introduces how to create tables using the SQL statement and the re Before reading this document, make sure that the following tasks are completed: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md). +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md). - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md). - [Create a Database](/develop/dev-guide-create-database.md). @@ -290,7 +290,7 @@ ALTER TABLE `bookshop`.`ratings` SET TIFLASH REPLICA 1; > **Note:** > -> If your cluster does not contain **TiFlash** nodes, this SQL statement will report an error: `1105 - the tiflash replica count: 1 should be less than the total tiflash server count: 0`. You can use [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster) to create a Serverless Tier cluster that includes **TiFlash**. +> If your cluster does not contain **TiFlash** nodes, this SQL statement will report an error: `1105 - the tiflash replica count: 1 should be less than the total tiflash server count: 0`. You can use [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster) to create a TiDB Serverless cluster that includes **TiFlash**. Then you can go on to perform the following query: diff --git a/develop/dev-guide-delete-data.md b/develop/dev-guide-delete-data.md index 5dfb874bc7566..96278a73313a5 100644 --- a/develop/dev-guide-delete-data.md +++ b/develop/dev-guide-delete-data.md @@ -11,7 +11,7 @@ This document describes how to use the [DELETE](/sql-statements/sql-statement-de Before reading this document, you need to prepare the following: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md) +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md) - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md), [Create a Database](/develop/dev-guide-create-database.md), [Create a Table](/develop/dev-guide-create-table.md), and [Create Secondary Indexes](/develop/dev-guide-create-secondary-indexes.md) - [Insert Data](/develop/dev-guide-insert-data.md) diff --git a/develop/dev-guide-insert-data.md b/develop/dev-guide-insert-data.md index b95183c995610..94694f824bba5 100644 --- a/develop/dev-guide-insert-data.md +++ b/develop/dev-guide-insert-data.md @@ -13,7 +13,7 @@ This document describes how to insert data into TiDB by using the SQL language w Before reading this document, you need to prepare the following: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md). +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md). - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md), [Create a Database](/develop/dev-guide-create-database.md), [Create a Table](/develop/dev-guide-create-table.md), and [Create Secondary Indexes](/develop/dev-guide-create-secondary-indexes.md) ## Insert rows diff --git a/develop/dev-guide-outdated-for-django.md b/develop/dev-guide-outdated-for-django.md index 7a0f8240cb80e..632acb1ce1759 100644 --- a/develop/dev-guide-outdated-for-django.md +++ b/develop/dev-guide-outdated-for-django.md @@ -25,7 +25,7 @@ The above command starts a temporary and single-node cluster with mock TiKV. The > > To deploy a "real" TiDB cluster for production, see the following guides: > -> + [Deploy TiDB using TiUP for On-Premises](https://docs.pingcap.com/tidb/v5.1/production-deployment-using-tiup) +> + [Deploy TiDB using TiUP for Self-Hosted Environment](https://docs.pingcap.com/tidb/v5.1/production-deployment-using-tiup) > + [Deploy TiDB on Kubernetes](https://docs.pingcap.com/tidb-in-kubernetes/stable) > > You can also [use TiDB Cloud](https://pingcap.com/products/tidbcloud/), a fully-managed Database-as-a-Service (DBaaS) of TiDB. diff --git a/develop/dev-guide-proxysql-integration.md b/develop/dev-guide-proxysql-integration.md index b89fd24ce7d84..e0465d329a0b4 100644 --- a/develop/dev-guide-proxysql-integration.md +++ b/develop/dev-guide-proxysql-integration.md @@ -119,11 +119,11 @@ systemctl start docker ### Option 1: Integrate TiDB Cloud with ProxySQL -For this integration, you will be using the [ProxySQL Docker image](https://hub.docker.com/r/proxysql/proxysql) along with a TiDB Serverless Tier cluster. The following steps will set up ProxySQL on port `16033`, so make sure this port is available. +For this integration, you will be using the [ProxySQL Docker image](https://hub.docker.com/r/proxysql/proxysql) along with a TiDB Serverless cluster. The following steps will set up ProxySQL on port `16033`, so make sure this port is available. -#### Step 1. Create a TiDB Cloud Serverless Tier cluster +#### Step 1. Create a TiDB Serverless cluster -1. [Create a free TiDB Serverless Tier cluster](https://docs.pingcap.com/tidbcloud/tidb-cloud-quickstart#step-1-create-a-tidb-cluster). Remember the root password that you set for your cluster. +1. [Create a free TiDB Serverless cluster](https://docs.pingcap.com/tidbcloud/tidb-cloud-quickstart#step-1-create-a-tidb-cluster). Remember the root password that you set for your cluster. 2. Get your cluster hostname, port, and username for later use. 1. On the [Clusters](https://tidbcloud.com/console/clusters) page, click your cluster name to go to the cluster overview page. @@ -327,12 +327,12 @@ For this integration, you will be using the [ProxySQL Docker image](https://hub. > > 1. Adds a user using the username and password of your cluster. > 2. Assigns the user to the monitoring account. - > 3. Adds your TiDB Serverless Tier cluster to the list of hosts. - > 4. Enables a secure connection between ProxySQL and the TiDB Serverless Tier cluster. + > 3. Adds your TiDB Serverless cluster to the list of hosts. + > 4. Enables a secure connection between ProxySQL and the TiDB Serverless cluster. > > To have a better understanding, it is strongly recommended that you check the `proxysql-prepare.sql` file. To learn more about ProxySQL configuration, see [ProxySQL documentation](https://proxysql.com/documentation/proxysql-configuration/). - The following is an example output. You will see that the hostname of your cluster is shown in the output, which means that the connectivity between ProxySQL and the TiDB Serverless Tier cluster is established. + The following is an example output. You will see that the hostname of your cluster is shown in the output, which means that the connectivity between ProxySQL and the TiDB Serverless cluster is established. ``` *************************** 1. row *************************** @@ -388,7 +388,7 @@ For this integration, you will be using the [ProxySQL Docker image](https://hub. SELECT VERSION(); ``` - If the TiDB version is displayed, you are successfully connected to your TiDB Serverless Tier cluster through ProxySQL. To exit from the MySQL client anytime, enter `quit` and press enter. + If the TiDB version is displayed, you are successfully connected to your TiDB Serverless cluster through ProxySQL. To exit from the MySQL client anytime, enter `quit` and press enter. > **Note:** > @@ -636,7 +636,7 @@ ProxySQL can be installed on many different platforms. The following takes CentO For a full list of supported platforms and the corresponding version requirements, see [ProxySQL documentation](https://proxysql.com/documentation/installing-proxysql/). -#### Step 1. Create a TiDB Cloud Dedicated Tier cluster +#### Step 1. Create a TiDB Dedicated cluster For detailed steps, see [Create a TiDB Cluster](https://docs.pingcap.com/tidbcloud/create-tidb-cluster). @@ -687,7 +687,7 @@ To use ProxySQL as a proxy for TiDB, you need to configure ProxySQL. To do so, y The above step will take you to the ProxySQL admin prompt. -2. Configure the TiDB clusters to be used, where you can add one or multiple TiDB clusters to ProxySQL. The following statement will add one TiDB Cloud Dedicated Tier cluster for example. You need to replace `` and `` with your TiDB Cloud endpoint and port (the default port is `4000`). +2. Configure the TiDB clusters to be used, where you can add one or multiple TiDB clusters to ProxySQL. The following statement will add one TiDB Dedicated cluster for example. You need to replace `` and `` with your TiDB Cloud endpoint and port (the default port is `4000`). ```sql INSERT INTO mysql_servers(hostgroup_id, hostname, port) diff --git a/develop/dev-guide-sample-application-golang-gorm.md b/develop/dev-guide-sample-application-golang-gorm.md new file mode 100644 index 0000000000000..95bb8e9d0dfda --- /dev/null +++ b/develop/dev-guide-sample-application-golang-gorm.md @@ -0,0 +1,326 @@ +--- +title: Build a Simple CRUD App with TiDB and GORM +summary: Learn how to build a simple CRUD application with TiDB and GORM. +--- + + + + +# Build a Simple CRUD App with TiDB and GORM + +[GORM](https://gorm.io/) is a popular open-source ORM library for Golang. + +This document describes how to use TiDB and GORM to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Golang 1.16 or a later version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB Cluster Using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-golang.git +``` + +Compared with GORM, the go-sql-driver/mysql implementation might be not a best practice, because you need to write error handling logic, close `*sql.Rows` manually and cannot reuse code easily, which makes your code slightly redundant. + +The following instructions take `v1.23.5` as an example. + +To adapt TiDB transactions, write a toolkit [util](https://github.com/pingcap-inc/tidb-example-golang/tree/main/util) according to the following code: + +```go +package util + +import ( + "context" + "database/sql" +) + +type TiDBSqlTx struct { + *sql.Tx + conn *sql.Conn + pessimistic bool +} + +func TiDBSqlBegin(db *sql.DB, pessimistic bool) (*TiDBSqlTx, error) { + ctx := context.Background() + conn, err := db.Conn(ctx) + if err != nil { + return nil, err + } + if pessimistic { + _, err = conn.ExecContext(ctx, "set @@tidb_txn_mode=?", "pessimistic") + } else { + _, err = conn.ExecContext(ctx, "set @@tidb_txn_mode=?", "optimistic") + } + if err != nil { + return nil, err + } + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + return &TiDBSqlTx{ + conn: conn, + Tx: tx, + pessimistic: pessimistic, + }, nil +} + +func (tx *TiDBSqlTx) Commit() error { + defer tx.conn.Close() + return tx.Tx.Commit() +} + +func (tx *TiDBSqlTx) Rollback() error { + defer tx.conn.Close() + return tx.Tx.Rollback() +} +``` + +Change to the `gorm` directory: + +```shell +cd gorm +``` + +The structure of this directory is as follows: + +``` +. +├── Makefile +├── go.mod +├── go.sum +└── gorm.go +``` + +`gorm.go` is the main body of the `gorm`. Compared with go-sql-driver/mysql, GORM avoids differences in database creation between different databases. It also implements a lot of operations, such as AutoMigrate and CRUD of objects, which greatly simplifies the code. + +`Player` is a data entity struct that is a mapping for tables. Each property of a `Player` corresponds to a field in the `player` table. Compared with go-sql-driver/mysql, `Player` in GORM adds struct tags to indicate mapping relationships for more information, such as `gorm:"primaryKey;type:VARCHAR(36);column:id"`. + +```go + +package main + +import ( + "fmt" + "math/rand" + + "github.com/google/uuid" + "github.com/pingcap-inc/tidb-example-golang/util" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/logger" +) + +type Player struct { + ID string `gorm:"primaryKey;type:VARCHAR(36);column:id"` + Coins int `gorm:"column:coins"` + Goods int `gorm:"column:goods"` +} + +func (*Player) TableName() string { + return "player" +} + +func main() { + // 1. Configure the example database connection. + db := createDB() + + // AutoMigrate for player table + db.AutoMigrate(&Player{}) + + // 2. Run some simple examples. + simpleExample(db) + + // 3. Explore more. + tradeExample(db) +} + +func tradeExample(db *gorm.DB) { + // Player 1: id is "1", has only 100 coins. + // Player 2: id is "2", has 114514 coins, and 20 goods. + player1 := &Player{ID: "1", Coins: 100} + player2 := &Player{ID: "2", Coins: 114514, Goods: 20} + + // Create two players "by hand", using the INSERT statement on the backend. + db.Clauses(clause.OnConflict{UpdateAll: true}).Create(player1) + db.Clauses(clause.OnConflict{UpdateAll: true}).Create(player2) + + // Player 1 wants to buy 10 goods from player 2. + // It will cost 500 coins, but player 1 cannot afford it. + fmt.Println("\nbuyGoods:\n => this trade will fail") + if err := buyGoods(db, player2.ID, player1.ID, 10, 500); err == nil { + panic("there shouldn't be success") + } + + // So player 1 has to reduce the incoming quantity to two. + fmt.Println("\nbuyGoods:\n => this trade will success") + if err := buyGoods(db, player2.ID, player1.ID, 2, 100); err != nil { + panic(err) + } +} + +func simpleExample(db *gorm.DB) { + // Create a player, who has a coin and a goods. + if err := db.Clauses(clause.OnConflict{UpdateAll: true}). + Create(&Player{ID: "test", Coins: 1, Goods: 1}).Error; err != nil { + panic(err) + } + + // Get a player. + var testPlayer Player + db.Find(&testPlayer, "id = ?", "test") + fmt.Printf("getPlayer: %+v\n", testPlayer) + + // Create players with bulk inserts. Insert 1919 players totally, with 114 players per batch. + bulkInsertPlayers := make([]Player, 1919, 1919) + total, batch := 1919, 114 + for i := 0; i < total; i++ { + bulkInsertPlayers[i] = Player{ + ID: uuid.New().String(), + Coins: rand.Intn(10000), + Goods: rand.Intn(10000), + } + } + + if err := db.Session(&gorm.Session{Logger: db.Logger.LogMode(logger.Error)}). + CreateInBatches(bulkInsertPlayers, batch).Error; err != nil { + panic(err) + } + + // Count players amount. + playersCount := int64(0) + db.Model(&Player{}).Count(&playersCount) + fmt.Printf("countPlayers: %d\n", playersCount) + + // Print 3 players. + threePlayers := make([]Player, 3, 3) + db.Limit(3).Find(&threePlayers) + for index, player := range threePlayers { + fmt.Printf("print %d player: %+v\n", index+1, player) + } +} + +func createDB() *gorm.DB { + dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + if err != nil { + panic(err) + } + + return db +} + +func buyGoods(db *gorm.DB, sellID, buyID string, amount, price int) error { + return util.TiDBGormBegin(db, true, func(tx *gorm.DB) error { + var sellPlayer, buyPlayer Player + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + Find(&sellPlayer, "id = ?", sellID).Error; err != nil { + return err + } + + if sellPlayer.ID != sellID || sellPlayer.Goods < amount { + return fmt.Errorf("sell player %s goods not enough", sellID) + } + + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + Find(&buyPlayer, "id = ?", buyID).Error; err != nil { + return err + } + + if buyPlayer.ID != buyID || buyPlayer.Coins < price { + return fmt.Errorf("buy player %s coins not enough", buyID) + } + + updateSQL := "UPDATE player set goods = goods + ?, coins = coins + ? WHERE id = ?" + if err := tx.Exec(updateSQL, -amount, price, sellID).Error; err != nil { + return err + } + + if err := tx.Exec(updateSQL, amount, -price, buyID).Error; err != nil { + return err + } + + fmt.Println("\n[buyGoods]:\n 'trade success'") + return nil + }) +} +``` + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, modify the value of the `dsn` in `gorm.go`: + +```go +dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4" +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `mysql.RegisterTLSConfig` and `dsn` as follows: + +```go +mysql.RegisterTLSConfig("register-tidb-tls", &tls.Config { + MinVersion: tls.VersionTLS12, + ServerName: "xxx.tidbcloud.com", +}) + +dsn := "2aEp24QWEDLqRFs.root:123456@tcp(xxx.tidbcloud.com:4000)/test?charset=utf8mb4&tls=register-tidb-tls" +``` + +### Step 3.2 Run the code + +To run the code, you can run `make build` and `make run` respectively: + +```shell +make build # this command executes `go build -o bin/gorm-example` +make run # this command executes `./bin/gorm-example` +``` + +Or you can use the native commands: + +```shell +go build -o bin/gorm-example +./bin/gorm-example +``` + +Or run the `make` command directly, which is a combination of `make build` and `make run`. + +## Step 4. Expected output + +[GORM Expected Output](https://github.com/pingcap-inc/tidb-example-golang/blob/main/Expected-Output.md#gorm) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-golang.md b/develop/dev-guide-sample-application-golang.md index 94d6409a4ffc1..28823c3fde145 100644 --- a/develop/dev-guide-sample-application-golang.md +++ b/develop/dev-guide-sample-application-golang.md @@ -20,9 +20,9 @@ This document describes how to use TiDB and Golang to build a simple CRUD applic The following introduces how to start a TiDB cluster. -**Use a TiDB Cloud Serverless Tier cluster** +**Use a TiDB Serverless cluster** -For detailed steps, see [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). **Use a local cluster** @@ -32,7 +32,7 @@ For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md# -See [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). @@ -768,7 +768,7 @@ When using go-sql-driver/mysql, you need to connect to your cluster and run the
-If you are using a TiDB Cloud Serverless Tier cluster, modify the value of the `dsn` in `gorm.go`: +If you are using a TiDB Serverless cluster, modify the value of the `dsn` in `gorm.go`: ```go dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4" @@ -795,7 +795,7 @@ dsn := "2aEp24QWEDLqRFs.root:123456@tcp(xxx.tidbcloud.com:4000)/test?charset=utf
-If you are using a TiDB Cloud Serverless Tier cluster, modify the value of the `dsn` in `sqldriver.go`: +If you are using a TiDB Serverless cluster, modify the value of the `dsn` in `sqldriver.go`: ```go dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4" diff --git a/develop/dev-guide-sample-application-java-hibernate.md b/develop/dev-guide-sample-application-java-hibernate.md new file mode 100644 index 0000000000000..ef346f9c3150d --- /dev/null +++ b/develop/dev-guide-sample-application-java-hibernate.md @@ -0,0 +1,426 @@ +--- +title: Build a Simple CRUD App with TiDB and Hibernate +summary: Learn how to build a simple CRUD application with TiDB and Hibernate. +--- + + + + +# Build a Simple CRUD App with TiDB and Hibernate + +This document describes how to use TiDB and Hibernate to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Java 8 or a later Java version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB Cluster Using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-java.git +``` + +Compared with Hibernate, the JDBC implementation might be not a best practice, because you need to write error handling logic manually and cannot reuse code easily, which makes your code slightly redundant. + +Hibernate is a popular open-source Java ORM, and it supports TiDB dialect starting from `v6.0.0.Beta2`, which fits TiDB features well. The following instructions take `v6.0.0.Beta2` as an example. + +Change to the `plain-java-hibernate` directory: + +```shell +cd plain-java-hibernate +``` + +The structure of this directory is as follows: + +``` +. +├── Makefile +├── plain-java-hibernate.iml +├── pom.xml +└── src + └── main + ├── java + │ └── com + │ └── pingcap + │ └── HibernateExample.java + └── resources + └── hibernate.cfg.xml +``` + +`hibernate.cfg.xml` is the Hibernate configuration file: + +```xml + + + + + + + com.mysql.cj.jdbc.Driver + org.hibernate.dialect.TiDBDialect + jdbc:mysql://localhost:4000/test + root + + false + + + create-drop + + + true + true + + +``` + +`HibernateExample.java` is the main body of the `plain-java-hibernate`. Compared with JDBC, when using Hibernate, you only need to write the path of the configuration file, because Hibernate avoids differences in database creation between different databases. + +`PlayerDAO` is a class used to manage data, in which `DAO` means [Data Access Object](https://en.wikipedia.org/wiki/Data_access_object). The class defines a set of data manipulation methods for writing data. Compared with JDBC, Hibernate encapsulates a large number of operations such as object mapping and CRUD of basic objects, which greatly simplifies the code. + +`PlayerBean` is a data entity class that is a mapping for tables. Each property of a `PlayerBean` corresponds to a field in the `player` table. Compared with JDBC, `PlayerBean` in Hibernate adds annotations to indicate mapping relationships for more information. + +```java +package com.pingcap; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.JDBCException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Entity +@Table(name = "player_hibernate") +class PlayerBean { + @Id + private String id; + @Column(name = "coins") + private Integer coins; + @Column(name = "goods") + private Integer goods; + + public PlayerBean() { + } + + public PlayerBean(String id, Integer coins, Integer goods) { + this.id = id; + this.coins = coins; + this.goods = goods; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getCoins() { + return coins; + } + + public void setCoins(Integer coins) { + this.coins = coins; + } + + public Integer getGoods() { + return goods; + } + + public void setGoods(Integer goods) { + this.goods = goods; + } + + @Override + public String toString() { + return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n", + "id", this.id, "coins", this.coins, "goods", this.goods); + } +} + +/** + * Main class for the basic Hibernate example. + **/ +public class HibernateExample +{ + public static class PlayerDAO { + public static class NotEnoughException extends RuntimeException { + public NotEnoughException(String message) { + super(message); + } + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + public Object runTransaction(Session session, Function fn) { + Object resultObject = null; + + Transaction txn = session.beginTransaction(); + try { + resultObject = fn.apply(session); + txn.commit(); + System.out.println("APP: COMMIT;"); + } catch (JDBCException e) { + System.out.println("APP: ROLLBACK BY JDBC ERROR;"); + txn.rollback(); + } catch (NotEnoughException e) { + System.out.printf("APP: ROLLBACK BY LOGIC; %s", e.getMessage()); + txn.rollback(); + } + return resultObject; + } + + public Function createPlayers(List players) throws JDBCException { + return session -> { + Integer addedPlayerAmount = 0; + for (PlayerBean player: players) { + session.persist(player); + addedPlayerAmount ++; + } + System.out.printf("APP: createPlayers() --> %d\n", addedPlayerAmount); + return addedPlayerAmount; + }; + } + + public Function buyGoods(String sellId, String buyId, Integer amount, Integer price) throws JDBCException { + return session -> { + PlayerBean sellPlayer = session.get(PlayerBean.class, sellId); + PlayerBean buyPlayer = session.get(PlayerBean.class, buyId); + + if (buyPlayer == null || sellPlayer == null) { + throw new NotEnoughException("sell or buy player not exist"); + } + + if (buyPlayer.getCoins() < price || sellPlayer.getGoods() < amount) { + throw new NotEnoughException("coins or goods not enough, rollback"); + } + + buyPlayer.setGoods(buyPlayer.getGoods() + amount); + buyPlayer.setCoins(buyPlayer.getCoins() - price); + session.persist(buyPlayer); + + sellPlayer.setGoods(sellPlayer.getGoods() - amount); + sellPlayer.setCoins(sellPlayer.getCoins() + price); + session.persist(sellPlayer); + + System.out.printf("APP: buyGoods --> sell: %s, buy: %s, amount: %d, price: %d\n", sellId, buyId, amount, price); + return 0; + }; + } + + public Function getPlayerByID(String id) throws JDBCException { + return session -> session.get(PlayerBean.class, id); + } + + public Function printPlayers(Integer limit) throws JDBCException { + return session -> { + NativeQuery limitQuery = session.createNativeQuery("SELECT * FROM player_hibernate LIMIT :limit", PlayerBean.class); + limitQuery.setParameter("limit", limit); + List players = limitQuery.getResultList(); + + for (PlayerBean player: players) { + System.out.println("\n[printPlayers]:\n" + player); + } + return 0; + }; + } + + public Function countPlayers() throws JDBCException { + return session -> { + Query countQuery = session.createQuery("SELECT count(player_hibernate) FROM PlayerBean player_hibernate", Long.class); + return countQuery.getSingleResult(); + }; + } + } + + public static void main(String[] args) { + // 1. Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory + = new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(PlayerBean.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + // 2. And then, create DAO to manager your data. + PlayerDAO playerDAO = new PlayerDAO(); + + // 3. Run some simple example. + + // Create a player who has 1 coin and 1 goods. + playerDAO.runTransaction(session, playerDAO.createPlayers(Collections.singletonList( + new PlayerBean("test", 1, 1)))); + + // Get a player. + PlayerBean testPlayer = (PlayerBean)playerDAO.runTransaction(session, playerDAO.getPlayerByID("test")); + System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n", + testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods()); + + // Count players amount. + Long count = (Long)playerDAO.runTransaction(session, playerDAO.countPlayers()); + System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count); + + // Print 3 players. + playerDAO.runTransaction(session, playerDAO.printPlayers(3)); + + // 4. Getting further. + + // Player 1: id is "1", has only 100 coins. + // Player 2: id is "2", has 114514 coins, and 20 goods. + PlayerBean player1 = new PlayerBean("1", 100, 0); + PlayerBean player2 = new PlayerBean("2", 114514, 20); + + // Create two players "by hand", using the INSERT statement on the backend. + int addedCount = (Integer)playerDAO.runTransaction(session, + playerDAO.createPlayers(Arrays.asList(player1, player2))); + System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount); + + // Player 1 wants to buy 10 goods from player 2. + // It will cost 500 coins, but player 1 can't afford it. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail"); + Integer updatedCount = (Integer)playerDAO.runTransaction(session, + playerDAO.buyGoods(player2.getId(), player1.getId(), 10, 500)); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + + // So player 1 have to reduce his incoming quantity to two. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success"); + updatedCount = (Integer)playerDAO.runTransaction(session, + playerDAO.buyGoods(player2.getId(), player1.getId(), 2, 100)); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + } finally { + sessionFactory.close(); + } + } +} +``` + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Table initialization + +No need to initialize tables manually. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, modify the `hibernate.connection.url`, `hibernate.connection.username`, `hibernate.connection.password` in `hibernate.cfg.xml`. + +```xml + + + + + + + com.mysql.cj.jdbc.Driver + org.hibernate.dialect.TiDBDialect + jdbc:mysql://localhost:4000/test + root + + false + + + create-drop + + + true + true + + +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the parameters as follows: + +```xml + + + + + + + com.mysql.cj.jdbc.Driver + org.hibernate.dialect.TiDBDialect + jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3 + 2aEp24QWEDLqRFs.root + 123456 + false + + + create-drop + + + true + true + + +``` + +### Step 3.3 Run + +To run the code, you can run `make build` and `make run` respectively: + +```shell +make build # this command executes `mvn clean package` +make run # this command executes `java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar` +``` + +Or you can use the native commands: + +```shell +mvn clean package +java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar +``` + +Or run the `make` command directly, which is a combination of `make build` and `make run`. + +## Step 4. Expected output + +[Hibernate Expected Output](https://github.com/pingcap-inc/tidb-example-java/blob/main/Expected-Output.md#plain-java-hibernate) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-java-jdbc.md b/develop/dev-guide-sample-application-java-jdbc.md new file mode 100644 index 0000000000000..02ba214c71e0c --- /dev/null +++ b/develop/dev-guide-sample-application-java-jdbc.md @@ -0,0 +1,591 @@ +--- +title: Build a Simple CRUD App with TiDB and JDBC +summary: Learn how to build a simple CRUD application with TiDB and JDBC. +--- + + + + +# Build a Simple CRUD App with TiDB and JDBC + +This document describes how to use TiDB and JDBC to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Java 8 or a later Java version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB Cluster Using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-java.git +``` + +Change to the `plain-java-jdbc` directory: + +```shell +cd plain-java-jdbc +``` + +The structure of this directory is as follows: + +``` +. +├── Makefile +├── plain-java-jdbc.iml +├── pom.xml +└── src + └── main + ├── java + │ └── com + │ └── pingcap + │ └── JDBCExample.java + └── resources + └── dbinit.sql +``` + +You can find initialization statements for the table creation in `dbinit.sql`: + +```sql +USE test; +DROP TABLE IF EXISTS player; + +CREATE TABLE player ( + `id` VARCHAR(36), + `coins` INTEGER, + `goods` INTEGER, + PRIMARY KEY (`id`) +); +``` + +`JDBCExample.java` is the main body of the `plain-java-jdbc`. TiDB is highly compatible with the MySQL protocol, so you need to initialize a MySQL source instance `MysqlDataSource` to connect to TiDB. Then, you can initialize `PlayerDAO` for object management and use it to read, edit, add, and delete data. + +`PlayerDAO` is a class used to manage data, in which `DAO` means [Data Access Object](https://en.wikipedia.org/wiki/Data_access_object). The class defines a set of data manipulation methods to provide the ability to write data. + +`PlayerBean` is a data entity class that is a mapping for tables. Each property of a `PlayerBean` corresponds to a field in the `player` table. + +```java +package com.pingcap; + +import com.mysql.cj.jdbc.MysqlDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * Main class for the basic JDBC example. + **/ +public class JDBCExample +{ + public static class PlayerBean { + private String id; + private Integer coins; + private Integer goods; + + public PlayerBean() { + } + + public PlayerBean(String id, Integer coins, Integer goods) { + this.id = id; + this.coins = coins; + this.goods = goods; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getCoins() { + return coins; + } + + public void setCoins(Integer coins) { + this.coins = coins; + } + + public Integer getGoods() { + return goods; + } + + public void setGoods(Integer goods) { + this.goods = goods; + } + + @Override + public String toString() { + return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n", + "id", this.id, "coins", this.coins, "goods", this.goods); + } + } + + /** + * Data access object used by 'ExampleDataSource'. + * Example for CURD and bulk insert. + */ + public static class PlayerDAO { + private final MysqlDataSource ds; + private final Random rand = new Random(); + + PlayerDAO(MysqlDataSource ds) { + this.ds = ds; + } + + /** + * Create players by passing in a List of PlayerBean. + * + * @param players Will create players list + * @return The number of create accounts + */ + public int createPlayers(List players){ + int rows = 0; + + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + connection = ds.getConnection(); + preparedStatement = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)"); + } catch (SQLException e) { + System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + e.printStackTrace(); + + return -1; + } + + try { + for (PlayerBean player : players) { + preparedStatement.setString(1, player.getId()); + preparedStatement.setInt(2, player.getCoins()); + preparedStatement.setInt(3, player.getGoods()); + + preparedStatement.execute(); + rows += preparedStatement.getUpdateCount(); + } + } catch (SQLException e) { + System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + e.printStackTrace(); + } finally { + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + System.out.printf("\n[createPlayers]:\n '%s'\n", preparedStatement); + return rows; + } + + /** + * Buy goods and transfer funds between one player and another in one transaction. + * @param sellId Sell player id. + * @param buyId Buy player id. + * @param amount Goods amount, if sell player has not enough goods, the trade will break. + * @param price Price should pay, if buy player has not enough coins, the trade will break. + * + * @return The number of effected players. + */ + public int buyGoods(String sellId, String buyId, Integer amount, Integer price) { + int effectPlayers = 0; + + Connection connection = null; + try { + connection = ds.getConnection(); + } catch (SQLException e) { + System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + e.printStackTrace(); + return effectPlayers; + } + + try { + connection.setAutoCommit(false); + + PreparedStatement playerQuery = connection.prepareStatement("SELECT * FROM player WHERE id=? OR id=? FOR UPDATE"); + playerQuery.setString(1, sellId); + playerQuery.setString(2, buyId); + playerQuery.execute(); + + PlayerBean sellPlayer = null; + PlayerBean buyPlayer = null; + + ResultSet playerQueryResultSet = playerQuery.getResultSet(); + while (playerQueryResultSet.next()) { + PlayerBean player = new PlayerBean( + playerQueryResultSet.getString("id"), + playerQueryResultSet.getInt("coins"), + playerQueryResultSet.getInt("goods") + ); + + System.out.println("\n[buyGoods]:\n 'check goods and coins enough'"); + System.out.println(player); + + if (sellId.equals(player.getId())) { + sellPlayer = player; + } else { + buyPlayer = player; + } + } + + if (sellPlayer == null || buyPlayer == null) { + throw new SQLException("player not exist."); + } + + if (sellPlayer.getGoods().compareTo(amount) < 0) { + throw new SQLException(String.format("sell player %s goods not enough.", sellId)); + } + + if (buyPlayer.getCoins().compareTo(price) < 0) { + throw new SQLException(String.format("buy player %s coins not enough.", buyId)); + } + + PreparedStatement transfer = connection.prepareStatement("UPDATE player set goods = goods + ?, coins = coins + ? WHERE id=?"); + transfer.setInt(1, -amount); + transfer.setInt(2, price); + transfer.setString(3, sellId); + transfer.execute(); + effectPlayers += transfer.getUpdateCount(); + + transfer.setInt(1, amount); + transfer.setInt(2, -price); + transfer.setString(3, buyId); + transfer.execute(); + effectPlayers += transfer.getUpdateCount(); + + connection.commit(); + + System.out.println("\n[buyGoods]:\n 'trade success'"); + } catch (SQLException e) { + System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + + try { + System.out.println("[buyGoods] Rollback"); + + connection.rollback(); + } catch (SQLException ex) { + // do nothing + } + } finally { + try { + connection.close(); + } catch (SQLException e) { + // do nothing + } + } + + return effectPlayers; + } + + /** + * Get the player info by id. + * + * @param id Player id. + * @return The player of this id. + */ + public PlayerBean getPlayer(String id) { + PlayerBean player = null; + + try (Connection connection = ds.getConnection()) { + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player WHERE id = ?"); + preparedStatement.setString(1, id); + preparedStatement.execute(); + + ResultSet res = preparedStatement.executeQuery(); + if(!res.next()) { + System.out.printf("No players in the table with id %s", id); + } else { + player = new PlayerBean(res.getString("id"), res.getInt("coins"), res.getInt("goods")); + } + } catch (SQLException e) { + System.out.printf("PlayerDAO.getPlayer ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + } + + return player; + } + + /** + * Insert randomized account data (id, coins, goods) using the JDBC fast path for + * bulk inserts. The fastest way to get data into TiDB is using the + * TiDB Lightning(https://docs.pingcap.com/tidb/stable/tidb-lightning-overview). + * However, if you must bulk insert from the application using INSERT SQL, the best + * option is the method shown here. It will require the following: + * + * Add `rewriteBatchedStatements=true` to your JDBC connection settings. + * Setting rewriteBatchedStatements to true now causes CallableStatements + * with batched arguments to be re-written in the form "CALL (...); CALL (...); ..." + * to send the batch in as few client/server round trips as possible. + * https://dev.mysql.com/doc/relnotes/connector-j/5.1/en/news-5-1-3.html + * + * You can see the `rewriteBatchedStatements` param effect logic at + * implement function: `com.mysql.cj.jdbc.StatementImpl.executeBatchUsingMultiQueries` + * + * @param total Add players amount. + * @param batchSize Bulk insert size for per batch. + * + * @return The number of new accounts inserted. + */ + public int bulkInsertRandomPlayers(Integer total, Integer batchSize) { + int totalNewPlayers = 0; + + try (Connection connection = ds.getConnection()) { + // We're managing the commit lifecycle ourselves, so we can + // control the size of our batch inserts. + connection.setAutoCommit(false); + + // In this example we are adding 500 rows to the database, + // but it could be any number. What's important is that + // the batch size is 128. + try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)")) { + for (int i=0; i<=(total/batchSize);i++) { + for (int j=0; j %s row(s) updated in this batch\n", count.length); + } + connection.commit(); + } catch (SQLException e) { + System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + } + } catch (SQLException e) { + System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + } + return totalNewPlayers; + } + + + /** + * Print a subset of players from the data store by limit. + * + * @param limit Print max size. + */ + public void printPlayers(Integer limit) { + try (Connection connection = ds.getConnection()) { + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player LIMIT ?"); + preparedStatement.setInt(1, limit); + preparedStatement.execute(); + + ResultSet res = preparedStatement.executeQuery(); + while (!res.next()) { + PlayerBean player = new PlayerBean(res.getString("id"), + res.getInt("coins"), res.getInt("goods")); + System.out.println("\n[printPlayers]:\n" + player); + } + } catch (SQLException e) { + System.out.printf("PlayerDAO.printPlayers ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + } + } + + + /** + * Count players from the data store. + * + * @return All players count + */ + public int countPlayers() { + int count = 0; + + try (Connection connection = ds.getConnection()) { + PreparedStatement preparedStatement = connection.prepareStatement("SELECT count(*) FROM player"); + preparedStatement.execute(); + + ResultSet res = preparedStatement.executeQuery(); + if(res.next()) { + count = res.getInt(1); + } + } catch (SQLException e) { + System.out.printf("PlayerDAO.countPlayers ERROR: { state => %s, cause => %s, message => %s }\n", + e.getSQLState(), e.getCause(), e.getMessage()); + } + + return count; + } + } + + public static void main(String[] args) { + // 1. Configure the example database connection. + + // 1.1 Create a mysql data source instance. + MysqlDataSource mysqlDataSource = new MysqlDataSource(); + + // 1.2 Set server name, port, database name, username and password. + mysqlDataSource.setServerName("localhost"); + mysqlDataSource.setPortNumber(4000); + mysqlDataSource.setDatabaseName("test"); + mysqlDataSource.setUser("root"); + mysqlDataSource.setPassword(""); + + // Or you can use jdbc string instead. + // mysqlDataSource.setURL("jdbc:mysql://{host}:{port}/test?user={user}&password={password}"); + + // 2. And then, create DAO to manager your data. + PlayerDAO dao = new PlayerDAO(mysqlDataSource); + + // 3. Run some simple example. + + // Create a player, has a coin and a goods. + dao.createPlayers(Collections.singletonList(new PlayerBean("test", 1, 1))); + + // Get a player. + PlayerBean testPlayer = dao.getPlayer("test"); + System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n", + testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods()); + + // Create players with bulk inserts, insert 1919 players totally, and per batch for 114 players. + int addedCount = dao.bulkInsertRandomPlayers(1919, 114); + System.out.printf("PlayerDAO.bulkInsertRandomPlayers:\n => %d total inserted players\n", addedCount); + + // Count players amount. + int count = dao.countPlayers(); + System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count); + + // Print 3 players. + dao.printPlayers(3); + + // 4. Getting further. + + // Player 1: id is "1", has only 100 coins. + // Player 2: id is "2", has 114514 coins, and 20 goods. + PlayerBean player1 = new PlayerBean("1", 100, 0); + PlayerBean player2 = new PlayerBean("2", 114514, 20); + + // Create two players "by hand", using the INSERT statement on the backend. + addedCount = dao.createPlayers(Arrays.asList(player1, player2)); + System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount); + + // Player 1 wants to buy 10 goods from player 2. + // It will cost 500 coins, but player 1 can't afford it. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail"); + int updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 10, 500); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + + // So player 1 have to reduce his incoming quantity to two. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success"); + updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 2, 100); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + } +} +``` + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Table initialization + + + +When using JDBC, you need to initialize the database tables manually. If you are using a local cluster, and MySQL client has been installed locally, you can run it directly in the `plain-java-jdbc` directory: + +```shell +make mysql +``` + +Or you can execute the following command: + +```shell +mysql --host 127.0.0.1 --port 4000 -u root + + + +When using JDBC, you need to connect to your cluster and run the statement in the `src/main/resources/dbinit.sql` file to initialize the database tables manually. + + + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, modify the parameters of the host, port, user, and password in `JDBCExample.java`: + +```java +mysqlDataSource.setServerName("localhost"); +mysqlDataSource.setPortNumber(4000); +mysqlDataSource.setDatabaseName("test"); +mysqlDataSource.setUser("root"); +mysqlDataSource.setPassword(""); +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the parameters as follows: + +```java +mysqlDataSource.setServerName("xxx.tidbcloud.com"); +mysqlDataSource.setPortNumber(4000); +mysqlDataSource.setDatabaseName("test"); +mysqlDataSource.setUser("2aEp24QWEDLqRFs.root"); +mysqlDataSource.setPassword("123456"); +mysqlDataSource.setSslMode(PropertyDefinitions.SslMode.VERIFY_IDENTITY.name()); +mysqlDataSource.setEnabledTLSProtocols("TLSv1.2,TLSv1.3"); +``` + +### Step 3.3 Run + +To run the code, you can run `make build` and `make run` respectively: + +```shell +make build # this command executes `mvn clean package` +make run # this command executes `java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar` +``` + +Or you can use the native commands: + +```shell +mvn clean package +java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar +``` + +Or run the `make` command directly, which is a combination of `make build` and `make run`. + +## Step 4. Expected output + +[JDBC Expected Output](https://github.com/pingcap-inc/tidb-example-java/blob/main/Expected-Output.md#plain-java-jdbc) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-java-mybatis.md b/develop/dev-guide-sample-application-java-mybatis.md new file mode 100644 index 0000000000000..36290ccb29789 --- /dev/null +++ b/develop/dev-guide-sample-application-java-mybatis.md @@ -0,0 +1,741 @@ +--- +title: Build a Simple CRUD App with TiDB and Mybatis +summary: Learn how to build a simple CRUD application with TiDB and Mybatis. +--- + + + + +# Build a Simple CRUD App with TiDB and Mybatis + +This document describes how to use TiDB and Mybatis to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Java 8 or a later Java version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB Cluster Using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-java.git +``` + +Compared with [Mybatis](https://mybatis.org/mybatis-3/index.html), the JDBC implementation might be not a best practice, because you need to write error handling logic manually and cannot reuse code easily, which makes your code slightly redundant. + +Mybatis is a popular open-source Java class persistence framework. The following uses [MyBatis Generator](https://mybatis.org/generator/quickstart.html) as a Maven plugin to generate the persistence layer code. + +Change to the `plain-java-mybatis` directory: + +```shell +cd plain-java-mybatis +``` + +The structure of this directory is as follows: + +``` +. +├── Makefile +├── pom.xml +└── src + └── main + ├── java + │   └── com + │   └── pingcap + │   ├── MybatisExample.java + │   ├── dao + │   │   └── PlayerDAO.java + │   └── model + │   ├── Player.java + │   ├── PlayerMapper.java + │   └── PlayerMapperEx.java + └── resources + ├── dbinit.sql + ├── log4j.properties + ├── mapper + │   ├── PlayerMapper.xml + │   └── PlayerMapperEx.xml + ├── mybatis-config.xml + └── mybatis-generator.xml +``` + +The automatically generated files are: + +- `src/main/java/com/pingcap/model/Player.java`: The `Player` entity class. +- `src/main/java/com/pingcap/model/PlayerMapper.java`: The interface of `PlayerMapper`. +- `src/main/resources/mapper/PlayerMapper.xml`: The XML mapping of `Player`. Mybatis uses this configuration to automatically generate the implementation class of the `PlayerMapper` interface. + +The strategy for generating these files is written in `mybatis-generator.xml`, which is the configuration file for [Mybatis Generator](https://mybatis.org/generator/quickstart.html). There are comments in the following configuration file to describe how to use it. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +`mybatis-generator.xml` is included in `pom.xml` as the configuration of `mybatis-generator-maven-plugin`. + +```xml + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.4.1 + + src/main/resources/mybatis-generator.xml + true + true + + + + + + mysql + mysql-connector-java + 5.1.49 + + + +``` + +Once included in the Maven plugin, you can delete the old generated files and make new ones using `mvn mybatis-generate`. Or you can use `make gen` to delete the old file and generate a new one at the same time. + +> **Note:** +> +> The property `configuration.overwrite` in `mybatis-generator.xml` only ensures that the generated Java code files are overwritten. But the XML mapping files are still written as appended. Therefore, it is recommended to delete the old file before Mybaits Generator generating a new one. + +`Player.java` is a data entity class file generated using Mybatis Generator, which is a mapping of database tables in the application. Each property of the `Player` class corresponds to a field in the `player` table. + +```java +package com.pingcap.model; + +public class Player { + private String id; + + private Integer coins; + + private Integer goods; + + public Player(String id, Integer coins, Integer goods) { + this.id = id; + this.coins = coins; + this.goods = goods; + } + + public Player() { + super(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getCoins() { + return coins; + } + + public void setCoins(Integer coins) { + this.coins = coins; + } + + public Integer getGoods() { + return goods; + } + + public void setGoods(Integer goods) { + this.goods = goods; + } +} +``` + +`PlayerMapper.java` is a mapping interface file generated using Mybatis Generator. This file only defines the interface, and the implementation classes of interface are automatically generated using XML or annotations. + +```java +package com.pingcap.model; + +import com.pingcap.model.Player; + +public interface PlayerMapper { + int deleteByPrimaryKey(String id); + + int insert(Player row); + + int insertSelective(Player row); + + Player selectByPrimaryKey(String id); + + int updateByPrimaryKeySelective(Player row); + + int updateByPrimaryKey(Player row); +} +``` + +`PlayerMapper.xml` is a mapping XML file generated using Mybatis Generator. Mybatis uses this to automatically generate the implementation class of the `PlayerMapper` interface. + +```xml + + + + + + + + + + + + id, coins, goods + + + + delete from player + where id = #{id,jdbcType=VARCHAR} + + + insert into player (id, coins, goods + ) + values (#{id,jdbcType=VARCHAR}, #{coins,jdbcType=INTEGER}, #{goods,jdbcType=INTEGER} + ) + + + insert into player + + + id, + + + coins, + + + goods, + + + + + #{id,jdbcType=VARCHAR}, + + + #{coins,jdbcType=INTEGER}, + + + #{goods,jdbcType=INTEGER}, + + + + + update player + + + coins = #{coins,jdbcType=INTEGER}, + + + goods = #{goods,jdbcType=INTEGER}, + + + where id = #{id,jdbcType=VARCHAR} + + + update player + set coins = #{coins,jdbcType=INTEGER}, + goods = #{goods,jdbcType=INTEGER} + where id = #{id,jdbcType=VARCHAR} + + +``` + +Since Mybatis Generator needs to generate the source code from the table definition, the table needs to be created first. To create the table, you can use `dbinit.sql`. + +```sql +USE test; +DROP TABLE IF EXISTS player; + +CREATE TABLE player ( + `id` VARCHAR(36), + `coins` INTEGER, + `goods` INTEGER, + PRIMARY KEY (`id`) +); +``` + +Split the interface `PlayerMapperEx` additionally to extend from `PlayerMapper` and write a matching `PlayerMapperEx.xml` file. Avoid changing `PlayerMapper.java` and `PlayerMapper.xml` directly. This is to avoid overwrite by Mybatis Generator. + +Define the added interface in `PlayerMapperEx.java`: + +```java +package com.pingcap.model; + +import java.util.List; + +public interface PlayerMapperEx extends PlayerMapper { + Player selectByPrimaryKeyWithLock(String id); + + List selectByLimit(Integer limit); + + Integer count(); +} +``` + +Define the mapping rules in `PlayerMapperEx.xml`: + +```xml + + + + + + + + + + + + id, coins, goods + + + + + + + + + +``` + +`PlayerDAO.java` is a class used to manage data, in which `DAO` means [Data Access Object](https://en.wikipedia.org/wiki/Data_access_object). The class defines a set of data manipulation methods for writing data. In it, Mybatis encapsulates a large number of operations such as object mapping and CRUD of basic objects, which greatly simplifies the code. + +```java +package com.pingcap.dao; + +import com.pingcap.model.Player; +import com.pingcap.model.PlayerMapperEx; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; + +import java.util.List; +import java.util.function.Function; + +public class PlayerDAO { + public static class NotEnoughException extends RuntimeException { + public NotEnoughException(String message) { + super(message); + } + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic, so we don't have to duplicate it in + // various places. + public Object runTransaction(SqlSessionFactory sessionFactory, Function fn) { + Object resultObject = null; + SqlSession session = null; + + try { + // open a session with autoCommit is false + session = sessionFactory.openSession(false); + + // get player mapper + PlayerMapperEx playerMapperEx = session.getMapper(PlayerMapperEx.class); + + resultObject = fn.apply(playerMapperEx); + session.commit(); + System.out.println("APP: COMMIT;"); + } catch (Exception e) { + if (e instanceof NotEnoughException) { + System.out.printf("APP: ROLLBACK BY LOGIC; \n%s\n", e.getMessage()); + } else { + System.out.printf("APP: ROLLBACK BY ERROR; \n%s\n", e.getMessage()); + } + + if (session != null) { + session.rollback(); + } + } finally { + if (session != null) { + session.close(); + } + } + + return resultObject; + } + + public Function createPlayers(List players) { + return playerMapperEx -> { + Integer addedPlayerAmount = 0; + for (Player player: players) { + playerMapperEx.insert(player); + addedPlayerAmount ++; + } + System.out.printf("APP: createPlayers() --> %d\n", addedPlayerAmount); + return addedPlayerAmount; + }; + } + + public Function buyGoods(String sellId, String buyId, Integer amount, Integer price) { + return playerMapperEx -> { + Player sellPlayer = playerMapperEx.selectByPrimaryKeyWithLock(sellId); + Player buyPlayer = playerMapperEx.selectByPrimaryKeyWithLock(buyId); + + if (buyPlayer == null || sellPlayer == null) { + throw new NotEnoughException("sell or buy player not exist"); + } + + if (buyPlayer.getCoins() < price || sellPlayer.getGoods() < amount) { + throw new NotEnoughException("coins or goods not enough, rollback"); + } + + int affectRows = 0; + buyPlayer.setGoods(buyPlayer.getGoods() + amount); + buyPlayer.setCoins(buyPlayer.getCoins() - price); + affectRows += playerMapperEx.updateByPrimaryKey(buyPlayer); + + sellPlayer.setGoods(sellPlayer.getGoods() - amount); + sellPlayer.setCoins(sellPlayer.getCoins() + price); + affectRows += playerMapperEx.updateByPrimaryKey(sellPlayer); + + System.out.printf("APP: buyGoods --> sell: %s, buy: %s, amount: %d, price: %d\n", sellId, buyId, amount, price); + return affectRows; + }; + } + + public Function getPlayerByID(String id) { + return playerMapperEx -> playerMapperEx.selectByPrimaryKey(id); + } + + public Function printPlayers(Integer limit) { + return playerMapperEx -> { + List players = playerMapperEx.selectByLimit(limit); + + for (Player player: players) { + System.out.println("\n[printPlayers]:\n" + player); + } + return 0; + }; + } + + public Function countPlayers() { + return PlayerMapperEx::count; + } +} +``` + +`MybatisExample` is the main class of the `plain-java-mybatis` sample application. It defines the entry functions: + +```java +package com.pingcap; + +import com.pingcap.dao.PlayerDAO; +import com.pingcap.model.Player; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; + +public class MybatisExample { + public static void main( String[] args ) throws IOException { + // 1. Create a SqlSessionFactory based on our mybatis-config.xml configuration + // file, which defines how to connect to the database. + InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); + SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); + + // 2. And then, create DAO to manager your data + PlayerDAO playerDAO = new PlayerDAO(); + + // 3. Run some simple examples. + + // Create a player who has 1 coin and 1 goods. + playerDAO.runTransaction(sessionFactory, playerDAO.createPlayers( + Collections.singletonList(new Player("test", 1, 1)))); + + // Get a player. + Player testPlayer = (Player)playerDAO.runTransaction(sessionFactory, playerDAO.getPlayerByID("test")); + System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n", + testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods()); + + // Count players amount. + Integer count = (Integer)playerDAO.runTransaction(sessionFactory, playerDAO.countPlayers()); + System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count); + + // Print 3 players. + playerDAO.runTransaction(sessionFactory, playerDAO.printPlayers(3)); + + // 4. Getting further. + + // Player 1: id is "1", has only 100 coins. + // Player 2: id is "2", has 114514 coins, and 20 goods. + Player player1 = new Player("1", 100, 0); + Player player2 = new Player("2", 114514, 20); + + // Create two players "by hand", using the INSERT statement on the backend. + int addedCount = (Integer)playerDAO.runTransaction(sessionFactory, + playerDAO.createPlayers(Arrays.asList(player1, player2))); + System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount); + + // Player 1 wants to buy 10 goods from player 2. + // It will cost 500 coins, but player 1 cannot afford it. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail"); + Integer updatedCount = (Integer)playerDAO.runTransaction(sessionFactory, + playerDAO.buyGoods(player2.getId(), player1.getId(), 10, 500)); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + + // So player 1 has to reduce the incoming quantity to two. + System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success"); + updatedCount = (Integer)playerDAO.runTransaction(sessionFactory, + playerDAO.buyGoods(player2.getId(), player1.getId(), 2, 100)); + System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); + } +} +``` + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Table initialization + +When using Mybatis, you need to initialize the database tables manually. If you are using a local cluster, and MySQL client has been installed locally, you can run it directly in the `plain-java-mybatis` directory: + +```shell +make prepare +``` + +Or you can execute the following command: + +```shell +mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql +``` + +If you are using a non-local cluster or MySQL client has not been installed, connect to your cluster and run the statement in the `src/main/resources/dbinit.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, modify the `dataSource.url`, `dataSource.username`, `dataSource.password` in `mybatis-config.xml`. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the parameters in `dataSource` node as follows: + +```xml + + + + + ... + + + + + + + + ... + + +``` + +### Step 3.3 Run + +To run the code, you can run `make prepare`, `make gen`, `make build` and `make run` respectively: + +```shell +make prepare +# this command executes : +# - `mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql` +# - `mysql --host 127.0.0.1 --port 4000 -u root -e "TRUNCATE test.player"` + +make gen +# this command executes : +# - `rm -f src/main/java/com/pingcap/model/Player.java` +# - `rm -f src/main/java/com/pingcap/model/PlayerMapper.java` +# - `rm -f src/main/resources/mapper/PlayerMapper.xml` +# - `mvn mybatis-generator:generate` + +make build # this command executes `mvn clean package` +make run # this command executes `java -jar target/plain-java-mybatis-0.0.1-jar-with-dependencies.jar` +``` + +Or you can use the native commands: + +```shell +mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql +mysql --host 127.0.0.1 --port 4000 -u root -e "TRUNCATE test.player" +rm -f src/main/java/com/pingcap/model/Player.java +rm -f src/main/java/com/pingcap/model/PlayerMapper.java +rm -f src/main/resources/mapper/PlayerMapper.xml +mvn mybatis-generator:generate +mvn clean package +java -jar target/plain-java-mybatis-0.0.1-jar-with-dependencies.jar +``` + +Or run the `make` command directly, which is a combination of `make prepare`, `make gen`, `make build` and `make run`. + +## Step 4. Expected output + +[Mybatis Expected Output](https://github.com/pingcap-inc/tidb-example-java/blob/main/Expected-Output.md#plain-java-mybatis) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-java.md b/develop/dev-guide-sample-application-java.md index 2a9d4fa368514..fe482bd741c2c 100644 --- a/develop/dev-guide-sample-application-java.md +++ b/develop/dev-guide-sample-application-java.md @@ -22,9 +22,9 @@ This document describes how to use TiDB and Java to build a simple CRUD applicat The following introduces how to start a TiDB cluster. -**Use a TiDB Cloud Serverless Tier cluster** +**Use a TiDB Serverless cluster** -For detailed steps, see [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). **Use a local cluster** @@ -34,7 +34,7 @@ For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md# -See [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). @@ -1448,7 +1448,7 @@ When using JDBC, you need to connect to your cluster and run the statement in th
-If you are using a TiDB Cloud Serverless Tier cluster, modify the `dataSource.url`, `dataSource.username`, `dataSource.password` in `mybatis-config.xml`. +If you are using a TiDB Serverless cluster, modify the `dataSource.url`, `dataSource.username`, `dataSource.password` in `mybatis-config.xml`. ```xml @@ -1523,7 +1523,7 @@ In this case, you can modify the parameters in `dataSource` node as follows:
-If you are using a TiDB Cloud Serverless Tier cluster, modify the `hibernate.connection.url`, `hibernate.connection.username`, `hibernate.connection.password` in `hibernate.cfg.xml`. +If you are using a TiDB Serverless cluster, modify the `hibernate.connection.url`, `hibernate.connection.username`, `hibernate.connection.password` in `hibernate.cfg.xml`. ```xml @@ -1589,7 +1589,7 @@ In this case, you can modify the parameters as follows:
-If you are using a TiDB Cloud Serverless Tier cluster, modify the parameters of the host, port, user, and password in `JDBCExample.java`: +If you are using a TiDB Serverless cluster, modify the parameters of the host, port, user, and password in `JDBCExample.java`: ```java mysqlDataSource.setServerName("localhost"); diff --git a/develop/dev-guide-sample-application-python-mysql-connector.md b/develop/dev-guide-sample-application-python-mysql-connector.md new file mode 100644 index 0000000000000..e9809293c1951 --- /dev/null +++ b/develop/dev-guide-sample-application-python-mysql-connector.md @@ -0,0 +1,291 @@ +--- +title: Build a Simple CRUD App with TiDB and MySQL Connector/Python +summary: Learn how to build a simple CRUD application with TiDB and MySQL Connector/Python. +aliases: ['/tidb/dev/dev-guide-sample-application-python','/tidb/dev/dev-guide-outdated-for-python-mysql-connector'] +--- + + + + +# Build a Simple CRUD App with TiDB and MySQL Connector/Python + +[MySQL Connector/Python](https://dev.mysql.com/doc/connector-python/en/) is a popular open-source driver for Python. + +This document describes how to use TiDB and MySQL Connector/Python to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Python 3.10 or a later Python version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB cluster using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-python.git +``` + +The following uses MySQL Connector/Python 8.0.31 as an example. Drivers for Python are more convenient to use than other languages, but they do not shield the underlying implementation and require manual management of transactions. If there are not a lot of scenarios where SQL is required, it is recommended to use ORM, which can help reduce the coupling of your program. + +```python +import uuid +from typing import List + +from mysql.connector import connect, MySQLConnection +from mysql.connector.cursor import MySQLCursor + + +def get_connection(autocommit: bool = True) -> MySQLConnection: + connection = connect(host='127.0.0.1', + port=4000, + user='root', + password='', + database='test') + connection.autocommit = autocommit + return connection + + +def create_player(cursor: MySQLCursor, player: tuple) -> None: + cursor.execute("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", player) + + +def get_player(cursor: MySQLCursor, player_id: str) -> tuple: + cursor.execute("SELECT id, coins, goods FROM player WHERE id = %s", (player_id,)) + return cursor.fetchone() + + +def get_players_with_limit(cursor: MySQLCursor, limit: int) -> List[tuple]: + cursor.execute("SELECT id, coins, goods FROM player LIMIT %s", (limit,)) + return cursor.fetchall() + + +def random_player(amount: int) -> List[tuple]: + players = [] + for _ in range(amount): + players.append((str(uuid.uuid4()), 10000, 10000)) + + return players + + +def bulk_create_player(cursor: MySQLCursor, players: List[tuple]) -> None: + cursor.executemany("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", players) + + +def get_count(cursor: MySQLCursor) -> int: + cursor.execute("SELECT count(*) FROM player") + return cursor.fetchone()[0] + + +def trade_check(cursor: MySQLCursor, sell_id: str, buy_id: str, amount: int, price: int) -> bool: + get_player_with_lock_sql = "SELECT coins, goods FROM player WHERE id = %s FOR UPDATE" + + # sell player goods check + cursor.execute(get_player_with_lock_sql, (sell_id,)) + _, sell_goods = cursor.fetchone() + if sell_goods < amount: + print(f'sell player {sell_id} goods not enough') + return False + + # buy player coins check + cursor.execute(get_player_with_lock_sql, (buy_id,)) + buy_coins, _ = cursor.fetchone() + if buy_coins < price: + print(f'buy player {buy_id} coins not enough') + return False + + +def trade_update(cursor: MySQLCursor, sell_id: str, buy_id: str, amount: int, price: int) -> None: + update_player_sql = "UPDATE player set goods = goods + %s, coins = coins + %s WHERE id = %s" + + # deduct the goods of seller, and raise his/her the coins + cursor.execute(update_player_sql, (-amount, price, sell_id)) + # deduct the coins of buyer, and raise his/her the goods + cursor.execute(update_player_sql, (amount, -price, buy_id)) + + +def trade(connection: MySQLConnection, sell_id: str, buy_id: str, amount: int, price: int) -> None: + with connection.cursor() as cursor: + if trade_check(cursor, sell_id, buy_id, amount, price) is False: + connection.rollback() + return + + try: + trade_update(cursor, sell_id, buy_id, amount, price) + except Exception as err: + connection.rollback() + print(f'something went wrong: {err}') + else: + connection.commit() + print("trade success") + + +def simple_example() -> None: + with get_connection(autocommit=True) as connection: + with connection.cursor() as cur: + # create a player, who has a coin and a goods. + create_player(cur, ("test", 1, 1)) + + # get this player, and print it. + test_player = get_player(cur, "test") + print(f'id:{test_player[0]}, coins:{test_player[1]}, goods:{test_player[2]}') + + # create players with bulk inserts. + # insert 1919 players totally, with 114 players per batch. + # each player has a random UUID + player_list = random_player(1919) + for idx in range(0, len(player_list), 114): + bulk_create_player(cur, player_list[idx:idx + 114]) + + # print the number of players + count = get_count(cur) + print(f'number of players: {count}') + + # print 3 players. + three_players = get_players_with_limit(cur, 3) + for player in three_players: + print(f'id:{player[0]}, coins:{player[1]}, goods:{player[2]}') + + +def trade_example() -> None: + with get_connection(autocommit=False) as conn: + with conn.cursor() as cur: + # create two players + # player 1: id is "1", has only 100 coins. + # player 2: id is "2", has 114514 coins, and 20 goods. + create_player(cur, ("1", 100, 0)) + create_player(cur, ("2", 114514, 20)) + conn.commit() + + # player 1 wants to buy 10 goods from player 2. + # it will cost 500 coins, but player 1 cannot afford it. + # so this trade will fail, and nobody will lose their coins or goods + trade(conn, sell_id="2", buy_id="1", amount=10, price=500) + + # then player 1 has to reduce the incoming quantity to 2. + # this trade will be successful + trade(conn, sell_id="2", buy_id="1", amount=2, price=100) + + # let's take a look for player 1 and player 2 currently + with conn.cursor() as cur: + _, player1_coin, player1_goods = get_player(cur, "1") + print(f'id:1, coins:{player1_coin}, goods:{player1_goods}') + _, player2_coin, player2_goods = get_player(cur, "2") + print(f'id:2, coins:{player2_coin}, goods:{player2_goods}') + + +simple_example() +trade_example() +``` + +The driver has a lower level of encapsulation than ORM, so there are a lot of SQL statements in the program. Unlike ORM, there is no data object in drivers, so the `Player` queried by the driver is represented as a tuple. + +For more information about how to use MySQL Connector/Python, refer to [MySQL Connector/Python documentation](https://dev.mysql.com/doc/connector-python/en/). + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Initialize table + +Before running the code, you need to initialize the table manually. If you are using a local TiDB cluster, you can run the following command: + + + +
+ +```shell +mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql +``` + +
+ +
+ +```shell +mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql +``` + +
+ +
+ +If you are not using a local cluster, or have not installed a MySQL client, connect to your cluster using your preferred method (such as Navicat, DBeaver, or other GUI tools) and run the SQL statements in the `player_init.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system).> + +If you are using a TiDB Serverless cluster, change the `get_connection` function in `mysql_connector_python_example.py`: + +```python +def get_connection(autocommit: bool = True) -> MySQLConnection: + connection = connect(host='127.0.0.1', + port=4000, + user='root', + password='', + database='test') + connection.autocommit = autocommit + return connection +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `get_connection` as follows: + +```python +def get_connection(autocommit: bool = True) -> MySQLConnection: + connection = connect( + host="xxx.tidbcloud.com", + port=4000, + user="2aEp24QWEDLqRFs.root", + password="123456", + database="test", + autocommit=autocommit, + ssl_ca='', + ssl_verify_identity=True + ) + connection.autocommit = autocommit + return connection +``` + +### Step 3.3 Run the code + +Before running the code, use the following command to install dependencies: + +```bash +pip3 install -r requirement.txt +``` + +If you need to run the script multiple times, follow the [Table initialization](#step-31-initialize-table) section to initialize the table again before each run. + +```bash +python3 mysql_connector_python_example.py +``` + +## Step 4. Expected output + +[MySQL Connector/Python Expected Output](https://github.com/pingcap-inc/tidb-example-python/blob/main/Expected-Output.md#mysql-connector-python) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-python-mysqlclient.md b/develop/dev-guide-sample-application-python-mysqlclient.md new file mode 100644 index 0000000000000..2e993a66b891e --- /dev/null +++ b/develop/dev-guide-sample-application-python-mysqlclient.md @@ -0,0 +1,292 @@ +--- +title: Build a Simple CRUD App with TiDB and mysqlclient +summary: Learn how to build a simple CRUD application with TiDB and mysqlclient. +--- + + + + +# Build a Simple CRUD App with TiDB and mysqlclient + +[mysqlclient](https://pypi.org/project/mysqlclient/) is a popular open-source driver for Python. + +This document describes how to use TiDB and mysqlclient to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Python 3.10 or a later Python version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB cluster using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-python.git +``` + +The following uses mysqlclient 2.1.1 as an example. Drivers for Python are more convenient to use than other languages, but they do not shield the underlying implementation and require manual management of transactions. If there are not a lot of scenarios where SQL is required, it is recommended to use ORM, which can help reduce the coupling of your program. + +```python +import uuid +from typing import List + +import MySQLdb +from MySQLdb import Connection +from MySQLdb.cursors import Cursor + +def get_connection(autocommit: bool = True) -> MySQLdb.Connection: + return MySQLdb.connect( + host="127.0.0.1", + port=4000, + user="root", + password="", + database="test", + autocommit=autocommit + ) + + +def create_player(cursor: Cursor, player: tuple) -> None: + cursor.execute("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", player) + + +def get_player(cursor: Cursor, player_id: str) -> tuple: + cursor.execute("SELECT id, coins, goods FROM player WHERE id = %s", (player_id,)) + return cursor.fetchone() + + +def get_players_with_limit(cursor: Cursor, limit: int) -> List[tuple]: + cursor.execute("SELECT id, coins, goods FROM player LIMIT %s", (limit,)) + return cursor.fetchall() + + +def random_player(amount: int) -> List[tuple]: + players = [] + for _ in range(amount): + players.append((uuid.uuid4(), 10000, 10000)) + + return players + + +def bulk_create_player(cursor: Cursor, players: List[tuple]) -> None: + cursor.executemany("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", players) + + +def get_count(cursor: Cursor) -> None: + cursor.execute("SELECT count(*) FROM player") + return cursor.fetchone()[0] + + +def trade_check(cursor: Cursor, sell_id: str, buy_id: str, amount: int, price: int) -> bool: + get_player_with_lock_sql = "SELECT coins, goods FROM player WHERE id = %s FOR UPDATE" + + # sell player goods check + cursor.execute(get_player_with_lock_sql, (sell_id,)) + _, sell_goods = cursor.fetchone() + if sell_goods < amount: + print(f'sell player {sell_id} goods not enough') + return False + + # buy player coins check + cursor.execute(get_player_with_lock_sql, (buy_id,)) + buy_coins, _ = cursor.fetchone() + if buy_coins < price: + print(f'buy player {buy_id} coins not enough') + return False + + +def trade_update(cursor: Cursor, sell_id: str, buy_id: str, amount: int, price: int) -> None: + update_player_sql = "UPDATE player set goods = goods + %s, coins = coins + %s WHERE id = %s" + + # deduct the goods of seller, and raise his/her the coins + cursor.execute(update_player_sql, (-amount, price, sell_id)) + # deduct the coins of buyer, and raise his/her the goods + cursor.execute(update_player_sql, (amount, -price, buy_id)) + + +def trade(connection: Connection, sell_id: str, buy_id: str, amount: int, price: int) -> None: + with connection.cursor() as cursor: + if trade_check(cursor, sell_id, buy_id, amount, price) is False: + connection.rollback() + return + + try: + trade_update(cursor, sell_id, buy_id, amount, price) + except Exception as err: + connection.rollback() + print(f'something went wrong: {err}') + else: + connection.commit() + print("trade success") + + +def simple_example() -> None: + with get_connection(autocommit=True) as conn: + with conn.cursor() as cur: + # create a player, who has a coin and a goods. + create_player(cur, ("test", 1, 1)) + + # get this player, and print it. + test_player = get_player(cur, "test") + print(f'id:{test_player[0]}, coins:{test_player[1]}, goods:{test_player[2]}') + + # create players with bulk inserts. + # insert 1919 players totally, with 114 players per batch. + # each player has a random UUID + player_list = random_player(1919) + for idx in range(0, len(player_list), 114): + bulk_create_player(cur, player_list[idx:idx + 114]) + + # print the number of players + count = get_count(cur) + print(f'number of players: {count}') + + # print 3 players. + three_players = get_players_with_limit(cur, 3) + for player in three_players: + print(f'id:{player[0]}, coins:{player[1]}, goods:{player[2]}') + + +def trade_example() -> None: + with get_connection(autocommit=False) as conn: + with conn.cursor() as cur: + # create two players + # player 1: id is "1", has only 100 coins. + # player 2: id is "2", has 114514 coins, and 20 goods. + create_player(cur, ("1", 100, 0)) + create_player(cur, ("2", 114514, 20)) + conn.commit() + + # player 1 wants to buy 10 goods from player 2. + # it will cost 500 coins, but player 1 cannot afford it. + # so this trade will fail, and nobody will lose their coins or goods + trade(conn, sell_id="2", buy_id="1", amount=10, price=500) + + # then player 1 has to reduce the incoming quantity to 2. + # this trade will be successful + trade(conn, sell_id="2", buy_id="1", amount=2, price=100) + + # let's take a look for player 1 and player 2 currently + with conn.cursor() as cur: + _, player1_coin, player1_goods = get_player(cur, "1") + print(f'id:1, coins:{player1_coin}, goods:{player1_goods}') + _, player2_coin, player2_goods = get_player(cur, "2") + print(f'id:2, coins:{player2_coin}, goods:{player2_goods}') + + +simple_example() +trade_example() +``` + +The driver has a lower level of encapsulation than ORM, so there are a lot of SQL statements in the program. Unlike ORM, there is no data object in drivers, so the `Player` queried by the driver is represented as a tuple. + +For more information about how to use mysqlclient, refer to [mysqlclient documentation](https://mysqlclient.readthedocs.io/). + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Initialize table + +Before running the code, you need to initialize the table manually. If you are using a local TiDB cluster, you can run the following command: + + + +
+ +```shell +mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql +``` + +
+ +
+ +```shell +mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql +``` + +
+ +
+ +If you are not using a local cluster, or have not installed a MySQL client, connect to your cluster using your preferred method (such as Navicat, DBeaver, or other GUI tools) and run the SQL statements in the `player_init.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system). + +If you are using a TiDB Serverless cluster, change the `get_connection` function in `mysqlclient_example.py`: + +```python +def get_connection(autocommit: bool = True) -> MySQLdb.Connection: + return MySQLdb.connect( + host="127.0.0.1", + port=4000, + user="root", + password="", + database="test", + autocommit=autocommit + ) +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `get_connection` as follows: + +```python +def get_connection(autocommit: bool = True) -> MySQLdb.Connection: + return MySQLdb.connect( + host="xxx.tidbcloud.com", + port=4000, + user="2aEp24QWEDLqRFs.root", + password="123456", + database="test", + autocommit=autocommit, + ssl_mode="VERIFY_IDENTITY", + ssl={ + "ca": "" + } + ) +``` + +### Step 3.3 Run the code + +Before running the code, use the following command to install dependencies: + +```bash +pip3 install -r requirement.txt +``` + +If you need to run the script multiple times, follow the [Table initialization](#step-31-initialize-table) section to initialize the table again before each run. + +```bash +python3 mysqlclient_example.py +``` + +## Step 4. Expected output + +[mysqlclient Expected Output](https://github.com/pingcap-inc/tidb-example-python/blob/main/Expected-Output.md#mysqlclient) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-python-peewee.md b/develop/dev-guide-sample-application-python-peewee.md new file mode 100644 index 0000000000000..96f116188d14f --- /dev/null +++ b/develop/dev-guide-sample-application-python-peewee.md @@ -0,0 +1,255 @@ +--- +title: Build a Simple CRUD App with TiDB and peewee +summary: Learn how to build a simple CRUD application with TiDB and peewee. +--- + + + + +# Build a Simple CRUD App with TiDB and peewee + +[peewee](http://docs.peewee-orm.com/en/latest/) is a popular open-source ORM library for Python. + +This document describes how to use TiDB and peewee to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Python 3.10 or a later Python version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB cluster using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-python.git +``` + +The following uses peewee 3.15.4 as an example. + +```python +import os +import uuid +from typing import List + +from peewee import * + +from playhouse.db_url import connect + +db = connect('mysql://root:@127.0.0.1:4000/test') + + +class Player(Model): + id = CharField(max_length=36, primary_key=True) + coins = IntegerField() + goods = IntegerField() + + class Meta: + database = db + table_name = "player" + + +def random_player(amount: int) -> List[Player]: + players = [] + for _ in range(amount): + players.append(Player(id=uuid.uuid4(), coins=10000, goods=10000)) + + return players + + +def simple_example() -> None: + # create a player, who has a coin and a goods. + Player.create(id="test", coins=1, goods=1) + + # get this player, and print it. + test_player = Player.select().where(Player.id == "test").get() + print(f'id:{test_player.id}, coins:{test_player.coins}, goods:{test_player.goods}') + + # create players with bulk inserts. + # insert 1919 players totally, with 114 players per batch. + # each player has a random UUID + player_list = random_player(1919) + Player.bulk_create(player_list, 114) + + # print the number of players + count = Player.select().count() + print(f'number of players: {count}') + + # print 3 players. + three_players = Player.select().limit(3) + for player in three_players: + print(f'id:{player.id}, coins:{player.coins}, goods:{player.goods}') + + +def trade_check(sell_id: str, buy_id: str, amount: int, price: int) -> bool: + sell_goods = Player.select(Player.goods).where(Player.id == sell_id).get().goods + if sell_goods < amount: + print(f'sell player {sell_id} goods not enough') + return False + + buy_coins = Player.select(Player.coins).where(Player.id == buy_id).get().coins + if buy_coins < price: + print(f'buy player {buy_id} coins not enough') + return False + + return True + + +def trade(sell_id: str, buy_id: str, amount: int, price: int) -> None: + with db.atomic() as txn: + try: + if trade_check(sell_id, buy_id, amount, price) is False: + txn.rollback() + return + + # deduct the goods of seller, and raise his/her the coins + Player.update(goods=Player.goods - amount, coins=Player.coins + price).where(Player.id == sell_id).execute() + # deduct the coins of buyer, and raise his/her the goods + Player.update(goods=Player.goods + amount, coins=Player.coins - price).where(Player.id == buy_id).execute() + + except Exception as err: + txn.rollback() + print(f'something went wrong: {err}') + else: + txn.commit() + print("trade success") + + +def trade_example() -> None: + # create two players + # player 1: id is "1", has only 100 coins. + # player 2: id is "2", has 114514 coins, and 20 goods. + Player.create(id="1", coins=100, goods=0) + Player.create(id="2", coins=114514, goods=20) + + # player 1 wants to buy 10 goods from player 2. + # it will cost 500 coins, but player 1 cannot afford it. + # so this trade will fail, and nobody will lose their coins or goods + trade(sell_id="2", buy_id="1", amount=10, price=500) + + # then player 1 has to reduce the incoming quantity to 2. + # this trade will be successful + trade(sell_id="2", buy_id="1", amount=2, price=100) + + # let's take a look for player 1 and player 2 currently + after_trade_players = Player.select().where(Player.id.in_(["1", "2"])) + for player in after_trade_players: + print(f'id:{player.id}, coins:{player.coins}, goods:{player.goods}') + + +db.connect() + +# recreate the player table +db.drop_tables([Player]) +db.create_tables([Player]) + +simple_example() +trade_example() +``` + +Compared with using drivers directly, peewee provides an abstraction for the specific details of different databases when you create a database connection. In addition, peewee encapsulates some operations such as session management and CRUD of basic objects, which greatly simplifies the code. + +The `Player` class is a mapping of a table to attributes in the application. Each attribute of `Player` corresponds to a field in the `player` table. To provide SQLAlchemy with more information, the attribute is defined as `id = Column(String(36), primary_key=True)` to indicate the field type and its additional attributes. For example, `id = Column(String(36), primary_key=True)` indicates that the `id` attribute is `String` type, the corresponding field in database is `VARCHAR` type, the length is `36`, and it is a primary key. + +For more information about how to use peewee, refer to [peewee documentation](http://docs.peewee-orm.com/en/latest/). + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Initialize table + +Before running the code, you need to initialize the table manually. If you are using a local TiDB cluster, you can run the following command: + + + +
+ +```shell +mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql +``` + +
+ +
+ +```shell +mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql +``` + +
+ +
+ +If you are not using a local cluster, or have not installed a MySQL client, connect to your cluster using your preferred method (such as Navicat, DBeaver, or other GUI tools) and run the SQL statements in the `player_init.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system). + +If you are using a TiDB Serverless cluster, modify the parameters of the `connect` function in `peewee_example.py`: + +```python +db = connect('mysql://root:@127.0.0.1:4000/test') +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `connect` as follows: + +- When peewee uses PyMySQL as the driver: + + ```python + db = connect('mysql://2aEp24QWEDLqRFs.root:123456@xxx.tidbcloud.com:4000/test', + ssl_verify_cert=True, ssl_ca="") + ``` + +- When peewee uses mysqlclient as the driver: + + ```python + db = connect('mysql://2aEp24QWEDLqRFs.root:123456@xxx.tidbcloud.com:4000/test', + ssl_mode="VERIFY_IDENTITY", ssl={"ca": ""}) + ``` + +Because peewee will pass parameters to the driver, you need to pay attention to the usage type of the driver when using peewee. + +### Step 3.3 Run the code + +Before running the code, use the following command to install dependencies: + +```bash +pip3 install -r requirement.txt +``` + +If you need to run the script multiple times, follow the [Table initialization](#step-31-initialize-table) section to initialize the table again before each run. + +```bash +python3 peewee_example.py +``` + +## Step 4. Expected output + +[peewee Expected Output](https://github.com/pingcap-inc/tidb-example-python/blob/main/Expected-Output.md#peewee) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-python-pymysql.md b/develop/dev-guide-sample-application-python-pymysql.md new file mode 100644 index 0000000000000..bac670caf7fe4 --- /dev/null +++ b/develop/dev-guide-sample-application-python-pymysql.md @@ -0,0 +1,287 @@ +--- +title: Build a Simple CRUD App with TiDB and PyMySQL +summary: Learn how to build a simple CRUD application with TiDB and PyMySQL. +--- + + + + +# Build a Simple CRUD App with TiDB and PyMySQL + +[PyMySQL](https://pypi.org/project/PyMySQL/) is a popular open-source driver for Python. + +This document describes how to use TiDB and PyMySQL to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Python 3.10 or a later Python version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB cluster using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-python.git +``` + +The following uses PyMySQL 1.0.2 as an example. Drivers for Python are more convenient to use than other languages, but they do not shield the underlying implementation and require manual management of transactions. If there are not a lot of scenarios where SQL is required, it is recommended to use ORM, which can help reduce the coupling of your program. + +```python +import uuid +from typing import List + +import pymysql.cursors +from pymysql import Connection +from pymysql.cursors import DictCursor + + +def get_connection(autocommit: bool = False) -> Connection: + return pymysql.connect(host='127.0.0.1', + port=4000, + user='root', + password='', + database='test', + cursorclass=DictCursor, + autocommit=autocommit) + + +def create_player(cursor: DictCursor, player: tuple) -> None: + cursor.execute("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", player) + + +def get_player(cursor: DictCursor, player_id: str) -> dict: + cursor.execute("SELECT id, coins, goods FROM player WHERE id = %s", (player_id,)) + return cursor.fetchone() + + +def get_players_with_limit(cursor: DictCursor, limit: int) -> tuple: + cursor.execute("SELECT id, coins, goods FROM player LIMIT %s", (limit,)) + return cursor.fetchall() + + +def random_player(amount: int) -> List[tuple]: + players = [] + for _ in range(amount): + players.append((uuid.uuid4(), 10000, 10000)) + + return players + + +def bulk_create_player(cursor: DictCursor, players: List[tuple]) -> None: + cursor.executemany("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", players) + + +def get_count(cursor: DictCursor) -> int: + cursor.execute("SELECT count(*) as count FROM player") + return cursor.fetchone()['count'] + + +def trade_check(cursor: DictCursor, sell_id: str, buy_id: str, amount: int, price: int) -> bool: + get_player_with_lock_sql = "SELECT coins, goods FROM player WHERE id = %s FOR UPDATE" + + # sell player goods check + cursor.execute(get_player_with_lock_sql, (sell_id,)) + seller = cursor.fetchone() + if seller['goods'] < amount: + print(f'sell player {sell_id} goods not enough') + return False + + # buy player coins check + cursor.execute(get_player_with_lock_sql, (buy_id,)) + buyer = cursor.fetchone() + if buyer['coins'] < price: + print(f'buy player {buy_id} coins not enough') + return False + + +def trade_update(cursor: DictCursor, sell_id: str, buy_id: str, amount: int, price: int) -> None: + update_player_sql = "UPDATE player set goods = goods + %s, coins = coins + %s WHERE id = %s" + + # deduct the goods of seller, and raise his/her the coins + cursor.execute(update_player_sql, (-amount, price, sell_id)) + # deduct the coins of buyer, and raise his/her the goods + cursor.execute(update_player_sql, (amount, -price, buy_id)) + + +def trade(connection: Connection, sell_id: str, buy_id: str, amount: int, price: int) -> None: + with connection.cursor() as cursor: + if trade_check(cursor, sell_id, buy_id, amount, price) is False: + connection.rollback() + return + + try: + trade_update(cursor, sell_id, buy_id, amount, price) + except Exception as err: + connection.rollback() + print(f'something went wrong: {err}') + else: + connection.commit() + print("trade success") + + +def simple_example() -> None: + with get_connection(autocommit=True) as connection: + with connection.cursor() as cur: + # create a player, who has a coin and a goods. + create_player(cur, ("test", 1, 1)) + + # get this player, and print it. + test_player = get_player(cur, "test") + print(test_player) + + # create players with bulk inserts. + # insert 1919 players totally, with 114 players per batch. + # each player has a random UUID + player_list = random_player(1919) + for idx in range(0, len(player_list), 114): + bulk_create_player(cur, player_list[idx:idx + 114]) + + # print the number of players + count = get_count(cur) + print(f'number of players: {count}') + + # print 3 players. + three_players = get_players_with_limit(cur, 3) + for player in three_players: + print(player) + + +def trade_example() -> None: + with get_connection(autocommit=False) as connection: + with connection.cursor() as cur: + # create two players + # player 1: id is "1", has only 100 coins. + # player 2: id is "2", has 114514 coins, and 20 goods. + create_player(cur, ("1", 100, 0)) + create_player(cur, ("2", 114514, 20)) + connection.commit() + + # player 1 wants to buy 10 goods from player 2. + # it will cost 500 coins, but player 1 cannot afford it. + # so this trade will fail, and nobody will lose their coins or goods + trade(connection, sell_id="2", buy_id="1", amount=10, price=500) + + # then player 1 has to reduce the incoming quantity to 2. + # this trade will be successful + trade(connection, sell_id="2", buy_id="1", amount=2, price=100) + + # let's take a look for player 1 and player 2 currently + with connection.cursor() as cur: + print(get_player(cur, "1")) + print(get_player(cur, "2")) + + +simple_example() +trade_example() +``` + +The driver has a lower level of encapsulation than ORM, so there are a lot of SQL statements in the program. Unlike ORM, there is no data object in drivers, so the `Player` queried by the driver is represented as a dictionary. + +For more information about how to use PyMySQL, refer to [PyMySQL documentation](https://pymysql.readthedocs.io/en/latest/). + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Initialize table + +Before running the code, you need to initialize the table manually. If you are using a local TiDB cluster, you can run the following command: + + + +
+ +```shell +mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql +``` + +
+ +
+ +```shell +mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql +``` + +
+ +
+ +If you are not using a local cluster, or have not installed a MySQL client, connect to your cluster using your preferred method (such as Navicat, DBeaver, or other GUI tools) and run the SQL statements in the `player_init.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system). + +If you are using a TiDB Serverless cluster, change the `get_connection` function in `pymysql_example.py`: + +```python +def get_connection(autocommit: bool = False) -> Connection: + return pymysql.connect(host='127.0.0.1', + port=4000, + user='root', + password='', + database='test', + cursorclass=DictCursor, + autocommit=autocommit) +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `get_connection` as follows: + +```python +def get_connection(autocommit: bool = False) -> Connection: + return pymysql.connect(host='xxx.tidbcloud.com', + port=4000, + user='2aEp24QWEDLqRFs.root', + password='123546', + database='test', + cursorclass=DictCursor, + autocommit=autocommit, + ssl_ca='', + ssl_verify_cert=True, + ssl_verify_identity=True) +``` + +### Step 3.3 Run the code + +Before running the code, use the following command to install dependencies: + +```bash +pip3 install -r requirement.txt +``` + +If you need to run the script multiple times, follow the [Table initialization](#step-31-initialize-table) section to initialize the table again before each run. + +```bash +python3 pymysql_example.py +``` + +## Step 4. Expected output + +[PyMySQL Expected Output](https://github.com/pingcap-inc/tidb-example-python/blob/main/Expected-Output.md#PyMySQL) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-python-sqlalchemy.md b/develop/dev-guide-sample-application-python-sqlalchemy.md new file mode 100644 index 0000000000000..7e003b803f6fc --- /dev/null +++ b/develop/dev-guide-sample-application-python-sqlalchemy.md @@ -0,0 +1,249 @@ +--- +title: Build a Simple CRUD App with TiDB and SQLAlchemy +summary: Learn how to build a simple CRUD application with TiDB and SQLAlchemy. +aliases: ['/tidb/dev/dev-guide-outdated-for-sqlalchemy'] +--- + + + + +# Build a Simple CRUD App with TiDB and SQLAlchemy + +[SQLAlchemy](https://www.sqlalchemy.org/) is a popular open-source ORM library for Python. + +This document describes how to use TiDB and SQLAlchemy to build a simple CRUD application. + +> **Note:** +> +> It is recommended to use Python 3.10 or a later Python version. + +## Step 1. Launch your TiDB cluster + + + +The following introduces how to start a TiDB cluster. + +**Use a TiDB Serverless cluster** + +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + +**Use a local cluster** + +For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a TiDB cluster using TiUP](/production-deployment-using-tiup.md). + + + + + +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). + + + +## Step 2. Get the code + +```shell +git clone https://github.com/pingcap-inc/tidb-example-python.git +``` + +The following uses SQLAlchemy 1.44 as an example. + +```python +import uuid +from typing import List + +from sqlalchemy import create_engine, String, Column, Integer, select, func +from sqlalchemy.orm import declarative_base, sessionmaker + +engine = create_engine('mysql://root:@127.0.0.1:4000/test') +Base = declarative_base() +Base.metadata.create_all(engine) +Session = sessionmaker(bind=engine) + + +class Player(Base): + __tablename__ = "player" + + id = Column(String(36), primary_key=True) + coins = Column(Integer) + goods = Column(Integer) + + def __repr__(self): + return f'Player(id={self.id!r}, coins={self.coins!r}, goods={self.goods!r})' + + +def random_player(amount: int) -> List[Player]: + players = [] + for _ in range(amount): + players.append(Player(id=uuid.uuid4(), coins=10000, goods=10000)) + + return players + + +def simple_example() -> None: + with Session() as session: + # create a player, who has a coin and a goods. + session.add(Player(id="test", coins=1, goods=1)) + + # get this player, and print it. + get_test_stmt = select(Player).where(Player.id == "test") + for player in session.scalars(get_test_stmt): + print(player) + + # create players with bulk inserts. + # insert 1919 players totally, with 114 players per batch. + # each player has a random UUID + player_list = random_player(1919) + for idx in range(0, len(player_list), 114): + session.bulk_save_objects(player_list[idx:idx + 114]) + + # print the number of players + count = session.query(func.count(Player.id)).scalar() + print(f'number of players: {count}') + + # print 3 players. + three_players = session.query(Player).limit(3).all() + for player in three_players: + print(player) + + session.commit() + + +def trade_check(session: Session, sell_id: str, buy_id: str, amount: int, price: int) -> bool: + # sell player goods check + sell_player = session.query(Player.goods).filter(Player.id == sell_id).with_for_update().one() + if sell_player.goods < amount: + print(f'sell player {sell_id} goods not enough') + return False + + # buy player coins check + buy_player = session.query(Player.coins).filter(Player.id == buy_id).with_for_update().one() + if buy_player.coins < price: + print(f'buy player {buy_id} coins not enough') + return False + + +def trade(sell_id: str, buy_id: str, amount: int, price: int) -> None: + with Session() as session: + if trade_check(session, sell_id, buy_id, amount, price) is False: + return + + # deduct the goods of seller, and raise his/her the coins + session.query(Player).filter(Player.id == sell_id). \ + update({'goods': Player.goods - amount, 'coins': Player.coins + price}) + # deduct the coins of buyer, and raise his/her the goods + session.query(Player).filter(Player.id == buy_id). \ + update({'goods': Player.goods + amount, 'coins': Player.coins - price}) + + session.commit() + print("trade success") + + +def trade_example() -> None: + with Session() as session: + # create two players + # player 1: id is "1", has only 100 coins. + # player 2: id is "2", has 114514 coins, and 20 goods. + session.add(Player(id="1", coins=100, goods=0)) + session.add(Player(id="2", coins=114514, goods=20)) + session.commit() + + # player 1 wants to buy 10 goods from player 2. + # it will cost 500 coins, but player 1 cannot afford it. + # so this trade will fail, and nobody will lose their coins or goods + trade(sell_id="2", buy_id="1", amount=10, price=500) + + # then player 1 has to reduce the incoming quantity to 2. + # this trade will be successful + trade(sell_id="2", buy_id="1", amount=2, price=100) + + with Session() as session: + traders = session.query(Player).filter(Player.id.in_(("1", "2"))).all() + for player in traders: + print(player) + session.commit() + + +simple_example() +trade_example() +``` + +Compared with using drivers directly, SQLAlchemy provides an abstraction for the specific details of different databases when you create a database connection. In addition, SQLAlchemy encapsulates some operations such as session management and CRUD of basic objects, which greatly simplifies the code. + +The `Player` class is a mapping of a table to attributes in the application. Each attribute of `Player` corresponds to a field in the `player` table. To provide SQLAlchemy with more information, the attribute is defined as `id = Column(String(36), primary_key=True)` to indicate the field type and its additional attributes. For example, `id = Column(String(36), primary_key=True)` indicates that the `id` attribute is `String` type, the corresponding field in database is `VARCHAR` type, the length is `36`, and it is a primary key. + +For more information about how to use SQLAlchemy, refer to [SQLAlchemy documentation](https://www.sqlalchemy.org/). + +## Step 3. Run the code + +The following content introduces how to run the code step by step. + +### Step 3.1 Initialize table + +Before running the code, you need to initialize the table manually. If you are using a local TiDB cluster, you can run the following command: + + + +
+ +```shell +mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql +``` + +
+ +
+ +```shell +mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql +``` + +
+ +
+ +If you are not using a local cluster, or have not installed a MySQL client, connect to your cluster using your preferred method (such as Navicat, DBeaver, or other GUI tools) and run the SQL statements in the `player_init.sql` file. + +### Step 3.2 Modify parameters for TiDB Cloud + +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system). + +If you are using a TiDB Serverless cluster, modify the parameters of the `create_engine` function in `sqlalchemy_example.py`: + +```python +engine = create_engine('mysql://root:@127.0.0.1:4000/test') +``` + +Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: + +- Endpoint: `xxx.tidbcloud.com` +- Port: `4000` +- User: `2aEp24QWEDLqRFs.root` + +In this case, you can modify the `create_engine` as follows: + +```python +engine = create_engine('mysql://2aEp24QWEDLqRFs.root:123456@xxx.tidbcloud.com:4000/test', connect_args={ + "ssl_mode": "VERIFY_IDENTITY", + "ssl": { + "ca": "" + } +}) +``` + +### Step 3.3 Run the code + +Before running the code, use the following command to install dependencies: + +```bash +pip3 install -r requirement.txt +``` + +If you need to run the script multiple times, follow the [Table initialization](#step-31-initialize-table) section to initialize the table again before each run. + +```bash +python3 sqlalchemy_example.py +``` + +## Step 4. Expected output + +[SQLAlchemy Expected Output](https://github.com/pingcap-inc/tidb-example-python/blob/main/Expected-Output.md#SQLAlchemy) \ No newline at end of file diff --git a/develop/dev-guide-sample-application-python.md b/develop/dev-guide-sample-application-python.md index 5beaaa544268a..3f4f76840991e 100644 --- a/develop/dev-guide-sample-application-python.md +++ b/develop/dev-guide-sample-application-python.md @@ -20,9 +20,9 @@ This document describes how to use TiDB and Python to build a simple CRUD applic The following introduces how to start a TiDB cluster. -**Use a TiDB Cloud Serverless Tier cluster** +**Use a TiDB Serverless cluster** -For detailed steps, see [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). **Use a local cluster** @@ -32,7 +32,7 @@ For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md# -See [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). @@ -822,13 +822,13 @@ If you are not using a local cluster, or have not installed a MySQL client, conn ### Step 3.2 Modify parameters for TiDB Cloud -If you are using a TiDB Cloud Serverless Tier cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system). +If you are using a TiDB Serverless cluster, you need to provide your CA root path and replace `` in the following examples with your CA path. To get the CA root path on your system, refer to [Where is the CA root path on my system?](https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters#where-is-the-ca-root-path-on-my-system).
-If you are using a TiDB Cloud Serverless Tier cluster, modify the parameters of the `create_engine` function in `sqlalchemy_example.py`: +If you are using a TiDB Serverless cluster, modify the parameters of the `create_engine` function in `sqlalchemy_example.py`: ```python engine = create_engine('mysql://root:@127.0.0.1:4000/test') @@ -855,7 +855,7 @@ engine = create_engine('mysql://2aEp24QWEDLqRFs.root:123456@xxx.tidbcloud.com:40
-If you are using a TiDB Cloud Serverless Tier cluster, modify the parameters of the `create_engine` function in `sqlalchemy_example.py`: +If you are using a TiDB Serverless cluster, modify the parameters of the `create_engine` function in `sqlalchemy_example.py`: ```python db = connect('mysql://root:@127.0.0.1:4000/test') @@ -889,7 +889,7 @@ Because peewee will pass parameters to the driver, you need to pay attention to
-If you are using a TiDB Cloud Serverless Tier cluster, change the `get_connection` function in `mysqlclient_example.py`: +If you are using a TiDB Serverless cluster, change the `get_connection` function in `mysqlclient_example.py`: ```python def get_connection(autocommit: bool = True) -> MySQLdb.Connection: @@ -931,7 +931,7 @@ def get_connection(autocommit: bool = True) -> MySQLdb.Connection:
-If you are using a TiDB Cloud Serverless Tier cluster, change the `get_connection` function in `pymysql_example.py`: +If you are using a TiDB Serverless cluster, change the `get_connection` function in `pymysql_example.py`: ```python def get_connection(autocommit: bool = False) -> Connection: @@ -970,7 +970,7 @@ def get_connection(autocommit: bool = False) -> Connection:
-If you are using a TiDB Cloud Serverless Tier cluster, change the `get_connection` function in `mysql_connector_python_example.py`: +If you are using a TiDB Serverless cluster, change the `get_connection` function in `mysql_connector_python_example.py`: ```python def get_connection(autocommit: bool = True) -> MySQLConnection: diff --git a/develop/dev-guide-sample-application-spring-boot.md b/develop/dev-guide-sample-application-spring-boot.md index e6c5b95f82faa..543842a1e6e4e 100644 --- a/develop/dev-guide-sample-application-spring-boot.md +++ b/develop/dev-guide-sample-application-spring-boot.md @@ -21,9 +21,9 @@ You can build your own application based on this example. The following introduces how to start a TiDB cluster. -**Use a TiDB Cloud Serverless Tier cluster** +**Use a TiDB Serverless cluster** -For detailed steps, see [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +For detailed steps, see [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). **Use a local cluster** @@ -33,7 +33,7 @@ For detailed steps, see [Deploy a local test cluster](/quick-start-with-tidb.md# -See [Create a Serverless Tier cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster). +See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). @@ -97,7 +97,7 @@ If you want to learn more about the code of this application, refer to [Implemen ### Step 5.1 Change parameters -If you are using a TiDB Cloud Serverless Tier cluster, change the `spring.datasource.url`, `spring.datasource.username`, `spring.datasource.password` parameters in the `application.yml` (located in `src/main/resources`). +If you are using a TiDB Serverless cluster, change the `spring.datasource.url`, `spring.datasource.username`, `spring.datasource.password` parameters in the `application.yml` (located in `src/main/resources`). ```yaml spring: diff --git a/develop/dev-guide-tidb-crud-sql.md b/develop/dev-guide-tidb-crud-sql.md index cdf1495f10d06..9e0ca6c900e99 100644 --- a/develop/dev-guide-tidb-crud-sql.md +++ b/develop/dev-guide-tidb-crud-sql.md @@ -9,7 +9,7 @@ This document briefly introduces how to use TiDB's CURD SQL. ## Before you start -Please make sure you are connected to a TiDB cluster. If not, refer to [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-serverless-tier-cluster) to create a Serverless Tier cluster. +Please make sure you are connected to a TiDB cluster. If not, refer to [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster) to create a TiDB Serverless cluster. ## Explore SQL with TiDB diff --git a/develop/dev-guide-update-data.md b/develop/dev-guide-update-data.md index b0a96d7f84c5d..2dcb69a80ccbb 100644 --- a/develop/dev-guide-update-data.md +++ b/develop/dev-guide-update-data.md @@ -14,7 +14,7 @@ This document describes how to use the following SQL statements to update the da Before reading this document, you need to prepare the following: -- [Build a TiDB Cluster in TiDB Cloud (Serverless Tier)](/develop/dev-guide-build-cluster-in-cloud.md). +- [Build a TiDB Serverless Cluster](/develop/dev-guide-build-cluster-in-cloud.md). - Read [Schema Design Overview](/develop/dev-guide-schema-design-overview.md), [Create a Database](/develop/dev-guide-create-database.md), [Create a Table](/develop/dev-guide-create-table.md), and [Create Secondary Indexes](/develop/dev-guide-create-secondary-indexes.md). - If you want to `UPDATE` data, you need to [insert data](/develop/dev-guide-insert-data.md) first. diff --git a/encryption-at-rest.md b/encryption-at-rest.md index 774d74532f9ca..cbe7a5bff3cce 100644 --- a/encryption-at-rest.md +++ b/encryption-at-rest.md @@ -21,7 +21,7 @@ When a TiDB cluster is deployed, the majority of user data is stored on TiKV and TiKV supports encryption at rest. This feature allows TiKV to transparently encrypt data files using [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) or [SM4](https://en.wikipedia.org/wiki/SM4_(cipher)) in [CTR](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation) mode. To enable encryption at rest, an encryption key must be provided by the user and this key is called master key. TiKV automatically rotates data keys that it used to encrypt actual data files. Manually rotating the master key can be done occasionally. Note that encryption at rest only encrypts data at rest (namely, on disk) and not while data is transferred over network. It is advised to use TLS together with encryption at rest. -Optionally, you can use AWS KMS for both cloud and on-premises deployments. You can also supply the plaintext master key in a file. +Optionally, you can use AWS KMS for both cloud and self-hosted deployments. You can also supply the plaintext master key in a file. TiKV currently does not exclude encryption keys and user data from core dumps. It is advised to disable core dumps for the TiKV process when using encryption at rest. This is not currently handled by TiKV itself. diff --git a/explore-htap.md b/explore-htap.md index 5d32d5cb3d9a2..ec62e5e4be6f4 100644 --- a/explore-htap.md +++ b/explore-htap.md @@ -13,7 +13,7 @@ This guide describes how to explore and use the features of TiDB Hybrid Transact ## Use cases -TiDB HTAP can handle the massive data that increases rapidly, reduce the cost of DevOps, and be deployed in either on-premises or cloud environments easily, which brings the value of data assets in real time. +TiDB HTAP can handle the massive data that increases rapidly, reduce the cost of DevOps, and be deployed in either self-hosted or cloud environments easily, which brings the value of data assets in real time. The following are the typical use cases of HTAP: diff --git a/garbage-collection-configuration.md b/garbage-collection-configuration.md index f2857d6898d57..89e8beaacfcaf 100644 --- a/garbage-collection-configuration.md +++ b/garbage-collection-configuration.md @@ -20,7 +20,7 @@ Garbage collection is configured via the following system variables: > **Note:** > -> This section is only applicable to on-premises TiDB. TiDB Cloud does not have a GC I/O limit by default. +> This section is only applicable to TiDB Self-Hosted. TiDB Cloud does not have a GC I/O limit by default. @@ -58,7 +58,7 @@ Based on the `DISTRIBUTED` GC mode, the mechanism of GC in Compaction Filter use > **Note:** > -> The following examples of modifying TiKV configurations are only applicable to on-premises TiDB. For TiDB Cloud, the mechanism of GC in Compaction Filter is enabled by default. +> The following examples of modifying TiKV configurations are only applicable to TiDB Self-Hosted. For TiDB Cloud, the mechanism of GC in Compaction Filter is enabled by default. diff --git a/information-schema/information-schema-resource-groups.md b/information-schema/information-schema-resource-groups.md index 9c1aaaf20d03b..7e1f4bb544241 100644 --- a/information-schema/information-schema-resource-groups.md +++ b/information-schema/information-schema-resource-groups.md @@ -13,7 +13,7 @@ summary: Learn the `RESOURCE_GROUPS` information_schema table. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/information-schema/information-schema-slow-query.md b/information-schema/information-schema-slow-query.md index 8b72f9716316f..49f4ec8607c0a 100644 --- a/information-schema/information-schema-slow-query.md +++ b/information-schema/information-schema-slow-query.md @@ -11,7 +11,7 @@ The `SLOW_QUERY` table provides the slow query information of the current node, > **Note:** > -> The `SLOW_QUERY` table is unavailable for [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> The `SLOW_QUERY` table is unavailable for [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/quick-start-with-tidb.md b/quick-start-with-tidb.md index de2015994a1fc..be254f80d40c8 100644 --- a/quick-start-with-tidb.md +++ b/quick-start-with-tidb.md @@ -14,7 +14,7 @@ This guide walks you through the quickest way to get started with TiDB. For non- > > The deployment method provided in this guide is **ONLY FOR** quick start, **NOT FOR** production. > -> - To deploy an on-premises production cluster, see [production installation guide](/production-deployment-using-tiup.md). +> - To deploy a self-hosted production cluster, see [production installation guide](/production-deployment-using-tiup.md). > - To deploy TiDB on Kubernetes, see [Get Started with TiDB on Kubernetes](https://docs.pingcap.com/tidb-in-kubernetes/stable/get-started). > - To manage TiDB in the cloud, see [TiDB Cloud Quick Start](https://docs.pingcap.com/tidbcloud/tidb-cloud-quickstart). diff --git a/releases/release-5.2.0.md b/releases/release-5.2.0.md index 91d1c90240dba..ed6f6e0ebe14f 100644 --- a/releases/release-5.2.0.md +++ b/releases/release-5.2.0.md @@ -20,7 +20,7 @@ In v5.2, the key new features and improvements are as follows: - Add the TiFlash I/O traffic limit feature to improve the stability of read and write for TiFlash - TiKV introduces a new flow control mechanism to replace the previous RocksDB write stall mechanism to improve the stability of TiKV flow control - Simplify the operation and maintenance of Data Migration (DM) to reduce the management cost. -- TiCDC supports HTTP protocol OpenAPI to manage TiCDC tasks. It provides a more user-friendly operation method for both Kubernetes and on-premises environments. (Experimental feature) +- TiCDC supports HTTP protocol OpenAPI to manage TiCDC tasks. It provides a more user-friendly operation method for both Kubernetes and self-hosted environments. (Experimental feature) ## Compatibility changes @@ -165,7 +165,7 @@ In v5.2, the key new features and improvements are as follows: ### TiDB data share subscription -TiCDC supports using the HTTP protocol (OpenAPI) to manage TiCDC tasks, which is a more user-friendly operation method for both Kubernetes and on-premises environments. (Experimental feature) +TiCDC supports using the HTTP protocol (OpenAPI) to manage TiCDC tasks, which is a more user-friendly operation method for both Kubernetes and self-hosted environments. (Experimental feature) [#2411](https://github.com/pingcap/tiflow/issues/2411) @@ -210,7 +210,7 @@ Support running the `tiup playground` command on Mac computers with Apple M1 chi - Support completing the garbage collection automatically for the bindings in the "deleted" status [#26206](https://github.com/pingcap/tidb/pull/26206) - Support showing whether a binding is used for query optimization in the result of `EXPLAIN VERBOSE` [#26930](https://github.com/pingcap/tidb/pull/26930) - Add a new status variation `last_plan_binding_update_time` to view the timestamp corresponding to the binding cache in the current TiDB instance [#26340](https://github.com/pingcap/tidb/pull/26340) - - Support reporting an error when starting binding evolution or running `admin evolve bindings` to ban the baseline evolution (currently disabled in the on-premises TiDB version because it is an experimental feature) affecting other features [#26333](https://github.com/pingcap/tidb/pull/26333) + - Support reporting an error when starting binding evolution or running `admin evolve bindings` to ban the baseline evolution (currently disabled in the TiDB Self-Hosted version because it is an experimental feature) affecting other features [#26333](https://github.com/pingcap/tidb/pull/26333) + PD diff --git a/releases/release-6.0.0-dmr.md b/releases/release-6.0.0-dmr.md index 565324f36029e..9b8c8211fc528 100644 --- a/releases/release-6.0.0-dmr.md +++ b/releases/release-6.0.0-dmr.md @@ -41,7 +41,7 @@ Starting from TiDB v6.0.0, TiDB provides two types of releases: - Development Milestone Releases - Development Milestone Releases (DMR) are released approximately every two months. A DMR introduces new features and improvements, but does not accept patch releases. It is not recommended for on-premises users to use DMR in production environments. For example, v6.0.0-DMR is a DMR. + Development Milestone Releases (DMR) are released approximately every two months. A DMR introduces new features and improvements, but does not accept patch releases. It is not recommended for users to use DMR in production environments. For example, v6.0.0-DMR is a DMR. TiDB v6.0.0 is a DMR, and its version is 6.0.0-DMR. @@ -266,7 +266,7 @@ TiDB v6.0.0 is a DMR, and its version is 6.0.0-DMR. - An enterprise-level database management platform, TiDB Enterprise Manager - TiDB Enterprise Manager (TiEM) is an enterprise-level database management platform based on the TiDB database, which aims to help users manage TiDB clusters in on-premises or public cloud environments. + TiDB Enterprise Manager (TiEM) is an enterprise-level database management platform based on the TiDB database, which aims to help users manage TiDB clusters in self-hosted or public cloud environments. TiEM not only provides full lifecycle visual management for TiDB clusters, but also provides one-stop services: parameter management, version upgrades, cluster clone, active-standby cluster switching, data import and export, data replication, and data backup and restore services. TiEM can improve the efficiency of DevOps on TiDB and reduce the DevOps cost for enterprises. diff --git a/releases/release-7.0.0.md b/releases/release-7.0.0.md index cf7323fb7682a..59d030a69654a 100644 --- a/releases/release-7.0.0.md +++ b/releases/release-7.0.0.md @@ -248,7 +248,7 @@ In v7.0.0-DMR, the key new features and improvements are as follows: * [DBeaver](https://dbeaver.io/) v23.0.1 supports TiDB by default [#17396](https://github.com/dbeaver/dbeaver/issues/17396) @[Icemap](https://github.com/Icemap) - Provides an independent TiDB module, icon, and logo. - - The default configuration supports [TiDB Cloud Serverless Tier](https://docs.pingcap.com/tidbcloud/select-cluster-tier#serverless-tier-beta), making it easier to connect to Serverless Tier. + - The default configuration supports [TiDB Serverless](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta), making it easier to connect to TiDB Serverless. - Supports identifying TiDB versions to display or hide foreign key tabs. - Supports visualizing SQL execution plans in `EXPLAIN` results. - Supports highlighting TiDB keywords such as `PESSIMISTIC`, `OPTIMISTIC`, `AUTO_RANDOM`, `PLACEMENT`, `POLICY`, `REORGANIZE`, `EXCHANGE`, `CACHE`, `NONCLUSTERED`, and `CLUSTERED`. diff --git a/sql-statements/sql-statement-alter-resource-group.md b/sql-statements/sql-statement-alter-resource-group.md index f8f8ae3528b08..b025cc0e09a6e 100644 --- a/sql-statements/sql-statement-alter-resource-group.md +++ b/sql-statements/sql-statement-alter-resource-group.md @@ -9,7 +9,7 @@ summary: Learn the usage of ALTER RESOURCE GROUP in TiDB. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-calibrate-resource.md b/sql-statements/sql-statement-calibrate-resource.md index c848b87b3b2c7..f0a13d410a95e 100644 --- a/sql-statements/sql-statement-calibrate-resource.md +++ b/sql-statements/sql-statement-calibrate-resource.md @@ -11,7 +11,7 @@ The `CALIBRATE RESOURCE` statement is used to estimate and output the ['Request > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-create-resource-group.md b/sql-statements/sql-statement-create-resource-group.md index 9beb133fb4408..128de27c81ab5 100644 --- a/sql-statements/sql-statement-create-resource-group.md +++ b/sql-statements/sql-statement-create-resource-group.md @@ -9,7 +9,7 @@ summary: Learn the usage of CREATE RESOURCE GROUP in TiDB. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-drop-resource-group.md b/sql-statements/sql-statement-drop-resource-group.md index 2aa8de47500f6..a89bb9752fb8f 100644 --- a/sql-statements/sql-statement-drop-resource-group.md +++ b/sql-statements/sql-statement-drop-resource-group.md @@ -9,7 +9,7 @@ summary: Learn the usage of DROP RESOURCE GROUP in TiDB. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-flashback-to-timestamp.md b/sql-statements/sql-statement-flashback-to-timestamp.md index 3843499c35fce..c8159cbe17a18 100644 --- a/sql-statements/sql-statement-flashback-to-timestamp.md +++ b/sql-statements/sql-statement-flashback-to-timestamp.md @@ -11,7 +11,7 @@ TiDB v6.4.0 introduces the `FLASHBACK CLUSTER TO TIMESTAMP` syntax. You can use > **Warning:** > -> The `FLASHBACK CLUSTER TO TIMESTAMP` syntax is not applicable to TiDB Cloud [Serverless Tier](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta) clusters. Do not execute this statement on Serverless Tier clusters to avoid unexpected results. +> The `FLASHBACK CLUSTER TO TIMESTAMP` syntax is not applicable to [TiDB Serverless](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta) clusters. Do not execute this statement on TiDB Serverless clusters to avoid unexpected results. diff --git a/sql-statements/sql-statement-load-data.md b/sql-statements/sql-statement-load-data.md index a1ab732bbf675..3a1d84c30bf76 100644 --- a/sql-statements/sql-statement-load-data.md +++ b/sql-statements/sql-statement-load-data.md @@ -21,7 +21,7 @@ In TiDB v7.0.0, the `LOAD DATA` SQL statement supports the following features: > **Note:** > -> This feature is only available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is only available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-set-resource-group.md b/sql-statements/sql-statement-set-resource-group.md index 1275cf6ee1aba..e2c32677fa839 100644 --- a/sql-statements/sql-statement-set-resource-group.md +++ b/sql-statements/sql-statement-set-resource-group.md @@ -11,7 +11,7 @@ summary: An overview of the usage of SET RESOURCE GROUP in the TiDB database. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/sql-statements/sql-statement-show-create-resource-group.md b/sql-statements/sql-statement-show-create-resource-group.md index f6a3173ba7482..eb2d2e61284ea 100644 --- a/sql-statements/sql-statement-show-create-resource-group.md +++ b/sql-statements/sql-statement-show-create-resource-group.md @@ -9,7 +9,7 @@ summary: Learn the usage of SHOW CREATE RESOURCE GROUP in TiDB. > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). diff --git a/statement-summary-tables.md b/statement-summary-tables.md index 2ed5de8469553..e9af158c6c32c 100644 --- a/statement-summary-tables.md +++ b/statement-summary-tables.md @@ -19,7 +19,7 @@ Therefore, starting from v4.0.0-rc.1, TiDB provides system tables in `informatio > **Note:** > -> The following tables are unavailable for [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta): `statements_summary`, `statements_summary_history`, `cluster_statements_summary`, and `cluster_statements_summary_history`. +> The following tables are unavailable for [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta): `statements_summary`, `statements_summary_history`, `cluster_statements_summary`, and `cluster_statements_summary_history`. @@ -200,7 +200,7 @@ To address this issue, TiDB v6.6.0 experimentally introduces the [statement summ -This section is only applicable to on-premises TiDB. For TiDB Cloud, the value of the `tidb_stmt_summary_enable_persistent` parameter is `false` by default and does not support dynamic modification. +This section is only applicable to TiDB Self-Hosted. For TiDB Cloud, the value of the `tidb_stmt_summary_enable_persistent` parameter is `false` by default and does not support dynamic modification. diff --git a/statistics.md b/statistics.md index a6eb59822eb91..3e2c4e8503e95 100644 --- a/statistics.md +++ b/statistics.md @@ -7,7 +7,7 @@ summary: Learn how the statistics collect table-level and column-level informati TiDB uses statistics to decide [which index to choose](/choose-index.md). The `tidb_analyze_version` variable controls the statistics collected by TiDB. Currently, two versions of statistics are supported: `tidb_analyze_version = 1` and `tidb_analyze_version = 2`. -- For on-premises TiDB, the default value of this variable is `1` before v5.1.0. In v5.3.0 and later versions, the default value of this variable is `2`. If your cluster is upgraded from a version earlier than v5.3.0 to v5.3.0 or later, the default value of `tidb_analyze_version` does not change. +- For TiDB Self-Hosted, the default value of this variable is `1` before v5.1.0. In v5.3.0 and later versions, the default value of this variable is `2`. If your cluster is upgraded from a version earlier than v5.3.0 to v5.3.0 or later, the default value of `tidb_analyze_version` does not change. - For TiDB Cloud, the default value of this variable is `1`. > **Note:** diff --git a/system-variables.md b/system-variables.md index 5155dda316e5d..434878745880b 100644 --- a/system-variables.md +++ b/system-variables.md @@ -772,7 +772,7 @@ MPP is a distributed computing framework provided by the TiFlash engine, which a - Scope: SESSION | GLOBAL - Persists to cluster: Yes - Type: Integer -- Default value: `2` for on-premises TiDB and `1` for TiDB Cloud +- Default value: `2` for TiDB Self-Hosted and `1` for TiDB Cloud - Range: `[1, 2]` - Controls how TiDB collects statistics. @@ -1139,7 +1139,7 @@ MPP is a distributed computing framework provided by the TiFlash engine, which a > **Note:** > -> To improve the speed for index creation using this variable, make sure that your TiDB cluster is hosted on AWS and your TiDB node size is at least 8 vCPU. For [Serverless Tier](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta) clusters, this feature is unavailable. +> To improve the speed for index creation using this variable, make sure that your TiDB cluster is hosted on AWS and your TiDB node size is at least 8 vCPU. For [TiDB Serverless](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta) clusters, this feature is unavailable. diff --git a/tidb-resource-control.md b/tidb-resource-control.md index 6deea4927d0d7..7dcc297c3a271 100644 --- a/tidb-resource-control.md +++ b/tidb-resource-control.md @@ -13,7 +13,7 @@ summary: Learn how to use the resource control feature to control and schedule a > **Note:** > -> This feature is not available on [Serverless Tier clusters](/tidb-cloud/select-cluster-tier.md#serverless-tier-beta). +> This feature is not available on [TiDB Serverless clusters](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). @@ -75,7 +75,7 @@ The resource control feature introduces two new global variables. -* TiKV: For on-premises TiDB, you can use the `resource-control.enabled` parameter to control whether to use request scheduling based on resource group quotas. For TiDB Cloud, the value of the `resource-control.enabled` parameter is `true` by default and does not support dynamic modification. +* TiKV: For TiDB Self-Hosted, you can use the `resource-control.enabled` parameter to control whether to use request scheduling based on resource group quotas. For TiDB Cloud, the value of the `resource-control.enabled` parameter is `true` by default and does not support dynamic modification. @@ -195,7 +195,7 @@ SELECT /*+ RESOURCE_GROUP(rg1) */ * FROM t limit 10; SET GLOBAL tidb_enable_resource_control = 'OFF'; ``` -2. For on-premises TiDB, you can use the `resource-control.enabled` parameter to control whether to use request scheduling based on resource group quotas. For TiDB Cloud, the value of the `resource-control.enabled` parameter is `true` by default and does not support dynamic modification. If you need to disable it for TiDB Cloud Dedicated Tier clusters, contact [TiDB Cloud Support](/tidb-cloud/tidb-cloud-support.md). +2. For TiDB Self-Hosted, you can use the `resource-control.enabled` parameter to control whether to use request scheduling based on resource group quotas. For TiDB Cloud, the value of the `resource-control.enabled` parameter is `true` by default and does not support dynamic modification. If you need to disable it for TiDB Dedicated clusters, contact [TiDB Cloud Support](/tidb-cloud/tidb-cloud-support.md). @@ -213,7 +213,7 @@ TiKV also records the request QPS from different resource groups. For more detai > **Note:** > -> This section is only applicable to on-premises TiDB. Currently, TiDB Cloud does not provide resource control metrics. +> This section is only applicable to TiDB Self-Hosted. Currently, TiDB Cloud does not provide resource control metrics. TiDB regularly collects runtime information about resource control and provides visual charts of the metrics in Grafana's **TiDB** > **Resource Control** dashboard. diff --git a/time-to-live.md b/time-to-live.md index ab7f3b7cf5b30..e19b38d3eacdf 100644 --- a/time-to-live.md +++ b/time-to-live.md @@ -159,7 +159,7 @@ The preceding statement allows TTL jobs to be scheduled only between 1:00 and 5: > **Note:** > -> This section is only applicable to on-premises TiDB. Currently, TiDB Cloud does not provide TTL metrics. +> This section is only applicable to TiDB Self-Hosted. Currently, TiDB Cloud does not provide TTL metrics. @@ -252,7 +252,7 @@ Currently, the TTL feature has the following limitations: * A table with the TTL attribute does not support being referenced by other tables as the primary table in a foreign key constraint. * It is not guaranteed that all expired data is deleted immediately. The time when expired data is deleted depends on the scheduling interval and scheduling window of the background cleanup job. * For tables that use [clustered indexes](/clustered-indexes.md), if the primary key is neither an integer nor a binary string type, the TTL job cannot be split into multiple tasks. This will cause the TTL job to be executed sequentially on a single TiDB node. If the table contains a large amount of data, the execution of the TTL job might become slow. -* TTL is not available for [TiDB Cloud Serverless Tier](https://docs.pingcap.com/tidbcloud/select-cluster-tier#serverless-tier-beta). +* TTL is not available for [TiDB Serverless](https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-serverless-beta). ## FAQs