Spring JDBC最佳实践(2)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

使用DataSourceUtils进行Connection的管理
由上节代码可知,JdbcTemplate在获取Connection的时候,并不是直接调用DataSource的getConnection(),而是调用了如下的代码:
Connection con = DataSourceUtils.getConnection(getDataSource());

为什么要这么做呢?
实际上,如果对于一个功能带一的JdbcTemplate来说,调用如下的代码就够了:
Connection con = dataSource.getConnection();

只不过,spring所提供的JdbcTemplate要关注更多的东西,所以,在从dataSource取得连接的时候,需要多做一些事情。

org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用来从指定的DataSource中获取或者释放连接,它会将取得的Connection绑定到当前的线程,以便在使用Spring所提供的统一事务抽象层进行事务管理的时候使用。

为什么要使用NativeJdbcExtractor
在execute()方法中可以看到:

if (this.nativeJdbcExtractor != null &&this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {conToUse = this.nativeJdbcExtractor.getNativeConnection(con);}

if (this.nativeJdbcExtractor != null) {stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);}

通过该处理,获取的将是相应的驱动程序所提供的实现类,而不是相应的代理对象。
JdbcTemplate内部定义了一个NativeJdbcExtractor类型的实例变量:
/** Custom NativeJdbcExtractor */private NativeJdbcExtractor nativeJdbcExtractor;

当我们想用驱动对象所提供的原始API的时候,可以通过JdbcTemplate的如下代码:
public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {this.nativeJdbcExtractor = extractor;}
这样将会获取真正的目标对象而不是代理对象。

spring默认提供面向Commons DBCP、C3P0、Weblogic、Websphere等数据源的NativeJdbcExtractor的实现类: CommonsDbcpNativeJdbcExtractor:为Jakarta Commons DBCP数据库连接池所提供的NativeJdbcExtractor实现类 C3P0NativeJdbcExtractor:为C3P0数据库连接池所提供的NativeJdbcExtractor实现类 WebLogicNativeJdbcExtractor:为Weblogic所准备的NativeJdbcExtractor实现类

WebSphereNativeJdbcExtractor:为WebSphere所准备的NativeJdbcExtractor实现类

控制JdbcTemplate的行为 JdbcTemplate在使用Statement或者PreparedStatement等进行具体的数据操作之前,会调用如下的代码:

protected void applyStatementSettings(Statement stmt) throws SQLException {int fetchSize = getFetchSize();if (fetchSize > 0) {stmt.setFetchSize(fetchSize);}int maxRows = getMaxRows();if (maxRows > 0) {stmt.setMaxRows(maxRows);}DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());}

这样便可以设置Statement每次抓取的行数 等等。

SQLException到DataAccessException的转译 因为JdbcTemplate直接操作的是JDBC API,所以它需要捕获在此期间可能发生的SQLException,处理的宗旨是将SQLException 转译到spring的数据访问异常层次体系,以统一数据访问异常的处理方式,这个工作主要是交给了SQLExceptionTranslator,该 接口的定义如下:

package org.springframework.jdbc.support;import java.sql.SQLException;import org.springframework.dao.DataAccessException;/**** @author Rod Johnson* @author Juergen Hoeller* @see org.springframework.dao.DataAccessException*/
public interface SQLExceptionTranslator {DataAccessException translate(String task, String sql, SQLException ex);}

该接口有两个主要的实现类,SQLErrorCodeSQLExceptionTranslator和SQLStateSQLExceptionTranslator,如下所示:

SQLExceptionSubclassTranslator是Spring2.5新加的实现类,主要用于JDK6发布的将JDBC4版本中新定义的异常体系转化为spring的异常体系,对于之前的版本,该类派不上用场。
SQLErrorCodeSQLExceptionTranslator会基于SQLExcpetion所返回的ErrorCode进行异常转译。通常情况下,根据各个数据库提供商所提供的ErrorCode进行分析要比基于SqlState的方式要准确的多。默认情况下,JdbcTemplate会采用SQLErrorCodeSQLExceptionTranslator进行SQLException的转译,当ErrorCode无法提供足够的信息的时候,会转而求助SQLStateSQLExceptionTranslator。
如果JdbcTemplate默认的SQLErrorCodeSQLExceptionTranslator无法满足当前异常转译的需要,我们可以扩展SQLErrorCodeSQLExceptionTranslator,使其支持更多的情况,有两种方法进行扩展:提供其子类或者在classpath下提供相应的配置文件,

我们先大致看一下SQLErrorCodeSQLExceptionTranslator的大致调用规则,然后再从代码层面上研究下,r进行转译的大致的流程如下:
1、SQLErrorCodeSQLExceptionTranslator定义了如下的自定义异常转译的方法:

protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {return null;}

程序流程首先会检查该自定义转译的方法是否能够对当前的SQLException进行转译,如果可以,直接返回DataAccessException类型,如果为null,表示无法转译,程序将执行下一步,由上面代码可以看到该方法直接返回null,所以,流程要进入下一步。
2、使用org.springframework.jdbc.support.SQLErrorCodesFactory所加载的SQLErrorCodes进行异常转译,其中,SQLErrorCodesFactory加载SQLErrorCodes的流程为:
1>使用org/springframework/jdbc/support/sql-error-codes.xml路径下记载了各个数据库提供商的配置文件,提取相应的SQLErrorCodes。
2>如果发现当前应用的根目录下存在名称为sql-error-codes.xml的配置文件,则加载该文件并覆盖默认的ErrorCodes定义。

3、如果基于ErrorCode的异常转译还是没法搞定的话,SQLErrorCodeSQLExceptionTranslator只能求助于SQLStateSQLExceptionTranslator或者SQLExceptionSubclassTranslator

下面从代码层面上剖析之:
假若JdbcTemplate的如下模板方法在执行的过程中发生了异常:

public Object execute(StatementCallback action) throws DataAccessException {Assert.notNull(action, "Callback object must not be null");Connection con = DataSourceUtils.getConnection(getDataSource());Statement stmt = null;try {Connection conToUse = con;if (this.nativeJdbcExtractor != null &&this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {conToUse = this.nativeJdbcExtractor.getNativeConnection(con);}stmt = conToUse.createStatement();applyStatementSettings(stmt);Statement stmtToUse = stmt;if (this.nativeJdbcExtractor != null) {stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);}Object result = action.doInStatement(stmtToUse);handleWarnings(stmt);return result;}catch (SQLException ex) {// Release Connection early, to avoid potential connection pool deadlock// in the case when the exception translator hasn't been initialized yet.JdbcUtils.closeStatement(stmt);stmt = null;DataSourceUtils.releaseConnection(con, getDataSource());con = null;throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);}finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, getDataSource());}}

会执行catch块中的
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);

getExceptionTranslator()如下定义:
public synchronized SQLExceptionTranslator getExceptionTranslator() {if (this.exceptionTranslator == null) {DataSource dataSource = getDataSource();if (dataSource != null) {this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);}else {this.exceptionTranslator = new SQLStateSQLExceptionTranslator();}}return this.exceptionTranslator;}

dataSource不为null,所以创建了SQLErrorCodeSQLExceptionTranslator,看下其构造方法:
public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {this();setDataSource(dataSource);}

this()代码为:
public SQLErrorCodeSQLExceptionTranslator() {if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) {setFallbackTranslator(new SQLExceptionSubclassTranslator());}else {setFallbackTranslator(new SQLStateSQLExceptionTranslator());}}

如果JDK版本大于或等于6,备份了一个SQLExceptionSubclassTranslator类型的Translator,否则备份一个SQLStateSQLExceptionTranslator
setDataSource(DataSource dataSource)通过SQLErrorCodesFactory创建一个SQLErrorCodes类型的变量:
public void setDataSource(DataSource dataSource) {this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);}

SQLErrorCodesFactory采用了单例模式,在其构造方法中依然利用了BeanFactory,传入的文件为xml bean配置文件:
protected SQLErrorCodesFactory() {Map errorCodes = null;try {DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf);// Load default SQL error codes.Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH);if (resource != null && resource.exists()) {bdr.loadBeanDefinitions(resource);}else {logger.warn("Default sql-error-codes.xml not found (should be included in spring.jar)");}// Load custom SQL error codes, overriding defaults.resource = loadResource(SQL_ERROR_CODE_OVERRIDE_PATH);if (resource != null && resource.exists()) {bdr.loadBeanDefinitions(resource);logger.info("Found custom sql-error-codes.xml file at the root of the classpath");}// Check all beans of type SQLErrorCodes.errorCodes = lbf.getBeansOfType(SQLErrorCodes.class, true, false);if (logger.isInfoEnabled()) {logger.info("SQLErrorCodes loaded: " + errorCodes.keySet());}}catch (BeansException ex) {logger.warn("Error loading SQL error codes from config file", ex);errorCodes = Collections.EMPTY_MAP;}this.errorCodesMap = errorCodes;}


可知首先会读取org.springframework.jdbc.support下的sql-error-codes.xml文件,如果classpath下也有该文件,则覆盖之,
这样便生成了sqlErrorCodes
getExceptionTranslator().translate("StatementCallback", getSql(action), ex)的方法如下所示:

public DataAccessException translate(String task, String sql, SQLException ex) {Assert.notNull(ex, "Cannot translate a null SQLException");if (task == null) {task = "";}if (sql == null) {sql = "";}DataAccessException dex = doTranslate(task, sql, ex);if (dex != null) {// Specific exception match found.return dex;}// Looking for a fallback...SQLExceptionTranslator fallback = getFallbackTranslator();if (fallback != null) {return fallback.translate(task, sql, ex);}// We couldn't identify it more precisely.return new UncategorizedSQLException(task, sql, ex);}

doTranslate(task, sql, ex)让子类实现,在这个例子中即是SQLErrorCodeSQLExceptionTranslator,代码如下:
protected DataAccessException doTranslate(String task, String sql, SQLException ex) {SQLException sqlEx = ex;if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {SQLException nestedSqlEx = sqlEx.getNextException();if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {logger.debug("Using nested SQLException from the BatchUpdateException");sqlEx = nestedSqlEx;}}// First, try custom translation from overridden method.DataAccessException dex = customTranslate(task, sql, sqlEx);if (dex != null) {return dex;}// Check SQLErrorCodes with corresponding error code, if available.if (this.sqlErrorCodes != null) {String errorCode = null;if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {errorCode = sqlEx.getSQLState();}else {errorCode = Integer.toString(sqlEx.getErrorCode());}if (errorCode != null) {// Look for defined custom translations first.CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations();if (customTranslations != null) {for (int i = 0; i < customTranslations.length; i++) {CustomSQLErrorCodesTranslation customTranslation = customTranslations[i];if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) {if (customTranslation.getExceptionClass() != null) {DataAccessException customException = createCustomException(task, sql, sqlEx, customTranslation.getExceptionClass());if (customException != null) {logTranslation(task, sql, sqlEx, true);return customException;}}}}}// Next, look for grouped error codes.if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new BadSqlGrammarException(task, sql, sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new InvalidResultSetAccessException(task, sql, sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);}}}// We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.if (logger.isDebugEnabled()) {String codes = null;if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) {codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();}else {codes = "Error code '" + sqlEx.getErrorCode() + "'";}logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator");}return null;}

可知假如该方法返回的是null,translate方法会调用SQLExceptionSubclassTranslator或者SQLStateSQLExceptionTranslator的translate的方法转译这个异常。

在SQLErrorCodeSQLExceptionTranslator转译异常的过程中,我们可以在两个地方插入自定义的转译异常:
1、在customTranslate(String task, String sql, SQLException sqlEx)方法中,通过子类化SQLErrorCodeSQLExceptionTranslator,重写该方法。
2、在classpath下提供sql-error-codes.xml文件。
下面是使用这两种方式进行自定义转译的具体实施情况。
1、扩展SQLErrorCodeSQLExceptionTranslator
该方法最直接有效,却不够方便,需要子类化并且覆写它的customTranslate方法,

package com.google.spring.jdbc;import java.sql.SQLException;import org.springframework.dao.DataAccessException;
import org.springframework.dao.UncategorizedDataAccessException;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;public class SimpleSQLErrorCodeSQLExceptinTranslator extends SQLErrorCodeSQLExceptionTranslator
{@Overrideprotected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {if(sqlEx.getErrorCode()==111){StringBuilder builder = new StringBuilder();builder.append("unexpected data access exception raised when executing ");builder.append(task);builder.append(" with SQL>");builder.append(sql);return new UnknownUncategorizedDataAccessException(builder.toString(),sqlEx);}return null;}private class UnknownUncategorizedDataAccessException extends UncategorizedDataAccessException{public UnknownUncategorizedDataAccessException(String msg, Throwable cause) {super(msg, cause);}}
}

在这里,假设当数据库返回的错误代码为111的时候,将抛出UnknownUncategorizedDataAccessException类型的异常(或者是其它自定义的DataAccessException)除此之外,返回null以保证其它的异常转译依然采用超类的逻辑进行。
为了能使自定义的转译其作用,我们需要让JdbcTemplate使用我们的SimpleSQLErrorCodeSQLExceptinTranslator,而不是默认的SQLErrorCodeSQLExceptionTranslator,所以,需要如下代码所示,将SimpleSQLErrorCodeSQLExceptinTranslator设置给JdbcTemplate:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");JdbcTemplate jdbc = (JdbcTemplate)applicationContext.getBean("jdbc");DataSource dataSource = (DataSource)applicationContext.getBean("dataSource");SimpleSQLErrorCodeSQLExceptinTranslator simpleSQLErrorCodeSQLExceptinTranslator = new SimpleSQLErrorCodeSQLExceptinTranslator();simpleSQLErrorCodeSQLExceptinTranslator.setDataSource(dataSource);jdbc.setExceptionTranslator(simpleSQLErrorCodeSQLExceptinTranslator);
在classpath下放置一个sql-error-codes.xml文件,格式要与默认的文件格式相同。

实际上,它就是一个基本的基于DTD的Spring IOC容器的配置文件,只不过class是固定的。该配置文件对每个数据库类型均提供了一个org.springframework.jdbc.support.SQLErrorCodes的定义。假若我们有另外一个数据库AnotherDb,要扩展该转译,我们有两种方式:
1、

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans><bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes"><property name="databaseProductName"><value>AnotherDB*</value></property><property name="badSqlGrammarCodes"><value>001</value></property><property name="dataIntegrityViolationCodes"><value>002</value></property><property name="dataAccessResourceFailureCodes"><value>0031,0032</value></property><property name="transientDataAccessResourceCodes"><value>004</value></property><property name="deadlockLoserCodes"><value>0051,0052</value></property></bean>
</beans>


2、设置customTranslations属性:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans><bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes"><property name="databaseProductName"><value>AnotherDB*</value></property><property name="customTranslations"><list><bean class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation"><property name="errorCodes">111</property><property name="exceptionClass">org.springframework.dao.IncorrectResultSizeDataAccessException</property></bean></list></property></bean>
</beans>

至此,spring的异常转译部分全部分析完毕!

转载于:https://my.oschina.net/u/218421/blog/38576

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/464136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我所感受到的上海

大家好&#xff0c;文章转自张老师的公众号&#xff0c;文章的我不是小编本人&#xff0c;小编现居深圳&#xff0c;刚接受了一场大雨的洗礼。前两天公众号抽奖的书籍已经发货&#xff0c;中奖的朋友们注意查收。当格蠹园里的大灰反复犹豫到底应该在哪里生产的时候&#xff0c;…

设计模式学习笔记五——Prototype模式

动机&#xff1a;使用原型实例指定创建对象的种类&#xff0c;然后通过拷贝这些原型来创建新的对象。某些结构复杂对象面临着剧烈变化&#xff0c;但拥有比较稳定一致的接口&#xff0c;如何隔离出这些易变对象&#xff0c;使客户程序不随之改变&#xff1f;场景&#xff1a;Th…

2011年最佳代码

2019独角兽企业重金招聘Python工程师标准>>> 2011年最佳代码 try { if(you.bevieve(it) true || you.believe(it) false) { I.believe(it); } } catch(Exception ex) { throw new Exception("Its a miracle!"); } finally { it.justHappened(); } 转载于…

昨晚两点睡

深圳下雨两天&#xff0c;我们居家办公两天&#xff0c;不过奇怪的事情是&#xff0c;这两天我都到公司上班&#xff0c;昨天早上没有下雨&#xff0c;我想着应该要去公司&#xff0c;结果到了公司才知道原来今天可以居家办公。不过&#xff0c;在公司才有上班的感觉&#xff0…

hashmap详解

一.hashmap的数据结构 HashMap采取数组加链表的存储方式(哈希表)来实现。亦即数组&#xff08;散列桶&#xff09;中的每一个元素都是链表 二.hashmap的构造函数 HashMap()&#xff1a;构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 HashMap(int initia…

书籍推荐-记这几年看的书

这几年看了不少书&#xff0c;大部分是自掏腰包&#xff0c;看一本好书是享受&#xff0c;我很喜欢这种感觉。 这些是我这几年看书的一些心得&#xff0c;对于一些新手来说&#xff0c;可能有点帮助。 这几年一直在走技术路线&#xff0c;所以看的大部分都是技术方面的书籍&…

不复位MCU直接调试运行程序,让bug闻风丧胆

大家周末好呀&#xff0c;文章转自bug菌的公众号&#xff0c;文章介绍步复位情况下调试bug&#xff0c;希望对大家有用。1调试窘境经常有朋友在开发中遇到这样的窘境&#xff0c;当单片机程序运行异常以后&#xff0c;由于调试信息做得并不是很全面&#xff0c;导致相应的问题场…

这次比opencv快⑥倍!!!

打败opencv ,哦&#xff0c;是快了3倍上回书说道&#xff0c;我用汇编neon实现去畸变算法比opencv快3倍&#xff0c;这都不算啥&#xff0c;这次新增了透视变换算法&#xff0c;二者加起来比opencv快6倍&#xff01;拭目以待吧。啥玩意是透视变换&#xff1f;相信你们都开过高级…

数据和数据类型

一、什么是数据&#xff1a; 数据(date)是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未加工的原始素材。 1&#xff09;数据是信息的表现形式和载体&#xff0c;可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离…

涂鸦的这套宠物SDK设计,真香

我应该在之前的文章里面说过&#xff0c;我之前创业的时候做过宠物方面的产品&#xff0c;而且我们当时用的是乐鑫的芯片。最近知道在涂鸦工作的朋友也在研究这方面&#xff0c;他给我寄了几个小板子&#xff0c;还有涂鸦的IOT SDK&#xff0c;我玩了几天&#xff0c;觉得真的很…

准备 KVM 实验环境 - 每天5分钟玩转 OpenStack(3)

转载&#xff1a;http://cloudman.blog.51cto.com/10425448/1747415 KVM 是 OpenStack 使用最广泛的 Hypervisor&#xff0c;本节介绍如何搭建 KVM 实验环境 安装 KVM 上一节说了&#xff0c;KVM 是 2 型虚拟化&#xff0c;是运行在操作系统之上的&#xff0c;所以我们先要装一…

电子美图高清系列漫画分享给大家欣赏!

电子漫画搞电子的大家或多或少都会收集了电子漫画和表情到&#xff0c;小编找了一份比较全高清无码的电子美图漫画&#xff0c;供大家欣赏&#xff0c;提供下面三种下载方式&#xff01;1、GitHub&#xff1a;https://github.com/chiphome/Electronic-Comics2、Gitee&#xff1…

如何提高网页中图片显示的用户体验(附源码下载)

文章中加入适量的图片不仅可以更好的说明和补充文章的内容&#xff0c;而且还可以极大的减缓阅读者在阅读较长篇幅文章时的疲劳和不安。所以图文混排较好的文章能给阅读者更好的用户体验和享受。但是令人遗憾的是很多的网页图片的显示并不十分理想&#xff0c;非但没有给阅读者…

全开源最小电压表:24位ADC,测量0~2V,五位半

1、项目背景2015年5月份评估完十几种24位ADC后就从第一份工作岗位上离职了&#xff0c;做的24位AD都没有达到实际的效果&#xff0c;一直耽搁困扰了好久。是硬件设计的问题&#xff1f;还是软件开发的问题&#xff1f;还是24位ADC真的不咋地&#xff1f;还是要离职了干活就不负…

小小Table

图1 图2(加了蓝色&#xff0c;让问题更清楚)说明&#xff1a;1.工作需要要做这样一个图形&#xff08;图1&#xff09;&#xff0c;而且要求不用表(table)套表(table)即一个table画出。2.素材有图表两张:(12*14),(12*13) 颜色为Red感兴趣的朋友…

文件断点续传原理与实现

文件断点续传原理与实现 在网络状况不好的情况下&#xff0c;对于文件的传输&#xff0c;我们希望能够支持可以每次传部分数据。首先从文件传输协议FTP和TFTP开始分析&#xff0c; FTP是基于TCP的&#xff0c;一般情况下建立两个连接&#xff0c;一个负责指令&#xff0c;一个负…

Windows Forms、MFC、WTL、WxWidgets、Qt、GTK综合比较

图形界面库Windows Forms、 MFC、WTL、 WxWidgets、Qt、GTK 综合比较见下表&#xff1a; 总结&#xff1a; GTK主要用在X Window上&#xff0c;整个设计的架构和许多概念和MFC以及一般 Windows 上的程序开发大异其趣&#xff0c;入门门槛较高&#xff0c;而且最主要的特色是&am…

G3,是塔克和阿德巴约的热火队

这一场&#xff0c;是塔克和阿德巴约的热火队G2的比赛&#xff0c;波斯顿人的数据如下G3的比赛&#xff0c;波斯顿人的数据如下如果说田忌赛马可以用在篮球场上&#xff0c;那这场堪称经典热火和波斯顿人的第三场比赛&#xff0c;火队是客场作战&#xff0c;客场作战的热火在第…

微信小程序--数据存储

对本地缓存数据操作分为同步和异步两种。同步方法有成功回调函数&#xff0c;表示数 据处理成功后的操作。下面是小程序提供本地缓存操作接口&#xff1a; 以Sync结尾都是同步方法。同步方法和异步方法的区别是&#xff1a; 同步方法会堵塞当前任务&#xff0c;直到同步方法处理…

RTMPdump 源代码分析 1: main()函数

http://blog.csdn.net/leixiaohua1020/article/details/12952977 rtmpdump 是一个用来处理 RTMP 流媒体的工具包&#xff0c;支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps:// 等。之前在学习RTMP协议的时候&#xff0c;发现没有讲它源代码的&#xff0c;只好自己分…