mysql.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """MySQL 异步后端 —— 基于 aiomysql 连接池"""
  2. import logging
  3. from typing import Any, Optional
  4. from aiomysql import create_pool, DictCursor
  5. from supply.infra.database.ports import SqlBackend
  6. logger = logging.getLogger(__name__)
  7. class MySQLBackend(SqlBackend):
  8. """MySQL 异步连接池后端"""
  9. def __init__(self, name: str, config: Any):
  10. super().__init__(name, config)
  11. self._pool = None
  12. # ==================== 生命周期 ====================
  13. async def init(self) -> None:
  14. if self._initialized:
  15. return
  16. self._pool = await create_pool(
  17. host=self.config.host,
  18. port=self.config.port,
  19. user=self.config.user,
  20. password=self.config.password,
  21. db=self.config.db,
  22. charset=getattr(self.config, "charset", "utf8mb4"),
  23. minsize=getattr(self.config, "minsize", 5),
  24. maxsize=getattr(self.config, "maxsize", 20),
  25. cursorclass=DictCursor,
  26. autocommit=True,
  27. )
  28. self._initialized = True
  29. logger.info("MySQL backend [%s] 连接池创建成功", self.name)
  30. async def close(self) -> None:
  31. if self._pool is None:
  32. return
  33. self._pool.close()
  34. await self._pool.wait_closed()
  35. self._pool = None
  36. self._initialized = False
  37. logger.info("MySQL backend [%s] 连接池已关闭", self.name)
  38. async def health(self) -> bool:
  39. try:
  40. await self.fetch_one("SELECT 1")
  41. return True
  42. except Exception:
  43. return False
  44. # ==================== SQL 操作 ====================
  45. async def fetch(
  46. self,
  47. query: str,
  48. params: Optional[tuple] = None,
  49. ) -> list[dict]:
  50. self._ensure_ready()
  51. async with self._pool.acquire() as conn:
  52. async with conn.cursor(DictCursor) as cursor:
  53. await cursor.execute(query, params)
  54. return await cursor.fetchall()
  55. async def fetch_one(
  56. self,
  57. query: str,
  58. params: Optional[tuple] = None,
  59. ) -> Optional[dict]:
  60. self._ensure_ready()
  61. async with self._pool.acquire() as conn:
  62. async with conn.cursor(DictCursor) as cursor:
  63. await cursor.execute(query, params)
  64. return await cursor.fetchone()
  65. async def execute(
  66. self,
  67. query: str,
  68. params: Any = None,
  69. *,
  70. batch: bool = False,
  71. ) -> int:
  72. self._ensure_ready()
  73. async with self._pool.acquire() as conn:
  74. async with conn.cursor() as cursor:
  75. try:
  76. if batch:
  77. await cursor.executemany(query, params)
  78. else:
  79. await cursor.execute(query, params)
  80. affected = cursor.rowcount
  81. await conn.commit()
  82. return affected
  83. except Exception:
  84. await conn.rollback()
  85. raise
  86. # ==================== 内部 ====================
  87. def _ensure_ready(self):
  88. if not self._initialized or self._pool is None:
  89. raise RuntimeError(
  90. f"MySQL backend [{self.name}] 未初始化,请先调用 init()"
  91. )