一周过去了,我在这分享一下这一周来学习 JDBC 的知识,同时也希望可以帮到别人!

首先我们从获取 JDBC 连接开始

Driver(每个驱动程序类必须实现的接口)

获取数据库连接需要配置数据库连接信息,DriverClass 表示数据库驱动,user 表示数据库登录用户名,passWord 表示登录密码,url 用于标识一个被注册的驱动程序,驱动程序管理器通过 URL 选择正确的驱动程序,从而建立数据库连接

Oracle URL:jdbc:oracle:thin:@localhost:1521:数据库名

SQLServer URL:jdbc:microsoft:sqlserver//localhost:1433;DatabaseName=数据库名

MySQL URL:jdbc:mysql://localhsot:3306/数据库名;如果你的mysql 数据库默认端口没有改变其 URL 可以简写为 jdbc:mysql:///数据库名

下面就是获取数据库连接的代码:

package com.java.jdbc.test;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties; import org.junit.Test; public class TestConnection { // DriverClass 利用不通的构造器去创建一个对象
@Test
public void testDriver() throws SQLException {
// 连接数据 Mysql
Driver driver = new com.mysql.jdbc.Driver();
// 准备数据库连接信息
String url = "jdbc:mysql://localhost:3306/sh_db"; Properties info = new Properties();
// 我们除了可以利用 put 方法将连接信息存入 properties 对象,还可以利用 setProperty 去设置属性
// 用户名
info.put("user", "root");
// 密码
info.put("password", "zy961029"); Connection connection = driver.connect(url, info);
// 打印数据库连接信息
System.out.println(connection);
}
}

上面的代码是最基本的连接数据库的实现,但是我们要使用上面的代码去实现连接不同的数据库的时我们就需要去改变源代码中的数据库信息,这样做肯定是不方便,且容易出错的,所以我们接下来实现利用外部配置文件的去实现获取数据库连接

package com.java.jdbc.test;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties; import org.junit.Test; public class TestConnection { // 此方法具有普遍性,在本方法中没有和任何数据库厂商相连接,只需要改变配置文件即可达到连不同的数据库
public Connection getConnection() throws Exception {
String driverClass = null;
String url = null;
String user = null;
String password = null; InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties"); // 通过反射获得配置文件,用 properties 类来获取配置文件的属性
Properties properties = new Properties();
properties.load(in);
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password"); Driver driver = (Driver) Class.forName(driverClass).newInstance(); Properties info = new Properties();
info.put("user", user);
info.put("password", password); Connection connection = driver.connect(url, info); return connection;
}
} jdbc.properties
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/sh_db
user=root
password=zy961029

DriverManager

管理一组 JDBC 驱动程序的基本服务,可以通过重载的 getConnection() 获取连接更加方便,可以同时管理多个驱动程序,若注册了多个数据库驱动,只需要给 getConnection 方法传入不同的参数即可,下面是利用 DriverManager 获取数据库连接

@Test
public void getConnection() {
// 通过反射获取配置文件
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
try {
// 获取属性属性值
properties.load(inputStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driverClass"); // 加载数据库驱动类(注册驱动)
// 注册驱动本应如下注册,但在 com.mysql.Driver 中的静态代码块已经将其注册了,所以不需在写一遍
// DriverManager.registerDriver(Class.forName(driver).newInstance());
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.printf(String.valueOf(connection));
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} }

通过上面的介绍我们现在获取到了数据库的连接,那么接下来就是操作数据库(增删改查,首先利用 Statement,使用完毕需要释放)

@Test
public void testInser() {
// 获取数据库连接
Connection connection = getConnectionMy();
// statement 对象是操作 sql 语句的对象
Statement statement = null;
try {
// 获取 statement 对象
statement = connection.createStatement(); String sql = null;
// sql = "INSERT INTO book (BOOK_NAME, ISBN, PRICE, STOCK) VALUES ('SQLServer', '1004', '150', '30' )";
// sql = "DELETE FROM book WHERE id=4";
sql = "UPDATE book SET PRICE=400 WHERE id=5";
// 执行 sql 语句
statement.execute(sql);
System.out.printf("Success");
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
// 关闭连接以及 statement
if (statement != null) {
statement.close();
} if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

上面的插入操作我们看到其所需要的 sql 语句需要的是完整的,当我们插入的值非常多的时候这样拼写 sql 语句就显得有点不适合,所以我们需要去学习 PrepareStatement,它可以用 ? 代表插入值,以及更新和删除操作需要传入的参数,同时也需要利用 setXxx 方法去为每一个 ? 赋值

     @Test
public void testUpdateWithPrepare() {
Connection connection;
PreparedStatement preparedStatement = null; connection = JDBCTools.getConnection(); String sql = "INSERT INTO book (BOOK_NAME, ISBN, PRICE, STOCK) VALUES (?, ?, ?, ?)"; try {
// 获得 prepareStatement 对象
preparedStatement = connection.prepareStatement(sql);
// 为每一列赋值,需要传入下标,从 1 开始
preparedStatement.setString(1, "C#");
preparedStatement.setString(2, "1008");
preparedStatement.setString(3, "320");
preparedStatement.setInt(4, 120);
// 执行更新操作
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCTools.releaseUpdate(preparedStatement, connection);
}

接下来我们介绍如何进行查的操作,首先需要了解 ResultSet 接口(使用完毕需要释放资源)

ResultSet 封装了 JDBC查询的结果集,并返回一张数据表,并有一个指针指向数据表的第一行,我们调用 next() 方法检测下一行是否有效,若为 true 则下移,我们可以利用 getXxx() 方法获取每一行对应的值

@Test
public void testSelect() {
Connection connection = getConnectionMy();
Statement statement = null;
String id = null;
String bookName = null;
String isbn = null;
// 执行查询操作的对象
ResultSet resultSet = null;
String sql = null; try {
sql = "SELECT BOOK_NAME, ISBN, PRICE, STOCK from book WHERE id=5";
// 获取 statement 对象
statement = connection.createStatement();
// 执行查询操作
resultSet = statement.executeQuery(sql); // 处理 resultSet,首先需要判断其是否
if (resultSet.next()) {
// 获得对应的列的值
id = resultSet.getString(1);
bookName = resultSet.getString(2);
isbn = resultSet.getString(3);
} System.out.printf(id + "; " + bookName + "; " + isbn);
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
} if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

通过上面的方法我们可以体会到每次关闭连接以及以后每次进行相应的操作的时候我们不可能像这样每次都写完整的代码,我们应该像封装获取连接的函数一样去封装其其他方法,写为一个工具类

package jdbc.example.test.myself;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties; public class JDBCTools { /*
* 关闭数据库连接资源
* */
public static void release(Statement statement, Connection connection, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} /*
* 获取数据库连接
* */
public static Connection getConnection() {
String user;
String password;
String url; Connection connection = null; InputStream inputStream = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
try {
properties.load(inputStream); user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url"); connection = DriverManager.getConnection(url, user, password);
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} return connection;
} /*
* 执行数据库操作,除查询操作
* */
public static void update(String sql) {
Connection connection;
Statement statement = null; connection = getConnection();
try {
statement = connection.createStatement();
statement.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
} finally {
releaseUpdate(statement, connection);
}
}
}

现在我们的工具类中并没有通用的查询方法,为了完善我们的工具类我们需要学习 JavaBean 和 ResultSetMetaData

JavaBean 其实就是普通的 java 类,不同的是没有 main 方法,只包含变量和对应的 set、get 方法,数据表对应的类就需要用 JavaBean 去写,其变量名对应数据表的列名,若列名为两个单词那么对应的变量名的第二个单词需大写,如:book_name --> bookName,isbn --> isbn

ResultSetMetaData 是描述 ResultSet 元数据的接口,它可以获取到结果集有多少列,以及列名和列的别名

我们都已经知道 ResultSet 返回的是一张数据表,如果我们还像以前那样在方法中为每一列新建一个变量,就不能完成通用的查询方法,所以我们需要为每张数据表创建一个对应的类,用 JavaBean 的规则。这样 ResultSet 结果集的每一行对应一个对象。

在方法中我们可以利用 ResultSetMetaData 获得结果集中列的别名,以及从结果集中获得对应的值,我们将其存为一个键位列名,值为列值的键值对,方便后面为数据表对应的对象赋值以便打印。

注意:在测试方法中书写 SQL 语句的时候,我们应该向 javaBean 看齐,也就是如果数据表对应的列名为两个单词,那么就应该为其起一个别名,和 JavaBean 对应的变量名统一,如果没有统一,将打印 null!

//   通用的查询方法,clazz 为数据表对应的类
public <T> T get(Class<T> clazz, String sql, Object ... args) {
T entity = null; Connection connection;
PreparedStatement preparedStatement;
ResultSet resultSet;
ResultSetMetaData resultSetMetaData;
// 存储列名以及列值
Map<String, Object> map = new HashMap<String, Object>(); connection = JDBCTools.getConnection();
try {
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(1, args[i]);
}
resultSet = preparedStatement.executeQuery();
resultSetMetaData = resultSet.getMetaData(); if (resultSet.next()) {
for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
map.put(resultSetMetaData.getColumnLabel(i), resultSet.getObject(i));
}
} // 判断 map 中是否有值,若有则利用反射创建对象,并为之赋值
if (map.size() > 0) {
entity = clazz.newInstance();
// 利用for 循环为每一个变量赋值
for (Map.Entry<String, Object> entry: map.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue(); Field field = clazz.getDeclaredField(fieldName);
// 打破封装
field.setAccessible(true);
field.set(entity, fieldValue);
}
} System.out.printf(String.valueOf(entity));
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} return entity;
}
@Test
public void testSelect() {
String sql = "SELECT id, BOOK_NAME bookName, isbn, price, stock FROM book WHERE id = ?";
// testSelect(SH_DB.class, sql, 5);
get(SH_DB.class, sql, 6);
}
package com.jdbc.dao.my.first.test;

/**
* Created by shkstart on 2017/10/31.
*/
public class SH_DB {
private String bookName;
private String isbn;
private int price;
private int stock; public SH_DB() {
} public SH_DB(String bookName, String isbn, int price, int stock) {
this.bookName = bookName;
this.isbn = isbn;
this.price = price;
this.stock = stock;
} public String getBookName() {
return bookName;
} public void setBookName(String bookName) {
this.bookName = bookName;
} public String getIsbn() {
return isbn;
} public void setIsbn(String isbn) {
this.isbn = isbn;
} public int getPrice() {
return price;
} public void setPrice(int price) {
this.price = price;
} public int getStock() {
return stock;
} public void setStock(int stock) {
this.stock = stock;
}
// 为了方便打印重写 toString 方法
@Override
public String toString() {
return "SH_DB{" +
"bookName='" + bookName + '\'' +
", isbn='" + isbn + '\'' +
", price=" + price +
", stock=" + stock +
'}';
}
}

对应的 JavaBean

上面我们利用反射的方式对数据表对象进行了赋值操作,我们还可以利用一个 beanUtils (在使用之前必须导入 beanUtils jar 包和它所依赖的 commons-logging jar 包)工具类为其赋值   BeanUtils.setProperty(class, fieldName, fieldValue);

现在我们可以将工具类方法进行完善以及将其进行重构,包括了查询多条记录,查询单条记录,查询单个值,更新等操作,如下:

package com.jdbc.dao.my.first.test;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.*; /**
* Created by shkstart on 2017/10/31.
*/ /*
* Dao -> Data Access Object
* 包括了对数据库 CRUD (Create read update delete) 操作,而不包含任何业务逻辑信息
* 可以实现功能模块化,便于维护
* */
public class DaoOfMy { // 更新数据库的方法,可能是删除,更新,增加操作
public void update(String sql, Object... args) {
Connection connection;
PreparedStatement preparedStatement = null; connection = JDBCTools.getConnection();
try {
preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
} preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCTools.releaseConnection(connection, preparedStatement, null);
}
} // 查询一条记录
public <T> T get(Class<T> clazz, String sql, Object... args) {
T entity = null; Connection connection;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
ResultSetMetaData resultSetMetaData = null;
Map<String, Object> map = new HashMap<String, Object>(); connection = JDBCTools.getConnection();
try {
preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
} resultSet = preparedStatement.executeQuery();
resultSetMetaData = resultSet.getMetaData(); if (resultSet.next()) {
for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
String colLabel = resultSetMetaData.getColumnLabel(i + 1);
Object colVal = resultSet.getObject(i + 1);
map.put(colLabel, colVal);
}
} if (map.size() > 0) {
entity = clazz.newInstance();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String fieldName = entry.getKey();
Object fieldVal = entry.getValue();
setFieldVal(entity, fieldName, fieldVal);
}
} } catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
JDBCTools.releaseConnection(connection, preparedStatement, resultSet);
} return entity;
}
//查询多条记录
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
// 存取所有对象
List<T> entities = new ArrayList<T>(); Connection connection;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; connection = JDBCTools.getConnection();
try {
preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
} resultSet = preparedStatement.executeQuery(); // 存取查询到的多条记录
List<Map<String, Object>> listMap = handleResultSetToMapList(resultSet);
// 存取对象集合
entities = changeMapListToBeanList(clazz, listMap); } catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
JDBCTools.releaseConnection(connection, preparedStatement, resultSet);
} return entities;
} private <T> List<T> changeMapListToBeanList(Class<T> clazz, List<Map<String, Object>> listMap) throws InstantiationException, IllegalAccessException { List<T> entities = new ArrayList<T>();
T entity2;
if (listMap.size() > 0) { Iterator<Map<String, Object>> iterator = listMap.iterator(); while (iterator.hasNext()) {
entity2 = clazz.newInstance();
for (Map.Entry<String, Object> entry : iterator.next().entrySet()) {
String fieldName = entry.getKey();
Object fieldVal = entry.getValue();
setFieldVal(entity2, fieldName, fieldVal);
}
entities.add(entity2);
}
}
return entities;
} // 处理结果集,将查询到的每一条结果集存入到 List 中
private List<Map<String, Object>> handleResultSetToMapList(ResultSet resultSet) throws SQLException { List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
// 存取查询结果中的一条记录
Map<String, Object> map;
while (resultSet.next()) {
// 每次都将 map 对象 new 一下,这样每次就是不同的值,不会出现一直存取第一个值
map = new HashMap<String, Object>();
for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
String colLabel = resultSetMetaData.getColumnLabel(i + 1);
Object colVal = resultSet.getObject(i + 1);
map.put(colLabel, colVal);
}
mapList.add(map);
}
return mapList;
} public void setFieldVal(Object obj, String fieldName, Object fieldVal) {
// Field field;
try {
// 使用反射赋值
// field = obj.getClass().getDeclaredField(fieldName);
// field.setAccessible(true);
// field.set(obj, fieldVal);
// 使用 BeanUtils 操作类的属性
BeanUtils.setProperty(obj, fieldName, fieldVal);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} // 获取单个值
public int getForValue(String sql) {
Connection connection;
int count = 0;
PreparedStatement preparedStatement;
ResultSet resultSet; connection = JDBCTools.getConnection();
try {
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery(); if (resultSet.next()) {
count = resultSet.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}
}

以上是上一周学习的一部分,剩下的我很快就会发布,希望大家提出自己宝贵的意见,谢谢!

JDBC(MySQL)一周学习总结(一)的更多相关文章

  1. JDBC(MySQL)一周学习总结(二)

    上一篇文章我们总结了获取数据库连接以及操作数据表的一些知识点,本篇将继续上次的文章给大家分享! 1. 上一篇文章我们可以对数据表进行增删改查的操作了,对与一些小项目的部分功能我们也足以胜任.但现在有一 ...

  2. JDBC+MYSQL初始学习

    JDBC+MYSQL初始学习 一.学习准备 Eclipse 开发工具  + mysql数据库+navicat 数据库连接工具 Mysql的数据库连接驱动jar包  + testing测试集成+mave ...

  3. JDBC MYSQL 学习笔记(一) JDBC 基本使用

    1.JDBC简单介绍 SUN公司为了简化.统一对数据库的操作,定义了一套Java操作数据库的规范.称之为JDBC. JDBC全称为:Java Data Base Connectivity(java数据 ...

  4. 20145212 《Java程序设计》第9周学习总结

    20145212 <Java程序设计>第9周学习总结 教材学习内容总结 一.JDBC架构 1.数据库驱动 这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插 ...

  5. 20145213《Java程序设计》第九周学习总结

    20145213<Java程序设计>第九周学习总结 教材学习总结 "五一"假期过得太快,就像龙卷风.没有一点点防备,就与Java博客撞个满怀.在这个普天同庆的节日里,根 ...

  6. 20145206《Java程序设计》第9周学习总结

    20145206 <Java程序设计>第9周学习总结 教材学习内容总结 第十六章 整合数据库 JDBC是用于执行SQL的解决方案,开发人员使用JDBC的标准接口,数据库厂商则对接口进行操作 ...

  7. 20145223《Java程序程序设计》第9周学习总结

    20145223<Java程序设计>第9周学习总结 教材学习内容总结 第十六章:整合数据库 JDBC入门 1.JDBC简介: 2.JDBC主要分成两个部分,JDBC应用程序开发者接口和JD ...

  8. 21045308刘昊阳 《Java程序设计》第九周学习总结

    21045308刘昊阳 <Java程序设计>第九周学习总结 教材学习内容总结 第16章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 数据库本身是个独立运行的应用程序 撰 ...

  9. # 20145334 《Java程序设计》第9周学习总结

    20145334 <Java程序设计>第9周学习总结 教材学习内容总结 第十六章 整合数据库 JDBC 1.Java语言访问数据库的一种规范,是一套API. 2.JDBC (Java Da ...

随机推荐

  1. mybatis快速入门(五)

    今天写写user表和orders表的mybatis的高级映射,一对一映射和一对多映射 1.创建一个orders.java文件 1.1一对一映射,一条订单对应一个用户 package cn.my.myb ...

  2. snmp4j 之 ArgumentParser

    ArgumentParser ArgumentParser命令行解析器 将数组转换成Java对象 根据预定计划选项和参数,以及相应的命令行选项联合每个对象 ArgumentParser argumen ...

  3. 使用C语言和Java分别实现冒泡排序和选择排序

    经典排序算法--冒泡和选择排序法 Java实现冒泡排序 基本思想是,对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素放到顶端,最终达到完全有序,首先看个动图: 我们要清楚 ...

  4. excel表格数据导入数据库Oracle

    方法一: 1.创建数据表 CREATE TABLE T_USER (   ID             VARCHAR2(32) primary key,   NAME           VARCH ...

  5. ubuntu远程桌面介绍

    一.windows远程ubuntu14.04 由于xrdp.gnome和unity之间的兼容性问题,在Ubuntu 14.04版本中仍然无法使用xrdp登陆gnome或unity的远程桌面,现象是登录 ...

  6. 【转】 Python subprocess模块学习总结

    从Python 2.4开始,Python引入subprocess模块来管理子进程,以取代一些旧模块的方法:如 os.system.os.spawn*.os.popen*.popen2.*.comman ...

  7. Python接口自动化——soap协议传参的类型是ns0类型的要创建工厂方法纪要

    1:在Python接口自动化中,对于soap协议的xml的请求我们可以使用Suds Client来实现,其soap协议传参的类型基本上是有2种: 第一种是传参,不需要再创建啥, 第二种就是ns0类型的 ...

  8. SQLite中的时间日期函数

    SQLite包含了如下时间/日期函数: datetime().......................产生日期和时间 date()...........................产生日期 t ...

  9. phpstudy升级mysql数据库

    因为MySQL支持全文索引的只有5.6以上,而我下的phpstudy只有5.5的版本,在导入数据库的时候因为该数据库的表内有使用全文索引,因此必须升级phpstudy的mysql版本,这里就把自己当升 ...

  10. WebApi系列~HttpClient的性能隐患

    回到目录 最近在进行开发过程中,基于都是接口开发,A站接口访问B接口接口来请求数据,而在这个过程中我们使用的是HttpClient这个框架,当然也是微软自己的框架,性能当前没有问题,但如果你直接使用官 ...