springbootAI 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spring与MyBatis集成模块
|
|
3
|
+
提供@MapperScan、@Mapper等注解,实现Mapper自动注册到Spring容器
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional, List, Dict, Any, Type
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import inspect
|
|
9
|
+
import logging
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from contextvars import ContextVar
|
|
12
|
+
from spring.annotations.core import SpringAnnotation
|
|
13
|
+
from spring.context.bean_factory import BeanFactory
|
|
14
|
+
from spring.context.bean_definition import BeanDefinition
|
|
15
|
+
from spring.config.config_loader import ConfigLoader
|
|
16
|
+
from spring.orm.pymybatis import build_session_factory, SqlSessionFactory, SqlSession
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("Spring.MyBatis")
|
|
19
|
+
_transaction_session: ContextVar[Optional[SqlSession]] = ContextVar(
|
|
20
|
+
'spring_mybatis_transaction_session', default=None
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_transaction_session() -> Optional[SqlSession]:
|
|
25
|
+
return _transaction_session.get()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def mybatis_transaction(session_factory: SqlSessionFactory, propagation: str = 'REQUIRED'):
|
|
30
|
+
"""Bind one ``SqlSession`` to the current execution context.
|
|
31
|
+
|
|
32
|
+
The propagation names intentionally follow Spring's transaction contract.
|
|
33
|
+
``REQUIRES_NEW`` gets a separate session/connection while the outer
|
|
34
|
+
context is suspended. ``NOT_SUPPORTED`` runs without a bound session so
|
|
35
|
+
mapper calls use their normal short-lived, auto-commit session.
|
|
36
|
+
"""
|
|
37
|
+
normalized = str(propagation or 'REQUIRED').upper()
|
|
38
|
+
supported = {
|
|
39
|
+
'REQUIRED', 'REQUIRES_NEW', 'NESTED', 'SUPPORTS',
|
|
40
|
+
'MANDATORY', 'NOT_SUPPORTED', 'NEVER',
|
|
41
|
+
}
|
|
42
|
+
if normalized not in supported:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"不支持的事务传播级别: {propagation}; 可选: {', '.join(sorted(supported))}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
existing_session = get_transaction_session()
|
|
48
|
+
if normalized == 'NEVER':
|
|
49
|
+
if existing_session is not None and existing_session.in_transaction:
|
|
50
|
+
raise RuntimeError("事务传播 NEVER 要求当前不存在活动事务")
|
|
51
|
+
yield None
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if normalized == 'MANDATORY' and (
|
|
55
|
+
existing_session is None or not existing_session.in_transaction
|
|
56
|
+
):
|
|
57
|
+
raise RuntimeError("事务传播 MANDATORY 要求当前已存在活动事务")
|
|
58
|
+
|
|
59
|
+
if normalized in {'SUPPORTS', 'NOT_SUPPORTED'}:
|
|
60
|
+
if normalized == 'SUPPORTS' and existing_session is not None:
|
|
61
|
+
yield existing_session
|
|
62
|
+
return
|
|
63
|
+
# Suspend the outer connection while mapper calls create their normal
|
|
64
|
+
# short-lived auto-commit sessions. This avoids accidentally running
|
|
65
|
+
# NOT_SUPPORTED work on the outer transaction.
|
|
66
|
+
if existing_session is not None and normalized == 'NOT_SUPPORTED':
|
|
67
|
+
with existing_session._suspended_transaction():
|
|
68
|
+
token = _transaction_session.set(None)
|
|
69
|
+
try:
|
|
70
|
+
yield None
|
|
71
|
+
finally:
|
|
72
|
+
_transaction_session.reset(token)
|
|
73
|
+
return
|
|
74
|
+
yield None
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if normalized == 'REQUIRED' and existing_session is not None:
|
|
78
|
+
with existing_session.transaction(propagation='REQUIRED'):
|
|
79
|
+
yield existing_session
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
if normalized == 'NESTED' and existing_session is not None:
|
|
83
|
+
with existing_session.transaction(propagation='NESTED'):
|
|
84
|
+
yield existing_session
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# REQUIRES_NEW always creates a new physical session. The outer session
|
|
88
|
+
# remains open and is restored after the inner boundary completes.
|
|
89
|
+
if normalized == 'REQUIRES_NEW':
|
|
90
|
+
with session_factory.open_session() as session:
|
|
91
|
+
token = _transaction_session.set(session)
|
|
92
|
+
try:
|
|
93
|
+
with session.transaction(propagation='REQUIRED'):
|
|
94
|
+
yield session
|
|
95
|
+
finally:
|
|
96
|
+
_transaction_session.reset(token)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
# REQUIRED with no existing session starts the physical transaction.
|
|
100
|
+
if existing_session is not None:
|
|
101
|
+
raise RuntimeError(f"无法建立事务传播上下文: {normalized}")
|
|
102
|
+
|
|
103
|
+
with session_factory.open_session() as session:
|
|
104
|
+
token = _transaction_session.set(session)
|
|
105
|
+
try:
|
|
106
|
+
with session.transaction(propagation='REQUIRED'):
|
|
107
|
+
yield session
|
|
108
|
+
finally:
|
|
109
|
+
_transaction_session.reset(token)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ManagedMapperProxy:
|
|
113
|
+
"""为每次Mapper调用创建并关闭Session,避免跨请求共享会话状态。"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, session_factory: SqlSessionFactory, mapper_class: Type):
|
|
116
|
+
self._session_factory = session_factory
|
|
117
|
+
self._mapper_class = mapper_class
|
|
118
|
+
|
|
119
|
+
def __getattr__(self, name: str):
|
|
120
|
+
mapper_method = getattr(self._mapper_class, name, None)
|
|
121
|
+
if mapper_method is None or not callable(mapper_method):
|
|
122
|
+
raise AttributeError(f"Mapper {self._mapper_class.__name__} 没有方法: {name}")
|
|
123
|
+
|
|
124
|
+
def invoke(*args, **kwargs):
|
|
125
|
+
transaction_session = get_transaction_session()
|
|
126
|
+
if transaction_session is not None:
|
|
127
|
+
mapper = transaction_session.get_mapper(self._mapper_class)
|
|
128
|
+
return getattr(mapper, name)(*args, **kwargs)
|
|
129
|
+
with self._session_factory.open_session() as session:
|
|
130
|
+
mapper = session.get_mapper(self._mapper_class)
|
|
131
|
+
return getattr(mapper, name)(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
return invoke
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Mapper(SpringAnnotation):
|
|
137
|
+
"""
|
|
138
|
+
Mapper接口注解
|
|
139
|
+
标识一个类为MyBatis Mapper接口
|
|
140
|
+
|
|
141
|
+
使用示例:
|
|
142
|
+
@Mapper
|
|
143
|
+
class UserMapper:
|
|
144
|
+
@Select("SELECT * FROM users WHERE id = #{id}")
|
|
145
|
+
def find_by_id(self, id):
|
|
146
|
+
pass
|
|
147
|
+
"""
|
|
148
|
+
_annotation_type = "mapper"
|
|
149
|
+
|
|
150
|
+
def __init__(self, value: str = ""):
|
|
151
|
+
super().__init__(value=value)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class MapperScan(SpringAnnotation):
|
|
155
|
+
"""
|
|
156
|
+
Mapper扫描注解
|
|
157
|
+
指定要扫描的Mapper包路径
|
|
158
|
+
|
|
159
|
+
使用示例:
|
|
160
|
+
@SpringBootApplication
|
|
161
|
+
@MapperScan(base_packages=["example.mappers"])
|
|
162
|
+
class Application:
|
|
163
|
+
pass
|
|
164
|
+
"""
|
|
165
|
+
_annotation_type = "mapper_scan"
|
|
166
|
+
|
|
167
|
+
def __init__(self, base_packages: Optional[List[str]] = None):
|
|
168
|
+
super().__init__(base_packages=base_packages or [])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class MyBatisConfigurer:
|
|
172
|
+
"""
|
|
173
|
+
MyBatis配置器
|
|
174
|
+
负责初始化SqlSessionFactory和注册Mapper Bean
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def __init__(self, config_loader: ConfigLoader):
|
|
178
|
+
self.config_loader = config_loader
|
|
179
|
+
self.sql_session_factory: Optional[SqlSessionFactory] = None
|
|
180
|
+
self._mapper_registry: Dict[str, Type] = {}
|
|
181
|
+
|
|
182
|
+
def init(self, application_context) -> None:
|
|
183
|
+
"""
|
|
184
|
+
初始化MyBatis
|
|
185
|
+
"""
|
|
186
|
+
# 1. 获取数据库配置
|
|
187
|
+
db_config = self.config_loader.get_config().get('database', {})
|
|
188
|
+
orm_mode = str(db_config.get('orm', 'mybatis')).lower()
|
|
189
|
+
if not db_config.get('enabled', False) or orm_mode not in {'mybatis', 'both'}:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# 2. 构建配置字典,传递给build_session_factory
|
|
193
|
+
datasource_config = {
|
|
194
|
+
'driver': db_config.get('driver', 'sqlite'),
|
|
195
|
+
'host': db_config.get('host', 'localhost'),
|
|
196
|
+
'port': db_config.get('port', 3306),
|
|
197
|
+
'database': db_config.get('database', 'test'),
|
|
198
|
+
'username': db_config.get('username', ''),
|
|
199
|
+
'password': db_config.get('password', ''),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# 设置安全配置
|
|
203
|
+
security_config = dict(db_config.get('security', {}))
|
|
204
|
+
if 'ddl_block_enabled' in security_config and 'block_ddl' not in security_config:
|
|
205
|
+
security_config['block_ddl'] = security_config.pop('ddl_block_enabled')
|
|
206
|
+
|
|
207
|
+
# 设置缓存配置
|
|
208
|
+
cache_config = db_config.get('cache', {})
|
|
209
|
+
|
|
210
|
+
# 3. 构建SqlSessionFactory(内部会调用Configuration.load_config)
|
|
211
|
+
pool_config = {
|
|
212
|
+
'min_size': db_config.get('min_size', 5),
|
|
213
|
+
'max_size': db_config.get('max_size', 20),
|
|
214
|
+
'max_idle': db_config.get('max_idle', 3600),
|
|
215
|
+
'wait_timeout': db_config.get('wait_timeout', 30),
|
|
216
|
+
'validation_interval': db_config.get('validation_interval', 300),
|
|
217
|
+
'leak_detection_enabled': db_config.get('leak_detection_enabled', True),
|
|
218
|
+
'leak_timeout': db_config.get('leak_timeout', 300),
|
|
219
|
+
'circuit_breaker': db_config.get('circuit_breaker', {}),
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
mybatis_config = {
|
|
223
|
+
'datasource': datasource_config,
|
|
224
|
+
'pool': pool_config,
|
|
225
|
+
'security': security_config,
|
|
226
|
+
'cache': cache_config,
|
|
227
|
+
'transaction': db_config.get('transaction', {}),
|
|
228
|
+
'batch': db_config.get('batch', {}),
|
|
229
|
+
}
|
|
230
|
+
mapper_locations = db_config.get('mapper_locations') or db_config.get('mapper_paths')
|
|
231
|
+
if mapper_locations:
|
|
232
|
+
mybatis_config['mapper_locations'] = mapper_locations
|
|
233
|
+
|
|
234
|
+
self.sql_session_factory = build_session_factory(mybatis_config)
|
|
235
|
+
|
|
236
|
+
# 4. 初始化DDL自动建表(JPA hibernate.ddl-auto风格)
|
|
237
|
+
self._init_ddl_auto(db_config)
|
|
238
|
+
|
|
239
|
+
# 5. 扫描并注册Mapper
|
|
240
|
+
self._scan_mappers(application_context)
|
|
241
|
+
|
|
242
|
+
# 6. 注册SqlSessionFactory和SqlSession为Bean
|
|
243
|
+
self._register_beans(application_context.bean_factory)
|
|
244
|
+
|
|
245
|
+
def _init_ddl_auto(self, db_config: dict) -> None:
|
|
246
|
+
"""初始化DDL自动建表"""
|
|
247
|
+
try:
|
|
248
|
+
from spring.orm.ddl_auto import init_ddl_auto
|
|
249
|
+
# 获取连接池
|
|
250
|
+
pool = None
|
|
251
|
+
if hasattr(self.sql_session_factory, 'configuration'):
|
|
252
|
+
pool = getattr(self.sql_session_factory.configuration, 'pool', None)
|
|
253
|
+
if pool is None and hasattr(self.sql_session_factory, '_pool'):
|
|
254
|
+
pool = self.sql_session_factory._pool
|
|
255
|
+
if pool is not None:
|
|
256
|
+
init_ddl_auto(pool, db_config)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.warning(f"DDL auto initialization skipped: {e}")
|
|
259
|
+
|
|
260
|
+
def _scan_mappers(self, application_context) -> None:
|
|
261
|
+
"""
|
|
262
|
+
扫描Mapper类
|
|
263
|
+
"""
|
|
264
|
+
# 获取MapperScan注解配置
|
|
265
|
+
main_class = application_context.main_class
|
|
266
|
+
annotations = getattr(main_class, '__spring_annotations__', [])
|
|
267
|
+
|
|
268
|
+
base_packages = []
|
|
269
|
+
for annotation in annotations:
|
|
270
|
+
if isinstance(annotation, MapperScan):
|
|
271
|
+
base_packages.extend(annotation.base_packages)
|
|
272
|
+
|
|
273
|
+
if not base_packages:
|
|
274
|
+
# 默认扫描主类所在包下的mappers目录
|
|
275
|
+
base_packages.append(self._get_default_mapper_package(main_class))
|
|
276
|
+
|
|
277
|
+
# 扫描每个包
|
|
278
|
+
for package in base_packages:
|
|
279
|
+
self._scan_package(package)
|
|
280
|
+
|
|
281
|
+
def _get_default_mapper_package(self, main_class) -> str:
|
|
282
|
+
"""
|
|
283
|
+
获取默认的Mapper包路径
|
|
284
|
+
"""
|
|
285
|
+
module_name = main_class.__module__
|
|
286
|
+
if module_name == '__main__':
|
|
287
|
+
return 'mappers'
|
|
288
|
+
return f"{module_name.split('.')[0]}.mappers"
|
|
289
|
+
|
|
290
|
+
def _scan_package(self, package_name: str) -> None:
|
|
291
|
+
"""
|
|
292
|
+
扫描指定包下的Mapper类
|
|
293
|
+
"""
|
|
294
|
+
try:
|
|
295
|
+
# 将包名转换为路径
|
|
296
|
+
package_path = package_name.replace('.', os.sep)
|
|
297
|
+
|
|
298
|
+
# 查找包路径
|
|
299
|
+
for path in sys.path:
|
|
300
|
+
full_path = os.path.join(path, package_path)
|
|
301
|
+
if os.path.exists(full_path) and os.path.isdir(full_path):
|
|
302
|
+
# 遍历包下所有文件
|
|
303
|
+
for filename in os.listdir(full_path):
|
|
304
|
+
if filename.endswith('.py') and not filename.startswith('_'):
|
|
305
|
+
module_name = f"{package_name}.{filename[:-3]}"
|
|
306
|
+
self._import_module(module_name)
|
|
307
|
+
break
|
|
308
|
+
except Exception as e:
|
|
309
|
+
logger.warning(f"Failed to scan package {package_name}: {e}")
|
|
310
|
+
|
|
311
|
+
def _import_module(self, module_name: str) -> None:
|
|
312
|
+
"""
|
|
313
|
+
导入模块并查找Mapper类
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
module = __import__(module_name, fromlist=['*'])
|
|
317
|
+
for name in dir(module):
|
|
318
|
+
obj = getattr(module, name)
|
|
319
|
+
if inspect.isclass(obj) and hasattr(obj, '__spring_annotations__'):
|
|
320
|
+
for annotation in obj.__spring_annotations__:
|
|
321
|
+
if isinstance(annotation, Mapper):
|
|
322
|
+
self._mapper_registry[name] = obj
|
|
323
|
+
break
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
logger.warning("Failed to import mapper module %s: %s", module_name, exc)
|
|
326
|
+
|
|
327
|
+
def _generate_bean_name(self, cls_name: str) -> str:
|
|
328
|
+
"""
|
|
329
|
+
生成Bean名称,与Spring的命名规则保持一致
|
|
330
|
+
将驼峰式转换为下划线式,如 UserMapper -> user_mapper
|
|
331
|
+
"""
|
|
332
|
+
base_name = cls_name[:-6] if cls_name.endswith('Mapper') else cls_name
|
|
333
|
+
|
|
334
|
+
# 将驼峰式转换为下划线式
|
|
335
|
+
result = []
|
|
336
|
+
for i, char in enumerate(base_name):
|
|
337
|
+
if i > 0 and char.isupper():
|
|
338
|
+
result.append('_')
|
|
339
|
+
result.append(char.lower())
|
|
340
|
+
|
|
341
|
+
suffix = '_mapper' if cls_name.endswith('Mapper') else ''
|
|
342
|
+
|
|
343
|
+
return ''.join(result) + suffix
|
|
344
|
+
|
|
345
|
+
def _register_beans(self, bean_factory: BeanFactory) -> None:
|
|
346
|
+
"""
|
|
347
|
+
注册MyBatis相关Bean到Spring容器
|
|
348
|
+
"""
|
|
349
|
+
# 注册SqlSessionFactory
|
|
350
|
+
bean_factory.register_bean_definition(
|
|
351
|
+
'sqlSessionFactory',
|
|
352
|
+
BeanDefinition(
|
|
353
|
+
bean_class=SqlSessionFactory,
|
|
354
|
+
bean_name='sqlSessionFactory',
|
|
355
|
+
scope='singleton',
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
bean_factory.register_instance('sqlSessionFactory', self.sql_session_factory)
|
|
359
|
+
|
|
360
|
+
# 注册SqlSession(每次获取都创建新实例)
|
|
361
|
+
def create_sql_session():
|
|
362
|
+
return self.sql_session_factory.open_session()
|
|
363
|
+
|
|
364
|
+
bean_factory.register_bean_definition(
|
|
365
|
+
'sqlSession',
|
|
366
|
+
BeanDefinition(
|
|
367
|
+
bean_class=SqlSession,
|
|
368
|
+
bean_name='sqlSession',
|
|
369
|
+
scope='prototype',
|
|
370
|
+
factory_method=create_sql_session,
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# 注册所有Mapper(使用与Spring一致的命名规则)
|
|
375
|
+
for mapper_name, mapper_class in self._mapper_registry.items():
|
|
376
|
+
# 生成符合Spring命名规则的bean名称
|
|
377
|
+
bean_name = self._generate_bean_name(mapper_name)
|
|
378
|
+
|
|
379
|
+
# 创建Mapper代理工厂方法
|
|
380
|
+
def create_mapper_proxy(mapper_cls=mapper_class):
|
|
381
|
+
return ManagedMapperProxy(self.sql_session_factory, mapper_cls)
|
|
382
|
+
|
|
383
|
+
bean_factory.register_bean_definition(
|
|
384
|
+
bean_name,
|
|
385
|
+
BeanDefinition(
|
|
386
|
+
bean_class=mapper_class,
|
|
387
|
+
bean_name=bean_name,
|
|
388
|
+
scope='prototype',
|
|
389
|
+
factory_method=create_mapper_proxy,
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def init_mybatis(application_context) -> None:
|
|
395
|
+
"""
|
|
396
|
+
初始化MyBatis集成
|
|
397
|
+
在Spring应用启动时调用
|
|
398
|
+
"""
|
|
399
|
+
configurer = MyBatisConfigurer(application_context.config_loader)
|
|
400
|
+
configurer.init(application_context)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyMyBatis - Python版MyBatis ORM框架
|
|
3
|
+
|
|
4
|
+
对标Java MyBatis,实现SQL与代码分离,支持XML映射文件、注解两种SQL编写方式。
|
|
5
|
+
|
|
6
|
+
核心特性:
|
|
7
|
+
- SQL注入防御(参数化查询 + AST验证)
|
|
8
|
+
- 敏感数据脱敏
|
|
9
|
+
- 连接池管理(带熔断降级机制)
|
|
10
|
+
- 多数据源支持
|
|
11
|
+
- 动态SQL(if/where/foreach标签)
|
|
12
|
+
- 事务管理
|
|
13
|
+
- 自定义类型处理器
|
|
14
|
+
- 拦截器插件
|
|
15
|
+
- 查询缓存(支持Redis分布式缓存)
|
|
16
|
+
- 监控指标(Prometheus兼容)
|
|
17
|
+
|
|
18
|
+
支持数据库:MySQL、PostgreSQL、SQLite、Oracle
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .core import SqlSession, SqlSessionFactory
|
|
22
|
+
from .configuration import Configuration
|
|
23
|
+
from .mapper import Mapper
|
|
24
|
+
from .annotations import (
|
|
25
|
+
CacheNamespace, DataSource, Delete, Insert, Options, Param, Result,
|
|
26
|
+
ResultMap, Select, Transactional, Update,
|
|
27
|
+
SelectProvider, InsertProvider, UpdateProvider, DeleteProvider,
|
|
28
|
+
)
|
|
29
|
+
from .transaction import Transaction, TransactionIsolationLevel
|
|
30
|
+
from .pool import ConnectionPool
|
|
31
|
+
from .cache import SqlCache, LRUCache, GLOBAL_SECOND_LEVEL_CACHE
|
|
32
|
+
from .dialect import Dialect, MySQLDialect, PostgreSQLDialect, SQLiteDialect, OracleDialect
|
|
33
|
+
from .security import SensitiveDataMasker, SQLInjectionDetector
|
|
34
|
+
from .interceptor import Interceptor
|
|
35
|
+
from .type_handler import TypeHandler
|
|
36
|
+
|
|
37
|
+
__version__ = "1.4.0"
|
|
38
|
+
__author__ = "PyMyBatis Team"
|
|
39
|
+
|
|
40
|
+
# 基础导出列表
|
|
41
|
+
__all__ = [
|
|
42
|
+
'SqlSession', 'SqlSessionFactory', 'Configuration', 'Mapper',
|
|
43
|
+
'Select', 'Insert', 'Update', 'Delete',
|
|
44
|
+
'SelectProvider', 'InsertProvider', 'UpdateProvider', 'DeleteProvider',
|
|
45
|
+
'ResultMap', 'Result',
|
|
46
|
+
'Options', 'Param', 'CacheNamespace', 'DataSource', 'Transactional',
|
|
47
|
+
'Transaction', 'TransactionIsolationLevel', 'ConnectionPool',
|
|
48
|
+
'SqlCache', 'LRUCache', 'GLOBAL_SECOND_LEVEL_CACHE', 'Dialect',
|
|
49
|
+
'MySQLDialect', 'PostgreSQLDialect', 'SQLiteDialect', 'OracleDialect',
|
|
50
|
+
'SensitiveDataMasker', 'SQLInjectionDetector', 'Interceptor', 'TypeHandler',
|
|
51
|
+
'build_session_factory',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# 可选模块(按需导入)
|
|
55
|
+
try:
|
|
56
|
+
from .circuit_breaker import CircuitBreaker, DatabaseCircuitBreaker, CircuitBreakerState, CircuitBreakerError
|
|
57
|
+
__all__.extend(['CircuitBreaker', 'DatabaseCircuitBreaker', 'CircuitBreakerState', 'CircuitBreakerError'])
|
|
58
|
+
except ImportError:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
from .cache.redis_cache import RedisSecondLevelCache, create_redis_cache
|
|
63
|
+
__all__.extend(['RedisSecondLevelCache', 'create_redis_cache'])
|
|
64
|
+
except ImportError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
from .metrics import MetricsCollector, Counter, Gauge, Histogram, Timer, get_default_collector
|
|
69
|
+
__all__.extend(['MetricsCollector', 'Counter', 'Gauge', 'Histogram', 'Timer', 'get_default_collector'])
|
|
70
|
+
except ImportError:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_session_factory(config: dict) -> SqlSessionFactory:
|
|
75
|
+
"""
|
|
76
|
+
快速构建SqlSessionFactory
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
config: 配置字典,包含数据源、映射文件等配置
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
SqlSessionFactory实例
|
|
83
|
+
"""
|
|
84
|
+
configuration = Configuration()
|
|
85
|
+
configuration.load_config(config)
|
|
86
|
+
return SqlSessionFactory(configuration)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyMyBatis注解模块
|
|
3
|
+
|
|
4
|
+
提供SQL注解定义:@Select、@Insert、@Update、@Delete、@ResultMap、@Result
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .annotations import (
|
|
8
|
+
CacheNamespace,
|
|
9
|
+
DataSource,
|
|
10
|
+
Delete,
|
|
11
|
+
DeleteProvider,
|
|
12
|
+
Insert,
|
|
13
|
+
InsertProvider,
|
|
14
|
+
Options,
|
|
15
|
+
Param,
|
|
16
|
+
Result,
|
|
17
|
+
ResultMap,
|
|
18
|
+
Select,
|
|
19
|
+
SelectProvider,
|
|
20
|
+
Transactional,
|
|
21
|
+
Update,
|
|
22
|
+
UpdateProvider,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'Select', 'Insert', 'Update', 'Delete',
|
|
27
|
+
'SelectProvider', 'InsertProvider', 'UpdateProvider', 'DeleteProvider',
|
|
28
|
+
'ResultMap', 'Result',
|
|
29
|
+
'Options', 'Param', 'CacheNamespace', 'DataSource', 'Transactional',
|
|
30
|
+
]
|