From 8b572d11a66547f9d7a2a3627bbaf6d81b1074f1 Mon Sep 17 00:00:00 2001 From: Cheese Date: Fri, 8 Sep 2023 13:59:14 +0800 Subject: [PATCH] translate: simplify examples for java documents from the docs-cn (#14716) --- develop/dev-guide-insert-data.md | 6 +- develop/dev-guide-playground-gitpod.md | 2 - develop/dev-guide-prepared-statement.md | 6 +- ...guide-sample-application-java-hibernate.md | 519 +++----- .../dev-guide-sample-application-java-jdbc.md | 694 ++++------- ...v-guide-sample-application-java-mybatis.md | 822 ++++--------- ...ide-sample-application-java-spring-boot.md | 1050 +++-------------- 7 files changed, 736 insertions(+), 2363 deletions(-) diff --git a/develop/dev-guide-insert-data.md b/develop/dev-guide-insert-data.md index fe19b543e9524..4d474a79f774d 100644 --- a/develop/dev-guide-insert-data.md +++ b/develop/dev-guide-insert-data.md @@ -115,9 +115,9 @@ jdbc:mysql://127.0.0.1:4000/test?user=root&useConfigs=maxPerformance&useServerPr For complete examples in Java, see: -- [Build a simple CRUD app with TiDB and JDBC](/develop/dev-guide-sample-application-java-jdbc.md#step-2-get-the-code) -- [Build a simple CRUD app with TiDB and Hibernate](/develop/dev-guide-sample-application-java-hibernate.md#step-2-get-the-code) -- [Build the TiDB app using Spring Boot](/develop/dev-guide-sample-application-java-spring-boot.md) +- [Connect to TiDB with JDBC](/develop/dev-guide-sample-application-java-jdbc.md) +- [Connect to TiDB with Hibernate](/develop/dev-guide-sample-application-java-hibernate.md) +- [Connect to TiDB with Spring Boot](/develop/dev-guide-sample-application-java-spring-boot.md) diff --git a/develop/dev-guide-playground-gitpod.md b/develop/dev-guide-playground-gitpod.md index fae77fb1f7f05..3d573b66346f0 100644 --- a/develop/dev-guide-playground-gitpod.md +++ b/develop/dev-guide-playground-gitpod.md @@ -40,8 +40,6 @@ After all these tasks are finished, you will see a page similar to the following ![playground gitpod workspace ready](/media/develop/playground-gitpod-workspace-ready.png) -You can test the API by [sending an HTTP request](/develop/dev-guide-sample-application-java-spring-boot.md#step-6-http-requests). Make sure to replace the `http://localhost:8080` URL with the one you found in the `REMOTE EXPLORER` area. - ## Using custom Gitpod configuration and Docker image ### Customize Gitpod configurations diff --git a/develop/dev-guide-prepared-statement.md b/develop/dev-guide-prepared-statement.md index 30dca748801f6..deac8b0424c94 100644 --- a/develop/dev-guide-prepared-statement.md +++ b/develop/dev-guide-prepared-statement.md @@ -217,9 +217,9 @@ You can also see the [insert rows](/develop/dev-guide-insert-data.md#insert-rows For a complete example in Java, see: -- [Build a simple CRUD app with TiDB and JDBC](/develop/dev-guide-sample-application-java-jdbc.md#step-2-get-the-code) -- [Build a simple CRUD app with TiDB and Hibernate](/develop/dev-guide-sample-application-java-hibernate.md#step-2-get-the-code) -- [Build the TiDB app using Spring Boot](/develop/dev-guide-sample-application-java-spring-boot.md) +- [Connect to TiDB with JDBC](/develop/dev-guide-sample-application-java-jdbc.md) +- [Connect to TiDB with Hibernate](/develop/dev-guide-sample-application-java-hibernate.md) +- [Connect to TiDB with Spring Boot](/develop/dev-guide-sample-application-java-spring-boot.md) diff --git a/develop/dev-guide-sample-application-java-hibernate.md b/develop/dev-guide-sample-application-java-hibernate.md index b5557d4f91a1f..427dd79f68641 100644 --- a/develop/dev-guide-sample-application-java-hibernate.md +++ b/develop/dev-guide-sample-application-java-hibernate.md @@ -1,379 +1,189 @@ --- -title: Build a Simple CRUD App with TiDB and Hibernate -summary: Learn how to build a simple CRUD application with TiDB and Hibernate. +title: Connect to TiDB with Hibernate +summary: Learn how to connect to TiDB using Hibernate. This tutorial gives Java sample code snippets that work with TiDB using Hibernate. --- - - +# Connect to TiDB with Hibernate -# Build a Simple CRUD App with TiDB and Hibernate +TiDB is a MySQL-compatible database, and [Hibernate](https://hibernate.org/orm/) is a popular open-source Java ORM. Starting from version `6.0.0.Beta2`, Hibernate supports TiDB dialect, which fits TiDB features well. -[Hibernate](https://hibernate.org/) is a popular open-source Java ORM, and it supports TiDB dialect starting from `v6.0.0.Beta2`, which fits TiDB features well. +In this tutorial, you can learn how to use TiDB and Hibernate to accomplish the following tasks: -This document describes how to use TiDB and Hibernate to build a simple CRUD application. +- Set up your environment. +- Connect to your TiDB cluster using Hibernate. +- Build and run your application. Optionally, you can find [sample code snippets](#sample-code-snippets) for basic CRUD operations. > **Note:** > -> It is recommended to use Java 8 or a later Java version. +> This tutorial works with TiDB Serverless, TiDB Dedicated, and TiDB Self-Hosted. -## Step 1. Launch your TiDB cluster +## Prerequisites + +To complete this tutorial, you need: + +- **Java Development Kit (JDK) 17** or higher. You can choose [OpenJDK](https://openjdk.org/) or [Oracle JDK](https://www.oracle.com/hk/java/technologies/downloads/) based on your business and personal requirements. +- [Maven](https://maven.apache.org/install.html) **3.8** or higher. +- [Git](https://git-scm.com/downloads). +- A TiDB cluster. -The following introduces how to start a TiDB cluster. +**If you don't have a TiDB cluster, you can create one as follows:** -**Use a TiDB Serverless cluster** +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](/production-deployment-using-tiup.md) to create a local 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** +**If you don't have a TiDB cluster, you can create one as follows:** -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). +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](https://docs.pingcap.com/tidb/stable/quick-start-with-tidb#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](https://docs.pingcap.com/tidb/stable/production-deployment-using-tiup) to create a local cluster. - +## Run the sample app to connect to TiDB -See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). +This section demonstrates how to run the sample application code and connect to TiDB. - +### Step 1: Clone the sample app repository -## Step 2. Get the code +Run the following commands in your terminal window to clone the sample code repository: ```shell -git clone https://github.com/pingcap-inc/tidb-example-java.git +git clone https://github.com/tidb-samples/tidb-java-hibernate-quickstart.git +cd tidb-java-hibernate-quickstart ``` -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. +### Step 2: Configure connection information -The following instructions take `v6.0.0.Beta2` as an example. +Connect to your TiDB cluster depending on the TiDB deployment option you've selected. -Change to the `plain-java-hibernate` directory: + +
-```shell -cd plain-java-hibernate -``` +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -The structure of this directory is as follows: +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -``` -. -├── Makefile -├── plain-java-hibernate.iml -├── pom.xml -└── src - └── main - ├── java - │ └── com - │ └── pingcap - │ └── HibernateExample.java - └── resources - └── hibernate.cfg.xml -``` +3. Ensure the configurations in the connection dialog match your operating environment. -`hibernate.cfg.xml` is the Hibernate configuration file: + - **Endpoint Type** is set to `Public` + - **Connect With** is set to `General` + - **Operating System** matches your environment. -```xml - - - - + > **Tip:** + > + > If your program is running in Windows Subsystem for Linux (WSL), switch to the corresponding Linux distribution. - - com.mysql.cj.jdbc.Driver - org.hibernate.dialect.TiDBDialect - jdbc:mysql://localhost:4000/test - root - - false +4. Click **Create password** to create a random password. - - create-drop + > **Tip:** + > + > If you have created a password before, you can either use the original password or click **Reset password** to generate a new one. - - true - true - - -``` +5. Run the following command to copy `env.sh.example` and rename it to `env.sh`: -`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. + ```shell + cp env.sh.example env.sh + ``` -`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. +6. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: -`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. + ```shell + export TIDB_HOST='{host}' # e.g. gateway01.ap-northeast-1.prod.aws.tidbcloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. xxxxxx.root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='true' + ``` -```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); - } -} + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. -/** - * 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(); - } - } -} -``` + TiDB Serverless requires a secure connection. Therefore, you need to set the value of `USE_SSL` to `true`. -## Step 3. Run the code +7. Save the `env.sh` file. -The following content introduces how to run the code step by step. +
+
-### Step 3.1 Modify parameters for TiDB Cloud +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -If you are using a TiDB Serverless cluster, modify the `hibernate.connection.url`, `hibernate.connection.username`, `hibernate.connection.password` in `hibernate.cfg.xml`. +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -```xml - - - - +3. Click **Allow Access from Anywhere** and then click **Download TiDB cluster CA** to download the CA certificate. - - com.mysql.cj.jdbc.Driver - org.hibernate.dialect.TiDBDialect - jdbc:mysql://localhost:4000/test - root - - false + For more details about how to obtain the connection string, refer to [TiDB Dedicated standard connection](https://docs.pingcap.com/tidbcloud/connect-via-standard-connection). - - create-drop +4. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - - true - true - - -``` + ```shell + cp env.sh.example env.sh + ``` + +5. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: + + ```shell + export TIDB_HOST='{host}' # e.g. tidb.xxxx.clusters.tidb-cloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` + + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. + +6. Save the `env.sh` file. + +
+
+ +1. Run the following command to copy `env.sh.example` and rename it to `env.sh`: + + ```shell + cp env.sh.example env.sh + ``` + +2. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: + + ```shell + export TIDB_HOST='{host}' + export TIDB_PORT='4000' + export TIDB_USER='root' + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` + + Be sure to replace the placeholders `{}` with the connection parameters, and set `USE_SSL` to `false`. If you are running TiDB locally, the default host address is `127.0.0.1`, and the password is empty. + +3. Save the `env.sh` file. + +
+
+ +### Step 3: Run the code and check the result + +1. Execute the following command to run the sample code: + + ```shell + make + ``` + +2. Check the [Expected-Output.txt](https://github.com/tidb-samples/tidb-java-hibernate-quickstart/blob/main/Expected-Output.txt) to see if the output matches. -Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: +## Sample code snippets -- Endpoint: `xxx.tidbcloud.com` -- Port: `4000` -- User: `2aEp24QWEDLqRFs.root` +You can refer to the following sample code snippets to complete your own application development. -In this case, you can modify the parameters as follows: +For complete sample code and how to run it, check out the [tidb-samples/tidb-java-hibernate-quickstart](https://github.com/tidb-samples/tidb-java-hibernate-quickstart) repository. + +### Connect to TiDB + +Edit the Hibernate configuration file `hibernate.cfg.xml`: ```xml @@ -386,9 +196,9 @@ In this case, you can modify the parameters as follows: 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 + ${tidb_jdbc_url} + ${tidb_user} + ${tidb_password} false @@ -401,24 +211,57 @@ In this case, you can modify the parameters as follows: ``` -### Step 3.2 Run +Be sure to replace `${tidb_jdbc_url}`, `${tidb_user}`, and `${tidb_password}` with the actual values of your TiDB cluster. Then, define the following function: -To run the code, you can run `make build` and `make run` respectively: +```java +public SessionFactory getSessionFactory() { + return new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(${your_entity_class}) + .buildSessionFactory(); +} +``` -```shell -make build # this command executes `mvn clean package` -make run # this command executes `java -jar target/plain-java-hibernate-0.0.1-jar-with-dependencies.jar` +When using this function, you need to replace `${your_entity_class}` with your own data entity class. For multiple entity classes, you need to add a `.addAnnotatedClass(${your_entity_class})` statement for each. The preceding function is just one way to configure Hibernate. If you encounter any issues in the configuration or want to learn more about Hibernate, refer to the [Hibernate official documentation](https://hibernate.org/orm/documentation). + +### Insert or update data + +```java +try (Session session = sessionFactory.openSession()) { + session.persist(new PlayerBean("id", 1, 1)); +} ``` -Or you can use the native commands: +For more information, refer to [Insert data](/develop/dev-guide-insert-data.md) and [Update data](/develop/dev-guide-update-data.md). -```shell -mvn clean package -java -jar target/plain-java-hibernate-0.0.1-jar-with-dependencies.jar +### Query data + +```java +try (Session session = sessionFactory.openSession()) { + PlayerBean player = session.get(PlayerBean.class, "id"); + System.out.println(player); +} ``` -Or run the `make` command directly, which is a combination of `make build` and `make run`. +For more information, refer to [Query data](/develop/dev-guide-get-data-from-single-table.md). + +### Delete data + +```java +try (Session session = sessionFactory.openSession()) { + session.remove(new PlayerBean("id", 1, 1)); +} +``` + +For more information, refer to [Delete data](/develop/dev-guide-delete-data.md). + +## Next steps + +- Learn more usage of Hibernate from [the documentation of Hibernate](https://hibernate.org/orm/documentation). +- Learn the best practices for TiDB application development with the chapters in the [Developer guide](/develop/dev-guide-overview.md), such as [Insert data](/develop/dev-guide-insert-data.md), [Update data](/develop/dev-guide-update-data.md), [Delete data](/develop/dev-guide-delete-data.md), [Single table reading](/develop/dev-guide-get-data-from-single-table.md), [Transactions](/develop/dev-guide-transaction-overview.md), and [SQL performance optimization](/develop/dev-guide-optimize-sql-overview.md). +- Learn through the professional [TiDB developer courses](https://www.pingcap.com/education/) and earn [TiDB certifications](https://www.pingcap.com/education/certification/) after passing the exam. +- Learn through the course for Java developers: [Working with TiDB from Java](https://eng.edu.pingcap.com/catalog/info/id:212). -## Step 4. Expected output +## Need help? -[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 +Ask questions on the [Discord](https://discord.gg/vYU9h56kAX), or [create a support ticket](https://support.pingcap.com/). diff --git a/develop/dev-guide-sample-application-java-jdbc.md b/develop/dev-guide-sample-application-java-jdbc.md index 02ba214c71e0c..d3753c652b415 100644 --- a/develop/dev-guide-sample-application-java-jdbc.md +++ b/develop/dev-guide-sample-application-java-jdbc.md @@ -1,591 +1,301 @@ --- -title: Build a Simple CRUD App with TiDB and JDBC -summary: Learn how to build a simple CRUD application with TiDB and JDBC. +title: Connect to TiDB with JDBC +summary: Learn how to connect to TiDB using JDBC. This tutorial gives Java sample code snippets that work with TiDB using JDBC. --- - - +# Connect to TiDB with JDBC -# Build a Simple CRUD App with TiDB and JDBC +TiDB is a MySQL-compatible database, and JDBC (Java Database Connectivity) is the data access API for Java. [MySQL Connector/J](https://dev.mysql.com/downloads/connector/j/) is MySQL's implementation of JDBC. -This document describes how to use TiDB and JDBC to build a simple CRUD application. +In this tutorial, you can learn how to use TiDB and JDBC to accomplish the following tasks: + +- Set up your environment. +- Connect to your TiDB cluster using JDBC. +- Build and run your application. Optionally, you can find [sample code snippets](#sample-code-snippets) for basic CRUD operations. > **Note:** > -> It is recommended to use Java 8 or a later Java version. +> This tutorial works with TiDB Serverless, TiDB Dedicated, and TiDB Self-Hosted. + +## Prerequisites + +To complete this tutorial, you need: -## Step 1. Launch your TiDB cluster +- **Java Development Kit (JDK) 17** or higher. You can choose [OpenJDK](https://openjdk.org/) or [Oracle JDK](https://www.oracle.com/hk/java/technologies/downloads/) based on your business and personal requirements. +- [Maven](https://maven.apache.org/install.html) **3.8** or higher. +- [Git](https://git-scm.com/downloads). +- A TiDB cluster. -The following introduces how to start a TiDB cluster. +**If you don't have a TiDB cluster, you can create one as follows:** -**Use a TiDB Serverless cluster** +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](/production-deployment-using-tiup.md) to create a local 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** +**If you don't have a TiDB cluster, you can create one as follows:** -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). +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](https://docs.pingcap.com/tidb/stable/quick-start-with-tidb#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](https://docs.pingcap.com/tidb/stable/production-deployment-using-tiup) to create a local cluster. - +## Run the sample app to connect to TiDB -See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). +This section demonstrates how to run the sample application code and connect to TiDB. - +### Step 1: Clone the sample app repository -## Step 2. Get the code +Run the following commands in your terminal window to clone the sample code repository: ```shell -git clone https://github.com/pingcap-inc/tidb-example-java.git +git clone https://github.com/tidb-samples/tidb-java-jdbc-quickstart.git +cd tidb-java-jdbc-quickstart ``` -Change to the `plain-java-jdbc` directory: +### Step 2: Configure connection information -```shell -cd plain-java-jdbc -``` +Connect to your TiDB cluster depending on the TiDB deployment option you've selected. -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 -``` +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -You can find initialization statements for the table creation in `dbinit.sql`: +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -```sql -USE test; -DROP TABLE IF EXISTS player; +3. Ensure the configurations in the connection dialog match your operating environment. -CREATE TABLE player ( - `id` VARCHAR(36), - `coins` INTEGER, - `goods` INTEGER, - PRIMARY KEY (`id`) -); -``` + - **Endpoint Type** is set to `Public` + - **Connect With** is set to `General` + - **Operating System** matches your environment. -`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. + > **Tip:** + > + > If your program is running in Windows Subsystem for Linux (WSL), switch to the corresponding Linux distribution. -`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. +4. Click **Create password** to create a random password. -`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. + > **Tip:** + > + > If you have created a password before, you can either use the original password or click **Reset password** to generate a new one. -```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() { - } +5. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - public PlayerBean(String id, Integer coins, Integer goods) { - this.id = id; - this.coins = coins; - this.goods = goods; - } + ```shell + cp env.sh.example env.sh + ``` - public String getId() { - return id; - } +6. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: - public void setId(String id) { - this.id = id; - } + ```shell + export TIDB_HOST='{host}' # e.g. gateway01.ap-northeast-1.prod.aws.tidbcloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. xxxxxx.root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='true' + ``` - public Integer getCoins() { - return coins; - } + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. - public void setCoins(Integer coins) { - this.coins = coins; - } + TiDB Serverless requires a secure connection. Therefore, you need to set the value of `USE_SSL` to `true`. - public Integer getGoods() { - return goods; - } +7. Save the `env.sh` file. - 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); - } - } +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. - /** - * 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(); +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. - PlayerDAO(MysqlDataSource ds) { - this.ds = ds; - } +3. Click **Allow Access from Anywhere** and then click **Download TiDB cluster CA** to download the CA certificate. - /** - * 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; - } + For more details about how to obtain the connection string, refer to [TiDB Dedicated standard connection](https://docs.pingcap.com/tidbcloud/connect-via-standard-connection). - /** - * 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; - } +4. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - /** - * 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; - } + ```shell + cp env.sh.example env.sh + ``` - /** - * 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; - } +5. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: + ```shell + export TIDB_HOST='{host}' # e.g. tidb.xxxx.clusters.tidb-cloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` - /** - * 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()); - } - } + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. +6. Save the `env.sh` file. - /** - * 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. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - // 1.1 Create a mysql data source instance. - MysqlDataSource mysqlDataSource = new MysqlDataSource(); + ```shell + cp env.sh.example env.sh + ``` - // 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(""); +2. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: - // Or you can use jdbc string instead. - // mysqlDataSource.setURL("jdbc:mysql://{host}:{port}/test?user={user}&password={password}"); + ```shell + export TIDB_HOST='{host}' + export TIDB_PORT='4000' + export TIDB_USER='root' + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` - // 2. And then, create DAO to manager your data. - PlayerDAO dao = new PlayerDAO(mysqlDataSource); + Be sure to replace the placeholders `{}` with the connection parameters, and set `USE_SSL` to `false`. If you are running TiDB locally, the default host address is `127.0.0.1`, and the password is empty. - // 3. Run some simple example. +3. Save the `env.sh` file. - // 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()); +### Step 3: Run the code and check the result - // 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); +1. Execute the following command to run the sample code: - // Count players amount. - int count = dao.countPlayers(); - System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count); + ```shell + make + ``` - // Print 3 players. - dao.printPlayers(3); +2. Check the [Expected-Output.txt](https://github.com/tidb-samples/tidb-java-jdbc-quickstart/blob/main/Expected-Output.txt) to see if the output matches. - // 4. Getting further. +## Sample code snippets - // 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); +You can refer to the following sample code snippets to complete your own application development. - // 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); +For complete sample code and how to run it, check out the [tidb-samples/tidb-java-jdbc-quickstart](https://github.com/tidb-samples/tidb-java-jdbc-quickstart) repository. - // 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); +### Connect to TiDB - // 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); +```java +public MysqlDataSource getMysqlDataSource() throws SQLException { + MysqlDataSource mysqlDataSource = new MysqlDataSource(); + + mysqlDataSource.setServerName(${tidb_host}); + mysqlDataSource.setPortNumber(${tidb_port}); + mysqlDataSource.setUser(${tidb_user}); + mysqlDataSource.setPassword(${tidb_password}); + mysqlDataSource.setDatabaseName(${tidb_db_name}); + if (${tidb_use_ssl}) { + mysqlDataSource.setSslMode(PropertyDefinitions.SslMode.VERIFY_IDENTITY.name()); + mysqlDataSource.setEnabledTLSProtocols("TLSv1.2,TLSv1.3"); } + + return mysqlDataSource; } ``` -## Step 3. Run the code +When using this function, you need to replace `${tidb_host}`, `${tidb_port}`, `${tidb_user}`, `${tidb_password}`, and `${tidb_db_name}` with the actual values of your TiDB cluster. -The following content introduces how to run the code step by step. +### Insert data -### 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 +```java +public void createPlayer(PlayerBean player) throws SQLException { + MysqlDataSource mysqlDataSource = getMysqlDataSource(); + try (Connection connection = mysqlDataSource.getConnection()) { + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)"); + preparedStatement.setString(1, player.getId()); + preparedStatement.setInt(2, player.getCoins()); + preparedStatement.setInt(3, player.getGoods()); + + preparedStatement.execute(); + } +} ``` -Or you can execute the following command: +For more information, refer to [Insert data](/develop/dev-guide-insert-data.md). -```shell -mysql --host 127.0.0.1 --port 4000 -u root +```java +public void getPlayer(String id) throws SQLException { + MysqlDataSource mysqlDataSource = getMysqlDataSourceByEnv(); + try (Connection connection = mysqlDataSource.getConnection()) { + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player WHERE id = ?"); + preparedStatement.setString(1, id); + preparedStatement.execute(); + + ResultSet res = preparedStatement.executeQuery(); + if(res.next()) { + PlayerBean player = new PlayerBean(res.getString("id"), res.getInt("coins"), res.getInt("goods")); + System.out.println(player); + } + } +} +``` - +For more information, refer to [Query data](/develop/dev-guide-get-data-from-single-table.md). -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. +### Update data - +```java +public void updatePlayer(String id, int amount, int price) throws SQLException { + MysqlDataSource mysqlDataSource = getMysqlDataSourceByEnv(); + try (Connection connection = mysqlDataSource.getConnection()) { + PreparedStatement transfer = connection.prepareStatement("UPDATE player SET goods = goods + ?, coins = coins + ? WHERE id=?"); + transfer.setInt(1, -amount); + transfer.setInt(2, price); + transfer.setString(3, id); + transfer.execute(); + } +} +``` -### Step 3.2 Modify parameters for TiDB Cloud +For more information, refer to [Update data](/develop/dev-guide-update-data.md). -If you are using a TiDB Serverless cluster, modify the parameters of the host, port, user, and password in `JDBCExample.java`: +### Delete data ```java -mysqlDataSource.setServerName("localhost"); -mysqlDataSource.setPortNumber(4000); -mysqlDataSource.setDatabaseName("test"); -mysqlDataSource.setUser("root"); -mysqlDataSource.setPassword(""); +public void deletePlayer(String id) throws SQLException { + MysqlDataSource mysqlDataSource = getMysqlDataSourceByEnv(); + try (Connection connection = mysqlDataSource.getConnection()) { + PreparedStatement deleteStatement = connection.prepareStatement("DELETE FROM player WHERE id=?"); + deleteStatement.setString(1, id); + deleteStatement.execute(); + } +} ``` -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` +For more information, refer to [Delete data](/develop/dev-guide-delete-data.md). -In this case, you can modify the parameters as follows: +## Useful notes -```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"); -``` +### Using driver or ORM framework? -### Step 3.3 Run +The Java driver provides low-level access to the database, but it requires the developers to: -To run the code, you can run `make build` and `make run` respectively: +- Manually establish and release database connections. +- Manually manage database transactions. +- Manually map data rows to data objects. -```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` -``` +Unless you need to write complex SQL statements, it is recommended to use [ORM](https://en.wikipedia.org/w/index.php?title=Object-relational_mapping) framework for development, such as [Hibernate](/develop/dev-guide-sample-application-java-hibernate.md), [MyBatis](/develop/dev-guide-sample-application-java-mybatis.md), or [Spring Data JPA](/develop/dev-guide-sample-application-java-spring-boot.md). It can help you: -Or you can use the native commands: +- Reduce [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) for managing connections and transactions. +- Manipulate data with data objects instead of a number of SQL statements. -```shell -mvn clean package -java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar -``` +## Next steps -Or run the `make` command directly, which is a combination of `make build` and `make run`. +- Learn more usage of MySQL Connector/J from [the documentation of MySQL Connector/J](https://dev.mysql.com/doc/connector-j/8.1/en/). +- Learn the best practices for TiDB application development with the chapters in the [Developer guide](/develop/dev-guide-overview.md), such as [Insert data](/develop/dev-guide-insert-data.md), [Update data](/develop/dev-guide-update-data.md), [Delete data](/develop/dev-guide-delete-data.md), [Single table reading](/develop/dev-guide-get-data-from-single-table.md), [Transactions](/develop/dev-guide-transaction-overview.md), and [SQL performance optimization](/develop/dev-guide-optimize-sql-overview.md). +- Learn through the professional [TiDB developer courses](https://www.pingcap.com/education/) and earn [TiDB certifications](https://www.pingcap.com/education/certification/) after passing the exam. +- Learn through the course for Java developers: [Working with TiDB from Java](https://eng.edu.pingcap.com/catalog/info/id:212). -## Step 4. Expected output +## Need help? -[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 +Ask questions on the [Discord](https://discord.gg/vYU9h56kAX), or [create a support ticket](https://support.pingcap.com/). diff --git a/develop/dev-guide-sample-application-java-mybatis.md b/develop/dev-guide-sample-application-java-mybatis.md index 625cb2de4cde7..42f06f386681e 100644 --- a/develop/dev-guide-sample-application-java-mybatis.md +++ b/develop/dev-guide-sample-application-java-mybatis.md @@ -1,645 +1,195 @@ --- -title: Build a Simple CRUD App with TiDB and MyBatis -summary: Learn how to build a simple CRUD application with TiDB and MyBatis. +title: Connect to TiDB with MyBatis +summary: Learn how to connect to TiDB using MyBatis. This tutorial gives Java sample code snippets that work with TiDB using MyBatis. --- - - +# Connect to TiDB with MyBatis -# Build a Simple CRUD App with TiDB and MyBatis +TiDB is a MySQL-compatible database, and [MyBatis](https://mybatis.org/mybatis-3/index.html) is a popular open-source Java ORM. -[MyBatis](https://mybatis.org/mybatis-3/index.html) is a popular open-source Java class persistence framework. +In this tutorial, you can learn how to use TiDB and MyBatis to accomplish the following tasks: -This document describes how to use TiDB and MyBatis to build a simple CRUD application. +- Set up your environment. +- Connect to your TiDB cluster using MyBatis. +- Build and run your application. Optionally, you can find [sample code snippets](#sample-code-snippets) for basic CRUD operations. > **Note:** > -> It is recommended to use Java 8 or a later Java version. +> This tutorial works with TiDB Serverless, TiDB Dedicated, and TiDB Self-Hosted. -## Step 1. Launch your TiDB cluster +## Prerequisites - - -The following introduces how to start a TiDB cluster. +To complete this tutorial, you need: -**Use a TiDB Serverless cluster** +- **Java Development Kit (JDK) 17** or higher. You can choose [OpenJDK](https://openjdk.org/) or [Oracle JDK](https://www.oracle.com/hk/java/technologies/downloads/) based on your business and personal requirements. +- [Maven](https://maven.apache.org/install.html) **3.8** or higher. +- [Git](https://git-scm.com/downloads). +- A TiDB 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** +**If you don't have a TiDB cluster, you can create one as follows:** -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). +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](/production-deployment-using-tiup.md) to create a local cluster. - -See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). +**If you don't have a TiDB cluster, you can create one as follows:** - +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](https://docs.pingcap.com/tidb/stable/quick-start-with-tidb#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](https://docs.pingcap.com/tidb/stable/production-deployment-using-tiup) to create a local cluster. -## Step 2. Get the code + -```shell -git clone https://github.com/pingcap-inc/tidb-example-java.git -``` +## Run the sample app to connect to TiDB -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. +This section demonstrates how to run the sample application code and connect to TiDB. -The following uses [MyBatis Generator](https://mybatis.org/generator/quickstart.html) as a Maven plugin to generate the persistence layer code. +### Step 1: Clone the sample app repository -Change to the `plain-java-mybatis` directory: +Run the following commands in your terminal window to clone the sample code repository: ```shell -cd plain-java-mybatis +git clone https://github.com/tidb-samples/tidb-java-mybatis-quickstart.git +cd tidb-java-mybatis-quickstart ``` -The structure of this directory is as follows: +### Step 2: Configure connection information -``` -. -├── 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 -``` +Connect to your TiDB cluster depending on the TiDB deployment option you've selected. -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. +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -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. +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` +3. Ensure the configurations in the connection dialog match your operating environment. -`mybatis-generator.xml` is included in `pom.xml` as the configuration of `mybatis-generator-maven-plugin`. + - **Endpoint Type** is set to `Public` + - **Connect With** is set to `General` + - **Operating System** matches your environment. -```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 - - - -``` + > **Tip:** + > + > If your program is running in Windows Subsystem for Linux (WSL), switch to the corresponding Linux distribution. -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. +4. Click **Create password** to create a random password. -> **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. + > **Tip:** + > + > If you have created a password before, you can either use the original password or click **Reset password** to generate 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. +5. Run the following command to copy `env.sh.example` and rename it to `env.sh`: -```java -package com.pingcap.model; + ```shell + cp env.sh.example env.sh + ``` -public class Player { - private String id; +6. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: - private Integer coins; + ```shell + export TIDB_HOST='{host}' # e.g. gateway01.ap-northeast-1.prod.aws.tidbcloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. xxxxxx.root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='true' + ``` - private Integer goods; + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. - public Player(String id, Integer coins, Integer goods) { - this.id = id; - this.coins = coins; - this.goods = goods; - } + TiDB Serverless requires a secure connection. Therefore, you need to set the value of `USE_SSL` to `true`. - public Player() { - super(); - } +7. Save the `env.sh` file. - public String getId() { - return id; - } + +
- public void setId(String id) { - this.id = id; - } +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. - public Integer getCoins() { - return coins; - } +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. - public void setCoins(Integer coins) { - this.coins = coins; - } +3. Click **Allow Access from Anywhere** and then click **Download TiDB cluster CA** to download the CA certificate. - public Integer getGoods() { - return goods; - } + For more details about how to obtain the connection string, refer to [TiDB Dedicated standard connection](https://docs.pingcap.com/tidbcloud/connect-via-standard-connection). - 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); +4. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - int insertSelective(Player row); + ```shell + cp env.sh.example env.sh + ``` - Player selectByPrimaryKey(String id); +5. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: - int updateByPrimaryKeySelective(Player row); + ```shell + export TIDB_HOST='{host}' # e.g. tidb.xxxx.clusters.tidb-cloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` - int updateByPrimaryKey(Player row); -} -``` + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. -`PlayerMapper.xml` is a mapping XML file generated using MyBatis Generator. MyBatis uses this to automatically generate the implementation class of the `PlayerMapper` interface. +6. Save the `env.sh` file. -```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 - - - - - +
+
- +1. Run the following command to copy `env.sh.example` and rename it to `env.sh`: - -``` + ```shell + cp env.sh.example env.sh + ``` -`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. +2. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: -```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; - } -} -``` + ```shell + export TIDB_HOST='{host}' + export TIDB_PORT='4000' + export TIDB_USER='root' + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` -`MybatisExample` is the main class of the `plain-java-mybatis` sample application. It defines the entry functions: + Be sure to replace the placeholders `{}` with the connection parameters, and set `USE_SSL` to `false`. If you are running TiDB locally, the default host address is `127.0.0.1`, and the password is empty. -```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); - } -} -``` +3. Save the `env.sh` file. -## Step 3. Run the code +
+ -The following content introduces how to run the code step by step. +### Step 3: Run the code and check the result -### Step 3.1 Table initialization +1. Execute the following command to run the sample code: -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 + ``` -```shell -make prepare -``` +2. Check the [Expected-Output.txt](https://github.com/tidb-samples/tidb-java-mybatis-quickstart/blob/main/Expected-Output.txt) to see if the output matches. -Or you can execute the following command: +## Sample code snippets -```shell -mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql -``` +You can refer to the following sample code snippets to complete your own application development. -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. +For complete sample code and how to run it, check out the [tidb-samples/tidb-java-mybatis-quickstart](https://github.com/tidb-samples/tidb-java-mybatis-quickstart) repository. -### Step 3.2 Modify parameters for TiDB Cloud +### Connect to TiDB -If you are using a TiDB Serverless cluster, modify the `dataSource.url`, `dataSource.username`, `dataSource.password` in `mybatis-config.xml`. +Edit the MyBatis configuration file `mybatis-config.xml`: ```xml - - @@ -648,96 +198,120 @@ If you are using a TiDB Serverless cluster, modify the `dataSource.url`, `dataSo - - - - - - - - + + + + - - - + - ``` -Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: +Be sure to replace `${tidb_jdbc_url}`, `${tidb_user}`, and `${tidb_password}` with the actual values of your TiDB cluster. Also, replace `${mapper_location}` with the path of your mapper XML configuration file. For multiple mapper XML configuration files, you need to add a `` tag for each. Then, define the following function: -- Endpoint: `xxx.tidbcloud.com` -- Port: `4000` -- User: `2aEp24QWEDLqRFs.root` +```java +public SqlSessionFactory getSessionFactory() { + InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); + SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); +} +``` + +### Insert data -In this case, you can modify the parameters in `dataSource` node as follows: +Add a node in the mapper XML and add a function with the same name in the interface class configured in the `mapper.namespace` attribute of the XML configuration file: ```xml - + + + + + insert into player (id, coins, goods) + values (#{id,jdbcType=VARCHAR}, #{coins,jdbcType=INTEGER}, #{goods,jdbcType=INTEGER}) + + +``` - +For more information, refer to [Insert data](/develop/dev-guide-insert-data.md). - ... - - - - - - - - ... +### Query data - +Add a node in the mapper XML and add a function with the same name in the interface class configured in the `mapper.namespace` attribute of the XML configuration file. Specifically, if you use `resultMap` as the return type for MyBatis query functions, make sure that the `` node is configured correctly. + +```xml + + + + + + + + + + + + + ``` -### Step 3.3 Run +For more information, refer to [Query data](/develop/dev-guide-get-data-from-single-table.md). -To run the code, you can run `make prepare`, `make gen`, `make build` and `make run` respectively: +### Update data -```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` +Add a node in the mapper XML and add a function with the same name in the interface class configured in the `mapper.namespace` attribute of the XML configuration file: + +```xml + + + + + update player + set coins = #{coins,jdbcType=INTEGER}, + goods = #{goods,jdbcType=INTEGER} + where id = #{id,jdbcType=VARCHAR} + + ``` -Or you can use the native commands: +For more information, refer to [Update data](/develop/dev-guide-update-data.md). -```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 +### Delete data + +Add a node in the mapper XML and add a function with the same name in the interface class configured in the `mapper.namespace` attribute of the XML configuration file: + +```xml + + + + + delete from player + where id = #{id,jdbcType=VARCHAR} + + ``` -Or run the `make` command directly, which is a combination of `make prepare`, `make gen`, `make build` and `make run`. +For more information, refer to [Delete data](/develop/dev-guide-delete-data.md). + +## Next steps + +- Learn more usage of MyBatis from [the documentation of MyBatis](http://www.mybatis.org/mybatis-3/). +- Learn the best practices for TiDB application development with the chapters in the [Developer guide](/develop/dev-guide-overview.md), such as [Insert data](/develop/dev-guide-insert-data.md), [Update data](/develop/dev-guide-update-data.md), [Delete data](/develop/dev-guide-delete-data.md), [Single table reading](/develop/dev-guide-get-data-from-single-table.md), [Transactions](/develop/dev-guide-transaction-overview.md), and [SQL performance optimization](/develop/dev-guide-optimize-sql-overview.md). +- Learn through the professional [TiDB developer courses](https://www.pingcap.com/education/) and earn [TiDB certifications](https://www.pingcap.com/education/certification/) after passing the exam. +- Learn through the course for Java developers: [Working with TiDB from Java](https://eng.edu.pingcap.com/catalog/info/id:212). -## Step 4. Expected output +## Need help? -[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 +Ask questions on the [Discord](https://discord.gg/vYU9h56kAX), or [create a support ticket](https://support.pingcap.com/). diff --git a/develop/dev-guide-sample-application-java-spring-boot.md b/develop/dev-guide-sample-application-java-spring-boot.md index 7ef85cad5e28f..9e224a708fb10 100644 --- a/develop/dev-guide-sample-application-java-spring-boot.md +++ b/develop/dev-guide-sample-application-java-spring-boot.md @@ -1,569 +1,203 @@ --- -title: Build a TiDB App Using Spring Boot -summary: Learn an example of how to build a TiDB application using Spring Boot. +title: Connect to TiDB with Spring Boot +summary: Learn how to connect to TiDB using Spring Boot. This tutorial gives Java sample code snippets that work with TiDB using Spring Boot. aliases: ['/tidbcloud/dev-guide-sample-application-spring-boot','/tidb/dev/dev-guide-sample-application-spring-boot'] --- - +# Connect to TiDB with Spring Boot -# Build a TiDB App Using Spring Boot +TiDB is a MySQL-compatible database, and [Spring](https://spring.io/) is a popular open-source container framework for Java. This document uses [Spring Boot](https://spring.io/projects/spring-boot) as the way to use Spring. -This tutorial shows you how to build a [Spring Boot](https://spring.io/projects/spring-boot) web application using TiDB. The [Spring Data JPA](https://spring.io/projects/spring-data-jpa) module is used as the framework for data access capabilities. You can download the code for this sample application from [GitHub](https://github.com/pingcap-inc/tidb-example-java). +In this tutorial, you can learn how to use TiDB along with [Spring Data JPA](https://spring.io/projects/spring-data-jpa) and [Hibernate](https://hibernate.org/orm/) as the JPA provider to accomplish the following tasks: -This is a sample application for building a RESTful API, which shows a generic **Spring Boot** backend service using **TiDB** as the database. The following process was designed to recreate a real-world scenario: +- Set up your environment. +- Connect to your TiDB cluster using Hibernate and Spring Data JPA. +- Build and run your application. Optionally, you can find [sample code snippets](#sample-code-snippets) for basic CRUD operations. -This is an example of a game where each player has two attributes: `coins` and `goods`. Each player is uniquely identified by an `id` field. Players can trade freely if they have sufficient coins and goods. - -You can build your own application based on this example. +> **Note:** +> +> This tutorial works with TiDB Serverless, TiDB Dedicated, and TiDB Self-Hosted. -## Step 1: Launch your TiDB cluster +## Prerequisites - +To complete this tutorial, you need: -The following introduces how to start a TiDB cluster. +- **Java Development Kit (JDK) 17** or higher. You can choose [OpenJDK](https://openjdk.org/) or [Oracle JDK](https://www.oracle.com/hk/java/technologies/downloads/) based on your business and personal requirements. +- [Maven](https://maven.apache.org/install.html) **3.8** or higher. +- [Git](https://git-scm.com/downloads). +- 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** +**If you don't have a TiDB cluster, you can create one as follows:** -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). +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](/quick-start-with-tidb.md#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](/production-deployment-using-tiup.md) to create a local cluster. - -See [Create a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md#step-1-create-a-tidb-serverless-cluster). - - - -## Step 2: Install JDK - -Download and install the **Java Development Kit** (JDK) on your computer. It is a necessary tool for Java development. **Spring Boot** supports JDK for Java 8 and later versions. However, due to the **Hibernate** version, it is recommended that you use JDK for Java 11 and later versions. - -Both **Oracle JDK** and **OpenJDK** are supported. You can choose at your own discretion. This tutorial uses JDK 17 from **OpenJDK**. - -## Step 3: Install Maven - -This sample application uses **Apache Maven** to manage the application's dependencies. Spring supports Maven 3.3 and later versions. As dependency management software, the latest stable version of **Maven** is recommended. - -To install **Maven** from the command line. - -- macOS: - - {{< copyable "shell-regular" >}} - - ```shell - brew install maven - ``` - -- Debian-based Linux distributions (such as Ubuntu): - - {{< copyable "shell-regular" >}} - - ```shell - apt-get install maven - ``` - -- Red Hat-based Linux distributions (such as Fedora, CentOS): - - - dnf: - - {{< copyable "shell-regular" >}} - - ```shell - dnf install maven - ``` - - - yum: - - {{< copyable "shell-regular" >}} - - ```shell - yum install maven - ``` +**If you don't have a TiDB cluster, you can create one as follows:** -For other installation methods, refer to the [Maven official documentation](https://maven.apache.org/install.html). +- (Recommended) Follow [Creating a TiDB Serverless cluster](/develop/dev-guide-build-cluster-in-cloud.md) to create your own TiDB Cloud cluster. +- Follow [Deploy a local test TiDB cluster](https://docs.pingcap.com/tidb/stable/quick-start-with-tidb#deploy-a-local-test-cluster) or [Deploy a production TiDB cluster](https://docs.pingcap.com/tidb/stable/production-deployment-using-tiup) to create a local cluster. -## Step 4: Get the application code - -Download or clone the [sample code repository](https://github.com/pingcap-inc/tidb-example-java) and navigate to the `spring-jpa-hibernate` directory. - -## Step 5: Run the application + -In this step, the application code is compiled and run, which produces a web application. Hibernate creates a `player_jpa` table within the `test` database. If you make requests using the application's RESTful API, these requests run [database transactions](/develop/dev-guide-transaction-overview.md) on the TiDB cluster. +## Run the sample app to connect to TiDB -If you want to learn more about the code of this application, refer to [Implementation details](#implementation-details). +This section demonstrates how to run the sample application code and connect to TiDB. -### Step 5.1 Change parameters +### Step 1: Clone the sample app repository -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`). +Run the following commands in your terminal window to clone the sample code repository: -```yaml -spring: - datasource: - url: jdbc:mysql://localhost:4000/test - username: root - # password: xxx - driver-class-name: com.mysql.cj.jdbc.Driver - jpa: - show-sql: true - database-platform: org.hibernate.dialect.TiDBDialect - hibernate: - ddl-auto: create-drop +```shell +git clone https://github.com/tidb-samples/tidb-java-springboot-jpa-quickstart.git +cd tidb-java-springboot-jpa-quickstart ``` -Suppose that the password you set is `123456`, and the connection parameters you get from the cluster details page are the following: +### Step 2: Configure connection information -- Endpoint: `xxx.tidbcloud.com` -- Port: `4000` -- User: `2aEp24QWEDLqRFs.root` +Connect to your TiDB cluster depending on the TiDB deployment option you've selected. -Accordingly, the parameters must be set as folows: + +
-```yaml -spring: - datasource: - url: jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3 - username: 2aEp24QWEDLqRFs.root - password: 123456 - driver-class-name: com.mysql.cj.jdbc.Driver - jpa: - show-sql: true - database-platform: org.hibernate.dialect.TiDBDialect - hibernate: - ddl-auto: create-drop -``` +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -### Step 5.2 Run +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -Open a terminal session and make sure you are in the `spring-jpa-hibernate` directory. If you are not already in this directory, navigate to the directory with the following command: +3. Ensure the configurations in the connection dialog match your operating environment. -```shell -cd /tidb-example-java/spring-jpa-hibernate -``` + - **Endpoint Type** is set to `Public` + - **Connect With** is set to `General` + - **Operating System** matches your environment. -#### Build and run with Make (recommended) + > **Tip:** + > + > If your program is running in Windows Subsystem for Linux (WSL), switch to the corresponding Linux distribution. -```shell -make -``` - -#### Build and run manually +4. Click **Create password** to create a random password. -If you prefer to build manually, follow these steps: + > **Tip:** + > + > If you have created a password before, you can either use the original password or click **Reset password** to generate a new one. -1. Clear cache and package: - - {{< copyable "shell-regular" >}} +5. Run the following command to copy `env.sh.example` and rename it to `env.sh`: ```shell - mvn clean package + cp env.sh.example env.sh ``` -2. Run applications with JAR files: - - {{< copyable "shell-regular" >}} +6. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: ```shell - java -jar target/spring-jpa-hibernate-0.0.1.jar + export TIDB_HOST='{host}' # e.g. gateway01.ap-northeast-1.prod.aws.tidbcloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. xxxxxx.root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='true' ``` -### Step 5.3 Output - -The final part of the output should look like the following: - -``` - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ -( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v3.0.1) - -2023-01-05T14:06:54.427+08:00 INFO 22005 --- [ main] com.pingcap.App : Starting App using Java 17.0.2 with PID 22005 (/Users/cheese/IdeaProjects/tidb-example-java/spring-jpa-hibernate/target/classes started by cheese in /Users/cheese/IdeaProjects/tidb-example-java) -2023-01-05T14:06:54.428+08:00 INFO 22005 --- [ main] com.pingcap.App : No active profile set, falling back to 1 default profile: "default" -2023-01-05T14:06:54.642+08:00 INFO 22005 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. -2023-01-05T14:06:54.662+08:00 INFO 22005 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 17 ms. Found 1 JPA repository interfaces. -2023-01-05T14:06:54.830+08:00 INFO 22005 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) -2023-01-05T14:06:54.833+08:00 INFO 22005 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] -2023-01-05T14:06:54.833+08:00 INFO 22005 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.4] -2023-01-05T14:06:54.865+08:00 INFO 22005 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext -2023-01-05T14:06:54.865+08:00 INFO 22005 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 421 ms -2023-01-05T14:06:54.916+08:00 INFO 22005 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] -2023-01-05T14:06:54.929+08:00 INFO 22005 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.1.6.Final -2023-01-05T14:06:54.969+08:00 WARN 22005 --- [ main] org.hibernate.orm.deprecation : HHH90000021: Encountered deprecated setting [javax.persistence.sharedCache.mode], use [jakarta.persistence.sharedCache.mode] instead -2023-01-05T14:06:55.005+08:00 INFO 22005 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... -2023-01-05T14:06:55.074+08:00 INFO 22005 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@5e905f2c -2023-01-05T14:06:55.075+08:00 INFO 22005 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. -2023-01-05T14:06:55.089+08:00 INFO 22005 --- [ main] SQL dialect : HHH000400: Using dialect: org.hibernate.dialect.TiDBDialect -Hibernate: drop table if exists player_jpa -Hibernate: drop sequence player_jpa_id_seq -Hibernate: create sequence player_jpa_id_seq start with 1 increment by 1 -Hibernate: create table player_jpa (id bigint not null, coins integer, goods integer, primary key (id)) engine=InnoDB -2023-01-05T14:06:55.332+08:00 INFO 22005 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] -2023-01-05T14:06:55.335+08:00 INFO 22005 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' -2023-01-05T14:06:55.579+08:00 WARN 22005 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2023-01-05T14:06:55.710+08:00 INFO 22005 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' -2023-01-05T14:06:55.714+08:00 INFO 22005 --- [ main] com.pingcap.App : Started App in 1.432 seconds (process running for 1.654) -``` - -The output log indicates the application behavior during startup. In this example, the application starts a **Servlet** using [Tomcat](https://tomcat.apache.org/), uses Hibernate as the ORM, uses [HikariCP](https://github.com/brettwooldridge/HikariCP) as the database connection pool implementation, and uses `org.hibernate.dialect.TiDBDialect` as the database dialect. After startup, Hibernate deletes and re-creates the `player_jpa` table and the `player_jpa_id_seq` sequence. At the end of startup, the application listens on port `8080` to provide HTTP services to the outside. - -If you want to learn more about the code of this application, refer to [implementation details](#implementation-details). - -## Step 6: HTTP requests - -After the service is up and running, you can send the HTTP requests to the backend application. is the base URL that provides services. This tutorial uses a series of HTTP requests to show how to use the service. - -### Step 6.1 Use Postman requests (recommended) - -You can download this [configuration file](https://raw.githubusercontent.com/pingcap-inc/tidb-example-java/main/spring-jpa-hibernate/Player.postman_collection.json) locally and import it into [Postman](https://www.postman.com/) as shown here: - -![import the collection into Postman](/media/develop/IMG_20220402-003303222.png) - -#### Create players - -Click on the **Create** tab and the **Send** button to send a POST request to `http://localhost:8080/player/`. The return value is the number of players added, which is expected to be 1. - -![Postman-Create a player](/media/develop/IMG_20220402-003350731.png) - -#### Get player information by ID - -Click on the **GetByID** tab and the **Send** button to send a GET request to `http://localhost:8080/player/1`. The return value is the information of the player with ID `1`. - -![Postman-GetByID](/media/develop/IMG_20220402-003416079.png) - -#### Get player information in bulk by limit - -Click on the **GetByLimit** tab and the **Send** button to send a GET request to `http://localhost:8080/player/limit/3`. The return value is a list of information for up to 3 players. - -![Postman-GetByLimit](/media/develop/IMG_20220402-003505846.png) - -#### Get player information by page - -Click on the **GetByPage** tab and the **Send** button to send a GET request to `http://localhost:8080/player/page?index=0&size=2`. The return value is the page with index `0`, with `2` players per page. The return value also contains the paging information such as offset, totalPages, and sort. - -![Postman-GetByPage](/media/develop/IMG_20220402-003528474.png) - -#### Count players - -Click the **Count** tab and the **Send** button to send a GET request to `http://localhost:8080/player/count`. The return value is the number of players. - -![Postman-Count](/media/develop/IMG_20220402-003549966.png) - -#### Player trading - -Click on the **Trade** tab and the **Send** button to send a PUT request to `http://localhost:8080/player/trade`. The request parameters are the seller's ID `sellID`, the buyer's ID `buyID`, the number of goods purchased `amount`, the number of coins consumed for the purchase `price`. - -The return value is whether the transaction is successful or not. When there are insufficient goods for the seller, insufficient coins for the buyer, or a database error, the [database transaction](/develop/dev-guide-transaction-overview.md) guarantees that the trade is not successful and no player's coins or goods are lost. - -![Postman-Trade](/media/develop/IMG_20220402-003659102.png) - -### Step 6.2 Using curl requests - -You can also use curl to make requests directly. - -#### Create players - -To create players, you can send a **POST** request to the `/player` endpoint. For example: - -```shell -curl --location --request POST 'http://localhost:8080/player/' --header 'Content-Type: application/json' --data-raw '[{"coins":100,"goods":20}]' -``` - -The request uses JSON as the payload. The example above indicates creating a player with 100 `coins` and 20 `goods`. The return value is the number of players created. + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. -```json -1 -``` + TiDB Serverless requires a secure connection. Therefore, you need to set the value of `USE_SSL` to `true`. -#### Get player information by ID +7. Save the `env.sh` file. -To get the player information, you can send a **GET** request to the `/player` endpoint. You need to specify the `id` of the player in the path parameter as follows: `/player/{id}`. The following example shows how to get the information of a player with `id` 1: +
+
-```shell -curl --location --request GET 'http://localhost:8080/player/1' -``` +1. Navigate to the [**Clusters**](https://tidbcloud.com/console/clusters) page, and then click the name of your target cluster to go to its overview page. -The return value is the player's information: - -```json -{ - "coins": 200, - "goods": 10, - "id": 1 -} -``` +2. Click **Connect** in the upper-right corner. A connection dialog is displayed. -#### Get player information in bulk by limit - -To get the player information in bulk, you can send a **GET** request to the `/player/limit` endpoint. You need to specify the total number of players in the path parameter as follows: `/player/limit/{limit}`. The following example shows how to get the information of up to 3 players: - -```shell -curl --location --request GET 'http://localhost:8080/player/limit/3' -``` - -The return value is a list of player information: - -```json -[ - { - "coins": 200, - "goods": 10, - "id": 1 - }, - { - "coins": 0, - "goods": 30, - "id": 2 - }, - { - "coins": 100, - "goods": 20, - "id": 3 - } -] -``` - -#### Get player information by page - -To get paginated player information, you can send a **GET** request to the `/player/page` endpoint. To specify additional parameters, you need to use the URL parameter. The following example shows how to get the information from a page whose `index` is 0, where each page has a maximum `size` of 2 players. - -```shell -curl --location --request GET 'http://localhost:8080/player/page?index=0&size=2' -``` - -The return value is the page with `index` 0, where 2 players are listed per page. In addition, the return value contains pagination information such as offset, total pages, and whether the results are sorted. - -```json -{ - "content": [ - { - "coins": 200, - "goods": 10, - "id": 1 - }, - { - "coins": 0, - "goods": 30, - "id": 2 - } - ], - "empty": false, - "first": true, - "last": false, - "number": 0, - "numberOfElements": 2, - "pageable": { - "offset": 0, - "pageNumber": 0, - "pageSize": 2, - "paged": true, - "sort": { - "empty": true, - "sorted": false, - "unsorted": true - }, - "unpaged": false - }, - "size": 2, - "sort": { - "empty": true, - "sorted": false, - "unsorted": true - }, - "totalElements": 4, - "totalPages": 2 -} -``` - -#### Count players - -To get the number of players, you can send a **GET** request to the `/player/count` endpoint: - -```shell -curl --location --request GET 'http://localhost:8080/player/count' -``` +3. Click **Allow Access from Anywhere** and then click **Download TiDB cluster CA** to download the CA certificate. -The return value is the number of players: + For more details about how to obtain the connection string, refer to [TiDB Dedicated standard connection](https://docs.pingcap.com/tidbcloud/connect-via-standard-connection). -```json -4 -``` +4. Run the following command to copy `env.sh.example` and rename it to `env.sh`: -#### Player trading + ```shell + cp env.sh.example env.sh + ``` -To initiate a transaction between players, you can send a **PUT** request to the `/player/trade` endpoint. For example: +5. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: -```shell -curl --location --request PUT 'http://localhost:8080/player/trade' \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --data-urlencode 'sellID=1' \ - --data-urlencode 'buyID=2' \ - --data-urlencode 'amount=10' \ - --data-urlencode 'price=100' -``` + ```shell + export TIDB_HOST='{host}' # e.g. tidb.xxxx.clusters.tidb-cloud.com + export TIDB_PORT='4000' + export TIDB_USER='{user}' # e.g. root + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` -The request uses **Form Data** as the payload. The example request indicates that the seller's ID (`sellID`) is 1, the buyer's ID (`buyID`) is 2, the number of goods purchased (`amount`) is 10, and the number of coins consumed for purchase (`price`) is 100. + Be sure to replace the placeholders `{}` with the connection parameters obtained from the connection dialog. -The return value is whether the transaction is successful or not. When there are insufficient goods for the seller, insufficient coins for the buyer, or a database error, the [database transaction](/develop/dev-guide-transaction-overview.md) guarantees that the trade is not successful and no player's coins or goods are lost. +6. Save the `env.sh` file. -```json -true -``` +
+
-### Step 6.3 Requests with Shell script +1. Run the following command to copy `env.sh.example` and rename it to `env.sh`: -You can download [this shell script](https://github.com/pingcap-inc/tidb-example-java/blob/main/spring-jpa-hibernate/request.sh) for testing purposes. The script performs the following operations: + ```shell + cp env.sh.example env.sh + ``` -1. Create 10 players in a loop. -2. Get the information of players with the `id` of 1. -3. Get a list of up to 3 players. -4. Get a page of players with the `index` of 0 and the `size` of 2. -5. Get the total number of players. -6. Perform a transaction, where the player with the `id` of 1 is the seller and the player with the `id` of 2 is the buyer, and 10 `goods` are purchased at the cost of 100 `coins`. +2. Copy and paste the corresponding connection string into the `env.sh` file. The example result is as follows: -You can run this script with `make request` or `./request.sh`. The result should look like this: + ```shell + export TIDB_HOST='{host}' + export TIDB_PORT='4000' + export TIDB_USER='root' + export TIDB_PASSWORD='{password}' + export TIDB_DB_NAME='test' + export USE_SSL='false' + ``` -```shell -cheese@CheesedeMacBook-Pro spring-jpa-hibernate % make request -./request.sh -loop to create 10 players: -1111111111 + Be sure to replace the placeholders `{}` with the connection parameters, and set `USE_SSL` to `false`. If you are running TiDB locally, the default host address is `127.0.0.1`, and the password is empty. -get player 1: -{"id":1,"coins":200,"goods":10} +3. Save the `env.sh` file. -get players by limit 3: -[{"id":1,"coins":200,"goods":10},{"id":2,"coins":0,"goods":30},{"id":3,"coins":100,"goods":20}] +
+
-get first players: -{"content":[{"id":1,"coins":200,"goods":10},{"id":2,"coins":0,"goods":30}],"pageable":{"sort":{"empty":true,"unsorted":true,"sorted":false},"offset":0,"pageNumber":0,"pageSize":2,"paged":true,"unpaged":false},"last":false,"totalPages":7,"totalElements":14,"first":true,"size":2,"number":0,"sort":{"empty":true,"unsorted":true,"sorted":false},"numberOfElements":2,"empty":false} +### Step 3: Run the code and check the result -get players count: -14 +1. Execute the following command to run the sample code: -trade by two players: -false -``` + ```shell + make + ``` -## Implementation details +2. Run the request script in another terminal session: -This subsection describes the components in the sample application project. + ```shell + make request + ``` -### Overview +3. Check the [Expected-Output.txt](https://github.com/tidb-samples/tidb-java-springboot-jpa-quickstart/blob/main/Expected-Output.txt) to see if the output matches. -The catalog tree for this example project is shown below (some incomprehensible parts are removed): +## Sample code snippets -``` -. -├── pom.xml -└── src - └── main - ├── java - │ └── com - │ └── pingcap - │ ├── App.java - │ ├── controller - │ │ └── PlayerController.java - │ ├── dao - │ │ ├── PlayerBean.java - │ │ └── PlayerRepository.java - │ └── service - │ ├── PlayerService.java - │ └── impl - │ └── PlayerServiceImpl.java - └── resources - └── application.yml -``` +You can refer to the following sample code snippets to complete your own application development. -- `pom.xml` declares the project's Maven configuration, such as dependencies and packaging. -- `application.yml` declares the project's user configuration, such as database address, password, and database dialect used. -- `App.java` is the entry point of the project. -- `controller` is the package that exposes the HTTP interface to the outside. -- `service` is the package that implements the interface and logic of the project. -- `dao` is the package that implements the connection to the database and the persistence of the data. - -### Configuration - -This part briefly describes the Maven configuration in the `pom.xml` file and the user configuration in the `application.yml` file. - -#### Maven configuration - -The `pom.xml` file is a Maven configuration file that declares the project's Maven dependencies, packaging methods, and packaging information. You can replicate the process of generating this configuration file by [creating a blank application with the same dependency](#create-a-blank-application-with-the-same-dependency-optional), or copying it directly to your project. - -```xml - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.0.1 - - - - com.pingcap - spring-jpa-hibernate - 0.0.1 - spring-jpa-hibernate - an example for spring boot, jpa, hibernate and TiDB - - - 17 - 17 - 17 - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-web - - - - mysql - mysql-connector-java - runtime - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - -``` +For complete sample code and how to run it, check out the [tidb-samples/tidb-java-springboot-jpa-quickstart](https://github.com/tidb-samples/tidb-java-springboot-jpa-quickstart) repository. -#### User configuration +### Connect to TiDB -The `application.yml` configuration file declares the user configuration, such as database address, password, and the database dialect used. +Edit the configuration file `application.yml`: ```yaml spring: datasource: - url: jdbc:mysql://localhost:4000/test - username: root - # password: xxx + url: ${TIDB_JDBC_URL:jdbc:mysql://localhost:4000/test} + username: ${TIDB_USER:root} + password: ${TIDB_PASSWORD:} driver-class-name: com.mysql.cj.jdbc.Driver jpa: show-sql: true @@ -572,453 +206,67 @@ spring: ddl-auto: create-drop ``` -The configuration is written in [YAML](https://yaml.org/). The fields are described as follows: +After configuration, set the environment variables `TIDB_JDBC_URL`, `TIDB_USER`, and `TIDB_PASSWORD` to the actual values of your TiDB cluster. The configuration file provides default settings for these environment variables. If you do not configure the environment variables, the default values are as follows: -- `spring.datasource.url` : URL of the database connection. -- `spring.datasource.username` : the database username. -- `spring.datasource.password` : the database password. Empty. You need to comment out or delete this field. -- `spring.datasource.driver-class-name` : the database driver. Because TiDB is compatible with MySQL, use a mysql-connector-java driver class `com.mysql.cj.jdbc`. -- `jpa.show-sql` : when this field is set to `true`, the SQL statements run by JPA are printed. -- `jpa.database-platform` : the selected database dialect. Because the application connects to TiDB, choose **TiDB dialect**. Note that this dialect is only available in Hibernate `6.0.0.Beta2` and later versions, so choose the applicable dependency version. -- `jpa.hibernate.ddl-auto` : `create-drop` creates the table at the beginning of the program and deletes the table on exit. Do not set this option in a production environment. Because this is a sample application, this option is set to minimize the impact on the database data. +- `TIDB_JDBC_URL`: `"jdbc:mysql://localhost:4000/test"` +- `TIDB_USER`: `"root"` +- `TIDB_PASSWORD`: `""` -### Entry point +### Data management: `@Repository` -The `App.java` file is the entry point: +Spring Data JPA manages data through the `@Repository` interface. To use the CRUD operations provided by `JpaRepository`, you need to extend the `JpaRepository` interface: ```java -package com.pingcap; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.ApplicationPidFileWriter; - -@SpringBootApplication -public class App { - public static void main(String[] args) { - SpringApplication springApplication = new SpringApplication(App.class); - springApplication.addListeners(new ApplicationPidFileWriter("spring-jpa-hibernate.pid")); - springApplication.run(args); - } -} -``` - -The entry class starts with the standard configuration annotation [`@SpringBootApplication`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/SpringBootApplication.html) for Spring Boot applications. For more information, see [Using the `@SpringBootApplication` Annotation](https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation) in the Spring Boot official documentation. Then, the program uses the `ApplicationPidFileWriter` to write a PID (process identification number) file called `spring-jpa-hibernate.pid` during application startup. The PID file can be used to close this application from an external source. - -### Data access object - -The `dao` (Data Access Object) package implements the persistence of data objects. - -#### Entity objects - -The `PlayerBean.java` file is an entity object, which corresponds to a table in the database: - -```java -package com.pingcap.dao; - -import jakarta.persistence.*; - -/** - * it's core entity in hibernate - * @Table appoint to table name - */ -@Entity -@Table(name = "player_jpa") -public class PlayerBean { - /** - * @ID primary key - * @GeneratedValue generated way. this field will use generator named "player_id" - * @SequenceGenerator using `sequence` feature to create a generator, - * and it named "player_jpa_id_seq" in database, initial form 1 (by `initialValue` - * parameter default), and every operator will increase 1 (by `allocationSize`) - */ - @Id - @GeneratedValue(generator="player_id") - @SequenceGenerator(name="player_id", sequenceName="player_jpa_id_seq", allocationSize=1) - private Long id; - - /** - * @Column field - */ - @Column(name = "coins") - private Integer coins; - @Column(name = "goods") - private Integer goods; - - public Long getId() { - return id; - } - - public void setId(Long 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; - } -} -``` - -The entity class has several annotations that give Hibernate additional information to bind the entity class to the table. - -- `@Entity` declares that `PlayerBean` is an entity class. -- `@Table` relates this entity class to the `player_jpa` table using the annotation attribute `name`. -- `@Id` declares that this property is related to the primary key column of the table. -- `@GeneratedValue` indicates that the value of this column is generated automatically and should not be set manually. The attribute `generator` is used to specify the name of the generator as `player_id`. -- `@SequenceGenerator` declares a generator that uses [sequence](/sql-statements/sql-statement-create-sequence.md), and uses the annotation attribute `name` to declare the name of the generator as `player_id` (consistent with the name specified in `@GeneratedValue`). The annotation attribute `sequenceName` is used to specify the name of the sequence in the database. Finally, the annotation attribute `allocationSize` is used to declare the sequence's step size to be 1. -- `@Column` declares each private attribute as a column of the `player_jpa` table, and uses the annotation attribute `name` to determine the name of the column corresponding to the attribute. - -#### Repository - -To abstract the database layer, Spring applications use the [`Repository`](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories) interface, or a sub-interface of the `Repository`. This interface maps to a database object, such as a table. JPA implements some pre-built methods, such as [`INSERT`](/sql-statements/sql-statement-insert.md), or [`SELECT`](/sql-statements/sql-statement-select.md) using the primay key. - -```java -package com.pingcap.dao; - -import jakarta.persistence.LockModeType; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Lock; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; - @Repository public interface PlayerRepository extends JpaRepository { - /** - * use HQL to query by page - * @param pageable a pageable parameter required by hibernate - * @return player list package by page message - */ - @Query(value = "SELECT player_jpa FROM PlayerBean player_jpa") - Page getPlayersByPage(Pageable pageable); - - /** - * use SQL to query by limit, using named parameter - * @param limit sql parameter - * @return player list (max size by limit) - */ - @Query(value = "SELECT * FROM player_jpa LIMIT :limit", nativeQuery = true) - List getPlayersByLimit(@Param("limit") Integer limit); - - /** - * query player and add a lock for update - * @param id player id - * @return player - */ - @Lock(value = LockModeType.PESSIMISTIC_WRITE) - @Query(value = "SELECT player FROM PlayerBean player WHERE player.id = :id") - // @Query(value = "SELECT * FROM player_jpa WHERE id = :id FOR UPDATE", nativeQuery = true) - PlayerBean getPlayerAndLock(@Param("id") Long id); } ``` -The `PlayerRepository` interface extends the `JpaRepository` interface used by Spring for JPA data access. The `@Query` annotation is used to tell Hibernate how to implement queries in this interface. Two query syntaxes are used: - -* In the `getPlayersByPage` interface, [Hibernate Query Language](https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#hql) (HQL) is used. -* In the `getPlayersByLimit` interface, native SQL is used. When the interface uses the native SQL syntax, the `@Query` annotation parameter `nativeQuery` must be set to `true`. - -In the SQL for the `getPlayersByLimit` annotation, `:limit` is called a [named parameter](https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#jpql-query-parameters) in Hibernate. Hibernate automatically finds and splices the parameter by name within the interface where the annotation resides. You can also use `@Param` to specify a name different from the parameter for injection. - -In `getPlayerAndLock`, an annotation [`@Lock`](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/Lock.html) is used to declare that pessimistic locking is applied. For details on other locking methods, see [Entity Locking](https://openjpa.apache.org/builds/2.2.2/apache-openjpa/docs/jpa_overview_em_locking.html). The `@Lock` annotation must be used with `HQL`; otherwise, an error occurs. If you want to use SQL directly for locking, you can use the annotation from the comment: +Then, you can use `@Autowired` for automatic dependency injection in any class that requires the `PlayerRepository`. This enables you to directly use CRUD functions. The following is an example: ```java -@Query(value = "SELECT * FROM player_jpa WHERE id = :id FOR UPDATE", nativeQuery = true) +@Autowired +private PlayerRepository playerRepository; ``` -The SQL statement above uses `FOR UPDATE` to add locks directly. You can also dive deeper into the principles with the TiDB [`SELECT` statement](/sql-statements/sql-statement-select.md). - -### Logic implementation - -The logic implementation layer is the `service` package, which contains the interfaces and logic implemented by the project. - -#### Interface - -The `PlayerService.java` file defines the logical interface and implements the interface rather than writing a class directly. This is to keep the example as close to actual use as possible and to reflect the [open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) of the design. You may omit this interface and inject the implementation class directly in the dependency class, but this approach is not recommended. +### Insert or update data ```java -package com.pingcap.service; - -import com.pingcap.dao.PlayerBean; -import org.springframework.data.domain.Page; - -import java.util.List; - -public interface PlayerService { - /** - * create players by passing in a List of PlayerBean - * - * @param players will create players list - * @return The number of create accounts - */ - Integer createPlayers(List players); - - /** - * 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 - */ - void buyGoods(Long sellId, Long buyId, Integer amount, Integer price) throws RuntimeException; - - /** - * get the player info by id. - * - * @param id player id - * @return the player of this id - */ - PlayerBean getPlayerByID(Long id); - - /** - * get a subset of players from the data store by limit. - * - * @param limit return max size - * @return player list - */ - List getPlayers(Integer limit); - - /** - * get a page of players from the data store. - * - * @param index page index - * @param size page size - * @return player list - */ - Page getPlayersByPage(Integer index, Integer size); - - /** - * count players from the data store. - * - * @return all players count - */ - Long countPlayers(); -} +playerRepository.save(player); ``` -#### Implementation (Important) +For more information, refer to [Insert data](/develop/dev-guide-insert-data.md) and [Update data](/develop/dev-guide-update-data.md). -The `PlayerService.java` file implements the `PlayerService` interface, which contains all the data processing logic. +### Query data ```java -package com.pingcap.service.impl; - -import com.pingcap.dao.PlayerBean; -import com.pingcap.dao.PlayerRepository; -import com.pingcap.service.PlayerService; -import jakarta.transaction.Transactional; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * PlayerServiceImpl implements PlayerService interface - * @Transactional it means every method in this class, will package by a pair of - * transaction.begin() and transaction.commit(). and it will be call - * transaction.rollback() when method throw an exception - */ -@Service -@Transactional -public class PlayerServiceImpl implements PlayerService { - @Autowired - private PlayerRepository playerRepository; - - @Override - public Integer createPlayers(List players) { - return playerRepository.saveAll(players).size(); - } - - @Override - public void buyGoods(Long sellId, Long buyId, Integer amount, Integer price) throws RuntimeException { - PlayerBean buyPlayer = playerRepository.getPlayerAndLock(buyId); - PlayerBean sellPlayer = playerRepository.getPlayerAndLock(sellId); - if (buyPlayer == null || sellPlayer == null) { - throw new RuntimeException("sell or buy player not exist"); - } - - if (buyPlayer.getCoins() < price || sellPlayer.getGoods() < amount) { - throw new RuntimeException("coins or goods not enough, rollback"); - } - - buyPlayer.setGoods(buyPlayer.getGoods() + amount); - buyPlayer.setCoins(buyPlayer.getCoins() - price); - playerRepository.save(buyPlayer); - - sellPlayer.setGoods(sellPlayer.getGoods() - amount); - sellPlayer.setCoins(sellPlayer.getCoins() + price); - playerRepository.save(sellPlayer); - } - - @Override - public PlayerBean getPlayerByID(Long id) { - return playerRepository.findById(id).orElse(null); - } - - @Override - public List getPlayers(Integer limit) { - return playerRepository.getPlayersByLimit(limit); - } - - @Override - public Page getPlayersByPage(Integer index, Integer size) { - return playerRepository.getPlayersByPage(PageRequest.of(index, size)); - } - - @Override - public Long countPlayers() { - return playerRepository.count(); - } -} +PlayerBean player = playerRepository.findById(id).orElse(null); ``` -The `@Service` annotation is used to declare that the lifecycle of this object is managed by `Spring`. - -The `PlayerServiceImpl` implementation class also has a [`@Transactional`](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-declarative-annotations) annotation in addition to the `@Service` annotation. When transaction management is enabled in the application (which can be turned on using [`@EnableTransactionManagement`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html), but is turned on by default by `Spring Boot`. You don not need to manually configure it.), `Spring` automatically wraps all objects with the `@Transactional` annotation in a proxy and uses this proxy for object invocation processing. - -You can simply assume that when the agent calls a function inside an object with the `@Transactional` annotation: - -- At the top of the function, it starts the transaction with `transaction.begin()`. -- When the function returns, it calls `transaction.commit()` to commit the transaction. -- When any runtime error occurs, the agent calls `transaction.rollback()` to roll back. - -You can refer to [Database Transactions](/develop/dev-guide-transaction-overview.md) for more information on transactions, or read [Understanding the Spring Framework's Declarative Transaction Implementation](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-decl-explained) on the `Spring` website. - -In all implementation classes, the `buyGoods` function is requires attention. When the function encounters an illogical operation, it throws an exception and directs Hibernate to perform a transaction rollback to prevent incorrect data. +For more information, refer to [Query data](/develop/dev-guide-get-data-from-single-table.md). -### External HTTP Interface - -The `controller` package exposes the HTTP interface to the outside world and allows access to the service via the [REST API](https://www.redhat.com/en/topics/api/what-is-a-rest-api#). +### Delete data ```java -package com.pingcap.controller; - -import com.pingcap.dao.PlayerBean; -import com.pingcap.service.PlayerService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.lang.NonNull; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/player") -public class PlayerController { - @Autowired - private PlayerService playerService; - - @PostMapping - public Integer createPlayer(@RequestBody @NonNull List playerList) { - return playerService.createPlayers(playerList); - } - - @GetMapping("/{id}") - public PlayerBean getPlayerByID(@PathVariable Long id) { - return playerService.getPlayerByID(id); - } - - @GetMapping("/limit/{limit_size}") - public List getPlayerByLimit(@PathVariable("limit_size") Integer limit) { - return playerService.getPlayers(limit); - } - - @GetMapping("/page") - public Page getPlayerByPage(@RequestParam Integer index, @RequestParam("size") Integer size) { - return playerService.getPlayersByPage(index, size); - } - - @GetMapping("/count") - public Long getPlayersCount() { - return playerService.countPlayers(); - } - - @PutMapping("/trade") - public Boolean trade(@RequestParam Long sellID, @RequestParam Long buyID, @RequestParam Integer amount, @RequestParam Integer price) { - try { - playerService.buyGoods(sellID, buyID, amount, price); - } catch (RuntimeException e) { - return false; - } - - return true; - } -} +playerRepository.deleteById(id); ``` -`PlayerController` uses annotations as many as possible to demonstrate features. In real projects, keep the style consistent while following the rules of your company or team. The annotations in `PlayerController` are explained as follows: - -- [`@RestController`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html) declares `PlayerController` as a [Web Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) and serializes the return value as `JSON` output. -- [`@RequestMapping`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html) maps the URL endpoint to `/player`, that is, this `Web Controller` only listens for requests sent to the `/player` URL. -- `@Autowired` means `Spring` container can autowire relationships between collaborating beans. The declaration requires a `PlayerService` object, which is an interface and does not specify which implementation class to use. This is assembled by Spring. For the rules of this assembly, see [The IoC container](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html) on Spring's official website. -- [`@PostMapping`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html) declares that this function responds to a [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) request in HTTP. - - `@RequestBody` declares that the entire HTTP payload is parsed into the `playerList` parameter. - - `@NonNull` declares that the parameter must not be null; otherwise, it returns an error. -- [`@GetMapping`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html) declares that this function responds to a [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) request in HTTP. - - [`@PathVariable`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html) shows that the annotation has placeholders like `{id}` and `{limit_size}`, which are bound to the variable annotated by `@PathVariable`. Such binding is based on the annotation attribute `name`. If the annotation attribute `name` is not specified, it is the same as the variable name. The variable name can be omitted, that is, `@PathVariable(name="limit_size")` can be written as `@PathVariable("limit_size")`. -- [`@PutMapping`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PutMapping.html) declares that this function responds to a [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) request in HTTP. -- [`@RequestParam`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html) declares that this function parses URL parameters, form parameters, and other parameters in the request and binds them to the annotated variables. - -## Create a blank application with the same dependency (optional) - -This application is built using [Spring Initializr](https://start.spring.io/). You can quickly get a blank application with the same dependencies as this sample application by clicking on the following options and changing a few configuration items: - -**Project** +For more information, refer to [Delete data](/develop/dev-guide-delete-data.md). -- Maven Project +## Next steps -**Language** +- Learn more usage of Hibernate from [the documentation of Hibernate](https://hibernate.org/orm/documentation). +- Learn more usage about the third-party libraries and frameworks used in this document, refer to their official documentation: -- Java + - [The documentation of Spring Framework](https://spring.io/projects/spring-framework) + - [The documentation of Spring Boot](https://spring.io/projects/spring-boot) + - [The documentation of Spring Data JPA](https://spring.io/projects/spring-data-jpa) + - [The documentation of Hibernate](https://hibernate.org/orm/documentation) -**Spring Boot** +- Learn the best practices for TiDB application development with the chapters in the [Developer guide](/develop/dev-guide-overview.md), such as [Insert data](/develop/dev-guide-insert-data.md), [Update data](/develop/dev-guide-update-data.md), [Delete data](/develop/dev-guide-delete-data.md), [Single table reading](/develop/dev-guide-get-data-from-single-table.md), [Transactions](/develop/dev-guide-transaction-overview.md), and [SQL performance optimization](/develop/dev-guide-optimize-sql-overview.md). +- Learn through the professional [TiDB developer courses](https://www.pingcap.com/education/) and earn [TiDB certifications](https://www.pingcap.com/education/certification/) after passing the exam. +- Learn through the course for Java developers: [Working with TiDB from Java](https://eng.edu.pingcap.com/catalog/info/id:212). -- Latest stable version - -**Project Metadata** - -- Group: com.pingcap -- Artifact: spring-jpa-hibernate -- Name: spring-jpa-hibernate -- Package name: com.pingcap -- Packaging: Jar -- Java: 17 - -**Dependencies** - -- Spring Web -- Spring Data JPA -- MySQL Driver - -The complete configuration is as follows: - -![Spring Initializr Configuration](/media/develop/develop-spring-initializr-configuration.png) - -> **Note:** -> -> Although SQL is relatively standardized, each database vendor uses a subset and superset of ANSI SQL defined syntax. This is referred to as the database's dialect. Hibernate handles variations across these dialects through its `org.hibernate.dialect.Dialect` class and the various subclasses for each database vendor. -> -> In most cases, Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. For information on Hibernate's ability to determine the proper Dialect to use (and your ability to influence that resolution), see [Dialect resolution](https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#portability-dialectresolver). -> -> If for some reason it is not able to determine the proper one or you want to use a custom Dialect, you will need to set the `hibernate.dialect` setting. -> -> _—— Excerpt from the Hibernate official documentation: [Database Dialect](https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#database-dialect)_ +## Need help? -After the configuration, you can get a blank **Spring Boot** application with the same dependencies as the sample application. \ No newline at end of file +Ask questions on the [Discord](https://discord.gg/vYU9h56kAX), or [create a support ticket](https://support.pingcap.com/).