You are viewing the archived documentation of TiDB, which no longer receives updates.

View latest LTS version docs

Build a Simple CRUD App with TiDB and Hibernate

Hibernate is a popular open-source Java ORM, and it supports TiDB dialect starting from v6.0.0.Beta2, which fits TiDB features well.

This document describes how to use TiDB and Hibernate 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

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.

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(); } } }

Step 3. Run the code

The following content introduces how to run the code step by step.

Step 3.1 Modify parameters for TiDB Cloud

If you are using a TiDB Serverless cluster, modify the 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&amp;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>

Step 3.2 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-hibernate-0.0.1-jar-with-dependencies.jar`

Or you can use the native commands:

mvn clean package java -jar target/plain-java-hibernate-0.0.1-jar-with-dependencies.jar

Or run the make command directly, which is a combination of make build and make run.

Step 4. Expected output

Hibernate Expected Output