MyBatis的SqlSession理解
SqlSession是Mybatis最重要的构建之一,可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式)
1 .sqlsession的创建:
SqlSessionFactoryBuilder创建SqlSessionFactory openSession,sqlSession 执行增删改查
用了注解是通过org.mybatis.spring.SqlSessionFactoryBean该类创建sqlsession的,而mapper里面的每一个方法称为statement。
public void deleteUserTest() throws IOException {// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();// 传入id删除 用户sqlSession.delete("test.deleteUser", 39); //更新 sqlSession.update("test.updateUser", user); //插入 sqlSession.insert("test.insertUser", user); //查询 List<User> list = sqlSession.selectOne("test.findUserByName", "小明");// 提交事务 增删改 需要commit,查询无需commitsqlSession.commit();// 关闭会话sqlSession.close();}
2.SqlSession原理
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
映射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。
3.SqlSession重要的四个对象
1)Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句;2)StatementHandler:使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;3)ParammeterHandler:处理SQL参数;4)ResultHandler:结果集ResultSet封装处理返回。
- Execute执行器
execute接口有以下方法
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;int update(MappedStatement var1, Object var2) throws SQLException;<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;List<BatchResult> flushStatements() throws SQLException;void commit(boolean var1) throws SQLException;void rollback(boolean var1) throws SQLException;CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);boolean isCached(MappedStatement var1, CacheKey var2);void clearLocalCache();void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);Transaction getTransaction();void close(boolean var1);boolean isClosed();void setExecutorWrapper(Executor var1);
}
执行器起到至关重要的作用,它是真正执行Java与数据库交互的东西,参与了整个SQL查询执行过程中。
1)主要有三种执行器:简易执行器SIMPLE(不配置就是默认执行器)、REUSE是一种重用预处理语句、BATCH批量更新、批量专用处理器
2)执行器作用:Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本的运行参数,然后调用StatementHandler的parameterize()方法(实际上是启用了ParameterHandler设置参数)设置参数,resultHandler再组装查询结果返回调用者完成一次查询完成预编译,简单总结起来就是即先预编译SQL语句,之后设置参数(跟JDBC的prepareStatement过程类似)最后如果有查询结果就会组装返回。
4.Mapper
Mybatis官方手册建议通过mapper对象访问mybatis,因为使用mapper看起来更优雅,就像下面这样:
session = sqlSessionFactory.openSession();
UserDao userDao= session.getMapper(UserDao.class);
UserDto user =new UserDto();
user.setUsername("iMbatis");
user.setPassword("iMbatis");
userDao.insertUser(user);
那么这个mapper到底是什么呢,它是如何创建的呢,它又是怎么与sqlsession等关联起来的呢?下面为你一一解答。
1、创建
表面上看mapper是在sqlsession里创建的,但实际创建它的地方是MapperRegistry:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {if (!knownMappers.contains(type))throw new BindingException("Type " + type + " isnot known to the MapperRegistry.");try {return MapperProxy.newMapperProxy(type, sqlSession);} catch (Exceptione) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}
可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。我们进一步看看MapperProxy.newMapperProxy(type,sqlSession); 背后发生了什么事情:
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {ClassLoader classLoader = mapperInterface.getClassLoader();Class<?>[] interfaces = new Class[]{mapperInterface};MapperProxy proxy = new MapperProxy(sqlSession);return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
}
看起来没什么特别的,和其他代理类的创建一样,我们重点关注一下MapperProxy的invoke方法:
2、MapperProxy 的 invoke
我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来,MapperProxy的invoke如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{if (method.getDeclaringClass()== Object.class) {return method.invoke(this, args);}final Class<?> declaringInterface = findDeclaringInterface(proxy, method);final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);final Object result = mapperMethod.execute(args);if (result ==null && method.getReturnType().isPrimitive()&& !method.getReturnType().equals(Void.TYPE)) {throw new BindingException("Mapper method '" + method.getName() + "'(" + method.getDeclaringClass()+ ") attempted toreturn null from a method with a primitive return type ("+ method.getReturnType() + ").");}return result;
}
可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:
public Object execute(Object[] args) {Object result = null;if(SqlCommandType.INSERT == type) {Object param = getParam(args);result = sqlSession.insert(commandName, param);} else if(SqlCommandType.UPDATE == type) {Object param = getParam(args);result = sqlSession.update(commandName, param);} else if(SqlCommandType.DELETE == type) {Object param = getParam(args);result = sqlSession.delete(commandName, param);} else if(SqlCommandType.SELECT == type) {if (returnsVoid && resultHandlerIndex != null) {executeWithResultHandler(args);} else if (returnsList) {result = executeForList(args);} else if (returnsMap) {result = executeForMap(args);} else {Object param = getParam(args);result = sqlSession.selectOne(commandName, param);}} else {throw new BindingException("Unknown execution method for: " + commandName);}return result;}
可以看到,MapperMethod就像是一个分发者,他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。
5.Executor
前面提到过,sqlsession只是一个门面,真正发挥作用的是executor,对sqlsession方法的访问最终都会落到executor的相应方法上去。Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。Executor的创建前面已经介绍了,下面介绍下他们的功能:
CacheExecutor
CacheExecutor有一个重要属性delegate,它保存的是某类普通的Executor,值在构照时传入。执行数据库update操作时,它直接调用delegate的update方法,执行query方法时先尝试从cache中取值,取不到再调用delegate的查询方法,并将查询结果存入cache中。代码如下:
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {if (ms != null) {Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);cache.getReadWriteLock().readLock().lock();try {if (ms.isUseCache() && resultHandler ==null) {CacheKey key = createCacheKey(ms, parameterObject, rowBounds);final List cachedList = (List)cache.getObject(key);if (cachedList != null) {return cachedList;} else {List list = delegate.query(ms,parameterObject, rowBounds, resultHandler);tcm.putObject(cache,key, list);return list;}} else {return delegate.query(ms,parameterObject, rowBounds, resultHandler);}} finally {cache.getReadWriteLock().readLock().unlock();}}}return delegate.query(ms,parameterObject, rowBounds, resultHandler);
}
普通 Executor
普通Executor有3类,他们都继承于BaseExecutor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。下面以SimpleExecutor为例:
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {Statementstmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);stmt = prepareStatement(handler);return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}
可以看出,Executor本质上也是个甩手掌柜,具体的事情原来是StatementHandler来完成的。
6.StatementHandler
当Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。
创建
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}
可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的,本文作者也实现了一个分页拦截器,在后续的章节会分享给大家,敬请期待。
初始化
StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。代码如下:
private Statement prepareStatement(StatementHandler handler) throwsSQLException {Statement stmt;Connection connection = transaction.getConnection();stmt =handler.prepare(connection);handler.parameterize(stmt);return stmt;
}
statement的开启和参数设置没什么特别的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通过调用ParameterHandler的setParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandler:public ParameterHandler newParameterHandler(MappedStatement mappedStatement, ObjectparameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}
同Executor和StatementHandler一样,ParameterHandler也是可以被拦截的。
参数设置
DefaultParameterHandler里设置参数的代码如下:
public void setParameters(PreparedStatement ps) throws SQLException {ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if(parameterMappings != null) {MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);for (int i = 0; i< parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if(parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();PropertyTokenizer prop = new PropertyTokenizer(propertyName);if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){value = parameterObject;} else if (boundSql.hasAdditionalParameter(propertyName)){value = boundSql.getAdditionalParameter(propertyName);} else if(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)&& boundSql.hasAdditionalParameter(prop.getName())){value = boundSql.getAdditionalParameter(prop.getName());if (value != null) {value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));}} else {value = metaObject == null ? null :metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();if (typeHandler == null) {throw new ExecutorException("Therewas no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());}typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());}}}
}
这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法里有这么一句:
this.boundSql= mappedStatement.getBoundSql(parameterObject);
它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等。
参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。
7.ResultSetHandler
结果处理
结果处理使用ResultSetHandler来完成,默认的ResultSetHandler是FastResultSetHandler,它在创建StatementHandler时一起创建,代码如下:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,
RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;
}
可以看出ResultSetHandler也是可以被拦截的,可以编写自己的拦截器改变ResultSetHandler的默认行为。
// ResultSetHandler内部一条记录一条记录的处理,在处理每条记录的每一列时会调用TypeHandler转换结果,如下:
protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {boolean foundValues = false;for (StringcolumnName : unmappedColumnNames) {final Stringproperty = metaObject.findProperty(columnName);if (property!= null) {final ClasspropertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType)) {final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);final Object value = typeHandler.getResult(rs,columnName);if (value != null) {metaObject.setValue(property, value);foundValues = true;}}}}return foundValues;
}
从代码里可以看到,决断TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。
相关文章:

MyBatis的SqlSession理解
SqlSession是Mybatis最重要的构建之一,可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式…...

axios 某个接口使用自己独有的完整地址
可以在axios请求中使用完整的URL,而不使用baseURL, 只需将url字段设置为完整的URL即可 import axios from axios;export function getInfo() {return axios({url: http://192.168.3.15:8086/test/messages,method: post}); }直接在url字段中提供了完整的…...

WEB:Web_python_template_injection
背景知识 python模板注入 ssit 题目 打开题目,发现页面提示,翻译为python模板注入 先测试是否存在注入 可以发现被执行了 先查看所有的子类 payload {{[].__class__.__base__.__subclasses__()}} 利用site.Printer的os模块执行命令 payload {{.__…...

【Android安全】Embedded Trace Microcell模块
ETM: Embedded Trace Macrocell, hardware unit responsible to generate hardware instruction trace. ETM模块用于在硬件层面实现instruction trace,可用于辅助逆向分析。 使用教程: https://mcuoneclipse.com/2016/11/05/tutorial-getting-etm-inst…...

修改内核驱动之后-如何给内核打补丁
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言思路步骤1.进入下面路径2.修改文件calibrate.c3.使用git工具生产补丁文件4.移动补丁文件到自己的Linux的recipem目录下总结前言 本文来学习如何使用YOCTO修改Linux内核驱动之后,如何通过打补…...

【javaSE】 类和对象详解
目录 面向对象的初步认知 什么是面向对象 面向对象与面向过程 类定义和使用 简单认识类 类的定义格式 注意事项 练习定义类 定义一个狗类 定义一个学生类 注意事项 类的实例化 什么是实例化 注意事项 类和对象的说明 this引用 为什么要有this引用 什么是this引…...

大数据课程D5——hadoop的Sink
文章作者邮箱:yugongshiyesina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Sink的HDFS Sink; ⚪ 掌握Sink的Logger Sink; ⚪ 掌握Sink的File Roll Sink; ⚪ 掌握Sink的Null Sink; ⚪ 掌握Si…...

【数据结构】27.移除元素
💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …...

机器学习分布式框架ray运行xgboost实例
Ray是一个开源的分布式计算框架,专门用于构建高性能的机器学习和深度学习应用程序。它的目标是简化分布式计算的复杂性,使得用户能够轻松地将任务并行化并在多台机器上运行,以加速训练和推理的速度。Ray的主要特点包括支持分布式任务执行、Ac…...

C++设计模式笔记
设计模式 如何解决复杂性? 分解 核心思想:分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单的问题。 抽象 核心思想:从高层次角度讲,人们处理复杂性有一个通用的技术,及抽象。…...

简单聊聊创新与创造力
文章目录 前言一、大脑运行的两种方式1、聚焦模式2、发散模式3、影响想法的因素a、背景知识b、兴趣c、天赋 4、思维固化 二、想法的不可靠1、对想法进行验证2、颠覆性创新,挤牙膏式创新3、为什么模仿这么多 三、更多更多的idea1、个人的方面a、积累不同的背景知识b、…...

使用TensorFlow训练深度学习模型实战(上)
大家好,尽管大多数关于神经网络的文章都强调数学,而TensorFlow文档则强调使用现成数据集进行快速实现,但将这些资源应用于真实世界数据集是很有挑战性的,很难将数学概念和现成数据集与我的具体用例联系起来。本文旨在提供一个实用…...

【Spring】什么是Bean的生命周期及作用域,什么是Spring的执行流程?
博主简介:想进大厂的打工人博主主页:xyk:所属专栏: JavaEE进阶 在前面的播客中讲解了如何从Spring中存取Bean对象,那么本篇我们来讲解Bean对象的生命周期是什么,Bean对象的6种作用域分别是什么,都有哪些区别ÿ…...

立创EDA学习
学习树莓派3B的板子发现有个扩展板比较好,自己最好画一个,反正免费。 学习视频:立创EDA(专业版)电路设计与制作快速入门。 下载专业版,并激活。【分专业版和标准版,专业版也是免费的】 手机…...

清风学习笔记—层次分析法—matlab对判断矩阵的一致性检验
在判断矩阵是否为正互反矩阵这块,我写了两种代码,改进前很麻烦且有错误,改进后简洁多了,改进前的代码还有错误,忽略了对角线的值必须都是1,只考虑了除开对角线的元素相乘为1。 %% 改进前代码 A[3 2 4;1/2 …...

大众安徽内推
大众汽车(安徽)有限公司是大众汽车集团在中国第一家专注于新能源汽车的合资企业,是集团在中国首家拥有全面运营管理权的合资企业,担负着产品研发及数字化研发的重任,将成为集团全球电动出行中心之一。 VW Anhui Offic…...

Meta “地平线世界”移动端应用即将上线,手机快乐元宇宙?
根据海外记者 Janko Roettgers 的报道,Meta 预计很快推出移动版的 VR 元宇宙服务 "地平线世界",这是Meta 长期开发的产品。 根据最新报道,Meta宣布正在研发“地平线世界”的移动版,并表示这一服务已经可以在Quest VR设…...

更省更快更安全的云服务器,一站式集中管理,随时随地远程——站斧云桌面
随着全球化和数字化经济的发展,越来越多的企业开始海外扩张和拓展国际市场。而云服务器作为一种高效、灵活且可靠的IT基础设施方案,已成为出海企业不可或缺的重要工具。这里就为大家介绍云服务器在出海企业中的几个使用场景。 1.全球范围内协同办公 对…...

出现 Try run Maven import with -U flag (force update snapshots) 的解决方法
目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 在配置Maven依赖信息的时候,出现如下问题: com.alibaba.nacos:nacos‐client:pom:1.1.3 failed to transfer from http://nexus.hepengju.cn:8081/nexus/content/groups/public/ during a previous attempt. This failu…...

python多线程
目录 一.多线程的定义 A.什么是多线程? B.多线程如今遇到的挑战 C.总结 二.python中的多线程 A.python中的多线程底层原理: B.全局解释器锁导致python多线程不能实现真正的并行执行! C.总结应用场景 三.java多线程,以及…...

Spring Framework 提供缓存管理器Caffeine
说明 Spring Framework 提供了一个名为 Caffeine 的缓存管理器。Caffeine 是一个基于 Java 的高性能缓存库,被广泛用于处理大规模缓存数据。 使用 Caffeine 缓存管理器,可以轻松地在 Spring 应用程序中添加缓存功能。它提供了以下主要特性:…...

ZQC的游戏 题解
前言 这题题意描述不是很清楚啊,所以我找了个有权限的人把题面改了改,应该还是比较清楚了。 感觉这道题挺妙的,就来写一篇题解。 思路 首先,根据贪心思想,我们会将 1 1 1 号点半径以内能吃的都吃了,假…...

24考研数据结构-第一章 绪论
数据结构 引用文章第一章:绪论1.0 数据结构在学什么1.1 数据结构的基本概念1.2 数据结构的三要素1.3 算法的基本概念1.4 算法的时间复杂度1.4.1 渐近时间复杂度1.4.2 常对幂指阶1.4.3 时间复杂度的计算1.4.4 最好与最坏时间复杂度 1.5 算法的空间复杂度1.5.1 空间复…...

Gitlab 备份与恢复
备份 1、备份数据(手动备份) gitlab-rake gitlab:backup:create2、备份数据(定时任务备份) [rootlocalhost ]# crontab -l 00 1 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create 说明:每天凌晨1点备份数据…...

数据库—用户权限管理(三十三)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、概述 二、用户权限类型 三、用户赋权 四、权限删除 五、用户删除 前言 数据库用户权限管理是指对数据库用户的权限进行控制和管理,确保用户只能执…...

C语言【怎么定义变量?】
变量定义的目的是向编译器说明在哪里创建变量的存储,并指明如何创建变量的存储方式。变量定义会明确指定一个数据类型,并包含一个或多个变量的列表。例如: type variable_list; 在这里,"type"必须是一个合法的C数据类…...

vue中使用vab-magnifier实现放大镜效果
效果图如下: 1. 首先,使用npm或yarn安装vab-magnifier插件: npm install vab-magnifier或 yarn add vab-magnifier2. 在Vue组件中引入vab-magnifier插件: import VabMagnifier from vab-magnifier; import vab-magnifier/lib…...

无涯教程-jQuery - Highlight方法函数
Highlight 效果可以与effect()方法一起使用。这将以特定的颜色突出显示元素的背景,默认为黄色(yellow)。 Highlight - 语法 selector.effect( "highlight", {arguments}, speed ); 这是所有参数的描述- color - 高亮显示颜色。默认值为"#fff…...

【bar堆叠图形绘制】
绘制条形图示例 在数据可视化中,条形图是一种常用的图表类型,用于比较不同类别的数据值。Python的matplotlib库为我们提供了方便易用的功能来绘制条形图。 1. 基本条形图 首先,我们展示如何绘制基本的条形图。假设我们有一个包含十个类别的…...

ORACLE数据库灾难恢复
一:RMAN恢复 .1 创建测试用户,授权,分配测试表空间,给测试数据 –创建测试用户: SQL> alter session set containerPRODPDB; Session altered. SQL> SQL> show con_name; CON_NAME PRODPDB SQL> cre…...