一、python 操作mysql:
两个可用的模块:mysqldb和pymysql
python-mysql模块在python2.x版本下运行,参考文章:
python-mysqldb : http://www.cnblogs.com/wupeiqi/articles/5095821.html
pymysql模块可以在python3.x版本下运行,下面重点介绍。
linux下安装python-mysql模块:
yum install MySQL-python
使用pymsql操作MySQL数据库
pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同。
下载安装
pip3 install pymsql
使用操作
1、执行SQL,远程连接MySQL并执行简单的查询命令
'''在连接mysql之前,先创建用户,在mysql服务器端上,用root用户登录mysql执行:grant all on *.* to 'michael'@'%' identified by 'michael123';flush privileges;'''
import pymysql
# 创建连接
conn = pymysql.connect(host='192.168.0.50', port=3508, user='michael', passwd='michael123', db='goodboydb')
# 创建游标,就是光标的位置
cursor = conn.cursor()
# 执行SQL,并返回查询到的行数
effect_row = cursor.execute("select * from student")
print(effect_row) # 输出的是查询结果的记录条数
print(cursor.fetchone()) # 输出第一条记录,使用游标读取数据
print(cursor.fetchone()) # 输出第二条记录
print("-"*10)
print(cursor.fetchall()) # 输出全部未输出的记录
# 获取前n行数据
row_2 = cursor.fetchmany(3) # 获取1到3条记录
2、用Python在MySQL数据库的student表中插入数据
import pymysql
# 创建连接
conn = pymysql.connect(host='192.168.0.50', port=3508, user='michael', passwd='michael123', db='goodboydb')
# 创建游标,就是光标的位置
cursor = conn.cursor()
# 待插入MySQL的数据
data = [
('N1', "2017-1-1", "M"),
('N2', "2017-2-1", "M"),
('N3', "2017-3-1", "F"),
]
# 批量插入数据,在执行这条命令时,默认会执行bigin;开启一个事务,必须执行commit;才能存入数据库。
cursor.executemany("insert into student (name,register_date,gender) values(%s, %s, %s)", data)
# 提交,不然无法保存新建或者修改的数据
conn.commit()
# 关闭游标
cursor.close()
# 关闭连接
conn.close()
使用pymsql虽然可以操作数据库,但还是需要写原生的SQL语句,对于非专业的DBA程序员,写原生的SQL语言是件复杂的事情。下面使用SQLALchemy ORM操作数据库。
二、SQLALchemy ORM
1、 ORM介绍
orm英文全称object relational mapping,就是对象映射关系程序,简单来说类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的,为了保证一致的使用习惯,通过orm将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候可以直接使用编程语言的对象模型进行操作就可以了,而不用直接使用sql语言。
orm的优点:隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
ORM使我们构造固化数据结构变得简单易行。
缺点:无可避免的,自动化意味着映射和关联管理,代价是牺牲性能(早期,这是所有不喜欢ORM人的共同点)。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
2、 sqlalchemy安装
在Python中,最有名的ORM框架是SQLAlchemy。用户包括openstack\Dropbox等知名公司或应用,主要用户列表:http://www.sqlalchemy.org/organizations.html#openstack
Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
MySQL-Python
mysql+mysqldb://:@[:]/
pymysql
mysql+pymysql://:@/[?]
MySQL-Connector
mysql+mysqlconnector://:@[:]/
cx_Oracle
oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
更多详见:http://docs.sqlalchemy.org/en/latest/dialects/index.html
安装:
pip3 install sqlalchemy
3、sqlalchemy基本使用
下面就开始让见证orm的不同反响之处,使用ORM在数据库创建一个user表,详细代码如下:
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
# orm的底层还是sql语言,在连接之前要做一些准备工作
# 连接数据库。第一个参数代表的意思是:数据库是mysql,使用pymysql连接,后面依次是用户名、密码、主机、db(数据库)
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodboydb",
encoding='utf-8', echo=True) # echo=True会打印执行结果
Base = declarative_base() # 生成orm基类
# 定义User子类,继承Base基类
class User(Base):
__tablename__ = 'user' # 表名
id = Column(Integer, primary_key=True) # Column是前面导入的
name = Column(String(32))
password = Column(String(64))
# 执行下面的代码,就会执行所有子类继承父类的类,是通过父类调用子类,与平时的子类调用父类不同
Base.metadata.create_all(engine) # 创建表结构,engine是前面定义的变量
# 此时show tables;可以看见已经创建成功
除上面的创建之外,还有一种不常用的创建表的方式。这种方式实际上是,对第一种方式的再封装。
from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey
from sqlalchemy.orm import mapper
metadata = MetaData()
# 创建表结构
user = Table('user', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(50)),
Column('fullname', String(50)),
Column('password', String(12))
)
# 创建User类
class User(object):
def __init__(self, name, fullname, password):
self.name = name
self.fullname = fullname
self.password = password
# User类与表结构关联
mapper(User, user) # the table metadata is created separately with the Table construct, then associated with the User class via the mapper() function
前面的两个程序代码都是创建表,接下来创建数据试试
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodboydb",
encoding='utf-8', echo=True)
Base = declarative_base() # 生成orm基类
class User(Base):
__tablename__ = 'user' # 表名user
id = Column(Integer, primary_key=True) # Column是在代码开始处导入的
name = Column(String(32))
password = Column(String(64))
#Base.metadata.create_all(engine) # 表已经创建的话,执行这条命令不会再去创建一次
# 首先要创建一个类
Session_class = sessionmaker(bind=engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
# 其次要创建一个实例
Session = Session_class() # 生成session实例, 相当于cursor
# 用面向对象的方式创建4条记录
user_obj = User(name="Tim", password="123456") # 生成你要创建的数据对象
user_obj2 = User(name='Jack', password='112233')
user_obj3 = User(name='James', password='abcdef')
user_obj4 = User(name='Michael', password='qwertyui')
print(user_obj.name, user_obj.id) # 此时还没创建对象,打印一下id发现还是None
# 接下来统一创建记录
Session.add(user_obj) # 把要创建的数据对象添加到这个session里, 一会统一创建
Session.add(user_obj2) #
Session.add(user_obj3)
Session.add(user_obj4)
print(user_obj.name, user_obj.id) # 此时也依然还没创建
# 必须要执行commit()后记录才会创建
Session.commit() # 现此才统一提交,创建数据
查询
# Session.query() 查询操作
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodboydb",
encoding='utf-8')
Base = declarative_base() # 生成orm基类
class User(Base):
__tablename__ = 'user' # 表名user
id = Column(Integer, primary_key=True) # Column是在代码开始处导入的
name = Column(String(32))
password = Column(String(64))
def __repr__(self): # 格式化输出
return "[id:%sname:%spassword:%s]" % (self.id,self.name, self.password)
# 首先要创建一个类
Session_class = sessionmaker(bind=engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
# 其次要创建一个实例
Session = Session_class() # 生成session实例, 相当于cursor
# data = Session.query(User).filter_by(name='Tim') 该命令执行后返回的结果是select语句,还不是查询到的数据
# data = Session.query(User).filter_by(name='Tim').all() 该命令执行后返回的结果是一个查询对象
data = Session.query(User).filter_by(name='*').all()
print(data[0].name, data[0].password) # 通过查询对象去调用需要输出的信息
# 在User类中定义 __repr()__函数实现需要输出的字段信息
data = Session.query(User).filter_by().all()
print(data) # 输出:[[id:1 name:Tim password:123456], [id:2 name:Jack password:112233]]
# 使用first()方法输出第一条记录,注意没有last()方法
#data = Session.query(User).filter_by().first()
#print(data)
# 在filter()方法中添加查询条件
# data = Session.query(User).filter(User.id > 1).all()
# print(data) # 输出:[[id:2 name:Jack password:112233]]
#data = Session.query(User).filter_by(id>1).all()
#print(data)
# filter()与filter_by()方法:
# filter(User.id == 2) 条件可行
# filter(User.id > 1) 条件可行
# filter_by(User.id > 1) 与 filter_by(User.id = 1) 条件均不可行
# filter_by(id=1) 条件可行
# filter_by(id>1) 条件不可行
# 与 filter_by(id==1) 条件均不可行
修改
# 根据查询结果修改对象,注意后面是first()方法
data = Session.query(User).filter(User.id>1).filter(User.id<3).first()
print(data)
data.name = 'Kate M'
data.password = 'zxvbbnm'
# 必须要执行commit()后记录才会创建
Session.commit() # 现在才统一提交,创建数据
回滚
# 回滚
data = Session.query(User).filter(User.id>1).filter(User.id<3).first()
#创建一个用户
fake_user = User(name='Rown', password='12345')
Session.add(fake_user)
# 在没有执行回滚之前,输出有Rown这条记录
print(Session.query(User).filter(User.name.in_(['Jack', 'Rown'])).all()) # 这时看session里有刚添加和修改的数据
Session.rollback() # 此时执行rollback一下
print("after rollback")
# 在执行回滚之后,输出没有Rown这条记录,因为没有提交
print(Session.query(User).filter(User.name.in_(['Jack', 'Rown'])).all()) # 这时看session里有你刚添加和修改的数据
# 必须要执行commit()后记录才会创建
Session.commit()
获取所有数据
print(Session.query(User.name,User.id).all() )
多条件查询
# 多个查询条件
data = Session.query(User).filter(User.id>1).filter(User.id<3).all()
print(data)
上面2个filter的关系相当于 user.id >1 AND user.id <3 的效果
统计和分组
# 统计
print(Session.query(User).filter(User.name.in_(['Jack', 'michael'])).count()) # 输出:2,查询到2条记录
# 分组,要实现分组功能,要先导入func函数
from sqlalchemy import func
print(Session.query(func.count(User.name), User.name).group_by(User.name).all())
# 输出:[(1, 'Jack'), (1, 'James'), (1, 'Kate M'), (1, 'Michael'), (2, 'Tim')]
print(Session.query(User.name, func.count(User.name)).group_by(User.name).all())
# 输出:[('Jack', 1), ('James', 1), ('Kate M', 1), ('Michael', 1), ('Tim', 2)]
# 相当于原生sql为
# SELECT count(user.name) AS count_1, user.name AS user_name
# FROM user GROUP BY user.name
Session.commit()
删除操作
Session.query(User.name).filter_by(name='jack').delete() # 删除user表中所有jack的记录
Session.query(User).filter(User.id>2).delete() # 删除user表中所有id号大于2的记录
Session.commit()
连表查询
查询两个表或者多个表相关联的记录,下面代码假设有有user表和student表,相关代码如下:
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, DATE, Enum
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodboydb",
encoding='utf-8')
Base = declarative_base() # 生成orm基类
# 定义表结构
class User(Base):
__tablename__ = 'user' # 表名user
id = Column(Integer, primary_key=True) # Column是在代码开始处导入的
name = Column(String(32))
password = Column(String(64))
def __repr__(self): # 格式化输出
return "" % (self.id,self.name)
# 在操作Student表之前要先定义表结构,并且要继承Base类
class Student(Base):
__tablename__='student'
id = Column(Integer, primary_key=True)
name = Column(String(32), nullable=False)
register_date = Column(DATE, nullable=False)
gender = Column(Enum('M','F'), nullable=False)
# 注意这个字段是枚举类型并且不能为空,如果原表中有记录的这个字段为空,执行可能会报错,此时可用下面的语句代替
# gender = Column(String(32), nullable=False)
def __repr__(self):
return "" % (self.id, self.name)
Base.metadata.create_all(engine) # 表已经创建的话,执行这条命令不会再去创建一次
# 首先要创建一个类
Session_class = sessionmaker(bind=engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
# 其次要创建一个实例
Session = Session_class() # 生成session实例, 相当于cursor
# 连表查询
# 首先查询两个表上id号一样的记录
print(Session.query(User, Student).filter(User.id==Student.id).all())
# 使用join方法查询,两个表要有外键关联才行
# print(Session.query(User).join(Student).all()) # 执行会报错,因为两个没有外键关联
print(Session.query(User).join(Student, isouter=True).all()) # 同样因为没有外键关联不能查询
Session.commit()
连表查询(创建有外键关联的表)
在设计数据表存储数据时,要考虑不要将具有相同内容的数据重复存储在多个记录当中,数据表设计要合理,当多条记录需要指向相同的数据内容时,可对数据内容单独建数据表存储,使用外键进行关联。
为了演示连表查询,下面重新创建两个有外键关联的表,代码如下所示:
# 在mysql服务器上创建一个新的数据库:create database goodmandb charset utf8;
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, DATE, ForeignKey
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodmandb",
encoding='utf-8')
Base = declarative_base() # 生成orm基类
# 创建一个student表
class Student(Base):
__tablename__='student'
id = Column(Integer, primary_key=True)
name = Column(String(32), nullable=False)
register_date = Column(DATE, nullable=False)
def __repr__(self):
return "" % (self.id, self.name)
class StudyRecord(Base):
__tablename__ = 'study_record'
id = Column(Integer, primary_key=True)
day = Column(Integer, nullable=False)
status = Column(String(32), nullable=False)
stu_id = Column(Integer, ForeignKey("student.id")) # 外键关联student表的id字段
# 在StudyRecord类中加上下面这句,就可以通过student去反查student表中的字段
student = relationship("Student", backref="my_study_record") # 这个很有用,允许你在Student表里通过backref字段反向查出所有它在my_study_record表里的关联项
# 通过student去查询student表中的记录,然后Student又通过my_study_record查询study_record表中的记录
# 上面的命令相当于实例化student=Student(),study_obj = query(id=1)
#student=query(Student).filter(Student.id == stu_obj.stu_id).first()
def __repr__(self):
#return "" % (self.id, self.day, self.status)
return "" % (self.student.name, self.day, self.status)
# 创建表结构
Base.metadata.create_all(engine)
# 此时在mysql上的goodmandb数据库上可以看到student,study_record表已创建
# use goodmandb;
# show tables;
# show create tables study_record; # 查看表的创建信息
# 接下来往数据表中连续插入几条记录,先创建session
Session_class = sessionmaker(bind=engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
Session = Session_class() # 生成session实例, 相当于cursor
# # 创建记录
# s1 = Student(name='michael', register_date='2018-09-01')
# s2 = Student(name='James', register_date='2018-06-01')
# s3 = Student(name='Kate', register_date='2018-03-01')
# s4 = Student(name='Tom', register_date='2018-01-01')
#
# study_obj1 = StudyRecord(day=1, status='YES', stu_id=1)
# study_obj2 = StudyRecord(day=2, status='NO', stu_id=1)
# study_obj3 = StudyRecord(day=3, status='YES', stu_id=1)
# study_obj4 = StudyRecord(day=1, status='YES', stu_id=2)
#
# # 写入数据库, 在数据库上验证是否写入成功,select * from student;
# Session.add_all([s1, s2, s3,s4, study_obj1, study_obj2, study_obj3,study_obj4])
# 查询验证:
stu_obj = Session.query(Student).filter(Student.name=='michael').first()
# print(stu_obj) # 输出:<1 name:michael>1>
# 在Study_record类中添加关系后通过my_study_record查询student表中的字段。输出与Study_record类中的__repr__方法有关。
#print(stu_obj.my_study_record) # 输出:[<1 day:1 status:yes>, <2 day:2 status:no>, <3 day:3 status:yes>]3>2>1>
print(stu_obj.my_study_record) # 修改Study_record类中的__repr__方法后的输出结果如下:
# [, , ]
Session.commit()
多外键关联查询
现在有两个表, 一个是customer表,另一个是address表,在cutomer表中有两个字段关联到address表。
在实际操作中,创建表结构的文件与增删改查的文件应该分开,下面创建表结构的文件是orm_many_fk.py,在增删改查表中导入该文件。
# file name:orm_many_fk.py
# 多外键关联
# Customer表有2个字段都关联了Address表
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodmandb",
encoding='utf-8')
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String(64))
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address", foreign_keys=[billing_address_id])
shipping_address = relationship("Address", foreign_keys=[shipping_address_id])
# 上面两条命令后面必须要给参数foreign_keys指向哪个字段,要不然在插入数据的时候会报错,sqlalchemy才能分清楚哪个外键对应哪个字段
# def __repr__(self):
# return self.street, self.city, self.state
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String(64))
city = Column(String(64))
state = Column(String(64))
def __repr__(self):
return self.street
# 创建表结构
Base.metadata.create_all(engine)
# 创建成功后,show create table customer; 可以看到有两个外键关联
# desc customer;
# 在实际操作的时候,把表结构创建后增、删、改、查等操作应该在另外的代码文件上操作
接下来在orm_api.py文件中导入orm_manyfk.py文件,并且在orm_api.py文件中插入数据和查询数据。
from day_mysql import orm_many_fk
from sqlalchemy.orm import sessionmaker
# 创建Session
Session_class = sessionmaker(bind=orm_many_fk.engine)
session = Session_class()
# 插入数据
# addr1 = orm_many_fk.Address(street='Chungxilu', city='ChengDu', state='SiChuan')
# addr2 = orm_many_fk.Address(street='Dongdajie', city='ChengDu', state='SiChuan')
# addr3 = orm_many_fk.Address(street='TFGC', city='ChengDu', state='SiChuan')
#
# session.add_all([addr1, addr2, addr3])
# # 在创建customer表记录的时候,先创建Address表的记录
# c1 = orm_many_fk.Customer(name='Micheal', billing_address=addr1, shipping_address=addr2)
# c2 = orm_many_fk.Customer(name='TOM', billing_address=addr3, shipping_address=addr3)
#
# session.add_all([c1, c2])
# 此时前面的数据已经创建好了,接下来查询数据
obj = session.query(orm_many_fk.Customer).filter(orm_many_fk.Customer.name=='micheal').first()
print(obj.name, obj.billing_address, obj.shipping_address) # 输出:Micheal Chungxilu Dongdajie
session.commit()
多对多关系
现在来设计一个能描述“图书”与“作者”的关系的表结构,需求是
1.一本书可以有好几个作者一起出版
2.一个作者可以写好几本书
要实现这个功能,至少需要创建3张表,第一张表是作者表(authors):至少有name,author_id两个字段
第二张表是书名表(books):至少有bookname,book_id两个字段
第三张表存放前面两张表的id字段(book_m2m_author):即至少有book_id, author_id两个字段
下面是第一个代码文件orm_m2m.py,创建表结构。
# file name: orm_m2m.py
from sqlalchemy import Table, Column, Integer,String,DATE, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodmandb",
encoding='utf-8')
Base = declarative_base()
# 注意第三张的创建方式
book_m2m_author = Table('book_m2m_author', Base.metadata,
Column('book_id',Integer,ForeignKey('books.id')),
Column('author_id',Integer,ForeignKey('authors.id')),
)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer,primary_key=True)
name = Column(String(64))
pub_date = Column(DATE)
authors = relationship('Author',secondary=book_m2m_author,backref='books')
# 上面命令的secondary参数查询第三张表,再通过backref参数反向查books表的字段
def __repr__(self):
return self.name
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
name = Column(String(32))
def __repr__(self):
return self.name
# 创建表结构
Base.metadata.create_all(engine)
# 此时在数据库上执行:desc book_m2m_author;输出如下:
# +-----------+---------+------+-----+---------+-------+
# | Field | Type | Null | Key | Default | Extra |
# +-----------+---------+------+-----+---------+-------+
# | book_id | int(11) | YES | MUL | NULL | |
# | author_id | int(11) | YES | MUL | NULL | |
# +-----------+---------+------+-----+---------+-------+
接下来在orm_m2m_api.py文件中插入数据,并且查询数据:
# 通过orm_m2m创建表结构后,接下来创建数据
from day_mysql import orm_m2m
from sqlalchemy.orm import sessionmaker
# 首先要创建一个类
Session_class = sessionmaker(bind=orm_m2m.engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
# 其次要创建一个实例
session = Session_class() # 生成session实例, 相当于cursor
# 插入数据
# b1 = orm_m2m.Book(name='learn Python', pub_date='2016-03-15')
# b2 = orm_m2m.Book(name='learn Linux', pub_date='2017-05-15')
# b3 = orm_m2m.Book(name='learn Mysql', pub_date='2015-07-15')
#
# a1 = orm_m2m.Author(name='Michael')
# a2 = orm_m2m.Author(name='James')
# a3 = orm_m2m.Author(name='Jack')
#
# # 设置书与作者的关联关系
# b1.authors = [a1, a3]
# b3.authors = [a1, a2, a3] # 注意这里在实际关联的是b2
#
# session.add_all([b1, b2, b3, a1, a2, a3])
# session.commit()
# 此时在mysql上执行 select * from book_m2m_author;输出如下:
# +---------+-----------+
# | book_id | author_id |
# +---------+-----------+
# | 2 | 1 |
# | 2 | 2 |
# | 2 | 3 |
# | 1 | 1 |
# | 1 | 3 |
# +---------+-----------+
# 下面做查询操作,查询michael出版的所有书名
author_obj = session.query(orm_m2m.Author).filter(orm_m2m.Author.name == 'michael').first()
#print(author_obj.books) # 输出:[learn Python, learn Mysql]
print(author_obj.books[1].pub_date) # 输出:2015-07-15,通过书名获取出版日期
# 反向查询,通过书名查询作者
book_obj = session.query(orm_m2m.Book).filter(orm_m2m.Book.id == 2).first()
print(book_obj.authors) # 输出:[Michael, James, Jack],如果输出为空,注意检查book_id是否关联正确
session.commit()
(未完)