sqlalchemy 中 session 是什么, scoped_session 又是什么 ?

sqlalchemy 中的 session , scoped_session 的区别

摘要 : 本文 主要讲解 Session 是什么, scopedsession

  1. sqlalchemy 中的 session , scoped_session 的区别 是什么?

  2. 如何进行session 管理

Session 其实 就是一个会话, 可以和数据库打交道的一个会话.

官方是这样说明的.

The orm.mapper() function and declarative extensions are the primary configurational interface for the ORM. Once mappings are configured, the primary usage interface for persistence operations is the Session.

orm.mapper()函数和声明性扩展是ORM的主要配置接口。 配置映射后,持久性操作的主要用法接口是Session.

即用session 来操作数据库. 通过 session 来实现 增删改查的操作.

为了方便说明 首先先定义一个base

#base.py  from datetime import datetimefrom secure import XINYONGFEI_BI_URL
from sqlalchemy import Column, Integer, Stringfrom sqlalchemy import create_engine, DateTime
from sqlalchemy.ext.declarative import declarative_baseengine = create_engine(XINYONGFEI_BI_URL, pool_size=10, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')_Base = declarative_base(bind=engine, name='base')class Base(_Base):__abstract__ = True__table_args__ = {'mysql_engine': 'InnoDB','mysql_charset': 'utf8',"useexisting": True}create_time = Column(DateTime, nullable=False, default=datetime.now)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Time    : 2019/5/5 18:21
@File    : person.py
@Author  : frank.chang@shoufuyou.com
"""from base import Base
from sqlalchemy import Column, Integer, Stringclass Person(Base):__tablename__ = 'Person'id = Column(Integer, primary_key=True, autoincrement=True)name = Column(String(length=64), comment='姓名')mobile = Column(String(length=13), comment='手机号')id_card_number = Column(String(length=64), comment='身份证')def __str__(self):return '%s(name=%r,mobile=%r,id_card_number=%r)' % (self.__class__.__name__,self.name,self.mobile,self.id_card_number)__repr__ = __str__if __name__ == '__main__':person = Person(name='frank-' + 'job4', mobile='4444444444', id_card_number='123456789')person = Person(name='frank-' + 'job4', mobile='4444444444', id_card_number='123456789')person1 = Person(name='frank-' + 'job1', mobile='111111111', id_card_number='11111111111')print(person)pass

首先创建一个session ,操作一下 Session 对象

>>> from sqlalchemy.orm import sessionmaker, scoped_session>>> session_factory = sessionmaker(bind=engine)
...
>>>
>>>
>>> person = Person(name='frank-' + 'job3', mobile='111111', id_card_number='123456789')
...
>>> session= session_factory()
>>> s1= session_factory()
>>> s1
<sqlalchemy.orm.session.Session object at 0x107ec8c18>
>>> s2 = session_factory()
>>> s1,s2
(<sqlalchemy.orm.session.Session object at 0x107ec8c18>, <sqlalchemy.orm.session.Session object at 0x107ee3ac8>)
>>> s1 is s2
False
>>> id(s1),id(s2)
(4427910168, 4428020424)>>> s1.add(person)
>>> s2.add(person)
Traceback (most recent call last):File "<input>", line 1, in <module>File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1835, in addself._save_or_update_state(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1848, in _save_or_update_stateself._save_or_update_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2163, in _save_or_update_implself._save_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2115, in _save_implto_attach = self._before_attach(state, obj)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2238, in _before_attachstate.session_id, self.hash_key))
sqlalchemy.exc.InvalidRequestError: Object '<Person at 0x107ec8128>' is already attached to session '11' (this is '12')

当我使用 s2 添加 person 就会报错了. 说person 这个对象 已经和 另一个session 关联一起来了, 再次关联另一个session 就会报错.

如果s1 ,s2 关联不同的person就不会有问题.


>>> person4 = Person(name='frank-' + 'job4', mobile='4444444444', id_card_number='123456789')
>>> person1 = Person(name='frank-' + 'job1', mobile='111111', id_card_number='123456789')
>>>
>>> s1.rollback()
>>> s1.add(person1)
>>> s2.add(person4)
>>> s1.commit()  # 提交数据
>>> s2.commit()  # 提交数据, 写入数据库

当执行了 commit 操作的后, 数据才会写入数据库. 此时数据库里面已经有了 数据.

即不同的session 是关联不同的表, 一个session 如果有关联一个 从base 继承的对象, 其他的session 是不能够关联这个对象的. 就相当于把 session 和 对象绑定在一起了.
此时 数据库里面 就有两条数据了.

#对同一个对象 提交多次,commit 多次,也只会有一条数据写入数据库.

提交多次不同的对象可以 写入多条元素.

为了方便演示

def get_session_no_scope():engine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)session = session_factory()return session>>> p= Person(name='frank', mobile='11111111',id_card_number= '123456789')
>>>
>>> s1 = get_session_no_scope()
>>> s1.add(p)
>>> s1.add(p)
>>> s1.add(p)
>>> s1.commit()

commit 之后 数据库里面会有一条记录,并不会有三条记录.
而且此时 如果 ,不进行 close,或者回滚, 其他的session 是不能添加 p 这个对象的.


s2 = get_session_no_scope()
s2.add(p)
Traceback (most recent call last):File "<input>", line 1, in <module>File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1835, in addself._save_or_update_state(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1848, in _save_or_update_stateself._save_or_update_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2165, in _save_or_update_implself._update_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2148, in _update_implto_attach = self._before_attach(state, obj)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2238, in _before_attachstate.session_id, self.hash_key))
sqlalchemy.exc.InvalidRequestError: Object '<Person at 0x10e677f28>' is already attached to session '5' (this is '6')
s1.close()
s2.add(p)
s2.commit()

s1.close() 之后, s2 就可以顺利 add , commit了,但是此时 并没有新生成 一条数据,到数据库里面. 难道同一个对象 只能 commit 一次. 但是此时 数据库的数据并没有新增.

测试发现 同一对象, 只能提交到数据库一次.

engine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)def get_session_no_scope():return session_factory()
>>> s1 = get_session_no_scope()
>>> person = Person(name='frank-' + 'job1', mobile='111111', id_card_number='123456789')
...
>>> person
Person(name='frank-job1',mobile='111111',id_card_number='123456789')
>>> s1.add(person)
>>> s1.commit()
>>> s1.add(person)
>>> s1.commit()
>>> s1.close()

此时 数据库里面会增加 一条数据 , 不会增加 两条数据. 并且 一旦session.add(person), 其他的session 是不能操作这个对象的. 除非 session.close() 才能 有其他的session 操作这个 对象.

到此 大概清楚了session 是主要 给用户提供操作 数据库的接口, 用来进行 增删改查操作.
给用户提供比较高级的API ,来操作数据库, 并且并且 同一个session 只能操作一个对象,

总结:
假设 有两个session分别为 s1,s2 , s1.add(person),之后 s2 是不能操作 person 的.
直到 s1.close() ,s2 才能操作person

del session, session2
s1 =session_factory()
s2 =session_factory()
s1,s2
(<sqlalchemy.orm.session.Session object at 0x1115586d8>, <sqlalchemy.orm.session.Session object at 0x112030d30>)p= Person(name='frank-' + 'job4', mobile='4444444444', id_card_number='123456789')p.name='frank111'
p
Person(name='frank111',mobile='4444444444',id_card_number='123456789')
s1.add(p)
s1.rollback()
s2.add(p)
s2.rollback()
s1.add(p)
s1.commit()
s2.add(p)
Traceback (most recent call last):File "<input>", line 1, in <module>File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1947, in addself._save_or_update_state(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1960, in _save_or_update_stateself._save_or_update_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2303, in _save_or_update_implself._update_impl(state)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2286, in _update_implto_attach = self._before_attach(state, obj)File "/Users/frank/.local/share/virtualenvs/mysqlalchemy-demo-0htClb7e/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2374, in _before_attach% (state_str(state), state.session_id, self.hash_key)
sqlalchemy.exc.InvalidRequestError: Object '<Person at 0x1120ff208>' is already attached to session '6' (this is '7')
s1.close()
s2.add(p)
s2.commit()

2 scoped_session 是什么?

用scope_session 生成的对象

engine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)def get_session():return Session()
>>>
>>> s3= get_session()
>>> p2 = Person(name='frank-2', mobile='222222', id_card_number='123456789')
>>> s4= get_session()>>> s3 is s4
True
>>> s3.add(p2)
>>> s4.add(p2)
>>> s4.commit()

发现 就可以 用两个session 同时操作 一个 对象. 打印 id 发现 其实生成的是一个session 同一个id .

这个感觉 有点像 单例模式, 只有一个对象, 一个session 对象.

实际上 不是这样的. scoped_session 的作用是为了多线程下面共享 session .

从scoped_session对象 生成 的session 对象,然后我们没有这样的问题,因为scoped_session对象维护同一会话对象的注册表。

官方文档对scopesession 介绍如下 https://docs.sqlalchemy.org/en/13/orm/contextual.html#unitofwork-contextual

refer: https://docs.sqlalchemy.org/en/13/orm/contextual.html#unitofwork-contextualThread-Local Scope
Users who are familiar with multithreaded programming will note that representing anything as a global variable is usually a bad idea, as it implies that the global object will be accessed by many threads concurrently. The Session object is entirely designed to be used in a non-concurrent fashion, which in terms of multithreading means “only in one thread at a time”. So our above example of scoped_session usage, where the same Session object is maintained across multiple calls, suggests that some process needs to be in place such that multiple calls across many threads don’t actually get a handle to the same session. We call this notion thread local storage, which means, a special object is used that will maintain a distinct object per each application thread. Python provides this via the threading.local() construct. The scoped_session object by default uses this object as storage, so that a single Session is maintained for all who call upon the scoped_session registry, but only within the scope of a single thread. Callers who call upon the registry in a different thread get a Session instance that is local to that other thread.Using this technique, the scoped_session provides a quick and relatively simple (if one is familiar with thread-local storage) way of providing a single, global object in an application that is safe to be called upon from multiple threads.The scoped_session.remove() method, as always, removes the current Session associated with the thread, if any. However, one advantage of thethreading.local() object is that if the application thread itself ends, the “storage” for that thread is also garbage collected. So it is in fact “safe” to use thread local scope with an application that spawns and tears down threads, without the need to call scoped_session.remove(). However, the scope of transactions themselves, i.e. ending them via Session.commit() or Session.rollback(), will usually still be something that must be explicitly arranged for at the appropriate time, unless the application actually ties the lifespan of a thread to the lifespan of a transaction.

熟悉多线程编程的用户都知道 任何东西 作为一个全局变量不是一个 好主意. 这意味着 这个全局的对象可以被多线程并发的访问. 而 Session 这个对象 设计的时候 仅仅使用在 非并发场景, 这意味着 同一时刻 只用一个线程. 上面 所举例子, scoped_session ,多次调用 中 维护相同的 Session 对象. 这表明, 在某些进程中需要一个适当的位置 ,就像 多线程调用 不会获得相同的 session 句柄. 我们把这个概念 叫做 线程本地存储. 这意味着 使用一个特殊的对象 , 为每个应用线程 保持着一个不同的对象.

python 提供了 threading.local 构造器. scoped_session 对象 默认是使用这个对象作为存储,这样就可以为所有调用scoped_session注册表的对象 维护一个Session,但只能在单个线程的范围内。 在不同线程中 根据注册表的调用这个 可以得到一个session 对象, 这个session 对象 对于 其他的线程是 local 的. (理解: 就是说不同的线程 拿到的session 是不一样的.)

通过这种方式, scoped_session提供了一个快速 并且相对简单的 提供单个session 方式. 对于多线程的调用来说, 全局对象是安全的.

scoped_session.remove 方法 , 总是, 移除当前的session 和与之关联的线程的联系. 然而, 事务的范围, 通过session.commit() or sesson.rollback() 来结束. 并且 事务的范围在合适的时间 可以清晰的管理 , 除非 应用确实希望 线程的生命周期和 事务的生命周期紧紧地绑定在一起.

用多线程的方式来看下 scope_session 到底如何使用的.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Time    : 2019/5/5 18:33
@File    : test_session.py
@Author  : frank.chang@shoufuyou.com# person = Person(name='frank-' + 'job1', mobile='111111', id_card_number='123456789')
session = get_scoped_session()"""import threading
import timefrom sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from secure import XINYONGFEI_BI_URLfrom model.person import Personengine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)def get_scoped_session():"""scoped_session:return:"""return Session()def get_session_no_scope():""" noscoped_session """return session_factory()session = get_scoped_session()def job(name):global session# 这里打印一下 session id  print(f"id session:{id(session)}")person = Person(name='frank-' + name, mobile='111111', id_card_number='123456789')print(f"{name} person is add..")session.add(person)time.sleep(2)if name == 'job3':# 线程3 提交 , 其他线程不提交.session.commit()if __name__ == '__main__':thread_list = []# 创建5个线程for i in range(5):name = 'job' + str(i)t = threading.Thread(target=job, name=name, args=(name,))thread_list.append(t)for t in thread_list:t.start()for t in thread_list:t.join()

我这里开启5 个线程, 我想让 线程3 进行提交 person对象, 其他的线程不提交.

id session:4541434120
job0 person is add..
id session:4541434120
job1 person is add..
id session:4541434120
job2 person is add..
id session:4541434120
job3 person is add..
id session:4541434120
job4 person is add..Process finished with exit code 0

运行结果如下: 可以看出 因为 使用的是 同一个session , id 是一样的. 但是 只要有一个线程提交了. 其他 的线程 也会受到影响. 这个session 相当于被共享了.

如果想要实现 线程之间不会相互干扰, 可以每次都生成一个session
每次 任务都起来的时候, 重新创建一个session 就可以了.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Time    : 2019/5/5 18:33
@File    : test_session.py
@Author  : frank.chang@shoufuyou.com# person = Person(name='frank-' + 'job1', mobile='111111', id_card_number='123456789')
session = get_scoped_session()"""import threading
import timefrom sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from secure import XINYONGFEI_BI_URLfrom model.person import Personengine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)def get_scoped_session():"""scoped_session:return:"""return Session()def get_session_no_scope():""" noscoped_session """return session_factory()def job(name):# 这里每次 创建一个新的session 对象session = get_scoped_session()print(f"id session:{id(session)}")person = Person(name='frank-' + name, mobile='111111', id_card_number='123456789')print(f"{name} person is add..")session.add(person)time.sleep(2)if name == 'job3':# 线程3 提交 , 其他线程不提交.session.commit()session.close()if __name__ == '__main__':thread_list = []# 创建5个线程for i in range(5):name = 'job' + str(i)t = threading.Thread(target=job, name=name, args=(name,))thread_list.append(t)for t in thread_list:t.start()for t in thread_list:t.join()

结果如下:

id session:4584472304
job0 person is add..
id session:4584866200
job1 person is add..
id session:4584866704
job2 person is add..
id session:4584867208
job3 person is add..
id session:4584867936
job4 person is add..

可以看出 每次创建的 session_id 都不一样了.

此时数据库里面只有一条数据.

scoped_session 默认情况 会根据 线程 创建 不同的session , 同一个 线程下面 创建的session 是一样的. 不同的线程创建的session 是不一样的.

默认 情况 下 会调用 ThreadLocalRegistry() 这个对象的call 方法,这里做了线程隔离,让不同的线程 拿到不同的session.


用session 来重新测试上面的代码.

1 先用全局的session , 这样会导致 每条数据 都会被写入数据库.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Time    : 2019/5/5 18:33
@File    : test_session.py
@Author  : frank.chang@shoufuyou.com# person = Person(name='frank-' + 'job1', mobile='111111', id_card_number='123456789')
session = get_session_no_scope()"""import threading
import timefrom sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from secure import XINYONGFEI_BI_URLfrom model.person import Personengine = create_engine(XINYONGFEI_BI_URL, pool_size=5, pool_recycle=7200,pool_pre_ping=True, encoding='utf-8')session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)def get_session_no_scope():""" noscoped_session """return session_factory()session = get_session_no_scope()def job(name):global sessionprint(f"id session:{id(session)}")person = Person(name='frank-' + name, mobile='111111', id_card_number='123456789')print(f"{name} person is add..")session.add(person)time.sleep(1)if name == 'job3':# 线程3 提交, 其他线程不提交.session.commit()session.close()if __name__ == '__main__':thread_list = []# 创建5个线程for i in range(5):name = 'job' + str(i)t = threading.Thread(target=job, name=name, args=(name,))thread_list.append(t)for t in thread_list:t.start()for t in thread_list:t.join()

结果如下:

id session:4418463896
job0 person is add..
id session:4418463896
job1 person is add..
id session:4418463896
job2 person is add..
id session:4418463896
job3 person is add..
id session:4418463896
job4 person is add..

图4 所有的数据都会被写入数据库.

看出此时 所有的数据都被写入到数据库里面了.

总结

session 和scopedsession 的区别, scoped_session 实现了一个线程的隔离, 保证不同的线程 拿到 不同的session, 同一个线程拿到的session 是同一个值.
session 和scopedsession 本质上都是 用来 操作 数据库的. 只是session 只适合在单线程下面使用.

参考文档

1 数据库连接池SQLAlchemy中多线程安全的问题 https://blog.csdn.net/daijiguo/article/details/79486294
2 sqlalchemy_session http://rhel.cc/2016/07/14/sqlalchemy-session/
3 session 官方地址 https://docs.sqlalchemy.org/en/13/orm/session.html
4 scoped_session https://docs.sqlalchemy.org/en/13/orm/contextual.html#unitofwork-contextual
5 SQLAlchemy 基础知识 - autoflush 和 autocommit https://zhuanlan.zhihu.com/p/48994990
6 sesssion 官方首页 https://docs.sqlalchemy.org/en/13/orm/session.html
7 sqlalchemy 常见的问题 https://docs.sqlalchemy.org/en/13/orm/session_basics.html#session-faq-whentocreate
8 多线程下面的使用问题 https://blog.csdn.net/daijiguo/article/details/79486294

分享快乐,留住感动. 2019-05-13 19:19:27 --frank

sqlalchemy 系列教程五-Session和scopedsession相关推荐

  1. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  2. Linux求平方脚本,Linux Shell脚本系列教程(五):数学运算

    这篇文章主要介绍了Linux Shell脚本系列教程(五):数学运算,本文讲解了使用let.(())和[]进行算术运算.使用expr进行算术运算.使用bc进行算术运算三种方法,需要的朋友可以参考下 在 ...

  3. SpringCloud系列教程(五)之SpringCloud Gateway 网关聚合开发文档 swagger knife4j 和登录权限统一验证【Hoxton版】

    阅读提醒: 本文面向的是有一定springboot基础者 本次教程使用的Spring Cloud Hoxton RELEASE版本 由于knife4j比swagger更加友好,所以本文集成knife4 ...

  4. 米思齐(Mixly)图形化系列教程(五)-if……else……与逻辑运算

    目录 比较运算 逻辑运算符 if esle 说明 例子 if程序的嵌套 例子 教程导航 联系我们 比较运算和逻辑运算返回两种结果,条件成立(真true)与不成立(假false) 比较运算 下表显示了支 ...

  5. asp.NET自定义服务器控件内部细节系列教程五

    如大家要转载,请保留本人的版权: /* *Description:asp.NET自定义服务器控件内部细节系列教程 *Auther:崇崇-天真的好蓝 *MSN:chongchong2008@msn.co ...

  6. ClickHouse 系列教程五:多种连接方法

    文章目录 clickhouse-client HTTP 接口 JDBC Python接口 ClickHouse系列教程: ClickHouse系列教程 clickhouse-client 你可以通过c ...

  7. ASP .NET Core Web Razor Pages系列教程五:更新Razor Pages页面

    系列文章目录:系列教程:使用ASP.NET Core创建Razor Pages Web应用程序 - zhangpeterx的博客 系列教程代码的GitHub地址:ASP .Net Core Razor ...

  8. Unity3D脚本中文系列教程(五)

    http://dong2008hong.blog.163.com/blog/static/4696882720140302848544/?suggestedreading&wumii Unit ...

  9. Redis系列教程(五):Redis哨兵、复制、集群的设计原理,以及区别

    前一篇文章高并发架构系列:Redis为什么是单线程.及高并发快的3大原因详解谈了Redis高并发快的3个原因,本篇主要谈Redis的高可用,两篇合起来就可以把redis的高并发和高可用搞清楚了. 谈到 ...

最新文章

  1. GridView 实现LinkButton下载文件/附件
  2. Linux1:Linux概述
  3. 美团搜索中NER技术的探索与实践
  4. Redis 热 Key 发现以及解决办法
  5. SQL Server 2012安装图解
  6. android mysql 图片_android sqlite添加图片到数据库
  7. 位换记号、排列测试与状态图:杂耍中的数学
  8. BeautifulSoup入门案例
  9. 如何将 Oracle 单实例数据库转换为RAC数据库?
  10. 苹果也开始打价格战了
  11. 学会这两个技巧!让你的Mac文件共享超简单
  12. JAVA Map集合类简介
  13. Everthing搜索神器,工作利器
  14. 下面程序段中带下划线的语句的执行次数的数量级是( )
  15. 常见二维码上那些奇怪的图案是什么
  16. python实现自幂数
  17. 出门忘带身份证?那就试试这几款好用软件
  18. 归并排序【算法解析,代码模板】
  19. 微信小程序 | 做一个小程序端的扫雷游戏
  20. 学习英语单词16个 - day1

热门文章

  1. 亚信安全:2017年勒索软件与商业邮件欺骗将继续蔓延
  2. 遇到问题:(已解决)Texpad无法编译--“未找到TeX发型版 没有在您的系统中找到TeX发行版。....或安装您的TeX发行版”
  3. 海老师的技术博客: OCA 考试 准备笔记(一): Java Building Blocks
  4. Android弹幕功能实现,模仿斗鱼直播的弹幕效果
  5. Genome Research | 呼吸疾病国家重点实验室华大研究院合作解析新冠轻重症患者血浆cfRNA特征谱...
  6. 浅析uthash系列之User Guide(翻译)
  7. Qt学习(12)——Qt5中的容器
  8. 【步兵 cocos2dx】自动化测试
  9. JAVA小项目之影院系统和员工管理系统
  10. WPS文字转Word用什么软件