资讯动态

Oxyde ORM:异步优先、Rust驱动的新一代Python ORM实践指南

发布时间:2026/8/20 12:57:51 来源:尧图企业网站定制
1. 项目概述当Django ORM遇见异步与Rust如果你和我一样是从Django时代一路走过来的Python开发者那么对Model.objects.filter()这种清晰、直观的查询语法一定有着深厚的感情。它让数据库操作变得像说话一样自然。然而当我们大步迈入异步编程的时代面对FastAPI、asyncio构建的高性能应用时传统的同步ORM在性能上就显得有些力不从心了。另一方面像SQLAlchemy这样的“瑞士军刀”虽然功能强大但其学习曲线和“魔法”般的隐式行为有时也让追求明确性和类型安全的现代开发者感到头疼。正是在这种背景下我发现了Oxyde ORM。它像是一位深谙老派优雅又精通现代技艺的工匠。它的核心承诺非常明确给你Django ORM那种“说人话”的API体验但内核是异步优先的并且由高性能的Rust驱动同时将Pydantic v2的强类型验证和序列化能力作为一等公民。简单来说它试图在“开发者的幸福感”和“运行时的性能”之间找到一个黄金平衡点。对于正在构建需要与数据库频繁交互的现代异步API比如用FastAPI、Litestar的开发者或者任何厌倦了在复杂SQLAlchemy表达式和简单需求之间挣扎的团队Oxyde都值得你花上十分钟了解一下。2. 核心设计哲学显式优于魔法Oxyde的README里有一句话深得我心“focuses on explicitness over magic”。这可以说是它区别于许多其他ORM的灵魂所在。在深入代码之前理解这一点至关重要。2.1 何为“魔法”何为“显式”在ORM的语境里“魔法”通常指那些隐藏在幕后的、自动发生的、让开发者感到惊讶的行为。例如某些ORM会自动根据类名推断表名自动加载关联数据N1查询问题的根源之一或者在保存对象时静默地转换数据类型。这些功能在简单场景下很方便但在复杂应用或调试时它们会成为不确定性的来源。Oxyde选择了另一条路。它要求你明确地声明大多数事情。表名需要你在Meta里用is_table True来标记对于关联表等非主模型可以设为False。字段的类型和约束通过Python类型注解和Field类清晰定义。查询链式调用每一步的结果都是可预测的。这种设计带来的最大好处是可维护性和可调试性。当你半年后回头看代码或者新同事接手项目时代码本身几乎就是文档不需要去猜测ORM背后做了什么。2.2 Pydantic v2作为基石将Pydantic v2深度集成是Oxyde实现“显式”和“类型安全”的关键技术决策。你的数据模型首先是一个Pydantic模型这意味着开箱即用的验证在数据进入数据库之前Pydantic已经根据类型注解str,int,EmailStr等和Field约束完成了验证。这相当于在ORM层之前又加了一道坚固的防线。完美的IDE支持得益于Python的类型提示VS Code、PyCharm等编辑器可以提供无与伦比的代码补全、跳转和错误检查。当你写User.objects.filter(name__icontains时IDE能提示出name字段。无缝的序列化由于模型本身就是Pydantic模型你可以直接将Oxyde模型实例丢给FastAPI的response_model或者用.model_dump()方法轻松转换为字典/JSON用于缓存或响应无需额外的转换层。这种设计让Oxyde在Web API开发场景中如鱼得水因为它完美契合了“请求验证 - 业务处理含数据库操作- 响应序列化”这个标准流程。2.3 异步优先与Rust内核“异步优先”意味着Oxyde的所有数据库交互API从连接池管理到最简单的查询都设计为async/await模式。这要求你必须在异步上下文中如asyncio.run或FastAPI的路由函数内使用它。这对于现代基于事件循环的Web框架来说是天然匹配的能有效提升I/O密集型应用的并发能力。更引人注目的是其“Rust核心”。Oxyde并没有用Python重写一个SQL构建器而是利用sqlx和pyo3等工具将SQL查询的构建和部分执行逻辑下放到Rust层。这样做的好处显而易见性能Rust的零成本抽象和内存安全特性使得SQL生成和参数绑定等操作极其高效。这也是其基准测试成绩显著优于纯Python实现的ORM如Django ORM、SQLAlchemy Core的主要原因。安全性sqlx本身支持编译期检查SQL查询在Rust项目中虽然这在Python绑定中无法完全实现但其严谨的设计理念降低了SQL注入的风险。正确性由Rust层处理不同数据库PostgreSQL, SQLite, MySQL的方言差异保证了生成的SQL语句在各数据库上的正确性。3. 从零开始快速上手与项目配置理论说了不少现在让我们动手从一个干净的环境开始体验一下Oxyde的工作流。你会发现它和Django的体验非常相似这对于有相关经验的开发者来说几乎是零成本上手。3.1 安装与环境准备首先确保你的Python版本在3.8以上。然后通过pip安装Oxydepip install oxyde安装完成后系统里会多出一个名为oxyde的命令行工具。我们接下来所有的项目初始化、数据库迁移操作都会用到它。3.2 项目初始化与配置假设我们要创建一个名为myblog的项目。第一步不是直接写模型而是使用oxyde init命令来生成配置文件。# 在你的项目根目录下执行 oxyde init执行这个命令后它会在当前目录下创建一个名为oxyde_config.py的文件。这个文件是Oxyde项目的核心配置其结构非常清晰# oxyde_config.py from oxyde import DatabaseConfig # 数据库配置字典 DATABASES { # “default”是默认数据库连接的别名 default: DatabaseConfig( # 使用SQLite数据库文件名为app.db。你也可以换成PostgreSQL或MySQL的URL。 urlsqlite:///app.db, # 连接池大小等高级选项可以在这里配置 # pool_size10, # max_overflow20, ), # 你可以配置多个数据库比如一个用于主业务一个用于分析 # analytics: DatabaseConfig(urlpostgresql://user:passanalytics-host/db), } # 告诉Oxyde去哪里找你的数据模型 # 这里是一个Python模块路径的列表。通常就是你的models.py所在的模块。 MODELS_PACKAGES [ models, # 代表当前目录下的models.py模块 # “app.models” 如果你使用Django式的应用结构 ] # 是否在应用启动时自动创建表仅开发环境使用生产环境应用迁移 AUTO_CREATE_TABLES False注意AUTO_CREATE_TABLESTrue在快速原型阶段很方便但它不是迁移工具。对于生产环境任何表结构变更都应通过迁移文件来管理以确保变更可追溯、可回滚。所以这里我们保持False。这个配置文件将数据库连接信息与你的应用代码解耦非常利于不同环境开发、测试、生产的配置管理。你可以通过环境变量来动态设置url。3.3 定义第一个数据模型接下来我们在与oxyde_config.py同级的目录下创建models.py文件来定义我们的User模型。# models.py from datetime import datetime from typing import Optional from oxyde import Model, Field class User(Model): # 主键字段。通常命名为id类型为可选整数默认None并用 db_pkTrue 标记为主键。 # Oxyde会自动处理自增如果数据库支持如PostgreSQL的SERIALMySQL的AUTO_INCREMENT。 id: Optional[int] Field(defaultNone, db_pkTrue) # 必填字段。仅通过类型注解 str 定义表示该字段不能为None且对应数据库的NOT NULL约束。 username: str # 使用Field可以添加更多约束比如唯一索引。 email: str Field(db_uniqueTrue) # 可选字段。使用 Optional[int] 表示数据库里可为NULL默认值设为None。 age: Optional[int] Field(defaultNone) # 带默认值的字段。这里使用一个函数作为默认值确保每次创建时生成新的时间。 created_at: datetime Field(default_factorydatetime.utcnow) # 模型元数据。这是“显式”哲学的体现。 class Meta: # is_table True 明确声明这个类对应数据库中的一张表。 # 如果是多对多关系的中间表或者不直接映射的表可以设为False。 is_table True # 你可以自定义表名否则Oxyde会将类名转换为snake_caseuser。 # db_table_name users这个模型定义几乎不需要额外解释它读起来就像一份数据库表结构的声明书。每个字段的类型、是否可为空、是否唯一都一目了然。Field类提供了丰富的参数来控制数据库层面的行为如db_index创建索引、db_default数据库端默认值等。3.4 生成并应用数据库迁移模型定义好了我们需要在数据库中创建对应的表。Oxyde采用了与Django极其相似的迁移系统。# 1. 生成迁移文件 # 此命令会扫描MODELS_PACKAGES中定义的模型与当前数据库状态进行比较并生成一个描述变更的迁移文件。 oxyde makemigrations执行成功后你会看到一个类似migrations/0001_initial.py的文件被创建。打开它里面是人类可读的、描述创建user表的操作。# 2. 应用迁移 # 此命令会执行所有未应用的迁移实质上是运行迁移文件中的SQL。 oxyde migrate对于SQLite这会创建app.db文件对于PostgreSQL/MySQL它会在你配置的数据库中创建表。迁移系统是项目演化的基石务必在团队中规范使用。3.5 在应用中使用数据库连接在异步应用中我们需要在应用启动时建立数据库连接池并在关闭时清理。Oxyde提供了非常简洁的方式尤其是在FastAPI中。通用异步脚本中的使用# main_script.py import asyncio from oxyde import db from models import User async def main(): # 初始化数据库连接池。通常从配置加载这里为演示直接写URL。 await db.init(defaultsqlite:///app.db) try: # 你的业务逻辑在这里 user await User.objects.create(usernamealice, emailaliceexample.com) print(fCreated user: {user.id}, {user.username}) finally: # 关闭所有数据库连接 await db.close() if __name__ __main__: asyncio.run(main())与FastAPI集成推荐FastAPI的lifespan上下文管理器是处理启动/关闭事件的完美场所。# main_fastapi.py from contextlib import asynccontextmanager from fastapi import FastAPI from oxyde import db from . import models # 导入模型确保它们被注册 asynccontextmanager async def lifespan(app: FastAPI): # 启动时初始化数据库 await db.init(defaultpostgresql://user:passwordlocalhost/myappdb) yield # 关闭时清理连接池 await db.close() app FastAPI(lifespanlifespan) app.get(/users) async def list_users(): users await models.User.objects.all() return users这种方式确保了数据库连接的生命周期与FastAPI应用本身完全同步安全且高效。4. 查询API深度解析像Django一样思考Oxyde查询API的学习成本极低尤其是对Django开发者。它采用了类似的Manager模式通过Model.objects访问和链式调用。让我们深入看看各种查询操作。4.1 基础查询方法Model.objects是一个管理器它提供了创建查询的入口。.all()获取表中所有记录。all_users await User.objects.all().get(**kwargs)获取单条记录。如果找不到或找到多条会引发异常oxyde.exceptions.DoesNotExist或oxyde.exceptions.MultipleObjectsReturned。这是进行主键或唯一约束查询的首选方法。try: user await User.objects.get(id1) user_by_email await User.objects.get(emailaliceexample.com) except User.DoesNotExist: # 处理未找到的情况 pass.filter(**kwargs)过滤记录。这是最常用的方法它返回一个QuerySet支持进一步的链式操作。# 获取所有年龄大于等于18的用户 adults await User.objects.filter(age__gte18).all().create(**kwargs)创建并立即保存一条新记录到数据库返回模型实例。new_user await User.objects.create(usernamebob, emailbobexample.com, age25).update(**kwargs)批量更新匹配过滤条件的所有记录。# 将所有未激活用户的年龄字段设为NULL注意这是直接更新数据库不触发模型的.save()方法 updated_count await User.objects.filter(is_activeFalse).update(ageNone).delete()批量删除匹配过滤条件的所有记录。deleted_count await User.objects.filter(age__lt13).delete()4.2 字段查找Field Lookups这是Django ORM的精髓之一Oxyde也完美继承。通过在字段名后添加双下划线__和查找类型你可以构建复杂的查询。精确查找fieldvalue是默认的。await User.objects.filter(usernamealice).all()比较查找__gt大于__gte大于等于__lt小于__lte小于等于await User.objects.filter(age__gt18, age__lt65).all()包含查找__contains区分大小写的包含。__icontains不区分大小写的包含非常常用。__startswith,__istartswith以...开头。__endswith,__iendswith以...结尾。await User.objects.filter(email__icontainsgmail.com).all()范围查找__in值在某个列表中。__range值在某个范围内包含边界。await User.objects.filter(id__in[1, 3, 5]).all() await User.objects.filter(age__range(20, 30)).all() # age 20 and age 30空值查找__isnull是否为NULL。# 查找没有设置年龄的用户 await User.objects.filter(age__isnullTrue).all()4.3 查询集QuerySet的链式操作与延迟执行.filter()方法返回的是一个QuerySet对象它代表了一个“待执行”的查询。你可以对它进行多次链式调用Oxyde会将这些条件组合起来并且查询是延迟执行的直到你调用“求值”方法。求值方法触发真正的数据库查询.all() 获取所有结果的列表。.first() 获取第一个结果如果没有则返回None。.count() 获取结果数量执行COUNT(*)查询。.exists() 判断是否存在匹配的记录。# 这是一个链式调用但尚未执行查询 query User.objects.filter(is_activeTrue).filter(age__gte18) # 现在调用 .all() 才真正执行SQL active_adults await query.all() # 你也可以继续链式调用但每次返回的是新的QuerySet paginated_query query.filter(email__icontainsexample).all()这种模式非常高效因为你可以在业务逻辑的不同部分构建查询的基础部分最后再执行。4.4 排序、去重与切片排序.order_by()# 按年龄升序再按id降序 users await User.objects.all().order_by(age, -id)去重.distinct()# 获取所有不重复的年龄值 unique_ages await User.objects.distinct().values_list(age, flatTrue)切片分页# 获取第6到第15条记录实现分页 page await User.objects.all().order_by(id)[5:15] # 注意Oxyde/Django的切片在数据库层面使用LIMIT和OFFSET高效实现分页。4.5 选择特定字段.only()与.defer()在查询性能优化中避免使用SELECT *是一个重要原则。Oxyde提供了.only()和.defer()来精确控制加载的字段。.only(*fields)只加载指定的字段。其他字段在访问时会触发额外的查询延迟加载。# 只加载id和username访问user.email会触发新的查询 user await User.objects.only(id, username).get(id1) print(user.username) # OK已加载 print(user.email) # 警告会触发一次新的查询来获取email实操心得在列表页、下拉选择等只需要少数字段的场景强烈推荐使用.only()来减少网络传输和内存占用。但要警惕后续代码意外访问未加载字段导致的“N1”查询问题。.defer(*fields)排除指定的字段加载其他所有字段。与.only()相反。# 不加载可能很大的bio字段其他字段正常加载 users await User.objects.defer(bio).filter(is_activeTrue).all()4.6 关联查询RelationsOxyde支持定义模型之间的关系外键、一对一、多对多其API同样借鉴了Django。定义关联# models.py from oxyde import Model, Field, ForeignKey class Author(Model): id: Optional[int] Field(defaultNone, db_pkTrue) name: str class Meta: is_table True class Book(Model): id: Optional[int] Field(defaultNone, db_pkTrue) title: str # 定义外键关联到Author模型。on_delete指定级联行为。 author: ForeignKey[Author] ForeignKey(Author, on_deleteCASCADE) class Meta: is_table True正向查询从“多”的一方查“一”外键字段本身就是一个关联对象。book await Book.objects.get(id1) # 直接通过属性访问关联的Author对象这会触发一次额外的查询 author await book.author print(author.name)反向查询从“一”的一方查“多”Oxyde会自动在“一”的一方创建一个名为模型名小写_set的RelatedManager。author await Author.objects.get(id1) # 获取这位作者所有的书 books await author.book_set.all()使用select_related进行查询优化联表查询上面的正向查询会导致“N1”问题获取N本书需要N1次查询。Oxyde提供了select_related在单次查询中通过JOIN一次性获取关联对象。# 一次性获取Book及其关联的Author对象 book await Book.objects.select_related(author).get(id1) # 此时访问book.author不会触发新查询因为数据已经在第一次查询中获取了 print(book.author.name) # 无额外查询对于多对多或反向外键集合可以使用prefetch_related在额外查询中预先批量获取也能有效解决N1问题。这些高级关联查询方法是构建高效应用的关键Oxyde的API设计让它们用起来非常直观。5. 事务管理与数据完整性对于任何严肃的应用程序事务是保证数据一致性的基石。Oxyde提供了清晰的事务管理API其设计同样让人联想到Django。5.1 基础事务transaction.atomic()最常用的方式是使用async with上下文管理器。from oxyde.db import transaction async def transfer_funds(from_user_id, to_user_id, amount): async with transaction.atomic(): # 在这个代码块内所有数据库操作都在一个事务中 from_user await User.objects.get(idfrom_user_id) to_user await User.objects.get(idto_user_id) if from_user.balance amount: raise ValueError(Insufficient funds) from_user.balance - amount to_user.balance amount await from_user.save() await to_user.save() # 如果任何一行代码抛出异常整个事务都会回滚余额保持不变。当上下文管理器成功退出时事务会自动提交。如果发生任何异常事务会自动回滚。这极大地简化了错误处理。5.2 嵌套事务与保存点SavepointsOxyde支持嵌套事务通过保存点实现。这在复杂的业务逻辑中非常有用。async with transaction.atomic(): # 外层事务开始 user await User.objects.create(usernameparent) try: async with transaction.atomic(): # 内层事务保存点开始 profile await Profile.objects.create(user_iduser.id, bioA new user.) # 假设这里有个可能失败的操作 await some_risky_operation(profile) # 如果成功内层事务保存点提交 except SomeSpecificError: # 捕获内层异常内层事务保存点回滚但外层事务继续 logger.warning(Risky operation failed, but user creation persists.) # 可以在这里进行一些补偿操作 # 外层事务提交user被创建profile可能被创建也可能没有取决于内层是否回滚在这个例子中即使创建Profile的 risky operation 失败了User记录依然会被创建因为内层事务的回滚只影响到它自己所在的保存点。5.3 手动控制事务虽然不推荐但在某些高级场景下你可能需要手动控制。from oxyde.db import transaction async def manual_transaction(): # 获取当前连接的事务对象 txn await transaction.start() try: # ... 执行操作 ... await txn.commit() except Exception: await txn.rollback() raise注意事项手动事务需要非常小心地处理提交和回滚确保在任何异常路径下都能正确清理否则可能导致连接泄露或长时间持有锁。优先使用transaction.atomic()上下文管理器。6. 性能调优与最佳实践得益于Rust内核Oxyde在基础性能上已经领先。但要充分发挥其潜力还需要遵循一些最佳实践。6.1 连接池配置在生产环境中正确配置数据库连接池至关重要。这可以在oxyde_config.py的DatabaseConfig中完成。# oxyde_config.py DATABASES { default: DatabaseConfig( urlpostgresql://user:passlocalhost/dbname, # 最小连接数。保持一定数量的活跃连接避免每次请求都新建连接。 min_size5, # 最大连接数。这是连接池的上限取决于数据库和应用的负载。 max_size20, # 连接最大空闲时间秒。超时的连接会被回收。 max_idle_time300.0, # 连接最长存活时间秒。定期回收重建连接防止数据库端连接僵死。 max_lifetime3600.0, ), }配置合适的min_size和max_size需要在应用负载和数据库资源之间找到平衡。可以从一个较小的值开始根据监控指标进行调整。6.2 查询优化黄金法则善用.only()和.select_related()这是减少不必要数据传输和避免N1查询最有效的手段。在编写查询时始终问自己“我真的需要所有字段吗”、“我后续会访问关联对象吗”。谨慎使用.all()await Model.objects.all()会获取表中的所有记录。对于大表这会导致内存耗尽。务必与.filter()、切片分页结合使用。# 危险可能返回百万条记录 # all_users await User.objects.all() # 正确使用分页 page_of_users await User.objects.all().order_by(id)[0:100]为频繁查询的字段添加索引ORM无法替代良好的数据库设计。通过Field(db_indexTrue)为经常出现在WHERE、ORDER BY或作为关联条件的字段创建索引能带来数量级的性能提升。class User(Model): email: str Field(db_uniqueTrue, db_indexTrue) # 唯一约束自带索引 created_at: datetime Field(default_factorydatetime.utcnow, db_indexTrue) # 常用于排序和范围查询理解查询的评估时机记住QuerySet是惰性的。复杂的链式调用在.all()、.first()等求值方法调用前不会产生SQL。这允许你动态构建查询但也意味着你不能在未求值的QuerySet上调用某些方法。6.3 监控与调试日志启用数据库查询日志可以帮助你发现慢查询或N1问题。Oxyde的日志通常集成在标准的Python logging中你可以配置logging模块来捕获oxyde或sqlalchemy底层驱动的DEBUG级别日志。数据库内置工具使用EXPLAIN ANALYZEPostgreSQL或EXPLAINMySQL来分析Oxyde生成的复杂查询的执行计划。虽然Oxyde不直接暴露这个功能但你可以从日志中复制出SQL语句到数据库客户端中执行分析。7. 常见问题与故障排查实录在实际使用中你难免会遇到一些问题。这里记录了一些我踩过的坑和解决方案。7.1 连接与配置问题问题RuntimeError: Database not initialized. Call await db.init(...) first.原因在调用任何数据库操作前没有初始化数据库连接。解决确保在应用入口如FastAPI的lifespan、脚本的asyncio.run主函数中正确调用了await db.init()。问题连接MySQL 5.7或旧版本数据库失败。原因Oxyde明确要求MySQL 8.0因为它使用了如WITH语法等较新的特性。解决升级你的MySQL数据库到8.0或更高版本。如果无法升级可能需要考虑其他ORM或等待Oxyde未来可能对低版本的支持。7.2 查询与模型定义问题问题oxyde.exceptions.DoesNotExist异常。原因使用.get()方法时没有找到匹配的记录。解决这是预期行为用于处理“有且只有一条”的场景。你需要用try...except包裹调用或者使用.filter(...).first()来替代后者在找不到时返回None。问题定义模型时出现类型检查错误如Pylance提示。原因Pydantic对类型提示要求严格。特别是主键id字段在创建前是None创建后由数据库赋值。解决使用Optional[int]并设置defaultNone是正确的模式。确保你的IDE使用的是支持Pydantic v2和Python类型提示的语言服务器。# 正确 id: Optional[int] Field(defaultNone, db_pkTrue) # 创建前: user.id is None # 保存后: user.id is 1问题使用.only(“field”)后访问其他字段导致额外查询性能下降。原因对.only()的行为理解有误。它只加载指定字段其他字段是“延迟”的。解决在决定使用.only()时要确保后续业务逻辑不会访问那些未加载的字段。如果无法确定就不要用.only()或者使用.select_related()来主动加载关联对象。7.3 迁移相关问题问题oxyde makemigrations没有检测到模型变更。原因可能的原因有1) 模型文件未被正确导入到MODELS_PACKAGES指定的模块路径中2) 对模型Meta类的修改如db_table_name可能不会被自动检测。解决确保你的模型类在MODELS_PACKAGES列出的模块中被定义和导入。对于某些元数据变更有时需要手动创建迁移或删除旧的迁移记录重新生成。问题迁移文件冲突。原因在团队协作中多人同时生成了基于相同父迁移的新的迁移文件。解决和Django迁移类似需要手动合并迁移文件。比较两个冲突的迁移文件将operations列表合理合并并可能需要重命名依赖关系。这是一个需要谨慎操作的过程最好在代码合并前通过沟通避免。7.4 性能相关问题问题简单的列表API响应很慢数据库查询次数非常多N1问题。原因在序列化返回列表时循环中访问了每个对象的关联字段。解决使用select_related用于外键/一对一或prefetch_related用于多对多、反向外键集合在初始查询中一次性加载所有关联数据。# 错误N1次查询 books await Book.objects.all() result [] for book in books: author await book.author # 每次循环都查询一次数据库 result.append({title: book.title, author: author.name}) # 正确1次查询使用JOIN books await Book.objects.select_related(author).all() result [{title: b.title, author: b.author.name} for b in books] # 这里b.author已加载无查询问题批量插入大量数据时速度慢。原因在循环中逐条调用Model.objects.create()或instance.save()会产生大量独立的INSERT语句和网络往返。解决Oxyde提供了bulk_create方法进行批量插入能显著提升性能。user_list [User(namefuser{i}, emailfuser{i}test.com) for i in range(1000)] created_users await User.objects.bulk_create(user_list)记住bulk_create可能不会触发模型的save()方法中的自定义逻辑且某些数据库如SQLite在大量插入时可能需要考虑事务包裹。经过几个项目的实践Oxyde给我的感觉是“稳健的现代化”。它没有追求花哨的特性而是在开发者体验、类型安全和运行性能这三个现代Python后端开发的核心诉求上做到了扎实的平衡。它的学习曲线对于Django开发者来说几乎是平坦的而对于新手其显式的设计也减少了“黑盒”带来的困惑。Rust内核带来的性能红利是实实在在的尤其是在处理复杂查询或高并发场景时。如果你正在为一个新的异步Python项目选择ORM或者对现有项目中ORM的效率和体验不满Oxyde绝对是一个值得你放入评估清单的选项。当然它作为一个年轻项目社区和插件生态还在成长中但对于核心的ORM功能它已经足够成熟和可靠。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价