JDBC

Java 专栏收录该内容
52 篇文章 1 订阅

IDEA懒人插件 EasyCode:https://blog.csdn.net/qq_40794973/article/details/104831325 


1 建库建表 

docker部署mysql

docker run \
--name mysql_jdbc \
--env MYSQL_ROOT_HOST=%.%.%.% \
--env MYSQL_ROOT_PASSWORD=123456 \
-p 30000:3306  \
--rm \
-di mysql:8.0.18

连接mysql并创建表 

jdbc:mysql://121.36.33.154:30000?serverTimezone=UTC
-- 创建数据库
DROP DATABASE IF EXISTS jdbc_test;
CREATE DATABASE jdbc_test;
USE jdbc_test;
-- 创建测试表
CREATE TABLE `user_info`
(
    `id`       INT(10) UNSIGNED AUTO_INCREMENT COMMENT '主键ID',
    `username` VARCHAR(20)      NOT NULL COMMENT '用户名',
    `password` VARCHAR(64)      NOT NULL COMMENT '密码',
    `age`      TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '年龄',
    `email`    VARCHAR(50)      NOT NULL DEFAULT '' COMMENT '邮箱',
    PRIMARY KEY `id` (`id`),
    UNIQUE KEY `uk_user_username` (`username`)
);
-- 插入测试数据
INSERT INTO `user_info` (`id`, `username`, `password`, `age`, `email`)
VALUES ('1', 'zhangsan', '123456', '18', 'zhangsan@bug.cn'),
       ('2', 'lisi', '123456', '20', 'lisi@bug.cn');

mysql驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>

获取数据库连接

// JDBCConnectionUtil
public static Connection getConnection() {
    String user = "root";
    String password = "123456";
    String className = "com.mysql.cj.jdbc.Driver";
    String url = "jdbc:mysql://121.36.33.154:30000/jdbc_test?characterEncoding=utf-8&useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
    //
    Connection conn;
    try {
        Class.forName(className);
        conn = DriverManager.getConnection(url, user, password);
    } catch (ClassNotFoundException | SQLException e) {
        log.error(e.getMessage());
        throw new RuntimeException(e.getMessage());
    }
    return conn;
}

获取数据库标识

@Test
public void test_mysql() throws SQLException {
    DatabaseMetaData metaData = getConnection().getMetaData();
    String databaseProductName = metaData.getDatabaseProductName();
    System.out.println(databaseProductName);  // MySQL
}

2 JDBC编程步骤

2.1 6个步骤

1、加载数据库驱动,通常使用Class类的 forName() 静态方法来加载驱动

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");  // SQL Server
Class.forName("com.mysql.jdbc.Driver");                         // MYySQL
Class.forName("oracle.jdbc.driver.OracleDriver");               // Oracle

注意高版本的mysql是com.mysql.cj.jdbc.Driver,写com.mysql.jdbc.Driver也可以但是控制台会打印警告信息

// <dependency>
//    <groupId>mysql</groupId>
//     <artifactId>mysql-connector-java</artifactId>
//     <version>8.0.18</version>
// </dependency>
public class Driver extends com.mysql.cj.jdbc.Driver {
    public Driver() throws SQLException {
        super();
    }
    static {
        System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "
                + "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
    }
}

2、使用DriverManager获取数据库连接

/**
 * java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)
 *
 * @param url      url 
 * @param user     用户名
 * @param password 密码
 */
public static Connection getConnection(String url, String user, String password) throws SQLException {
   // ,...
}

3、使用Connection来创建一个Statment

4、使用Statment执行SQL语句

5、操作结果集

6、回收数据库资源(Java7后可以用自动关闭try语句来关闭资源)

2.2 完整流程演示

public static Connection getConnection() {
	String user = "root";
	String password = "123456";
	String className = "com.mysql.cj.jdbc.Driver";
	String url = "jdbc:mysql://121.36.33.154:30000/jdbc_test?characterEncoding=utf-8&useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
	Connection conn;
	try {
		// 1.加载驱动,使用反射的知识
		Class.forName(className);
		// 2.使用DriverManager获取数据库连接
		// 其中返回的Connection就代表了Java程序和数据库的连接
		// 不同数据库的URL写法需要查驱动文档,用户名和密码由DBA分配
		conn = DriverManager.getConnection(url, user, password);
	} catch (ClassNotFoundException | SQLException e) {
		log.error(e.getMessage());
		throw new RuntimeException(e.getMessage());
	}
	return conn;
}
@Test
public void test_jdbc() throws SQLException {
	try (
			Connection conn = getConnection();
			// 3.使用Connection来创建一个Statment对象
			Statement stmt = conn.createStatement();
	) {
		// 4.执行SQL语句
		// Statement有三种执行sql语句的方法
		//	 1、execute 可执行任何SQL语句,返回一个boolean值,如果执行后第一个结果是ResultSet,则返回true,否则返回false
		//	 2、executeQuery 执行Select语句,返回查询到的结果集
		//	 3、executeUpdate 用于执行DML语句,返回一个整数,代表被SQL语句影响的记录条数

		//mysql> SELECT username, password, age FROM user_info WHERE username = 'lisi';
		//+----------+----------+-----+
		//| username | password | age |
		//+----------+----------+-----+
		//| lisi     | 123456   |  30 |
		//+----------+----------+-----+
		//1 row in set (0.00 sec)
		String sql = "SELECT username, password, age FROM user_info WHERE username = " + "'lisi'";
		ResultSet rs = stmt.executeQuery(sql);
		// ResultSet有系列的getXxx (列索引|列名),用于获取记录指针
		// 指向行、特定列的值,不断地使用next()将记录指针下移一行,如果移动之后记录指针依然指向有效行,则next()方法返回true
		while (rs.next()) {
			String username = rs.getString(1);
			String password = rs.getString(2);
			int age = rs.getInt(3);
			log.info("res => username:{},password:{},age:{}", username, password, age);
		}
	}
}

3 Statement执行SQL的三种方法

  1. DML 数据操纵语言(Data Manipulation Language);对数据库中的数据进行一些简单操作,如insert、delete、update、select等;可以手动控制事务的开启、提交和回滚
  2. DDL 数据定义语言(Data Definition Language);对数据库中的某些对象(例如database和table)进行管理,如create、alter、drop、truncate、show等;隐性提交,不能rollback

3.1 execute

// 执行插入
@Test
public void test_execute_DML() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'wangwu';
	//Empty set (0.00 sec)
	String sql = "insert into user_info(username, password, age, email) values ('wangwu','123456',28,'wangwu@bug.cn');";
	boolean type = stmt.execute(sql); // false
	log.info("type => {}", type);
	//mysql> select * from user_info where username = 'wangwu';
	//+----+----------+----------+-----+---------------+
	//| id | username | password | age | email         |
	//+----+----------+----------+-----+---------------+
	//|  3 | wangwu   | 123456   |  28 | wangwu@bug.cn |
	//+----+----------+----------+-----+---------------+
	// 省略关闭连接步骤...
}
// 执行查询
@Test
public void test_execute_DML() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'wangwu';
	//+----+----------+----------+-----+---------------+
	//| id | username | password | age | email         |
	//+----+----------+----------+-----+---------------+
	//|  3 | wangwu   | 123456   |  28 | wangwu@bug.cn |
	//+----+----------+----------+-----+---------------+
	String sql = "SELECT * FROM user_info WHERE username = " + "'wangwu';";
	boolean type = stmt.execute(sql); // true
	if (type) {
		ResultSet resultSet = stmt.getResultSet();
		while (resultSet.next()) {
			int id = resultSet.getInt(1);
			String username = resultSet.getString(2);
			String password = resultSet.getString(3);
			int age = resultSet.getInt(4);
			String email = resultSet.getString(5);
			log.info("res => id:{},username:{},password:{},age:{},email:{}", id, username, password, age, email);
		}
	}
	// 省略关闭连接步骤...
}
// 执行修改
@Test
public void test_execute_DML() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'wangwu';
	//+----+----------+----------+-----+---------------+
	//| id | username | password | age | email         |
	//+----+----------+----------+-----+---------------+
	//|  3 | wangwu   | 123456   |  28 | wangwu@bug.cn |
	//+----+----------+----------+-----+---------------+
	String sql = "UPDATE user_info SET age = age + 10 WHERE username =" + "'wangwu';";
	boolean type = stmt.execute(sql); // false
	//mysql> select * from user_info where username = 'wangwu';
	//+----+----------+----------+-----+---------------+
	//| id | username | password | age | email         |
	//+----+----------+----------+-----+---------------+
	//|  3 | wangwu   | 123456   |  38 | wangwu@bug.cn |
	//+----+----------+----------+-----+---------------+
	// 省略关闭连接步骤...
}
// 执行删除
@Test
public void test_execute_DML() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'wangwu';
	//+----+----------+----------+-----+---------------+
	//| id | username | password | age | email         |
	//+----+----------+----------+-----+---------------+
	//|  3 | wangwu   | 123456   |  38 | wangwu@bug.cn |
	//+----+----------+----------+-----+---------------+
	String sql = "delete from user_info where username = 'wangwu';";
	boolean type = stmt.execute(sql); // false
	//mysql> select * from user_info where username = 'wangwu';
	//Empty set (0.00 sec)
	// 省略关闭连接步骤...
}
public void executeSql(String sql) throws Exception {
	/// 省略获取连接步骤...
	Connection conn = getConnection();
	// 使用Connection来创建一个Statement对象
	Statement stmt = conn.createStatement();
	// 执行SQL,返回boolean值表示是否包含ResultSet
	boolean hasResultSet = stmt.execute(sql);
	// 如果执行后有ResultSet结果集
	if (hasResultSet) {
		// 获取结果集
		ResultSet rs = stmt.getResultSet();
		// ResultSetMetaData是用于分析结果集的元数据接口
		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();
		// 迭代输出ResultSet对象
		while (rs.next()) {
			// 依次输出每列的值
			for (int i = 0; i < columnCount; i++) {
				System.out.print(rs.getString(i + 1) + "\t");
			}
			System.out.print("\n");
		}
		rs.close();
	} else {
		log.info("该SQL语句影响的记录有{}条", stmt.getUpdateCount());
	}
	conn.close();
}
// 打断点运行
@Test
public void test_() throws Exception {
	String sql;
	// 1、执行建表的DDL语句
	//create table tb_execute_test
	//(
	//    id   int primary key,
	//    name varchar(20)
	//) default char set utf8mb4;
	sql = "create table tb_execute_test(id int primary key, name varchar(20)) default char set utf8mb4;";
	executeSql(sql);
	//show create table tb_execute_test \G
	//mysql> desc tb_execute_test;
	//+-------+-------------+------+-----+---------+-------+
	//| Field | Type        | Null | Key | Default | Extra |
	//+-------+-------------+------+-----+---------+-------+
	//| id    | int(11)     | NO   | PRI | NULL    |       |
	//| name  | varchar(20) | YES  |     | NULL    |       |
	//+-------+-------------+------+-----+---------+-------+
	//2 rows in set (0.00 sec)

	// 2、执行插入数据的DML语句
	sql = "insert into tb_execute_test(id, name)values (1, '蔡徐坤');";
	executeSql(sql);
	//mysql> select * from tb_execute_test;
	//+----+------+
	//| id | name |
	//+----+------+
	//|  1 | ???  |
	//+----+------+
	//1 row in set (0.00 sec)

	// 3、执行查询数据的查询语句
	sql = "select * from tb_execute_test;";
	executeSql(sql);

	// 4、执行删除表的DDL语句
	sql = "drop table tb_execute_test;";
	executeSql(sql);
}

3.2 executeQuery

// 执行查询
@Test
public void test_executeQuery() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'lisi';
	//+----+----------+----------+-----+-------------+
	//| id | username | password | age | email       |
	//+----+----------+----------+-----+-------------+
	//|  2 | lisi     | 123456   |  20 | lisi@bug.cn |
	//+----+----------+----------+-----+-------------+
	//1 row in set (0.00 sec)
	String sql = "SELECT id, username, password, age, email FROM user_info WHERE username = " + "'lisi';";
	ResultSet resultSet = stmt.executeQuery(sql); // true
	while (resultSet.next()) {
		int id = resultSet.getInt(1);
		String username = resultSet.getString(2);
		String password = resultSet.getString(3);
		int age = resultSet.getInt(4);
		String email = resultSet.getString(5);
		log.info("res => id:{},username:{},password:{},age:{},email:{}", id, username, password, age, email);
		//
	}
	// 省略关闭连接步骤...
}

3.3 executeUpdate

// 执行修改
@Test
public void test_executeUpdate() throws SQLException {
	// 省略获取连接步骤...
	Connection conn = getConnection();
	Statement stmt = conn.createStatement();
	//mysql> select * from user_info where username = 'lisi';
	//+----+----------+----------+-----+-------------+
	//| id | username | password | age | email       |
	//+----+----------+----------+-----+-------------+
	//|  2 | lisi     | 123456   |  20 | lisi@bug.cn |
	//+----+----------+----------+-----+-------------+
	String sql = "UPDATE user_info SET age = age + 10 WHERE username =" + "'lisi';";
	int count = stmt.executeUpdate(sql);
	log.info("count => {}", count);
	//mysql> select * from user_info where username = 'lisi';
	//+----+----------+----------+-----+-------------+
	//| id | username | password | age | email       |
	//+----+----------+----------+-----+-------------+
	//|  2 | lisi     | 123456   |  30 | lisi@bug.cn |
	//+----+----------+----------+-----+-------------+
	// 省略关闭连接步骤...
}

一、创建数据库和表。

 

create database select_test;
use select_test;
-- 为了保证从表参照的主表存在,通常应该先建主表。
create table teacher_table
(
	-- auto_increment:实际上代表所有数据库的自动编号策略,通常用作数据表的逻辑主键。
	teacher_id int identity(1,1),
	teacher_name varchar(255),
	primary key(teacher_id)
);
create table student_table
(
	--为本表建立主键约束
	student_id int identity(1,1) primary key,
	student_name varchar(255),
	-- 指定java_teacher参照到teacher_table的teacher_id列
	java_teacher int,
	foreign key(java_teacher) references teacher_table(teacher_id)
);

insert into teacher_table
values
('Yeeku'),( 'Leegang'),( 'Martine');

insert into student_table
values
('张三' , 1),('张三' , 1),('李四' , 1),( '王五' , 2),( '_王五' , 2),(  null , 2),( '赵六' , null);


select * from teacher_table;
select * from student_table;

二、执行SQL语句的方式

1、下面来演示一个严格按照JDBC来执行的程序。

a) 使用executeQuery方法来执行查询语句。(只能执行查询)

import java.sql.*;
public class ConnMySql{
	public static void main(String[] args) throws Exception{
		// 1.加载驱动,使用反射的知识,现在记住这么写。
		Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
		try(
			// 2.使用DriverManager获取数据库连接,
			// 其中返回的Connection就代表了Java程序和数据库的连接
			// 不同数据库的URL写法需要查驱动文档知道,用户名、密码由DBA分配
			Connection conn = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433;databaseName=select_test","sa","123456");
			// 3.使用Connection来创建一个Statment对象
			Statement stmt = conn.createStatement();
			// 4.执行SQL语句
			/*
			Statement有三种执行sql语句的方法:
			1 execute 可执行任何SQL语句。- 返回一个boolean值,
			  如果执行后第一个结果是ResultSet,则返回true,否则返回false
			2 executeQuery 执行Select语句 - 返回查询到的结果集
			3 executeUpdate 用于执行DML语句。- 返回一个整数,
			  代表被SQL语句影响的记录条数
			*/
			ResultSet rs = stmt.executeQuery("select s.* , teacher_name"
				+ " from student_table s , teacher_table t"
				+ " where t.teacher_id = s.java_teacher"))
		{
			// ResultSet有系列的getXxx(列索引 | 列名),用于获取记录指针
			// 指向行、特定列的值,不断地使用next()将记录指针下移一行,
			// 如果移动之后记录指针依然指向有效行,则next()方法返回true。
			while(rs.next()){
				System.out.println(rs.getInt(1) + "\t"
					+ rs.getString(2) + "\t"
					+ rs.getString(3) + "\t"
					+ rs.getString(4));
			}
		}
	}
}

2、2使用Java8新增的executeUpdate方法执行DDL和DML语句。

executeUpdate执行DDL和DML语句,区别是DDL返回0,DML返回受影响的记录条数

配置文件

mysql.ini

driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
url=jdbc:sqlserver://127.0.0.1:1433;databaseName=select_test
user=sa
pass=123456
import java.util.*;
import java.io.*;
import java.sql.*;
public class ExecuteDDL{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void createTable(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
		// 获取数据库连接
		Connection conn = DriverManager.getConnection(url , user , pass);
		// 使用Connection来创建一个Statment对象
		Statement stmt = conn.createStatement()){
			// 执行DDL,创建数据表
			int x = (int) stmt.executeLargeUpdate(sql);
			System.out.println(x);
			//DDL返回0
			//DML返回受影响的记录条数
		}
	}
	public static void main(String[] args) throws Exception{
		ExecuteDDL ed = new ExecuteDDL();
		ed.initParam("mysql.ini");
		ed.createTable("create table jdbc_test "
			+ "( jdbc_id int identity(1,1) primary key, "
			+ "jdbc_name varchar(255), "
			+ "jdbc_desc text);");
		System.out.println("-----建表成功-----");
	}
}

在往刚刚创建的 jdbc_test添加几条记录

import java.util.*;
import java.io.*;
import java.sql.*;
public class ExecuteDML{
	private String driver;
	private String url;
	private String user;
	private String pass;

	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public int insertData(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass);
			// 使用Connection来创建一个Statment对象
			Statement stmt = conn.createStatement())
		{
			// 执行DML,返回受影响的记录条数
			return stmt.executeUpdate(sql);
		}
	}
	public static void main(String[] args)throws Exception{
		ExecuteDML ed = new ExecuteDML();
		ed.initParam("mysql.ini");
		int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)"
			+ "select s.student_name , t.teacher_name "
			+ "from student_table s , teacher_table t "
			+ "where s.java_teacher = t.teacher_id;");
		System.out.println("--系统中共有" + result + "条记录受影响--");
	}
}

 

3、使用execute方法执行SQL语句。

execute方法可以执行任何语句,缺点是麻烦。

import java.util.*;
import java.io.*;
import java.sql.*;
public class ExecuteSQL{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void executeSql(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url
				, user , pass);
			// 使用Connection来创建一个Statement对象
			Statement stmt = conn.createStatement())
		{
			// 执行SQL,返回boolean值表示是否包含ResultSet
			boolean hasResultSet = stmt.execute(sql);
			// 如果执行后有ResultSet结果集
			if (hasResultSet){
				try(
					// 获取结果集
					ResultSet rs = stmt.getResultSet())
				{
					// ResultSetMetaData是用于分析结果集的元数据接口
					ResultSetMetaData rsmd = rs.getMetaData();
					int columnCount = rsmd.getColumnCount();
					// 迭代输出ResultSet对象
					while (rs.next()){
						// 依次输出每列的值
						for (int i = 0 ; i < columnCount ; i++ ){
							System.out.print(rs.getString(i + 1) + "\t");
						}
						System.out.print("\n");
					}
				}
			}else{
				System.out.println("该SQL语句影响的记录有"
					+ stmt.getUpdateCount() + "条");
			}
		}
	}
	public static void main(String[] args) throws Exception{
		ExecuteSQL es = new ExecuteSQL();
		es.initParam("mysql.ini");
		System.out.println("------执行删除表的DDL语句-----");
		es.executeSql("drop table my_test");
		
		System.out.println("------执行建表的DDL语句-----");
		es.executeSql("create table my_test"
			+ "(test_id int  identity(1,1) primary key, "
			+ "test_name varchar(255))");
		
		System.out.println("------执行插入数据的DML语句-----");
		es.executeSql("insert into my_test(test_name) "
			+ "select student_name from student_table");
		
		System.out.println("------执行查询数据的查询语句-----");
		es.executeSql("select * from my_test");
	}
}

ResultSet 的getString()方法可以获取除了Blob子外的任意类型列的值。

4、使用PreparedStatement执行SQL语句。

当我们需要反复执行一条结构相似的SQL语句,就应该使用这种方法。预编译的SQL语句存储在PreparedStatement对象中,可以使用该对象多次高效地执行该语句。

import java.util.*;
import java.io.*;
import java.sql.*;
public class PreparedStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
		// 加载驱动
		Class.forName(driver);
	}
	public void insertUseStatement()throws Exception{
		long start = System.currentTimeMillis();
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url
				, user , pass);
			// 使用Connection来创建一个Statment对象
			Statement stmt = conn.createStatement())
		{
			// 需要使用100条SQL语句来插入100条记录
			for (int i = 0; i < 100 ; i++ ){
				stmt.executeUpdate("insert into student_table values("
					+ "'姓名" + i + "' , 1)");
			}
			System.out.println("使用Statement费时:"
				+ (System.currentTimeMillis() - start));
		}
	}
	public void insertUsePrepare()throws Exception{
		long start = System.currentTimeMillis();
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url
				, user , pass);
			// 使用Connection来创建一个PreparedStatement对象
			PreparedStatement pstmt = conn.prepareStatement(
				"insert into student_table values(?,1)"))

		{
			// 100次为PreparedStatement的参数设值,就可以插入100条记录
			for (int i = 0; i < 100 ; i++ ){
				pstmt.setString(1 , "姓名" + i);
				pstmt.executeUpdate();
			}
			System.out.println("使用PreparedStatement费时:"
				+ (System.currentTimeMillis() - start));
		}
	}
	public static void main(String[] args) throws Exception{
		PreparedStatementTest pt = new PreparedStatementTest();
		pt.initParam("mysql.ini");
		pt.insertUseStatement();
		pt.insertUsePrepare();
	}
}

 PreparedStatement可以防止SQL注入

把jdbc_test表中的jdbc_desc数据类型改成varchar(255)

 

import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.io.*;
import java.sql.*;
public class LoginFrame{
	private final String PROP_FILE = "mysql.ini";
	private String driver;
	// url是数据库的服务地址
	private String url;
	private String user;
	private String pass;
	// 登录界面的GUI组件
	private JFrame jf = new JFrame("登录");
	private JTextField userField = new JTextField(20);
	private JTextField passField = new JTextField(20);
	private JButton loginButton = new JButton("登录");
	public void init()throws Exception{
		Properties connProp = new Properties();
		connProp.load(new FileInputStream(PROP_FILE));
		driver = connProp.getProperty("driver");
		url = connProp.getProperty("url");
		user = connProp.getProperty("user");
		pass = connProp.getProperty("pass");
		// 加载驱动
		Class.forName(driver);
		// 为登录按钮添加事件监听器
		loginButton.addActionListener(e -> {
			// 登录成功则显示“登录成功”
			if (validate(userField.getText(), passField.getText())){
				JOptionPane.showMessageDialog(jf, "登录成功");
			}else{// 否则显示“登录失败”
				JOptionPane.showMessageDialog(jf, "登录失败");
			}
		});
		jf.add(userField , BorderLayout.NORTH);
		jf.add(passField);
		jf.add(loginButton , BorderLayout.SOUTH);
		jf.pack();
		jf.setVisible(true);
	}
	private boolean validate(String userName, String userPass)
	{
		// 执行查询的SQL语句
		String sql = "select * from jdbc_test "
			+ "where jdbc_name='" + userName
			+ "' and jdbc_desc='" + userPass + "'";
		System.out.println(sql);
		try(
			Connection conn = DriverManager.getConnection(url , user ,pass);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql))
		{
			// 如果查询的ResultSet里有超过一条的记录,则登录成功
			if (rs.next())
			{
				return true;
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		return false;
	}

	//使用PreparedStatement
//	private boolean validate(String userName, String userPass){
//		try(
//			Connection conn = DriverManager.getConnection(url
//				, user ,pass);
//			PreparedStatement pstmt = conn.prepareStatement(
//				"select * from jdbc_test where jdbc_name=? and jdbc_desc=?"))
//		{
//			pstmt.setString(1, userName);
//			pstmt.setString(2, userPass);
//			try(
//				ResultSet rs = pstmt.executeQuery())
//			{
//				//如果查询的ResultSet里有超过一条的记录,则登录成功
//				if (rs.next()){
//					return true;
//				}
//			}
//		}catch(Exception e){
//			e.printStackTrace();
//		}
//		return false;
//	}

	public static void main(String[] args) throws Exception{
		new LoginFrame().init();
	}
}

输入'or true or' 

#利用SQL语句注入后生成的SQL语句

select * from jdbc_test where jdbc_name='' or true or '' and jdbc_desc=''

使用PreparedStatement就不会有这种情况了。

注意:使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只能代替普通值,不要使用占位符参数代替表名,列名等数据库对象,更不要用占位符参数来代替SQL语句中的insert,select等关键字。

5、使用CallableStatement调用存储过程。

1、基本流程

a) 传入参数(Java程序必须为这些参数传入值)

通过CallableStatement对象的 setXxx()方法为传入参数设置值。

b) 传出参数(Java程序可以通过该参数获取存储过程里的值)

i) 调用CallableStatement对象的 registerOutParameter() 方法来注册该参数

注册CallableStatement的第三个参数是NVARCHAR类型
cstmt.registerOutParameter(3, Types.NVARCHAR);

ii) 调用CallableStatement对象的 execute() 方法来执行存储过程。

iii)通过CallableStatement对象的getXxx(int index)方法来获取传出参数的值。

2、示例

创建一个存储过程

go
create procedure add_pro(
	@a int, 
	@b int, 
	@out int output
	)
	as
	begin 
	set @out = @a+@b;
	end
go

查看存储过程

go
declare @out int 
exec add_pro 1,2,@out output
select @out
go

 

import java.util.*;
import java.io.*;
import java.sql.*;
public class CallableStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void callProcedure()throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url
				, user , pass);
			// 使用Connection来创建一个CallableStatment对象
			CallableStatement cstmt = conn.prepareCall(
				"{call add_pro(?,?,?)}"))
		{
			cstmt.setInt(1, 4);
			cstmt.setInt(2, 5);
			// 注册CallableStatement的第三个参数是int类型
			cstmt.registerOutParameter(3, Types.INTEGER);//  Types  定义用于标识通用SQL类型的常量的类,称为JDBC类型。 
			// 执行存储过程
			cstmt.execute();
			// 获取,并输出存储过程传出参数的值。
			System.out.println("执行结果是: " + cstmt.getInt(3));
		}
	}
	public static void main(String[] args) throws Exception{
		CallableStatementTest ct = new CallableStatementTest();
		ct.initParam("mysql.ini");
		ct.callProcedure();
	}
}

 3、一个不带参数的存储过程

在创建一个表S

use select_test;
--表2.1  学生表
create table S(
	SNo varchar(2) primary key,--学号
	SN nvarchar(10) not null,--姓名
	Sex nchar(1) not null,--系别
	Age tinyint not null,--年龄
	Dept nvarchar(20) not null--系别
)
insert S(SNo,SN,Sex,Age,Dept)
values
('S1','赵亦','女',17,'计算机'),
('S2','钱尔','男',18,'信息'),
('S3','孙珊','女',20,'信息'),
('S4','李思','男',21,'自动化'),
('S5','周武','男',19,'计算机'),
('S6','吴丽','女',20,'自动化')

 存储过程

--创建存储过程
use select_test
--不带参数的存储过程
go
create procedure MyProc 
as 
select * from S where Sex='男'
go

--执行存储过程
go 
exec MyProc
go

--查看存储过程
go 
exec sp_helptext MyProc
go

--删除存储过程
drop procedure MyProc;

 

 

import java.util.*;
import java.io.*;
import java.sql.*;
public class CallableStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void callProcedure()throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass);
			// 使用Connection来创建一个CallableStatment对象
			CallableStatement cstmt = conn.prepareCall("{call MyProc()}")
		){
			// 执行存储过程
			cstmt.execute();
			//输出结果集
			ResultSet resultSet = cstmt.getResultSet();
			ResultSetMetaData rsmd = resultSet.getMetaData();
			int columnCount = rsmd.getColumnCount();
			for(int i = 1; i <= columnCount; i++) {
				System.out.print(rsmd.getColumnName(i)+"\t");
			}System.out.println();
			while(resultSet.next()) {
				for(int i = 1; i <= columnCount; i++) {
					System.out.print(resultSet.getObject(i)+"\t");
				}
				System.out.println();
			}
		}
	}
	public static void main(String[] args) throws Exception{
		CallableStatementTest ct = new CallableStatementTest();
		ct.initParam("mysql.ini");
		ct.callProcedure();
	}
}

4、具有参数的存储过程,想S数据表中插入一条记录,新记录的值由参数提供

创建一个存储过程

--创建存储过程
use select_test
--具有参数的存储过程,想S数据表中插入一条记录,新记录的值由参数提供
go
create procedure insertRecord(
	@sno varchar(6),
	@sn nvarchar(10),
	@sex nchar(1),
	@age int,
	@dept nvarchar(20)
)
as 
insert into S values (@sno, @sn , @sex, @age, @dept)
go

--执行存储过程
select * from S
go
exec  insertRecord @sno='S7', @sn='王大利' ,@sex='男', @age=18, @dept='计算机系'
go
select * from S

--查看存储过程
go 
exec sp_helptext insertRecord
go

--删除存储过程
drop procedure insertRecord;

 

import java.util.*;
import java.io.*;
import java.sql.*;
public class CallableStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void callProcedure()throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass);
			// 使用Connection来创建一个CallableStatment对象
			CallableStatement cstmt = conn.prepareCall("{call insertRecord(?,?,?,?,?)}")
		){
			cstmt.setObject(1, "S7");
			cstmt.setObject(2, "王大利");
			cstmt.setObject(3, "男");
			cstmt.setObject(4, "18");
			cstmt.setObject(5, "计算机系");
			// 执行存储过程
			try {
				cstmt.execute();
				System.out.println("执行成功");
			}catch (Exception e) {
				System.out.println("执行失败");
				e.printStackTrace();
			}
			
			
		}
	}
	public static void main(String[] args) throws Exception{
		CallableStatementTest ct = new CallableStatementTest();
		ct.initParam("mysql.ini");
		ct.callProcedure();
	}
}

 5、定义具有参数默认值的存储过程, 向S表中插入一条记录,如果没有为提供系别,新记录的值由默认参数提供

创建存储过程

--创建存储过程
use select_test
--定义具有参数默认值的存储过程, 向S表中插入一条记录,新记录的值有参数提供,如果没有为提供系别Dept的值时,由参数默认值代替
go
create procedure InsertRecordDefa(
	@sno varchar(6),
	@sn nvarchar(10),
	@sex nchar(1),
	@age int,
	@dept nvarchar(20)='无'

)
as 
insert into S values (@sno, @sn , @sex, @age, @dept)
go

--执行存储过程
select * from S
go
exec  InsertRecordDefa @sno='S9', @sn='高平爷爷' ,@age=68 ,@sex='男'
go
select * from S


--查看存储过程
go 
exec sp_helptext InsertRecordDefa
go

--删除存储过程
drop procedure InsertRecordDefa;

 

 

import java.util.*;
import java.io.*;
import java.sql.*;
public class CallableStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void callProcedure()throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass);
			// 使用Connection来创建一个CallableStatment对象
			CallableStatement cstmt = conn.prepareCall("{call InsertRecordDefa(?,?,?,?)}")
		){
			cstmt.setObject(1, "S9");
			cstmt.setObject(2, "高平爷爷");
			cstmt.setObject(3, "男");
			cstmt.setObject(4, "68");
			// 执行存储过程
			try {
				cstmt.execute();
				System.out.println("执行成功");
			}catch (Exception e) {
				System.out.println("执行失败");
				e.printStackTrace();
			}
			
			
		}
	}
	public static void main(String[] args) throws Exception{
		CallableStatementTest ct = new CallableStatementTest();
		ct.initParam("mysql.ini");
		ct.callProcedure();
	}
}

 

 注意顺序要和创建存储过程的顺序来,不然是错误的。

但是如果你是用的脚本执行的话,前后位置顺序是没有影响的。 

go
exec  InsertRecordDefa @sn='高平爷爷',@sno='S9',@age=68 ,@sex='男'
go

6、能够返回值的存储过程  从S表中根据学号查询某一个同学的姓名和系别

 创建存储过程

--创建存储过程
use select_test
--能够返回值的存储过程  重S表中根据学号查询某一个同学的姓名和系别,查询结果由@sn和@dept返回
go
create procedure QueryTeach(
	@sno varchar(6),
	@sn nvarchar(10) output,
	@dept nvarchar(20) output
)
as 
	select @sn=SN, @dept=Dept
	from S
	where sNO = @sno
go

--执行存储过程
go
declare @sn nvarchar(10)
declare @dept nvarchar(20)
exec QueryTeach  'S1', @sn output , @dept output
select '姓名' = @sn,  '系别'=@dept
go

--查看存储过程
go 
exec sp_helptext QueryTeach
go

--删除存储过程
drop procedure QueryTeach;

 

import java.util.*;
import java.io.*;
import java.sql.*;
public class CallableStatementTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void callProcedure()throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass);
			// 使用Connection来创建一个CallableStatment对象
			CallableStatement cstmt = conn.prepareCall("{call QueryTeach(?,?,?)}")
		){
			cstmt.setObject(1, "S1");
			//调用registerOutParameter注册参数
			cstmt.registerOutParameter(2, Types.NVARCHAR);
			cstmt.registerOutParameter(3, Types.NVARCHAR);
			//执行存储过程
			cstmt.execute();
			
			//调用CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值
//			System.out.println("姓名:"+cstmt.getObject(2));
//			System.out.println("系别:"+cstmt.getObject(3));
			System.out.println("姓名:"+cstmt.getString(2));
			System.out.println("系别:"+cstmt.getString(3));
		}
	}
	public static void main(String[] args) throws Exception{
		CallableStatementTest ct = new CallableStatementTest();
		ct.initParam("mysql.ini");
		ct.callProcedure();
	}
}

获取指定传出参数的值

 System.out.println("姓名:"+cstmt.getObject(2));
 System.out.println("系别:"+cstmt.getObject(3));
System.out.println("姓名:"+cstmt.getString(2));
System.out.println("系别:"+cstmt.getString(3));

 上面两种方法都可以,只是getString()方法简单些,程序自动推断类型。

 

三、管理结果集


          ResultSet类是一种数据库查询结果存储类,就是当查询数据库的时候,可以将查询的结果放在具体的ResultSet对象中,其实我们把这种存储查询结果的ResultSet对象叫做ResultSet结果集。比如我们用aaa表示一个ResultSet对象。
          那么查询的结果无非就是一些符合查询条件的记录集,ResultSet结果集有一个索引指针,最初这个指针是指向第一条记录的前一个位置,也就是没有指向任何内容,使用aaa.next()方法就会使指针往后移动指向下一个记录,所以一定要先执行一次next()函数才会让指针指向第一条记录。
          一条记录可能会有好几个属性的内容,那么我们可以使用getXXX(int index)方法类获得具体属性的值,XXX代表以什么样的数据类型方式来读取内容,当指针指向一条记录的时候,比如这条记录的内容就是:0001   张三   3岁。我们可以使用aaa.getString(1)来获得内容0001,使用getString(2)来获得内容张三,值得注意:ResultSet的一条记录的索引位置是从1开始的,而不是从0开始
          查询数据库时,返回的是一个二维的结果集,我们需要用到ResultSet来遍历结果集,获取每一行的数据。
          结果集读取数据的方法主要是getXXX() ,他的参数可以是整型表示第几列(是从1开始的),还可以是列名,返回的是对应的XXX类型的值。如果对应那列是空值,XXX是对象的话返回XXX型的空值,如果XXX是数字类型,如Float等则返回0,boolean返回false。
          使用getString()可以返回所有的列的值,不过返回的都是字符串类型的。XXX可以代表的类型有:基本的数据类型如整型(int),布尔型(Boolean),浮点型(Float,Double)等,比特型(byte),还包括一些特殊的类型,如:日期类型(java.sql.Date),时间类型(java.sql.Time),时间戳类型 (java.sql.Timestamp),大数型(BigDecimal和BigInteger等)等。

String  getString(int columnIndex)  以java编程语言中String的形式获取此ResultSet对象的当前行中指定列的值
String  getString(String columnLabel)  以java编程语言中String的形式获取此ResultSet对象的当前行中指定列的值

          还可以使用getArray(int colindex/String columnname),通过这个方法获得当前行中,colindex所在列的元素组成的对象的数组。
          使用getAsciiStream(int colindex/String colname)可以获得该列对应的当前行的ascii流。也就是说所有的getXXX方法都是对当前行进行操作。 
          但是结果集并不仅仅具有存储功能,他同时还具有操纵数据功能,可能完成对数据的更新等。 

记录移动指针的方法:
> boolean next(): 将指针从当前位置向前移动一行(此方法是使指针向下一条记录移动)。
> boolean previous(): 将指针移动到此 ResultSet对象中的上一行。

> boolean absolute(int row): 将光标移动到此 ResultSet对象中的给定行号。(定位成功返回true,不成功返回false,返回值为false,则指针不会移动)

> void afterLast(): 将指针移动到这个 ResultSet对象的末尾,就在最后一行之后。 
> void beforeFirst(): 将指针移动到这个 ResultSet对象的正面,就在第一行之前。 

> boolean first(): 将指针移动到此 ResultSet对象中的第一行。 
> boolean last(): 将指针移动到此 ResultSet对象中的最后一行。 

> boolean relative(int rows): 将光标移动到正或负的相对行数(相对定位方法,参数值可正可负,参数为正,指针从当前位置向下移动指定值,参数为负,指针从当前位置向上移动指定值)。

其他方法:
> int getRow(): 检索当前行号。 
> void updateString(int columnIndex, String x): 使用 String值更新指定的列。 
> void updateString(String columnLabel, String x): 使用 String值更新指定的列。 
> void updateRow(): 使用此 ResultSet对象的当前行的新内容更新底层数据库。 

以默认方式打开的ResultSet是不可更新的,如果希望希望创建可更新的ResultSet, 则必须在创建Statement或PreparedStatement时传入额外的参数。Connection在创建Statement或PreparedStatement时还可以额外传入如下两个参数

➢ resultSetType:: 控制ResultSet的类型,该参数可以取如下三个值。

1、ResultSet.TYPE_FORWARD_ONLY: 该常量控制记录指针只能向前移动。这是JDK 1.4 以前的默认值。
2、ResultSet.TYPE_SCROLL_INSENSITIVE: 该常量控制记录指针可以自由移动(可滚动结果集),但底层数据的改变不会影响ResultSet的内容。
3、ResultSet.TYPE_SCROLL_SENSITIVE: 该常量控制记录指针可以自由移动(可滚动结果集),而且底层数据的改变会影响ResultSet的内容。

注意:
TYPE_SCROLL_INSENSITIVE , TYPE_SCROLL_SENSITIVE两个常量的作用需要底层数据库驱动的支持对于有些数据库驱动来说,这两个常量并没有太大的区别。


➢ resultSetConcurrency: 控制ResulSet的并发类型,该参数可以接收如下两个值。
1、ResultSet.CONCLR_READ_ONLY: 该常量指示ResultSet是只读的并发模式(默认)。
2、ResultSet.CONCUR_UPDATABLE: 该常量指示ResultSet是可更新的并发模式。

下面代码通过这两个参数创建了一 个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集。

//使用Connection创建一个 PreparedStatement对象
//传入控制结果集可滚对、可更新的参数
pstmt = con.preparedStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE,  ResultSet.CONCUR_UPDATABLE: );    

需要指出的是,可更新的结果集还需要满足如下两个条件。
      ➢ 所有数据都应该来自一个表。
      ➢ 选出的数据集必须包含主键列。

通过该PreparedStatement创建的ResulSet 就是可滚功、可更新的,程序可调用ResulSet 的updateXxx(in clumnIndex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResulSet 的updateRow()方法来提交修改。
         Java8为ResulSet 添加了updateObject(String  columnLabel , 0bject x, SQLType targetSqlType) updateObject(int  columnIndex , 0bject x, SQLType targetSqlType) 两个默头方法,这两个方法可以直接用Objct来修改记录指针所指记录、特定列的值,其中 SOLType用于指定该数据列的类型。但目前最新的MySQL驱动暂不支持该方法。
 演示:

1、可滚动、可更新的结果集演示

import java.util.*;
import java.io.*;
import java.sql.*;
public class ResultSetTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void query(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url , user , pass);
			// 使用Connection来创建一个PreparedStatement对象
			// 传入控制结果集可滚动,可更新的参数。
			PreparedStatement pstmt = conn.prepareStatement(sql
				, ResultSet.TYPE_SCROLL_SENSITIVE//TYPE_SCROLL_INSENSITIVE
				, ResultSet.CONCUR_UPDATABLE);
			ResultSet rs = pstmt.executeQuery())
		{
			rs.last();
			int rowCount = rs.getRow();
			for (int i = rowCount; i > 0 ; i-- ){
				rs.absolute(i);
				System.out.println(rs.getString(1) + "\t"
					+ rs.getString(2) + "\t" + rs.getString(3));
				// 修改记录指针所有记录、第2列的值
				rs.updateString(2 , "学生名" + i);
				// 提交修改
				rs.updateRow();
			}
		}
	}
	public static void main(String[] args) throws Exception{
		ResultSetTest rt = new ResultSetTest();
		rt.initParam("mysql.ini");
		rt.query("select * from student_table");
	}
}

student_table表倒序输出,student_table表中所有记录student_name列的值都被修改了。

结果集从其使用的特点上可以分为四类,这四类的结果集的所具备的特点都是和Statement语句的创建有关,因为结果集是通过Statement语句执行后产生的,所以可以说,结果集具备何种特点,完全决定于Statement,当然我是说下面要将的四个特点,在Statement创建时包括三种类型。首先是无参数类型的,他对应的就是下面要介绍的基本的ResultSet对应的Statement。下面的代码中用到的Connection并没有对其初始化,变量conn代表的就是Connection对应的对象。Sql代表的是响应的SQL语句。 

2、四种类型


1、最基本的ResultSet 


            之所以说是最基本的ResultSet是因为,这个ResultSet他起到的作用就是完成了查询结果的存储功能,而且只能读去一次,不能够来回的滚动读取。这种结果集的创建方式如下: 

Statement st = conn.CreateStatement 
ResultSet rs = Statement.excuteQuery(sql); 

由于这种结果集不支持,滚动的读写功能所以,如果获得这样一个结果集,只能使用它里面的next()方法,逐个的读去数据。 

 2、可滚动的ResultSet类型。 


           这个类型支持前后滚动取得纪录next()、previous(),回到第一行first(),同时还支持要去的ResultSet中的第几行absolute(int n),以及移动到相对当前行的第几行relative(int n),要实现这样的ResultSet在创建Statement时用如下的方法。 
 

Statement st = conn.createStatement (int resultSetType, int resultSetConcurrency) 
ResultSet rs = st.executeQuery(sqlStr) 

其中两个参数的意义是: 
esultSetType 是设置 ResultSet 对象的类型可滚动,或者是不可滚动。取值如下: 
    ResultSet.TYPE_FORWARD_ONLY 只能向前滚动 
    ResultSet.TYPE_SCROLL_INSENSITIVE 和 Result.TYPE_SCROLL_SENSITIVE 这两个方法都能够实现任意的前后滚动,使用各种移动的 ResultSet 指针的方法。二者的区别在于前者对于修改不敏感,而后者对于修改敏感。 
resultSetConcurency 是设置 ResultSet 对象能够修改的,取值如下: 
  ResultSet.CONCUR_READ_ONLY 设置为只读类型的参数。 
  ResultSet.CONCUR_UPDATABLE 设置为可修改类型的参数。 
所以如果只是想要可以滚动的类型的 Result 只要把 Statement 如下赋值就行了。 
  Statement st = conn.createStatement(Result.TYPE_SCROLL_INSENITIVE,  ResultSet.CONCUR_READ_ONLY); 
  ResultSet rs = st.excuteQuery(sqlStr) ; 
  用这个 Statement 执行的查询语句得到的就是可滚动的 ResultSet 。 


3、可更新的ResultSet 


  这样的ResultSet对象可以完成对数据库中表的修改,但是我知道ResultSet只是相当于数据库中表的视图,所以并不时所有的ResultSet只要设置了可更新就能够完成更新的,能够完成更新的ResultSet的SQL语句必须要具备如下的属性: 
    a 、只引用了单个表。 
    b 、不含有join或者group by子句。 
    c 、那些列中要包含主关键字。 
    具有上述条件的,可更新的ResultSet可以完成对数据的修改,可更新的结果集的创建方法是: 
  Statement st = createstatement(Result.TYPE_SCROLL_INSENSITIVE,Result.CONCUR_UPDATABLE) 

4、 可保持的ResultSet 


  正常情况下如果使用Statement执行完一个查询,又去执行另一个查询时这时候第一个查询的结果集就会被关闭,也就是说,所有的Statement的查询对应的结果集是一个,如果调用Connection的commit()方法也会关闭结果集。可保持性就是指当ResultSet的结果被提交时,是被关闭还是不被关闭。JDBC2.0和1.0提供的都是提交后ResultSet就会被关闭。不过在JDBC3.0中,我们可以设置ResultSet是否关闭。要完成这样的ResultSet的对象的创建,要使用的Statement的创建要具有三个参数,这个Statement的创建方式也就是,我所说的 Statement的第三种创建方式。 
  当使用ResultSet的时候,当查询出来的数据集记录很多,有一千万条的时候,那rs所指的对象是否会占用很多内存,如果记录过多,那程序会不会把系统的内存用光呢 ?
  不会的,ResultSet表面看起来是一个记录集,其实这个对象中只是记录了结果集的相关信息,具体的记录并没有存放在对象中,具体的记录内容知道你通过next方法提取的时候,再通过相关的getXXXXX方法提取字段内容的时候才能从数据库中得到,这些并不会占用内存,具体消耗内存是由于你将记录集中的数据提取出来加入到你自己的集合中的时候才会发生,如果你没有使用集合记录所有的记录就不会发生消耗内存厉害的情况。

注:在JDK1.4以前,默认打开的ResultSet是不可滚动的,必须在创建Statement或PrepareStatement时传入额外的参数。重Java5.0以后,默认打开的ResultSet就是可滚动的,无序传入额外的参数。

 

 

 2、处理Blob类型数据

创建一个数据表

create table img_table
(
	img_id int identity(1,1) primary key,
	img_name varchar(255),
	--创建一个image类型的数据列,用于保存图片数据
	img_data image
)
import java.sql.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Properties;
import java.util.ArrayList;
import java.io.*;
import javax.swing.filechooser.FileFilter;
public class BlobTest{
	JFrame jf = new JFrame("图片管理程序");
	private static Connection conn;
	private static PreparedStatement insert;
	private static PreparedStatement query;
	private static PreparedStatement queryAll;
	// 定义一个DefaultListModel对象
	private DefaultListModel<ImageHolder> imageModel
		= new DefaultListModel<>();
	private JList<ImageHolder> imageList = new JList<>(imageModel);
	private JTextField filePath = new JTextField(26);
	private JButton browserBn = new JButton("...");
	private JButton uploadBn = new JButton("上传");
	private JLabel imageLabel = new JLabel();
	// 以当前路径创建文件选择器
	JFileChooser chooser = new JFileChooser(".");
	// 创建文件过滤器
	ExtensionFileFilter filter = new ExtensionFileFilter();
	static{
		try{
			Properties props = new Properties();
			props.load(new FileInputStream("mysql.ini"));
			String driver = props.getProperty("driver");
			String url = props.getProperty("url");
			String user = props.getProperty("user");
			String pass = props.getProperty("pass");
			Class.forName(driver);
			// 获取数据库连接
			conn = DriverManager.getConnection(url , user , pass);
			// 创建执行插入的PreparedStatement对象,
			// 该对象执行插入后可以返回自动生成的主键
			insert = conn.prepareStatement("insert into img_table"
				+ " values(?,?)" , Statement.RETURN_GENERATED_KEYS);
			// 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片
			query = conn.prepareStatement("select img_data from img_table"
				+ " where img_id=?");
			queryAll = conn.prepareStatement("select img_id, "
				+ " img_name from img_table");
		}catch (Exception e){
			e.printStackTrace();
		}
	}
	public void init()throws SQLException{
		// -------初始化文件选择器--------
		filter.addExtension("jpg");
		filter.addExtension("jpeg");
		filter.addExtension("gif");
		filter.addExtension("png");
		filter.setDescription("图片文件(*.jpg,*.jpeg,*.gif,*.png)");
		chooser.addChoosableFileFilter(filter);
		// 禁止“文件类型”下拉列表中显示“所有文件”选项。
		chooser.setAcceptAllFileFilterUsed(false);
		// ---------初始化程序界面---------
		fillListModel();
		filePath.setEditable(false);
		// 只能单选
		imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		JPanel jp = new JPanel();
		jp.add(filePath);
		jp.add(browserBn);
		browserBn.addActionListener(event -> {
			// 显示文件对话框
			int result = chooser.showDialog(jf , "浏览图片文件上传");
			// 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮
			if(result == JFileChooser.APPROVE_OPTION){
				filePath.setText(chooser.getSelectedFile().getPath());
			}
		});
		jp.add(uploadBn);
		uploadBn.addActionListener(avt -> {
			// 如果上传文件的文本框有内容
			if (filePath.getText().trim().length() > 0){
				// 将指定文件保存到数据库
				upload(filePath.getText());
				// 清空文本框内容
				filePath.setText("");
			}
		});
		JPanel left = new JPanel();
		left.setLayout(new BorderLayout());
		left.add(new JScrollPane(imageLabel) , BorderLayout.CENTER);
		left.add(jp , BorderLayout.SOUTH);
		jf.add(left);
		imageList.setFixedCellWidth(160);
		jf.add(new JScrollPane(imageList) , BorderLayout.EAST);
		imageList.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				// 如果鼠标双击
				if (e.getClickCount() >= 2){
					// 取出选中的List项
					ImageHolder cur = (ImageHolder)imageList.
					getSelectedValue();
					try{
						// 显示选中项对应的Image
						showImage(cur.getId());
					}catch (SQLException sqle){
						sqle.printStackTrace();
					}
				}
			}
		});
		jf.setSize(620, 400);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
	}
	// ----------查找img_table填充ListModel----------
	public void fillListModel()throws SQLException{

		try(
			// 执行查询
			ResultSet rs = queryAll.executeQuery())
		{
			// 先清除所有元素
			imageModel.clear();
			// 把查询的全部记录添加到ListModel中
			while (rs.next()){
				imageModel.addElement(new ImageHolder(rs.getInt(1)
					,rs.getString(2)));
			}
		}
	}
	// ---------将指定图片放入数据库---------
	public void upload(String fileName){
		// 截取文件名
		String imageName = fileName.substring(fileName.lastIndexOf('\\')
			+ 1 , fileName.lastIndexOf('.'));
		File f = new File(fileName);
		try(
			InputStream is = new FileInputStream(f))
		{
			// 设置图片名参数
			insert.setString(1, imageName);
			// 设置二进制流参数
			insert.setBinaryStream(2, is , (int)f.length());
			int affect = insert.executeUpdate();
			if (affect == 1){
				// 重新更新ListModel,将会让JList显示最新的图片列表
				fillListModel();
			}
		}catch (Exception e){
			e.printStackTrace();
		}
	}
	// ---------根据图片ID来显示图片----------
	public void showImage(int id)throws SQLException{
		// 设置参数
		query.setInt(1, id);
		try(
			// 执行查询
			ResultSet rs = query.executeQuery())
		{
			if (rs.next()){
				// 取出Blob列
				Blob imgBlob = rs.getBlob(1);
				// 取出Blob列里的数据
				ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L
					,(int)imgBlob.length()));
				imageLabel.setIcon(icon);
			}
		}
	}
	public static void main(String[] args)throws SQLException{
		new BlobTest().init();
	}
}
// 创建FileFilter的子类,用以实现文件过滤功能
class ExtensionFileFilter extends FileFilter{
	private String description = "";
	private ArrayList<String> extensions = new ArrayList<>();
	// 自定义方法,用于添加文件扩展名
	public void addExtension(String extension){
		if (!extension.startsWith(".")){
			extension = "." + extension;
			extensions.add(extension.toLowerCase());
		}
	}
	// 用于设置该文件过滤器的描述文本
	public void setDescription(String aDescription){
		description = aDescription;
	}
	// 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本
	public String getDescription(){
		return description;
	}
	// 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件
	public boolean accept(File f){
		// 如果该文件是路径,接受该文件
		if (f.isDirectory()) return true;
		// 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写)
		String name = f.getName().toLowerCase();
		// 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受。
		for (String extension : extensions){
			if (name.endsWith(extension)){
				return true;
			}
		}
		return false;
	}
}
// 创建一个ImageHolder类,用于封装图片名、图片ID
class ImageHolder{
	// 封装图片的ID
	private int id;
	// 封装图片的图片名字
	private String name;
	public ImageHolder(){}
	public ImageHolder(int id , String name){
		this.id = id;
		this.name = name;
	}
	// id的setter和getter方法
	public void setId(int id){
		this.id = id;
	}
	public int getId(){
		return this.id;
	}
	// name的setter和getter方法
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
	// 重写toString方法,返回图片名
	public String toString(){
		return name;
	}
}

3、使用ResultSetMetaData分析结果集

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import java.util.*;
import java.io.*;
import java.sql.*;
public class QueryExecutor{
	JFrame jf = new JFrame("查询执行器");
	private JScrollPane scrollPane;
	private JButton execBn = new JButton("查询");
	// 用于输入查询语句的文本框
	private JTextField sqlField = new JTextField(45);
	private static Connection conn;
	private static Statement stmt;
	// 采用静态初始化块来初始化Connection、Statement对象
	static{
		try{
			Properties props = new Properties();
			props.load(new FileInputStream("mysql.ini"));
			String drivers = props.getProperty("driver");
			String url = props.getProperty("url");
			String username = props.getProperty("user");
			String password = props.getProperty("pass");
			// 加载数据库驱动
			Class.forName(drivers);
			// 取得数据库连接
			conn = DriverManager.getConnection(url, username, password);
			stmt = conn.createStatement();
		}catch (Exception e){
			e.printStackTrace();
		}
	}
	// --------初始化界面的方法---------
	public void init(){
		JPanel top = new JPanel();
		top.add(new JLabel("输入查询语句:"));
		top.add(sqlField);
		top.add(execBn);
		// 为执行按钮、单行文本框添加事件监听器
		execBn.addActionListener(new ExceListener());
		sqlField.addActionListener(new ExceListener());
		jf.add(top , BorderLayout.NORTH);
		jf.setSize(680, 480);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
	}
	// 定义监听器
	class ExceListener implements ActionListener{
		public void actionPerformed(ActionEvent evt){
			// 删除原来的JTable(JTable使用scrollPane来包装)
			if (scrollPane != null){
				jf.remove(scrollPane);
			}try(
				// 根据用户输入的SQL执行查询
				ResultSet rs = stmt.executeQuery(sqlField.getText()))
			{
				// 取出ResultSet的MetaData
				ResultSetMetaData rsmd = rs.getMetaData();
				Vector<String> columnNames =  new Vector<>();
				Vector<Vector<String>> data = new Vector<>();
				// 把ResultSet的所有列名添加到Vector里
				for (int i = 0 ; i < rsmd.getColumnCount(); i++ ){
					columnNames.add(rsmd.getColumnName(i + 1));
				}
				// 把ResultSet的所有记录添加到Vector里
				while (rs.next()){
					Vector<String> v = new Vector<>();
					for (int i = 0 ; i < rsmd.getColumnCount(); i++ ){
						v.add(rs.getString(i + 1));
					}
					data.add(v);
				}
				// 创建新的JTable
				JTable table = new JTable(data , columnNames);
				scrollPane = new JScrollPane(table);
				// 添加新的Table
				jf.add(scrollPane);
				// 更新主窗口
				jf.validate();
			}catch (Exception e){
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args){
		new QueryExecutor().init();
	}
}

 

有一个学生表 

  

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
public class Main {
	@Test
	public void function() throws SQLException, ClassNotFoundException {
		Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
		try(
				Connection conn = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433;databaseName=第六次上机","sa","123456");
				Statement stmt = conn.createStatement();
				ResultSet rs = stmt.executeQuery("select * from 学生表")
				){
			ResultSetMetaData rsmd = rs.getMetaData();
			int columnCount = rsmd.getColumnCount(); //列数

			for(int i = 1; i <= columnCount; i++) {
				System.out.print(rsmd.getColumnName(i)+"\t");
			}
			System.out.println();

			// 迭代输出ResultSet对象
			while (rs.next()){
				// 依次输出每列的值
				for (int i = 0 ; i < columnCount ; i++ ){
					System.out.print(rs.getString(i + 1) + "\t");
				}
				System.out.print("\n");
			}
		}

	}
}

四、Java7的RowSet1.1 

RowSet接口继承了 Resultset接口,RowSet接口下包含 JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowset和 WebRowSet常用子接口。除了 JdbcRowSet需要保持与数据库的连接之外,其余4个子接口都是离线的 RowSet。无须保持与数据库的连接。

与 Resultset相比,RowSet默认是可滚动、可更新、可序列化的结果集,而且作为 JavaBean使用,因此能方便地在网络上传输,用于同步两端的数据。对于离线 RowSet而言,程序在创建 RowSet时已把数据从底层数据库读取到了内存,因此可以充分利用计算机的内存,从而降低数据库服务器的负载,提高程序性能。

 CachedRowSet及其子接口都代表了离线 RowSet,它们都不需要底层数据库连接。

1、Java7新增的 RowSetFactory与 RowSet

在Java6.0以前,RowSet及其5个子接口都已经存在了,但在实际编程中的应用一直并不广泛,这是因为Java公开API并没有为 RowSet及其各子接口提供实现类,而且也没有提供公开的方法来创建 RowSet及其各子接口的实例。

实际上,Java6.0已经在 com.sun.rowset包下提供了 JdbcRowSetlmpl、CachedRowSetlmpl、WebRowSetlmpl、FilteredRowSetlmpl和 JoinRowSetlmpl五个实现类,它们代表了各种 RowSet接口的实现类。

以 JdbcRowSet为例,在Java6.0及以前的版本中,如果程序需要使用 JdbcRowSet,则必须通过调用 JdbcRowSetlmpl的构造器来创建 JdbcRowSet实例,JdbcRowSetlmpl提供了如下常用构造器。

>>  JdbcRowSetlmpl():创建一个默认的 JdbcRowSetlmpl对象。

>>  JdbcRowSetlmple(Connection conn):以给定的 Connection对象作为数据库连接来创建JdbcRowSetlmpl对象

>>  JdbcRowSetlmpl(Resultset rs):创建一个包装 Resultset对象的 JdbcRowSetImpl对象。

除此之外,RowSet接口中定义了如下常用方法。

>>  setUrl(String url):设置该 RowSet要访问的数据库的URL。

>>  setUsername(String name):设置该 RowSet要访问的数据库的用户名。

>>  setPassword(String password):设置该 RowSet要访问的数据库的密码。

>>  setCommand(String sql):设置使用该sql语句的查询结果来装填该 RowSet。

>>  execute():执行查询。

>>  populate(Resultset rs):让该 RowSet直接包装给定的 Resultset对象。

 

通过 JdbcRowSet的构造器和上面几个方法不难看出,为 JdbcRowSet装填数据有如下两种方式。

>>> 创建 JdbcRowSetlmpl时,直接传入Resultset对象。

>>> 使用 execute方法来执行SQL查询,用查询返回的数据来装填 RowSet。

对于第二种方式来说,如果创建 JabcRowSetlmpl时已经传入了 Connection参数,则只要先调用setCommand(String sql)指定SQL查询语句,接下来就可调用 execute方法执行查询了。如果创建JdbcRowSetlmpl时没有传入 Connection参数,则先要为 JdbcRowSet设置数据库的URL、用户名、密码等连接信息。

下面程序通过 JdbcRowSetlmpl示范了使用 JdbcRowSet的可滚动、可修改特性。

import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.rowset.*;
import com.sun.rowset.*;
public class JdbcRowSetTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public void update(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url , user , pass);
			// 创建JdbcRowSetImpl对象
			JdbcRowSet jdbcRs = new JdbcRowSetImpl(conn))   // ①
		{
			// 设置SQL查询语句
			jdbcRs.setCommand(sql);
			// 执行查询
			jdbcRs.execute();
			
			//将光标移动到这个ResultSet对象的最后一行。 如果结果集不包含行,则此方法不起作用。 
			jdbcRs.afterLast();
			// 向前滚动结果集
			/*previous()  将光标移动到此ResultSet对象中的上一行。 
			当调用previous方法返回false时,光标位于第一行之前。 任何调用需要当前行的ResultSet方法将导致抛出SQLException 。 
			如果当前行的输入流已打开,则调用方法previous会隐式关闭它。 读取新行时,将清除ResultSet对象的警告更改。 
			结果 
			true如果光标现在位于有效的行上; false如果光标位于第一行之前 */
			while (jdbcRs.previous()){
				System.out.println(jdbcRs.getString(1)
					+ "\t" + jdbcRs.getString(2)
					+ "\t" + jdbcRs.getString(3));
				if (jdbcRs.getInt("student_id") == 3){
					// 修改指定记录行
					jdbcRs.updateString("student_name", "孙悟空");
					jdbcRs.updateRow();
				}
			}
		}
	}
	public static void main(String[] args)throws Exception{
		JdbcRowSetTest jt = new JdbcRowSetTest();
		jt.initParam("mysql.ini");
		jt.update("select * from student_table");
	}
}

上面程序中①号粗体字代码创建一个 JdbcRowSetlmpl实例,这就是一个 JdbcRowSet对象。接下来的粗体字代码则示范了 JdbcRowSet的可滚动、可修改的特性。

编译该程序,编译器将会在①号粗体字代码处发出警告:JdbcRowSetlmpl是内部专用API,可能会在未来发行版中删除。这就是直接使用 JdbcRowSetlmpl的代价。

运行上面程序,一切正常。JdbcRowSet是一个可滚动、可修改的结果集,因此底层数据表中相应的记录也被修改了。

需要说明的是,使用 JdbcRowSetlmpl除了编译器会发出警告之外,还有如下坏处。

         a)程序直接与 JdbcRowSetlmpl实现类耦合,不利于后期的升级、扩展。

         b)JdbcRowSetlmpl实现类不是一个公开的APl,未来可能被删除。

正是因为上面两个原因,所以在Java6.0时代,RowSet并未得到广泛应用。Java7新增了RowSetProvider类和 RowSetFactory接口,其中 RowSetProvider负责创建 RowSetFactory,而RowSetFactory则提供了如下方法来创建 RowSet实例

>>  CachedRowSet createCachedRowSet():创建一个默认的 CachedRowSet。

>>  FilteredRowSet createFilteredRowSet():创建一个默认的 FilteredRowSet。

>>  JdbcRowSet createJdbcRowSet():创建一个默认的 JdbcRowSet。

>>  JoinRowSet  createJoinRowSet():创建一个默认的 JoinRowSet。

>>  WebRowSet createWebRowSet():创建一个默认的 WebRowSet 。

通过使用 RowSetFactory,就可以把应用程序与 RowSet实现类分离开,避免直接使用 JabcRowSetlmpl等非公开的API,也更有利于后期的升级、扩展。

RowSetProvider------创建------->RowSetFactory------创建------->RowSet实例

下面程序使用 RowSetFactory来创建 JdbcRowSet实例。

import java.util.*;
import java.io.*;
import javax.sql.rowset.*;
public class RowSetFactoryTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public void update(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		// 使用RowSetProvider创建RowSetFactory
		RowSetFactory factory = RowSetProvider.newFactory();
		try(
				// 使用RowSetFactory创建默认的JdbcRowSet实例
				JdbcRowSet jdbcRs = factory.createJdbcRowSet())
		{
			// 设置必要的连接信息
			jdbcRs.setUrl(url);
			jdbcRs.setUsername(user);
			jdbcRs.setPassword(pass);
			// 设置SQL查询语句
			jdbcRs.setCommand(sql);
			// 执行查询
			jdbcRs.execute();
			jdbcRs.afterLast();
			// 向前滚动结果集
			while (jdbcRs.previous()){
				System.out.println(jdbcRs.getString(1)
						+ "\t" + jdbcRs.getString(2)
						+ "\t" + jdbcRs.getString(3));
				if (jdbcRs.getInt("student_id") == 3){
					// 修改指定记录行
					jdbcRs.updateString("student_name", "孙悟空");
					jdbcRs.updateRow();
				}
			}
		}
	}
	public static void main(String[] args)throws Exception{
		RowSetFactoryTest jt = new RowSetFactoryTest();
		jt.initParam("mysql.ini");
		jt.update("select * from student_table");
	}
}

 2、离线RowSet

在使用 Resultset的时代,程序查询得到 Resultset之后必须立即读取或处理它对应的记录,否则一旦 Connection关闭,再去通过 Resultset读取记录就会引发异常。在这种模式下,JDBC编程十分痛苦假设应用程序架构被分为两层:数据访问层和视图显示层,当应用程序在数据访问层查询得到Resultset之后,对 Resultset的处理有如下两种常见方式。

1、使用迭代访问 Resultset里的记录,并将这些记录转换成 JavaBean,再将多个 JavaBean封装成个List集合,也就是完成“Resultset→ JavaBean集合”的转换。转换完成后可以关闭 Connection等资源,然后将 Javabean集合传到视图显示层,视图显示层可以显示查询得到的数据

2、直接将 Resultset传到视图显示层——这要求当视图显示层显示数据时,底层 Connection必须直处于打开状态,否则 Resultset无法读取记录。

第一种方式比较安全,但编程十分烦琐;第二种方式则需要 Connection一直处于打开状态,这不仅不安全,而且对程序性能也有较大的影响。

通过使用离线 RowSet可以十分“优雅”地处理上面的问题,离线 RowSet会直接将底层数据读入内存中,封装成 RowSet对象,而 RowSet对象则完全可以当成 JavaBean来使用。因此不仅安全,而且编程十分简单。CachedRowSet是所有离线 RowSet的父接口,因此下面以 CachedRowSet为例进行介绍。

看下面程序。

import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.rowset.*;
public class CachedRowSetTest{
	private static String driver;
	private static String url;
	private static String user;
	private static String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public CachedRowSet query(String sql)throws Exception{
		// 加载驱动
		Class.forName(driver);
		// 获取数据库连接
		Connection conn = DriverManager.getConnection(url , user , pass);
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt.executeQuery(sql);
		// 使用RowSetProvider创建RowSetFactory
		RowSetFactory factory = RowSetProvider.newFactory();
		// 创建默认的CachedRowSet实例
		CachedRowSet cachedRs = factory.createCachedRowSet();
		// 使用ResultSet装填RowSet
		cachedRs.populate(rs);    // ①
		// 关闭资源
		rs.close();
		stmt.close();
		conn.close();
		return cachedRs;
	}
	public static void main(String[] args)throws Exception{
		CachedRowSetTest ct = new CachedRowSetTest();
		ct.initParam("mysql.ini");
		CachedRowSet rs = ct.query("select * from student_table");
		rs.afterLast();
		// 向前滚动结果集
		while (rs.previous()){
			System.out.println(rs.getString(1)
				+ "\t" + rs.getString(2)
				+ "\t" + rs.getString(3));
			if (rs.getInt("student_id") == 3){
				// 修改指定记录行
				rs.updateString("student_name", "孙悟空");
				rs.updateRow();
			}
		}
		// 重新获取数据库连接
		Connection conn = DriverManager.getConnection(url, user , pass);
		conn.setAutoCommit(false);
		// 把对RowSet所做的修改同步到底层数据库
		rs.setTableName("student_table");//要加上 不然报错 java.sql.SQLException: writeData 无法确定表名
		rs.acceptChanges(conn);
	}
}

rs.setTableName("student_table");//要加上 不然报错 java.sql.SQLException:   

 上面程序中的①号粗体字代码调用了 RowSet的 populate(Resultset rs)方法来包装给定的 Resultset,接下来的粗体字代码关闭了 ResultSet、Statement、Connection等数据库资源。如果程序直接返回Resultset,那么这个 Resultset无法使用一一因为底层的 Connection已经关闭;但程序返回的是CachedRowSet,它是一个离线 RowSet,因此程序依然可以读取、修改 Row Set中的记录。

运行该程序,可以看到在 Connection关闭的情况下,程序依然可以读取、修改 RowSet里的记录。为了将程序对离线 RowSet所做的修改同步到底层数据库,程序在调用 RowSet的 acceptChanges()方法时必须传入 Connection。

 3、使用RowSet的查询分页

由于 CachedRowSet会将数据记录直接装载到内存中,因此如果SQL查询返回的记录过大CachedRowSet将会占用大量的内存,在某些极端的情况下,它甚至会直接导致内存溢出。

为了解决该问题,Cached RowSet提供了分页功能。所谓分页功能就是一次只装载 Resultset里的某几条记录,这样就可以避免 CachedRowSet占用内存过大的问题

CachedRowSet提供了如下方法来控制分页。

>>  populate(Resultset rs,int startRow):使用给定的 Resultset装填 RowSet,从 Resultset的第 startRow条记录开始装填。

>>  setPageSize(int pageSize):设置 CachedRowSet每次返回多少条记录。

>>  previousPage():在底层 Resultset可用的情况下,让 CachedRowSet读取上一页记录。

>> nextPage():在底层 Resultset可用的情况下,让 CachedRowSet读取下一页记录

下面程序示范了 CachedRow Set的分页支持

import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.rowset.*;
public class CachedRowSetPage{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public CachedRowSet query(String sql , int pageSize, int page)throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url , user , pass);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql))
		{
			// 使用RowSetProvider创建RowSetFactory
			RowSetFactory factory = RowSetProvider.newFactory();
			// 创建默认的CachedRowSet实例
			CachedRowSet cachedRs = factory.createCachedRowSet();
			// 设置每页显示pageSize条记录
			cachedRs.setPageSize(pageSize);
			// 使用ResultSet装填RowSet,设置从第几条记录开始
			cachedRs.populate(rs , (page - 1) * pageSize + 1);
			return cachedRs;
		}
	}
	public static void main(String[] args)throws Exception{
		CachedRowSetPage cp = new CachedRowSetPage();
		cp.initParam("mysql.ini");
		CachedRowSet rs = cp.query("select * from student_table" , 3 , 2);   // ①
		// 向后滚动结果集
		while (rs.next()){
			System.out.println(rs.getString(1)
				+ "\t" + rs.getString(2)
				+ "\t" + rs.getString(3));
		}
	}
}

 上面两行粗体字代码就是使用 Cached Row Set实现分页的关键代码。程序中①号代码显示要查询第2页的记录,每页显示3条记录。运行上面程序,可以看到程序只会显示从第4行到第6行的记录,这就实现了分页

五、事物支持

1、JDBC的事务支持

创建一个bank表

create table bank(
	name nvarchar(50),--姓名
	myMoney money--在银行中存的钱
)
insert into bank values('A',100);
insert into bank values('B',100);
drop table bank;

例子,账号A转移资金R到账号B 

select * from bank;
--A转移资金R到B  A--->B
go
begin transaction
	declare @R money;
	set @R = 20;--要取的钱
	declare @aMoney money;
	declare @bMoney money;
	set @aMoney = (select myMoney from bank where name = 'A');--重数据库中查找A剩余的钱
	set @aMoney = @aMoney - @R;
	update bank set myMoney = @aMoney where  name = 'A';--减去A取的钱
		if @aMoney < 0 or @aMoney < 0 --不满足条件 取不出这么多的钱
			begin 
				print 'A存款足!';
			    rollback;--开始回滚 撤销操作
			end
		else --满足条件  把取的钱加到B上
			begin
				set @bMoney =  (select myMoney from bank where name = 'B');
				set @bMoney = @bMoney + @R;
				update bank set myMoney = @bMoney where  name = 'B';--把重A取出的钱加到B身上
				commit;
			end
go
select * from bank;

import java.sql.*;
import java.io.*;
import java.util.*;
public class TransactionTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void insertInTransaction(String[] sqls) throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			Connection conn = DriverManager.getConnection(url , user , pass))
		{
			// 关闭自动提交,开启事务
			conn.setAutoCommit(false);
			try(
				// 使用Connection来创建一个Statment对象
				Statement stmt = conn.createStatement())
			{
				// 循环多次执行SQL语句
				for (String sql : sqls){
					stmt.executeUpdate(sql);
				}
			}
			// 提交事务
			conn.commit();
		}
	}
	public static void main(String[] args) throws Exception{
		TransactionTest tt = new TransactionTest();
		tt.initParam("mysql.ini");
		String[] sqls = new String[]{
			"insert into student_table values('aaa' ,1)",
			"insert into student_table values('bbb' ,1)",
			"insert into student_table values('ccc' ,1)",
			// 下面这条SQL语句将会违反外键约束,
			// 因为teacher_table中没有ID为5的记录。
			"insert into student_table values('ccc' ,5)" //①
		};
		tt.insertInTransaction(sqls);
	}
}

 

运行上面的程序会看到数据不会被提交。

2、java8增加的批量更新

import java.sql.*;
import java.io.*;
import java.util.*;
public class TransactionTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void insertInTransaction(String[] sqls) throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			Connection conn = DriverManager.getConnection(url , user , pass))
		
		{
			//保存当前的自动提交模式
			boolean autoCommit = conn.getAutoCommit();
			System.out.println(autoCommit);
			// 关闭自动提交,开启事务
			conn.setAutoCommit(false);
			try(
				// 使用Connection来创建一个Statment对象
				Statement stmt = conn.createStatement()
			){
				//使用Statement同时搜集多条SQL语句
				for (String sql : sqls){
					stmt.addBatch(sql);
				}
				
				//同时提交所有的SQL语句
				stmt.executeLargeBatch();
			}
			// 提交事务
			conn.commit();
			//恢复原有的自动提交模式
			conn.setAutoCommit(autoCommit);
		}
	}
	public static void main(String[] args) throws Exception{
		TransactionTest tt = new TransactionTest();
		tt.initParam("mysql.ini");
		String[] sqls = new String[]{
			"insert into student_table values('aaa' ,1)",
			"insert into student_table values('bbb' ,1)",
			"insert into student_table values('ccc' ,1)",
		};
		tt.insertInTransaction(sqls);
	}
}

六、分析数据库信息

1、使用DatabaseMetaData分析数据库信息

import java.sql.*;
import java.util.*;
import java.io.*;
public class DatabaseMetaDataTest{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void info() throws Exception{
		// 加载驱动
		Class.forName(driver);
		try(
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user , pass))
		{
			// 获取的DatabaseMetaData对象
			DatabaseMetaData dbmd = conn.getMetaData();
			// 获取MySQL支持的所有表类型
			ResultSet rs = dbmd.getTableTypes();
			System.out.println("--MySQL支持的表类型信息--");
			printResultSet(rs);
			// 获取当前数据库的全部数据表
			rs = dbmd.getTables(null,null, "%" , new String[]{"TABLE"});
			System.out.println("\n\n--当前数据库里的数据表信息--");
			printResultSet(rs);
			// 获取student_table表的主键
			rs = dbmd.getPrimaryKeys(null , null, "student_table");
			System.out.println("\n\n--student_table表的主键信息--");
			printResultSet(rs);
			// 获取当前数据库的全部存储过程
			rs = dbmd.getProcedures(null , null, "%");
			System.out.println("\n\n--当前数据库里的存储过程信息--");
			printResultSet(rs);
			// 获取teacher_table表和student_table之间的外键约束
			rs = dbmd.getCrossReference(null,null, "teacher_table", null, null, "student_table");
			System.out.println("\n\n--teacher_table表和student_table之间"+ "的外键约束--");
			printResultSet(rs);
			// 获取student_table表的全部数据列
			rs = dbmd.getColumns(null, null, "student_table", "%");
			System.out.println("\n\n--student_table表的全部数据列--");
			printResultSet(rs);
		}
	}
	public void printResultSet(ResultSet rs)throws SQLException{
		ResultSetMetaData rsmd = rs.getMetaData();
		// 打印ResultSet的所有列标题
		for (int i = 0 ; i < rsmd.getColumnCount() ; i++ ){
			System.out.print(rsmd.getColumnName(i + 1) + "\t");
		}
		System.out.print("\n");
		// 打印ResultSet里的全部数据
		while (rs.next()){
			for (int i = 0; i < rsmd.getColumnCount() ; i++ )
			{
				System.out.print(rs.getString(i + 1) + "\t");
			}
			System.out.print("\n");
		}
		rs.close();
	}
	public static void main(String[] args)throws Exception{
		DatabaseMetaDataTest dt = new DatabaseMetaDataTest();
		dt.initParam("mysql.ini");
		dt.info();
	}
}

七、使用连接处管理连接

a) DBCP连接

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.Properties;
import org.apache.commons.dbcp2.BasicDataSource;
public class Main{
	//创建数据源对象
	public static BasicDataSource ds = new BasicDataSource();//整个应用程序只需要一个数据源即可
	private String driverClassName;
	private String url;
	private String userName;
	private String password;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driverClassName = props.getProperty("driver");
		url = props.getProperty("url");
		userName = props.getProperty("user");
		password = props.getProperty("pass");
	}
	public void function()throws Exception{
		//设置连接池所需要的驱动
		ds.setDriverClassName(driverClassName);
		//设置连接数据库的URL
		ds.setUrl(url);
		//设置连接数据库的用户名
		ds.setUsername(userName);
		//设置连接数据库的密码
		ds.setPassword(password);
		//设置连接池的初始化连接数
		ds.setInitialSize(5);
		//设置连接池最多可有多少个活动连接数
		ds.setMaxIdle(20);//ds.setMaxActive(20);
		//设置连接池最少有两个空闲连接
	//	ds.setMinIdle(2);
		
		try(
				// 获取数据库连接
				Connection conn = ds.getConnection();
				// 使用Connection来创建一个Statment对象
				Statement stmt = conn.createStatement();
				ResultSet rs = stmt.executeQuery("select * from teacher_table");
				
			){
				ResultSetMetaData rsmd = rs.getMetaData();
				int columnCount = rsmd.getColumnCount(); //列数

				for(int i = 1; i <= columnCount; i++) {
					System.out.print(rsmd.getColumnName(i)+"\t");
				}
				System.out.println();

				// 迭代输出ResultSet对象
				while (rs.next()){
					// 依次输出每列的值
					for (int i = 0 ; i < columnCount ; i++ ){
						System.out.print(rs.getString(i + 1) + "\t");
					}
					System.out.print("\n");
				}
			}
	}
	
	public static void main(String[] args) throws Exception{
		Main ed = new Main();
		ed.initParam("mysql.ini");
		ed.function();
		
	}
}

b) C3P0数据源

下载C3P0工具包:  https://sourceforge.net/projects/c3p0/files/latest/download?source=files

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.Properties;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class Main{
	//创建数据源对象
	public static ComboPooledDataSource  ds = new ComboPooledDataSource();//整个应用程序只需要一个数据源即可
	private String driverClassName;
	private String url;
	private String userName;
	private String password;
	public void initParam(String paramFile)throws Exception{
		// 使用Properties类来加载属性文件
		Properties props = new Properties();
		props.load(new FileInputStream(paramFile));
		driverClassName = props.getProperty("driver");
		url = props.getProperty("url");
		userName = props.getProperty("user");
		password = props.getProperty("pass");
	}
	public void function()throws Exception{
		//设置连接池所需要的驱动
		ds.setDriverClass(driverClassName);
		//设置连接数据库的URL
		ds.setJdbcUrl(url);
		//设置连接数据库的用户名
		ds.setUser(userName);
		//设置连接数据库的密码
		ds.setPassword(password);
		//设置连接池的最大连接数
		ds.setMaxPoolSize(40);
		//设置连接池的最小连接数
		ds.setMinPoolSize(2);
		//设置连接池的初始连接数
		ds.setInitialPoolSize(10);
		//设置连接池的缓存Statement最大数
		ds.setMaxStatements(180);
		
		try(
				// 获取数据库连接
				Connection conn = ds.getConnection();
				// 使用Connection来创建一个Statment对象
				Statement stmt = conn.createStatement();
				ResultSet rs = stmt.executeQuery("select * from teacher_table");
			){
				ResultSetMetaData rsmd = rs.getMetaData();
				int columnCount = rsmd.getColumnCount(); //列数

				for(int i = 1; i <= columnCount; i++) {
					System.out.print(rsmd.getColumnName(i)+"\t");
				}
				System.out.println();

				// 迭代输出ResultSet对象
				while (rs.next()){
					// 依次输出每列的值
					for (int i = 0 ; i < columnCount ; i++ ){
						System.out.print(rs.getString(i + 1) + "\t");
					}
					System.out.print("\n");
				}
			}
	}
	
	public static void main(String[] args) throws Exception{
		Main ed = new Main();
		ed.initParam("mysql.ini");
		ed.function();
		
	}
}

依赖关系:

+--- com.mchange:c3p0:0.9.5.2
|    \--- com.mchange:mchange-commons-java:0.2.11

 

  • 5
    点赞
  • 1
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:猿与汪的秘密 设计师:我叫白小胖 返回首页

打赏作者

爪 哇

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值