参考别人的:https://www.cnblogs.com/coser/archive/2013/01/28/2880328.html

 

参考官方文档:https://docs.python.org/zh-cn/3/library/contextlib.html#module-contextlib

 

第一种【重点推荐】:利用contextlib.contextmanager模块自动封装一个支持with语句实现Session异常处理机制【非常给力】

@contextmanager
    def auto_commit_db(self):
        """
            功能:利用contextlib.contextmanager模块自动封装一个支持with语句的
            核心作用:
                1、数据库的CRUD增删改查之前都必须commit提交一次【提交就会立即更新,确保你的数据为最新的】
                2、关于查询:虽然查询操作并未改变数据,但确保当前查询的数据为最新的【必须先commit提交更新完成后才能进行查询】
            关于装饰器contextmanager模块【要调用的函数必须返回generator --iterator(使用yield语句)】:
                使用装饰器模块:contextlib是为了加强with语句,提供上下文机制的模块,它是通过Generator实现的。
                传统with方式:通过定义类以及写__enter__和__exit__来进行上下文管理虽然不难,但是很繁琐。
                        contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。
            实现原理:参考我总结的文档:https://blog.csdn.net/weixin_43343144/article/details/104438805
            温馨提示:
                1、执行commit提交方法首先会自动执行flush方法【将所有对象更改刷新到数据库】
                2、关于数据库更新必须遵循以下规则:
                   每次修改一张表,就必须先执行更新一次【千万不能同时修改多张表然后在统一执行更新,会严重导致数据错乱或丢失的情况】
                3、具体原因:因为_session.commit提交才会更新数据库生效,
                   如果多张表交叉一起更新,可能导致部分表更新成功,另外部分根本没更新的问题,
                   所以为了数据更安全【尤其资金问题】,每修改一张表必须先执行commit更新成功后,再去修改另一张表继续更新!
            装饰器contextmanager执行顺序:
                【第一步】执行yield语句以上的代码
                【第二步】执行yield语句中的代码【也就是as db_session_instance:内部这部分代码】
                【第三步】执行yield语句后部分代码【处理commit提交、remove移除、及异常回滚】
            细节问题:
                1、为什么db_session_instance必须等待失效抛出异常后才能remove【而不能每次请求前新创建的新session实例,请求结束后立即remove】?
                   答案:
                        A、首先基于scoped_session新创建的session()实例会自动加入本地线程,只要没有执行remove之前,都会是同一个实例【类单例模式】
                        一旦remove则重新创建一个,全新的session()实例,重新添加到db_session的本地线程中,来确保单线程永远仅有一个活跃的session实例!
                        B、比如执行先查询后更新操作:首先通过一个db_session_instance执行query获取一个model,然后必须使用同一个使用同一个session实例去更新,
                        而这时如果你当前的session实例已经被remove,再一次创建的实例肯定不一样了,此时的model在新的session已失效,
                        最终导致更新失败【这也是为什么执行更新失败的根本原因,因为查询后更新必须是同一个session实例】
        """
        try:
            # 【非常重要】这里生成器返回的结果会传递给as后的对象【比如: with OwnDBSessionRollback() as db_session_instance】
            yield self.db_session_instance
            # 【非常重要】数据库的CRUD增删改查之前都必须commit【确保下一次操作之前你的数据库数据为最新的】
            self.db_session_instance.commit()
        except BaseException as e:
            """
                关于session异常处理:
                    1、一旦当前db_session_instance实例连接出异常了,就要从db_session本地线程中移除,
                       当下一次重新请求数据库时,在创建一个新的new_db_session_instance实例加入db_session本地线程,
                       确保每一次正常运行单线程中有且仅有一个活跃的db_session_instance实例
                    2、一旦session出现异常,必须先回滚,然后在移除【确保本次请求的数据恢复原始状态】
            """
            self.db_session_instance.rollback()
            self.db_session.remove()
            raise OwnGlobalException(
                f"由于本次mysql请求连接失败,出现了异常,强制执行事务回滚后," +
                f"从本地线程remove此session实例,并创建一个新的实例并添加到本地线程" +
                f"【确保单线程永远只有一个活跃的db_session_instance】" +
                f"本次请求具体异常原因如下:{e}")

 

 

 

第二种方式【不推荐,太繁琐,仅供参考,方便理解第一种的实现原理】:传统自己实现__enter__ 和 __exit__方法也是可行的,但是需要处理2次try-except异常【比较繁琐】

class OriginalOwnDBSessionRollback:
    """
        功能:基于数据库的创建session实例、提交更新、异常回滚统一的类【必须结合with才可以调用】
    """

    def __init__(self, db_session):
        """
            功能:
                db_session:全局共享一个session会话管理对象
                db_session_instance:由当前session创建出来的实例
                is_query:兼容仅执行增删改等更新操作才需要commit操作!
        """
        self.db_session = db_session
        self.db_session_instance = db_session()

    def __enter__(self):
        """
            功能:with OwnDBSessionRollback(db_session) as _session 这里返回的就是当前session的实例
        """
        return self.db_session_instance

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
           功能:with执行完任务之后,会自动处理以下方法并退出!!!
        """
               try:
            # 【非常重要】这里生成器返回的结果会传递给as后的对象【比如: with OwnDBSessionRollback() as db_session_instance】
            yield self.db_session_instance
            self.db_session_instance.commit()
        except BaseException as e:
            """
                关于session异常处理:
                    1、一旦当前db_session_instance实例连接出异常了,就要从db_session本地线程中移除,
                       当下一次重新请求数据库时,在创建一个新的new_db_session_instance实例加入db_session本地线程,
                       确保每一次正常运行单线程中有且仅有一个活跃的db_session_instance实例
                    2、一旦session出现异常,必须先回滚,然后在移除【确保本次请求的数据恢复原始状态】
            """
            self.db_session_instance.rollback()
            self.db_session.remove()
            raise OwnGlobalException(
                f"由于本次mysql请求连接失败,出现了异常,强制执行事务回滚后," +
                f"从本地线程remove此session实例,并创建一个新的实例并添加到本地线程" +
                f"【确保单线程永远只有一个活跃的db_session_instance】" +
                f"本次请求具体异常原因如下:{e}")


class OwnBaseModel(db.Model, ClassIterator):
    def add_one_model(self, **fields):
        """
            功能:实例方法,增加一个model
        """
        self.set_attrs(**fields)
        # 如果使用传统自己实现__enter__ 和 __exit__方法也是可行的,但是需要处理2次try-except异常
        with OriginalOwnDBSessionRollback(db_session=db.session) as _session:
            try:
                _session.add(self)
            except BaseException as e:
                # 加入数据库commit提交失败,必须回滚!!!
                self._session.rollback()
                raise OwnGlobalException(f"由于本次mysql请求连接失败,出现了异常,强制执行事务回滚,具体异常原因如下:{e}")
        return True

 

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐