Build a Simple CRUD App with TiDB and Java
This document describes how to use TiDB and Java to build a simple CRUD application.
Step 1. Launch your TiDB cluster
The following introduces how to start a TiDB cluster.
Use a TiDB Serverless cluster
For detailed steps, see Create a TiDB Serverless cluster.
Use a local cluster
For detailed steps, see Deploy a local test cluster or Deploy a TiDB Cluster Using TiUP.
Step 2. Get the code
git clone https://github.com/pingcap-inc/tidb-example-java.git
- Using Mybatis (Recommended)
- Using Hibernate (Recommended)
- Using JDBC
Compared with Mybatis, the JDBC implementation might be not a best practice, because you need to write error handling logic manually and cannot reuse code easily, which makes your code slightly redundant.
Mybatis is a popular open-source Java class persistence framework. The following uses MyBatis Generator as a Maven plugin to generate the persistence layer code.
Change to the plain-java-mybatis
directory:
cd plain-java-mybatis
The structure of this directory is as follows:
.
├── Makefile
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── pingcap
│ ├── MybatisExample.java
│ ├── dao
│ │ └── PlayerDAO.java
│ └── model
│ ├── Player.java
│ ├── PlayerMapper.java
│ └── PlayerMapperEx.java
└── resources
├── dbinit.sql
├── log4j.properties
├── mapper
│ ├── PlayerMapper.xml
│ └── PlayerMapperEx.xml
├── mybatis-config.xml
└── mybatis-generator.xml
The automatically generated files are:
src/main/java/com/pingcap/model/Player.java
: ThePlayer
entity class.src/main/java/com/pingcap/model/PlayerMapper.java
: The interface ofPlayerMapper
.src/main/resources/mapper/PlayerMapper.xml
: The XML mapping ofPlayer
. Mybatis uses this configuration to automatically generate the implementation class of thePlayerMapper
interface.
The strategy for generating these files is written in mybatis-generator.xml
, which is the configuration file for Mybatis Generator. There are comments in the following configuration file to describe how to use it.
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
<context/> entire document: https://mybatis.org/generator/configreference/context.html
context.id: A unique identifier you like
context.targetRuntime: Used to specify the runtime target for generated code.
It has MyBatis3DynamicSql / MyBatis3Kotlin / MyBatis3 / MyBatis3Simple 4 selection to choice.
-->
<context id="simple" targetRuntime="MyBatis3">
<!--
<commentGenerator/> entire document: https://mybatis.org/generator/configreference/commentGenerator.html
commentGenerator:
- property(suppressDate): remove timestamp in comments
- property(suppressAllComments): remove all comments
-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--
<jdbcConnection/> entire document: https://mybatis.org/generator/configreference/jdbcConnection.html
jdbcConnection.driverClass: The fully qualified class name for the JDBC driver used to access the database.
Used mysql-connector-java:5.1.49, should specify JDBC is com.mysql.jdbc.Driver
jdbcConnection.connectionURL: The JDBC connection URL used to access the database.
-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:4000/test?user=root" />
<!--
<javaModelGenerator/> entire document: https://mybatis.org/generator/configreference/javaModelGenerator.html
Model code file will be generated at ${targetProject}/${targetPackage}
javaModelGenerator:
- property(constructorBased): If it's true, generator will create constructor function in model
-->
<javaModelGenerator targetPackage="com.pingcap.model" targetProject="src/main/java">
<property name="constructorBased" value="true"/>
</javaModelGenerator>
<!--
<sqlMapGenerator/> entire document: https://mybatis.org/generator/configreference/sqlMapGenerator.html
XML SQL mapper file will be generated at ${targetProject}/${targetPackage}
-->
<sqlMapGenerator targetPackage="." targetProject="src/main/resources/mapper"/>
<!--
<javaClientGenerator/> entire document: https://mybatis.org/generator/configreference/javaClientGenerator.html
Java code mapper interface file will be generated at ${targetProject}/${targetPackage}
javaClientGenerator.type (context.targetRuntime is MyBatis3):
This attribute indicated Mybatis how to implement interface.
It has ANNOTATEDMAPPER / MIXEDMAPPER / XMLMAPPER 3 selection to choice.
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.pingcap.model" targetProject="src/main/java"/>
<!--
<table/> entire document: https://mybatis.org/generator/configreference/table.html
table.tableName: The name of the database table.
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.
table.enableCountByExample: Signifies whether a count by example statement should be generated.
table.enableUpdateByExample: Signifies whether an update by example statement should be generated.
table.enableDeleteByExample: Signifies whether a delete by example statement should be generated.
table.enableSelectByExample: Signifies whether a select by example statement should be generated.
table.selectByExampleQueryId: This value will be added to the select list of the select by example statement in this form: "'<value>' as QUERYID".
-->
<table tableName="player" domainObjectName="Player"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>
mybatis-generator.xml
is included in pom.xml
as the configuration of mybatis-generator-maven-plugin
.
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<configuration>
<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependencies>
</plugin>
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.
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.
package com.pingcap.model;
public class Player {
private String id;
private Integer coins;
private Integer goods;
public Player(String id, Integer coins, Integer goods) {
this.id = id;
this.coins = coins;
this.goods = goods;
}
public Player() {
super();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getCoins() {
return coins;
}
public void setCoins(Integer coins) {
this.coins = coins;
}
public Integer getGoods() {
return goods;
}
public void setGoods(Integer goods) {
this.goods = goods;
}
}
PlayerMapper.java
is a mapping interface file generated using Mybatis Generator. This file only defines the interface, and the implementation classes of interface are automatically generated using XML or annotations.
package com.pingcap.model;
import com.pingcap.model.Player;
public interface PlayerMapper {
int deleteByPrimaryKey(String id);
int insert(Player row);
int insertSelective(Player row);
Player selectByPrimaryKey(String id);
int updateByPrimaryKeySelective(Player row);
int updateByPrimaryKey(Player row);
}
PlayerMapper.xml
is a mapping XML file generated using Mybatis Generator. Mybatis uses this to automatically generate the implementation class of the PlayerMapper
interface.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pingcap.model.PlayerMapper">
<resultMap id="BaseResultMap" type="com.pingcap.model.Player">
<constructor>
<idArg column="id" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="coins" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="goods" javaType="java.lang.Integer" jdbcType="INTEGER" />
</constructor>
</resultMap>
<sql id="Base_Column_List">
id, coins, goods
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from player
where id = #{id,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String">
delete from player
where id = #{id,jdbcType=VARCHAR}
</delete>
<insert id="insert" parameterType="com.pingcap.model.Player">
insert into player (id, coins, goods
)
values (#{id,jdbcType=VARCHAR}, #{coins,jdbcType=INTEGER}, #{goods,jdbcType=INTEGER}
)
</insert>
<insert id="insertSelective" parameterType="com.pingcap.model.Player">
insert into player
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="coins != null">
coins,
</if>
<if test="goods != null">
goods,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=VARCHAR},
</if>
<if test="coins != null">
#{coins,jdbcType=INTEGER},
</if>
<if test="goods != null">
#{goods,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.pingcap.model.Player">
update player
<set>
<if test="coins != null">
coins = #{coins,jdbcType=INTEGER},
</if>
<if test="goods != null">
goods = #{goods,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="com.pingcap.model.Player">
update player
set coins = #{coins,jdbcType=INTEGER},
goods = #{goods,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>
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
.
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
:
package com.pingcap.model;
import java.util.List;
public interface PlayerMapperEx extends PlayerMapper {
Player selectByPrimaryKeyWithLock(String id);
List<Player> selectByLimit(Integer limit);
Integer count();
}
Define the mapping rules in PlayerMapperEx.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pingcap.model.PlayerMapperEx">
<resultMap id="BaseResultMap" type="com.pingcap.model.Player">
<constructor>
<idArg column="id" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="coins" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="goods" javaType="java.lang.Integer" jdbcType="INTEGER" />
</constructor>
</resultMap>
<sql id="Base_Column_List">
id, coins, goods
</sql>
<select id="selectByPrimaryKeyWithLock" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from player
where `id` = #{id,jdbcType=VARCHAR}
for update
</select>
<select id="selectByLimit" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from player
limit #{id,jdbcType=INTEGER}
</select>
<select id="count" resultType="java.lang.Integer">
select count(*) from player
</select>
</mapper>
PlayerDAO.java
is a class used to manage data, in which DAO
means 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.
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<PlayerMapperEx, Object> 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<PlayerMapperEx, Object> createPlayers(List<Player> 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<PlayerMapperEx, Object> 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<PlayerMapperEx, Object> getPlayerByID(String id) {
return playerMapperEx -> playerMapperEx.selectByPrimaryKey(id);
}
public Function<PlayerMapperEx, Object> printPlayers(Integer limit) {
return playerMapperEx -> {
List<Player> players = playerMapperEx.selectByLimit(limit);
for (Player player: players) {
System.out.println("\n[printPlayers]:\n" + player);
}
return 0;
};
}
public Function<PlayerMapperEx, Object> countPlayers() {
return PlayerMapperEx::count;
}
}
MybatisExample
is the main class of the plain-java-mybatis
sample application. It defines the entry functions:
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);
}
}
Compared with Hibernate, the JDBC implementation might be not a best practice, because you need to write error handling logic manually and cannot reuse code easily, which makes your code slightly redundant.
Hibernate is a popular open-source Java ORM, and it supports TiDB dialect starting from v6.0.0.Beta2
, which fits TiDB features well. The following instructions take v6.0.0.Beta2
as an example.
Change to the plain-java-hibernate
directory:
cd plain-java-hibernate
The structure of this directory is as follows:
.
├── Makefile
├── plain-java-hibernate.iml
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── pingcap
│ └── HibernateExample.java
└── resources
└── hibernate.cfg.xml
hibernate.cfg.xml
is the Hibernate configuration file:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:4000/test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.autocommit">false</property>
<!-- Required so a table can be created from the 'PlayerDAO' class -->
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- Optional: Show SQL output for debugging -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
</session-factory>
</hibernate-configuration>
HibernateExample.java
is the main body of the plain-java-hibernate
. Compared with JDBC, when using Hibernate, you only need to write the path of the configuration file, because Hibernate avoids differences in database creation between different databases.
PlayerDAO
is a class used to manage data, in which DAO
means Data Access Object. The class defines a set of data manipulation methods for writing data. Compared with JDBC, Hibernate encapsulates a large number of operations such as object mapping and CRUD of basic objects, which greatly simplifies the code.
PlayerBean
is a data entity class that is a mapping for tables. Each property of a PlayerBean
corresponds to a field in the player
table. Compared with JDBC, PlayerBean
in Hibernate adds annotations to indicate mapping relationships for more information.
package com.pingcap;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.JDBCException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@Entity
@Table(name = "player_hibernate")
class PlayerBean {
@Id
private String id;
@Column(name = "coins")
private Integer coins;
@Column(name = "goods")
private Integer goods;
public PlayerBean() {
}
public PlayerBean(String id, Integer coins, Integer goods) {
this.id = id;
this.coins = coins;
this.goods = goods;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getCoins() {
return coins;
}
public void setCoins(Integer coins) {
this.coins = coins;
}
public Integer getGoods() {
return goods;
}
public void setGoods(Integer goods) {
this.goods = goods;
}
@Override
public String toString() {
return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n",
"id", this.id, "coins", this.coins, "goods", this.goods);
}
}
/**
* Main class for the basic Hibernate example.
**/
public class HibernateExample
{
public static class PlayerDAO {
public static class NotEnoughException extends RuntimeException {
public NotEnoughException(String message) {
super(message);
}
}
// Run SQL code in a way that automatically handles the
// transaction retry logic so we don't have to duplicate it in
// various places.
public Object runTransaction(Session session, Function<Session, Object> 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<Session, Object> createPlayers(List<PlayerBean> 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<Session, Object> 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<Session, Object> getPlayerByID(String id) throws JDBCException {
return session -> session.get(PlayerBean.class, id);
}
public Function<Session, Object> printPlayers(Integer limit) throws JDBCException {
return session -> {
NativeQuery<PlayerBean> limitQuery = session.createNativeQuery("SELECT * FROM player_hibernate LIMIT :limit", PlayerBean.class);
limitQuery.setParameter("limit", limit);
List<PlayerBean> players = limitQuery.getResultList();
for (PlayerBean player: players) {
System.out.println("\n[printPlayers]:\n" + player);
}
return 0;
};
}
public Function<Session, Object> countPlayers() throws JDBCException {
return session -> {
Query<Long> 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();
}
}
}
Change to the plain-java-jdbc
directory:
cd plain-java-jdbc
The structure of this directory is as follows:
.
├── Makefile
├── plain-java-jdbc.iml
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── pingcap
│ └── JDBCExample.java
└── resources
└── dbinit.sql
You can find initialization statements for the table creation in dbinit.sql
:
USE test;
DROP TABLE IF EXISTS player;
CREATE TABLE player (
`id` VARCHAR(36),
`coins` INTEGER,
`goods` INTEGER,
PRIMARY KEY (`id`)
);
JDBCExample.java
is the main body of the plain-java-jdbc
. TiDB is highly compatible with the MySQL protocol, so you need to initialize a MySQL source instance MysqlDataSource
to connect to TiDB. Then, you can initialize PlayerDAO
for object management and use it to read, edit, add, and delete data.
PlayerDAO
is a class used to manage data, in which DAO
means Data Access Object. The class defines a set of data manipulation methods to provide the ability to write data.
PlayerBean
is a data entity class that is a mapping for tables. Each property of a PlayerBean
corresponds to a field in the player
table.
package com.pingcap;
import com.mysql.cj.jdbc.MysqlDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
/**
* Main class for the basic JDBC example.
**/
public class JDBCExample
{
public static class PlayerBean {
private String id;
private Integer coins;
private Integer goods;
public PlayerBean() {
}
public PlayerBean(String id, Integer coins, Integer goods) {
this.id = id;
this.coins = coins;
this.goods = goods;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getCoins() {
return coins;
}
public void setCoins(Integer coins) {
this.coins = coins;
}
public Integer getGoods() {
return goods;
}
public void setGoods(Integer goods) {
this.goods = goods;
}
@Override
public String toString() {
return String.format(" %-8s => %10s\n %-8s => %10s\n %-8s => %10s\n",
"id", this.id, "coins", this.coins, "goods", this.goods);
}
}
/**
* Data access object used by 'ExampleDataSource'.
* Example for CURD and bulk insert.
*/
public static class PlayerDAO {
private final MysqlDataSource ds;
private final Random rand = new Random();
PlayerDAO(MysqlDataSource ds) {
this.ds = ds;
}
/**
* Create players by passing in a List of PlayerBean.
*
* @param players Will create players list
* @return The number of create accounts
*/
public int createPlayers(List<PlayerBean> players){
int rows = 0;
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = ds.getConnection();
preparedStatement = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)");
} catch (SQLException e) {
System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
e.printStackTrace();
return -1;
}
try {
for (PlayerBean player : players) {
preparedStatement.setString(1, player.getId());
preparedStatement.setInt(2, player.getCoins());
preparedStatement.setInt(3, player.getGoods());
preparedStatement.execute();
rows += preparedStatement.getUpdateCount();
}
} catch (SQLException e) {
System.out.printf("[createPlayers] ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
System.out.printf("\n[createPlayers]:\n '%s'\n", preparedStatement);
return rows;
}
/**
* Buy goods and transfer funds between one player and another in one transaction.
* @param sellId Sell player id.
* @param buyId Buy player id.
* @param amount Goods amount, if sell player has not enough goods, the trade will break.
* @param price Price should pay, if buy player has not enough coins, the trade will break.
*
* @return The number of effected players.
*/
public int buyGoods(String sellId, String buyId, Integer amount, Integer price) {
int effectPlayers = 0;
Connection connection = null;
try {
connection = ds.getConnection();
} catch (SQLException e) {
System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
e.printStackTrace();
return effectPlayers;
}
try {
connection.setAutoCommit(false);
PreparedStatement playerQuery = connection.prepareStatement("SELECT * FROM player WHERE id=? OR id=? FOR UPDATE");
playerQuery.setString(1, sellId);
playerQuery.setString(2, buyId);
playerQuery.execute();
PlayerBean sellPlayer = null;
PlayerBean buyPlayer = null;
ResultSet playerQueryResultSet = playerQuery.getResultSet();
while (playerQueryResultSet.next()) {
PlayerBean player = new PlayerBean(
playerQueryResultSet.getString("id"),
playerQueryResultSet.getInt("coins"),
playerQueryResultSet.getInt("goods")
);
System.out.println("\n[buyGoods]:\n 'check goods and coins enough'");
System.out.println(player);
if (sellId.equals(player.getId())) {
sellPlayer = player;
} else {
buyPlayer = player;
}
}
if (sellPlayer == null || buyPlayer == null) {
throw new SQLException("player not exist.");
}
if (sellPlayer.getGoods().compareTo(amount) < 0) {
throw new SQLException(String.format("sell player %s goods not enough.", sellId));
}
if (buyPlayer.getCoins().compareTo(price) < 0) {
throw new SQLException(String.format("buy player %s coins not enough.", buyId));
}
PreparedStatement transfer = connection.prepareStatement("UPDATE player set goods = goods + ?, coins = coins + ? WHERE id=?");
transfer.setInt(1, -amount);
transfer.setInt(2, price);
transfer.setString(3, sellId);
transfer.execute();
effectPlayers += transfer.getUpdateCount();
transfer.setInt(1, amount);
transfer.setInt(2, -price);
transfer.setString(3, buyId);
transfer.execute();
effectPlayers += transfer.getUpdateCount();
connection.commit();
System.out.println("\n[buyGoods]:\n 'trade success'");
} catch (SQLException e) {
System.out.printf("[buyGoods] ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
try {
System.out.println("[buyGoods] Rollback");
connection.rollback();
} catch (SQLException ex) {
// do nothing
}
} finally {
try {
connection.close();
} catch (SQLException e) {
// do nothing
}
}
return effectPlayers;
}
/**
* Get the player info by id.
*
* @param id Player id.
* @return The player of this id.
*/
public PlayerBean getPlayer(String id) {
PlayerBean player = null;
try (Connection connection = ds.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player WHERE id = ?");
preparedStatement.setString(1, id);
preparedStatement.execute();
ResultSet res = preparedStatement.executeQuery();
if(!res.next()) {
System.out.printf("No players in the table with id %s", id);
} else {
player = new PlayerBean(res.getString("id"), res.getInt("coins"), res.getInt("goods"));
}
} catch (SQLException e) {
System.out.printf("PlayerDAO.getPlayer ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
}
return player;
}
/**
* Insert randomized account data (id, coins, goods) using the JDBC fast path for
* bulk inserts. The fastest way to get data into TiDB is using the
* TiDB Lightning(https://docs.pingcap.com/tidb/stable/tidb-lightning-overview).
* However, if you must bulk insert from the application using INSERT SQL, the best
* option is the method shown here. It will require the following:
*
* Add `rewriteBatchedStatements=true` to your JDBC connection settings.
* Setting rewriteBatchedStatements to true now causes CallableStatements
* with batched arguments to be re-written in the form "CALL (...); CALL (...); ..."
* to send the batch in as few client/server round trips as possible.
* https://dev.mysql.com/doc/relnotes/connector-j/5.1/en/news-5-1-3.html
*
* You can see the `rewriteBatchedStatements` param effect logic at
* implement function: `com.mysql.cj.jdbc.StatementImpl.executeBatchUsingMultiQueries`
*
* @param total Add players amount.
* @param batchSize Bulk insert size for per batch.
*
* @return The number of new accounts inserted.
*/
public int bulkInsertRandomPlayers(Integer total, Integer batchSize) {
int totalNewPlayers = 0;
try (Connection connection = ds.getConnection()) {
// We're managing the commit lifecycle ourselves, so we can
// control the size of our batch inserts.
connection.setAutoCommit(false);
// In this example we are adding 500 rows to the database,
// but it could be any number. What's important is that
// the batch size is 128.
try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)")) {
for (int i=0; i<=(total/batchSize);i++) {
for (int j=0; j<batchSize; j++) {
String id = UUID.randomUUID().toString();
pstmt.setString(1, id);
pstmt.setInt(2, rand.nextInt(10000));
pstmt.setInt(3, rand.nextInt(10000));
pstmt.addBatch();
}
int[] count = pstmt.executeBatch();
totalNewPlayers += count.length;
System.out.printf("\nPlayerDAO.bulkInsertRandomPlayers:\n '%s'\n", pstmt);
System.out.printf(" => %s row(s) updated in this batch\n", count.length);
}
connection.commit();
} catch (SQLException e) {
System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
}
} catch (SQLException e) {
System.out.printf("PlayerDAO.bulkInsertRandomPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
}
return totalNewPlayers;
}
/**
* Print a subset of players from the data store by limit.
*
* @param limit Print max size.
*/
public void printPlayers(Integer limit) {
try (Connection connection = ds.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM player LIMIT ?");
preparedStatement.setInt(1, limit);
preparedStatement.execute();
ResultSet res = preparedStatement.executeQuery();
while (!res.next()) {
PlayerBean player = new PlayerBean(res.getString("id"),
res.getInt("coins"), res.getInt("goods"));
System.out.println("\n[printPlayers]:\n" + player);
}
} catch (SQLException e) {
System.out.printf("PlayerDAO.printPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
}
}
/**
* Count players from the data store.
*
* @return All players count
*/
public int countPlayers() {
int count = 0;
try (Connection connection = ds.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT count(*) FROM player");
preparedStatement.execute();
ResultSet res = preparedStatement.executeQuery();
if(res.next()) {
count = res.getInt(1);
}
} catch (SQLException e) {
System.out.printf("PlayerDAO.countPlayers ERROR: { state => %s, cause => %s, message => %s }\n",
e.getSQLState(), e.getCause(), e.getMessage());
}
return count;
}
}
public static void main(String[] args) {
// 1. Configure the example database connection.
// 1.1 Create a mysql data source instance.
MysqlDataSource mysqlDataSource = new MysqlDataSource();
// 1.2 Set server name, port, database name, username and password.
mysqlDataSource.setServerName("localhost");
mysqlDataSource.setPortNumber(4000);
mysqlDataSource.setDatabaseName("test");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("");
// Or you can use jdbc string instead.
// mysqlDataSource.setURL("jdbc:mysql://{host}:{port}/test?user={user}&password={password}");
// 2. And then, create DAO to manager your data.
PlayerDAO dao = new PlayerDAO(mysqlDataSource);
// 3. Run some simple example.
// Create a player, has a coin and a goods.
dao.createPlayers(Collections.singletonList(new PlayerBean("test", 1, 1)));
// Get a player.
PlayerBean testPlayer = dao.getPlayer("test");
System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n",
testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods());
// Create players with bulk inserts, insert 1919 players totally, and per batch for 114 players.
int addedCount = dao.bulkInsertRandomPlayers(1919, 114);
System.out.printf("PlayerDAO.bulkInsertRandomPlayers:\n => %d total inserted players\n", addedCount);
// Count players amount.
int count = dao.countPlayers();
System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count);
// Print 3 players.
dao.printPlayers(3);
// 4. Getting further.
// Player 1: id is "1", has only 100 coins.
// Player 2: id is "2", has 114514 coins, and 20 goods.
PlayerBean player1 = new PlayerBean("1", 100, 0);
PlayerBean player2 = new PlayerBean("2", 114514, 20);
// Create two players "by hand", using the INSERT statement on the backend.
addedCount = dao.createPlayers(Arrays.asList(player1, player2));
System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount);
// Player 1 wants to buy 10 goods from player 2.
// It will cost 500 coins, but player 1 can't afford it.
System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail");
int updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 10, 500);
System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
// So player 1 have to reduce his incoming quantity to two.
System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success");
updatedCount = dao.buyGoods(player2.getId(), player1.getId(), 2, 100);
System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
}
}
Step 3. Run the code
The following content introduces how to run the code step by step.
Step 3.1 Table initialization
- Using Mybatis (Recommended)
- Using Hibernate (Recommended)
- Using JDBC
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:
make prepare
Or you can execute the following command:
mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql
If you are using a non-local cluster or MySQL client has not been installed, connect to your cluster and run the statement in the src/main/resources/dbinit.sql
file.
No need to initialize tables manually.
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:
make mysql
Or you can execute the following command:
mysql --host 127.0.0.1 --port 4000 -u root<src/main/resources/dbinit.sql
If you are using a non-local cluster or MySQL client has not been installed, connect to your cluster and run the statement in the src/main/resources/dbinit.sql
file.
Step 3.2 Modify parameters for TiDB Cloud
- Using Mybatis (Recommended)
- Using Hibernate (Recommended)
- Using JDBC
If you are using a TiDB Serverless cluster, modify the dataSource.url
, dataSource.username
, dataSource.password
in mybatis-config.xml
.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
<package name="com.pingcap.dao"/>
</typeAliases>
<environments default="development">
<environment id="development">
<!-- JDBC transaction manager -->
<transactionManager type="JDBC"/>
<!-- Database pool -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:4000/test"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/PlayerMapper.xml"/>
<mapper resource="mapper/PlayerMapperEx.xml"/>
</mappers>
</configuration>
Suppose that the password you set is 123456
, and the connection parameters you get from the cluster details page are the following:
- Endpoint:
xxx.tidbcloud.com
- Port:
4000
- User:
2aEp24QWEDLqRFs.root
In this case, you can modify the parameters in dataSource
node as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
...
<!-- Database pool -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3"/>
<property name="username" value="2aEp24QWEDLqRFs.root"/>
<property name="password" value="123456"/>
</dataSource>
...
</configuration>
If you are using a TiDB Serverless cluster, modify the hibernate.connection.url
, hibernate.connection.username
, hibernate.connection.password
in hibernate.cfg.xml
.
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:4000/test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.autocommit">false</property>
<!-- Required so a table can be created from the 'PlayerDAO' class -->
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- Optional: Show SQL output for debugging -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
</session-factory>
</hibernate-configuration>
Suppose that the password you set is 123456
, and the connection parameters you get from the cluster details page are the following:
- Endpoint:
xxx.tidbcloud.com
- Port:
4000
- User:
2aEp24QWEDLqRFs.root
In this case, you can modify the parameters as follows:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property>
<property name="hibernate.connection.url">jdbc:mysql://xxx.tidbcloud.com:4000/test?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3</property>
<property name="hibernate.connection.username">2aEp24QWEDLqRFs.root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.connection.autocommit">false</property>
<!-- Required so a table can be created from the 'PlayerDAO' class -->
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- Optional: Show SQL output for debugging -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
</session-factory>
</hibernate-configuration>
If you are using a TiDB Serverless cluster, modify the parameters of the host, port, user, and password in JDBCExample.java
:
mysqlDataSource.setServerName("localhost");
mysqlDataSource.setPortNumber(4000);
mysqlDataSource.setDatabaseName("test");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("");
Suppose that the password you set is 123456
, and the connection parameters you get from the cluster details page are the following:
- Endpoint:
xxx.tidbcloud.com
- Port:
4000
- User:
2aEp24QWEDLqRFs.root
In this case, you can modify the parameters as follows:
mysqlDataSource.setServerName("xxx.tidbcloud.com");
mysqlDataSource.setPortNumber(4000);
mysqlDataSource.setDatabaseName("test");
mysqlDataSource.setUser("2aEp24QWEDLqRFs.root");
mysqlDataSource.setPassword("123456");
mysqlDataSource.setSslMode(PropertyDefinitions.SslMode.VERIFY_IDENTITY.name());
mysqlDataSource.setEnabledTLSProtocols("TLSv1.2,TLSv1.3");
Step 3.3 Run
- Using Mybatis (Recommended)
- Using Hibernate (Recommended)
- Using JDBC
To run the code, you can run make prepare
, make gen
, make build
and make run
respectively:
make prepare
# this command executes :
# - `mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql`
# - `mysql --host 127.0.0.1 --port 4000 -u root -e "TRUNCATE test.player"`
make gen
# this command executes :
# - `rm -f src/main/java/com/pingcap/model/Player.java`
# - `rm -f src/main/java/com/pingcap/model/PlayerMapper.java`
# - `rm -f src/main/resources/mapper/PlayerMapper.xml`
# - `mvn mybatis-generator:generate`
make build # this command executes `mvn clean package`
make run # this command executes `java -jar target/plain-java-mybatis-0.0.1-jar-with-dependencies.jar`
Or you can use the native commands:
mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql
mysql --host 127.0.0.1 --port 4000 -u root -e "TRUNCATE test.player"
rm -f src/main/java/com/pingcap/model/Player.java
rm -f src/main/java/com/pingcap/model/PlayerMapper.java
rm -f src/main/resources/mapper/PlayerMapper.xml
mvn mybatis-generator:generate
mvn clean package
java -jar target/plain-java-mybatis-0.0.1-jar-with-dependencies.jar
Or run the make
command directly, which is a combination of make prepare
, make gen
, make build
and make run
.
To run the code, you can run make build
and make run
respectively:
make build # this command executes `mvn clean package`
make run # this command executes `java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar`
Or you can use the native commands:
mvn clean package
java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar
Or run the make
command directly, which is a combination of make build
and make run
.
To run the code, you can run make build
and make run
respectively:
make build # this command executes `mvn clean package`
make run # this command executes `java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar`
Or you can use the native commands:
mvn clean package
java -jar target/plain-java-jdbc-0.0.1-jar-with-dependencies.jar
Or run the make
command directly, which is a combination of make build
and make run
.
Step 4. Expected output
- Using Mybatis (Recommended)
- Using Hibernate (Recommended)
- Using JDBC