Java基础(三十二)JDBC(2)连接数据库
一、连接数据库的过程
连接数据库的过程:加载数据库驱动程序,不过只需在第一次访问数据库时加载一次,然后在每次访问数据库时创建一个Connection实例,然后执行操作数据库的SQL语句,并返回执行结果,最后在完成此次操作时销毁前面创建的Connection实例,释放与数据库的连接。
1.加载JDBC驱动程序
// 加载数据库驱动类,加载失败抛出ClassNotFoundException异常 Class.forName(Driver);
2.创建数据库连接
// 创建一个数据库连接对象,创建失败会抛出SQLException异常 Connection conn = DriverManager.getConnection(Url, User, Password);
3.创建一个Statement对象
// 通过Connection示例创建Statement实例 Statement statement = conn.createStatement();
4.执行SQL语句并获得查询结果
// 通过Statement实例执行SQL语句并返回执行结果 ResultSet rs = statement.executeQuery("select * from user");
5.关闭连接释放资源
在每次访问数据库后,应该按照下面的顺序,及时销毁这些实例,释放它们占用的所有资源。
rs.close();
statement.close();
conn.close();
二、Statement实例的三种类型
Statement接口中,执行executeQuery方法可以返回查询结果到结果集中,执行executeUpdate方法可以插入、删除或者修改数据库记录,并返回一个int型数值,表示影响数据库记录的条数。
Statement实例分为三种类型:Statement实例、(继承自Statement)PreparedStatement实例和(继承自PreparedStatement)CallableStatement实例。
(1)Statement实例是最简单的Statement实例,只能用来执行静态的SQL语句
ResultSet rs_queue = statement.executeQuery("select * from user"); while (rs_queue.next()) { System.out.println(rs_queue.getInt("id") + " " + rs_queue.getString("name") + " " + rs_queue.getString("sex") + " " + rs_queue.getString("birthday")); } System.out.println(statement.executeUpdate("update user set sex='女' where id=1")); // 打印:1
rs_queue.close();
statement.close()
(2)PreparedStatement实例增加了执行动态SQL语句的功能
String sql = "update user set name = ?, sex = ?, birthday = ?where id =?"; PreparedStatement predStatement = conn.prepareStatement(sql); predStatement.setString(1, "loser"); predStatement.setString(2, "女"); predStatement.setDate(3, new Date(System.currentTimeMillis())); predStatement.setInt(4, 1); System.out.println(predStatement.executeUpdate()); // 打印:1 predStatement.close();
(3)CallableStatement实例增加了执行数据库存储过程的功能
首先在MySQL中创建一个存储过程并测试:
mysql> select * from user // +----+-------+------+------------+ | id | name | sex | birthday | +----+-------+------+------------+ | 1 | loser | 女 | 2018-08-06 | | 2 | lsl | 男 | 2017-12-12 | | 3 | zgs | 女 | 2016-06-01 | +----+-------+------+------------+ 3 rows in set (0.00 sec) mysql> create procedure proc_count_select_by_sex(IN girl_or_boy VARCHAR(255)) -> READS SQL DATA -> BEGIN -> select count(*) from user where sex=girl_or_boy; -> END -> // Query OK, 0 rows affected (0.00 sec) mysql> call proc_count_select_by_sex('女') // +----------+ | count(*) | +----------+ | 2 | +----------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec)
然后使用CallableStatement对象调用存储过程:
String sql = "{call proc_count_select_by_sex(?)}"; CallableStatement cablStat = conn.prepareCall(sql); cablStat.setString(1, "女"); ResultSet rs = cablStat.executeQuery(); while (rs.next()) { System.out.println(rs.getInt(1)); // 打印:2 }
rs.close(); cablStat.close();
三、标准JDBC程序设计
package jdbc.jun.iplab; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBC { private static final String Driver = "com.mysql.jdbc.Driver"; private static final String Url = "jdbc:mysql://localhost:3306/mysqldb?useSSL=false"; private static final String User = "root"; private static final String Password = "bjtungirc"; static { try { Class.forName(Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { try { // 创建一个数据库连接对象,创建失败会抛出SQLException异常 Connection conn = DriverManager.getConnection(Url, User, Password); // 通过Connection示例创建Statement实例 Statement statement = conn.createStatement(); // 通过Statement实例执行SQL语句并返回执行结果 ResultSet rs_queue = statement.executeQuery("select * from user"); while (rs_queue.next()) { System.out.println(rs_queue.getInt("id") + " " + rs_queue.getString("name") + " " + rs_queue.getString("sex") + " " + rs_queue.getString("birthday")); } System.out.println(statement.executeUpdate("update user set sex='女' where id=1")); rs_queue.close(); statement.close(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
实际工程中使用JDBC的标准写法
package fileTransfer; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtils { private static String driverClass; private static String url ; private static String userName; private static String password; private static Connection connection = null; static{ //读取配置文件,加载数据库相关信息 ResourceBundle resourceBundle = ResourceBundle.getBundle("info"); driverClass = resourceBundle.getString("driverClass"); url = resourceBundle.getString("url"); userName = resourceBundle.getString("userName"); password = resourceBundle.getString("password"); try { Class.forName(driverClass); } catch (Exception e) { System.out.println(e.toString()+"加载驱动失败!"); } } public static Connection getConnection(){ try { connection = DriverManager.getConnection(url, userName, password); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println(e.toString()+"数据库连接失败!"); } return connection; } public static void CloseAll(ResultSet resultSet, PreparedStatement pStatement, Connection connection){ if (resultSet!=null) { try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (pStatement != null) { try { pStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
四、JDBC连接池
实际工程中应该考虑下面的问题:建立数据库连接需要开销,因为数据库连接是有限的资源,如果用户要离开应用一段时间,那么他占用的连接就不应该保持打开状态;另一方面,每次查询都获取连接并在随后关闭它的代价也是相当高的。
解决上述问题的方法时建立数据库连接池(pool),这意味着数据库连接在物理上并为关闭,而是保留在一个队列中并被反复重用。
连接池的使用对程序员来说是完全透明的,可以通过获取数据源并调用getConnection方法来得到连接池中的连接。使用完连接后,需要调用close()方法,该方法不再物理上关闭连接,而是只告诉连接池已经使用完该连接,将Connection对象返回到LinkedList对象中。
1.编写连接池需要实现java.sql.DataSource接口
2.创建LinkedList对象,并创建“最小连接数”个Connection对象并将这些对象添加到LinkedList对象中
3.重写getConnection方法,使用动态代理技术管理连接池中的Connection对象
4.封装getConnection()方法和release()方法
示例代码
- 配置文件jdbc.properties
driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/mysqldb?useSSL=false userName = root password = bjtungirc ConnectionPoolInitSize = 10
- 连接池类ConnectionPool
- 初始化:通过ResourceBundle.getBundle("jdbc")读取jdbc.properties里面的配置内容,然后初始化新建立默认的最小数据库连接对象10个Connection对象,并将这些对象加入到由LinkedList类实现的链表中。
public class ConnectionPool implements DataSource { private static String driver; private static String url ; private static String userName; private static String password; private static int ConnectionPoolInitSize; private static LinkedList<Connection> conn_list = new LinkedList<>(); static { try { ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc"); driver = resourceBundle.getString("driver"); url = resourceBundle.getString("url"); userName = resourceBundle.getString("userName"); password = resourceBundle.getString("password"); ConnectionPoolInitSize = Integer.parseInt(resourceBundle.getString("ConnectionPoolInitSize")); Class.forName(driver); for (int i = 0; i < ConnectionPoolInitSize; i++) { Connection conn = DriverManager.getConnection(url, userName, password); conn_list.add(conn); } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } @Override public Connection getConnection() throws SQLException { ...
} } - getConnection()方法的重写:执行该方法会从LinkedList链表中拿出一个Connection对象conn并返回,然后通过动态代理实现:如果拿出来的这个conn对象执行了close方法,就将这个conn对象重新放回到LinkedList链表中。
@Override public Connection getConnection() throws SQLException { if (conn_list.size()>0) { final Connection conn = conn_list.removeFirst(); System.out.println(1); return (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (!method.getName().equalsIgnoreCase("close")) { return method.invoke(conn, args); } else { conn_list.add(conn); return null; } } }); } else { System.out.println("数据库连接失败"); } return null; }
- 封装的JDBC连接类DBUtils类(包括了getConnection方法和closeAll方法)
package connPool.jun.iplab; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class Get_jdbc_conn_from_cPool { private static ConnectionPool cPool = new ConnectionPool(); public static Connection getConnection() throws SQLException{ return cPool.getConnection(); } public static void CloseAll(ResultSet resultSet, PreparedStatement pStatement, Connection connection){ if (resultSet!=null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pStatement != null) { try { pStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
- 测试类
package connPool.jun.iplab; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class CPoolTest { public static void main(String[] args) throws SQLException { // 得到数据库连接对象 Connection conn = DBUtils.getConnection(); // 数据库操作 Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery("select * from user where sex='男'"); while (rs.next()) { System.out.println(rs.getInt("id") + " " + rs.getString("name") + " " + rs.getString("sex") + " " + rs.getString("birthday")); } // 执行这条语句时,conn对象执行了close()方法,因此会将conn对象重新添加到LinkedList集合中 DBUtils.CloseAll(rs, statement, conn); } }
- 输出
2 lsl 男 2017-12-12 4 winner 男 2018-08-07 9 nine 男 2018-08-07 ResultSet对象已关闭 Statement对象已关闭 Connection对象已关闭