连接池和DBUtils
为什么要使用连接池
Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化. 数据库的连接建立,开销比较大,所以在一开始就用一个池子来装若干个连接对象。
程序初始化的时候,初始化多个连接,将多个连接放入到池(集合)中.每次获取的时候,都可以直接从连接池中进行获取.使用结束以后,将连接归还到池中.
连接池原理【重点】
-
开始先使用集合存储若干个连接对象到集合中
-
如果有程序需要使用连接,就从连接池中获取
-
使用完毕后,要记得归还而不是关闭连接
-
如果连接池中的连接对象已经使用完毕,再来一个新的程序想要连接,那么可以创建一个新的连接给它。使用完毕之后,可以归还到连接池里面,也可以选择不归还。
-
连接池最好使用LinkedList来实现
因为它增删比较快。
使用连接池的目的: 可以让连接得到复用, 避免浪费
自定义连接池-初级版本
package com.itheima.pool_01;
import com.itheima.util.JdbcUtil02;
import java.sql.Connection;
import java.util.LinkedList;
/*
* @创建时间: 2020/7/23 9:40
* @描述: 自定义连接池(初级版本)
* @ 思路
* 1. 一开始要准备好5个连接放到集合当中 (只会发生一次) :: 静态代码块
*
* 2. 暴露一个方法,供他人获取连接
*
* 3. 暴露一个方法,供他人归还连接
*/
public class MyDataSource01 {
//连接池
static LinkedList<Connection> pool;
static{
try {
//构建连接池
pool = new LinkedList<>();
//初始化5个连接对象,放到连接池中
for (int i = 0; i < 5; i++) {
Connection conn = JdbcUtil02.getConnection();
pool.addLast(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public Connection getConn(){
return pool.removeFirst() ;
}
//归还连接
public void addBack(Connection conn){
pool.addLast(conn);
}
public int size(){
return pool.size();
}
}
自定义连接池-进阶版本
datasource接口
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商(用户)需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
package com.itheima.pool_01;
import com.itheima.util.JdbcUtil02;
import javax.sql.DataSource;
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;
/*
* @创建时间: 2020/7/23 9:56
*
* @描述: 自定义连接池(升级版) : 实现了Java的数据源连接池的规范
*
* @思路
* 1. 一开始也要准备连接对象,放到连接池里面去。(和初级版本一样)
*
* 2. 提供一个方法,供他人获取连接。
*
* 3. 提供一个方法,供他人归还连接
*/
public class MyDataSource02 implements DataSource {
static LinkedList<Connection> pool;
static{
try {
pool = new LinkedList<>();
for (int i = 0; i < 5; i++) {
Connection conn = JdbcUtil02.getConnection();
pool.addLast(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
return pool.removeFirst();
}
public void addBack(Connection conn){
pool.addLast(conn);
}
public int size(){
return pool.size();
}
//=========================下面的方法都不需要理会======================
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
- 测试代码
package com.itheima.pool_01;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* @创建时间: 2020/7/23 9:46
* @描述: 测试升级版本的连接池
*/
public class TestMyDataSource02 {
@Test
public void testDataSource() throws SQLException {
//1. 拿连接
MyDataSource02 ds = new MyDataSource02();
System.out.println("连接数量1:" + ds.size());
Connection conn = ds.getConnection();
System.out.println("连接数量2:" + ds.size());
String sql = "insert into user values (null , ? , ? , ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "bb");
ps.setString(2, "bb");
ps.setString(3, "bb");
ps.executeUpdate();
//执行完毕之后,
ps.close();
//得归还连接
ds.addBack(conn);
System.out.println("连接数量3:" + ds.size());
}
}
1. 定义一个类,实现接口DataSource
2. 定义一个linkedList作为连接池,在静态代码块里面,初始化5个连接
3. 使用实现的getConnection方法对外提供连接
4. 自己编写归还的方法 :: addBack
升级版本的优势和弱势
优势: 规范了,实现了DataSource
缺点:有很多的方法,不用,但是也得写上。
自定义连接池-终极版本
装饰者模式
装饰者模式,是 23种常用的面向对象软件的设计模式之一. 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。
装饰者的作用:改写已存在的类的某个方法或某些方法, 增强方法的逻辑
-
使用装饰者模式需要满足的条件
- 增强类和被增强类实现的是同一个接口
- 增强类里面要拿到被增强类的引用 句柄 | 对象
装饰者模式的使用【重点】
实现步骤:
- 增强类(WrapperCar)和被增强类(Qq)需要实现同一个接口(Car)
- 增强类(WrapperCar)里面需要得到被增强类(Qq)的引用, eg:把车开进去
- 对于不需要改写的方法(stop()),调用被增强类原有的方法。
- 对于需要改写的方法(run()),写自己的代码
例子: 张三追妹子, 需要买车.
张三钱不够, 只能买Qq车(只能跑60迈, 可以刹车)
张三开了一段时间, 发现追不到妹子
把车开到改装店进行改装(把速度改成5s破百, 刹车不改)
interface Car{
void run();
void stop();
}
class Qq implements Car{} //QQ
public class QQWrap implements Car{
- 接口: Car.java
package com.itheima.mode_02;
/*
* @创建时间: 2020/7/23 10:33
* @描述: TODO
*/
public interface Car {
void run();
void stop();
}
- 被增强的类: Qq.java
package com.itheima.mode_02;
/*
* @创建时间: 2020/7/23 10:34
* @描述: 被装饰类
*/
public class Qq implements Car{
@Override
public void run() {
System.out.println("10s 破百");
}
@Override
public void stop() {
System.out.println("停车");
}
}
- 增强的类: WrapperCar.java
package com.itheima.mode_02;
/*
* @创建时间: 2020/7/23 10:36
* @描述: 装饰类,装饰QQ车
*
* @ 思路:
* 1. 实现Car接口
* 2. 要持有被装饰类的对象,为什么要持有,因为有些功能不想增强,那还是得用原来的功能
*/
public class QQWrap implements Car{
//被装饰的对象
Car c;
public QQWrap(Car c) {
this.c = c;
}
//只想改造这个run方法
@Override
public void run() {
System.out.println("3s 破百");
}
//如果不想改造某些功能,那么还是使用原来的功能
@Override
public void stop() {
c.stop();
}
}
- 测试代码
package com.itheima.mode_02;
/*
* @创建时间: 2020/7/23 10:35
* @描述: TODO
*/
public class Test {
public static void main(String[] args) {
//原厂的车
Car c = new Qq();
// c.run();
// c.stop();
//把原厂的车丢给装饰类
Car c2= new QQWrap(c);
//最终用的是装饰的对象
c2.run();
c2.stop();
}
}
自定义连接池终极版本
- 目标就是装饰conn对象
- 创建MyConnection实现Connection
- 在MyConnection里面需要得到被增强的connection对象(通过构造方法传进去)
- 改写close()的逻辑, 变成归还
- 其它方法的逻辑, 还是调用被增强connection对象之前的逻辑
package com.itheima.pool_01;
import java.sql.*;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
/*
* @创建时间: 2020/7/23 10:49
* @描述: 装饰类,装饰原生的Connection对象
*
* @思路:
* 1. 装饰类和被装饰类要实现同一个接口
*
* 2. 方法这么多,要装饰哪个方法呢??
* 至少目前知道了要装饰close方法
*
* 3. 原来的连接对象,调用close执行的是关闭的操作,现在我们想执行归还的动作。
*
* 4. 归还连接,需要用到两个对象,
* a. 连接池,
* b. 原生对象.
*/
public class ConnectionWrap implements Connection {
LinkedList<Connection> pool;
Connection srcConnection;
public ConnectionWrap(LinkedList<Connection> pool, Connection srcConnection) {
this.pool = pool;
this.srcConnection = srcConnection;
}
//装饰的方法
@Override
public void close() throws SQLException {
//连接池对象.addLast(连接对象);
pool.addLast(srcConnection);
}
//这两个方法我们不想装饰,但是也不能直接写null ,因为外面的程序需要使用他们。
@Override
public Statement createStatement() throws SQLException {
return srcConnection.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
System.out.println("执行这个函数了~~");
return srcConnection.prepareStatement(sql);
}
//无需理会的方法
....
}
package com.itheima.pool_01;
import com.itheima.util.JdbcUtil02;
import javax.sql.DataSource;
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;
/*
* @创建时间: 2020/7/23 9:56
*
* @描述: 自定义连接池(终极版本)
*
* @思路
* 使用装饰者模式对连接对象进行装饰
*
* 1. 返回连接的对象的时候,不能直接返回原生的conn,需要对原生的conn装饰一番
*
* 2. 在getConnection 返回装饰的对象
*
* 3. 定义一个装饰类,装饰连接对象。
*/
public class MyDataSource03 implements DataSource {
static LinkedList<Connection> pool;
static{
try {
pool = new LinkedList<>();
for (int i = 0; i < 5; i++) {
Connection conn = JdbcUtil02.getConnection();
pool.addLast(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
//这是原生的连接对象
Connection conn = pool.removeFirst();
//装饰一下原生对象
Connection c = new ConnectionWrap(pool , conn);
//把装饰的对象抛出去。
return c;
}
public void addBack(Connection conn){
pool.addLast(conn);
}
public int size(){
return pool.size();
}
//=========================下面的方法都不需要理会======================
}
- 测试代码
package com.itheima.pool_01;
import com.itheima.util.JdbcUtil02;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* @创建时间: 2020/7/23 9:46
* @描述: 测试终极版本的连接池
*/
public class TestMyDataSource03 {
@Test
public void testDataSource() throws SQLException {
//1. 拿连接
MyDataSource03 ds = new MyDataSource03();
System.out.println("连接数量1:" + ds.size());
Connection conn = ds.getConnection();
System.out.println("连接数量2:" + ds.size());
String sql = "insert into user values (null , ? , ? , ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "dd");
ps.setString(2, "dd");
ps.setString(3, "dd");
ps.executeUpdate();
JdbcUtil02.closeAll(ps, conn);
System.out.println("连接数量3:" + ds.size());
}
}
第三方连接池
- C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。C3P0是异步操作的,所以一些操作时间过长的JDBC通过其它的辅助线程完成。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能
- 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
- DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。dbcp没有自动回收空闲连接的功能。
用的比较多的是:
- C3P0
- druid
- 光连接池
C3P0
- C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.
- 使用C3P0需要添加
c3p0-0.9.1.2.jar
c3p0的使用
通过硬编码来编写
步骤
- 拷贝jar
- 创建C3P0连接池对象
- 从C3P0连接池对象里面获得connection
实现:
package com.itheima.c3p0_03;
import com.itheima.util.JdbcUtil02;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
/*
* @创建时间: 2020/7/23 11:17
* @描述: 硬编码,c3p0连接池, 使用代码来构建c3p0连接池。
*/
public class TestC3P0Demo01 {
@Test
public void testDataSource(){
Connection conn = null;
PreparedStatement ps = null;
try {
//c3p0连接池
ComboPooledDataSource ds = new ComboPooledDataSource();
//设置连接池,设置如何去拿连接,账号,密码,拿的是哪个数据库的连接
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/day20");
ds.setUser("root");
ds.setPassword("root");
System.out.println("初始化的大小:"+ds.getInitialPoolSize());
System.out.println("最大的连接数:"+ds.getMaxPoolSize());
System.out.println("最小的连接数:"+ds.getMinPoolSize());
//获取连接
conn = ds.getConnection();
//可以执行操作
String sql = "insert into user values (null , ? , ? , ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "zz");
ps.setString(2, "zz");
ps.setString(3, "zz");
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil02.closeAll(ps, conn);
}
}
}
通过配置文件来编写
步骤:
- 拷贝jar包
- 在src源码路径下,定义一个xml文件,文件的名称固定是: c3p0-config.xml
- 内容如下:
<c3p0-config>
<default-config>
<!--连接数据库的信息-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day20</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property>
</default-config>
</c3p0-config>
- 如果对这个连接池的配置不满意,请查看c3p0的官方文档。
- 默认创建连接池对象,会使用
default-config
的配置。 当然也可以指定使用具体的配置。
DataSource ds = new ComboPooledDataSource();
- 实现代码
package com.itheima.c3p0_03;
import com.itheima.util.JdbcUtil02;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
/*
* @创建时间: 2020/7/23 11:17
* @描述: 使用配置文件来配置连接池
*/
public class TestC3P0Demo02 {
@Test
public void testDataSource(){
Connection conn = null;
PreparedStatement ps = null;
try {
//c3p0连接池
ComboPooledDataSource ds = new ComboPooledDataSource();
//这是使用指定名称的配置
//ComboPooledDataSource ds = new ComboPooledDataSource("intergalactoApp");
System.out.println("初始化的大小:"+ds.getInitialPoolSize());
System.out.println("最大的连接数:"+ds.getMaxPoolSize());
System.out.println("最小的连接数:"+ds.getMinPoolSize());
//获取连接
conn = ds.getConnection();
//可以执行操作
String sql = "insert into user values (null , ? , ? , ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "yy");
ps.setString(2, "yy");
ps.setString(3, "yy");
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil02.closeAll(ps, conn);
}
}
}
我们之前写的工具类(JdbcUtils)每次都会创建一个新的连接, 使用完成之后, 都给销毁了; 所以现在我们要使用c3p0来改写工具类. 也就意味着,我们从此告别了JdbcUtils. 后面会使用c3p0写的工具类
思路:
- 创建C3P0Utils这个类
- 定义DataSource, 保证DataSource全局只有一个
- 定义getConnection()方法从DataSource获得连接
- 定义closeAll()方法 释放资源
package com.itheima.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*
* @创建时间: 2020/7/23 11:56
* @描述: c3p0连接池的工具类
* @思路:
* 1. 首先得先有连接池,连接池做几个? 一个。 ComboPooledDataSource ds = new ComboPooledDataSource();
* 2. 提供一个方法供别人获取连接
* 3. 提供关闭释放的方法
*/
public class C3p0Util {
static ComboPooledDataSource ds;
static {
ds = new ComboPooledDataSource();
}
//提供获取连接
public static Connection getConn(){
try {
return ds.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
return null;
}
}
//关闭释放资源
//重载!!!!!
public static void closeAll( Statement st , Connection conn){
closeAll(null , st ,conn);
}
//真正这个st和 conn 是
public static void closeAll(ResultSet rs , Statement st , Connection conn){
try {
if(rs!=null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(st!=null)
st.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(conn !=null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
- 测试类的代码
package com.itheima.c3p0_03;
import com.itheima.util.C3p0Util;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
/*
* @创建时间: 2020/7/23 11:17
* @描述: 测试c3p0的工具类
*/
public class TestC3P0Demo03 {
@Test
public void testDataSource(){
Connection conn = null;
PreparedStatement ps = null;
try {
//c3p0连接池
conn = C3p0Util.getConn();
//可以执行操作
String sql = "insert into user values (null , ? , ? , ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "abc");
ps.setString(2, "abc");
ps.setString(3, "abc");
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
C3p0Util.closeAll(ps , conn);
}
}
}
小结
-
C3P0 配置文件方式使用
- 拷贝jar包, jar包有两个
- 拷贝配置文件
- 直接创建连接池对象, ComboPooledDataSource
-
C3P0工具类
- 使用静态代码块来创建连接池,保证只有一个连接池
- 对外提供一个方法,获取连接
- 对外提供回收释放资源的方法 ,关闭所有
代替昨天写的JdbcUtils
DRUID
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是国内目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
DRUID的使用
通过硬编码方式
步骤:
- 导入jar包
- 创建dataSource
- 设置连接的属性
- 获取连接
实现:
package com.itheima.druid_04;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* @创建时间: 2020/7/23 12:13
* @描述: 硬编码配置, 代码配置druid连接池
*/
public class TestDruidDataSource01 {
@Test
public void testDataSource() throws SQLException {
//技巧,如果想给这个对象设置东西,1. 在构造里面设置, 2. 采用setXXX设置
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/day20");
ds.setUsername("root");
ds.setPassword("root");
Connection conn = ds.getConnection();
//可以执行操作
String sql = "insert into user values (null , ? , ? , ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "abc");
ps.setString(2, "yy");
ps.setString(3, "yy");
ps.executeUpdate();
ps.close();
conn.close();
}
}
通过配置文件方式
步骤:
- 导入jar包
- 在src下面,创建一个properties文件,名字随意。一般:druid.properties, 内容如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day20
username=root
password=root
- 创建连接池,这里用的是工厂来创建。
package com.itheima.druid_04;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;
/*
* @创建时间: 2020/7/23 12:13
* @描述: 配置文件配置druid连接池
*/
public class TestDruidDataSource02 {
@Test
public void testDataSource() throws Exception {
//目标: 就是为了要构建一个数据源。
//在Java语言里面创建对象有2种方式
// 1. 直接new , 2.使用工厂方法创建
Properties p = new Properties();
p.load(TestDruidDataSource02.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(p);
Connection conn = ds.getConnection();
//可以执行操作
String sql = "insert into user values (null , ? , ? , ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "abc");
ps.setString(2, "yy");
ps.setString(3, "yy");
ps.executeUpdate();
ps.close();
conn.close();
}
}
小结
- Druid配置文件使用
- 拷贝jar
- 拷贝配置文件到src
- 读取配置文件成properties对象
- 使用工厂根据properties创建DataSource
- 从DataSource获得Connection
DBUtils
DbUtils是Apache组织提供的一个对JDBC进行简单封装CRUD的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能
DBUtils的常用API介绍
- 创建QueryRunner对象的API
QueryRunner 是所有增删改查都要用的一个对象,它需要传递进去连接对象,现在的连接对象都是从连接池里面拿。
QueryRunner 会自动维护连接对象
- 执行增删改的SQL语句的API
QueryRunner runner = new QueryRunner(datasource); //增删改,有参数和没有参数 runner.update(sql , "" , "" , ""); runner.update(sql);
- 执行查询的SQL语句的API
QueryRunner runner = new QueryRunner(datasource);
//查询数据
runner.query(sql , 结果集处理器 , 参数 );
runner.query(conn , sql , 结果集处理器 , 参数)
知识点-使用DBUtils完成增删改
1.目标
- 掌握使用DBUtils完成增删改
2.步骤
- 拷贝jar包
- 创建QueryRunner()对象,传入dataSource
- 调用update()方法
3.实现
package com.itheima._05_dbutil;
import com.itheima.util.C3p0Util;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;
import java.sql.SQLException;
/*
* @创建时间: 2020/7/23 14:42
* @描述: DBUtils的简单入门 (增删改)
*/
public class DBUtilsDemo01 {
@Test
public void testInsert() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
int result = runner.update("insert into user values(null , ? , ?,?)" , "aa","bb","cc" );
if(result > 0 ){
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
}
@Test
public void testDelete() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
int result = runner.update("delete from user where id = ?" , 42 );
if(result > 0 ){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}
@Test
public void testUpdate() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
int result = runner.update("update user set nickname = ? where id = ?" , "管理员" ,45);
if(result > 0 ){
System.out.println("更新成功");
}else{
System.out.println("更新失败");
}
}
}
4.小结
- 创建QueryRuner()对象, 传入DataSource
- 调用update(String sql, Object…params)
使用DBUtils完成查询
- 拷贝jar包
- 创建QueryRunner()对象 传入DataSource
- 调用query(sql, resultSetHandler,params)方法
ResultSetHandler结果集处理类介绍
代码实现
查询一条数据封装到JavaBean对象中(使用BeanHandler)
//查询一条完整的记录
@Test
public void testQueryForBean() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
String sql = "select * from user where id = ?";
//1. 这个Query方法返回的是一个实实在在的user对象
//2. 那么就表明了,这个方法的内部一定会先创建出来user对象
//3. 把查表得到的数据放到这个对象身上然后返回。
//4. 问!!!!! BeanHandler的构造参数要的是一个字节码, 请问,要这个字节码有何用??
User user = runner.query(sql , new BeanHandler<User>(User.class), 45);
System.out.println("user=" + user);
}
查询多条数据封装到List中(使用BeanListHandler)
//查询多条完整的记录
@Test
public void testQueryAll() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
String sql = "select * from user";
//如果要查询多条记录,那么传递进去的是BeanListHandler 返回的是List<对象>
List<User> list = runner.query(sql ,new BeanListHandler<User>(User.class));
for(User u : list){
System.out.println("u=" + u);
}
}
查询一条数据,封装到Map对象中(使用MapHandler)
//查询返回的结果装到一个map集合里面 KEY = VALUE
// map 的key就是列的名字, value就是列的值。
@Test
public void testQueryMap() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
String sql = "select * from user where id = ?";
//得到数组。
Map<String , Object> m= runner.query(sql , new MapHandler(), 45);
for(Map.Entry<String , Object> entry : m.entrySet()){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
查询多条数据,封装到List<Map>
对象中(使用MapListHandler)
//查询很多数据放到 List<Map<String , Object>>
@Test
public void testQueryListMap() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
String sql = "select * from user";
//得到数组。
List<Map<String , Object>> list= runner.query(sql , new MapListHandler());
//遍历
for(Map<String ,Object> m : list){
for(Map.Entry<String , Object> entry : m.entrySet()){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("------\n");
}
}
查询单个数据(使用ScalarHandler())
//聚合查询 , 其实就是查询总数 、 最大值、 最小值,平均值,
@Test
public void testQueryScalar() throws SQLException {
QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
String sql = "select count(*) from user";
//查询得到的只是一个数字
Long count = (Long)runner.query(sql , new ScalarHandler());
System.out.println("count=" + count);
}
-
步骤
a. 拷贝jar包
b. 创建QueryRunner对象,执行操作,要记得传递进去DataSource
c. update | query 对应是增删改 和 查询
-
查询的结果处理器的区别
- BeanHandler : 查询一条记录(完整记录)
- BeanListHandler : 查询多条记录
- ScalarHandler : 聚合查询
-
注意的地方
- JavaBean要规范, 要提供get & set方法 , 无参构造也要给上。
自定义DBUtils
我们要自定义DBUtils, 就需要知道列名, 参数个数等, 这些可以通过数的元数据就是一些注明数据库信息的数据。
简单来说: 数据库的元数据就是 数据库、表、列的定义信息。
① 由PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。
② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。
ParameterMetaData
ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData
可用于获取有关PreparedStatement
对象和其预编译sql语句
中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型
获得ParameterMetaData:
`ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData ()`
ParameterMetaData相关的API
- int getParameterCount(); 获得参数个数
- int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
实例代码
@Test
public void testPs() throws SQLException {
Connection conn = C3p0Util.getConn();
String sql = "insert into user values(null , ? , ?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "aa");
ps.setString(2, "aa");
ps.setString(3, "aa");
//得到元数据
ParameterMetaData md = ps.getParameterMetaData();
System.out.println("参数的个数:" + md.getParameterCount());
//后续的这些方法会报错。
System.out.println("第一个的参数类型名称:"+md.getParameterClassName(1));
}
ResultSetMetaData
ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来,
ResultSetMetaData
可用于获取有关ResultSet
对象中列的类型和属性的信息。
获得ResultSetMetaData:
`ResultSetMetaData resultSetMetaData = resultSet.getMetaData()`
resultSetMetaData 相关的API
- getColumnCount(); 获取结果集中列项目的个数
- getColumnName(int column); 获得数据指定列的列名
- getColumnTypeName();获取指定列的SQL类型
- getColumnClassName();获取指定列SQL类型对应于Java的类型
实例代码
@Test
public void testResultSetMetaData() throws SQLException {
Connection conn = C3p0Util.getConn();
String sql = "select * from user where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 45);
ResultSet rs = ps.executeQuery();
//重点关注元数据
ResultSetMetaData md = rs.getMetaData();
System.out.println("列的个数:" + md.getColumnCount());
System.out.println("第1列的名字:"+md.getColumnName(1));
System.out.println("第2列的数据库类型:"+md.getColumnTypeName(2));
System.out.println("第2列的java类型:"+md.getColumnClassName(2));
}
小结
- 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型…
- mysql元数据:
- ParameterMetaData
- ResultSetMetaData
案例-自定义DBUtils增删改
- 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
- 定义int update(String sql, Object…params)方法
//0.非空判断
//1.从dataSource里面获得connection
//2.根据sql语句创建预编译sql语句对象
//3.获得参数元数据对象, 获得参数的个数
//4.遍历, 从params取出值, 依次给参数? 赋值
//5.执行
//6.释放资源
实现
package com.itheima._05_dbutil;
import com.itheima.util.C3p0Util;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* @创建时间: 2020/7/23 16:13
* @描述: TODO
*/
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds){
this.ds = ds;
}
//增删改的功能
// MyQueryRunner runner = new MyQueryRunner(C3P0Util.getDataSource);
// String sql = "insert into user values (null , ? , ? , ?)";
//runner.update(sql , "aa","bb","cc")
public int update(String sql , Object ... param){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = ds.getConnection();
ps = conn.prepareStatement(sql);
//1. 不知道SQL语句里面的 ? 有几个, :: 元数据可以知道
ParameterMetaData md = ps.getParameterMetaData();
int paramCount = md.getParameterCount();
//如果可变参数的个数和?的个数不对等
if(param.length != paramCount) {
System.err.println("参数个数和?的总数不对等");
throw new RuntimeException("参数个数和?的总数不对等");
//return -1;
}
for (int i = 0; i <paramCount ; i++) {
ps.setObject(i+1 , param[i] );
}
return ps.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
C3p0Util.closeAll(ps , conn);
}
return 0 ;
}
}
小结
- 创建一个类,MyQuerryRunner
- 通过构造函数,从外面传递进去DataSource
- 定义一个方法 update , 用于完成 增删改的功能
- 在update 函数内部,步骤如下:
- 通过dataSource获取连接对象
- 创建preparestatement对象
- 使用preparestatement对象获取元数据, 以便知道 ? 的个数有几个
- 循环给占位赋值
- 执行excuteUpdate .
- 关闭ps , conn.