orm 框架引入
- django orm--》只能用在django中,不能独立使用
[1]Python界的orm框架
- peewee
- sqlalchemy:企业级
- djagno rom
- Tortoise ORM
- GINO
[2]Go界orm框架
- gorm 国人写的
- Xorm
[3]Java界orm框架
- ssh 框架springmvc structs Hibernate(java的orm框架)
- ssh spring springmvc Hibernate
- ssm Spring SpringMVC MyBatis (orm框架)
- springboot :sb框架 ---》java工程师就是spring工程师
- spring cloud
【一】ORM框架引入
-
ORM (Object-Relational Mapping) 是一种编程技术,用于在关系型数据库和面向对象编程语言之间建立映射关系,将数据库中的表、记录等转化为对象,并使开发人员可以通过操作对象的方式来操作数据库。以下是一些常见的ORM框架。
-
Django ORM: Django是一个功能强大的Python Web框架,其中包含了自己的ORM,但它只能在Django项目中使用。可以使用Django ORM来定义模型类,然后使用该类进行数据库查询和操作。
【二】Python界的ORM框架
【1】Peewee
- Peewee是一个简单而轻量级的Python ORM框架,具有简单直观的API和丰富的功能。
- 示例代码:
from peewee import * db = SqliteDatabase('my_app.db') class User(Model): username = CharField() password = CharField() class Meta: database = db # 创建表格 User.create_table() # 插入数据 user = User(username='john', password='secret') user.save()
【2】SQLAlchemy
- SQLAlchemy是一个功能强大且广泛使用的Python ORM框架,适用于企业级应用程序。
- 示例代码:
from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base engine = create_engine('sqlite:///my_app.db') Session = sessionmaker(bind=engine) session = Session() Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String) password = Column(String) Base.metadata.create_all(engine) # 创建表格 # 插入数据 user = User(username='john', password='secret') session.add(user) session.commit()
【4】Django ORM
-
Django的ORM提供了丰富的功能,可与Django Web框架紧密集成,用于查询和操作数据库。
-
示例代码:
from django.db import models class User(models.Model): username = models.CharField(max_length=100) password = models.CharField(max_length=100) # 插入数据 user = User(username='john', password='secret') user.save()
【5】Tortoise ORM
- Tortoise ORM是一个异步Python ORM框架,具有简单易用的API,并支持多种异步IO库。
- 示例代码:
from tortoise import fields, models class User(models.Model): username = fields.CharField(max_length=100) password = fields.CharField(max_length=100) # 插入数据 user = User(username='john', password='secret') await user.save()
【6】GINO
- GINO是一个异步Python ORM框架,基于SQLAlchemy构建,专注于与异步IO库的兼容性和性能优化。
- 示例代码:
from sqlalchemy import Column, String from gino import Gino db = Gino() class User(db.Model): __tablename__ = 'users' username = Column(String) password = Column(String) # 插入数据 await db.gino.create_all() # 创建表格 user = await User.create(username='john', password='secret')
【三】Go界ORM框架
【1】GORM
- GORM是一个功能强大且易用的Go ORM框架,由国人编写,支持多种数据库。
- 示例代码:
package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) type User struct { ID uint Username string Password string } func main() { dsn := "username:password@tcp(127.0.0.1:3306)/db_name?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") } db.AutoMigrate(&User{}) // 创建表格 // 插入数据 user := User{Username: "john", Password: "secret"} db.Create(&user) }
【2】Xorm
- Xorm是Go语言中另一个流行的ORM框架,拥有丰富的特性,并支持多种数据库。
- 示例代码:
package main import ( "github.com/go-xorm/xorm" _ "github.com/go-sql-driver/mysql" ) type User struct { ID int64 Username string Password string } func main() { engine, err := xorm.NewEngine("mysql", "username:password@tcp(127.0.0.1:3306)/db_name") if err != nil { panic("failed to connect database") } engine.Sync2(&User{}) // 创建表格 // 插入数据 user := User{Username: "john", Password: "secret"} engine.Insert(&user) }
【四】Java界ORM框架
【1】Hibernate
- Hibernate是Java语言中最常用的ORM框架之一,通过映射配置文件或注解来建立对象和数据库表之间的映射关系。
- 示例代码:
import javax.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @Column private String password; // Getters and Setters } // 插入数据 EntityManagerFactory emf = Persistence.createEntityManagerFactory("my_app"); EntityManager em = emf.createEntityManager(); User user = new User(); user.setUsername("john"); user.setPassword("secret"); em.getTransaction().begin(); em.persist(user); em.getTransaction().commit();
【2】MyBatis
- MyBatis是一种半自动ORM框架,将SQL语句与Java方法进行映射,提供了灵活的查询和操作数据库的方式。
- 示例代码:
import org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { @Insert("INSERT INTO users(username, password) VALUES(#{username}, #{password})") void insertUser(User user); } // 插入数据 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = new User(); user.setUsername("john"); user.setPassword("secret"); mapper.insertUser(user); session.commit(); session.close();
【3】Spring Data JPA
- Spring Data JPA是基于JPA(Java Persistence API)标准的ORM框架,通过继承或声明接口的方式,提供了简化数据库访问的方法。
- 示例代码:
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { } // 插入数据 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserRepository repository = context.getBean(UserRepository.class); User user = new User(); user.setUsername("john"); user.setPassword("secret"); repository.save(user);
【4】Spring Boot + Spring Data JPA
- Spring Boot是一个快速开发的框架,可轻松集成Spring Data JPA进行ORM操作。
- 示例代码:
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.SpringApplication; import org.springframework.beans.factory.annotation.Autowired; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @Column private String password; // Getters and Setters } public interface UserRepository extends JpaRepository<User, Long> { } @SpringBootApplication public class Application implements CommandLineRunner { @Autowired private UserRepository repository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } public void run(String... args) throws Exception { User user = new User(); user.setUsername("john"); user.setPassword("secret"); repository.save(user); } }
sqlalchemy框架深入学习
【一】介绍
- SQLAlchemy是一个基于Python实现的ORM框架。
- 该框架建立在 DB API之上,使用关系对象映射进行数据库操作
- 简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
pip3 install sqlalchemy
【二】组成部分
- Engine,框架的引擎
- Connection Pooling ,数据库连接池
- Dialect,选择连接数据库的DB API种类(sqlite,mysql...)
- Schema/Types,架构和类型
- SQL Exprression Language,SQL表达式语言
- SQLAlchemy本身无法操作数据库,其必须以来pymsql等第三方插件,Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
【三】操作不同数据库
MySQL-Python
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
pymysql
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
MySQL-Connector
mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
cx_Oracle
oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
orm不能创建数据库---》只能创建表,删除表---》sqlalchemy不能增加删除字段--》借助于第三方插件实现
【四】简单使用(能创建表,删除表,不能修改表)
-
修改表:在数据库添加字段,类对应上
-
执行原生sql(不常用)
import time
import threading
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/test?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
def task(arg):
# 第二步:通过engine获得链接
conn = engine.raw_connection()
cursor = conn.cursor()
cursor.execute(
"select * from app01_book"
)
result = cursor.fetchall()
print(result)
cursor.close()
conn.close()
for i in range(20):
t = threading.Thread(target=task, args=(i,))
t.start()
【五】orm进阶使用
models.py
import datetime
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
Base = declarative_base()
class Users(Base):
__tablename__ = 'users' # 数据库表名称
id = Column(Integer, primary_key=True) # id 主键
name = Column(String(32), index=True, nullable=False) # name列,索引,不可为空
# email = Column(String(32), unique=True)
#datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
# ctime = Column(DateTime, default=datetime.datetime.now)
# extra = Column(Text, nullable=True)
__table_args__ = (
# UniqueConstraint('id', 'name', name='uix_id_name'), #联合唯一
# Index('ix_id_name', 'name', 'email'), #索引
)
def init_db():
"""
根据类创建数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.create_all(engine)
def drop_db():
"""
根据类删除数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.drop_all(engine)
if __name__ == '__main__':
# drop_db()
init_db()
app.py
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from models import Users
#"mysql+pymysql://root@127.0.0.1:3306/aaa"
engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Connection = sessionmaker(bind=engine)
# 每次执行数据库操作时,都需要创建一个Connection
con = Connection()
# ############# 执行ORM操作 #############
obj1 = Users(name="lqz")
con.add(obj1)
# 提交事务
con.commit()
# 关闭session,其实是将连接放回连接池
con.close()
【六】sqlalchemy
- 创建模型表
models.py
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, DateTime, UniqueConstraint, Index
# 1 sqlalchemy,原生操作sql
# 2 sqlalchemy创建表删除表
Base = declarative_base()
# print(type(Base))
class User(Base):
# 以__开头的是配置
__tablename__ = 'users' # 数据库表名称,如果不写,以类名作为表名
id = Column(Integer, primary_key=True) # 主键索引,聚簇索引
name = Column(String(64), index=True, nullable=False) # name字段加辅助索引
email = Column(String(32), unique=True)
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
ctime = Column(DateTime, default=datetime.datetime.now())
extra = Column(Text, nullable=True)
__table_args__ = (
UniqueConstraint('id', 'name', name='uix_id_name'), # 联合唯一
Index('ix_id_name', 'name', 'email'), # 索引
)
- 创建表
from sqlalchemy import create_engine
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 创建表,删除表
# 1 在数据库中创建表
Base.metadata.create_all(engine)
# 2 删除表
# Base.metadata.drop_all(engine)
- 存储数据(基于session)
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
from models import Base, User
from sqlalchemy.orm import sessionmaker
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 创建表,删除表
# (1)在数据库中创建表
# Base.metadata.create_all(engine)
# (2)删除表
# Base.metadata.drop_all(engine)
# 操作表中得数据
# 第二步:得到一个session对象---》不是flask的session---》会话---》链接
# 把引擎传入
Session = sessionmaker(bind=engine)
# 得到session对象
session = Session()
# 第三步:使用session对象操作数据
# 创建对象
user = User(name='dream', email='123@qq.com')
# 保存到数据库:将创建的对象交给 session会话
session.add(user)
# 提交事务
session.commit()
# 关闭会话
session.close()
- 查看数据
mysql> use db001;
Database changed
mysql> show tables;
+-----------------+
| Tables_in_db001 |
+-----------------+
| users |
+-----------------+
1 row in set (0.00 sec)
mysql> select * from users;
+----+-------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+-------+------------+---------------------+-------+
| 1 | dream | 123@qq.com | 2023-08-26 14:52:13 | NULL |
+----+-------+------------+---------------------+-------+
1 row in set (0.00 sec)
【七】scoped_session线程安全
- 如果集成到flask中,session会话是要做成全局,还是每个视图函数有自己的一个
- 应该做成,每个视图函数,都新创建一个session对象
- 这样每次都要加括号得到session对象
- scoped_session 全局只有一个session对象,在不同视图函数就用这一个---》保证线程安全
- 做成了每个线程自己一个单独的session对象
【1】scoped_session 使用
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
from models import Base, User
from sqlalchemy.orm import sessionmaker, scoped_session
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 创建表,删除表
# (1)在数据库中创建表
# Base.metadata.create_all(engine)
# (2)删除表
# Base.metadata.drop_all(engine)
# 操作表中得数据
# 第二步:得到一个session对象---》不是flask的session---》会话---》链接
# 把引擎传入
Session = sessionmaker(bind=engine)
# 得到session对象
session = Session()
print(type(session)) # <class 'sqlalchemy.orm.session.Session'>
# 使用 scoped_session 将 原来的 Session包一下, 这个session全局用一个即可
# :sqlalchemy.orm.scoping.scoped_session
session = scoped_session(Session)
print(type(session)) # <class 'sqlalchemy.orm.scoping.scoped_session'>
# 第三步:使用session对象操作数据
# 创建对象
user = User(name='dream', email='456@qq.com')
# 保存到数据库:将创建的对象交给 session会话
session.add(user)
# 提交事务
session.commit()
# 关闭会话
session.close()
- 查看数据
mysql> select * from users;
+----+-------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+-------+------------+---------------------+-------+
| 1 | dream | 123@qq.com | 2023-08-26 14:52:13 | NULL |
| 2 | dream | 456@qq.com | 2023-08-26 14:57:55 | NULL |
+----+-------+------------+---------------------+-------+
2 rows in set (0.00 sec)
【2】scoped_session 源码分析
scoped_session
是SQLAlchemy中用于实现线程范围的会话对象的类。- 它通过为每个线程创建独立的会话来提供线程安全的数据库访问。
from sqlalchemy.orm.scoping import scoped_session
scoped_session
类没有add
方法,但是可以通过add
方法添加数据。- 这是因为在
scoped_session
类上加了一个装饰器@create_proxy_methods
。 - 装饰器的作用是为
scoped_session
类添加一组代理方法和属性,这些方法和属性都是通过委派给底层的Session
对象实现的。 - 装饰器接收一个参数列表,指定了要为
scoped_session
类添加的方法和属性。 - 这些方法包括
add
、query
、commit
等常用的数据库操作方法。
- 这是因为在
@create_proxy_methods(
Session,
":class:`_orm.Session`",
":class:`_orm.scoping.scoped_session`",
classmethods=["close_all", "object_session", "identity_key"],
methods=[
"__contains__",
"__iter__",
"add",
"add_all",
"begin",
"begin_nested",
"close",
"commit",
"connection",
"delete",
"execute",
"expire",
"expire_all",
"expunge",
"expunge_all",
"flush",
"get",
"get_bind",
"is_modified",
"bulk_save_objects",
"bulk_insert_mappings",
"bulk_update_mappings",
"merge",
"query",
"refresh",
"rollback",
"scalar",
"scalars",
],
attributes=[
"bind",
"dirty",
"deleted",
"new",
"identity_map",
"is_active",
"autoflush",
"no_autoflush",
"info",
],
)
class scoped_session(Generic[_S]):
- 补充:类装饰器本质
###1 加在类上的装饰器 def auth(func): def inner(*args,**kwargs): res=func(*args,**kwargs) res.add='999' return res return inner @auth # Person=auth(Person) class Person(): pass # 执行:Person()----->在执行---》inner p=Person() # inner() inner的返回值给了p print(p.add) # 2 类作为装饰器
- 每个线程自己的一个session对象
scoped_session
类的构造函数有两个参数:session_factory
和scopefunc
。
- 其中,
session_factory
是一个sessionmaker
对象,用于创建新的Session对象。 scopefunc
是一个可选的函数,用于确定会话对象的作用域。- 我们只传入了与一个 session_factory
- 而 scopefunc 是None
def __init__(
self,
session_factory: sessionmaker[_S],
scopefunc: Optional[Callable[[], Any]] = None,
):
self.session_factory = session_factory
if scopefunc:
self.registry = ScopedRegistry(session_factory, scopefunc)
else:
self.registry = ThreadLocalRegistry(session_factory)
- 如果
scopefunc
参数为None
,则会执行self.registry = ThreadLocalRegistry(session_factory)
,创建一个ThreadLocalRegistry
对象来管理会话对象。- 它通过Python标准库中的
threading.local()
实现线程范围的数据存储。 ThreadLocalRegistry
类的构造函数接收一个createfunc
参数,该参数是一个可调用对象,用于创建新的会话对象。ThreadLocalRegistry
内部使用了threading.local()
来维护每个线程独立的会话对象。- 每个线程通过调用
createfunc
获取自己的会话对象,并将其保存在threading.local()
对象中供后续使用。
- 它通过Python标准库中的
def __init__(self, createfunc: Callable[[], _T]):
self.createfunc = createfunc
self.registry = threading.local()
def __call__(self) -> _T:
try:
return self.registry.value # type: ignore[no-any-return]
except AttributeError:
# self.createfunc() ---- > Session() --- >真正的session对象
# value 是 session 会话对象,放在了 registry 里面
# threading.local() --- > 每个线程单独用自己的
val = self.registry.value = self.createfunc()
return val # type: ignore[no-any-return]
-
threading.local()
-
多线程并发操作,不需要加锁,不会出现并发安全问题,每个线程用的都是自己的那个数据
-
核心原理是:通过线程id号做个区分
-线程1 t.a=88 内部 ---》{线程id号:{a:88}} -线程1 t.a=77 内部 ---》{线程id号:{a:88},线程id号:{a:77}}
-
不同线程用的都是 t 对象,threading.local(),但是每个线程都是用自己的
线程1 t.a=100 --->在当前线程中 print(t.a) ---》100 线程2 t.a=99 --->在当前线程中 print(t.a) ---》99
-
-
小结
- scoped_session的对象,就像使用Session的对象一样用(内部用了装饰器修饰)
- scoped_session 是线程安全的,每个线程自己的一个session对象
- t=threading.local(),多线程并发操作,不需要加锁,不会出现并发安全问题,保证了每个线程用的都是自己的那个数据
【八】基于 scoped_session 增删查改数据
【0】基础数据
- models.py
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, DateTime, UniqueConstraint, Index
# 1 sqlalchemy,原生操作sql
# 2 sqlalchemy创建表删除表
Base = declarative_base()
# print(type(Base))
class User(Base):
# 以__开头的是配置
__tablename__ = 'users' # 数据库表名称,如果不写,以类名作为表名
id = Column(Integer, primary_key=True) # 主键索引,聚簇索引
name = Column(String(64), index=True, nullable=False) # name字段加辅助索引
email = Column(String(32), unique=True)
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
ctime = Column(DateTime, default=datetime.datetime.now())
extra = Column(Text, nullable=True)
__table_args__ = (
UniqueConstraint('id', 'name', name='uix_id_name'), # 联合唯一
Index('ix_id_name', 'name', 'email'), # 索引
)
- main.py
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
from models import Base, User
from sqlalchemy.orm import sessionmaker, scoped_session
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 创建表,删除表
#1 在数据库中创建表
# Base.metadata.create_all(engine)
# 2 删除表
# Base.metadata.drop_all(engine)
# ----------- 最后要提价事务,关闭链接 -----------
# 提交事务
session.commit()
# 关闭会话
session.close()
【1】添加数据
(1)add
# (1)增加数据
# (1.1) add
session.add(User(name="dream", email="555"))
mysql> select * from users;
+----+-------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+-------+------------+---------------------+-------+
| 1 | dream | 123@qq.com | 2023-08-26 14:52:13 | NULL |
| 2 | dream | 456@qq.com | 2023-08-26 14:57:55 | NULL |
| 3 | dream | 789@qq.com | 2023-08-26 14:59:15 | NULL |
| 4 | dream | 555 | 2023-08-26 15:35:50 | NULL |
+----+-------+------------+---------------------+-------+
4 rows in set (0.00 sec)
(2)add_all
# (1)增加数据
# (1.2) add_all
user1 = User(name="dream_1", email="111")
user2 = User(name="dream_2", email="222")
# add_all 参数为可迭代对象,内部是 for 循环
session.add_all([user1, user2])
mysql> select * from users;
+----+---------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+---------+------------+---------------------+-------+
| 1 | dream | 123@qq.com | 2023-08-26 14:52:13 | NULL |
| 2 | dream | 456@qq.com | 2023-08-26 14:57:55 | NULL |
| 3 | dream | 789@qq.com | 2023-08-26 14:59:15 | NULL |
| 4 | dream | 555 | 2023-08-26 15:35:50 | NULL |
| 5 | dream_1 | 111 | 2023-08-26 15:36:36 | NULL |
| 6 | dream_2 | 222 | 2023-08-26 15:36:36 | NULL |
+----+---------+------------+---------------------+-------+
6 rows in set (0.00 sec)
【2】查询数据
(1)filter
# (2) 查找数据
# (2.1) filter :传表达式
res = session.query(User).filter(User.id > 2).all()
print(res)
[<models.User object at 0x00000131B50C6AF0>, <models.User object at 0x00000131B50C6A60>, <models.User object at 0x00000131B50C6B80>, <models.User object at 0x00000131B50C6BE0>]
- 解决办法
- 重写模型表加入
# 查询多条数据不生效 # 只有打印对象的时候才会触发 __str__ 方法 # 当查询单条数据时,会触发 __str__ 方法 def __str__(self): return self.name # 重写 __repr__ 方法,查到的数据返回的是 name 否则是 Object # 当查询结果是多条数据时,会触发 __repr__ 方法 def __repr__(self): return self.name
# (2) 查找数据
# (2.1) filter :传表达式
# .all() : 查出来的数据就是一个列表,不是queryset对象
# .first() : 想查单条直接用 first 方法
res_all = session.query(User).filter(User.id > 2).all()
# 等价于 select * from User where User.id > 2 limit 1;
res_first = session.query(User).filter(User.id > 2).first()
# 在表达式中 == 才是 = 的效果
res_name_dream_all = session.query(User).filter(User.name == 'dream').all()
res_name_dream_first = session.query(User).filter(User.name == 'dream').first()
print('res_all :>>> ', res_all)
print('res_first :>>> ', res_first)
print('res_name_dream_all :>>> ', res_name_dream_all)
print('res_name_dream_first :>>> ', res_name_dream_first)
res_all :>>> [dream, dream, dream_1, dream_2]
res_first :>>> dream
res_name_dream_all :>>> [dream, dream, dream, dream]
res_name_dream_first :>>> dream
(2)filter by
# (2.2)filter by : 传参数
res_all = session.query(User).filter_by(id=2).all()
res_first = session.query(User).filter_by(id=2).first()
res_name_dream_all = session.query(User).filter_by(name='dream').all()
res_name_dream_first = session.query(User).filter_by(name='dream').first()
print('res_all :>>> ', res_all)
print('res_first :>>> ', res_first)
print('res_name_dream_all :>>> ', res_name_dream_all)
print('res_name_dream_first :>>> ', res_name_dream_first)
res_all :>>> [dream]
res_first :>>> dream
res_name_dream_all :>>> [dream, dream, dream, dream]
res_name_dream_first :>>> dream
【3】删除数据
# (3)删除数据
# delete * from User where id >=6 ;
res_del = session.query(User).filter(User.id >= 6).delete()
print(res_del) # 结果为影响的行数
# 没有先查询出对象再删除对象的方法,只有查完就删的方法
user=session.query(User).filter(User.id == 5).first()
user.delete() # 它没有单独删对象的
【4】修改数据
# (4)修改数据
# 先查出 数据 再 修改数据 , res 为影响的行数
res = session.query(User).filter(User.id > 0).update({"name": "dream"})
mysql> select * from users;
+----+-------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+-------+------------+---------------------+-------+
| 1 | dream | 123@qq.com | 2023-08-26 14:52:13 | NULL |
| 2 | dream | 456@qq.com | 2023-08-26 14:57:55 | NULL |
| 3 | dream | 789@qq.com | 2023-08-26 14:59:15 | NULL |
| 4 | dream | 555 | 2023-08-26 15:35:50 | NULL |
| 5 | dream | 111 | 2023-08-26 15:36:36 | NULL |
+----+-------+------------+---------------------+-------+
5 rows in set (0.00 sec)
# 类似于django的F查询
# (1)字符串相加
session.query(User).filter(User.id > 0).update({User.name: User.name + "099"}, synchronize_session=False)
# (2)数字相加
session.query(User).filter(User.id > 0).update({"age": User.age + 1}, synchronize_session="evaluate")
# 查到单个对象,修改属性
# 再用add增加进去
# 修改数据成功
# add 只要有id,就是修改
res = session.query(User).filter(User.id == 1).first()
# print(res)
res.name = 'yyyy'
session.add(res)
mysql> select * from users;
+----+-------+------------+---------------------+-------+
| id | name | email | ctime | extra |
+----+-------+------------+---------------------+-------+
| 1 | yyyy | 123@qq.com | 2023-08-26 14:52:13 | NULL |
| 2 | dream | 456@qq.com | 2023-08-26 14:57:55 | NULL |
| 3 | dream | 789@qq.com | 2023-08-26 14:59:15 | NULL |
| 4 | dream | 555 | 2023-08-26 15:35:50 | NULL |
| 5 | dream | 111 | 2023-08-26 15:36:36 | NULL |
+----+-------+------------+---------------------+-------+
5 rows in set (0.00 sec)
【九】模型表的关系
- 一对一
- 一对多
- 多对多
- 都是外键关系
- 一对一其实是 一对多 的一种特例
【1】一对多(一对一)
(1)表关系创建
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, DateTime, UniqueConstraint, Index, ForeignKey
from sqlalchemy.orm import relationship
class Hobby(Base):
__tablename__ = 'hobby'
id = Column(Integer, primary_key=True)
caption = Column(String(50), default='篮球')
def __str__(self):
return self.caption
def __repr__(self):
return self.caption
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True) # 不会自动生成id
name = Column(String(32), index=True, nullable=True)
# hobby指的是tablename而不是类名
# 一对多关系一旦确立,关联关系写在多的一方---》物理外键
hobby_id = Column(Integer, ForeignKey("hobby.id"))
# 跟数据库无关,不会新增字段,只用于快速链表操作
# 类名,backref用于反向查询
hobby = relationship('Hobby', backref='pers') # 以后 person.hobby 就是hobby对象
def __str__(self):
return self.name
def __repr__(self):
return self.name
(2)创建表
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
from models import Base, User
from sqlalchemy.orm import sessionmaker, scoped_session
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 创建表,删除表
# 1 在数据库中创建表
Base.metadata.create_all(engine)
# 2 删除表
# Base.metadata.drop_all(engine)
# ----------- 最后要提价事务,关闭链接 -----------
# 提交事务
session.commit()
# 关闭会话
session.close()
mysql> show tables;
+-----------------+
| Tables_in_db001 |
+-----------------+
| hobby |
| person |
| users |
+-----------------+
3 rows in set (0.00 sec)
(3)操作数据
[1]添加数据
# (1) 添加数据
# 因为有外键约束,所以要先添加 hobby 再添加 person
# 方式一 :
session.add(Hobby(caption="音乐"))
# 已经创建好 数据 并且 明确知道外键 ID
session.add(Person(name="dream", hobby_id=1))
mysql> select * from person;
+----+-------+----------+
| id | name | hobby_id |
+----+-------+----------+
| 1 | dream | 1 |
+----+-------+----------+
1 row in set (0.00 sec)
mysql> select * from hobby;
+----+---------+
| id | caption |
+----+---------+
| 1 | 音乐 |
+----+---------+
1 row in set (0.00 sec)
# 方式二
# 外键可以是一个对象
# 可以先查存来再 hobby = 查出来的数据 (只创建 person)
# 也可以 直接在 hobby = 一个新的对象 (可以同时创建 person 和 hobby)
session.add(Person(name="hope", hobby=Hobby(caption="篮球")))
mysql> select * from person;
+----+-------+----------+
| id | name | hobby_id |
+----+-------+----------+
| 1 | dream | 1 |
| 2 | hope | 2 |
+----+-------+----------+
2 rows in set (0.00 sec)
mysql> select * from hobby;
+----+---------+
| id | caption |
+----+---------+
| 1 | 音乐 |
| 2 | 篮球 |
+----+---------+
2 rows in set (0.00 sec)
[2]查询数据
# (2) 查询数据
# (2.1)通过 person 查询 hobby 正向查询
people = session.query(Person).filter_by(id=2).first()
print(f'people :>>> {people} ')
print(f'hobby_id :>>> {people.hobby_id}')
print(f'hobby :>>> {people.hobby}')
print(f'hobby.caption :>>> {people.hobby.caption}')
people :>>> hope
hobby_id :>>> 2
hobby :>>> 篮球
hobby.caption :>>> 篮球
# (2.2)通过 hobby 查询 person 反向查询
hobby = session.query(Hobby).filter_by(id=2).first()
# 所有喜欢 id=2的人
print(f'hobby :>>> {hobby}')
# 模型表中定义的 relationship 中的 backref='pers'
print(f'hobby.pers :>>> {hobby.pers}')
hobby :>>> 篮球
hobby.pers :>>> [hope]
[3]更新删除同上面
【2】多对多
(1)表关系创建
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, DateTime, UniqueConstraint, Index, ForeignKey
from sqlalchemy.orm import relationship
# 多对多关系
# 中间表 手动创建
class Boy2Girl(Base):
__tablename__ = 'boy2girl'
id = Column(Integer, primary_key=True, autoincrement=True)
girl_id = Column(Integer, ForeignKey('girl.id'))
boy_id = Column(Integer, ForeignKey('boy.id'))
class Girl(Base):
__tablename__ = 'girl'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True, nullable=False)
def __str__(self):
return self.name
def __repr__(self):
return self.name
class Boy(Base):
__tablename__ = 'boy'
id = Column(Integer, primary_key=True, autoincrement=True) #autoincrement 默认就是true
name = Column(String(64), unique=True, nullable=False)
# 就是咱们之前的ManyToMany,不会在表中生成字段---》因为它是个表----》这个字段可以放在Girl表
girls = relationship('Girl', secondary='boy2girl', backref='boys')
def __str__(self):
return self.name
def __repr__(self):
return self.name
(2)创建表
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
from models import Base, User, Hobby, Person
from sqlalchemy.orm import sessionmaker, scoped_session
# 第一步:创建engine对象
engine = create_engine(
"mysql+pymysql://root:1314521@127.0.0.1:3306/db001?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
# 创建表,删除表
# 1 在数据库中创建表
Base.metadata.create_all(engine)
# 2 删除表
# Base.metadata.drop_all(engine)
# ----------- 最后要提价事务,关闭链接 -----------
# 提交事务
session.commit()
# 关闭会话
session.close()
(3)操作数据
[1]添加数据
# (1) 添加数据
# (1.1) 笨办法 添加数据
girl = Girl(name="John")
boy = Boy(name="Revin")
session.add_all([girl, boy])
# 操作中间表(纯手动操作中间表)
session.add(Boy2Girl(girl_id=1, boy_id=1))
mysql> select * from boy;
+----+-------+
| id | name |
+----+-------+
| 1 | Revin |
+----+-------+
1 row in set (0.00 sec)
mysql> select * from girl;
+----+------+
| id | name |
+----+------+
| 1 | John |
+----+------+
1 row in set (0.00 sec)
mysql> select * from boy2girl;
+----+---------+--------+
| id | girl_id | boy_id |
+----+---------+--------+
| 1 | 1 | 1 |
+----+---------+--------+
1 row in set (0.00 sec)
# (1.2) 使用 relationship
# 增加了一个 boy
boy = Boy(name="Dream")
# 增加了两个 girl
boy.girls = [Girl(name="Hope"), Girl(name="Fun")]
# 并且 在中间表中 给 boy 增加了两条记录
session.add(boy)
mysql> select * from boy;
+----+-------+
| id | name |
+----+-------+
| 2 | Dream |
| 1 | Revin |
+----+-------+
2 rows in set (0.00 sec)
mysql> select * from girl;
+----+------+
| id | name |
+----+------+
| 3 | Fun |
| 2 | Hope |
| 1 | John |
+----+------+
3 rows in set (0.00 sec)
mysql> select * from boy2girl;
+----+---------+--------+
| id | girl_id | boy_id |
+----+---------+--------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 2 |
+----+---------+--------+
3 rows in set (0.00 sec)
[2]查询数据
# (2)查询
# 基于对象的跨表查询
# 正向
boy = session.query(Boy).filter(Boy.id == 2).first()
print('boy.girls :>>> ',boy.girls)
boy.girls :>>> [Hope, Fun]
# 反向
girl = session.query(Girl).filter(Girl.id == 2).first()
print('girl.boys :>>> ',girl.boys)
girl.boys :>>> [Dream]
【十】更多查询方式
【1】查询所有记录并返回一个普通列表
res = session.query(User).all()
- 这将查询
User
表的所有记录,并以普通列表形式返回结果。
【2】只查询某几个字段
res = session.query(User.name.label('xx'), User.email)
- 这将查询
User
表的name
和email
字段,并将name
字段重命名为xx
。 - 返回结果也是一个查询对象。
【3】使用filter
和filter_by
方法进行条件查询
res = session.query(User).filter(User.name == "dream").all()
res = session.query(User).filter(User.name != "dream").all()
res = session.query(User).filter(User.name != "dream", User.email == '3@qq.com').all()
- 这些示例演示了如何使用
filter
方法根据条件筛选记录,例如等于、不等于等条件。
【4】在Django中使用Q对象的类似功能
res = session.query(User).filter_by(name='dream999').all()
res = session.query(User).filter_by(name='dream999',email='47@qq.com').all()
- 这些示例演示了如何使用
filter_by
方法根据等于条件筛选记录。
【5】获取第一个记录
res = session.query(User).first()
- 这将返回查询结果的第一个记录。
【6】使用占位符进行查询
res = session.query(User).filter(text("id<:value or name=:name")).params(value=20, name='dream').all()
- 这个示例演示了如何使用占位符进行查询,其中
:value
和:name
是占位符,可以通过params
方法传入具体的值。
【7】自定义查询
res=session.query(User).from_statement(text("SELECT * FROM users where email=:email")).params(email='3@qq.com').all()
- 这个示例演示了如何执行自定义的SQL查询语句。
【8】高级查询
(1)查询满足条件 User.id > 1 且 User.name 等于 'dream999' 的所有结果
res = session.query(User).filter(User.id > 1, User.name == 'dream999').all()
- 这个查询语句将返回所有 User 表中满足条件的用户记录,其中 id 大于 1 并且 name 等于 'dream999'。
- 这个例子的用途可能是获取具有指定 id 和名称的特定用户的信息。
(2)查询满足条件 User.id 在 1 到 9 之间且 User.name 等于 'dream999' 的所有结果
res = session.query(User).filter(User.id.between(1, 9), User.name == 'dream999').all()
- 这个查询语句将返回 User 表中 id 在 1 到 9 之间且 name 等于 'dream999' 的所有用户记录。
- 这个例子可以用来获取在特定范围内有特定名称的用户记录。
(3)查询满足条件 User.id 在 1 到 9 之间的所有结果
res = session.query(User).filter(User.id.between(1, 9)).all()
- 这个查询语句将返回 User 表中 id 在 1 到 9 之间的所有用户记录。
- 这个例子可以用来获取在指定范围内的用户记录。
(4)查询满足条件 User.id 在 [1, 3, 4] 中的所有结果
res = session.query(User).filter(User.id.in_([1,3,4])).all()
- 这个查询语句将返回 User 表中 id 在给定列表 [1, 3, 4] 中的所有用户记录。
- 这个例子可以用来获取具有特定 id 的用户记录。
(5)查询满足条件 User.email 在 ['3@qq.com', 'r@qq.com'] 中的所有结果
res = session.query(User).filter(User.email.in_(['3@qq.com','r@qq.com'])).all()
- 这个查询语句将返回 User 表中 email 在给定列表 ['3@qq.com', 'r@qq.com'] 中的所有用户记录。
- 这个例子可以用来获取具有特定电子邮件地址的用户记录。
(6)查询不满足条件 User.id 不在 [1, 3, 4] 中的所有结果
res = session.query(User).filter(~User.id.in_([1,3,4])).all()
- 这个查询语句将返回 User 表中 id 不在给定列表 [1, 3, 4] 中的所有用户记录。
- 这个例子可以用来排除具有特定 id 的用户记录。
(7)查询不满足条件 User.id 不在满足条件 User.name 为 'dream' 的用户记录中的所有结果
res = session.query(User).filter(~User.id.in_(session.query(User.id).filter_by(name='dream'))).all()
- 这个查询语句将返回 User 表中 id 不在满足条件 User.name 为 'dream' 的用户记录中的所有用户记录。
- 这个例子可以用来排除特定条件下的用户记录。
(8)查询满足条件 User.id 大于等于 3 且 User.name 为 'dream999' 的所有结果
res = session.query(User).filter(and_(User.id >= 3, User.name == 'dream999')).all()
- 这个查询语句将返回 User 表中 id 大于等于 3 且 name 为 'dream999' 的所有用户记录。
- 这个例子可以用来获取同时满足多个条件的用户记录。
(9)查询满足条件 User.id 小于 3 且 User.name 为 'dream999' 的所有结果
res = session.query(User).filter(User.id < 3, User.name == 'dream999').all()
- 这个查询语句将返回 User 表中 id 小于 3 且 name 为 'dream999' 的所有用户记录。
- 这个例子可以用来获取同时满足多个条件的用户记录。
(10)查询满足条件 User.id 小于 2 或 User.name 为 'eric' 的所有结果
res = session.query(User).filter(or_(User.id < 2, User.name == 'eric')).all()
- 这个查询语句将返回 User 表中 id 小于 2 或 name 为 'eric' 的所有用户记录。
- 这个例子可以用来获取满足任一条件的用户记录。
(11)查询满足条件 User.id 小于 2,同时满足 User.name 为 'dream999' 且 User.id 大于 3,或 User.extra 不为空的所有结果
res = session.query(User).filter(or_(User.id < 2, and_(User.name == 'dream999', User.id > 3), User.extra != "")).all()
- 这个查询语句将返回 User 表中满足以下任一条件的用户记录:id 小于 2,或者同时满足 name 为 'dream999' 且 id 大于 3,或者 User.extra 字段不为空。这个例子可以用来获取多条件联合查询时的用户记录。
(12)查询满足条件 User.email 包含 '@' 符号的所有结果
res = session.query(User).filter(User.email.like('%@%')).all()
- 这个查询语句将返回 User 表中 email 字段包含 '@' 符号的所有用户记录。
- 这个例子可以用来获取具有有效电子邮件地址的用户记录。
(13)查询不满足条件 User.name 以字母 'e' 开头的所有结果
res = session.query(User.id).filter(~User.name.like('e%'))
- 这个查询语句将返回 User 表中 name 不以字母 'e' 开头的用户记录的 id 列表。
- 这个例子可以用来查找不符合特定名字格式要求的用户。
(14)查询不满足条件 User.name 以字母 'e' 开头的所有结果
res = session.query(User).filter(~User.name.like('e%')).all()
- 这个查询语句将返回 User 表中 name 不以字母 'e' 开头的所有用户记录。
- 这个例子可以用来查找不符合特定名字格式要求的用户。
【9】分页
-
分页是一种常见的数据处理方式,用于将大量数据分割为更小的部分进行展示或处理。
-
对于SQLAlchemy而言,可以通过切片和限制查询结果来实现分页查询。
-
对于给定的查询语句
session.query(User)[2*5:2*5+2]
,意味着一页显示2条数据,需要查询第5页的结果。 -
其中,
User
是要查询的实体类。解释如下: -
[2*5:2*5+2]
表示从索引10(第5页的起始索引)开始,取2条数据,即第5页的数据。
【10】排序的查询
- 下面是关于排序的查询操作,以及不同的排序方式和多条件排序的示例代码:
(1)根据 name
降序排列(从大到小)的示例代码
res = session.query(User).order_by(User.name.desc()).all()
(2)根据 email
降序排列(从大到小)的示例代码
res = session.query(User).order_by(User.email.desc()).all()
(3)根据 price
升序排列(从小到大)的示例代码
res = session.query(Book).order_by(Book.price.asc()).all()
(4)先根据 name
降序排列,再按照 id
升序排列的示例代码
res = session.query(User).order_by(User.name.desc(), User.id.asc())
【12】分组查询
- 接下来是关于分组查询的操作,包括了5个常见聚合函数:
(1)分组后只查询分组字段和聚合函数结果(严格模式下,不能查询除此之外的字段)的示例代码
res = session.query(User).group_by(User.extra)
(2)分组后查询分组字段以及最大 id
、id
之和、最小 id
和平均 id
的示例代码
from sqlalchemy.sql import func
res = session.query(
User.name,
func.max(User.id),
func.sum(User.id),
func.min(User.id),
func.avg(User.id)).group_by(User.name).all()
for item in res:
print(item)
- 在以上示例中,
name
表示分组字段,max(id)
表示计算每组中的最大id
,sum(id)
表示计算每组中id
的总和,min(id)
表示计算每组中的最小id
,avg(id)
表示计算每组中id
的平均值。
(3)分组后使用 having
进行条件筛选(筛选条件为最大 id
大于2)的示例代码
from sqlalchemy.sql import func
res = session.query(
User.name,
func.max(User.id),
func.sum(User.id),
func.min(User.id)).group_by(User.name).having(func.max(User.id) > 2).all()
print(res)
- 以上示例中,使用
having
条件筛选出最大id
大于2的分组结果。
【十一】sqlalchemy执行原生sql
# 原生sql查询,查出的结果是对象
# 原生sql查询,查询结果列表套元组
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/db001", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
session = Session()
#### 执行原生sql方式一:
# 查询方式一:
# cursor = session.execute('select * from users')
# result = cursor.fetchall()
# print(result) #列表套元组
# 添加
# cursor = session.execute('insert into users(name,email) values(:name,:email)',
# params={"name": 'dream', 'email': '3333@qq.com'})
# session.commit()
# print(cursor.lastrowid)
###执行原生sql方式二(以后都用session操作---》socpe_session线程安全)一般不用
# conn = engine.raw_connection()
# cursor = conn.cursor()
# cursor.execute(
# "select * from app01_book"
# )
# result = cursor.fetchall()
# 执行原生sql方式三:
# res = session.query(User).from_statement(text("SELECT * FROM boy where name=:name")).params(name='dream').all()
session.close()
【补充】django中如何反向生成models
python manage.py inspectdb > app/models.py
【补充】原生sql(django-orm如何执行原生sql)
# 执行完的结果映射到对象中---》上面讲的 方式三:
from model import Book
books_obj_list = Book.objects.raw('select distinct id, book_name from test_book')
for book_obj in books_obj_list:
print(book_obj.id, book_obj.book_name)
# 纯原生sql
from django.db import connection
cur=connection.cursor()
cur.execute('select distinct id, book_name from test_book')
print(cur.fetch_all())
cur.close()
with connection.cursor() as cur:
cur.execute('select distinct id, book_name from test_book')