# py_mybatis **Repository Path**: pingfan888/py_mybatis ## Basic Information - **Project Name**: py_mybatis - **Description**: python版 mybatis 应用 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 3 - **Created**: 2021-03-30 - **Last Updated**: 2023-09-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # py_mybatis - python的mybatis实现 - python的SqlTemplate实现 ## 项目起源 **特别鸣谢**:mybatis-mapper2sql **借助这位大佬使我快速实现了py_mybatis,mapper xml解析主体代码都是 hhyo大佬的** 本人是位javaer,刚刚开始学习python,当开始学习数据库相关api时,发现python中并未有很好的开发包, 重型orm 直接淘汰,不要问为什么,问就是我喜欢写sql,虽然sql写的不咋的 急需一种轻型的,开箱即用的类似mybatis的开发包,找了半天也没有,算了,不如自己造轮子,这个念头一直在我心中萦绕 无奈,python刚学没多久,稍微有些难度,时间也不太充足,直到遇到了mybatis-mapper2sql,才成为可能,感谢大佬 **特别提醒** 暂未发布开发包,只能下载使用 私以为未经过测试就发布,容易挨骂,所以等等看,看看反馈吧 ## mybatis 语法支持 ### 标签支持 - sql,select,update,insert,delete - include,if,choose,when,otherwise - trim,where,set,foreach,bind ### 动态语法支持 - #{},${} - 新增 $f{}函数语法 ,允许开发者自行注册函数,参见:mapper_func.py - 废弃ognl语法 改为 python语法 - 参数转换(核心代码在type_handler.py) ### sql 参数支持 参数 目前只支持 dict 类型 名称为params ### 结果映射 暂不支持,查询结果参见pymysql,pymysql.cursors.DictCursor ### 数据库支持 - mysql **理论上可支持所有sql类型数据库** ## 使用示例 ### 基本示例 **测试表结构** ```sql DROP TABLE IF EXISTS `fruits`; CREATE TABLE `fruits` ( `id` bigint(20) NOT NULL, `name` varchar(20) DEFAULT NULL, `category` varchar(20) DEFAULT NULL, `price` double DEFAULT NULL, `create_time` datetime DEFAULT NULL, `type` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; insert into `fruits`(`id`,`name`,`category`,`price`,`create_time`,`type`) values (1,'苹果','apple',5,'2020-06-27 11:54:38',0), (2,'梨','pear',4,'2020-06-28 11:54:38',1), (3,'香蕉','banana',3,'2020-06-30 11:55:19',2); ``` #### PdbcSqlTemplate(sql模板用法) **参见:tests/sql_template_test.py** ```python from py_mybatis.sql.pdbc_sql_template import * import pymysql import unittest def query_function(connection): cursor = connection.cursor() try: cursor.execute('select * from fruits') data = cursor.fetchall() connection.commit() return data finally: cursor.close() class PyMybatisTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.sql_template = PdbcSqlTemplate(dataSource=PooledDB( creator=pymysql, maxconnections=6, mincached=2, maxcached=5, blocking=True, maxusage=None, setsession=[], ping=0, host="localhost", user="root", password="root", database="api_user", cursorclass=pymysql.cursors.DictCursor, charset='utf8' )) def test_select_list(self): print("============{}============".format('select_with row_bound')) print( self.sql_template.select_list(sql="select * from fruits where id in (%s,%s,%s)", row_bound=RowBound(1, 2), args=(1, 2, 3)) ) print("============{}============".format('select_only')) print( self.sql_template.select_list(sql="select * from fruits where id in (%s,%s,%s)", args=(1, 2, 3)) ) def test_select_one(self): print("============{}============".format('test_select_one')) print( self.sql_template.select_one(sql="select * from fruits where id=%s", args=(1)) ) def test_select_with_no_params(self): print("============{}============".format('test_select_with_no_params')) print( self.sql_template.select_one(sql="select * from fruits where id=1") ) def test_select_page(self): print("============{}============".format('test_select_page')) print( self.sql_template.select_page(sql="select * from fruits", row_bound=RowBound(1, 2)) ) def test_delete(self): print("============{}============".format('test_delete')) row = self.sql_template.delete( sql="delete from fruits where id in(%s,%s,%s)", args=(10, 11, 9)) print(row) def test_insert_one(self): print("============{}============".format('test_insert_one')) row = self.sql_template.insert(sql="insert into fruits(id,name,price,category) values(%s,%s,%s,%s)", args=(10, '菠萝', 10, 'boluo')) print(row) def test_insert_mul(self): print("============{}============".format('test_insert_mul')) row = self.sql_template.insert_batch( sql="insert into fruits(id,name,price,category) values(%s,%s,%s,%s)", args=((11, '菠萝', 11, 'boluo11'), (9, '菠萝1', 9, 'boluo9'))) print(row) def test_query_in_connection(self): print("============{}============".format('test_query_in_connection')) print(self.sql_template.execute_in_connection(query_function)) if __name__ == '__main__': unittest.main() ``` #### MybatisSqlSession 使用 - mapper.xml ```xml fruits WHERE category = #{category} FROM delete from fruits where id =#{id} UPDATE fruits category = #{category}, price = ${price} WHERE name = #{name} INSERT INTO fruits ( id, name, category, price, create_time ) VALUES ( #{fruit['id']}, #{fruit['name']}, #{fruit['category']}, #{fruit['price']}, $f{time_format(fruit['create_time'])} ) insert into fruits name, category, price, #{name}, #{category}, #{price}, insert into fruits(id,name,category,price) values(#{id},#{name},#{category},#{price}) ``` - 示例代码 ```python from py_mybatis.sql.mybatis_sql_session import MybatisMapperScanner, MybatisSqlSession, PooledDB import pymysql import os import unittest import time class PyMybatisTest(unittest.TestCase): @classmethod def setUpClass(cls): path = os.path.abspath("./") mapper_scanner = MybatisMapperScanner() mybatis_mapper_dict = mapper_scanner.mapper_xml_scan(mapper_xml_dir=path) pool = PooledDB( creator=pymysql, maxconnections=6, mincached=2, maxcached=5, blocking=True, maxusage=None, setsession=[], ping=0, host="localhost", user="root", password="root", database="api_user", cursorclass=pymysql.cursors.DictCursor, charset='utf8' ) cls.sql_session = MybatisSqlSession(mapper_dict=mybatis_mapper_dict, dataSource=pool) cls.sql_namespace = 'Test.' def test_base(self): self.sql_id = self.sql_namespace + 'testBasic' print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id))) def test_parameters(self): self.sql_id = self.sql_namespace + 'testParameters' params = {'category': 'apple', 'price': '15.0', 'type': '001', 'content': '图书'} print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_include(self): self.sql_id = 'testInclude' params = {'category': 'apple'} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_if(self): self.sql_id = 'testIf' params = {'category': 'apple', 'price': 5} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_one(self.sql_id, params=params))) def test_trim(self): self.sql_id = 'testTrim' self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_one(self.sql_id))) def test_where(self): self.sql_id = 'testWhere' params = {'category': 'apple', 'price': 500} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_one(self.sql_id, params=params))) def test_set(self): self.sql_id = 'testSet' params = {'category': 'apple', 'price': 500, 'name': 'name1'} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.update(self.sql_id, params=params))) def test_choose(self): self.sql_id = 'testChoose' params = {'age': 1, 'sex': '1', 'name': 'name', 'price': 'price', 'category': 'banana'} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_foreach(self): self.sql_id = 'testForeach' params = {'names': ['name1', 'name2', 'name3']} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_bind(self): self.sql_id = 'testBind' params = {'name': 'name'} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_choose_native(self): self.sql_id = 'testChooseNative' params = {'category': 'banana', 'price': 500, 'name': 'name1'} self.sql_id = self.sql_namespace + self.sql_id print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.select_list(self.sql_id, params=params))) def test_insert_multi(self): self.sql_id = 'testInsertMulti' self.sql_id = self.sql_namespace + self.sql_id create_time = time.localtime() params = {'fruits': [ { 'id': 1, 'name': 'apple', 'category': 'apple', 'price': 5.0, 'create_time': create_time }, { 'id': 2, 'name': 'banana', 'category': 'banana', 'price': 3.0, 'create_time': create_time }, { 'id': 3, 'name': 'pear', 'category': 'pear', 'price': 4.0, 'create_time': create_time }, ]} print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.insert(self.sql_id, params=params))) def test_insert_selective(self): self.sql_id = 'testInsertSelective' self.sql_id = self.sql_namespace + self.sql_id params = {'name': 'apple', 'category': 'apple', 'price': 5.0 } print("============{}============,sql_result:{}".format(self.sql_id, self.sql_session.insert(self.sql_id, params=params))) """ self.sql_session.begin_tx(): 会在当前线程的thread_local下绑定一个connection 此后执行所有方法都会在一个 connection下 除非手动调用 self.sql_session.close(): 关闭连接 self.sql_session.commit():提交事务 self.sql_session.rollback(): 关闭事务 未调用:self.sql_session.begin_tx()方法:每次调用sql_session 都会新建一个连接,建议使用连接池 参考用法: try: self.sql_session.begin_tx() .... do_something_tx self.sql_session.commit() print("commit") except Exception as e: print("exception rollback", e) self.sql_session.rollback() finally: print("finally close") self.sql_session.close() """ def test_tx(self): print("============{}============".format('test_tx')) """ template method """ fruits = [ { 'id': 4, 'name': 'apple', 'category': 'apple', 'price': 5.0 }, { 'id': 5, 'name': 'banana', 'category': 'banana', 'price': 3.0 }, { 'id': 6, 'name': 'pear', 'category': 'pear', 'price': 4.0 }, ] try: self.sql_session.begin_tx() for index, item in enumerate(fruits): self.sql_session.insert(sql_id='Test.insertOne', params=item) if index > 1: raise Exception('rollback test') self.sql_session.commit() print("commit") except Exception as e: print("exception rollback", e) self.sql_session.rollback() finally: print("finally close") self.sql_session.close() if __name__ == '__main__': unittest.main() ```