1 JDBC基础
JDBC的全称是Java Database Connectivity,即Java数据库连接,是一种可以执行SQL语句的Java API。程序可以通过JDBC API连接到关系数据库,并使用结构化查询语言来完成对数据库的查询、更新。
1.1 JDBC常用接口和类
- Drivermanager::用于管理JDBC驱动的服务类,程序中使用该类的主要功能是获取Connection对象,该类包含如下用法:
public static synchronized Connection getConnection(String url, String user,String pass) throws SQLExpection;
,该方法获得url对应数据库的连接 - Connection:代表数据库连接对象,每个Connection代表一个物理连接对话,要想访问数据库,必须先获得数据库连接,常用方法有:
- Statement createStatement() throws SQLException:该方法返回一个Statement对象
- PreparedStatement prepareStatement(String sql) throws SQLException:该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译
- CallableStatement prepareCall(String sql) throws SQLException:该方法返回CallableStatement对象,该对象用于调用存储过程
- 注意:上述三个方法都返回用于执行SQL语句的Statement对象,只有获得了Statement或其子类之后才可执行SQL语句
- Connection提供的控制事务的方法:
- Savepoint setSavepoint():创建一个保存点
- Savepoint setSavepoint(String name):以指定名字来创建一个保存点
- void setTransactionIsolation(int level):设置事务的隔离级别
- void rollback():回滚事务
- void rollback(Savepoint savepoint):将事务回滚到指定的保存点
- void setAutoCommit(bollean autoCommit):关闭自动提交,打开事务
- void commit()::提交事务
- Statement:用于执行SQL语句的工具接口,可执行DDL、DCL、DML,查询语句,常用方法如下:
- ReaultSet executeQuery(String sql) throws SQLException:执行查询语句,并返回查询结果对应的ResultSet对象
- Int executeUpdate(String sql) throws SQLException:该方法用于执行DML语句,并返回受影响的行数,也可以执行DDL语句,返回0
- Boolean execute(String sql) throws SQLException:执行任何SQL语句,如果第一个结果是ResultSet的话,返回true,其他的返回false
- PreparedStatement:预编译的Statement对象,是Statement的子接口,允许数据库预编译SQL语句(这些SQL语句通常带有参数),以后每次只改变SQL命令的参数即可,避免数据库每次都需要编译SQL语句,因此性能更好,相对于Statement而言,使用PreparedStatement执行SQL语句时,无须再传入SQL语句,只要为预编译的SQL语句传入参数值即可,所以它比Statement多了如下方法:
void setXxx(int parameterIndex, Xxx value);
该方法根据传入参数值的类型不同,需要使用不同的方法。PreparedStatement同样有executeUpdate()、executeQuery()和execute()三个方法,只是方法无须接收SQL字符串,因为PreparedStatement对象已经预编译了SQL命令,只要为这些命令传入参数即可。 - ResultSet:结果集对象,包含访问查询结果的方法,可以通过列索引或列名获得列数据,它包含了如下常用方法来移动记录指针,当把记录指针移动到指定行之后,ResultSet可通过getXxx(int columnIndex)或getXxx(String columnLabel)方法来获取当前行、指定列的值。
- void close():释放ResultSet对象
- boolean absolute(int row):将结果集的指针移动到第row行,如果是负数的话,就是倒数第row行,如果指针指向有效记录,返回true
- void beforeFirst():将ResultSet的记录指针定位到首行之前
- boolean first():定位到首行
- boolean previous():定位到上一行
- boolean next():定位到下一行
- boolean last():定位到最后一行
- void afterLast():定位到最后一行之后
1.2 JDBC编程步骤
- 加载数据库驱动,通常使用Class类的forName()静态方法来加载驱动:
Class.forName(driverClass)
,加载驱动时并不是真正使用数据库的驱动,只是使用数据库驱动程序类名的字符串。
附:
MySQL:com.mysql.jdbc.Driver
Oracle:oracle.jdbc.driver.OracleDriver - 通过DriverManager获取数据库连接:
DriverManager.getConnection(String url, String user, String pass)
数据库URL的写法:jdbc:subprotocol:other stuff
MySQL:jdbc:mysql://hostname:port/databasename
Oracle:jdbc:oracle:thin:@hostname:port:databasename - 通过Connection对象创建Statement对象
- createStatement():创建基本的Statement对象
- prepareStatement(String sql):根据传入的SQL语句创建预编译的Statement对象
- prepareCall(String sql):根据传入的SQL语句创建CallableStatement对象
- 使用Statement执行SQL语句,所有的Statement都有如下三个方法来执行SQL语句
- execute():可以执行任何SQL语句,但比较麻烦
- executeUpdate():主要用于执行DML和DDL语句,返回受SQL影响的行数
- executeQuery():只能执行查询语句,执行后返回代表查询结果的ResultSet语句
- 操作结果集ResultSet
- 回收数据库资源,包括关闭ResultSet、Statement和Connection对象
public class ConnMySql {
public static void main(String[] args) throws Exception {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
try (
// 2.使用DriverManager获取数据库连接,其中返回的Connection就代表了Java程序和数据库的连接
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/select_test", "root", "root");
// 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));
}
}
}
}
1.3 执行SQL语句
1.3.1 Statement
- Statement的execute()方法几乎可以执行任何SQL语句,但它执行SQL语句时比较麻烦,通常没有必要使用execute()方法来执行SQL语句,使用executeUpdate()或executeQuery()方法更简单。但是如果不清楚SQL语句的类型,则只能使用execute()方法来执行SQL语句了。使用execute()方法执行SQL语句的返回值只是boolean值,表明执行该SQL语句是否返回了ResultSet对象,Statement提供了如下两个方法来获取执行结果:
- getResultSet():获取该Statement执行查询语句所返回的ResultSet对象
- getUpdateCount():获取该Statement()执行DML语句所影响的记录行数
1.3.3 PreparedStatement
- PreparedStatement是Statement接口的子接口,可以预编译SQL语句,预编译之后的SQL语句被存储在PreparedStatement对象中,然后可以使用该对象多次高效地执行该语句,简而言之,使用PreparedStatement比使用Statement的效率更高
- 创建PreparedStatement对象使用Connection的preparedStatement()方法,该方法需要传入一个SQL字符串,该字符串可以包含占位符参数,在执行SQL语句之前必须为这些参数传入参数值,PreparedStatement提供了一系列的setXxx(int index, Xxx value)方法来传入参数。
- PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三个方法来执行SQL语句,不过这三个方法无须参数,因为PreparedStatement已存储了预编译的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(" + " null ,'姓名" + 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(null,?,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的好处:
- PreparedStatement预编译SQL语句,性能更好
- PreparedStatement无须“拼接”SQL语句,编程更简单
- PreparedStatement可以防止SQL注入,安全性更好
- 注意:使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只能代替普通值,不要使用占位符参数代替参数名、列名等数据库对象,更不要用占位符参数来代替SQL语句中的insert、select等关键字。
1.3.3 CallableStatement
- 调用存储过程使用CallableStatement,可以通过Connection的prepareCall()方法来创建CallableStatement对象,创建该对象时需要传入调用存储过程的SQL语句。调用存储过程的SQL语句总是这种格式:{call 过程名(?,?,?……)},其中问号作为存储过程参数的占位符,存储过程的参数既有传入参数,也有传出参数,传入参数指的是在使用前需要传入参数值,可以通过调用CallableStatement的setXxx()方法为传入参数设置值;所谓传出参数就是指可以通过该参数获取存储过程里的值;CallableStatement需要调用registerOutParameter()方法来注册该参数
- 经过上面的步骤之后,就可以调用CallableStatement的execute()方法来执行存储过程了,执行结束后通过CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值,
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);
// 执行存储过程
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();
}
}
2 ResultSet和RowSet
JDBC使用ResultSet来封装执行查询得到的查询结果,然后通过一佛那个ResultSet的记录指针来取出结果集的内容,除此之外,JDBC还允许通过ResultSet来更新记录,并提供了ResultSetMetaData来获得ResultSet对象的相关信息。
2.1 结果集
2.1.1 可滚动、可更新的结果集
- 可滚动的结果集:可以使用absolute()、previous()、afterLast()等方法自由移动记录指针的ResultSet,默认可滚动,无须指定额外的参数
- 默认方式打开的Result是不可更新的,如果希望创建可更新的ResultSet,则必须在创建Statement或PreparedStatement时传入额外的参数,Connection在创建Statement或PreparedStatement时还可额外传入如下两个参数:
- resultSetType:控制ResultSet的类型,该参数可以取如下三个值:
- ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只能向前移动
- ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动,但底层数据的改变不会影响ResultSet的内容
- ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自由移动,而且底层数据的改变会影响ResultSet的内容
- resultSetConcurrency:控制ResultSet的并发类型
- ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式
- ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet是可更新的并发模式
- resultSetType:控制ResultSet的类型,该参数可以取如下三个值:
- 可更新的结果集还需要满足如下两个条件:
- 所有数据都应该来自一个表
- 选出的数据集必须包含主键列
- 通过调用ResultSet的updateXxx(int columnindex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改
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_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");
}
}
2.1.2 处理Blob类型数据
- Blob(Binary Long Object, 二进制长对象),通常用于存储大文件,例如:图片、音乐、视频,因为Blob常量无法表示,因此,将Blob数据插入数据库需要使用PreparedStatement,该对象有一个方法setBinaryStream(int parameterIndex, InputStream x)为指定参数传入二进制输入流,从而实现将Blob数据保存到数据库的功能
- 当需要从ResultSet里取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法,该方法将返回一个Blob对象,Blob对象提供了getBinaryStream()方法来获取该Blob数据的输入流,也可以使用Blob对象提供的getByte()方法直接取出该Blob对象封装的二进制数据
2.1.3 使用ResultSetMetaData分析结果集
- ResultSet里包含一个getMetaData()方法,该方法返回该ResultSet对应的ResultSetMetaData对象,一旦获得了ResultSetMetaData对象,就可以通过其提供的方法来返回ResultSet的描述信息,常用的方法有:
- int getColumnCount():返回该ResultSet的列数量
- String getColumnName(int column):返回指定索引的列名
- int getColumnType(int column):返回指定索引的列类型
2.2 行集
RowSet继承了ResultSet接口,下含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口,除了JdbcRowset需要保持与数据库的连接外,其余的都是离线RowSet接口,RowSet默认是可滚动、可更新、可序列化的结果集
2.2.1 RowSet
- JdbcRowSetImpl提供的构造器:
- JdbcRowSetImpl():创建一个默认的JdbcRowSetImpl对象
- JdbcRowSetImpl(Connection conn):以给定的Connection对象作为数据库连接来创建JdbcRowSetImpl对象
- JdbcRowSetImpl(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装填数据的两种方式:
- 创建JdbcRowSetImpl时,直接传入ResultSet对象
- 使用execute()方法来执行SQL查询,用查询返回的数据来装填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();
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 {
JdbcRowSetTest jt = new JdbcRowSetTest();
jt.initParam("mysql.ini");
jt.update("select * from student_table");
}
}
2.2.2 RowSetFactory
- 使用RowSetProvider创建RowSetFactory,使用RowSetFactory创建RowSet实例,提供了如下方法:
- CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet
- FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet
- JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet
- JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet
- WebRowSet createWebRowSet():创建一个默认的WebRowSet
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.2.3 CachedRowSet
- 离线RowSet用来处理在连接关闭后如何访问ResultSet数据的问题,它直接将底层数据读入到内存中,封装成RowSet对象
- 在连接关闭的条件下,程序依然可以读取、修改RowSet里的记录,为了将修改后的数据同步到底层数据库,需要调用RowSet的acceptChanges(Connection conn)方法
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.acceptChanges(conn);
}
}
- 离线RowSet可以实现分页功能,每次只装载ResultSet的某几条记录,避免占用内存过大问题,CachedRowSet提供了如下方法来控制分页:
- popular(ResultSet rs, int startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始装填
- setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录
- previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录
- nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录
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));
}
}
}
3 JDBC的事务支持
- JDBC连接也提供了事务支持,JDBC连接的事务支持由Connection提供,Connection默认打开自动提交,即关闭事务,在这种情况下,每条SQL语句一旦执行,变回立即提交到数据库,永久生效,无法对其进行回滚操作,可以调用Connection的setAutoCommit()方法来关闭自动提交,开启事务
- 一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了Statement对象之后,可以执行任意多条DML语句,虽然语句执行了,但是并不会生效,需要调用Connection对象的commit()方法来提交事务,这样才会将修改结果同步到底层数据库
- 如果SQL语句执行失败,则应该使用Connection对象的rollback()方法来回滚事务。
- Connection也提供了设置中间点的方法:setSavePoint(),配和rollback(Savepoint savepoint)方法来回滚到指定中间点
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(null , 'aaa' ,1)",
"insert into student_table values(null , 'bbb' ,1)",
"insert into student_table values(null , 'ccc' ,1)",
// 下面这条SQL语句将会违反外键约束,
// 因为teacher_table中没有ID为5的记录。
"insert into student_table values(null , 'ccc' ,5)" //①
};
tt.insertInTransaction(sqls);
}
}
- 批量更新:多条SQL语句将被作为一批操作同时提交,使用Statement对象的addBatch()方法收集SQL语句,再使用executeBatch()方法同时执行这些语句。
4 连接池管理连接
- 数据库连接池的解决方案是:当应用程序启动时,系统主动创建足够多的数据库连接,并将这些链接组成一个连接池。每次应用程序请求数据库连接时,无须重新打开连接(打开连接极耗费系统资源),而是从连接池中取出已有的连接使用,使用完后不再关闭数据库连接,而是直接将连接归还给连接池
- 数据库连接池是Connection对象的工厂,常用参数如下:
- 数据库的初始连接数
- 连接池的最大连接数
- 连接池的最小连接数
- 连接池每次增加的容量
- JDBC的数据库连接池使用Java.sql.DataSource来表示
4.1 DBCP数据源
- DBCP数据源依赖于common-pool,如果使用该连接池实现,需要引入两个jar包:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
- DBCP获得数据库连接的方式:
//创建数据源对象
BasicDataSource ds = new BasicDataSource();
//设置连接池所需的驱动
ds.setDriverClassName(“com.mysql.jdbc.Driver”);
//设置连接数据库的URL
ds.setUrl(“jdbc:mysql://localhost:3306/javaee”);
//设置连接数据库的用户名
ds.setUsername(“root”);
//设置连接数据库的密码
ds.setPassword(“pass”);
//设置连接池的初始连接数
ds.setInitialSize(5);
//设置连接池最多可有多少个活动连接数
ds.setMaxActive(20);
//设置连接池种最少有2个空闲的连接
ds.setMinIdle(2);
获取连接:
Connection conn = ds.getConnection();
释放连接:
Conn.close()
4.2 C3P0数据源
- C3P0连接池可以自动清理不再使用的Connection,还可以自动清理Statement和ResultSet,如果需要使用C3P0连接池,则需要引入:c3p0-0.9.1.2.jar:c3p0连接池的实现
- 通过C3P0获得数据库连接的方式:
//创建连接池实例
ComboPooledDataSource ds = new ComboPooledDataSource ();
//设置连接池所需的驱动
ds.setDriverClass (“com.mysql.jdbc.Driver”);
//设置连接数据库的URL
ds.setJdbcUrl(“jdbc:mysql://localhost:3306/javaee”);
//设置连接数据库的用户名
ds.setUser (“root”);
//设置连接数据库的密码
ds.setPassword(“pass”);
//设置连接池的最大连接数
ds.setMaxPoolSize(40);
//设置连接池的最小连接数
ds.setMinPoolSize(2);
//设置连接池的初始连接数
ds.setInitialPoolSize(10);
//设置连接池的缓存Statement的最大数
ds.setMaxStatements(180);
获得数据库连接:
Connection = ds.getConnection();