Package: py-myb
py_mybatispython的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,deleteinclude,if,choose,when,otherwisetrim,where,set,foreach,bind动态语法支持#{},${}新增 $f{}函数语法 ,允许开发者自行注册函数,参见:mapper_func.py废弃ognl语法 改为 python语法参数转换(核心代码在type_handler.py)sql 参数支持参数 目前只支持 dict 类型 名称为params结果映射暂不支持,查询结果参见pymysql,pymysql.cursors.DictCursor数据库支持mysql理论上可支持所有sql类型数据库使用示例基本示例测试表结构DROPTABLEIFEXISTS`fruits`;CREATETABLE`fruits`(`id`bigint(20)NOTNULL,`name`varchar(20)DEFAULTNULL,`category`varchar(20)DEFAULTNULL,`price`doubleDEFAULTNULL,`create_time`datetimeDEFAULTNULL,`type`int(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;insertinto`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.pyfrompy_mybatis.sql.pdbc_sql_templateimport*importpymysqlimportunittestdefquery_function(connection):cursor=connection.cursor()try:cursor.execute('select * from fruits')data=cursor.fetchall()connection.commit()returndatafinally:cursor.close()classPyMybatisTest(unittest.TestCase):@classmethoddefsetUpClass(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'))deftest_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)))deftest_select_one(self):print("============{}============".format('test_select_one'))print(self.sql_template.select_one(sql="select * from fruits where id=%s",args=(1)))deftest_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"))deftest_select_page(self):print("============{}============".format('test_select_page'))print(self.sql_template.select_page(sql="select * from fruits",row_bound=RowBound(1,2)))deftest_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)deftest_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)deftest_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)deftest_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 version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="Test"><sqlid="sometable">fruits</sql><sqlid="somewhere">WHEREcategory=#{category}</sql><sqlid="someinclude">FROM<includerefid="${include_target}"/><includerefid="somewhere"/></sql><deleteid="deleteById">deletefromfruitswhereid=#{id}</delete><selectid="testBasic">SELECTid,name,category,priceFROMfruitsWHEREcategory='apple'AND<![CDATA[ price < 500 ]]></select><selectid="test_function">SELECTname,category,priceFROMfruitsWHEREcategory=$f{like(params['name'])}andcreate_time=$f{time_format(params['create_time'])}</select><selectid="testParameters">SELECTid,name,category,priceFROMfruitsWHEREcategory=#{category,sql_type=VARCHAR}ANDprice>${price,sql_type=BIGINT}ANDtype=${type,sql_type=BOOLEAN}</select><selectid="testInclude">SELECTname,category,price<includerefid="someinclude"><propertyname="prefix"value="Some"/><propertyname="include_target"value="sometable"/></include></select><selectid="testIf">SELECTname,category,priceFROMfruitsWHERE1=1<iftest="'category' in params">ANDcategory=#{category}</if><iftest="'price' in params">ANDprice=${price}<iftest="params['price'] >= 400">ANDname='pear'</if></if></select><selectid="testTrim">SELECTname,category,priceFROMfruits<trimprefix="WHERE"prefixOverrides="OR">ORcategory='apple'ORprice=200</trim><trimprefix="AND"prefixOverrides="OR">(type=1ORtype=0)</trim></select><selectid="testWhere">SELECTname,category,priceFROMfruits<where>ANDcategory='apple'<iftest="'price' in params ">ANDprice=${price}</if></where></select><updateid="testSet">UPDATEfruits<set><iftest="'category' in params">category=#{category},</if><iftest="'price' in params">price=${price}</if></set>WHEREname=#{name}</update><selectid="testChoose">SELECTname,category,priceFROMfruits<where><choose><whentest="'name' in params">ANDname=#{name}</when><whentest="'category' in params and  params['category'] == 'banana'">ANDcategory=#{category}<iftest="'price' in params">ANDprice=${price}</if></when><otherwise>ANDcategory='apple'</otherwise></choose></where></select><selectid="testForeach">SELECTname,category,priceFROMfruits<where>category='apple'ANDnamein<foreachcollection="names"item="name"open="("close=")"separator=",">#{name}</foreach></where></select><insertid="testInsertMulti">INSERTINTOfruits(id,name,category,price,create_time)VALUES<foreachcollection="fruits"item="fruit"separator=",">(#{fruit['id']},#{fruit['name']},#{fruit['category']},#{fruit['price']},$f{time_format(fruit['create_time'])})</foreach></insert><selectid="testBind"><bindname="likeName"value="'%{}%'.format(params['name'])"/>SELECTname,category,priceFROMfruitsWHEREnamelike#{likeName}</select><insertid="testInsertSelective">insertintofruits<trimprefix="("suffix=")"suffixOverrides=","><iftest="'name' in params">name,</if><iftest="'category' in params">category,</if><iftest="'price' in params">price,</if></trim><trimprefix="values ("suffix=")"suffixOverrides=","><iftest="'name' in params">#{name},</if><iftest="'category' in params">#{category},</if><iftest="'category' in params">#{price},</if></trim></insert><selectid="testChooseNative">SELECTname,category,priceFROMfruits<where><choose><whentest="'name' in params">ANDname=#{name}</when><whentest="'category' in params and params['category'] == 'banana'">ANDcategory=#{category}<iftest="'price' in params">ANDprice=${price}</if></when><otherwise>ANDcategory='apple'</otherwise></choose></where></select><insertid="insertOne">insertintofruits(id,name,category,price)values(#{id},#{name},#{category},#{price})</insert></mapper>示例代码frompy_mybatis.sql.mybatis_sql_sessionimportMybatisMapperScanner,MybatisSqlSession,PooledDBimportpymysqlimportosimportunittestimporttimeclassPyMybatisTest(unittest.TestCase):@classmethoddefsetUpClass(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.'deftest_base(self):self.sql_id=self.sql_namespace+'testBasic'print("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id)))deftest_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)))deftest_include(self):self.sql_id='testInclude'params={'category':'apple'}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id,params=params)))deftest_if(self):self.sql_id='testIf'params={'category':'apple','price':5}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_one(self.sql_id,params=params)))deftest_trim(self):self.sql_id='testTrim'self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_one(self.sql_id)))deftest_where(self):self.sql_id='testWhere'params={'category':'apple','price':500}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_one(self.sql_id,params=params)))deftest_set(self):self.sql_id='testSet'params={'category':'apple','price':500,'name':'name1'}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.update(self.sql_id,params=params)))deftest_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_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id,params=params)))deftest_foreach(self):self.sql_id='testForeach'params={'names':['name1','name2','name3']}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id,params=params)))deftest_bind(self):self.sql_id='testBind'params={'name':'name'}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id,params=params)))deftest_choose_native(self):self.sql_id='testChooseNative'params={'category':'banana','price':500,'name':'name1'}self.sql_id=self.sql_namespace+self.sql_idprint("============{}============,sql_result:{}".format(self.sql_id,self.sql_session.select_list(self.sql_id,params=params)))deftest_insert_multi(self):self.sql_id='testInsertMulti'self.sql_id=self.sql_namespace+self.sql_idcreate_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)))deftest_insert_selective(self):self.sql_id='testInsertSelective'self.sql_id=self.sql_namespace+self.sql_idparams={'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_txself.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()"""deftest_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()forindex,iteminenumerate(fruits):self.sql_session.insert(sql_id='Test.insertOne',params=item)ifindex>1:raiseException('rollback test')self.sql_session.commit()print("commit")exceptExceptionase:print("exception rollback",e)self.sql_session.rollback()finally:print("finally close")self.sql_session.close()if__name__=='__main__':unittest.main()