返回顶部
首页 > 资讯 > 数据库 >开源组件:(1)DBCP和C3P0
  • 435
分享到

开源组件:(1)DBCP和C3P0

2024-04-02 19:04:59 435人浏览 独家记忆
摘要

一种技术的出现,要么是解决实际问题,要么是优化现有技术。数据库连接池技术的出现,是为了优化数据库连接操作的性能。在使用JDBC进行数据库开发的时候,一般经历这样一个过程:1)加载数据库的驱动2)建立数据库的

一种技术的出现,要么是解决实际问题,要么是优化现有技术。数据库连接池技术的出现,是为了优化数据库连接操作的性能。

在使用JDBC进行数据库开发的时候,一般经历这样一个过程:

1)加载数据库的驱动

2)建立数据库的连接(Connection)

3)创建sql语句声明(Statement)

4)执行更新(executeUpdate)或查询(executeQuery)


本文中讲的数据库连接池,只是针对Connection的部分的优化。


学习连接池

a. 自定义一个连接池

b. 学习优秀的连接池组件

1)DBCP

2)C3P0



1、引入

思考:程序中Connection连接是如何管理的?

数据库的连接(Connection)涉及到的操作有:a)数据库操作开始,创建连接,b)操作结束,关闭连接。

我们知道连接资源十分宝贵,因此需要对它进行管理。如果频繁的打开和关闭连接,会影响程序的运行效率!

连接管理的思路:预先创建一组连接,用的时候每次取出一个;用完之后,将连接放回去。





2、自定义连接池

第一个版本

package com.rk.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;



public class MyConnectionPool
{
		private final static String url = "jdbc:Mysql:///testdb";
		private final static String driverClassName = "com.mysql.jdbc.Driver";
		private final static String user = "root";
		private final static String passWord = "root";
		
		private LinkedList<Connection> freeConnections = null;//// 连接池 (存放所有的初始化连接)
		private final int init_count = 3;//初始的连接(Connection)数量(最小值)
		private final int max_count = 6;//最大的连接数量(最大值)
		private int current_count = 0;//当前拥有的连接数量(当前值)
		

		
		static 
		{
			try
			{
				Class.forName(driverClassName);
			}
			catch (ClassNotFoundException e)
			{
				throw new RuntimeException(e);
			}
		}
		
		//1.初始化
		public MyConnectionPool() throws SQLException
		{
			//初始化连接池
			freeConnections = new LinkedList<Connection>();
			//将指定数量(init_cont)加入到连接池中
			for(int i=0;i<init_count;i++)
			{
				// 记录当前连接数目
				current_count++;
				// 创建连接对象
				Connection con = createConnection();
				// 把连接加入连接池
				freeConnections.add(con);
				
			}
		}
		
		public int getCurrentCount()
		{
			return current_count;
		}
		
		
		private static Connection createConnection() throws SQLException
		{
			return DriverManager.getConnection(url, user, password);
		}
		//2.获取数据库的连接
		public Connection getConnection() throws SQLException
		{
			//判断连接池中是否有连接, 如果有连接,就直接从连接池取出
			if(freeConnections.size()>0)
			{
				return freeConnections.removeFirst();
			}
			else if(current_count<max_count)//连接池中没有连接;并且如果没有达到最大连接数,创建新连接;
			{
				current_count++;
				return createConnection();
			}
			else//当前已经达到最大连接数
			{
				System.out.println("已经达到连接最大数量限制,无法获得新的连接");
				return null;
			}
		}
		
		//3.释放连接
		public void releaseConnection(Connection conn) throws SQLException
		{
			//池的数目如果小于初始化连接,就放入池中
			if(freeConnections.size() < init_count)
			{
				freeConnections.addLast(conn);
			}
			else
			{//关闭
				current_count--;
				conn.close();
			}
		}
		public static void main(String[] args) throws SQLException
		{
			
			
			MyConnectionPool pool = new MyConnectionPool();
			System.out.println("0-->" + pool.getCurrentCount());
			Connection conn1  = pool.getConnection();
			Connection conn2  = pool.getConnection();
			Connection conn3  = pool.getConnection();
			System.out.println("3-->" + pool.getCurrentCount());
			pool.releaseConnection(conn1);
			Connection conn4  = pool.getConnection();
			System.out.println("4-->" + pool.getCurrentCount());
		}
}


第二个版本(在创建Connection时,增加了动态代理)


如果开发人员得到Connection对象时,并调用它的close方法,并不会关闭连接,而是将Connection对象放回到连接池中,这就是通过动态代理来实现的。

如果对某个接口中的某个指定的方法的功能进行扩展,而不想实现接口里所有方法,可以使用(动态)代理模式! 使用动态代理,可以监测接口中方法的执行!

Java中代理模式:静态/动态/Cglib代理(spring)


如何对Connection对象,生成一个代理对象:

|--Proxy

static Object newProxyInstance(

            ClassLoader loader,    当前使用的类加载器

            Class<?>[] interfaces,   目标对象(Connection)实现的接口类型

            InvocationHandler h    事件处理器:当执行上面接口中的方法的时候,就会自动触发事件处理器代码,把当前执行的方法(method)作为参数传入。

)  

package com.rk.pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;



public class MyConnectionPoolUpdate
{
		private final static String url = "jdbc:mysql:///testdb";
		private final static String driverClassName = "com.mysql.jdbc.Driver";
		private final static String user = "root";
		private final static String password = "root";
		
		private LinkedList<Connection> freeConnections = null;//// 连接池 (存放所有的初始化连接)
		private final int init_count = 3;//初始的连接(Connection)数量(最小值)
		private final int max_count = 6;//最大的连接数量(最大值)
		private int current_count = 0;//当前拥有的连接数量(当前值)
		

		
		static 
		{
			try
			{
				Class.forName(driverClassName);
			}
			catch (ClassNotFoundException e)
			{
				throw new RuntimeException(e);
			}
		}
		
		//1.初始化
		public MyConnectionPoolUpdate() throws SQLException
		{
			//初始化连接池
			freeConnections = new LinkedList<Connection>();
			//将指定数量(init_cont)加入到连接池中
			for(int i=0;i<init_count;i++)
			{
				// 记录当前连接数目
				current_count++;
				// 创建连接对象
				Connection con = createConnection();
				// 把连接加入连接池
				freeConnections.add(con);
				
			}
		}
		
		public int getCurrentCount()
		{
			return current_count;
		}
		
		
		private Connection createConnection() throws SQLException
		{
			final Connection conn = DriverManager.getConnection(url, user, password);
			Connection proxy = (Connection)Proxy.newProxyInstance(
										//conn.getClass().getClassLoader();// 第一个参数,类加载器(方法一)
										Connection.class.getClassLoader(), // 第一个参数,类加载器(方法二)
										//conn.getClass().getInterfaces(),//第二个参数,如果当前目标对象是一个具体的类的时候,用这个
										new Class[]{Connection.class}, //第二个参数,由于Connection是一个接口
										new InvocationHandler()
										{
											@Override
											public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
											{
												// 方法返回值
												Object result = null;
												System.out.println(proxy.getClass());
												// 当前执行的方法的方法名
												String methodName = method.getName();
												if("close".equals(methodName))
												{
													System.out.println("begin:当前执行close方法开始!");
													// 调用释放连接池的方法
													MyConnectionPoolUpdate.this.releaseConnection(conn);
													System.out.println("end: 当前连接已经放入连接池了!");
												}
												else
												{
													// 调用目标对象方法
													result = method.invoke(conn, args);
												}
												return result;
											}
										});
			return proxy;
		}
		//2.获取数据库的连接
		public Connection getConnection() throws SQLException
		{
			//判断连接池中是否有连接, 如果有连接,就直接从连接池取出
			if(freeConnections.size()>0)
			{
				return freeConnections.removeFirst();
			}
			else if(current_count<max_count)//连接池中没有连接;并且如果没有达到最大连接数,创建新连接;
			{
				current_count++;
				return createConnection();
			}
			else//当前已经达到最大连接数
			{
				System.out.println("已经达到连接最大数量限制,无法获得新的连接");
				return null;
			}
		}
		
		//3.释放连接
		public void releaseConnection(Connection conn) throws SQLException
		{
			System.out.println("MyConnectionPoolUpdate.releaseConnection()");
			//池的数目如果小于初始化连接,就放入池中
			if(freeConnections.size() < init_count)
			{
				freeConnections.addLast(conn);
			}
			else
			{//关闭
				current_count--;
				conn.close();
			}
		}
		public static void main(String[] args) throws SQLException
		{
			MyConnectionPoolUpdate pool = new MyConnectionPoolUpdate();
			System.out.println("0-->" + pool.getCurrentCount());
			Connection conn1  = pool.getConnection();
			conn1.close();
		}
}





3、开源的连接池技术

Sun公司约定: 如果是连接池技术,需要实现一个接口:javax.sql.DataSource!

注意,DataSource位于javax.sql包下,而不是java.sql包下,其中x表示扩展的意思。

下面是DataSource的源代码,它只提供了两个重载方法getConnection。

public interface DataSource  extends CommonDataSource,Wrapper {

  
  Connection getConnection() throws SQLException;

  
  Connection getConnection(String username, String password)
    throws SQLException;

}


javax.sql.DataSource接口

(1)DataSource object是对DriverManager的一种替代。推荐使用DataSource来获得Connection对象。

An alternative to the DriverManager facility, a DataSource object is the preferred means of getting a connection.

(2)实现DataSource接口的object常常是用JNDI注册的。

 An object that implements the DataSource interface will typically be reGIStered with a naming service based on the JavaTM Naming and Directory (JNDI) api.

(3)DataSource接口由driver vendor实现,有三种实现类型:

3.1)Basic implementation:基本实现,产生标准的Connection对象

3.2)Connection pooling implementation:连接池实现,有connection pool,由a middle-tier connection pooling manager

3.3)Distributed transaction implementation:分布式事务实现,由a middle-tier transaction manager和a connection pooling manager.


The DataSource interface is implemented by a driver vendor. There are three types of implementations:

a)Basic implementation -- produces a standard Connection object

b)Connection pooling implementation -- produces a Connection object that will automatically participate in connection pooling. This implementation works with a middle-tier connection pooling manager.

c)Distributed transaction implementation -- produces a Connection object that may be used for distributed transactions and almost always participates in connection pooling. This implementation works with a middle-tier transaction manager and almost always with a connection pooling manager.


(4)通过DataSource获得的driver,并不会用DriverManager注册。

回忆一下:DriverManager可以注册驱动。

        Driver driver = new com.mysql.jdbc.Driver();

        //注册驱动程序(可以注册多个驱动程序)

        DriverManager.registerDriver(driver);



A driver that is accessed via a DataSource object does not register itself with the DriverManager. Rather, a DataSource object is retrieved though a lookup operation and then used to create a Connection object.

(5)如果是basic implementation,那么通过DataSource object获得的Connection与通过DriverManager获得的Connection是一样的。

With a basic implementation, the connection obtained through a DataSource object is identical to a connection obtained through the DriverManager facility.



3.1、DBCP连接池

DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:

Commons-dbcp.jar:连接池的实现

Commons-pool.jar:连接池实现的依赖库

Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

核心类:BasicDataSource

使用步骤

引入jar文件

commons-dbcp-1.4.jar  

Http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi

commons-pool-1.6.jar  

http://commons.apache.org/proper/commons-pool/download_pool.cgi


使用连接池,创建连接

a) 硬编码方式

b) 配置方式(db.properties)

package com.rk.dbcp;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

public class Demo
{
	// 1. 硬编码方式实现连接池
	@Test
	public void testDbcp() throws SQLException
	{
		// DBCP连接池核心类
		BasicDataSource dataSource = new BasicDataSource();
		// 连接池参数配置: 连接字符串、驱动、用户、密码
		dataSource.setUrl("jdbc:mysql:///testdb");//"jdbc:mysql://localhost:3306/testdb"
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUsername("root");
		dataSource.setPassword("root");
		// 连接池参数配置:初始化连接数、最大连接数 、最大空闲时间
		dataSource.setInitialSize(3);
		dataSource.setMaxActive(6);
		dataSource.setMaxIdle(3000);
		
		// 获取连接
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
		// 关闭
		conn.close();
	}
	
	@Test
	// 2. 【推荐】配置方式实现连接池  ,  便于维护
	public void testProp() throws Exception
	{
		// 获取文件流
		InputStream inStream = Demo.class.getResourceAsStream("db.properties");
		//创建Properties对象
		Properties prop = new Properties();
		// 加载属性配置文件
		prop.load(inStream);
		
		// 根据prop配置,直接创建数据源对象
		DataSource dataSource = BasicDataSourceFactory.createDataSource(prop);
		// 获取连接
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
		// 关闭
		conn.close();
	}
}



配置方式实现DBCP连接池,  配置文件中的key与BaseDataSouce中的属性一样:

url=jdbc:mysql://localhost:3306/testdb
driverClassName=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=6
maxIdle=3000


org.apache.commons.dbcp.BasicDataSource

(1)Apache的DBCP中的核心类BasicDataSource是对javax.sql.DataSource接口的Basic implementation。

Basic implementation of javax.sql.DataSource that is configured via JavaBeans properties.



org.apache.commons.dbcp.BasicDataSourceFactory

(1)BasicDataSourceFactory是一个JNDI object factory,用于创建一个BasicDataSource实例。

JNDI object factory that creates an instance of BasicDataSource.



3.2、C3P0连接池

C3P0连接池:最常用的连接池技术!Spring框架,默认支持C3P0连接池技术!

核心类:ComboPooledDataSource

使用:


  1.下载,引入jar文件:  c3p0-0.9.1.2.jar 

https://sourceforge.net/projects/c3p0/

http://www.mchange.com/projects/c3p0/


2. 使用连接池,创建连接

a) 硬编码方式

b) 配置方式(xml)

package com.rk.c3p0;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

import org.junit.Test;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class Demo
{
	@Test
	//1. 硬编码方式,使用C3P0连接池管理连接
	public void testCode() throws PropertyVetoException, SQLException
	{
		// 创建连接池核心工具类
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		// 设置连接参数:url、驱动、用户密码、初始连接数、最大连接数
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setUser("root");
		dataSource.setPassword("root");
		dataSource.setInitialPoolSize(3);
		dataSource.setMaxPoolSize(6);
		dataSource.setMaxIdleTime(1000);
		
		// ---> 从连接池对象中,获取连接对象
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
		// 关闭
		conn.close();
	}
	
	@Test
	//2. XML配置方式,使用C3P0连接池管理连接
	public void testXML() throws SQLException
	{
		// 创建c3p0连接池核心工具类
		// 自动加载src下c3p0的配置文件【c3p0-config.xml】
		ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用默认的配置
		//ComboPooledDataSource dataSource = new ComboPooledDataSource("oracle_config");// 使用指定的配置
		
		// ---> 从连接池对象中,获取连接对象
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
		// 关闭
		conn.close();
	}
}

c3p0-config.xml文件放在src目录下

<c3p0-config>
	<default-config>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="initialPoolSize">3</property>
		<property name="maxPoolSize">6</property>
		<property name="maxIdleTime">1000</property>
	</default-config>


	<named-config name="oracle_config">
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="initialPoolSize">3</property>
		<property name="maxPoolSize">6</property>
		<property name="maxIdleTime">1000</property>
	</named-config>


</c3p0-config>


您可能感兴趣的文档:

--结束END--

本文标题: 开源组件:(1)DBCP和C3P0

本文链接: https://lsjlt.com/news/37821.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 开源组件:(1)DBCP和C3P0
    一种技术的出现,要么是解决实际问题,要么是优化现有技术。数据库连接池技术的出现,是为了优化数据库连接操作的性能。在使用JDBC进行数据库开发的时候,一般经历这样一个过程:1)加载数据库的驱动2)建立数据库的...
    99+
    2024-04-02
  • 开源组件:(3)dbutils
    commons-dbutils 是 Apache 组织提供的一个开源JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢...
    99+
    2023-01-31
    开源 组件 dbutils
  • Android开源组件小结
    前言       Android自带的组件比较丑陋(个人感觉),自己写组件比较复杂,而且必须熟悉android应用层开发的一些...
    99+
    2022-06-06
    小结 Android
  • jQuery开源组件BootstrapValidator怎么用
    这篇文章主要介绍了jQuery开源组件BootstrapValidator怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体内容如下表...
    99+
    2024-04-02
  • Flex中FlexReport开源组件如何使用
    这期内容当中小编将会给大家带来有关Flex中FlexReport开源组件如何使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Flex中FlexReport开源组件用法FlexReport开源组件是基于L...
    99+
    2023-06-17
  • J2EE中使用Spring AOP框架和EJB组件(1)
    J2EE中使用Spring AOP框架和EJB组件(1)[@more@]本文介绍了一种把J2EE应用程序中的EJB转换为Spring托管组件的方法,以及转换之后可以采用的强大技术。它还给出了几个实际的例子,说明如何借助于Spring的AOP...
    99+
    2023-06-03
  • 开源日志记录组件Log4Net怎么用
    这篇文章主要介绍了开源日志记录组件Log4Net怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。log4net是.Net下一个非常优秀的开源日志记录组件。log4net记...
    99+
    2023-06-03
  • Vite + React从零开始搭建一个开源组件库
    目录前言目标搭建开发环境️生成模板CSS预处理器eslint组件库编译⚡️vite的打包配置⚡️自动生成ts类型文件⚡️样式懒加载与全量加载文档❤️npm 发布与发布前的测试测试前言...
    99+
    2024-04-02
  • vue3新拟态组件库开发流程之table组件源码分析
    目录基础表格固定表头高度/流体高度自定义列宽基础表格 首先开发table组件之前,先想好要用什么样式的api,因为笔者在生产工作中用的都是element,所以前面几个组件风格和ele...
    99+
    2023-05-18
    vue3 table组件源码 vue3组件库开发
  • .NET 开源配置组件 AgileConfig的使用简介
    目录介绍架构图部署服务端在客户端程序使用总结介绍 在微服务大行其道的今天,系统会被拆分成多个模块,作为单独的服务运行,同时为了集中化管理,我们还需要日志中心,配置中心等,很多开发人...
    99+
    2024-04-02
  • Flex开源组件怎么显示各种文档
    这篇文章给大家分享的是有关Flex开源组件怎么显示各种文档的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Flex开源组件显示各种文档教程FlexPaper是一个开源轻量级的在浏览器上显示各种文档的组件,被设计用来...
    99+
    2023-06-17
  • 鸿蒙开源第三方组件之连续滚动图像组件功能
    目录前言背景组件效果展示Sample解析Library解析前言 基于安卓平台的连续滚动图像组件ContinuousScrollableImageView(https://github...
    99+
    2024-04-02
  • VUE 单文件组件:社区资源和支持指南
    作为一名 Vue.js 开发人员,了解可用于支持单文件组件开发的社区资源和指南至关重要。本文列出各种资源,包括论坛、文档、教程和示例,以帮助您充分利用单文件组件。 论坛 Vue.js 官方论坛:https://forum.vuejs.o...
    99+
    2024-04-02
  • Android开源组件SlidingMenu侧滑菜单使用介绍
    现在很多android应用都有侧滑菜单,效果很不错。 GitHub上有SlidingMenu的开源库,使用起来很方便。 SlidingMenu GitHub地址:https:/...
    99+
    2022-06-06
    菜单 slidingmenu Android
  • Vue组件事件触发和监听实现源码解析
    目录正文Vue 的事件触发和监听源码分析ononceemitoff动手实现总结正文 通常我们在使用Vue的时候,会使用$emit和$on来触发和监听事件,但是有没有思考是如何实现的呢...
    99+
    2022-12-22
    Vue 组件事件触发监听 Vue 组件触发监听
  • 开源组件:(4)用元数据和BeanUtils写一个简单的ORM映射BaseDAO.java
    1、JDBC元数据 在jdbc中可以使用: 数据库元数据、参数元数据、结果集元数据(1)数据库元数据通过Connection对象的getMetaData() 方法可以得到DatabaseMetaData对象...
    99+
    2024-04-02
  • 《基于 Vue 组件库 的 Webpack5 配置》1.模式 Mode 和 vue-loader
    一定要配置 模式 Mode,这里有个小知识点,环境变量 process.env.NODE_ENV module.exports = { mode: 'production',// process.env.NODE_ENV 或 dev...
    99+
    2023-08-30
    vue.js 前端 webpack
  • PHP框架的生态系统:开源组件、第三方库、资源指南
    php框架生态系统包括开源组件、第三方库和丰富资源,其中开源组件包含twig、doctrine和symfony bundle,而第三方库涵盖monlog、intervention ima...
    99+
    2024-05-23
    框架 php git iphone overflow
  • 怎么使用Vite+React搭建一个开源组件库
    今天小编给大家分享一下怎么使用Vite+React搭建一个开源组件库的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。目标开发环...
    99+
    2023-07-02
  • 【微信小程序 | 实战开发】常用的视图容器类组件介绍和使用(1)
    个人名片: 🐼作者简介:一名大二在校生,喜欢编程🎋 🐻‍❄️个人主页🥇:小新爱学习. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️ 零基础学Java——小白入门必备 重...
    99+
    2023-09-03
    微信小程序 小程序 visualstudio html 前端
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作