黄东旭解析 TiDB 的核心优势
1202
2023-06-21
本文关于如何使用 TiDB 和 Java 构造简单的 CRUD 应用程序的相关内容。
第 1 步:启动你的 TiDB 集群
本节将介绍 TiDB 集群的启动方法。
第 2 步:获取代码
git clone https://github.com/pingcap-inc/tidb-example-java.git
使用 Mybatis(推荐)
使用 Hibernate(推荐)
使用 JDBC
Mybatis 是当前比较流行的开源 Java 应用持久层框架,本文将以 Maven 插件的方式使用 MyBatis Generator 生成部分持久层代码。
进入目录 plain-java-mybatis
:
cd plain-java-mybatis
目录结构如下所示:
.
├── 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
其中,自动生成的文件有:
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 的配置文件,下面配置文件中添加了使用方法的说明:
<!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
在 pom.xml
中,以 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>
在 Maven 插件内引入后,可删除旧的生成文件后,通过命令 mvn mybatis-generate
生成新的文件。或者你也可以使用已经编写好的 make
命令,通过 make gen
来同时删除旧文件,并生成新文件。
注意
mybatis-generator.xml
中的属性 configuration.overwrite
仅可控制新生成的 Java 代码文件使用覆盖方式被写入,但 XML 映射文件仍会以追加方式写入。因此,推荐在 Mybaits Generator 生成新的文件前,先删除掉旧的文件。
Player.java
是使用 Mybatis Generator 生成出的数据实体类文件,为数据库表在程序内的映射。Player
类的每个属性都对应着 player
表的一个字段。
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
是使用 Mybatis Generator 生成出的映射接口文件,它仅规定了接口,接口的实现类是由 Mybatis 来通过 XML 或注解自动生成的:
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
是使用 Mybatis Generator 生成出的映射 XML 文件,Mybatis 将使用这个文件自动生成 PlayerMapper
接口的实现类:
<?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>
由于 Mybatis Generator 需要逆向生成源码,因此,数据库中需先行有此表结构,可使用 dbinit.sql
生成表结构:
USE test;DROP TABLE IF EXISTS player;CREATE TABLE player (
`id` VARCHAR(36),
`coins` INTEGER,
`goods` INTEGER, PRIMARY KEY (`id`)
);
额外拆分接口 PlayerMapperEx
继承 PlayerMapper
,并且编写与之匹配的 PlayerMapperEx.xml
。避免直接更改 PlayerMapper.java
和 PlayerMapper.xml
。这是为了规避 Mybatis Generator 的反复生成,影响到自行编写的代码。
在 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();
}
在 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
是程序用来管理数据对象的类。其中 DAO
是 Data Access Object 的缩写。在其中定义了一系列数据的操作方法,用于数据的写入。
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
是 plain-java-mybatis
这个示例程序的主类。其中定义了入口函数:
package com.pingcap;import com.pingcap.dao.PlayerDAO;import com.pingcap.model.Player;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;import java.util.Arrays;import java.util.Collections;public class MybatisExample { public static void main( String[] args ) throws IOException { // 1. Create a SqlSessionFactory based on our mybatis-config.xml configuration
// file, which defines how to connect to the database.
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. And then, create DAO to manager your data
PlayerDAO playerDAO = new PlayerDAO(); // 3. Run some simple examples.
// Create a player who has 1 coin and 1 goods.
playerDAO.runTransaction(sessionFactory, playerDAO.createPlayers(
Collections.singletonList(new Player("test", 1, 1)))); // Get a player.
Player testPlayer = (Player)playerDAO.runTransaction(sessionFactory, playerDAO.getPlayerByID("test"));
System.out.printf("PlayerDAO.getPlayer:\n => id: %s\n => coins: %s\n => goods: %s\n",
testPlayer.getId(), testPlayer.getCoins(), testPlayer.getGoods()); // Count players amount.
Integer count = (Integer)playerDAO.runTransaction(sessionFactory, playerDAO.countPlayers());
System.out.printf("PlayerDAO.countPlayers:\n => %d total players\n", count); // Print 3 players.
playerDAO.runTransaction(sessionFactory, playerDAO.printPlayers(3)); // 4. Getting further.
// Player 1: id is "1", has only 100 coins.
// Player 2: id is "2", has 114514 coins, and 20 goods.
Player player1 = new Player("1", 100, 0); Player player2 = new Player("2", 114514, 20); // Create two players "by hand", using the INSERT statement on the backend.
int addedCount = (Integer)playerDAO.runTransaction(sessionFactory,
playerDAO.createPlayers(Arrays.asList(player1, player2)));
System.out.printf("PlayerDAO.createPlayers:\n => %d total inserted players\n", addedCount); // Player 1 wants to buy 10 goods from player 2.
// It will cost 500 coins, but player 1 cannot afford it.
System.out.println("\nPlayerDAO.buyGoods:\n => this trade will fail"); Integer updatedCount = (Integer)playerDAO.runTransaction(sessionFactory,
playerDAO.buyGoods(player2.getId(), player1.getId(), 10, 500));
System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount); // So player 1 has to reduce the incoming quantity to two.
System.out.println("\nPlayerDAO.buyGoods:\n => this trade will success");
updatedCount = (Integer)playerDAO.runTransaction(sessionFactory,
playerDAO.buyGoods(player2.getId(), player1.getId(), 2, 100));
System.out.printf("PlayerDAO.buyGoods:\n => %d total update players\n", updatedCount);
}
}
第 3 步:运行代码
本节将逐步介绍代码的运行方法。
第 3 步第 1 部分:JDBC 表初始化
使用 Mybatis(推荐)
使用 Hibernate(推荐)
使用 JDBC
小贴士
使用 Mybatis 时,需手动初始化数据库表。若你本地已经安装了 mysql-client
,且使用本地集群,可直接在 plain-java-mybatis
目录下通过 make prepare
运行:
make prepare
或直接执行:
mysql --host 127.0.0.1 --port 4000 -u root < src/main/resources/dbinit.sql
若你不使用本地集群,或未安装 mysql-client
,请直接登录你的集群,并运行 src/main/resources/dbinit.sql
文件内的 SQL 语句。
第 3 步第 2 部分:TiDB Cloud 更改参数
使用 Mybatis(推荐)
使用 Hibernate(推荐)
使用 JDBC
若你使用 TiDB Serverless 集群,更改 mybatis-config.xml
内关于 dataSource.url
、dataSource.username
、dataSource.password
的参数:
<?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>
若你设定的密码为 123456
,而且从 TiDB Serverless 集群面板中得到的连接信息为:
Endpoint: xxx.tidbcloud.com
Port: 4000
User: 2aEp24QWEDLqRFs.root
那么此处应将配置文件中 dataSource
节点内更改为:
<?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>
第 3 步第 3 部分:运行
使用 Mybatis(推荐)
使用 Hibernate(推荐)
使用 JDBC
运行 make
,这是以下四个操作的组合:
创建表 (make prepare
):
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
):
rm -f src/main/java/com/pingcap/model/Player.javarm -f src/main/java/com/pingcap/model/PlayerMapper.javarm -f src/main/resources/mapper/PlayerMapper.xml
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
命令或原生命令。
第 4 步:预期输出
Mybatis 预期输出
以上就是小编整理的关于TiDB 和 Java 的简单 CRUD 应用程序的相关内容。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。