暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

示例程序丨TiDB 和 SQLAlchemy 的简单 CRUD 应用程序

PingCAP 2023-08-29
239

SQLAlchemy 为当前比较流行的开源 Python ORM 之一。

本文档将展示如何使用 TiDB 和 SQLAlchemy 来构造一个简单的 CRUD 应用程序。

注意

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

第 1 步:启动你的 TiDB 集群

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

使用 TiDB Serverless 集群

详细步骤,请参考:创建 TiDB Serverless 集群

使用本地集群

详细步骤,请参考:部署本地测试 TiDB 集群部署正式 TiDB 集群

第 2 步:获取代码

git clone https://github.com/pingcap-inc/tidb-example-python.git

此处将以 SQLAlchemy 1.4.44 版本进行说明。

import uuid from typing import List from sqlalchemy import create_engine, String, Column, Integer, select, func from sqlalchemy.orm import declarative_base, sessionmaker engine = create_engine('mysql://root:@127.0.0.1:4000/test') Base = declarative_base() Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) class Player(Base): __tablename__ = "player" id = Column(String(36), primary_key=True) coins = Column(Integer) goods = Column(Integer) def __repr__(self): return f'Player(id={self.id!r}, coins={self.coins!r}, goods={self.goods!r})' def random_player(amount: int) -> List[Player]: players = [] for _ in range(amount): players.append(Player(id=uuid.uuid4(), coins=10000, goods=10000)) return players def simple_example() -> None: with Session() as session: # create a player, who has a coin and a goods. session.add(Player(id="test", coins=1, goods=1)) # get this player, and print it. get_test_stmt = select(Player).where(Player.id == "test") for player in session.scalars(get_test_stmt): print(player) # create players with bulk inserts. # insert 1919 players totally, with 114 players per batch. # each player has a random UUID player_list = random_player(1919) for idx in range(0, len(player_list), 114): session.bulk_save_objects(player_list[idx:idx + 114]) # print the number of players count = session.query(func.count(Player.id)).scalar() print(f'number of players: {count}') # print 3 players. three_players = session.query(Player).limit(3).all() for player in three_players: print(player) session.commit() def trade_check(session: Session, sell_id: str, buy_id: str, amount: int, price: int) -> bool: # sell player goods check sell_player = session.query(Player.goods).filter(Player.id == sell_id).with_for_update().one() if sell_player.goods < amount: print(f'sell player {sell_id} goods not enough') return False # buy player coins check buy_player = session.query(Player.coins).filter(Player.id == buy_id).with_for_update().one() if buy_player.coins < price: print(f'buy player {buy_id} coins not enough') return False def trade(sell_id: str, buy_id: str, amount: int, price: int) -> None: with Session() as session: if trade_check(session, sell_id, buy_id, amount, price) is False: return # deduct the goods of seller, and raise his/her the coins session.query(Player).filter(Player.id == sell_id). \ update({'goods': Player.goods - amount, 'coins': Player.coins + price}) # deduct the coins of buyer, and raise his/her the goods session.query(Player).filter(Player.id == buy_id). \ update({'goods': Player.goods + amount, 'coins': Player.coins - price}) session.commit() print("trade success") def trade_example() -> None: with Session() as session: # create two players # player 1: id is "1", has only 100 coins. # player 2: id is "2", has 114514 coins, and 20 goods. session.add(Player(id="1", coins=100, goods=0)) session.add(Player(id="2", coins=114514, goods=20)) session.commit() # player 1 wants to buy 10 goods from player 2. # it will cost 500 coins, but player 1 cannot afford it. # so this trade will fail, and nobody will lose their coins or goods trade(sell_id="2", buy_id="1", amount=10, price=500) # then player 1 has to reduce the incoming quantity to 2. # this trade will be successful trade(sell_id="2", buy_id="1", amount=2, price=100) with Session() as session: traders = session.query(Player).filter(Player.id.in_(("1", "2"))).all() for player in traders: print(player) session.commit() simple_example() trade_example()

相较于直接使用 Driver,SQLAlchemy 屏蔽了创建数据库连接时,不同数据库差异的细节。SQLAlchemy 还封装了大量的操作,如会话管理、基本对象的 CRUD 等,极大地简化了代码量。

Player 类为数据库表在程序内的映射。Player 的每个属性都对应着 player 表的一个字段。SQLAlchemy 使用 Player 类为了给 SQLAlchemy 提供更多的信息,使用了形如以上示例中的 id = Column(String(36), primary_key=True) 的类型定义,用来指示字段类型和其附加属性。id = Column(String(36), primary_key=True) 表示 id 字段为 String 类型,对应数据库类型为 VARCHAR,长度为 36,且为主键。

关于 SQLAlchemy 的更多使用方法,你可以参考 SQLAlchemy 官网

第 3 步:运行代码

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

第 3 步第 1 部分:表初始化

本示例需手动初始化表,若你使用本地集群,可直接运行:

MySQL CLI

mysql --host 127.0.0.1 --port 4000 -u root < player_init.sql

MyCLI

mycli --host 127.0.0.1 --port 4000 -u root --no-warn < player_init.sql

若不使用本地集群,或未安装命令行客户端,请用喜欢的方式(如 Navicat、DBeaver 等 GUI 工具)直接登录集群,并运行 player_init.sql 文件内的 SQL 语句。

第 3 步第 2 部分:TiDB Cloud 更改参数

若你使用了 TiDB Serverless 集群,此处需使用系统本地的 CA 证书,并将证书路径记为 以供后续指代。你可以参考 Where is the CA root path on my system? 文档获取你所使用的操作系统的 CA 证书位置。

若你使用 TiDB Serverless 集群,更改 sqlalchemy_example.pycreate_engine 函数的入参:

engine = create_engine('mysql://root:@127.0.0.1:4000/test')

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

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

那么此处应将 create_engine 更改为:

engine = create_engine('mysql://2aEp24QWEDLqRFs.root:123456@xxx.tidbcloud.com:4000/test', connect_args={ "ssl_mode": "VERIFY_IDENTITY", "ssl": { "ca": "" } })

第 3 步第 3 部分:运行

运行前请先安装依赖:

pip3 install -r requirement.txt

当以后需要多次运行脚本时,请在每次运行前先依照表初始化一节再次进行表初始化。

python3 sqlalchemy_example.py

第 4 步:预期输出

SQLAlchemy 预期输出

最后修改时间:2023-08-29 22:57:37
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论