文章目录
数据库连接池
在 JDBC 编程中,每次创建和断开 Connection 对象都会消耗一定的时间和 IO 资源。这是因为在 Java 程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则要把代表连接的 java.sql.Connection 对象等加载到内存中,所以建立数据库连接的开销很大,尤其是在大量的并发访问时。假如某网站一天的访问量是 10 万,那么,该网站的服务器就需要创建、断开连接 10 万次,频繁地创建、断开数据库连接势必会影响数据库的访问效率,甚至导致数据库崩溃。为了避免频繁地创建数据库连接,数据库连接池技术应运而生。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。接下来,通过一张图来简单描述应用程序如何通过连接池连接数据库,如图所示。
从图中可以看出,数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,当应用程序访问数据库时并不是直接创建 Connection, 而是向连接池
“申请” 一个 Connection。如果连接池中有空闲的 Connection,则将其返回,否则创建新的Connection。使用完毕后,连接池会将该 Connection 回收,并交付其他的线程使用,以减少创建和断开数据库连接的次数,提高数据库的访问效率。
DataSource 接口
为了获取数据库连接对象(Connection),JDBC 提供了 javax.sql.Datasource 接口,它负责与数据库建立连接,并定义了返回值为 Connection 对象的方法:
- Connection getConnection()
- Connection getConnection(String username, String password)
上述两个重载的方法都能用来获取 Connection 对象。不同的是,第 1 个方法是通过无参的方式建立与数据库的连接,第 2 个方法是通过传入登录信息的方式建立与数据库的连接。
把实现了 javax.DataSource 接口的类称为数据源(数据的来源),在数据源中存储了所有建立数据库连接的信息。就像通过指定文件名称可以在文件系统中找到文件一样,通过提供正确的数据源名称,也可以找到相应的数据库连接。
数据源中包含数据库连接池。如果数据是水,数据库就是水库,数据源就是连接到水库的管道,终端用户看到的数据集是管道里流出来的水。一些开源组织提供了数据源的独立实现,常用的有 DBCP 数据源和 C3P0 数据源。
DBCP 数据源
DBCP 是数据库连接池(DataBase Connection Pool)的简称是 Apache 组织下的开源连接池实现,也是 Tomcat 服务器使用的连接池组件。单独使用 DBCP 数据源时,需要在应用程序中导入两个 JAR 包,具体如下。
commons-dbcp.jar 包
commons-dbcp.jar 包是 DBCP 数据源的实现包,包含所有操作数据库连接信息和数据连接池初始化信息的方法,并实现了 DataSource 接口的 getConnection() 方法。
commons-pool.jar 包
commons-pool.jar 包是 DBCP 数据库连接池实现包的依赖包,为 commons- dbcp.jar 包中的方法提供了支持。可以这么说,没有该依赖包,commons- dbcp.jar 包中的很多方法就没有办法实现。这两个 JAR 包可以在 Apache 官网地址 “http://commons. apache.org/proper/” 中查询下载到。其中,commons-dbcp.jar 中包含两个核心的类,分别是 BasicDataSourceFactory 和 BasicDataSource,它们都包含获取 DBCP 数据源对象的方法。接下来,针对这两个类的方法进行详细的讲解。BasicDataSource 是 DataSource 接口的实现类,主要包括设置数据源对象的方法,该类的常用方法介绍如表所示。
在表中,列举了 BasicDataSource 对象的常用方法,其中,setDriverClassName()、setUrl()、setUsername()、setPassword() 等方法都是设置数据库连接信息的方法,setlnitialSize()、setMaxActive()、setMinldle()等方法都是设置数据库连接池初始化值的方法,getConnection()方法表示从 DBCP 数据源中获取一个数据库连接。BasicDataSourceFactory 是创建 BasicDataSource 对象的工厂类,它包含一个返回值为 BasicDataSource 对象的方法 createDataSource(),该方法通过读取配置文件的信息生成数据源对象并返回给调用者。这种把数据库的连接信息和数据源的初始化信息提取出来写进配置文件的方式,让代码看起来更加简洁,思路也更加清晰。
1. 通过 BasicDataSource 类直接创建数据源对象
需要加载的包:
MyDBCPTest.java
package com.xxx;
import org.apache.commons.dbcp2.BasicDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
public class MyDBCPTest {
public static DataSource ds;
static {
// 获取 DBCP 数据源实现对象
BasicDataSource bds = new BasicDataSource();
// 设置连接数据库需要的配置信息
// 设置驱动类
bds.setDriverClassName("com.mysql.jdbc.Driver");
// 设置初始化参数
String url = "jdbc:mysql://localhost:3306/javatask";
String username = "root";
String password = "root";
bds.setUrl(url);
bds.setUsername(username);
bds.setPassword(password);
bds.setInitialSize(5);
bds.setMaxTotal(10);
// 数据源
ds = bds;
}
public static void main(String[] args) throws SQLException {
// 获取数据库连接对象
Connection conn = ds.getConnection();
// 获取数据库连接信息
DatabaseMetaData metaData = conn.getMetaData();
System.out.println("url="+metaData.getURL()
+ ",username="+metaData.getUserName()
+ ",DriverName="+metaData.getDriverName());
}
}
运行结果
2. 通过读取配置文件创建数据源对象
除了使用 BasicDataSource 直接创建数据源对象外,还可以使用BasicDataSourceFactory 工厂类读取配置文件,创建数据源对象,然后获取数据库连接对象。
1)在 src 目录下创建 dbcp.properties 配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/javatask
username=root
password=root
initialSize=5
maxInitial=10
maxIdle=5
2)MyDBCPTest2.java
package com.xxx;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Properties;
public class MyDBCPTest2 {
public static DataSource ds;
static {
Properties prop = new Properties();
try{
// 通过类加载器找到文件路径,读取配置文件
InputStream ins = new MyDBCPTest2().getClass().getClassLoader().getResourceAsStream("dbcp.properties");
// 把文件以输入流的形式加载到配置对象中
prop.load(ins);
// 创建数据源对象
ds = BasicDataSourceFactory.createDataSource(prop);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws SQLException {
// 通过数据源获取 connection
Connection conn = ds.getConnection();
// 获取数据库连接信息
DatabaseMetaData metaData = conn.getMetaData();
System.out.println("url="+metaData.getURL()
+ ",username="+metaData.getUserName()
+ ",DriverName="+metaData.getDriverName());
}
}
运行结果
C3P0 数据源
C3P0 是目前最流行的开源数据库连接池之一,它实现了 DataSource 数据源接口,支持 JDBC2 和 JDBC3 的标准规范,易于扩展并且性能优越,著名的开源框架Hibernate 和 Spring 都支持该数据源。在使用 C3P0 数据源开发时,需要了解 C3P0 中 DataSource 接口的实现类 ComboPooledDataSource,它是 C3P0 的核心类,提供了数据源对象的相关方法,该类的常用方法介绍如表所示。
通过 ComboPooledDataSource 表和 BasicDataSource 表的比较,发现 C3P0 和 DBCP 数据源所提供的方法大部分功能相同,都包含了设置数据库连接信息的方法和数据库连接池初始化的方法,以及 DataSource 接口中的 getConnection() 方法。
当使用 C3P0 数据源时,首先需要创建数据源对象,创建数据源对象可以使用 ComboPooledDataSource 类,该类有两个构造方法,分别是ComboPooledDataSource() 和 ComboPooledDataSource(String configName)。
需要载入的包
1. 通过 ComboPooledDataSource() 构造方法创建数据源对象
C3P0Test.java
package com.xxx;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class C3P0Test {
public static DataSource ds;
static {
ComboPooledDataSource cpds = new ComboPooledDataSource();
try {
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/javatask");
cpds.setUser("root");
cpds.setPassword("root");
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(10);
ds = cpds;
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws SQLException {
Connection conn = ds.getConnection();
System.out.println(conn);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user where id=1");
while (rs.next()){
System.out.println(rs.getInt(1) + "\t"
+ rs.getString(2) + "\t"
+ rs.getString(3) + "\t"
+ rs.getString(4));
}
}
}
运行效果
2. 通过读取配置文件创建数据源对象
使用 ComboPooledDataSource(String configName) 构造方法读取 c3p0-config.xml 配置文件,从而创建数据源对象,然后获取数据库连接对象。
1)在 src 下创建一个 c3p0-config.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/javatask</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="checkoutTimeout">30000</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<named-config name="jiaotong">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/javatask
</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
</named-config>
</c3p0-config>
2)C3P0Test02.java
package com.xxx;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class C3P0Test02 {
public static DataSource ds;
static {
ComboPooledDataSource cpds = new ComboPooledDataSource("jiaotong");
// ComboPooledDataSource cpds = new ComboPooledDataSource();
ds = cpds;
}
public static void main(String[] args) throws SQLException {
Connection conn = ds.getConnection();
System.out.println(conn);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user where id=1");
while (rs.next()){
System.out.println(rs.getInt(1) + "\t"
+ rs.getString(2) + "\t"
+ rs.getString(3) + "\t"
+ rs.getString(4));
}
}
}
运行效果
DBUtils 工具介绍
为了更加简单地使用 JDBC,Apache 组织提供了一个 DBUtils 工具,它是操作数据库的一个组件,实现了对 JDBC 的简单封装,可以在不影响性能的情况下极大地简化 JDBC 的编码工作量。DBUtils 工具可以在 “http://commons apache.org/proper/commons-dbutils/index.html” 下载到。DBUtils 工具的核心是 org.apache.commons.dbutils.QueryRunner 类和 org .apache.commons.dbutils.ResultSetHandler 接口,了解它们对于 DBUtils 工具的学习和使用非常重要。
需要导入的包
QueryRunner 类
QueryRunner 类简化了执行 SQL 语句的代码,它与 ResultSetHandler 组合在一起就能完成大部分的数据库操作,大大地减少了编码量。QueryRunner 类提供了带有一个参数的构造方法,该方法以 javax.sql.DataSource 作为参数传递到 QueryRunner 的构造方法中来获取 Connection 对象。针对不同的数据库操作,
QueryRunner 类提供了几种常见的方法,具体如下。
- query(String sql, ResultSetHandler rsh, Object… params)方法
该方法用于执行查询操作,它可以从提供给构造方法的数据源 DataSource 或使用的 setDataSource() 方法中获得连接。 - update(String sql, Object… params)方法
该方法用于执行插入、更新或者删除操作,其中,参数params表示SQL语句中的置换参数。 - update(String sql)方法
该方法用来执行插入、更新或者删除操作,它不需要置换参数。
ResultSetHandler 接口
ResultSetHandler 接口用于处理 ResultSet 结果集,它可以将结果集中的数据转为不同的形式。根据结果集中数据类型的不同,ResultSetHandler 提供了几种常见的实现类,具体如下。
- BeanHandler:将结果集中的第1行数据封装到一个对应的 JavaBean 实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的 JavaBean 实例中,并存放到 List 里。
- ScalarHandler:将结果集中某一条记录的其中某一列的数据存储成 Object 对象。
另外,在 ResultSetHandler 接口中,提供了一个单独的方法 handle (java.sql.ResultSet rs),如果上述实现类没有提供想要的功能,可以通过自定义一个实现 ResultSetHandler 接口的类,然后通过重写 handle() 方法,实现结果集的处理。
实例
项目包结构
User.java —— 该类用于封装 User 对象
package com.xxx.domain;
public class User {
private int id;
private String name;
private String username;
private String pass;
private String email;
public User() {
}
public User(int id, String name, String username, String pass, String email) {
this.id = id;
this.name = name;
this.username = username;
this.pass = pass;
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", username='" + username + '\'' +
", pass='" + pass + '\'' +
", email='" + email + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
封装Util,任选其一即可
DBCPUtil
package com.xxx.util;
import org.apache.commons.dbcp2.BasicDataSource;
import javax.sql.DataSource;
public class DBCPUtils {
public static DataSource ds;
static {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://127.0.0.1:3306/javatask");
bds.setUsername("root");
bds.setPassword("root");
bds.setInitialSize(5);
bds.setMaxTotal(10);
bds.setMaxIdle(5);
ds = bds;
}
}
JDBCUtil
package com.xxx.util;
import java.sql.*;
public class JDBCUtils {
public static Connection getConn() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/javatask";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
// 关闭资源
public static void release(Statement stmt, Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void release(ResultSet rs, Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
release(stmt, conn);
}
}
BaseDao.java —— 该类实现了一个通用的查询方法
package com.xxx.dao;
import com.xxx.util.DBCPUtils;
import com.xxx.util.JDBCUtils;
import org.apache.commons.dbutils.ResultSetHandler;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class BaseDao {
// 优化查询
public static Object query(String sql, ResultSetHandler<?> rsh, Object... params){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
// 定义返回结果
Object obj = null;
try {
// 获得连接
// conn = JDBCUtils.getConn();
conn = DBCPUtils.ds.getConnection();
// 预编译 sql
pstmt = conn.prepareStatement(sql);
// 将参数设置进去
for (int i = 0; i < params.length; i++){
pstmt.setObject(i+1, params[i]);
}
// 发送 sql
rs = pstmt.executeQuery();
// 让调用者去实现对结果集的处理
obj = rsh.handle(rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
// 返回结果
return obj;
}
}
DBUtilTest 测试使用
package com.xxx.test;
import com.xxx.dao.BaseDao;
import com.xxx.domain.User;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.util.Scanner;
public class DBUtilsTest {
public static void main(String[] args) {
BaseDao baseDao = new BaseDao();
String sql = "select * from user where id=?";
System.out.println("请输入要查找的用户id(整数)...");
Scanner scan = new Scanner(System.in);
int id = scan.nextInt();
Object obj = baseDao.query(sql, new BeanHandler<User>(User.class), id);
if(obj != null && obj instanceof User){
User u = (User) obj;
System.out.println("你要查找的用户是:"+u);
}else {
System.out.println("没有找到你要查找的用户");
}
}
}
运行结果
使用 DBUtils 实现增删该查
需要导入的包
项目结构
1. 创建 Book 实体类
package com.xxx.domain;
public class Book {
private int bid;
private String bookname;
private String author;
private double price;
public Book() {
}
public Book(int bid, String bookname, String author, double price) {
this.bid = bid;
this.bookname = bookname;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"bid=" + bid +
", bookname='" + bookname + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
public int getBid() {
return bid;
}
public void setBid(int bid) {
this.bid = bid;
}
public String getBookname() {
return bookname;
}
public void setBookname(String bookname) {
this.bookname = bookname;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
2. 创建 DBCPUtils 类
package com.xxx.util;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
// 根据配置文件加载
public class DBCPUtils {
public static DataSource ds;
static {
Properties prop = new Properties();
try{
InputStream ins = new DBCPUtils().getClass().getClassLoader().getResourceAsStream("dbcp.properties");
prop.load(ins);
ds = BasicDataSourceFactory.createDataSource(prop);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 创建 DBUtilsDao 类
package com.xxx.test;
import com.xxx.Book;
import com.xxx.DBCPUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
public class DBUtilsDao {
// 返回 List
public List findAll() throws SQLException{
QueryRunner runner = new QueryRunner(DBCPUtils.ds);
String sql = "select * from book";
List books = (List) runner.query(sql, new BeanListHandler(Book.class));
return books;
}
// 单个对象
public Book find(int id) throws SQLException{
QueryRunner runner = new QueryRunner(DBCPUtils.ds);
String sql = "select * from book where bid=?";
Book book = runner.query(sql, new BeanHandler<>(Book.class),new Object[]{id});
return book;
}
// 添加书
public Boolean insert(Book book) throws SQLException{
QueryRunner runner = new QueryRunner(DBCPUtils.ds);
String sql = "insert into book (bookname,author,price) values (?,?,?)";
int num = runner.update(sql, new Object[]{book.getBookname(), book.getAuthor(),book.getPrice()});
if(num > 0) return true;
return false;
}
// 修改书信息
public Boolean update(Book book) throws SQLException{
QueryRunner runner = new QueryRunner(DBCPUtils.ds);
String sql = "update book set bookname=?,author=?,price=? where bid=?";
int num = runner.update(sql, new Object[]{book.getBookname(),book.getAuthor(),book.getPrice(), book.getBid()});
if(num > 0) return true;
return false;
}
// 删除书
public Boolean delete(int id) throws SQLException {
QueryRunner runner = new QueryRunner(DBCPUtils.ds);
String sql = "delete from book where bid=?";
int num = runner.update(sql, id);
if (num > 0) return true;
return false;
}
}
4. 测试 DBUtilsDao 类中的增删改查操作
测试单个查找
package com.xxx.test;
import com.xxx.Book;
import java.sql.SQLException;
import java.util.Scanner;
// 测试单个查找
public class TestFind {
private static DBUtilsDao dao = new DBUtilsDao();
public static void testfind() throws SQLException {
Scanner scan = new Scanner(System.in);
System.out.print("请输入要查找的 bid: ");
int id = scan.nextInt();
Book book = dao.find(id);
if(book != null){
System.out.println(book.getBid() + "," + book.getBookname() + ","
+ book.getAuthor() + "," + book.getPrice());
}else{
System.out.println("查找失败");
}
}
public static void main(String[] args) throws SQLException {
testfind();
}
}
测试全部数据的查询
package com.xxx.test;
import com.xxx.Book;
import java.sql.SQLException;
import java.util.List;
// 测试查询
public class TestFindAll {
private static DBUtilsDao dao = new DBUtilsDao();
public static void testfindAll() throws SQLException {
List<Book> books = dao.findAll();
if(books != null){
for (Book b: books){
System.out.println(b);
}
}else{
System.out.println("查找失败");
}
}
public static void main(String[] args) throws SQLException {
testfindAll();
}
}
测试插入数据
package com.xxx.test;
import com.xxx.Book;
import java.sql.SQLException;
// 测试插入
public class TestInsert {
private static DBUtilsDao dao = new DBUtilsDao();
public static void testInsert() throws SQLException {
Book book = new Book();
book.setBookname("Java");
book.setAuthor("大龙");
book.setPrice(59.0);
boolean flag = dao.insert(book);
if(flag){
System.out.println("添加成功");
}else {
System.out.println("添加失败");
}
}
public static void main(String[] args) throws SQLException {
testInsert();
}
}
测试修改数据
package com.xxx.test;
import com.xxx.Book;
import java.sql.SQLException;
import java.util.Scanner;
// 测试更新
public class TestUpdate {
private static DBUtilsDao dao = new DBUtilsDao();
public static void testupdate() throws SQLException {
Book book = new Book();
book.setBookname("Java");
book.setAuthor("小博");
book.setPrice(43.3);
Scanner scan = new Scanner(System.in);
System.out.print("请输入要更新的 bid: ");
int id = scan.nextInt();
book.setBid(id);
boolean flag = dao.update(book);
if(flag){
System.out.println("更新成功");
}else {
System.out.println("更新失败");
}
}
public static void main(String[] args) throws SQLException {
testupdate();
}
}
测试删除数据
package com.xxx.test;
import java.sql.SQLException;
import java.util.Scanner;
// 测试删除
public class TestDelete {
private static DBUtilsDao dao = new DBUtilsDao();
public static void testdelete() throws SQLException {
Scanner scan = new Scanner(System.in);
System.out.print("请输入要删除的 bid: ");
int id = scan.nextInt();
boolean flag = dao.delete(id);
System.out.println(flag);
}
public static void main(String[] args) throws SQLException {
testdelete();
}
}