Java中的JDBC(详解、带例子)

JDBC: (摘自百度百科)

一、 JDBC(Java DataBase Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成,JDBC提供一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能编写数据库应用程序。
在这里插入图片描述
也就是说因为Java程序员需要连接多种数据库,为了避免每一种数据库都学习一套新的api,Sun公司提出了一个JDBC的接口,各个数据库的厂商根据此接口写实现类(驱动),这样Java程序员只需要掌握JDBC接口的调用,即可访问任何数据库。

二、执行JDBC的流程:(下面会逐步进行解释)

  1. 注册驱动(加载数据库驱动)
  2. 获取连接对象(Connection)
  3. 创建SQL执行对象(Statement)
  4. 执行SQL语句
  5. 关闭资源

三、JDBC核心类(接口)介绍:

JDBC的核心类有:DriverManagerConnectionStatementResultSet

1、DriverManager类 驱动管理器,是管理一组JDBC驱动程序的基本服务,主要是用于 注册驱动获取连接

a、注册驱动 :这可以让JDBC接口知道连接的是哪个驱动(也就是连接哪个数据库)。
Class.forName(“全限定名(包名+类名)”)

Class.forName("com.mysql.jdbc.Driver"); 	//Mysql
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");	//SQL server

b、获取连接对象:通过DriverManager的api来获取连接对象。
Connection DriverManager.getConnection(url,username,password);

常用数据库URL地址的写法:
MySql:jdbc:mysql://localhost:3306/数据库名称
Oracle:jdbc:oracle:thin:@localhost:1521/数据库名称
SqlServer:jdbc:microsoft:sqlserver://localhost:1433/数据库名称

参数的含义:
url:用于标识数据库的位置,要连接的是什么数据库。
url格式:协议:子协议:子协议名称:IP地址:端口号:数据库名称(前部分是数据库厂商规定的)
username:是我们要连接的数据库的登陆名
password:使我们要登陆用户的对应密码(如果没设置可以为空)


2、Connection 接口:如果可以获取到Connection对象,那么说明已经与数据库连接上了,Connection对象表示连接,与数据库的通讯录都是通过这个对象展开的:Connection最重要的一个方法就是用来获取Statement对象和PreparedStatement对象;

常用方法:
a、创建Statement -语句执行者:Statement createStatement();

Statement st = null;
//获取用于向数据库发送sql语句的statement
st = conn.createStatement();
//执行SQL语句
String sql = "select idd,username,password from users";
st.executeQuery(sql);

b、创建一个预编译的语句执行对象:PreparedStatement prepareStatement(String sql);

PreperedStatement st = null;
String sql = "select * from users where username=? and password=?";
//获取用于向数据库发送sql语句的Preperedstatement
st = conn.preparedStatement(sql);//在此次传入,进行预编译
st.setString(1, username);
st.setString(2, password);
//执行SQL语句
st.executeQuery();//在这里不需要传入sql

c、创建一个 CallableStatement 对象来调用数据库存储过程(了解):CallableStatement prepareCall(String sql);

d、事务相关的使用:(下面有例子详细介绍:)

  • 设置事务是否自动提交:setAutoCommit(boolean autoCommit);
  • 在连接上提交事务:commit();
  • 在此链接上回滚事务:rollback();

注:比较Statement和PreperedStatement对象:
1、相对于Statement对象而言PreperedStatement可以避免SQL注入问题。(下面有更详细的解释)
如:String sql=“select * from users where name=’”+loginName+"’ and pwd=’"+loginPwd+"’";
实际上发送:select * from admin where loginname=‘小明’ and loginpwd=‘123’or’1’=‘1’,登录成功!
因为这条SQL语句后面有一个or 1=1 则这条语句一定会成功其实并没有输入真正的密码,所以SQL注入成功。
2、Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率。
3、并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。


3、Statement接口: Statement对象是SQL语句的执行者,是用来向数据库发送SQL语句的。

常用方法:
a、执行查询语句,返回一个集合:ResultSet executeQuery(String sql)

b、执行更新 插入 删除语句,返回影响行数:int executeUpdate(String sql)

c、执行给定的 SQL 语句,该语句可能返回多个结果:boolean execute(sql)
注:该方法返回值不同,使用方式也不同:返回值为true时,表示执行的查询语句,使用getResultSet方法获取结果;返回值为false时,表示执行更新或DDL语句,使用getUpdateCount获取结果。

d、把多条sql语句放到一个批处理中:addBatch(String sql),然后通过executeBatch()向数据库发送一批sql语句执行。


4、ResultSet接口: ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式,ResultSet 对象维护了一个指针,初始的时候,指针指向的是结果集的第一行,可以通过提供的api对指针进行操作,查看结果集。

1、关于ResultSet对象对指针操作的方法:

  • next():移动到下一行
  • Previous():移动到前一行
  • absolute(int row):移动到指定行
  • beforeFirst():移动resultSet的最前面
  • afterLast() :移动到resultSet的最后面

2、ResultSet也提供了一些直接获取值的方法:

获取任意类型的数据:

  • getObject(int index)
  • getObject(string columnName)

获取指定类型的数据,例如:

  • getString(int index)
  • getString(String columnName)

常用数据类型转换:
在这里插入图片描述
例如:我们最后会整体的执行一遍(下面只是一个例子)

ResultSet rs = null;
//执行sql语句,并获取结果集
String sql = "select idd,username,password from users";
rs = st.executeQuery(sql);
//取出结果集的数据
rs.afterLast();
rs.previous();
System.out.println("idd=" + rs.getObject("idd"));
System.out.println("username=" + rs.getObject("username"));
System.out.println("password=" + rs.getObject("password"));

四、整体流程: 我们这里是使用的mysql数据库

1、首先创建Maven项目,然后在pom.xml添加依赖。

<dependencies>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.6</version>
	</dependency>
	<!-- 数据库连接池,后续会用到 -->
	<dependency>
		<groupId>commons-dbcp</groupId>
		<artifactId>commons-dbcp</artifactId>
		<version>1.4</version>
	</dependency>
</dependencies>

2、我们添加一个配置文件jdbc.properties放在src/resources,配置数据库的连接信息。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名称
username=填写你的登陆名
password=填写你的登陆密码

3、添加一个class类,添加一个静态块,然后读取配置文件。通常情况下,这个过程是每次都要执行的,所以一般都封装成一个DBUtils类,然后每次直接调用就行了。

public class DBUtils {
	private static String driver;
	private static String url;
	private static String username;
	private static String password;
	static{
		//创建一个读取配置文件的属性对象
		Properties prop=new Properties();
		//获取文件的输入流
		InputStreamips=DBUtils.class.getClassLoader()
					.getResourceAsStream("jdbc.properties");
		try {
			//把文件加载到属性对象中
			prop.load(ips);
			//获取数据
			driver=prop.getProperty("driver");
			url=prop.getProperty("url");
			username=prop.getProperty("username");
			password=prop.getProperty("password");
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				//关闭流
				ips.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

4、注册驱动,获取连接,我们将这两步封装在DBUtils中的一个getConn()方法中,便于后续调用。

public static Connection getConn() throws Exception{
	//注册驱动
	Class.forName(driver);
	//获取连接
	Connection conn =DriverManager.getConnection(url,username,password);
	return conn;	//返回连接对象Connection
}

5、关闭资源,我们也封装为DBUtils的一个close()方法。

//关闭资源
public static void close(Connection conn,Statement stat,ResultSet rs){
	try {
		//判断有值时关闭
		if(rs!=null){
			rs.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
	try {
		if(stat!=null){
			stat.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
	try {
		if(conn!=null){
			conn.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
}

到此我们就将DBUtils类封装好了,我们后续直接调用就可以了。

五、对数据库进行操作:(代码中加了注释,直接看代码就行)
1、插入操作:(使用的是excuteUpdate执行SQL 返回值为int)

@Test
public void insert(){
	//声明连接对象Connection和执行Sql的Statement对象
	Connection conn=null;
	Statement stat=null;
	try{
		conn=DBUtils.getConn();	 //调用上面我们封装的获取Connection对象的方法
		stat=conn.createStatement();	//通过Connection获取Statement对象
		String sql="insert into jdbc values(1,'Tom')";	//创建一条SQL语句
		stat.executeUpdate(sql);	//执行SQL语句(这里也可以接受一下int返回值,查看受影响条数)
	}catch(Exception e){
		e.printStackTrace();
	}finally{
		DBUtils.close(conn, stat, null);	//最后调用我们封装好的close方法关闭资源
	}
}

2、修改操作:(这里和上面基本流程相同)

@Test
public void update(){
	Connection conn = null;
	Statement stat = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		String sql="update jdbc set name='Jerry' where id=1";
		stat.executeUpdate(sql);
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

3、删除操作:(这里也是用的是excuteUpdate返回int 受影响行数)

@Test
public void delete(){
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		String sql="delete from jdbc where id=1";
		stat.executeUpdate(sql);
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

4、查询操作:(这里使用的是excuteQuery 返回值为ResultSet一个结果集)

@Test
public void select(){
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;	//这里多声明一个结果集对象ResultSet
	try {
		conn = DBUtils.getConn();
		stat=conn.createStatement();
		rs=stat.executeQuery("select * from emp");	//执行查询并获取结果集
		while(rs.next()){		//使用next()方法逐条遍历所有结果
			String name=rs.getString("ename");
			double sal=rs.getDouble("sal");
			System.out.println(name+" "+sal);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);	//关闭资源
	}
}

5、使用事务:

public static void main(String[] args) {
	//create table person (id int,name varchar(10),money int);
	//insert into person values(1,'超人',500),(2,'钢铁侠',5000);
	String sql1="update person set money=money+2000 where id=1";
	String sql2="update person set money=money-2000 where id=2";
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		//关闭自动提交(开启事务)
		conn.setAutoCommit(false);
		//让超人+2000
		stat.executeUpdate(sql1);
		//让钢铁侠-2000
		stat.executeUpdate(sql2);
		//查询钢铁侠剩余的钱是否>=0
		rs=stat.executeQuery("select money from person where id=2");
		while(rs.next()){
			int money=rs.getInt("money");
			if(money>=0){
				conn.commit();		//事务提交
				System.out.println("转账成功");
			}else{
				conn.rollback();	//事务回滚
				System.out.println("转账失败,钢铁侠余额不足");
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

六、使用数据库连接池DBCP:(优化过程)
(一)连接池作用:(和线程池有些类似)
使用连接池DBCP可以起到提高性能、控制并发、提高安全性的作用,连接池是将已经创建好的连接保存到池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问,这样省略了创建连接和销毁连接的过程,这样性能上得到了提高。(总结了一篇专门关于连接池相关的文章,可以点下面链接了解一下)

(二)基本原理:点击了解:连接池相关内容
1、建立数据库连接池对象(服务器启动)。
2、按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
3、对于一个数据库的访问请求,直接从连接池中得到一个连接,如果数据库连接池对象中有空闲连接则直接使用、若没有空闲的链接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接来处理该请求,如果没有空闲连接并且达到最大活跃值则进行等待其它的链接释放再进行该请求的处理。
4、存取数据库。
5、关闭数据库,释放所有数据库连接(此时福安比数据库连接,并非真正的关闭,而是将其放入空闲队列中。如果实际空闲连接数大于初始空闲连接数则释放连接)。
6、释放数据库连接池对象(在服务器停止、维护期间,真正的释放数据库连接池对象,并释放所有资源)。

(三)实现过程:(使用步骤:)
1、前几步和上面第四节相同,需要我们做的就是:多添加一下连接池的依赖,修改配置文件以及重写DBUtils类,依赖上面也已经添加过了,下面将DBUtils类进行一部分更改即可。

2、首先是static静态块的更改:(这里直接将数据源配置好)
我们这里连接池:最大活跃数初始连接数量参数是直接设置了,也可以和其他参数一样配置到配置文件中,然后通过读取配置文件的方式来获取。

public class DBUtils {
	private static String driver;
	private static String url;
	private static String username;
	private static String password;
	private static BasicDataSource dataSource;	//声明数据源,用于配置数据库的链接信息
	static{
		Properties prop=new Properties();
		InputStream ips=DBUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
		try {
			prop.load(ips);	//加载配置文件
			driver=prop.getProperty("driver");	//获取我们在配置文件中配置的driver等信息
			url=prop.getProperty("url");
			username=prop.getProperty("username");
			password=prop.getProperty("password");
			//创建数据源
			dataSource=new BasicDataSource();	//创建一个数据源
			//设置数据库的连接信息:
			dataSource.setDriverClassName(driver);
			dataSource.setUrl(url);
			dataSource.setUsername(username);
			dataSource.setPassword(password);
			//设置初始连接数量
			dataSource.setInitialSize(3);
			//设置最大连接数量
			dataSource.setMaxActive(3);
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				ips.close();	//关闭资源
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

3、修改一下获取连接对象Connection的方法:(因为上面已经配置好了,我们直接通过数据源获取Conn就行了)

public static Connection getConn() throws Exception{
	/*Class.forName(driver);
	Connection conn=DriverManager.getConnection(url,username,password);*/
	//从连接池中获取连接
	Connection conn=dataSource.getConnection();
	return conn;
}

4、修改close方法:(这里将自动提交打开)

public static void close(Connection conn,Statement stat,ResultSet rs){
	try {
		if(rs!=null){
			rs.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
	try {
		if(stat!=null){
			stat.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
	try {
		if(conn!=null){
			//打开自动提交
			conn.setAutoCommit(true);
			conn.close();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
}

5、执行查询操作:

@Test
public void select(){
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		String sql="select * from emp";
		rs=stat.executeQuery(sql);
		while(rs.next()){	//这里的方法上面都有介绍
			int empno=rs.getInt("empno");	//获取不同类型的参数(通过参数名)
			String name=rs.getString("ename");
			double sal=rs.getDouble("sal");
			System.out.println(empno+" "+name+" "+sal);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);	//别忘了关闭资源
	}
}

6、插入操作:我们这里使用的是PrepareStatement对象(下面实现通过控制台输入的用户名和年龄进行插入,不多说了看注释吧)

public static void main(String[] args) {
	Scanner scan=new Scanner(System.in);
	System.out.println("请输入姓名");
	String name=scan.nextLine();	//接收用户输入的用户名
	Scanner scr=new Scanner(System.in);
	System.out.println("请输入年龄");
	int age=Integer.parseInt(scr.nextLine());	//接收用户输入的年龄
	//2.把得到的内容拼接到sql语句中
	String sql="insert into jdbcuser values(?,?)";	//创建一条SQL,保留两个参数
	Connection conn = null;		//声明连接对象
	PreparedStatement stat = null;	//声明预加载SQL执行对象
	ResultSet rs = null;	//声明结果集对象
	try {
		conn = DBUtils.getConn();	//获取连接
		stat = conn.prepareStatement(sql);	//获取预加载SQL执行对象
		//把?换回正确的内容
		stat.setString(1, name);
		stat.setInt(2, age);
		stat.executeUpdate();	//这里不要再添加SQL语句了,上面也说过了
		System.out.println("插入成功");
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);	//关闭资源
	}
}

7、SQL注入问题:(使用Statement对象存在SQL注入问题)
控制台接收用户输入的用户名和密码,验证以后看是否登陆成功。结果后面加上 1 = 1 以后肯定为true,所以不论我们前面验证是否成功,所以都会登陆成功,也就是我们通过后期输入参数的形式进行了SQL注入,这样可能会发生危险。

public static void main(String[] args) {
	Scanner scan=new Scanner(System.in);
	System.out.println("请输入用户名");
	String username=scan.nextLine();
	Scanner scr=new Scanner(System.in);
	System.out.println("请输入密码");
	String password=scr.nextLine();
	boolean b=login(username,password);
	if(b){
		System.out.println("登陆成功");
	}else{
		System.out.println("登录失败");
	}
}
//' or '1'='1   SQL注入改变原有SQL语句的逻辑导致什么都可以登陆成功
private static boolean login(String username, String password) {
	String sql="select count(*) from user where username='"+username+"' and password='"+password+"'";
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		rs=stat.executeQuery(sql);
		while(rs.next()){
			//得到查询的数量
			int count=rs.getInt(1);
			if(count>0){
				return true;
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
	return false;
}

8、使用PrepareStatement来实现登陆验证:(可以避免SQL注入)

public static void main(String[] args) {
	Scanner scan=new Scanner(System.in);
	System.out.println("请输入用户名");
	String username=scan.nextLine();
	Scanner scr=new Scanner(System.in);
	System.out.println("请输入密码");
	String password=scr.nextLine();
	boolean b=login(username,password);
	if(b){
		System.out.println("登陆成功");
	}else{
		System.out.println("登录失败");
	}
}
private static boolean login(String username, String password) {
	String sql="select count(*) from user where username=? and password=?";
	Connection conn = null;
	PreparedStatement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.prepareStatement(sql);
		stat.setString(1, username);
		stat.setString(2, password);
		rs=stat.executeQuery();
		while(rs.next()){
			//得到查询的数量
			int count=rs.getInt(1);
			if(count>0){
				return true;
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
	return false;
}

9、批量操作(使用addBatch):使用Statement对象:

@Test
public void test01(){
	String sql1="insert into user values(null,'悟空','aaa')";
	String sql2="insert into user values(null,'八戒','bbb')";
	String sql3="insert into user values(null,'沙僧','ccc')";
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		stat.addBatch(sql1);
		stat.addBatch(sql2);
		stat.addBatch(sql3);
		//执行批量操作
		stat.executeBatch();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

10、批量操作(使用addBatch):使用PrepareStatement对象:

@Test
public void test02(){
	String sql="insert into user values(null,?,?)";
	Connection conn = null;
	PreparedStatement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.prepareStatement(sql);
		stat.setString(1, "刘备");
		stat.setString(2, "aaa");
		//添加到批量处理
		stat.addBatch();
		stat.setString(1, "关羽");
		stat.setString(2, "bbb");
		stat.addBatch();
		stat.setString(1, "张飞");
		stat.setString(2, "ccc");
		stat.addBatch();
		stat.executeBatch();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

11、批量操作(使用addBatch):避免内存溢出:

@Test
public void test03(){
	String sql="insert into user values(null,?,?)";
	Connection conn = null;
	PreparedStatement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.prepareStatement(sql);
		for(int i=0;i<100;i++){
			stat.setString(1, "name"+i);
			stat.setString(2, "admin");
			stat.addBatch();
			//下面写法可以避免内存溢出
			if(i%20==0){
				stat.executeBatch();
				//清除批处理内容
				stat.clearBatch();
			}
		}
		stat.executeBatch();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

12、自己手动设置主键:获取主键

public static void main(String[] args) {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		String sql="insert into user values(null,'刘德华','abc')";
		//执行SQL并且制定需要获取自增主键值
		stat.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
		//获取返回的主键值
		rs=stat.getGeneratedKeys();
		while(rs.next()){
			int id=rs.getInt(1);
			System.out.println("自增主键"+id);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

13、可以查看数据库的元数据:

public static void main(String[] args) {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		conn = DBUtils.getConn();
		stat = conn.createStatement();
		//得到数据库的元数据
		DatabaseMetaData dbData=conn.getMetaData();
		System.out.println("驱动版本:"+dbData.getDriverMajorVersion());
		System.out.println("用户名:"+dbData.getUserName());
		System.out.println("链接地址:"+dbData.getURL());
		System.out.println("数据库名称:"+dbData.getDatabaseProductName());
		//获取表相关的元数据
		rs=stat.executeQuery("select * from emp");
		ResultSetMetaData rsData=rs.getMetaData();
		//得到表字段的数量
		int count =rsData.getColumnCount();
		for(int i=0;i<count;i++){
			String name=rsData.getColumnName(i+1);
			String type=rsData.getColumnTypeName(i+1);
			System.out.println(name+":"+type);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		DBUtils.close(conn, stat, rs);
	}
}

其实JDBC的这些类和接口封装了好多的方法,有兴趣可以自己去看看源代码,举的例子也够多的了,其实掌握上面的内容完全足够用了。

上面就是所有关于JDBC的内容了,希望能对你有所帮助,如果喜欢的话那就点个赞吧,或者点个关注支持一下!!谢谢!!

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页