TiDB 和 Java 的简单 CRUD 应用程序

    注意

    推荐使用 Java 8 及以上版本进行 TiDB 的应用程序的编写。

    Java - 图2

    小贴士

    如果你希望使用 Spring Boot 进行 TiDB 应用程序的编写,可以查看 。

    本节将介绍 TiDB 集群的启动方法。

    • TiDB Cloud
    • 本地集群
    • Gitpod

    你可以部署一个本地测试的 TiDB 集群或正式的 TiDB 集群。详细步骤,请参考:

    基于 Git 的预配置的开发环境:现在就试试

    该环境会自动克隆代码,并通过 TiUP 部署测试集群。

    • 使用 Mybatis(推荐)
    • 使用 Hibernate(推荐)
    • 使用 JDBC

    Mybatis 是当前比较流行的开源 Java 应用持久层框架,本文将以 Maven 插件的方式使用 生成部分持久层代码。

    进入目录 :

    1. cd plain-java-mybatis

    目录结构如下所示:

    1. .
    2. ├── Makefile
    3. ├── pom.xml
    4. └── src
    5. └── main
    6. ├── java
    7. └── com
    8. └── pingcap
    9. ├── MybatisExample.java
    10. ├── dao
    11. └── PlayerDAO.java
    12. └── model
    13. ├── Player.java
    14. ├── PlayerMapper.java
    15. └── PlayerMapperEx.java
    16. └── resources
    17. ├── dbinit.sql
    18. ├── log4j.properties
    19. ├── mapper
    20. ├── PlayerMapper.xml
    21. └── PlayerMapperEx.xml
    22. ├── mybatis-config.xml
    23. └── mybatis-generator.xml

    其中,自动生成的文件有:

    • src/main/java/com/pingcap/model/Player.java:Player 实体类文件
    • src/main/java/com/pingcap/model/PlayerMapper.java:Player Mapper 的接口文件
    • src/main/resources/mapper/PlayerMapper.xml:Player Mapper 的 XML 映射,它是 Mybatis 用于生成 Player Mapper 接口的实现类的配置

    这些文件的生成策略被写在了 mybatis-generator.xml 配置文件内,它是 Mybatis Generator 的配置文件,下面配置文件中添加了使用方法的说明:

    1. <!DOCTYPE generatorConfiguration PUBLIC
    2. "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    3. "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    4. <generatorConfiguration>
    5. <!--
    6. <context/> entire document: https://mybatis.org/generator/configreference/context.html
    7. context.id: A unique identifier you like
    8. context.targetRuntime: Used to specify the runtime target for generated code.
    9. It has MyBatis3DynamicSql / MyBatis3Kotlin / MyBatis3 / MyBatis3Simple 4 selection to choice.
    10. -->
    11. <context id="simple" targetRuntime="MyBatis3">
    12. <!--
    13. <commentGenerator/> entire document: https://mybatis.org/generator/configreference/commentGenerator.html
    14. commentGenerator:
    15. - property(suppressDate): remove timestamp in comments
    16. - property(suppressAllComments): remove all comments
    17. -->
    18. <commentGenerator>
    19. <property name="suppressDate" value="true"/>
    20. <property name="suppressAllComments" value="true" />
    21. </commentGenerator>
    22. <!--
    23. <jdbcConnection/> entire document: https://mybatis.org/generator/configreference/jdbcConnection.html
    24. jdbcConnection.driverClass: The fully qualified class name for the JDBC driver used to access the database.
    25. Used mysql-connector-java:5.1.49, should specify JDBC is com.mysql.jdbc.Driver
    26. jdbcConnection.connectionURL: The JDBC connection URL used to access the database.
    27. -->
    28. <jdbcConnection driverClass="com.mysql.jdbc.Driver"
    29. connectionURL="jdbc:mysql://localhost:4000/test?user=root" />
    30. <!--
    31. <javaModelGenerator/> entire document: https://mybatis.org/generator/configreference/javaModelGenerator.html
    32. Model code file will be generated at ${targetProject}/${targetPackage}
    33. javaModelGenerator:
    34. - property(constructorBased): If it's true, generator will create constructor function in model
    35. -->
    36. <javaModelGenerator targetPackage="com.pingcap.model" targetProject="src/main/java">
    37. <property name="constructorBased" value="true"/>
    38. </javaModelGenerator>
    39. <!--
    40. <sqlMapGenerator/> entire document: https://mybatis.org/generator/configreference/sqlMapGenerator.html
    41. XML SQL mapper file will be generated at ${targetProject}/${targetPackage}
    42. -->
    43. <sqlMapGenerator targetPackage="." targetProject="src/main/resources/mapper"/>
    44. <!--
    45. <javaClientGenerator/> entire document: https://mybatis.org/generator/configreference/javaClientGenerator.html
    46. Java code mapper interface file will be generated at ${targetProject}/${targetPackage}
    47. javaClientGenerator.type (context.targetRuntime is MyBatis3):
    48. This attribute indicated Mybatis how to implement interface.
    49. It has ANNOTATEDMAPPER / MIXEDMAPPER / XMLMAPPER 3 selection to choice.
    50. -->
    51. <javaClientGenerator type="XMLMAPPER" targetPackage="com.pingcap.model" targetProject="src/main/java"/>
    52. <!--
    53. <table/> entire document: https://mybatis.org/generator/configreference/table.html
    54. table.tableName: The name of the database table.
    55. table.domainObjectName: The base name from which generated object names will be generated. If not specified, MBG will generate a name automatically based on the tableName.
    56. table.enableCountByExample: Signifies whether a count by example statement should be generated.
    57. table.enableUpdateByExample: Signifies whether an update by example statement should be generated.
    58. table.enableDeleteByExample: Signifies whether a delete by example statement should be generated.
    59. table.enableSelectByExample: Signifies whether a select by example statement should be generated.
    60. table.selectByExampleQueryId: This value will be added to the select list of the select by example statement in this form: "'<value>' as QUERYID".
    61. -->
    62. <table tableName="player" domainObjectName="Player"
    63. enableCountByExample="false" enableUpdateByExample="false"
    64. enableDeleteByExample="false" enableSelectByExample="false"
    65. selectByExampleQueryId="false"/>
    66. </context>
    67. </generatorConfiguration>

    mybatis-generator.xmlpom.xml 中,以 mybatis-generator-maven-plugin 插件配置的方式被引入:

    1. <plugin>
    2. <groupId>org.mybatis.generator</groupId>
    3. <artifactId>mybatis-generator-maven-plugin</artifactId>
    4. <version>1.4.1</version>
    5. <configuration>
    6. <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
    7. <verbose>true</verbose>
    8. <overwrite>true</overwrite>
    9. </configuration>
    10. <dependencies>
    11. <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    12. <dependency>
    13. <groupId>mysql</groupId>
    14. <artifactId>mysql-connector-java</artifactId>
    15. <version>5.1.49</version>
    16. </dependency>
    17. </dependencies>
    18. </plugin>

    在 Maven 插件内引入后,可删除旧的生成文件后,通过命令 mvn mybatis-generate 生成新的文件。或者你也可以使用已经编写好的 make 命令,通过 make gen 来同时删除旧文件,并生成新文件。

    注意

    mybatis-generator.xml 中的属性 configuration.overwrite 仅可控制新生成的 Java 代码文件使用覆盖方式被写入,但 XML 映射文件仍会以追加方式写入。因此,推荐在 Mybaits Generator 生成新的文件前,先删除掉旧的文件。

    Player.java 是使用 Mybatis Generator 生成出的数据实体类文件,为数据库表在程序内的映射。Player 类的每个属性都对应着 player 表的一个字段。

    1. package com.pingcap.model;
    2. public class Player {
    3. private String id;
    4. private Integer coins;
    5. private Integer goods;
    6. public Player(String id, Integer coins, Integer goods) {
    7. this.id = id;
    8. this.coins = coins;
    9. this.goods = goods;
    10. }
    11. public Player() {
    12. super();
    13. }
    14. public String getId() {
    15. return id;
    16. }
    17. public void setId(String id) {
    18. this.id = id;
    19. }
    20. public Integer getCoins() {
    21. return coins;
    22. }
    23. public void setCoins(Integer coins) {
    24. this.coins = coins;
    25. }
    26. public Integer getGoods() {
    27. return goods;
    28. }
    29. public void setGoods(Integer goods) {
    30. this.goods = goods;
    31. }
    32. }

    PlayerMapper.java 是使用 Mybatis Generator 生成出的映射接口文件,它仅规定了接口,接口的实现类是由 Mybatis 来通过 XML 或注解自动生成的:

    1. package com.pingcap.model;
    2. import com.pingcap.model.Player;
    3. public interface PlayerMapper {
    4. int deleteByPrimaryKey(String id);
    5. int insert(Player row);
    6. int insertSelective(Player row);
    7. Player selectByPrimaryKey(String id);
    8. int updateByPrimaryKeySelective(Player row);
    9. int updateByPrimaryKey(Player row);
    10. }

    PlayerMapper.xml 是使用 Mybatis Generator 生成出的映射 XML 文件,Mybatis 将使用这个文件自动生成 PlayerMapper 接口的实现类:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.pingcap.model.PlayerMapper">
    4. <resultMap id="BaseResultMap" type="com.pingcap.model.Player">
    5. <constructor>
    6. <idArg column="id" javaType="java.lang.String" jdbcType="VARCHAR" />
    7. <arg column="coins" javaType="java.lang.Integer" jdbcType="INTEGER" />
    8. <arg column="goods" javaType="java.lang.Integer" jdbcType="INTEGER" />
    9. </constructor>
    10. </resultMap>
    11. <sql id="Base_Column_List">
    12. id, coins, goods
    13. </sql>
    14. <select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
    15. select
    16. <include refid="Base_Column_List" />
    17. from player
    18. where id = #{id,jdbcType=VARCHAR}
    19. </select>
    20. <delete id="deleteByPrimaryKey" parameterType="java.lang.String">
    21. delete from player
    22. where id = #{id,jdbcType=VARCHAR}
    23. </delete>
    24. <insert id="insert" parameterType="com.pingcap.model.Player">
    25. insert into player (id, coins, goods
    26. )
    27. values (#{id,jdbcType=VARCHAR}, #{coins,jdbcType=INTEGER}, #{goods,jdbcType=INTEGER}
    28. )
    29. </insert>
    30. <insert id="insertSelective" parameterType="com.pingcap.model.Player">
    31. insert into player
    32. <trim prefix="(" suffix=")" suffixOverrides=",">
    33. <if test="id != null">
    34. id,
    35. </if>
    36. <if test="coins != null">
    37. coins,
    38. </if>
    39. <if test="goods != null">
    40. goods,
    41. </if>
    42. </trim>
    43. <trim prefix="values (" suffix=")" suffixOverrides=",">
    44. <if test="id != null">
    45. #{id,jdbcType=VARCHAR},
    46. </if>
    47. <if test="coins != null">
    48. #{coins,jdbcType=INTEGER},
    49. </if>
    50. <if test="goods != null">
    51. #{goods,jdbcType=INTEGER},
    52. </if>
    53. </trim>
    54. </insert>
    55. <update id="updateByPrimaryKeySelective" parameterType="com.pingcap.model.Player">
    56. update player
    57. <set>
    58. <if test="coins != null">
    59. coins = #{coins,jdbcType=INTEGER},
    60. </if>
    61. <if test="goods != null">
    62. goods = #{goods,jdbcType=INTEGER},
    63. </if>
    64. </set>
    65. where id = #{id,jdbcType=VARCHAR}
    66. </update>
    67. <update id="updateByPrimaryKey" parameterType="com.pingcap.model.Player">
    68. update player
    69. set coins = #{coins,jdbcType=INTEGER},
    70. goods = #{goods,jdbcType=INTEGER}
    71. where id = #{id,jdbcType=VARCHAR}
    72. </update>
    73. </mapper>

    由于 Mybatis Generator 需要逆向生成源码,因此,数据库中需先行有此表结构,可使用 dbinit.sql 生成表结构:

    1. USE test;
    2. DROP TABLE IF EXISTS player;
    3. CREATE TABLE player (
    4. `id` VARCHAR(36),
    5. `coins` INTEGER,
    6. `goods` INTEGER,
    7. PRIMARY KEY (`id`)
    8. );

    额外拆分接口 PlayerMapperEx 继承 PlayerMapper,并且编写与之匹配的 PlayerMapperEx.xml。避免直接更改 PlayerMapper.javaPlayerMapper.xml。这是为了规避 Mybatis Generator 的反复生成,影响到自行编写的代码。

    1. package com.pingcap.model;
    2. import java.util.List;
    3. public interface PlayerMapperEx extends PlayerMapper {
    4. Player selectByPrimaryKeyWithLock(String id);
    5. List<Player> selectByLimit(Integer limit);
    6. Integer count();
    7. }

    PlayerMapperEx.xml 中定义映射规则:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.pingcap.model.PlayerMapperEx">
    4. <resultMap id="BaseResultMap" type="com.pingcap.model.Player">
    5. <constructor>
    6. <idArg column="id" javaType="java.lang.String" jdbcType="VARCHAR" />
    7. <arg column="coins" javaType="java.lang.Integer" jdbcType="INTEGER" />
    8. <arg column="goods" javaType="java.lang.Integer" jdbcType="INTEGER" />
    9. </constructor>
    10. </resultMap>
    11. <sql id="Base_Column_List">
    12. id, coins, goods
    13. </sql>
    14. <select id="selectByPrimaryKeyWithLock" parameterType="java.lang.String" resultMap="BaseResultMap">
    15. select
    16. <include refid="Base_Column_List" />
    17. from player
    18. where `id` = #{id,jdbcType=VARCHAR}
    19. for update
    20. </select>
    21. <select id="selectByLimit" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    22. select
    23. <include refid="Base_Column_List" />
    24. from player
    25. limit #{id,jdbcType=INTEGER}
    26. </select>
    27. <select id="count" resultType="java.lang.Integer">
    28. select count(*) from player
    29. </select>
    30. </mapper>

    PlayerDAO.java 是程序用来管理数据对象的类。其中 DAO 是 的缩写。在其中定义了一系列数据的操作方法,用于数据的写入。

    MybatisExampleplain-java-mybatis 这个示例程序的主类。其中定义了入口函数:

    1. package com.pingcap;
    2. import com.pingcap.dao.PlayerDAO;
    3. import com.pingcap.model.Player;
    4. import org.apache.ibatis.io.Resources;
    5. import org.apache.ibatis.session.SqlSessionFactory;
    6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    7. import java.io.IOException;
    8. import java.io.InputStream;
    9. import java.util.Arrays;
    10. import java.util.Collections;
    11. public class MybatisExample {
    12. public static void main( String[] args ) throws IOException {
    13. // 1. Create a SqlSessionFactory based on our mybatis-config.xml configuration
    14. // file, which defines how to connect to the database.
    15. InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    16. SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    17. // 2. And then, create DAO to manager your data
    18. PlayerDAO playerDAO = new PlayerDAO();
    19. // 3. Run some simple examples.
    20. // Create a player who has 1 coin and 1 goods.
    21. playerDAO.runTransaction(sessionFactory, playerDAO.createPlayers(
    22. Collections.singletonList(new Player("test", 1, 1))));
    23. // Get a player.
    24. Player testPlayer = (Player)playerDAO.runTransaction(sessionFactory, playerDAO.getPlayerByID("test"));
    25. System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n",
    26. testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods());
    27. // Count players amount.
    28. Integer count = (Integer)playerDAO.runTransaction(sessionFactory, playerDAO.countPlayers());
    29. System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count);
    30. // Print 3 players.
    31. playerDAO.runTransaction(sessionFactory, playerDAO.printPlayers(3));
    32. // 4. Getting further.
    33. // Player 1: id is "1", has only 100 coins.
    34. // Player 2: id is "2", has 114514 coins, and 20 goods.
    35. Player player1 = new Player("1", 100, 0);
    36. Player player2 = new Player("2", 114514, 20);
    37. // Create two players "by hand", using the INSERT statement on the backend.
    38. int addedCount = (Integer)playerDAO.runTransaction(sessionFactory,
    39. playerDAO.createPlayers(Arrays.asList(player1, player2)));
    40. System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount);
    41. // Player 1 wants to buy 10 goods from player 2.
    42. // It will cost 500 coins, but player 1 cannot afford it.
    43. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail");
    44. Integer updatedCount = (Integer)playerDAO.runTransaction(sessionFactory,
    45. playerDAO.buyGoods(player2.getId(), player1.getId(), 10, 500));
    46. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    47. // So player 1 has to reduce the incoming quantity to two.
    48. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success");
    49. updatedCount = (Integer)playerDAO.runTransaction(sessionFactory,
    50. playerDAO.buyGoods(player2.getId(), player1.getId(), 2, 100));
    51. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    52. }
    53. }

    当前开源比较流行的 Java ORM 为 Hibernate,且 Hibernate 在版本 6.0.0.Beta2 及以后支持了 TiDB 方言。完美适配了 TiDB 的特性。因此,此处将以 6.0.0.Beta2 + 版本进行说明。

    进入目录 plain-java-hibernate

    1. cd plain-java-hibernate

    目录结构如下所示:

    1. .
    2. ├── Makefile
    3. ├── plain-java-hibernate.iml
    4. ├── pom.xml
    5. └── src
    6. └── main
    7. ├── java
    8. └── com
    9. └── pingcap
    10. └── HibernateExample.java
    11. └── resources
    12. └── hibernate.cfg.xml

    其中,hibernate.cfg.xml 为 Hibernate 配置文件,定义了:

    1. <?xml version='1.0' encoding='utf-8'?>
    2. <!DOCTYPE hibernate-configuration PUBLIC
    3. "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    4. "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    5. <hibernate-configuration>
    6. <session-factory>
    7. <!-- Database connection settings -->
    8. <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
    9. <property name="hibernate.connection.url">jdbc:mysql://localhost:4000/test</property>
    10. <property name="hibernate.connection.username">root</property>
    11. <property name="hibernate.connection.password"></property>
    12. <property name="hibernate.connection.autocommit">false</property>
    13. <!-- Required so a table can be created from the 'PlayerDAO' class -->
    14. <property name="hibernate.hbm2ddl.auto">create-drop</property>
    15. <!-- Optional: Show SQL output for debugging -->
    16. <property name="hibernate.show_sql">true</property>
    17. <property name="hibernate.format_sql">true</property>
    18. </session-factory>
    19. </hibernate-configuration>

    HibernateExample.javaplain-java-hibernate 这个示例程序的主体。使用 Hibernate 时,相较于 JDBC,这里仅需写入配置文件地址,Hibernate 屏蔽了创建数据库连接时,不同数据库差异的细节。

    PlayerDAO 是程序用来管理数据对象的类。其中 DAOData Access Object 的缩写。其中定义了一系列数据的操作方法,用来提供数据的写入能力。相较于 JDBC,Hibernate 封装了大量的操作,如对象映射、基本对象的 CRUD 等,极大的简化了代码量。

    PlayerBean 是数据实体类,为数据库表在程序内的映射。PlayerBean 的每个属性都对应着 player 表的一个字段。相较于 JDBC,Hibernate 的 PlayerBean 实体类为了给 Hibernate 提供更多的信息,加入了注解,用来指示映射关系。

    1. package com.pingcap;
    2. import jakarta.persistence.Column;
    3. import jakarta.persistence.Entity;
    4. import jakarta.persistence.Id;
    5. import jakarta.persistence.Table;
    6. import org.hibernate.JDBCException;
    7. import org.hibernate.Session;
    8. import org.hibernate.SessionFactory;
    9. import org.hibernate.Transaction;
    10. import org.hibernate.cfg.Configuration;
    11. import org.hibernate.query.NativeQuery;
    12. import org.hibernate.query.Query;
    13. import java.util.Arrays;
    14. import java.util.Collections;
    15. import java.util.List;
    16. import java.util.function.Function;
    17. @Entity
    18. @Table(name = "player_hibernate")
    19. class PlayerBean {
    20. @Id
    21. private String id;
    22. @Column(name = "coins")
    23. private Integer coins;
    24. @Column(name = "goods")
    25. private Integer goods;
    26. public PlayerBean() {
    27. }
    28. public PlayerBean(String id, Integer coins, Integer goods) {
    29. this.id = id;
    30. this.coins = coins;
    31. this.goods = goods;
    32. }
    33. public String getId() {
    34. return id;
    35. }
    36. public void setId(String id) {
    37. this.id = id;
    38. }
    39. public Integer getCoins() {
    40. return coins;
    41. }
    42. public void setCoins(Integer coins) {
    43. this.coins = coins;
    44. }
    45. public Integer getGoods() {
    46. return goods;
    47. }
    48. public void setGoods(Integer goods) {
    49. this.goods = goods;
    50. }
    51. @Override
    52. public String toString() {
    53. return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n",
    54. "id", this.id, "coins", this.coins, "goods", this.goods);
    55. }
    56. }
    57. /**
    58. * Main class for the basic Hibernate example.
    59. **/
    60. public class HibernateExample
    61. {
    62. public static class PlayerDAO {
    63. public static class NotEnoughException extends RuntimeException {
    64. public NotEnoughException(String message) {
    65. super(message);
    66. }
    67. }
    68. // Run SQL code in a way that automatically handles the
    69. // transaction retry logic so we don't have to duplicate it in
    70. // various places.
    71. public Object runTransaction(Session session, Function<Session, Object> fn) {
    72. Object resultObject = null;
    73. Transaction txn = session.beginTransaction();
    74. try {
    75. resultObject = fn.apply(session);
    76. txn.commit();
    77. System.out.println("APP: COMMIT;");
    78. } catch (JDBCException e) {
    79. System.out.println("APP: ROLLBACK BY JDBC ERROR;");
    80. txn.rollback();
    81. } catch (NotEnoughException e) {
    82. System.out.printf("APP: ROLLBACK BY LOGIC; %s", e.getMessage());
    83. txn.rollback();
    84. }
    85. return resultObject;
    86. }
    87. public Function<Session, Object> createPlayers(List<PlayerBean> players) throws JDBCException {
    88. return session -> {
    89. Integer addedPlayerAmount = 0;
    90. for (PlayerBean player: players) {
    91. session.persist(player);
    92. addedPlayerAmount ++;
    93. }
    94. System.out.printf("APP: createPlayers() --> %d\n", addedPlayerAmount);
    95. return addedPlayerAmount;
    96. };
    97. }
    98. public Function<Session, Object> buyGoods(String sellId, String buyId, Integer amount, Integer price) throws JDBCException {
    99. return session -> {
    100. PlayerBean sellPlayer = session.get(PlayerBean.class, sellId);
    101. PlayerBean buyPlayer = session.get(PlayerBean.class, buyId);
    102. if (buyPlayer == null || sellPlayer == null) {
    103. throw new NotEnoughException("sell or buy player not exist");
    104. }
    105. if (buyPlayer.getCoins() < price || sellPlayer.getGoods() < amount) {
    106. throw new NotEnoughException("coins or goods not enough, rollback");
    107. }
    108. buyPlayer.setGoods(buyPlayer.getGoods() + amount);
    109. buyPlayer.setCoins(buyPlayer.getCoins() - price);
    110. session.persist(buyPlayer);
    111. sellPlayer.setGoods(sellPlayer.getGoods() - amount);
    112. sellPlayer.setCoins(sellPlayer.getCoins() + price);
    113. session.persist(sellPlayer);
    114. System.out.printf("APP: buyGoods --> sell: %s, buy: %s, amount: %d, price: %d\n", sellId, buyId, amount, price);
    115. return 0;
    116. };
    117. }
    118. public Function<Session, Object> getPlayerByID(String id) throws JDBCException {
    119. return session -> session.get(PlayerBean.class, id);
    120. }
    121. public Function<Session, Object> printPlayers(Integer limit) throws JDBCException {
    122. return session -> {
    123. NativeQuery<PlayerBean> limitQuery = session.createNativeQuery("SELECT * FROM player_hibernate LIMIT :limit", PlayerBean.class);
    124. limitQuery.setParameter("limit", limit);
    125. List<PlayerBean> players = limitQuery.getResultList();
    126. for (PlayerBean player: players) {
    127. System.out.println("\n[printPlayers]:\n" + player);
    128. }
    129. return 0;
    130. };
    131. }
    132. public Function<Session, Object> countPlayers() throws JDBCException {
    133. return session -> {
    134. Query<Long> countQuery = session.createQuery("SELECT count(player_hibernate) FROM PlayerBean player_hibernate", Long.class);
    135. return countQuery.getSingleResult();
    136. };
    137. }
    138. }
    139. public static void main(String[] args) {
    140. // 1. Create a SessionFactory based on our hibernate.cfg.xml configuration
    141. // file, which defines how to connect to the database.
    142. SessionFactory sessionFactory
    143. = new Configuration()
    144. .configure("hibernate.cfg.xml")
    145. .addAnnotatedClass(PlayerBean.class)
    146. .buildSessionFactory();
    147. try (Session session = sessionFactory.openSession()) {
    148. // 2. And then, create DAO to manager your data.
    149. PlayerDAO playerDAO = new PlayerDAO();
    150. // 3. Run some simple examples.
    151. // Create a player who has 1 coin and 1 goods.
    152. playerDAO.runTransaction(session, playerDAO.createPlayers(Collections.singletonList(
    153. new PlayerBean("test", 1, 1))));
    154. // Get a player.
    155. PlayerBean testPlayer = (PlayerBean)playerDAO.runTransaction(session, playerDAO.getPlayerByID("test"));
    156. System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n",
    157. testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods());
    158. // Count players amount.
    159. Long count = (Long)playerDAO.runTransaction(session, playerDAO.countPlayers());
    160. System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count);
    161. // Print 3 players.
    162. playerDAO.runTransaction(session, playerDAO.printPlayers(3));
    163. // 4. Explore more.
    164. // Player 1: id is "1", has only 100 coins.
    165. // Player 2: id is "2", has 114514 coins, and 20 goods.
    166. PlayerBean player1 = new PlayerBean("1", 100, 0);
    167. PlayerBean player2 = new PlayerBean("2", 114514, 20);
    168. // Create two players "by hand", using the INSERT statement on the backend.
    169. int addedCount = (Integer)playerDAO.runTransaction(session,
    170. playerDAO.createPlayers(Arrays.asList(player1, player2)));
    171. System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount);
    172. // Player 1 wants to buy 10 goods from player 2.
    173. // It will cost 500 coins, but player 1 cannot afford it.
    174. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail");
    175. Integer updatedCount = (Integer)playerDAO.runTransaction(session,
    176. playerDAO.buyGoods(player2.getId(), player1.getId(), 10, 500));
    177. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    178. // So player 1 has to reduce the incoming quantity to two.
    179. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success");
    180. updatedCount = (Integer)playerDAO.runTransaction(session,
    181. playerDAO.buyGoods(player2.getId(), player1.getId(), 2, 100));
    182. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    183. } finally {
    184. sessionFactory.close();
    185. }
    186. }
    187. }

    进入目录 plain-java-jdbc

    1. cd plain-java-jdbc

    目录结构如下所示:

    1. .
    2. ├── Makefile
    3. ├── plain-java-jdbc.iml
    4. ├── pom.xml
    5. └── src
    6. └── main
    7. ├── java
    8. └── com
    9. └── pingcap
    10. └── JDBCExample.java
    11. └── resources
    12. └── dbinit.sql

    其中,dbinit.sql 为数据表初始化语句:

    1. USE test;
    2. DROP TABLE IF EXISTS player;
    3. CREATE TABLE player (
    4. `id` VARCHAR(36),
    5. `coins` INTEGER,
    6. `goods` INTEGER,
    7. PRIMARY KEY (`id`)
    8. );

    JDBCExample.javaplain-java-jdbc 这个示例程序的主体。因为 TiDB 与 MySQL 协议兼容,因此,需要初始化一个 MySQL 协议的数据源 MysqlDataSource,以此连接到 TiDB。并在其后,初始化 PlayerDAO,用来管理数据对象,进行增删改查等操作。

    PlayerDAO 是程序用来管理数据对象的类。其中 DAO 是 的缩写。在其中定义了一系列数据的操作方法,用来对提供数据的写入能力。

    PlayerBean 是数据实体类,为数据库表在程序内的映射。PlayerBean 的每个属性都对应着 player 表的一个字段。

    1. package com.pingcap;
    2. import com.mysql.cj.jdbc.MysqlDataSource;
    3. import java.sql.Connection;
    4. import java.sql.PreparedStatement;
    5. import java.sql.ResultSet;
    6. import java.sql.SQLException;
    7. import java.util.*;
    8. /**
    9. * Main class for the basic JDBC example.
    10. **/
    11. public class JDBCExample
    12. {
    13. public static class PlayerBean {
    14. private String id;
    15. private Integer coins;
    16. private Integer goods;
    17. public PlayerBean() {
    18. }
    19. public PlayerBean(String id, Integer coins, Integer goods) {
    20. this.id = id;
    21. this.coins = coins;
    22. this.goods = goods;
    23. }
    24. public String getId() {
    25. return id;
    26. }
    27. public void setId(String id) {
    28. this.id = id;
    29. }
    30. public Integer getCoins() {
    31. return coins;
    32. }
    33. public void setCoins(Integer coins) {
    34. this.coins = coins;
    35. }
    36. public Integer getGoods() {
    37. return goods;
    38. }
    39. public void setGoods(Integer goods) {
    40. this.goods = goods;
    41. }
    42. @Override
    43. public String toString() {
    44. return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n",
    45. "id", this.id, "coins", this.coins, "goods", this.goods);
    46. }
    47. }
    48. /**
    49. * Data access object used by 'ExampleDataSource'.
    50. * Example for CURD and bulk insert.
    51. */
    52. public static class PlayerDAO {
    53. private final MysqlDataSource ds;
    54. private final Random rand = new Random();
    55. PlayerDAO(MysqlDataSource ds) {
    56. this.ds = ds;
    57. }
    58. /**
    59. * Create players by passing in a List of PlayerBean.
    60. *
    61. * @param players Will create players list
    62. * @return The number of create accounts
    63. */
    64. public int createPlayers(List<PlayerBean> players){
    65. int rows = 0;
    66. Connection connection = null;
    67. PreparedStatement preparedStatement = null;
    68. try {
    69. connection = ds.getConnection();
    70. preparedStatement = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)");
    71. } catch (SQLException e) {
    72. System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n",
    73. e.getSQLState(), e.getCause(), e.getMessage());
    74. e.printStackTrace();
    75. return -1;
    76. }
    77. try {
    78. for (PlayerBean player : players) {
    79. preparedStatement.setString(1, player.getId());
    80. preparedStatement.setInt(2, player.getCoins());
    81. preparedStatement.setInt(3, player.getGoods());
    82. preparedStatement.execute();
    83. rows += preparedStatement.getUpdateCount();
    84. }
    85. } catch (SQLException e) {
    86. System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n",
    87. e.getSQLState(), e.getCause(), e.getMessage());
    88. e.printStackTrace();
    89. } finally {
    90. try {
    91. connection.close();
    92. } catch (SQLException e) {
    93. e.printStackTrace();
    94. }
    95. }
    96. System.out.printf("\n[createPlayers]:\n '%s'\n", preparedStatement);
    97. return rows;
    98. }
    99. /**
    100. * Buy goods and transfer funds between one player and another in one transaction.
    101. * @param sellId Sell player id.
    102. * @param buyId Buy player id.
    103. * @param amount Goods amount, if sell player has not enough goods, the trade will break.
    104. * @param price Price should pay, if buy player has not enough coins, the trade will break.
    105. *
    106. * @return The number of effected players.
    107. */
    108. public int buyGoods(String sellId, String buyId, Integer amount, Integer price) {
    109. int effectPlayers = 0;
    110. Connection connection = null;
    111. try {
    112. connection = ds.getConnection();
    113. } catch (SQLException e) {
    114. System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n",
    115. e.getSQLState(), e.getCause(), e.getMessage());
    116. e.printStackTrace();
    117. return effectPlayers;
    118. }
    119. try {
    120. connection.setAutoCommit(false);
    121. PreparedStatement playerQuery = connection.prepareStatement("SELECT * FROM player WHERE id=? OR id=? FOR UPDATE");
    122. playerQuery.setString(1, sellId);
    123. playerQuery.setString(2, buyId);
    124. playerQuery.execute();
    125. PlayerBean sellPlayer = null;
    126. PlayerBean buyPlayer = null;
    127. PlayerBean player = new PlayerBean(
    128. playerQueryResultSet.getString("id"),
    129. playerQueryResultSet.getInt("coins"),
    130. playerQueryResultSet.getInt("goods")
    131. );
    132. System.out.println("\n[buyGoods]:\n 'check goods and coins enough'");
    133. System.out.println(player);
    134. if (sellId.equals(player.getId())) {
    135. sellPlayer = player;
    136. } else {
    137. buyPlayer = player;
    138. }
    139. }
    140. if (sellPlayer == null || buyPlayer == null) {
    141. throw new SQLException("player not exist.");
    142. }
    143. if (sellPlayer.getGoods().compareTo(amount) < 0) {
    144. throw new SQLException(String.format("sell player %s goods not enough.", sellId));
    145. }
    146. if (buyPlayer.getCoins().compareTo(price) < 0) {
    147. throw new SQLException(String.format("buy player %s coins not enough.", buyId));
    148. }
    149. PreparedStatement transfer = connection.prepareStatement("UPDATE player set goods = goods + ?, coins = coins + ? WHERE id=?");
    150. transfer.setInt(1, -amount);
    151. transfer.setInt(2, price);
    152. transfer.setString(3, sellId);
    153. transfer.execute();
    154. effectPlayers += transfer.getUpdateCount();
    155. transfer.setInt(1, amount);
    156. transfer.setInt(2, -price);
    157. transfer.setString(3, buyId);
    158. transfer.execute();
    159. effectPlayers += transfer.getUpdateCount();
    160. connection.commit();
    161. System.out.println("\n[buyGoods]:\n 'trade success'");
    162. } catch (SQLException e) {
    163. System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n",
    164. e.getSQLState(), e.getCause(), e.getMessage());
    165. try {
    166. System.out.println("[buyGoods] Rollback");
    167. connection.rollback();
    168. } catch (SQLException ex) {
    169. // do nothing
    170. }
    171. } finally {
    172. try {
    173. connection.close();
    174. } catch (SQLException e) {
    175. // do nothing
    176. }
    177. }
    178. return effectPlayers;
    179. }
    180. /**
    181. * Get the player info by id.
    182. *
    183. * @param id Player id.
    184. * @return The player of this id.
    185. */
    186. public PlayerBean getPlayer(String id) {
    187. PlayerBean player = null;
    188. try (Connection connection = ds.getConnection()) {
    189. PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player WHERE id = ?");
    190. preparedStatement.setString(1, id);
    191. preparedStatement.execute();
    192. ResultSet res = preparedStatement.executeQuery();
    193. if(!res.next()) {
    194. System.out.printf("No players in the table with id %s", id);
    195. } else {
    196. player = new PlayerBean(res.getString("id"), res.getInt("coins"), res.getInt("goods"));
    197. }
    198. } catch (SQLException e) {
    199. System.out.printf("PlayerDAO.getPlayer ERROR: { state => %s, cause => %s, message => %s }\n",
    200. e.getSQLState(), e.getCause(), e.getMessage());
    201. }
    202. return player;
    203. }
    204. /**
    205. * Insert randomized account data (id, coins, goods) using the JDBC fast path for
    206. * bulk inserts. The fastest way to get data into TiDB is using the
    207. * TiDB Lightning(https://docs.pingcap.com/tidb/stable/tidb-lightning-overview).
    208. * However, if you must bulk insert from the application using INSERT SQL, the best
    209. * option is the method shown here. It will require the following:
    210. *
    211. * Add `rewriteBatchedStatements=true` to your JDBC connection settings.
    212. * Setting rewriteBatchedStatements to true now causes CallableStatements
    213. * with batched arguments to be re-written in the form "CALL (...); CALL (...); ..."
    214. * to send the batch in as few client/server round trips as possible.
    215. * https://dev.mysql.com/doc/relnotes/connector-j/5.1/en/news-5-1-3.html
    216. *
    217. * You can see the `rewriteBatchedStatements` param effect logic at
    218. * implement function: `com.mysql.cj.jdbc.StatementImpl.executeBatchUsingMultiQueries`
    219. *
    220. * @param total Add players amount.
    221. * @param batchSize Bulk insert size for per batch.
    222. *
    223. * @return The number of new accounts inserted.
    224. */
    225. public int bulkInsertRandomPlayers(Integer total, Integer batchSize) {
    226. int totalNewPlayers = 0;
    227. try (Connection connection = ds.getConnection()) {
    228. // We're managing the commit lifecycle ourselves, so we can
    229. // control the size of our batch inserts.
    230. connection.setAutoCommit(false);
    231. // In this example we are adding 500 rows to the database,
    232. // but it could be any number. What's important is that
    233. // the batch size is 128.
    234. try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)")) {
    235. for (int i=0; i<=(total/batchSize);i++) {
    236. for (int j=0; j<batchSize; j++) {
    237. String id = UUID.randomUUID().toString();
    238. pstmt.setString(1, id);
    239. pstmt.setInt(2, rand.nextInt(10000));
    240. pstmt.setInt(3, rand.nextInt(10000));
    241. pstmt.addBatch();
    242. }
    243. int[] count = pstmt.executeBatch();
    244. totalNewPlayers += count.length;
    245. System.out.printf("\nPlayerDAO.bulkInsertRandomPlayers:\n '%s'\n", pstmt);
    246. System.out.printf(" => %s row(s) updated in this batch\n", count.length);
    247. }
    248. connection.commit();
    249. } catch (SQLException e) {
    250. System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
    251. e.getSQLState(), e.getCause(), e.getMessage());
    252. }
    253. } catch (SQLException e) {
    254. System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
    255. e.getSQLState(), e.getCause(), e.getMessage());
    256. }
    257. return totalNewPlayers;
    258. }
    259. /**
    260. * Print a subset of players from the data store by limit.
    261. *
    262. * @param limit Print max size.
    263. */
    264. public void printPlayers(Integer limit) {
    265. try (Connection connection = ds.getConnection()) {
    266. PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player LIMIT ?");
    267. preparedStatement.setInt(1, limit);
    268. preparedStatement.execute();
    269. ResultSet res = preparedStatement.executeQuery();
    270. while (!res.next()) {
    271. PlayerBean player = new PlayerBean(res.getString("id"),
    272. res.getInt("coins"), res.getInt("goods"));
    273. System.out.println("\n[printPlayers]:\n" + player);
    274. }
    275. } catch (SQLException e) {
    276. System.out.printf("PlayerDAO.printPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
    277. e.getSQLState(), e.getCause(), e.getMessage());
    278. }
    279. }
    280. /**
    281. * Count players from the data store.
    282. *
    283. * @return All players count
    284. */
    285. public int countPlayers() {
    286. int count = 0;
    287. try (Connection connection = ds.getConnection()) {
    288. PreparedStatement preparedStatement = connection.prepareStatement("SELECT count(*) FROM player");
    289. preparedStatement.execute();
    290. ResultSet res = preparedStatement.executeQuery();
    291. if(res.next()) {
    292. count = res.getInt(1);
    293. }
    294. } catch (SQLException e) {
    295. System.out.printf("PlayerDAO.countPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
    296. e.getSQLState(), e.getCause(), e.getMessage());
    297. }
    298. return count;
    299. }
    300. }
    301. public static void main(String[] args) {
    302. // 1. Configure the example database connection.
    303. // 1.1 Create a mysql data source instance.
    304. MysqlDataSource mysqlDataSource = new MysqlDataSource();
    305. // 1.2 Set server name, port, database name, username and password.
    306. mysqlDataSource.setServerName("localhost");
    307. mysqlDataSource.setPortNumber(4000);
    308. mysqlDataSource.setDatabaseName("test");
    309. mysqlDataSource.setUser("root");
    310. mysqlDataSource.setPassword("");
    311. // Or you can use jdbc string instead.
    312. // mysqlDataSource.setURL("jdbc:mysql://{host}:{port}/test?user={user}&password={password}");
    313. // 2. And then, create DAO to manager your data.
    314. PlayerDAO dao = new PlayerDAO(mysqlDataSource);
    315. // 3. Run some simple examples.
    316. // Create a player, who has a coin and a goods..
    317. dao.createPlayers(Collections.singletonList(new PlayerBean("test", 1, 1)));
    318. // Get a player.
    319. PlayerBean testPlayer = dao.getPlayer("test");
    320. System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n",
    321. testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods());
    322. // Create players with bulk inserts. Insert 1919 players totally, with 114 players per batch.
    323. int addedCount = dao.bulkInsertRandomPlayers(1919, 114);
    324. System.out.printf("PlayerDAO.bulkInsertRandomPlayers:\n => %d total inserted players\n", addedCount);
    325. // Count players amount.
    326. int count = dao.countPlayers();
    327. System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count);
    328. // Print 3 players.
    329. dao.printPlayers(3);
    330. // 4. Explore more.
    331. // Player 1: id is "1", has only 100 coins.
    332. // Player 2: id is "2", has 114514 coins, and 20 goods.
    333. PlayerBean player1 = new PlayerBean("1", 100, 0);
    334. PlayerBean player2 = new PlayerBean("2", 114514, 20);
    335. // Create two players "by hand", using the INSERT statement on the backend.
    336. addedCount = dao.createPlayers(Arrays.asList(player1, player2));
    337. System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount);
    338. // Player 1 wants to buy 10 goods from player 2.
    339. // It will cost 500 coins, but player 1 cannot afford it.
    340. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail");
    341. int updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 10, 500);
    342. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    343. // So player 1 has to reduce the incoming quantity to two.
    344. System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success");
    345. updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 2, 100);
    346. System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
    347. }
    348. }

    本节将逐步介绍代码的运行方法。

    • 使用 Mybatis(推荐)
    • 使用 Hibernate(推荐)
    • 使用 JDBC

    Java - 图4

    小贴士

    在 Gitpod Playground 中尝试 Mybatis:现在就试试

    使用 Mybatis 时,需手动初始化数据库表。若你本地已经安装了 mysql-client,且使用本地集群,可直接在 plain-java-mybatis 目录下通过 make prepare 运行:

    1. make prepare

    或直接执行:

    若你不使用本地集群,或未安装 mysql-client,请直接登录你的集群,并运行 src/main/resources/dbinit.sql 文件内的 SQL 语句。

    小贴士

    在 Gitpod Playground 中尝试 Hibernate:

    Java - 图6

    小贴士

    在 Gitpod Playground 中尝试 JDBC:现在就试试

    使用 JDBC 时,需手动初始化数据库表,若你本地已经安装了 mysql-client,且使用本地集群,可直接在 plain-java-jdbc 目录下运行:

    1. make mysql

    或直接执行:

    1. mysql --host 127.0.0.1 --port 4000 -u root<src/main/resources/dbinit.sql

    若你不使用本地集群,或未安装 mysql-client,请直接登录你的集群,并运行 src/main/resources/dbinit.sql 文件内的 SQL 语句。

    • 使用 Mybatis(推荐)
    • 使用 Hibernate(推荐)
    • 使用 JDBC

    若你使用 TiDB Cloud Serverless Tier 集群,更改 mybatis-config.xml 内关于 dataSource.urldataSource.usernamedataSource.password 的参数:

    1. <?xml version="1.0" encoding="UTF-8" ?>
    2. <!DOCTYPE configuration
    3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
    5. <configuration>
    6. <settings>
    7. <setting name="cacheEnabled" value="true"/>
    8. <setting name="lazyLoadingEnabled" value="false"/>
    9. <setting name="aggressiveLazyLoading" value="true"/>
    10. <setting name="logImpl" value="LOG4J"/>
    11. </settings>
    12. <typeAliases>
    13. <package name="com.pingcap.dao"/>
    14. </typeAliases>
    15. <environments default="development">
    16. <environment id="development">
    17. <!-- JDBC transaction manager -->
    18. <transactionManager type="JDBC"/>
    19. <!-- Database pool -->
    20. <dataSource type="POOLED">
    21. <property name="driver" value="com.mysql.jdbc.Driver"/>
    22. <property name="url" value="jdbc:mysql://127.0.0.1:4000/test"/>
    23. <property name="username" value="root"/>
    24. <property name="password" value=""/>
    25. </dataSource>
    26. </environment>
    27. </environments>
    28. <mappers>
    29. <mapper resource="mapper/PlayerMapper.xml"/>
    30. <mapper resource="mapper/PlayerMapperEx.xml"/>
    31. </mappers>
    32. </configuration>

    若你设定的密码为 123456,而且从 TiDB Cloud Serverless Tier 集群面板中得到的连接信息为:

    • Endpoint: xxx.tidbcloud.com
    • Port: 4000
    • User: 2aEp24QWEDLqRFs.root

    那么此处应将配置文件中 dataSource 节点内更改为:

    1. <?xml version="1.0" encoding="UTF-8" ?>
    2. <!DOCTYPE configuration
    3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
    5. ...
    6. <!-- Database pool -->
    7. <dataSource type="POOLED">
    8. <property name="driver" value="com.mysql.jdbc.Driver"/>
    9. <property name="url" value="jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&amp;enabledTLSProtocols=TLSv1.2,TLSv1.3"/>
    10. <property name="username" value="2aEp24QWEDLqRFs.root"/>
    11. <property name="password" value="123456"/>
    12. </dataSource>
    13. ...
    14. </configuration>

    若你使用 TiDB Cloud Serverless Tier 集群,更改 hibernate.cfg.xml 内关于 hibernate.connection.urlhibernate.connection.usernamehibernate.connection.password 的参数:

    1. <?xml version='1.0' encoding='utf-8'?>
    2. <!DOCTYPE hibernate-configuration PUBLIC
    3. "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    4. "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    5. <hibernate-configuration>
    6. <session-factory>
    7. <!-- Database connection settings -->
    8. <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
    9. <property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property>
    10. <property name="hibernate.connection.url">jdbc:mysql://localhost:4000/test</property>
    11. <property name="hibernate.connection.username">root</property>
    12. <property name="hibernate.connection.password"></property>
    13. <property name="hibernate.connection.autocommit">false</property>
    14. <!-- Required so a table can be created from the 'PlayerDAO' class -->
    15. <property name="hibernate.hbm2ddl.auto">create-drop</property>
    16. <!-- Optional: Show SQL output for debugging -->
    17. <property name="hibernate.show_sql">true</property>
    18. <property name="hibernate.format_sql">true</property>
    19. </session-factory>
    20. </hibernate-configuration>

    若你设定的密码为 123456,而且从 TiDB Cloud Serverless Tier 集群面板中得到的连接信息为:

    • Endpoint: xxx.tidbcloud.com
    • Port: 4000
    • User: 2aEp24QWEDLqRFs.root

    那么此处应将配置文件更改为:

    1. <?xml version='1.0' encoding='utf-8'?>
    2. <!DOCTYPE hibernate-configuration PUBLIC
    3. "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    4. "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    5. <hibernate-configuration>
    6. <session-factory>
    7. <!-- Database connection settings -->
    8. <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
    9. <property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property>
    10. <property name="hibernate.connection.url">jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&amp;enabledTLSProtocols=TLSv1.2,TLSv1.3</property>
    11. <property name="hibernate.connection.username">2aEp24QWEDLqRFs.root</property>
    12. <property name="hibernate.connection.password">123456</property>
    13. <property name="hibernate.connection.autocommit">false</property>
    14. <!-- Required so a table can be created from the 'PlayerDAO' class -->
    15. <property name="hibernate.hbm2ddl.auto">create-drop</property>
    16. <!-- Optional: Show SQL output for debugging -->
    17. <property name="hibernate.show_sql">true</property>
    18. <property name="hibernate.format_sql">true</property>
    19. </session-factory>
    20. </hibernate-configuration>

    若你使用 TiDB Cloud Serverless Tier 集群,更改 JDBCExample.java 内关于 Host、Port、User、Password 的参数:

    1. mysqlDataSource.setServerName("localhost");
    2. mysqlDataSource.setPortNumber(4000);
    3. mysqlDataSource.setDatabaseName("test");
    4. mysqlDataSource.setUser("root");
    5. mysqlDataSource.setPassword("");

    若你设定的密码为 123456,而且从 TiDB Cloud Serverless Tier 集群面板中得到的连接信息为:

    • Endpoint: xxx.tidbcloud.com
    • Port: 4000
    • User: 2aEp24QWEDLqRFs.root

    那么此处应将参数更改为:

    1. mysqlDataSource.setServerName("xxx.tidbcloud.com");
    2. mysqlDataSource.setPortNumber(4000);
    3. mysqlDataSource.setDatabaseName("test");
    4. mysqlDataSource.setUser("2aEp24QWEDLqRFs.root");
    5. mysqlDataSource.setPassword("123456");
    6. mysqlDataSource.setSslMode(PropertyDefinitions.SslMode.VERIFY_IDENTITY.name());
    7. mysqlDataSource.setEnabledTLSProtocols("TLSv1.2,TLSv1.3");
    • 使用 Mybatis(推荐)
    • 使用 Hibernate(推荐)
    • 使用 JDBC

    运行 make,这是以下四个操作的组合:

    • 创建表 (make prepare):

      1. mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql
      2. mysql --host 127.0.0.1 --port 4000 -u root -e "TRUNCATE test.player"
    • 清理并构建 (make gen):

      1. rm -f src/main/java/com/pingcap/model/Player.java
      2. rm -f src/main/java/com/pingcap/model/PlayerMapper.java
      3. rm -f src/main/resources/mapper/PlayerMapper.xml
      4. mvn mybatis-generator:generate
    • 清理并构建 (make build):mvn clean package

    • 运行 (make run):java -jar target/plain-java-mybatis-0.0.1-jar-with-dependencies.jar

    你也可以单独运行这四个 make 命令或原生命令。

    运行 make,这是以下两个操作的组合:

    • 清理并构建 (make build):mvn clean package
    • 运行 (make run):java -jar target/plain-java-hibernate-0.0.1-jar-with-dependencies.jar

    你也可以单独运行这两个 make 命令或原生命令。

    运行 make,这是以下两个操作的组合:

    • 清理并构建 (make build):
    • 运行 (make run): java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar

    你也可以单独运行这两个 make 命令或原生命令。

    • 使用 Mybatis(推荐)
    • 使用 Hibernate(推荐)

    Mybatis 预期输出