文档章节

读写分离(spring事务代理+mybaits拦截器实现)

bodomg
 bodomg
发布于 2017/02/13 14:31
字数 2241
阅读 313
收藏 3
点赞 0
评论 0

1、读写分离

         采用继承DataSourceTransactionManager控制事务读写分离,以及mybatis拦截器控制方法读写分离,通过继承AbstractRoutingDataSource获取动态数据源。

1.1、代码说明

1.1.1、本地数据源管理

创建本地数据源类型管理类,使用ThreadLocal保存本地数据源类型

bodsite-common - com.bodsite.common.datasource. DataSourceHandler

package com.bodsite.common.datasource;

/**

 * @Description:本地线程数据源

 * @author bod

 * @date

 *

 */

public class DataSourceHandler {

   public enum DYNAMIC_DATA_SOURCE{

      MASTER,//主库(写)

      SLAVE;//从库(读)

   }

  

   private static final ThreadLocal<DYNAMIC_DATA_SOURCE> dataSourceThreadLocal = new ThreadLocal<DYNAMIC_DATA_SOURCE>();

  

   protected static void set(DYNAMIC_DATA_SOURCE dynamic_data_source){

      dataSourceThreadLocal.set(dynamic_data_source);

   }

  

   /**

    * 设置为主库

    * @author bod

    */

   protected static void setMaster(){

      dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.MASTER);

   }

  

   /**

    * 设置为读库

    * @author bod

    */

   protected static void setSlave(){

      dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.SLAVE);

   }

 

   /**

    * 判断是否为主库

    * @author bod

    */

   protected static boolean isMaster(){

      return isThis(DYNAMIC_DATA_SOURCE.MASTER);

   }

  

   /**

    * 判断是否为从

    * @author bod

    */

   protected static boolean isSlave(){

      return isThis(DYNAMIC_DATA_SOURCE.SLAVE);

   }

 

   protected static boolean isThis(DYNAMIC_DATA_SOURCE dynamic_data_source){

      if(dataSourceThreadLocal.get()==null){

         return false;

      }

      return dynamic_data_source == dataSourceThreadLocal.get();

   }

  

   protected static void DataSoruceClean(){

      dataSourceThreadLocal.remove();

   }

}

1.1.2、mybatis拦截,设置数据源

创建mybatis拦截器,拦截update、query方法,设置数据源,如果有事务,不做拦截,有事务的情况,在事务管理器中进行设置。

bodsite-common - com.bodsite.common.datasource. DynamicDataSourceInterceptor

package com.bodsite.common.datasource;

 

import java.util.Properties;

 

import org.apache.ibatis.executor.Executor;

import org.apache.ibatis.executor.keygen.SelectKeyGenerator;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.SqlCommandType;

import org.apache.ibatis.plugin.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Plugin;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.springframework.transaction.support.TransactionSynchronizationManager;

 

/**

 * @Description: mybatis plugin 拦截器-设置数据源

 * @author bod

 * @date

 *

 */

@Intercepts({

      @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,

            RowBounds.class, ResultHandler.class }), // method:方法名,type:类,args:方法参数

      @Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }) })

public class DynamicDataSourceInterceptor implements Interceptor {

 

   @Override

   public Object intercept(Invocation invocation) throws Throwable {

      // 是否有事务

      boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();

      if (!synchronizationActive) {

         Object[] args = invocation.getArgs();

         MappedStatement ms = (MappedStatement) args[0];

         if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){//查询

              //!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库

                if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {

                DataSourceHandler.setMaster();

                }else{

                DataSourceHandler.setSlave();

                }

         }else{//其他

            DataSourceHandler.setMaster();

         }

      }

      Object result = invocation.proceed();

      DataSourceHandler.DataSoruceClean();

      return result;

   }

 

   @Override

   public Object plugin(Object target) {

      if (target instanceof Executor) {

         return Plugin.wrap(target, this);

      } else {

         return target;

      }

   }

 

   @Override

   public void setProperties(Properties properties) {

 

   }

 

}

 

1.1.3、spring事务管理,设置数据源

创建动态事务管理类,继承DataSourceTransactionManager,根据读写判断,设置数据源。

bodsite-common - com.bodsite.common.datasource. DataSourceTransactionManager

   package com.bodsite.common.datasource;

 

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.transaction.TransactionDefinition;

 

/**

 *

 * @Description:根据事务这是数据源(必须有事务才能进入)

 * @author bod

 * @date

 *

 */

public class  DynamicDataSourceTransactionManager extends DataSourceTransactionManager{

 

   /**

    *

    */

   private static final long serialVersionUID = 1L;

 

   @Override

   protected void doBegin(Object transaction, TransactionDefinition definition) {

      boolean readOnly = definition.isReadOnly();

      if(readOnly){//只读

         DataSourceHandler.setSlave();

      }else{//读写

         DataSourceHandler.setMaster();

      }

      super.doBegin(transaction, definition);

   }

 

   @Override

   protected void doCleanupAfterCompletion(Object transaction) {

      super.doCleanupAfterCompletion(transaction);

      DataSourceHandler.DataSoruceClean();

   }

  

}

 

1.1.4、动态获取数据源

创建动态获取数据源类,继承AbstractRoutingDataSource,根据读写判断,设置数据源。

提供两种获取数据源方式:

1、通过重写determineCurrentLookupKey方法,返回数据源名称,走AbstractRoutingDataSource的determineTargetDataSource(根据determineCurrentLookupKey返回的名称)方法获取数据源。

2、通过重写determineTargetDataSource。直接返回数据源。

bodsite-common - com.bodsite.common.datasource. DynamicDataSource

package com.bodsite.common.datasource;

 

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ThreadLocalRandom;

import java.util.concurrent.atomic.AtomicInteger;

 

import javax.sql.DataSource;

 

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

 

import com.bodsite.common.logger.Logger;

import com.bodsite.common.logger.LoggerFactory;

 

/**

 * @Description:动态数据源

 * @author bod

 * @date

 *

 */

public class DynamicDataSource extends AbstractRoutingDataSource {

   private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

   private DataSource master;

   private List<DataSource> slaves;

    private int slaveSize; //读数据源个数

   private Integer strategy;// 默认,0:轮询,1,随机

   private AtomicInteger counter = new AtomicInteger();

   private Map<Object, Object> targetDataSources = new HashMap<>();

 

   /******************* 1、加载数据源关系, 设置数据源名,会根据数据源名返回数据源 ************/

   /**

    * 设置数据源名

    */

   @Override

   protected Object determineCurrentLookupKey() {

      return getDataSourceName();

   }

 

   /**

    * 设置数据源映射关系

    */

   @Override

   public void afterPropertiesSet() {

      if (this.master == null) {

         throw new IllegalArgumentException("Property 'targetDataSources' is required");

      }

      setDefaultTargetDataSource(master);

      targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name(), master);

      if (slaves != null && !slaves.isEmpty()) {

         this.slaveSize = slaves.size();

         for (int i = 0; i < slaveSize; i++) {

            targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name() + i, slaves.get(i));

         }

      }

      setTargetDataSources(targetDataSources);

      super.afterPropertiesSet();

   }

 

   /******************* 2、直接返回数据源 ***********************/

   /**

    * 原方法:根据数据源名称返回数据源,自定义直接返回数据源

    */

   /*@Override

   protected DataSource determineTargetDataSource() {

      return (DataSource) targetDataSources.get(getDataSourceName());

   }*/

 

   /**

    * 获取数据源名称

    * @author bod

    */

   public String getDataSourceName() {

      String dataSourceName = null;

      if (DataSourceHandler.isMaster()) {

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name();

      } else if (DataSourceHandler.isSlave() && slaves != null && !slaves.isEmpty()) {

         int index = 0;

         if (strategy == null || strategy == 0) {

            int count = counter.incrementAndGet();

            index = count%slaveSize;

         }else if(strategy == 1){

               index = ThreadLocalRandom.current().nextInt(0, slaveSize);

         }

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name()+index;

      }else{

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name();

      }

      logger.info("This data source name is "+dataSourceName);

      return dataSourceName;

   }

   public DataSource getMaster() {

      return master;

   }

 

   public void setMaster(DataSource master) {

      this.master = master;

   }

 

   public List<DataSource> getSlaves() {

      return slaves;

   }

 

   public void setSlaves(List<DataSource> slaves) {

      this.slaves = slaves;

   }

 

   public Integer getStrategy() {

      return strategy;

   }

 

   public void setStrategy(Integer strategy) {

      this.strategy = strategy;

   }

 

}

 

 

1.1.5、application-mybatis.xml配置

bodsite-site-service - application-mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

   xmlns:context="http://www.springframework.org/schema/context"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"

   xmlns:aop="http://www.springframework.org/schema/aop"

   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd

      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd

            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

   <!-- druid 连接池配置信息 http://www.cnblogs.com/SummerinShire/p/5828888.html -->

   <!-- druid 数据库连接池 -->

   <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"

      init-method="init" destroy-method="close">

      <!-- 连接数据库信息 -->

      <property name="driverClassName" value="${master.jdbc.driver}" />

      <property name="url" value="${master.jdbc.url}" />

      <property name="username" value="${master.jdbc.username}" />

      <property name="password" value="${master.jdbc.password}" />

 

      <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 -->

      <property name="initialSize" value="1" />

      <!-- 最大连接池数量 -->

      <property name="maxActive" value="50" />

      <!-- 最小连接池数量 -->

      <property name="minIdle" value="10" />

      <!-- 获取连接时最大等待时间,单位毫秒 -->

      <property name="maxWait" value="60000" />

      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->

      <property name="timeBetweenEvictionRunsMillis" value="60000" />

      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->

      <property name="minEvictableIdleTimeMillis" value="300000" />

      <!-- 用来检测连接是否有效的sq -->

      <property name="validationQuery" value="SELECT 'x' FROM DUAL" />

      <!-- 建议配置为true,不影响性能,并且保证安全性 -->

      <property name="testWhileIdle" value="true" />

      <!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 -->

      <property name="testOnBorrow" value="false" />

      <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 -->

      <property name="testOnReturn" value="false" />

      <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall -->

      <property name="filters" value="stat" />

   </bean>

 

   <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"

      init-method="init" destroy-method="close">

      <!-- 连接数据库信息 -->

      <property name="driverClassName" value="${slave1.jdbc.driver}" />

      <property name="url" value="${slave1.jdbc.url}" />

      <property name="username" value="${slave1.jdbc.username}" />

      <property name="password" value="${slave1.jdbc.password}" />

 

      <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 -->

      <property name="initialSize" value="1" />

      <!-- 最大连接池数量 -->

      <property name="maxActive" value="50" />

      <!-- 最小连接池数量 -->

      <property name="minIdle" value="10" />

      <!-- 获取连接时最大等待时间,单位毫秒 -->

      <property name="maxWait" value="60000" />

      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->

      <property name="timeBetweenEvictionRunsMillis" value="60000" />

      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->

      <property name="minEvictableIdleTimeMillis" value="300000" />

      <!-- 用来检测连接是否有效的sq -->

      <property name="validationQuery" value="SELECT 'x' FROM DUAL" />

      <!-- 建议配置为true,不影响性能,并且保证安全性 -->

      <property name="testWhileIdle" value="true" />

      <!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 -->

      <property name="testOnBorrow" value="false" />

      <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 -->

      <property name="testOnReturn" value="false" />

      <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall -->

      <property name="filters" value="stat" />

   </bean>

   <!-- 动态数据源 -->

   <bean id="dataSource" class="com.bodsite.common.datasource.DynamicDataSource">

      <property name="master" ref="masterDataSource" />

      <property name="slaves">

         <list>

            <ref bean="slaveDataSource" />

         </list>

      </property>

      <property name="strategy" value="0"/>

   </bean>

 

 

   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

      <property name="dataSource" ref="dataSource" />

      <property name="configLocation" value="classpath:mybatis-config.xml" />

      <property name="mapperLocations" value="classpath:/mappings/**/*.xml" />

   </bean>

   <!-- 定义事务 -->

   <bean id="transactionManager"

      class="com.bodsite.common.datasource.DynamicDataSourceTransactionManager">

      <property name="dataSource" ref="dataSource" />

   </bean>

   <!-- 扫描@Transactional注解的类定义事务 -->

   <tx:annotation-driven transaction-manager="transactionManager"

      proxy-target-class="true" />

   <!-- 自动注册mybatis mapper bean -->

   <!-- 注意,没有必要去指定SqlSessionFactory或SqlSessionTemplate, 因为MapperScannerConfigurer将会创建

      MapperFactoryBean,之后自动装配。 但是,如果你使 用了一个以上的DataSource,那 么自动装配可能会失效。 这种情况下,你可以使用

      sqlSessionFactoryBeanName或sqlSessionTemplateBeanName 属性来设置正确的 bean名称来使用。 -->

   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

      <property name="basePackage" value="com.bodsite.**.dao" />

   </bean>

</beans>

1.1.6、mybatis-generator.xml 配置-添加拦截器

bodsite-site-service - mybatis- generator.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

   <settings>

      <!--对在此配置文件下的所有cache 进行全局性开/关设置。 -->

      <setting name="cacheEnabled" value="true" />

      <!-- 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 -->

      <setting name="lazyLoadingEnabled" value="true" />

      <!-- 允许和不允许单条语句返回多个数据集(取决于驱动需求) -->

      <setting name="multipleResultSetsEnabled" value="true" />

      <!-- 使用列标签代替列名称。 -->

      <setting name="useColumnLabel" value="true" />

      <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 -->

      <setting name="useGeneratedKeys" value="false" />

      <!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH:

         执行器可以重复执行语句和批量更新) -->

      <setting name="defaultExecutorType" value="SIMPLE" />

      <!-- 配置Java属性-数据库表字段对应驼峰规则 -->

      <setting name="mapUnderscoreToCamelCase" value="true" />

   </settings>

   <plugins>

   <!-- 读写分离拦截器 -->

   <plugin interceptor="com.bodsite.common.datasource.DynamicDataSourceInterceptor">

   </plugin>

   </plugins>

</configuration>

1.2、测试

1、启动bodsite-site-service,在bodsite-site中的DemoConsumer类,执行查询

  

2、在bodsite-site中的DemoConsumer类,执行插入

项目地址:https://git.oschina.net/bodsite/bodsite

© 著作权归作者所有

共有 人打赏支持
bodomg
粉丝 16
博文 7
码字总数 5810
作品 0
徐汇
高级程序员
分布式事务系列(1.2)Spring的事务体系

1 系列目录 - 分布式事务系列(开篇)提出疑问和研究过程- 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析- 分布式事务系列(1.2)Spring事务体系- 分布式事务系...

乒乓狂魔
2015/05/18
0
4
分布式系统 - iBase4J

Spring boot,Spring,SpringMVC,Mybatis,mybatis-plus,motan/dubbo分布式,Redis缓存,Shiro权限管理,Spring-Session单点登录,Quartz分布式集群调度,Restful服务,QQ/微信登录,App t...

iBase4J
2016/05/04
0
74
Spring编程式和声明式事务

1.编程式事务 1.1 编程式和声明式事务的区别 Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务...

梨加橙
06/19
0
0
【分布式事务系列三】Spring的事务体系

分布式事务 【分布式事务系列一】提出疑问和研究过程 【分布式事务系列二】Spring事务管理器PlatformTransactionManager 【分布式事务系列三】Spring的事务体系 本地事务模型:开发人员不用知...

陶邦仁
2015/12/09
227
0
SSH中各个框架的作用以及Spring AOP,IOC,DI详解

在SSH框假中spring充当了管理容器的角色。我们都知道Hibernate用来做持久层,因为它将JDBC做了一个良好的封装,程序员在与数据库进行交互时可以不用书写大量的SQL语句。Struts是用来做应用层...

包包大人
2014/02/18
0
2
Spring MVC拦截器实现分析

一、Servlet Filter与Spring interceptor的执行顺序 Filter有顺序吗?我们怎么控制filter的执行顺序。通过Tomcat的代码分析,servlet在Filter执行完成后才调用,如有多个filter怎么控制执行顺...

姚君
2014/04/18
0
0
Spring AOP 日志拦截器的事务管理

如果要在方法执行前或后或抛出异常后加上一个自己的拦截器,或者一个环绕拦截器,在拦截器中执行一些操作,比如执行一些数据库操作,记录一些信 息,这些操作通过调用一个服务类的方法来执行...

哲别0
05/18
0
0
MyBatis整合Spring-->SqlSession获取

目的 MyBatis在执行SQL语句时,都需要创建一个SqlSession,但是这里还需要与Spring的事务进行整合,那么SqlSession是怎么创建的呢?下面就来分析一下。 上一章节已经分析MapperProxy代理类中...

tara_qri
2015/11/01
0
0
MiniDao_1.6-SNAPSHOT 版本发布,轻量级Java持久化框架

MiniDao-PE 简介 MiniDao-PE 是一种持久化解决方案,类似mybatis的持久层解决方案,可以轻松集成Hibernate工程,事务统一管理,解决了Hibernate工程想支持mybaits的功能问题。Hibernate的最佳...

Jeecg
2016/07/21
21
0
EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~终结~配置的优化和事务里读写的统一

本讲是通过DbCommand拦截器来实现读写分离的最后一讲,对之前几篇文章做了一个优化,无论是程序可读性还是实用性上都有一个提升,在配置信息这块,去除了字符串方式的拼接,取而代之的是sec...

mcy247
2017/12/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Android 获取各大音乐平台的真实下载地址

废话 电脑使用谷歌浏览器或者QQ浏览器的时候。。。。。。。说不清楚,还是看图吧 大概意思就是,只要网页上需要播放,只要能播放并且开始播放,这个过程就肯定会请求到相关的音乐资源,然后就...

她叫我小渝
30分钟前
0
0
shell中的函数、shell中的数组、告警系统需求分析

shell中的函数 格式: 格式: function f_name() { command } 函数必须要放在最前面 示例1(用来打印参数) 示例2(用于定义加法) 示例3(用于显示IP) shell中的数组 shell中的数组1 定义数...

Zhouliang6
今天
2
0
用 Scikit-Learn 和 Pandas 学习线性回归

      对于想深入了解线性回归的童鞋,这里给出一个完整的例子,详细学完这个例子,对用scikit-learn来运行线性回归,评估模型不会有什么问题了。 1. 获取数据,定义问题     没有...

wangxuwei
今天
1
0
MAC安装MAVEN

一:下载maven压缩包(Zip或tar可选),解压压缩包 二:打开终端输入:vim ~/.bash_profile(如果找不到该文件新建一个:touch ./bash_profile) 三:输入i 四:输入maven环境变量配置 MAVEN_HO...

WALK_MAN
今天
0
0
33.iptables备份与恢复 firewalld的9个zone以及操作 service的操作

10.19 iptables规则备份和恢复 10.20 firewalld的9个zone 10.21 firewalld关于zone的操作 10.22 firewalld关于service的操作 10.19 iptables规则备份和恢复: ~1. 保存和备份iptables规则 ~2...

王鑫linux
今天
2
0
大数据教程(2.11):keeperalived+nginx高可用集群搭建教程

上一章节博主为大家介绍了目前大型互联网项目的系统架构体系,相信大家应该注意到其中很重要的一块知识nginx技术,在本节博主将为大家分享nginx的相关技术以及配置过程。 一、nginx相关概念 ...

em_aaron
今天
1
0
Apache Directory Studio连接Weblogic内置LDAP

OBIEE默认使用Weblogic内置LDAP管理用户及组。 要整理已存在的用户及组,此前办法是导出安全数据,文本编辑器打开认证文件,使用正则表达式获取用户及组的信息。 后来想到直接用Apache Dire...

wffger
今天
2
0
HFS

FS,它是一种上传文件的软件。 专为个人用户所设计的 HTTP 档案系统 - Http File Server,如果您觉得架设 FTP Server 太麻烦,那么这个软件可以提供您更方便的档案传输系统,下载后无须安装,...

garkey
今天
1
0
Java IO类库之BufferedInputStream

一、BufferedInputStream介绍 /** * A <code>BufferedInputStream</code> adds * functionality to another input stream-namely, * the ability to buffer the input and to * sup......

老韭菜
今天
0
0
STM 32 窗口看门狗

http://bbs.elecfans.com/jishu_805708_1_1.html https://blog.csdn.net/a1985831055/article/details/77404131...

whoisliang
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部