快速学会使用JDBC+原理的全面详解(二)之快速学会使用JDBC、工具类、连接池
目录
快速学会使用JDBC+原理的全面详解(二)之快速学会使用JDBC、工具类、连接池
方式二:还可以通过工厂BasicDataSourceFactory从DBCP连接池中获得连接。
接上一篇博客:https://mp.csdn.net/mdeditor/85762993#
目录
方式二:还可以通过工厂BasicDataSourceFactory从DBCP连接池中获得连接。
2.4完整代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SQLInject2 {
public static void main(String[] args) throws Exception {
read2("'or 1 or'");// 会查询表里面的全部内容
// 利用MySQL的一些关键字产生错误
}
public static void read2(String name) throws Exception {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC";
// url格式:jdbc:自协议:子名称//主机名:端口/数据库名称
String userName = "root";
String password = "mysql";
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.建立连接
conn = DriverManager.getConnection(url, userName, password);
// 3.创建语句
// PreparedStatement可以对sql语句进行预处理,过滤一些MySQL中的字符
String sql = "select * from user where name=?";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
// 4.执行语句
rs = ps.executeQuery();
// 5.处理结果
while (rs.next()) {// 按行遍历
System.out.println(rs.getObject("id") + "\t" + rs.getObject("name")
+ "\t" + rs.getObject("birthday")
+ "\t" + rs.getObject("money") + "\t");// 获取每一列
}
} finally {
// 6.释放资源(后创建的先关闭):调用工具类中的方法
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
}
三、使用工具类编写JDBC程序
在使用JDBC连接数据库的过程中,我们发现大多数步骤都是一样的,如第(1)、(2)步注册驱动、建立连接,以及第(6)步释放资源的代码完全以封装起来。而我们平常使用数据库的频率如果很高所以我们需要对JDBC的通用代码进行提炼,提高代码复用率,提炼出来的工具类我们一般称为JDBCUtils,工具类中包含了我们常用的很多方法,比如连接数据库和断开连接就是常用的方法,我们只要掌握了JDBC原理,就可以自己设计满足需求工具类或参考以下工具类(后面我们会说到DBUtils工具类,这是Apache组织提供的JDBC工具类,比较全面,基本能够满足我们的需求)
如下为将第(1)、(2)步注册驱动、建立连接,以及第(6)步释放资源的代码封装进工具类的代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcUtils {
private static String url="jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC";//url格式:jdbc:自协议:子名称//主机名:端口/数据库名称
private static String name="root";
private static String password="mysql";
private JdbcUtils() {
}
//1.注册驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
}
//2.建立连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,name,password);
}
// 6.释放资源(后创建的先关闭)
public static void free(ResultSet rs, Statement st,Connection conn) {
try{
if(rs!=null)
rs.close();
}catch(SQLException e) {
e.printStackTrace();
}finally {
try{
if(st!=null)
st.close();
}catch(SQLException e) {
e.printStackTrace();
}finally {
try{
if(conn!=null)
conn.close();
}catch(SQLException e) {
e.printStackTrace();
}
}
}
}
}
2.4中的代码就可以通过工具类JdbcUtils中的静态代码块实现注册驱动,通过getConnection()静态方法实现与数据库建立连接,通过free()方法实现资源的释放。
调用工具类的代码如下:
public static void read2(String name) throws Exception {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//1.注册驱动:工具类实现
//2.建立连接:调用工具类中的方法
conn=JdbcUtils.getConnection();
//3.创建语句并建立语句执行对象
/*
* PreparedStatement相比于Statement的优势:
* 1、PreparedStatement没有SQL注入的问题,可以对sql语句进行预处理,过滤一些MySQL中的字符
* 2、当需要执行多次相似的SQL命令时,能够比较高效地执行。(只执行一次的话,PreparedStatement会包含与处理的时间)
*/
String sql="select * from user where name=?";
ps=conn.prepareStatement(sql);
ps.setString(1, name);
//4.执行语句
rs=ps.executeQuery();//区别于Statement,不传入参数
//5.处理结果
while(rs.next()) {//按行遍历
System.out.println(rs.getObject("id")+"\t"+rs.getObject("name")+"\t"
+rs.getObject("birthday")+"\t"+rs.getObject("money")+"\t");//获取每一列
}
}finally {
//6.释放资源(后创建的先关闭):调用工具类中的方法
JdbcUtils.free(rs, ps, conn);
}
}
}
四、连接池的使用
4.1、连接池是什么?为什么使用连接池?
程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
上述问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
4.2、自定义共享连接池
Java提供了一个公共接口:Javax.sql.DataSource。此接口提供了 DataSource
对象所表示的物理数据源的连接。作为 DriverManager
工具的替代项,DataSource
对象是获取连接的首选方法。
简单来说,就是DateSource接口是Drivermanager的替代项,提供了getConnection()方法并生产标准的Connection对象,那么要实现连接池,就需要实现该接口和该方法。
一般步骤:
- 实现数据源DataSource,即实现Javax.sql.DataSource接口;由于只是简单的演示,我们只实现其中的getConnection()和returnConnToPool()方法即可。使用getConnection()取出连接对象,使用returnConnToPool()归还连接对象。
- 创建一个LinkList容器。既然是“池子”,就需要保存东西,即存储连接池对象,而连接池涉及移除/添加连接池对象,优先考虑使用LinkList来存储。
- 使用静态代码块初始化若干个连接池对象。由于只是测试,我们初始化5个就行。
- 实现getConnection()方法。注意,为了保证连接对象只提供给一个线程(一个用户)使用,我们需要先将连接对象从池子中取出来。
- 用完的连接对象不需要执行close()而是放回池子去。
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
import javax.sql.DataSource;
import cn.itcast.jdbc.JdbcUtils;
public class MyDataSource implements DataSource {// [1]实现接口
// [2]创建一个容器存储连接池里的Connection对象。
private static LinkedList<Connection> pool = new LinkedList<Connection>();
// [3]初始化3个Connection对象放进池子。
static {
Connection conn = null;
for (int i = 0; i < 3; i++) {
try {
conn = JdbcUtils.getConnection();
} catch (SQLException e) {
e.printStackTrace();
} // 这里我们使用上面创建的JdbcUtils来获取连接
pool.add(conn);
}
}
@Override
// [4]从池子里取连接对象
public Connection getConnection() throws SQLException {
// 使用前先判断连接池是否有连接对象,没有则添加
Connection conn = null;
if (pool.size() == 0) {
for (int i = 0; i < 5; i++) {
conn = JdbcUtils.getConnection();
pool.add(conn);
}
}
conn = pool.removeFirst();// 取出来
return conn;
}
// [5]用完归还连接到连接池
public boolean returnConnToPool(Connection conn) {
return pool.add(conn);
}
// 下面是未实现的方法。
@Override
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
上面通过自定义连接池的案例简单介绍了连接池的原理。事实上,我们在开发过程中,不需要再关心数据库连接的问题,因为实际开发过程中有成熟的数据库连接池帮助我们处理。下面为大家介绍c3p0和DBCP连接池的使用,笔者通过案例介绍这两个连接池的简单实用,更为深层次的理解和使用大家可以参照他们各自的API。
(1)c3p0的API:https://www.mchange.com/projects/c3p0/
(2)DBCP的API:http://commons.apache.org/proper/commons-dbcp/apidocs/index.html
4.3、c3p0连接池
使用c3p0需要都jar包:c3p0-0.9.1.2.jar
使用方式一:快速创建
该方法对应于c3p0的API中快速创建部分,需要在程序内通过ComboPooledDataSource类的对象设置数据库的url地址、用户名user和用户密码password等信息。最后通过ComboPooledDataSource类的对象调用getConnection()方法获取连接池中的连接。
@Test
public void testC3p0() throws Exception {
/**
* 查看c3p0的API:网址:https://www.mchange.com/projects/c3p0/
*
* 1. 如何快速创建
* 2. 如何使用xml文件
*
* 此部分代码对应为API中的为快速创建c3p0
*/
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC" );
cpds.setUser("root");
cpds.setPassword("mysql");
Connection conn=cpds.getConnection();
System.out.println(conn);
}
使用方式二:使用xml文件
此部分内容参考于c3p0的API中 Appendix B: Configuation Files 的内容
步骤为:
1. 创建 c3p0-config.xml 文件,
参考帮助文档中 Appendix B: Configuation Files 的内容
2. 创建 ComboPooledDataSource 实例;
DataSource dataSource =
new ComboPooledDataSource("helloc3p0");
3. 从 DataSource 实例中获取数据库连接.
配置文件 c3p0-config.xml的代码如下:需要注意的是,配置时诸如"driverClass"、"jdbcUrl"、"initialPoolSize"等属性名需和API当中要求的一致,否则无法识别。
<c3p0-config>
<!-- This app is massive! -->
<named-config name="helloc3p0">
<!-- 指定连接数据源的基本属性 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">mysql</property>
<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">50</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- intergalactoApp adopts a different approach to configuring statement
caching -->
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
通过c3p0-config.xml 文件使用c3p0连接池的代码为:
@Test
public void testC3poWithConfigFile() throws Exception{
/**
* 查看c3p0的API:网址:https://www.mchange.com/projects/c3p0/
*
* 1. 如何快速创建
* 2. 如何使用xml文件
*
* 此部分代码为使用xml文件实现c3p0
*/
/**
* 1. 创建 c3p0-config.xml 文件,
* 参考帮助文档中 Appendix B: Configuation Files 的内容
* 2. 创建 ComboPooledDataSource 实例;
* DataSource dataSource =
* new ComboPooledDataSource("helloc3p0");
* 3. 从 DataSource 实例中获取数据库连接.
*/
DataSource dataSource =
new ComboPooledDataSource("helloc3p0"); //helloc3p0是c3p0-config.xml文件的名称
Connection conn=dataSource.getConnection();
}
4.4、DBCP连接池
方式一:在程序中设置必须的属性值
使用步骤:
1. 加入jar包:commons-dbcp-1.4和commons-pool-1.6
2.创建数据库连接池
3. 为数据源实例指定必须的属性
4. 从数据源中获取数据库连接
/*
* 使用DBCP数据库连接池
* 1. 加入jar包:commons-dbcp-1.4和commons-pool-1.6
* 2.创建数据库连接池
* 3. 为数据源实例指定必须的属性
* 4. 从数据源中获取数据库连接
*/
@Test
public void testDBCP() throws IOException, SQLException {
//通过dbcp.properties文件中的键值对来获取一部分属性
Properties properties = new Properties();
InputStream in = JDBCTest.class
.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(in);
String driver = properties.getProperty("Driver");
String url = properties.getProperty("url");
String userName = properties.getProperty("name");
String password = properties.getProperty("password");
System.out.println(driver);
BasicDataSource dataSource = new BasicDataSource();
//2. 为数据源实例指定必须的属性
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
//3. 指定数据源的一些可选的属性.
//1). 指定数据库连接池中初始化连接数的个数
dataSource.setInitialSize(5);
//2). 指定最大的连接数: 同一时刻可以同时向数据库申请的连接数
dataSource.setMaxActive(5);
//3). 指定小连接数: 在数据库连接池中保存的最少的空闲连接的数量
dataSource.setMinIdle(2);
//4).等待数据库连接池分配连接的最长时间. 单位为毫秒. 超出该时间将抛出异常.
dataSource.setMaxWait(1000 * 5);
//4. 从数据源中获取5个数据库连接
Connection connection1 = dataSource.getConnection();
System.out.println(connection1.getClass());
Connection connection2 = dataSource.getConnection();
System.out.println(connection2.getClass());
Connection connection3 = dataSource.getConnection();
System.out.println(connection3.getClass());
Connection connection4 = dataSource.getConnection();
System.out.println(connection4.getClass());
Connection connection5 = dataSource.getConnection();
System.out.println("5"+connection5.getClass());
//当连接诶池内5个连接被分配完时,创建一个线程在等待数据库连接池分配连接的最长时间maxWait之内释放一个连接,就可以再次获得一个连接。
new Thread() {
@Override
public void run() {
Connection connection6;
try {
connection6 = dataSource.getConnection();
System.out.println("6"+connection6.getClass());
} catch (SQLException e) {
e.printStackTrace();
}
}
}.start();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
connection5.close();
}
方式二:还可以通过工厂BasicDataSourceFactory从DBCP连接池中获得连接。
步骤为:
1. 加载 dbcp 的 properties 配置文件: 配置文件中的键需要来自 BasicDataSource(键的名字有规定,必须来自于 BasicDataSource)的属性.
2. 调用 BasicDataSourceFactory 的 createDataSource 方法创建 DataSource
实例
3. 从 DataSource 实例中获取数据库连接
dbcp.properties配置文件如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC
username=root
password=940918
initialSize=5
maxActive=5
minIdle=2
maxWait=5000
通过BasicDataSourceFactory 的 createDataSource() 方法创建 DataSource,再通过 DataSource 实例调用getConnection()方法获取数据库连接。
@Test
public void testDBCPWithDataSouceFactory() throws Exception {
Properties properties=new Properties();
InputStream in = JDBCTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(in);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());
}
相比于方式一,通过BasicDataSourceFactory的 createDataSource() 方法创建 DataSource来获取连接的方式解耦与数据库的属性配置,因为数据库的属性都在dbcp.properties文件中,不同的数据库或不同的用户只需要修改这个文件,而不需要修改程序,就可以实现获取连接。