【Java】14 JDBC编程学习总结

一、JDBC是什么?

JDBC-Java Database Connectivity-Java数据库连接

二、为什么要使用JDBC?

可以使用相同的API实现连接、操作不同的数据库

三、DDL、DML、DCL分别代表什么

DDL(Data Definition Language 数据定义语言)语句:主要由create,alter,drop和truncate四个关键字完成
DML(Data Manipulation Language 数据操作语言)语句:主要由insert,update和delete三个关键字完成
DCL(Data Control Language 数据控制语言) 语句:主要由grant和revoke两个关键字组成
事务性语言:主要由commit,rollback和savepoint三个关键字组成

四、JDBC有三个操作步骤

建立与数据库的连接、执行SQL语句、获得SQL语句执行结果

  1. 加载驱动,规范
    Class.forName(dirver)
  2. 使用Connection创建Connection对象,以便可以使用Connection创建Statement对象
    Connection conn = DriverManager.getConnection(url,user,pass)
  3. 使用Connection对象创建Statement对象,以便使用Statement对象的executeXxx方法
    Statement stmt = conn.createStatement()
  4. 调用Statement对象的executeUpdate()、executeQuery()、execute()方法

executeUpdate():用于执行DML语句,返回一个整数

实例代码
/** 
 * @ClassName: ExecutionDDLTest
 * @description: 本节代码主要实现使用executeLargeUpdate()来执行DDL语句
 * DDL语言:主要由create,drop,alter,truncate
 * @author: FFIDEAL
 * @Date: 2020年4月5日 下午9:28:59
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;

import com.mysql.cj.jdbc.Driver;

public class ExecutionDDLTest {
	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来创建一个Statement对象
				Statement stmt = conn.createStatement()
				){
			//执行DDL语句,创建数据表
			stmt.executeUpdate(sql);
		}
	}
	public static void main(String[] args) throws Exception{
		ExecutionDDLTest ed = new ExecutionDDLTest();
		ed.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		ed.createTable("create table jdbc_test ("
				+ "jdbc_id int auto_increment primary key,"
				+ "jdbc_name varchar(255),"
				+ "jdbc_desc text"
				+ ");");
		System.out.println("=======表创建完成======");
	}
}

executeQuery():执行select语句,查询到的结果

实例代码
/** 
 * @ClassName: ConnMySql
 * @description: 本节代码简单示范了JDBC编程,并通过ResultSet获得结果集的过程
 * @author: FFIDEAL
 * @Date: 2020年4月4日 下午8:22:06
 */

package M13;

import java.security.interfaces.RSAKey;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class ConnMySql {
	public static void main(String[] args) throws Exception{
		
		//1.加载驱动,使用反射知识,现在就记住
		/* 5版本是Class.forName("com.mysql.jdbc.Driver");
		 * 8版本是Class.forName("com.mysql.cj.jdbc.Driver");
		 */
		Class.forName("com.mysql.cj.jdbc.Driver");
		try (
			/*2.使用DriverManager获取数据库连接
			 *  其中返回的Connection就代表了Java程序和数据库的连接
			 *  不同数据库URL写法需要查驱动文档,用户名、密码有DBA分配
			 */
			Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crazyjavajdbc?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC", "root","root");
			//3.使用Connection来创建一个Statement对象
			Statement stmt = conn.createStatement();
			/*4.执行SQL语句
			 *  Statement有三种执行SQL语句的方法:
			 *  ①.execute()可执行任何SQL语句-返回一个boolean值,如果执行后第一个结果是ResultSet就返回true,否则返回false
			 *  ②.executeQuery()执行select语句 - 返回查询到的结果集
			 *  ③.executeUpdate()用于执行DML语句-返回一个整数,代表被SQL语句影响的记录条数
			 */
//			ResultSet rs = stmt.executeQuery("select s.* , teacher_name from student_table s,teacher_table t where s.java_teacher = t.teacher_id")
			ResultSet rs = stmt.executeQuery("select s.* , t.teacher_name "
					+ "from student_table s , teacher_table t "
					+ "where s.java_teacher = t.teacher_id")
					)
		{
			
		/*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));
			}
		}
	}
}

execute():可以执行任何一个SQL语句,返回一个boolean
但是可以执行getResultSet():获得该Statement执行查询语句所返回的ResultSet对象
getUpdateCount():获取该Statement执行的DML语句所影响的记录条数

实例代码
/** 
 * @ClassName: ExecuteSQL
 * @description: 本机代码主要讨论使用execute()方法来执行SQL语句
 * execute()方法的有点:几乎可以执行任何的SQL语句,而不用考虑DML(executeUpdate)还是DDL()
 * execute():可以执行任何一个SQL语句,返回一个boolean,
 * 但是可以执行
 * getResultSet():获得该Statement执行查询语句所返回的ResultSet对象
 * getUpdateCount():获取该Statement执行的DML语句所影响的记录条数 
 * @author: FFIDEAL
 * @Date: 2020年4月6日 下午2:30:58
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;


public class ExecuteSQL {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam(String paramFile) throws Exception{
		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);
				Statement stmt = conn.createStatement();
				){
			//执行SQL语句,返回boolean值表示是否包含ResultSet
			boolean hasResultSet = stmt.execute(sql);
			if(hasResultSet) {
				try(
						//获取结果集
						ResultSet rs = stmt.getResultSet();
						){
					//ResultSetMetaData是用于分析结果集合的元数据接口
					java.sql.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.println("\n");
					}
				}
			}
		}
	}
	
	public static void main(String[] args) throws Exception {
		ExecuteSQL es = new ExecuteSQL();
		es.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		System.out.println("=====执行删除表的DDL=====");
		es.executeSQL("drop table if exists my_test");
		System.out.println("=====执行新建表的DDl语句=====");
		es.executeSQL("create table my_test"
				+ "(test_id int auto_increment primary key, "
				+ "test_name varchar(255));");
		System.out.println("=====执行插入数据的DML语句======");
		es.executeSQL("insert into my_test(test_name) select student_name from student_table");
		es.executeSQL("insert into my_test values(3,'小红');");
		System.out.println("=====执行查询数据的查询语句=====");
		es.executeSQL("select * from my_test");
	}
}

五、使用PreparedStatement执行SQL语句

PreparedStatement接口可以允许在SQL中使用占位符(?),这是在Statement接口中中不被允许的。

PreparedStatement的效率比Statement高,这是因为可以使用PreparedStatement对象多次高效的执行SQL语句。使用办法和Statement方法类似,且提供的方法也是类似的,有execute()方法、executeUpdate()方法和executeQuery()方法等

除了效率较高以外,PreparedStatement还有一个优势,就是使用PreparedStatement不需要拼接SQL语句,见一下代码的两个方法中的SQL语句。

除了上述两个优点以外,使用PreparedStatement可以防止SQL注入(常见的利用SQL语句漏洞来入侵的方法)

PreparedStatement ps = conn.preparedStatement("insert into student_table values(null,?,1)");
实例代码
/** 
 * @ClassName: PreparedStatementTest
 * @description: 本节代码主要讨论PreparedStatement和Statement性能的对比
 * @author: FFIDEAL
 * @Date: 2020年4月6日 下午6:01:24
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.Properties;

public class PreparedStatementTest {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam(String paramFile) throws Exception{
		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);
				Statement stmt = conn.createStatement();
				){
			for (int i = 0; i < 100; i++) {
				stmt.executeUpdate(
                    "insert into student_table values(null,'姓名" + i + "',1)");
			}
			System.out.println(
                "使用Statement费时:" + (System.currentTimeMillis() - start));
		}
	}
	
	public void insertUsePreparedStatement() throws Exception{
		long start = System.currentTimeMillis();
		try(
				Connection conn = DriverManager.getConnection(url, user, pass);
				PreparedStatement ps = conn.prepareStatement(
                    "insert into student_table values(null,1,?)");
				){
			//100次为PreparedStatement的参数设置,可以插入100条记录
			for (int i = 0; i < 100; i++) {
				ps.setString(1, "姓名" + i);
				ps.setInt(1, i);
				ps.executeUpdate();
			}
			System.out.println("使用PreparedStatement费时:"
                               + (System.currentTimeMillis() - start));
		}
		
	}
	//使用execute()方法来输出查询结果
	public void selectAll() throws Exception{
		try(Connection conn = DriverManager.getConnection(url, user, pass);
				Statement stmt = conn.createStatement();
				){
			boolean hasResultSet = stmt.execute("select * from student_table");
			if(hasResultSet) {
				try(
						//通过getResultSet方法获得结果集
						ResultSet rs = stmt.getResultSet();
						){
					//获得变化的数字
					ResultSetMetaData rsmd = rs.getMetaData();
					int col = rsmd.getColumnCount();
					while(rs.next()) {
						for (int i = 0; i < col; i++) {
							System.out.print(rs.getString(i + 1) + "\t");
						}
						System.out.println("\n");
					}
				}
				
			}
		}
	}
	
	public static void main(String[] args) throws Exception{
		PreparedStatementTest pst = new PreparedStatementTest();
		pst.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		pst.insertUseStatement();
		pst.insertUsePreparedStatement();
		pst.selectAll();
	}
}


六、使用CallableStatement调用存储过程

前提是要在数据库里有这个事务

//使用Connection来创建一个CallableStatement对象
cstmt = conn.prepareCall("{call add_pro(?,?,?)}");
//传入参数可以用setXxx(index, number);
//使用registerOutParameter()方法来注册参数
//之后用execute()来执行
//getXxx(int index)来获得参数
实例代码
/** 
 * @ClassName: CallableStatemnetTest
 * @description: 本节代码主要讨论CallableStatement调用存储过程
 * @author: FFIDEAL
 * @Date: 2020年4月6日 下午8:51:05
 */  

package M13;

import java.io.FileInputStream;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import java.util.Properties;

public class CallableStatemnetTest {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 CallProcedure() throws Exception{
		try(
				Connection conn = DriverManager.getConnection(url, user, pass);
				//通过Connection对象创建一个CallableStatement对象,"{call 存储过程名称(占位符参数1,占位符参数2,……)}"
				CallableStatement cs = conn.prepareCall("{call add_pro(?,?,?)}");
				){
			cs.setInt(1, 2);
			cs.setInt(2, 3);
			//输出int类型
			cs.registerOutParameter(3, Types.INTEGER);
			//执行程序
			cs.execute();
			//输出结果
			System.out.println("执行结果是:" + cs.getInt(3));
		}
	}
	public static void main(String[] args) throws Exception {
		CallableStatemnetTest cst = new CallableStatemnetTest();
		cst.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		cst.CallProcedure();
	}
}

七、可滚动、可更新结果集

ResultSet除了next()指针外,还可以使用absolute()、previous()、afterLast()等方法自由移动记录指针

在使用Connection创建了Statement或者PreparedStatement是还额外传入如下两个参数

  1. resultSetType:控制ResultSet的类型,可以是以下三个值

    • ResultSet.TYPE_FORWARD_ONLY:该常量记录指针只能向前移动
    • ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自动移动(可滚动结果集),但是底层数据的改变不会影响ResultSet的内容变化
    • ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自动移动(可滚动结果集),但是底层数据的改变会影响ResultSet的内容变化
  2. ResultSetConcurrent:控制ResultSet的并发类型,该参数可以接收如下两个值

    • ResultSet.CONCUR_READ_ONLY:该常量表示ResultSet是只读的并发模式(默认)
    • ResultSet.CONCUR_UPDATABLE:该常量表示ResultSet是可更新的并发模式

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

//使用Connection创建一个PreparedStatement对象
//传入控制结果集可滚动、可更新的参数
ps = conn.preparedStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,SesultSet.CONCUR_UPDATABLE);
实例代码
/** 
 * @ClassName: ResultSetTest
 * @description: 本段代码主要讨论通过Connection来创建PreparedStatement对象,并通过这一对象来是数据库达到可滚动可更新的目的
 * @author: FFIDEAL
 * @Date: 2020年4月6日 下午11:18:39
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class ResultSetTest {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 query(String sql) throws Exception{
		try(
				Connection conn = DriverManager.getConnection(url, user, pass);
				PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
				ResultSet rs = ps.executeQuery();
				){
			rs.last();
			//获得行数
			int rowCount = rs.getRow();
			System.out.println("共有" + rowCount + "行");
			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 rst = new ResultSetTest();
		rst.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		rst.query("select * from student_table");
	}
}

八、处理Bob类型的数据

Bob(Binary Long Object)是二进制长对象的意思,Blob列通常用于存储大文件,典型的Blob内容是一张图片或者一个声音文件,由于它的特殊性,必须使用特殊的方法来存储。而Blob列可以吧图片、声音等文件转化为二进制数据保存在数据库里,并可以从数据库中恢复指定文件。

Blob数据插入数据库需要使用PreparedStatement,该对象有一个方法:setBinaryStream()

九、 Java7新增的RowSetFactory与RowSet

RowSet继承自ResultSet接口,RowSet接口下包含JdbcRowSet、CacheRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet需要与数据库保持连接以外,其他的子接口都是离线的RowSet,但是并不好用(仅做了解)——可滚动、客休该的特性

Java新增的RowSetFactory接口和RowSetProvider类,其中RowSetProvider负责创建RowSetFactory,而RowSetFactory则提供了以下方法创建RowSet实例

  • CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet
  • FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet
  • JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet
  • JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet
  • WebRowSet createWebRowSet():创建一个默认的WebRowSet
实例代码
/** 
 * @ClassName: JdbcRowSetTest
 * @description: 本节代码主要讨论通过JdbcRowSetImpl示范了使用JdbcRowSet的可滚动、可修改特性
 * @author: FFIDEAL
 * @Date: 2020年4月7日 下午4:34:09
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

import javax.sql.rowset.JdbcRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;

public class JdbcRowSetTest {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 update(String sql) throws Exception{
		//使用RowSetProvider创建RowSetFactory
		RowSetFactory rsf = RowSetProvider.newFactory();
		try(
				//使用RowSetFactory创建默认的JdbcRowSet实例
				JdbcRowSet jrs = rsf.createJdbcRowSet()
				){
			//设置必要的连接信息
			jrs.setUrl(url);
			jrs.setUsername(user);
			jrs.setPassword(pass);
			//设置查询语句
			jrs.setCommand(sql);
			//执行查询
			jrs.execute();
			jrs.afterLast();
			//将前滚动结果集
			while(jrs.previous()) {
				System.out.println("===1====");
				System.out.println(jrs.getString(1)
						+ "\t" + jrs.getString(2)
						+ "\t" + jrs.getString(3)
						+ "\t" + jrs.getInt("student_id"));
				if(jrs.getInt("student_id") == 3) {
					//修改制定记录行
					jrs.updateString("student_name","Java");
					System.out.println("===2====");
					jrs.updateRow();
				}
			}
		}
	}
	
	public static void main(String[] args) throws Exception{
		JdbcRowSetTest jrst = new JdbcRowSetTest();
		jrst.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		jrst.update("select s.* from student_table s");
	}
}

十、离线RowSet

使用离线RowSet可以将底层数据库数据读入内存,封装成RowSet对象,该对象不仅安全,编程还比较简单。

实例代码
/** 
 * @ClassName: CachedRowSetTest
 * @description: 本节代码讨论用CachedRowSet来处理读取数据
 * @author: FFIDEAL
 * @Date: 2020年4月7日 下午6:14:27
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;

public class CachedRowSetTest {
	private String driver;
	private static String url;
	private static String user;
	private static String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 CachedRowSet query(String sql) throws Exception{
		Connection conn = DriverManager.getConnection(url, user, pass);
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt.executeQuery(sql);
		//使用RowSetProvider创建RowSetFactory
		RowSetFactory factory = RowSetProvider.newFactory();
		//创建默认的CachedRowSet实例
		CachedRowSet cachers = factory.createCachedRowSet();
		//使用ResultSet装填RowSet,调用了RowSet的populate(ResultSet rs)方法来包装给定的ResultSet 
		//接下来就关闭了ResultSet、Statement、Connection等数据库资源
		cachers.populate(rs);
		//关闭资源
		rs.close();
		stmt.close();
		conn.close();
		return cachers;
	}
	
	public static void main(String[] args) throws Exception{
		CachedRowSetTest ct = new CachedRowSetTest();
		ct.initParam("E:\\JavaCode\\Code\\src\\M13\\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","Java");
				rs.updateRow();
			}
		}
		
		//重新获取数据库连接
		Connection conn = DriverManager.getConnection(url, user, pass);
		conn.setAutoCommit(false);
		//把对RowSet所做的修改同步到底底层数据库
		rs.acceptChanges(conn);
	}
}

十一、离线RowSet的分页查询

若一次性将数据加载到内存中,就会造成内存资源紧张的问题,因此在CachedRowSet提供了分页的功能

CachedRowSet通过以下方法控制分页

  • populate(ResultSet rs, int satrtRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始填装
  • setPageSize(int PageSize):设置CachedRowSet每次返回多少条记录
  • previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录
  • nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录
实例代码
/** 
 * @ClassName: CachedRowSetPage
 * @description: 本节代码主要讨论RowSet的查询分页的问题
 * @author: FFIDEAL
 * @Date: 2020年4月7日 下午8:02:59
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;

public class CachedRowSetPage {
	private String driver;
	private static String url;
	private static String user;
	private static String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 CachedRowSet query(String sql, int pageSize, int page) throws Exception{
		try(
				Connection conn = DriverManager.getConnection(url, user, pass);
				Statement stmt = conn.createStatement();
				ResultSet rs = stmt.executeQuery(sql);){
			//要通过RowSetprovider创建RowSetFactory
			RowSetFactory factory = RowSetProvider.newFactory();
			//通过RowSetFactory创建默认的CachedRowSet
			CachedRowSet cachedRs = factory.createCachedRowSet();
			//显示每页显示的pagesize条记录
			cachedRs.setPageSize(10);
			//使用ResultSet装填RowSet,设置从第几条记录开始
			cachedRs.populate(rs, (page - 1) * pageSize + 1);
			return cachedRs;
		}
	}
	
	public static void main(String[] args) throws Exception{
		CachedRowSetPage cachedRsp = new CachedRowSetPage();
		cachedRsp.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		//显示要看第十页的记录,每页显示10行
		CachedRowSet rs = cachedRsp.query("select * from student_table", 10, 10);
		//向后滚动
		while(rs.next()) {
			System.out.println(rs.getString(1)
					+ "\t" + rs.getString(2)
					+ "\t" + rs.getString(3));
		}
	}
}

十二、事务处理

事务具备四个特性(ACID):分别为原子性、一致性、隔离性、持续性

数据库事务由以下语句组成:

  • 一组DML,经过这组DML语句修改之后数据保持较好的一致性
  • 一条DDL语句
  • 一条DCL语句

提交事务:显示提交(commit);自动提交(执行DDL或者DCL语句,或者程序正常退出)

事务遇到执行失败会如何——回滚:显式回滚(rollback);自动回滚(系统错误或者强行退出)

#历史开始事务
begin
#向student_table表中插入3条数据
insert into jdbc_test
values(null,'xx',1);
insert into jdbc_test
values(null,'yy',1);
insert into jdbc_test
values(null,'zz',1);
#查询student_table表的记录
select * from jdbc_test;
#回滚事务①
rollback;
#再次查询②
select * from jdbc_test;

以上代码在①处发现插入的三条记录看不到了(隔离性),执行②时看到的是刚插入时候的样子

在JDBC中可以调用Connection的setAutoCommit()方法来关闭自动提交,开启事务;同时可以通过Connection中 getAutoCommit()方法来返回该链接的自动提交模式

//关闭自动提交,开启事务
conn.setAutoCommit(false);

一旦事务创建完毕,就像平时创建Statement对象,然后执行任意多条语句

stmt.executeUpdate(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);

以上代码只是被执行但是没有生效,最后要调用Connection中的commit()来提交

conn.commit(); //提交事务

如果任意一条语句失败,就要调用Connection中的rollback()来回滚

//回滚事务
conn.rollback();
实例代码
/** 
 * @ClassName: TranscationTest
 * @description: 本节代码讨论了当程序出现了SQLException错误时,系统将回滚事务
 * @author: FFIDEAL
 * @Date: 2020年4月7日 下午10:17:59
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;

public class TranscationTest {
	private String driver;
	private static String url;
	private static String user;
	private static String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 insetInTransaction(String[] sqls) throws Exception{
		try(
				Connection conn = DriverManager.getConnection(url, user, pass);
				){
			//关闭自动提交,开启事务
			conn.setAutoCommit(false);
			try(
					//使用Connection来创建一个Statement对象
					Statement stmt = conn.createStatement();
					){
				//循环多次执行SQL语句
				for(String sql:sqls) {
					stmt.executeUpdate(sql);
				}
			}
			conn.commit();
		}
	}
	
	public static void main(String[] args) throws Exception{
		TranscationTest tt = new TranscationTest();
		tt.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		String[] sqls = new String[] {
				"insert into jdbc_test values(null,'xx',1);",
				"insert into jdbc_test values(null,'yy',1);",
				"insert into jdbc_test values(null,'zz',1);",
				"insert into jdbc_test values(null,'ccc',5)"
		};
		tt.insetInTransaction(sqls);
		System.out.println("==");
	}
}

十三、使用DatabaseMetaData分析数据库信息

JDBC提供了DatabaseMetaData来封装数据库信息,通过Connection提供的getMetaData()就可以获取数据库对应的DatabaseMetaData对象

实例代码
/** 
 * @ClassName: DatabaseMetaDataTest
 * @description: 本节代码主要讨论通过DatabaseMetaData分析当前Connection连接对应数据库的一些基本信息,
 * 包括当前数据库包含多少数据表,存储过程,student_table表的数据列、主键、外键等信息
 * @author: FFIDEAL
 * @Date: 2020年4月7日 下午11:05:41
 */  

package M13;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import com.mysql.cj.jdbc.result.ResultSetMetaData;

public class DatabaseMetaDataTest {
	private String driver;
	private static String url;
	private static String user;
	private static String pass;
	
	public void initParam(String paramFile) throws Exception{
		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 info() throws Exception{
		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("===当前数据库里的数据表信息===");
			printResultSet(rs);
			//获取student_table表中的主键
			rs = dbmd.getPrimaryKeys(null, null, "student_table");
			System.out.println("===student_table表中主键信息===");
			printResultSet(rs);
			//获取当前数据库全部的存储过程
			rs = dbmd.getProcedures(null, null, "%");
			System.out.println("===当前数据库中所有的存储过程===");
			printResultSet(rs);
			//获得teacher_table和student_table表之间的外键约束
			rs = dbmd.getCrossReference(null, null, "student_table", null, null, "teacher_table");
			System.out.println("===获得teacher_table和student_table表之间的外键约束===");
			printResultSet(rs);
			//获得student_table表的全部数据列
			rs = dbmd.getColumns(null, null, "student_table", "%");
			System.out.println("获得student_table表的全部数据列");
			printResultSet(rs);
		}
	}

	/**
	 * @param rs
	 */
	private void printResultSet(ResultSet rs) throws SQLException{
		// TODO Auto-generated method stub
		java.sql.ResultSetMetaData rsmd = rs.getMetaData();
		//打印ResultSet所有的列标题
		for (int i = 0; i < rsmd.getColumnCount(); i++) {
			System.out.print(rsmd.getColumnName(i + 1) + "\t");
		}
		System.out.println("");
		//打印ResultSet所有的数据
		while(rs.next()) {
			for (int i = 0; i < rsmd.getColumnCount(); i++) {
				System.out.print(rs.getString(i + 1) + "\t");
			}
			System.out.println("");
		}
		rs.close();
	}
	
	public static void main(String[] args) throws Exception{
		DatabaseMetaDataTest dmdt = new DatabaseMetaDataTest();
		dmdt.initParam("E:\\JavaCode\\Code\\src\\M13\\mysql.ini");
		dmdt.info();
	}
}

十四、使用连接池管理连接

数据库连接池是Connection对象工厂。数据库连接池的主要参数有:

  • 数据库的初始连接数
  • 连接池的最大连接数
  • 连接池的最小连接数
  • 连接池每次增加的容量
实例代码1
//以下代码示范了Tomcat使用DBCP获取数据库的方式
//创建数据源对象
BasicDataSource ds = new BasicDataSource();
//设置连接池所需的驱动
ds.setDriverClassName("com.mysql.jc.jdbc.Driver");
//设置连接数据库的URL
ds.setUrl("jdbc:mysql://127.0.0.1:3306/crazyjavajdbc?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
//设置连接池数据库的用户名
ds.setUsername("root");
//设置连接数据库的密码
ds.setPassword("root");
//设置连接池的初始连接数
ds.setInitialSize(5);
//设置连接池最多可有多少个活动连接数
ds.setMaxActive(20);
//设置连接池中最小有2个空闲的连接
ds.setMinIdle(2);
//所依赖的jar包:commons-dbcp.jar/commons-pool.jar
//通过数据源获取数据库连接诶
Connection conn = ds.getConnection();
//释放数据库连接
conn.close();
实例代码2
//以下代码示范了C3P0连接数据库的方式
//创建数据源对象
ComboPooledDataSource ds = new ComboPooledDataSource();
//设置连接池所需的驱动
ds.setDriverClass("com.mysql.jc.jdbc.Driver");
//设置连接数据库的URL
ds.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/crazyjavajdbc?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
//设置连接池数据库的用户名
ds.setUser("root");
//设置连接数据库的密码
ds.setPassword("root");
//设置连接池的初始连接数
ds.setInitialPoolSize(5);
//设置连接池最多可有多少个活动连接数
ds.setMaxPoolSize(20);
//设置连接池中最小有2个空闲的连接
ds.setMinPoolSize(2);
//所依赖的jar包:c3p0-0.9.1.2.jar
//通过数据源获取数据库连接诶
Connection conn = ds.getConnection();
//释放数据库连接
conn.close();
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_之桐_

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值