JDBC与数据库连接池
JDBC
1 JDBC概述
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API)
简单理解为:JDBC,是SUN提供的一套 API,使用这套API可以实现对具体数据库的操作(获取连接、关闭连接、DML、DDL、DCL)
数据库的驱动:数据库厂商针对于JDBC这套接口,提供的具体实现类的集合。
面向接口编程的思想:
- JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
- 不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。
数据库创建:
CREATE DATABASE `jdbcstudy` /*!40100 DEFAULT CHARACTER SET utf8 */; -- ---------------------------- -- Table structure for account -- ---------------------------- DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `money` int(255) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for photos -- ---------------------------- DROP TABLE IF EXISTS `photos`; CREATE TABLE `photos` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `photo` longblob, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for users -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(11) NOT NULL, `NAME` varchar(40) DEFAULT NULL, `PASSWORD` varchar(40) DEFAULT NULL, `email` varchar(60) DEFAULT NULL, `birthday` date DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
通过Driver接口获取数据库连接:
准备工作:
Driver是一个接口,数据库厂商必须提供实现的接口,从而获取到数据库连接,可以通过Driver的实现类的对象获取连接
加入mysql驱动
- 方法一:导入jar包
- 下载并解压mysql-connector-java-5.1.18.zip
- 在当前目录下新建lib目录
- 把mysql-connector-java-5.1.18-bin.jar复制到lib目录
- 右键->build-path->add build path加载到类路径下
- 方法二:Maven配置
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
1、创建一个Driver实现类的对象
Driver driver=new com.mysql.jdbc.Driver();
2、准备连接数据库的基本信息,url,user,password
url=jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=false; String user = "root"; String password = "123456";
3、调用DriverManager.getConnection获取连接
Connection connection = DriverManager.getConnection(url, user, password);
4、关闭数据库连接
connection.close();
代码实现:
public class BestConnection { public static void main(String[] args) throws SQLException, ClassNotFoundException { // 1、加载驱动 DriverManager Class.forName("com.mysql.jdbc.Driver"); // 2、参数配置 String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&userSSL=false"; String username = "root"; String password = "123456"; // 3、连接 数据库对象 Connection 代表数据库 Connection connection = DriverManager.getConnection(url, username, password); // 4、数据库执行对象获取 Statement statement = connection.createStatement(); // 5、执行sql String sql = "select * from users"; ResultSet resultSet = statement.executeQuery(sql); while(resultSet.next()){ System.out.println(resultSet.getObject("id")); System.out.println(resultSet.getObject("NAME")); System.out.println(resultSet.getObject("PASSWORD")); System.out.println(resultSet.getObject("email")); System.out.println(resultSet.getObject("birthday")); System.out.println("========================================="); } // 6、释放资源 resultSet.close(); statement.close(); connection.close(); } }
连接数据库最佳方法:
上面这种方式不太理想,对于数据库的信息起不到保护作用!
首先,我们新建一个配置文件名字叫做jdbc.Properties(最好放在src下面,方便引用),里面添加我们连接数据库需要的信息
注意:配置文件中不要加空格,不要加双引号
url=jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=false username=root password=123456 driver=com.mysql.jdbc.Driver
完整代码:
public class BestConnection { public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException { // 1、获得类加载器 ClassLoader classLoader = BestConnection.class.getClassLoader(); // 2、读取配置文件信息 InputStream is = classLoader.getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); // 3、加载对应的输入流 properties.load(is); // 4、拿到配置文件的具体值,getProperty里的值必须和配置文件的一致 String user = properties.getProperty("user"); String url = properties.getProperty("url"); String password = properties.getProperty("password"); String driverClass = properties.getProperty("driverClass"); // 5、加载数据库驱动程序 Class.forName(driverClass); // 6、建立连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); // 7、关闭连接 connection.close(); } }
2 JDBCUtils
说明:
为了方便操作,将连接(关闭)数据库封装成一个工具类
/** * jdbc 工具类 */ public class JdbcUtil { /** * 参数定义 */ private static String driver = null; private static String url = null; private static String username = null; private static String password = null; static { try { InputStream is = JdbcUtil.class.getResourceAsStream("/db.properties"); Properties properties = new Properties(); properties.load(is); driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } /** * 获取链接对象 * @return */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } /** * 链接释放 * @param conn * @param st * @param rs */ public static void release(Connection conn, Statement st, ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(st != null){ try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
工具类调用:
public class TestSql { public static void main(String[] args) { login("'' or 1=1", "'' or 1=1"); } public static void login(String username, String password) { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); st = conn.createStatement(); String sql = "select * from users where `NAME` = " + username + " AND `password` = " + password; ResultSet resultSet = st.executeQuery(sql); while (resultSet.next()){ System.out.println(resultSet.getObject("NAME")); System.out.println(resultSet.getObject("PASSWORD")); System.out.println("==========================="); } } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, st, rs); } } }
PreparedStatement 优化SQL注入:
public class TestSql_PreparedStatement { public static void main(String[] args) { login("showger", "123456"); // login("'' or 1=1", "'' or 1=1"); } private static void login(String username, String password) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); // conn = JdbcUtil_DBCP.getConnection(); // conn = JdbcUtil_C3P0.getConnection(); // conn = JdbcUtil_Druid.getConnection(); String sql = "select * from users where `NAME` = ? AND `password` = ?"; st = conn.prepareStatement(sql); st.setString(1, username); st.setString(2, password); ResultSet resultSet = st.executeQuery(); while (resultSet.next()){ System.out.println(resultSet.getObject("NAME")); System.out.println(resultSet.getObject("PASSWORD")); System.out.println("==========================="); } } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, st, rs); } } }
3 使用PreparedStatement操作Blob(图片、音频等大型文件)类型的变量
插入blob类型数据到数据库:
public class TestPhotoInsert { public static void main(String[] args) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); String sql = "insert into photos(photo) values(?);"; st = conn.prepareStatement(sql); String projectPath = System.getProperty("user.dir"); System.out.println(projectPath); FileInputStream fis = new FileInputStream(projectPath + "\\src\\main\\resources\\java-php.jpg"); st.setObject(1, fis); boolean result = st.execute(); if(result){ System.out.println("数据插入成功!"); } } catch (SQLException | FileNotFoundException e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, st, rs); } } }
查询数据库的Blob数据:
public class TestPhotoGet { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; InputStream is = null; FileOutputStream fos = null; try { conn = JdbcUtil.getConnection(); String sql = "select `id`, `photo` from `photos`;"; ps = conn.prepareStatement(sql); ResultSet resultSet = ps.executeQuery(); if(resultSet.next()){ int id = resultSet.getInt("id"); System.out.println("id === " + id); Blob photo = resultSet.getBlob("photo"); is = photo.getBinaryStream(); String projectPath = System.getProperty("user.dir"); fos = new FileOutputStream(projectPath + "\\src\\main\\resources\\java-php-"+id+".jpg"); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1){ fos.write(buffer, 0, len); } } System.out.println("图片数据读取成功!"); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } JdbcUtil.release(conn, ps, rs); } } }
注意:
数据库,默认max_allowed_packet=4M,更根据你文件大小进行增加。
blob类型不够的话,使用longblob可以解决,xxx too long 的问题
开发中使用PreparedStatement替换Statement,Statement存在SQL注入问题
4 数据库事务
事务:
一组逻辑操作单元,使数据从一种状态变换到另一种状态。
一组逻辑操作单元:
一个或多个DML操作。
事务处理的原则:
保证所事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
哪些操作会导致数据的自动提交?
DDL操作一旦执行,都会自动提交。set autocommit = false 对DDL操作失效
DML默认情况下,一旦执行,就会自动提交。我们可以通过set autocommit = false的方式取消DML操作的自动提交。
默认在关闭连接时,会自动的提交数据
代码实现:
public class TestTransation { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); // 关闭自动提交事务 conn.setAutoCommit(false); String sql1 = "update account set money = money - 100 where name = 'xiaogang'"; ps = conn.prepareStatement(sql1); ps.executeUpdate(); String sql2 = "update account set money = money + 100 where name = 'showger'"; ps = conn.prepareStatement(sql2); ps.executeUpdate(); // 提交事务 conn.commit(); System.out.println("业务成功!"); } catch (SQLException e) { try { // 回滚 (默认) conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { JdbcUtil.release(conn, ps, rs); } } }
5 通用的增删改查
实体定:
public class User { private int id; private String name; private String password; private String email; private Date birthday; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", birthday=" + birthday + '}'; } }
通用的增删改:
public static void CommonCRD(String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; try { // 1、 建立数据库连接 conn = JdbcUtil.getConnection(); // 2、预编译sql语句 ps = conn.prepareStatement(sql); // 3、占位符填充参数 for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } // 4、执行 ps.execute(); System.out.println("操作成功!"); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, ps); } }
测试代码:
String updateSql = "UPDATE users SET `name` = ?, `password` = ?, `email` = ?, `birthday` = ? WHERE id = ?"; CommonUtil.CommonCRD(updateSql, "xiaogang", "999999", "999999@qq.com", "2024-02-02", 1);
通用的返回表中所有数据:
public static <T> List<T> commonGetList(Class<T> clazz, String sql, Object... args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; List<T> dataList = null; try { // 1、 建立数据库连接 conn = JdbcUtil.getConnection(); // 2、预编译sql语句 ps = conn.prepareStatement(sql); // 3、占位符填充参数 for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } // 4、执行并且获得结果集 rs = ps.executeQuery(); // 5、获取结果集元数据 ResultSetMetaData rsmd = rs.getMetaData(); // 6、获取结果集字段数量 int columnCount = rsmd.getColumnCount(); dataList = new ArrayList<>(); while(rs.next()){ T t = clazz.newInstance(); // 8、处理结果集一行数据的每一个字段 for (int i = 0; i < columnCount; i++) { int columNum = i + 1; // 9、获取列值 Object columValue = rs.getObject(columNum); // 10、获取列名 String columName = rsmd.getColumnClassName(columNum); String columName2 = rsmd.getColumnName(columNum); String columnLabel = rsmd.getColumnLabel(columNum); // 11、赋值:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columValue); } dataList.add(t); } System.out.println("查询成功!"); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, ps, rs); } return dataList; }
测试代码:
String querySql = "select * from users"; List<User> users = CommonUtil.commonGetList(User.class, querySql); for (User user : users) { if(user != null){ System.out.println(user.toString()); } }
通用的单条查询:
public static <T> T commonGetOne(Class<T> clazz, String sql, Object... args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1、 建立数据库连接 conn = JdbcUtil.getConnection(); // 2、预编译sql语句 ps = conn.prepareStatement(sql); // 3、占位符填充参数 for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } // 4、执行并且获得结果集 rs = ps.executeQuery(); // 5、获取结果集元数据 ResultSetMetaData rsmd = rs.getMetaData(); // 6、获取结果集字段数量 int columnCount = rsmd.getColumnCount(); if(rs.next()){ T t = clazz.newInstance(); // 8、处理结果集一行数据的每一个字段 for (int i = 0; i < columnCount; i++) { int columNum = i + 1; // 9、获取列值 Object columValue = rs.getObject(columNum); // 10、获取列名 String columnLabel = rsmd.getColumnLabel(columNum); // 11、赋值:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columValue); } return t; } System.out.println("查询成功!"); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.release(conn, ps, rs); } return null; }
测试代码:
String oneSql = "select * from users where id = ?"; User user = CommonUtil.commonGetOne(User.class, oneSql, 1); if(user != null){ System.out.println(user.toString()); }
6 数据库连接池
数据库:
在每一次请求数据库都要经历上述过程,创建连接和释放资源也都是些重复性的动作,当请求量比较大时,资源是个很大的浪费。
假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。
连接池:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
几种常用的数据库连接池:
1、DBCP(Database Connection Pool)
是一个依赖Jakarta commons-pool对象池机制的数据库连接池,Tomcat的数据源使用的就是DBCP。
2、C3P0
是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象,sql执行效率较低。
3、Proxool
是一个Java SQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能,在激烈并发时会抛异常,完全不靠谱。
4、BoneCP
是一个开源的快速的 JDBC 连接池。BoneCP很小,只有四十几K
5、Druid
是阿里提供的数据库连接池,据说是集DBCP、C3P0、Proxool优点于一身的连接池,性能最好的数据库连接池
6.1 DBCP数据库连接池
准备工作 :
在项目src目录下创建dbcp.properties配置文件
# 连接配置 driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=false username=root password=123456 #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true; characterEncoding=utf8 #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED
需要引用的包 commons-dbcp-1.4.jar commons-pool-1.6.jar
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-pool/commons-pool --> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency>
通过DBCP封装连接数据库的方法:
public class JdbcUtil_DBCP { /** * 创建一个DBCP的数据库连接池 */ private static DataSource dataSource; static { try { InputStream is = JdbcUtil_DBCP.class.getResourceAsStream("/dbcp.properties"); Properties properties = new Properties(); properties.load(is); // 创建数据源 工厂模式 dataSource = BasicDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取链接对象 * @return */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }
6.2 C3P0数据库连接池
准备工作 :
在项目src目录下创建c3p0-config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- c3p0的缺省设置 如果在代码中 ComboPooledDataSource ds = new ComboPooledDataSource() 这样表示的使用c3p0的默认设置 --> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="maxPoolSize">20</property> <property name="minPoolSize">5</property> </default-config> <!-- c3p0的命名设置 如果在代码中 ComboPooledDataSource ds = new ComboPooledDataSource("mysql") 这样表示的使用c3p0的命名设置 name 与 代码中名称对应 --> <named-config name="MYSQL"> <!-- 提供获取连接的4个基本信息 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- & ==> & --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true</property> <property name="user">root</property> <property name="password">123456</property> <!-- 进行数据库连接池管理的基本信息 --> <!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 --> <property name="acquireIncrement">5</property> <!-- c3p0数据库连接池中初始化时的连接数 --> <property name="initialPoolSize">10</property> <!-- c3p0数据库连接池维护的最少连接数 --> <property name="minPoolSize">10</property> <!-- c3p0数据库连接池维护的最多的连接数 --> <property name="maxPoolSize">100</property> <!-- c3p0数据库连接池最多维护的Statement的个数 --> <property name="maxStatements">50</property> <!-- 每个连接中可以最多使用的Statement的个数 --> <property name="maxStatementsPerConnection">2</property> </named-config> </c3p0-config>
导入c3p0的包
<!-- https://mvnrepository.com/artifact/com.mchange/mchange-commons-java --> <dependency> <groupId>com.mchange</groupId> <artifactId>mchange-commons-java</artifactId> <version>0.2.19</version> </dependency>
通过C3P0封装连接数据库的方法:
public class JdbcUtil_C3P0 { /** * 数据源配置 */ private static ComboPooledDataSource dataSource = null; static { try { // 与配置文件名称对应 dataSource = new ComboPooledDataSource("MYSQL"); } catch (Exception e) { e.printStackTrace(); } } /** * 获取链接对象 * @return */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }
6.3 Druid数据库连接池
准备工作:
创建druid.properties:
url=jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=false username=root password=123456 driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=10
导入Druid的jar包
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency>
测试代码:
/** * jdbc 工具类 */ public class JdbcUtil_Druid { /** * 数据源配置 */ private static DataSource dataSource = null; static { try { InputStream is = JdbcUtil_DBCP.class.getResourceAsStream("/druid.properties"); Properties properties = new Properties(); properties.load(is); // 创建数据源 工厂模式 dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取链接对象 * @return */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)