ColumnListHandler typora-copy-images-to: img
day20-连接池和DBUtils
今日内容
- 连接池
- 自定义连接池------>难点,不需要掌握---->目的在于理解数据库连接池的原理以及装饰者设计模式的使用
- 使用第三方连接池----->重点掌握
- C3P0
- DRUID
- DBUtils----->重点掌握
- 元数据
复习
-
使用PreparedStatement进行CRUD操作步骤:
- 拷贝mysql驱动包到模块下,并添加依赖
- 拷贝JDBCUtils.java工具类和db.properties配置文件到模块下
- 步骤:
- 注册驱动,获得连接
- 预编译sql语句,得到预编译对象
- 为sql语句设置参数
- 执行sql语句,处理结果
- 释放资源
/** * Created by PengZhiLin on 2021/8/24 9:05 */ public class Test1_查 { public static void main(String[] args) throws Exception{ //- 注册驱动,获得连接 Connection connection = JDBCUtils.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 为sql语句设置参数 ps.setInt(1,2); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } //- 释放资源 JDBCUtils.release(resultSet,ps,connection); System.out.println(user); } }
/** * Created by PengZhiLin on 2021/8/24 9:11 */ public class Test2_增 { public static void main(String[] args) throws Exception { // 1.注册驱动,获得连接 Connection connection = JDBCUtils.getConnection(); // 2.预编译sql语句,得到预编译对象 String sql = "insert into user values(null,?,?,?)"; PreparedStatement ps = connection.prepareStatement(sql); // 3.为sql语句设置参数 ps.setString(1, "zs"); ps.setString(2, "123456"); ps.setString(3, "老张"); // 4.执行sql语句,处理结果 int rows = ps.executeUpdate(); System.out.println("受影响的行数:" + rows); // 5.释放资源 JDBCUtils.release(null,ps,connection); } }
第一章-自定义连接池
1.1 连接池概念
为什么要使用连接池
Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化.
程序初始化的时候,初始化多个连接,将多个连接放入到池(集合)中.每次获取的时候,都可以直接从连接池中进行获取.使用结束以后,将连接归还到池中.
连接池原理【重点】
- 程序一开始就创建一定数量的连接,放在一个容器(集合)中,这个容器称为连接池。
- 使用的时候直接从连接池中取一个已经创建好的连接对象, 使用完成之后 归还到池子
- 如果池子里面的连接使用完了, 还有程序需要使用连接, 先等待一段时间(eg: 3s), 如果在这段时间之内有连接归还, 就拿去使用; 如果还没有连接归还, 新创建一个, 但是新创建的这一个不会归还了(销毁)
- 集合选择LinkedList
- 增删比较快
- LinkedList里面的removeFirst()和addLast()方法和连接池的原理吻合
1.2 自定义连接池-初级版本
分析
- 创建连接池类
- 在连接池类中,定义一个LinkedList集合(表示连接池)
- 在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中
- 提供一个公共的非静态方法来获取连接对象(getAbc)
- 提供一个公共的非静态方法来归还连接对象(addBack)
- 提供一个公共的静态方法来获取连接池中连接的数量
实现
-
连接池:
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class MyDataSource01 { //- 在连接池类中,定义一个LinkedList集合(表示连接池) private static LinkedList<Connection> pools = new LinkedList<>(); //- 在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中 static { try { for (int i = 0; i < 5; i++) { // 创建连接 Connection connection = JDBCUtils.getConnection(); // 把连接添加到连接池中 pools.add(connection); } } catch (SQLException e) { e.printStackTrace(); } } //- 提供一个公共的非静态方法来获取连接对象(getAbc) public Connection getAbc(){ // 获取连接池中第一个连接 Connection connection = pools.removeFirst(); // 返回连接 return connection; } //- 提供一个公共的非静态方法来归还连接对象(addBack) public void addBack(Connection connection){ pools.addLast(connection); } //- 提供一个公共的静态方法来获取连接池中连接的数量 public static int size(){ return pools.size(); } }
-
测试:
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class Test { public static void main(String[] args) throws Exception { // 创建连接池对象 MyDataSource01 dataSource = new MyDataSource01(); System.out.println("获取连接之前,连接池中连接的数量:"+MyDataSource01.size());// 5 // 1.注册驱动,获得连接 Connection connection = dataSource.getAbc(); System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource01.size());// 4 // 2.预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); // 3.设置sql语句参数 ps.setInt(1, 2); // 4.执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id, username, password, nickname); } // 归还连接 dataSource.addBack(connection); System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource01.size());// 5 // 5.释放资源 JDBCUtils.release(resultSet, ps, null); System.out.println(user); } }
1.3 自定义连接池-进阶版本
分析
在初级版本版本中, 我们定义的方法是getAbc(). 因为是自定义的.如果改用李四的自定义的连接池,李四定义的方法是getCon(), 那么我们的源码就需要修改, 这样不方便维护. 所以sun公司定义了一个接口DataSource,让自定义连接池有了规范
实现
-
概述: javax.sql.DataSource是Java为数据库连接池提供的公共接口,各个厂商(用户)需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
-
分析:
- 创建连接池类, 实现DataSource接口,重写方法
- 在连接池类中,定义一个LinkedList集合(表示连接池)
- 在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中
- 使用重写的方法getConnection,来获取连接对象
- 提供一个公共的非静态方法来归还连接对象(addBack)
- 提供一个公共的静态方法来获取连接池中连接的数量
-
实现:
-
连接池:
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class MyDataSource02 implements DataSource { //- 在连接池类中,定义一个LinkedList集合(表示连接池) private static LinkedList<Connection> pools = new LinkedList<>(); //- 在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中 static { try { for (int i = 0; i < 5; i++) { // 创建连接 Connection connection = JDBCUtils.getConnection(); // 把连接添加到连接池中 pools.add(connection); } } catch (SQLException e) { e.printStackTrace(); } } //- 提供一个公共的非静态方法来获取连接对象(getAbc) /* public Connection getAbc(){ // 获取连接池中第一个连接 Connection connection = pools.removeFirst(); // 返回连接 return connection; }*/ @Override public Connection getConnection() throws SQLException { // 获取连接池中第一个连接 Connection connection = pools.removeFirst(); // 返回连接 return connection; } //- 提供一个公共的非静态方法来归还连接对象(addBack) public void addBack(Connection connection){ pools.addLast(connection); } //- 提供一个公共的静态方法来获取连接池中连接的数量 public static int size(){ return pools.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; } }
-
测试:
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class Test { public static void main(String[] args) throws Exception { // 创建连接池对象 MyDataSource02 dataSource = new MyDataSource02(); System.out.println("获取连接之前,连接池中连接的数量:"+MyDataSource02.size());// 5 // 1.注册驱动,获得连接 Connection connection = dataSource.getConnection(); System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource02.size());// 4 // 2.预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); // 3.设置sql语句参数 ps.setInt(1, 2); // 4.执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id, username, password, nickname); } // 归还连接 dataSource.addBack(connection); System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource02.size());// 5 // 5.释放资源 JDBCUtils.release(resultSet, ps, null); System.out.println(user); } }
-
1.4 进阶版后存在的问题分析
编写连接池遇到的问题
- 实现DataSource接口后,addBack()又有问题了
- 在进阶版本中, 我们定义的归还连接的方法是addBack(). 因为是自定义的连接池.如果改用李四的自定义的连接池,李四定义的归还连接的方法是back(), 那么我们的源码就需要修改, 这样不方便维护.
- DataSource接口中也没有定义归还连接的方法,所以只要自定义的连接池,归还连接的方法就可以随便定义,不方便维护.
- 解决办法: 能不能不引入新的api,直接调用之前的connection.close(),但是这个close不是关闭,而是归还
- Connection原有的close方法是关闭连接(销毁连接)
- 增强close方法,把原有关闭连接的功能变成归还连接的功能,这样连接池中就不需要定义归还连接的方法了
解决办法
-
继承
- 条件:可以控制父类, 最起码知道父类的名字
- 返回的连接对象所属类的类名无法得知,只知道该类是实现了Connection接口
-
装饰者模式
-
作用:改写已存在的类的某个方法或某些方法
-
条件:
- 装饰类和被装饰类要实现同一个接口
- 装饰类里面要拿到被装饰类的引用
- 在装饰类中,对需要增强的方法进行增强
- 在装饰类中,对不需要增强的方法就调用被装饰类中原有的方法
-
案例:
public interface Star { void sing(); void dance(); } // 被装饰类 public class LiuDeHua implements Star { @Override public void sing() { System.out.println("刘德华在唱忘情水..."); } @Override public void dance() { System.out.println("刘德华在跳街舞..."); } } // 装饰类 public class LiuDeHuaWrapper implements Star{ Star star; public LiuDeHuaWrapper(Star star) { this.star = star; } @Override public void sing() { // 增强 System.out.println("刘德华在唱忘情水..."); System.out.println("刘德华在唱冰雨..."); System.out.println("刘德华在唱笨小孩..."); } @Override public void dance() { star.dance(); } } public class Test { public static void main(String[] args) { LiuDeHua ldh = new LiuDeHua(); LiuDeHuaWrapper ldhw = new LiuDeHuaWrapper(ldh); ldhw.sing(); ldhw.dance(); } }
-
-
动态代理
1.5 自定义连接池-终极版本
分析
-
创建增强的连接类,对close方法进行增强,其余方法依然调用原有的连接对象的方法
-
创建连接池类, 实现DataSource接口,重写方法
-
在连接池类中,定义一个LinkedList集合(表示连接池)
-
在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中
-
使用重写的方法getConnection,来获取连接对象----->增强的连接对象
-
提供一个公共的静态方法来获取连接池中连接的数量
-
与进阶版的区别:
- 连接池类中不需要提供归还连接的方法
- getConnection获得连接的方法不再返回被增强的连接对象,而是返回增强的连接对象
实现
-
增强的连接类
/** * Created by PengZhiLin on 2021/8/24 10:27 */ public class MyConnectionWrapper implements Connection { private Connection connection;// 接收被增强的连接对象 private LinkedList<Connection> pools;// 接收连接池 public MyConnectionWrapper(Connection connection, LinkedList<Connection> pools) { this.connection = connection; this.pools = pools; } @Override public void close() throws SQLException { // 归还连接--->连接池,被增强的连接对象 pools.addLast(connection); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return connection.prepareStatement(sql); } // .... }
-
连接池
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class MyDataSource03 implements DataSource { //- 在连接池类中,定义一个LinkedList集合(表示连接池) private static LinkedList<Connection> pools = new LinkedList<>(); //- 在连接池类的静态代码块中,创建固定数量的连接,并存储到LinkedList集合中 static { try { for (int i = 0; i < 5; i++) { // 创建连接 Connection connection = JDBCUtils.getConnection(); // 把连接添加到连接池中 pools.add(connection); } } catch (SQLException e) { e.printStackTrace(); } } //- 提供一个公共的非静态方法来获取连接对象(getAbc) /* public Connection getAbc(){ // 获取连接池中第一个连接 Connection connection = pools.removeFirst(); // 返回连接 return connection; }*/ @Override public Connection getConnection() throws SQLException { // 获取连接池中第一个连接 Connection connection = pools.removeFirst();// 被增强的连接对象 // 创建增强的连接对象,传入被增强的连接对象,以及连接池 MyConnectionWrapper myConnectionWrapper = new MyConnectionWrapper(connection, pools); // 返回增强的连接对象 return myConnectionWrapper; } //- 提供一个公共的非静态方法来归还连接对象(addBack) /*public void addBack(Connection connection){ pools.addLast(connection); }*/ //- 提供一个公共的静态方法来获取连接池中连接的数量 public static int size(){ return pools.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; } }
-
测试类
/** * Created by PengZhiLin on 2021/8/24 9:53 */ public class Test { public static void main(String[] args) throws Exception { // 创建连接池对象 MyDataSource03 dataSource = new MyDataSource03(); System.out.println("获取连接之前,连接池中连接的数量:"+MyDataSource03.size());// 5 // 1.注册驱动,获得连接 Connection connection = dataSource.getConnection(); System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource03.size());// 4 // 2.预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); // 3.设置sql语句参数 ps.setInt(1, 2); // 4.执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id, username, password, nickname); } // 5.释放资源 JDBCUtils.release(resultSet, ps, connection);// connection.close(); 归还连接 System.out.println("获取连接之后,连接池中连接的数量:"+MyDataSource03.size());// 5 System.out.println(user); } }
第二章-第三方连接池
2.1 C3P0连接池
c3p0介绍
- C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.
- 使用C3P0需要添加c3p0-0.9.1.2.jar
c3p0的使用
通过硬编码来编写【了解】
-
思路:
- 创建C3P0连接池对象
- 设置连接池参数
- 获得连接
- 预编译sql语句,得到预编译对象
- 设置sql语句参数
- 执行sql语句,处理结果
- 释放资源
-
实现:
/** * Created by PengZhiLin on 2021/8/24 10:59 */ public class Test1_硬编码方式 { public static void main(String[] args) throws Exception{ //- 创建C3P0连接池对象 ComboPooledDataSource dataSource = new ComboPooledDataSource(); //- 设置连接池参数 dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/day19_1"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setInitialPoolSize(5); //- 获得连接 Connection connection = dataSource.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 设置sql语句参数 ps.setInt(1,2); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数 System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数 System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数 //- 释放资源 JDBCUtils.release(resultSet,ps,connection); System.out.println(user); Thread.sleep(5000); System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数 System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数 System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数 } }
-
C3P0连接池的配置参数----- 参考c3p0的官方文档,或者网络上直接搜索
通过配置文件来编写【重点】
-
思路:
- 拷贝c3p0-config.xml配置文件到src路径下,然后修改配置文件中的参数值
- c3p0-config.xml文件必须放在src路径下,否则会报错
- c3p0-config.xml文件名必须是c3p0-config.xml,不能修改
- c3p0-config.xml文件中property标签中的name属性值不能修改
- 创建C3P0连接池对象
- 获得连接对象
- 预编译sql语句,得到预编译对象
- 设置sql语句参数
- 执行sql语句,处理结果
- 释放资源
- 拷贝c3p0-config.xml配置文件到src路径下,然后修改配置文件中的参数值
-
实现:
/** * Created by PengZhiLin on 2021/8/24 10:59 */ public class Test2_配置文件方式 { public static void main(String[] args) throws Exception{ //- 创建C3P0连接池对象 ComboPooledDataSource dataSource = new ComboPooledDataSource(); //- 获得连接 Connection connection = dataSource.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 设置sql语句参数 ps.setInt(1,2); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数 System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数 System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数 //- 释放资源 JDBCUtils.release(resultSet,ps,connection); System.out.println(user); Thread.sleep(5000); System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数 System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数 System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数 } }
使用c3p0改写工具类【重点】
-
问题: 每次需要连接的时候,都需要创建连接池对象,用完了就销毁,所以就会不断的创建连接池,销毁连接池
-
解决: 整个程序只需要创建一个连接池对象,其余地方直接使用这个唯一的连接池对象获得连接即可
-
工具类:
-
思路:
- 创建唯一的连接池对象---->private static final修饰
- 提供一个获取连接池对象的静态方法
- 提供一个获取连接的静态方法
- 提供一个释放资源的静态方法
-
实现:
/** * Created by PengZhiLin on 2021/8/24 11:30 */ public class C3P0Utils { //- 创建唯一的C3P0连接池对象 private static final ComboPooledDataSource DATASOURCE = new ComboPooledDataSource(); /** * 获得连接池的方法 * @return */ public static DataSource getDataSource(){ return DATASOURCE; } /** * 获得连接 * @return * @throws SQLException */ public static Connection getConnection() throws SQLException { Connection connection = DATASOURCE.getConnection(); return connection; } /** * 释放资源 * * @param resultSet * @param statement * @param connection */ public static void release(ResultSet resultSet, Statement statement, Connection connection) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
/** * Created by PengZhiLin on 2021/8/24 10:59 */ public class Test3_测试工具类 { public static void main(String[] args) throws Exception{ // 获得连接池 Connection connection = C3P0Utils.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 设置sql语句参数 ps.setInt(1,2); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } //- 释放资源 C3P0Utils.release(resultSet,ps,connection); System.out.println(user); } }
-
2.2 DRUID连接池
DRUID介绍
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是国内目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid 或者 maven仓库
DRUID连接池使用的jar包:druid-1.0.9.jar
DRUID的使用
通过硬编码方式【了解】
-
思路:
- 创建Druid连接池对象
- 设置连接池的配置参数
- 获得连接
- 预编译sql语句,得到预编译对象
- 设置sql语句参数
- 执行sql语句,处理结果
- 释放资源
-
实现:
/** * Created by PengZhiLin on 2021/8/24 11:54 */ public class Test1_硬编码方式 { public static void main(String[] args) throws Exception{ //- 创建Druid连接池对象 DruidDataSource dataSource = new DruidDataSource(); //- 设置连接池的配置参数 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/day19_1"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setInitialSize(5); //- 获得连接 Connection connection = dataSource.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 设置sql语句参数 ps.setInt(1,2); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } System.out.println("正在使用的:"+dataSource.getActiveCount());// 1 System.out.println("正在空闲的:"+dataSource.getPoolingCount());// 4 //- 释放资源 JDBCUtils.release(resultSet,ps,connection); System.out.println(user); System.out.println("正在使用的:"+dataSource.getActiveCount());// 0 System.out.println("正在空闲的:"+dataSource.getPoolingCount());// 5 } }
通过配置文件方式【重点】
-
思路:
- 导入Druid的jar包
- 拷贝druid.properties配置文件到src路径下
- druid.properties配置文件建议放在src路径下,可以修改
- druid.properties配置文件名建议是druid.properties,可以修改
- druid.properties配置文件的键名不能修改,必须是set方法去掉set,然后首字母变小写
- 使用:
- 创建Properties对象,加载配置文件中的数据
- 创建Druid连接池对象,传入Properties对象
- 获得连接
- 预编译sql语句,得到预编译对象
- 设置sql语句参数
- 执行slq语句,处理结果
- 释放资源
-
实现:
/** * Created by PengZhiLin on 2021/8/24 11:54 */ public class Test2_配置文件方式 { public static void main(String[] args) throws Exception{ // - 创建Properties对象 Properties pro = new Properties(); // - 加载配置文件中的数据 FileInputStream fis = new FileInputStream("day20\\src\\druid.properties"); pro.load(fis); //- 创建Druid连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(pro); //- 获得连接 Connection connection = dataSource.getConnection(); //- 预编译sql语句,得到预编译对象 String sql = "select * from user where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); //- 设置sql语句参数 ps.setInt(1,4); //- 执行sql语句,处理结果 ResultSet resultSet = ps.executeQuery(); // 定义一个User变量 User user = null; while (resultSet.next()) { // 取值 int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); // 封装数据 user = new User(id,username,password,nickname); } //- 释放资源 JDBCUtils.release(resultSet,ps,connection); System.out.println(user); } }
Druid工具类的制作
- 步骤:
- 0.定义一个DataSource成员变量
- 1.在静态代码块中,加载配置文件,创建Druid连接池对象
- 2.提供一个公共的静态方法获得连接池
- 3.提供一个公共的静态方法获得连接
- 4.提供一个公共的静态方法释放资源
/**
* Created by PengZhiLin on 2021/8/24 12:10
*/
public class DruidUtils {
// 定义DataSource成员变量
private static DataSource dataSource;
static {
try {
// - 创建Properties对象
Properties pro = new Properties();
// - 加载配置文件中的数据
InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//- 创建Druid连接池对象
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获得连接池
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 获得连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 释放资源
*
* @param resultSet
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* Created by PengZhiLin on 2021/8/24 11:54
*/
public class Test3_测试工具类 {
public static void main(String[] args) throws Exception{
// 获得连接
Connection connection = DruidUtils.getConnection();
//- 预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
//- 设置sql语句参数
ps.setInt(1,4);
//- 执行sql语句,处理结果
ResultSet resultSet = ps.executeQuery();
// 定义一个User变量
User user = null;
while (resultSet.next()) {
// 取值
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
// 封装数据
user = new User(id,username,password,nickname);
}
//- 释放资源
DruidUtils.release(resultSet,ps,connection);
System.out.println(user);
}
}
第三章-DBUtils
3.1 DBUtils的介绍
DBUtils的概述
DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能
DBUtils的常用API介绍
-
创建QueryRunner对象的API
public QueryRunner(DataSource ds)
,提供数据源(连接池),DBUtils底层自动维护连接connection -
QueryRunner执行增删改的SQL语句的API
int update(String sql, Object... params)
, params参数就是可变参数,参数个数取决于语句中问号的个数功能: 执行增删改的sql语句
参数1: sql语句
参数2: sql语句需要的参数值
-
执行查询的SQL语句的API
返回值类型 query(String sql, ResultSetHandler<T> rsh, Object... params)
,其中ResultSetHandler是一个接口,表示结果集处理者
3.2 使用DBUtils完成增删改
-
实现步骤:
- 导入DButils的jar包---->mysql驱动包,第三方数据库连接池的jar包,配置文件,工具类
- 创建QueryRunner对象,传入连接池对象
- 调用update方法执行sql语句
-
增删改
/** * Created by PengZhiLin on 2021/8/24 14:34 */ public class TestDemo { // 创建QueryRunner对象,传入连接池 QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); @Test public void insert() throws SQLException { // 调用update方法 String sql = "insert into user values(null,?,?,?)"; int rows = qr.update(sql, "tq", "123456", "老田"); System.out.println("受影响的行数:" + rows); } @Test public void update() throws SQLException { // 调用update方法 String sql = "update user set password = ? where nickname = ?"; int rows = qr.update(sql, "abcdef","老田"); System.out.println("受影响的行数:" + rows); } @Test public void delete() throws SQLException { // 调用update方法 String sql = "delete from user where nickname = ?"; int rows = qr.update(sql,"老田"); System.out.println("受影响的行数:" + rows); } }
3.3 JavaBean
-
JavaBean说白了就是一个类, 用来封装数据用的
-
JavaBean要求
- 私有字段\成员变量
- 提供公共的get/set方法
- 无参构造
- 建议满参构造
- 实现Serializable
-
字段(成员变量)和属性
- 字段: 成员变量 eg:
private String username;
- 属性: set\get方法去掉get或者set首字母变小写 eg:
setUsername()方法-去掉set->Username-->首字母变小写->username
一般情况下,我们通过IDEA直接生成的set/get 习惯把字段和属性搞成一样而言
- 字段: 成员变量 eg:
-
注意:
- 使用DBUtils查询得到的结果可以自动封装成员一个对象,但有一个前提条件就是该对象所属的类的属性名必须和表中的字段名一致,否则封装失败
- 也就是说: 类的set\get方法去掉set\get,然后首字母变小写之后得到的名称,必须和表中的列名一致
- 学习完DBUtils完成查询操作后,进行测试
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String nickname;
public User(Integer id, String username, String password, String nickname) {
this.id = id;
this.username = username;
this.password = password;
this.nickname = nickname;
}
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
3.4 使用DBUtils完成查询
ResultSetHandler结果集处理接口的实现类介绍
ArrayHandler: 适合封装查询结果为一条记录,会把这条记录中每个字段的值封装到Object[]数组中
ArrayListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个数组中,再把这些数组存储到List集合
BeanHandler:适合封装查询结果为一条记录的,把这条记录的数据封装到一个指定的对象中
BeanListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个对象中,再把这些对象存储到List集合
ColunmListHandler:适合封装查询结果为单列多行的,会把当前列中所有的值存储到List集合
KeyedHandler:适合封装查询结果为多条记录的,会把每一条记录封装成一个Map集合,再把Map集合存储到另一个Map集合中
MapHandler: 适合封装查询结果为一条记录的,会把这条记录中每个字段的值封装到Map集合中
MapListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个Map集合中,再把这些Map集合存储到List集合
ScalarHandler:适合查询结果为单个值的
代码实现
查询一条记录(使用ArrayHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo1_查询结果为一条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select01() throws SQLException {
// 查询一条记录,封装成一个数组
String sql = "select * from user where id = ?";
Object[] arr = qr.query(sql, new ArrayHandler(), 6);
System.out.println(Arrays.toString(arr));
}
}
查询一条数据封装到JavaBean对象中(使用BeanHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo1_查询结果为一条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select02() throws SQLException {
// 查询一条记录,封装成一个JavaBean
String sql = "select * from user where id = ?";
User user = qr.query(sql, new BeanHandler<User>(User.class), 6);
System.out.println(user);
}
}
查询一条数据,封装到Map对象中(使用MapHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo1_查询结果为一条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select03() throws SQLException {
// 查询一条记录,封装成一个Map
String sql = "select * from user where id = ?";
Map<String, Object> map = qr.query(sql, new MapHandler(), 6);
System.out.println(map);
}
}
查询多条数据封装到List<Object[]>中(使用ArrayListHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo2_查询结果多条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select01() throws SQLException {
// 查询多条记录,每条记录封装成一个数组,再把数组存储到List集合中
String sql = "select * from user";
List<Object[]> list = qr.query(sql, new ArrayListHandler());
for (Object[] arr : list) {
System.out.println(Arrays.toString(arr));
}
}
}
查询多条数据封装到List中(使用BeanListHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo2_查询结果多条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select02() throws SQLException {
// 查询多条记录,每条记录封装成一个JavaBean,再把JavaBean存储到List集合中
String sql = "select * from user";
List<User> list = qr.query(sql, new BeanListHandler<User>(User.class));
for (User user : list) {
System.out.println(user);
}
}
}
查询多条数据,封装到List<Map>
对象中(使用MapListHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo2_查询结果多条记录 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select03() throws SQLException {
// 查询多条记录,每条封装成一个Map,再把Map存储到List集合中
String sql = "select * from user";
List<Map<String, Object>> list = qr.query(sql, new MapListHandler());
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
}
查询单个数据(使用ScalarHandler())
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo4_查询结果单个值 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select01() throws SQLException {
// 查询单列多行,封装到一个List集合中
String sql = "select count(*) from user";
long count = (Long)qr.query(sql, new ScalarHandler());
System.out.println(count);
}
}
查询单列多个值(使用ColumnListHandler)
/**
* Created by PengZhiLin on 2021/8/24 15:02
*/
public class TestDemo3_查询结果单列多行 {
// 创建QueryRunner对象,传入连接池对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
@Test
public void select01() throws SQLException {
// 查询单列多行,封装到一个List集合中
String sql = "select username from user";
List<Object> list = qr.query(sql, new ColumnListHandler());
for (Object o : list) {
System.out.println(o);
}
}
}
第四章-自定义DBUtils
4.1 元数据
-
概述:元数据(MetaData),即定义数据的数据。打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。
简单来说: 数据库的元数据就是 数据库、表、列的定义信息。
分类: 参数元数据,结果集元数据 -
参数:---->数据
- 参数元数据: 参数的个数,参数的类型
-
结果集--->数据
- 结果集元数据:
- 结果集列的个数
- 结果集列的名字
- 结果集列的类型
- 结果集列对应的java类型
- ....
- 结果集元数据:
4.2 参数元数据
-
概述: 参数元素数据就是使用ParameterMetaData类来表示
-
如何获取参数元数据对象:
- 使用PreparedStatement预编译对象来获取参数的元数据对象
public ParameterMetaData getParameterMetaData()
- 使用PreparedStatement预编译对象来获取参数的元数据对象
-
ParameterMetaData相关的API
-
int getParameterCount(); 获得参数个数
-
int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
-
-
获取参数元素数据:
- 步骤:
- 获取参数的元数据对象
- 根据参数的元数据对象获取参数的元数据
/** * Created by PengZhiLin on 2021/8/24 15:39 */ public class Test { public static void main(String[] args) throws Exception{ String sql = "select * from user where username = ? and password = ?"; // 需求: 获取该sql语句参数的个数 // 1.获得连接 Connection connection = C3P0Utils.getConnection(); // 2.预编译sql语句,得到预编译对象 PreparedStatement ps = connection.prepareStatement(sql); // 3.通过预编译对象获得参数元数据对象 ParameterMetaData pmd = ps.getParameterMetaData(); // 4.通过参数元数据对象获得参数的个数 System.out.println("参数个数:"+pmd.getParameterCount()); //System.out.println("参数类型:"+pmd.getParameterClassName(1));// 报异常 //System.out.println("参数类型:"+pmd.getParameterTypeName(1));// 报异常 //System.out.println("参数类型:"+pmd.getParameterType(1));// 报异常 // ... } }
- 步骤:
4.3 结果集元素数据
-
1.概述: 使用ResultSetMetaData类来表示结果集元数据
-
获取方式: ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来
-
作用:ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。
-
-
2.ResultSetMetaData相关的API
- getColumnCount(); 获取结果集中列项目的个数
- getColumnName(int column); 获得数据指定列的列名
- getColumnTypeName();获取指定列的SQL类型
- getColumnClassName();获取指定列SQL类型对应于Java的类型
- ...
-
3.使用步骤:
- 获得结果集的元数据对象
- 根据结果集的元数据对象获取结果集中的元素数据(结果集中列的个数,列的名称,列的类型...)
/** * Created by PengZhiLin on 2021/8/24 15:47 */ public class Test { public static void main(String[] args) throws Exception{ String sql = "select * from user"; // 需求: 获取该sql语句查询的结果集元数据(结果集列的个数,列的名称,列的类型,...) // 1.获得连接 Connection connection = C3P0Utils.getConnection(); // 2.预编译sql语句,得到预编译对象 PreparedStatement ps = connection.prepareStatement(sql); // 3.执行sql语句,得到结果集对象 ResultSet resultSet = ps.executeQuery(); // 4.通过结果集对象获得结果集元数据对象 ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // 5.通过结果集元数据对象获得结果集的元数据(结果集列的个数,列的名称,列的类型,...) int columnCount = resultSetMetaData.getColumnCount(); System.out.println("列的个数:"+columnCount); for (int i = 1; i <= columnCount; i++) { System.out.println("列的名称:"+resultSetMetaData.getColumnName(i)); System.out.println("列的sql类型:"+resultSetMetaData.getColumnTypeName(i)); System.out.println("列的java类型:"+resultSetMetaData.getColumnClassName(i)); } } }
4.4 自定义DBUtils增删改
1.需求
2.分析
- 创建MyQueryRunner类
- 定义一个DataSource成员变量
- 定义一个有参构造方法,空参构造方法
- 定义一个update(String sql,Object... args)完成增删改操作
3.实现
/**
* Created by PengZhiLin on 2021/8/24 16:19
*/
public class MyQueryRunner {
//- 定义一个DataSource成员变量
private DataSource dataSource;
//- 定义一个有参构造方法,空参构造方法
public MyQueryRunner() {
}
public MyQueryRunner(DataSource dataSource) {
this.dataSource = dataSource;
}
//- 定义一个update(String sql,Object... args)完成增删改操作
public int update(String sql,Object... args) throws SQLException {
// 1.获得连接
Connection connection = dataSource.getConnection();
// 2.预编译sql语句,得到预编译对象
PreparedStatement ps = connection.prepareStatement(sql);
// 3.设置sql语句参数
// 3.1 获得参数的元数据对象
ParameterMetaData pmd = ps.getParameterMetaData();
// 3.2 获得参数的个数
int parameterCount = pmd.getParameterCount();
// 3.3 设置参数
for (int i = 0; i < parameterCount; i++) {
ps.setObject(i+1,args[i]);
}
// 4.执行sql语句,处理结果
int rows = ps.executeUpdate();
// 5.释放资源
JDBCUtils.release(null,ps,connection);
// 6.返回结果
return rows;
}
}
总结
必须练习:
1.通过配置文件使用C3P0连接池---必须掌握
2.通过配置文件使用DRUID连接池---必须掌握
3.编写C3P0工具类
4.编写DRUID工具类
5.DBUtils的增删查改----必须\重点掌握--开发常用
- 能够理解连接池解决现状问题的原理
解决的问题:连接可以得到重复利用
原理:1. 程序一开始就创建一定数量的连接,放在一个容器(集合)中,这个容器称为连接池。
2. 使用的时候直接从连接池中取一个已经创建好的连接对象, 使用完成之后 归还到池子
3. 如果池子里面的连接使用完了, 还有程序需要使用连接, 先等待一段时间(eg: 3s), 如果在这段时间之内有连接归还, 就拿去使用; 如果还没有连接归还, 新创建一个, 但是新创建的这一个不会归还了(销毁)
- 能够使用C3P0连接池
1.导入jar包---c3p0\驱动包
2.创建连接池对象 ---->配置文件(配置文件一定要放在src目录下,并且名字必须为c3p0-config.xml)
3.根据连接池获得连接
4.创建预编译sql语句对象
5.设置参数
6.执行sql语句,处理结果
7.释放资源
- 能够使用DRUID连接池
0.导入jar包---druid\驱动包
1.创建Properties对象,加载配置文件中的数据---->配置文件(配置文件建议放在src目录下,名字无所谓)
2.创建连接池对象
3.根据连接池获得连接
4.创建预编译sql语句对象
5.设置参数
6.执行sql语句,处理结果
7.释放资源
- 能够编写C3P0连接池工具类
1.创建静态的连接池常量
2.提供一个用来获取连接池的静态方法
3.提供一个用来获取连接的静态方法
4.提供一个用来获取释放资源的静态方法
- 能够使用DBUtils完成CRUD
创建QueryRunner对象:public QueryRunner(DataSource datasource);
增删改: int update(String sql,Object... args);
查询: 返回值 query(String sql,ResultSetHandler<T> rsh,Object... args)
ResultSetHandler接口的实现类:
BeanHandler:适合查询结果是一条记录的,会把这条记录的数据封装到一个javaBean对象中
BeanListHandler:适合查询结果是多条记录的,会把每条记录的数据封装到一个javaBean对象中,然后把这些javaBean对象添加到List集合中
ColumnListHandler:适合查询结果是单列多行的,会把该列的所有数据存储到List集合中
ScalarHandler:适合查询结果是单个值的,会把这个值封装成一个对象
- 能够理解元数据
定义数据的数据
ParameterMetaData类:
概述:表示参数元数据对象,可以用来获取sql语句参数的元数据
获取参数元数据对象: 使用预编译sql语句对象调用方法获得
public ParameterMetaData getParameterMetaData ();
根据参数元数据对象获取参数的元数据:使用ParameterMetaData类的方法
- int getParameterCount(); 获得参数个数
ResultSetMetaData类:
概述:表示结果集的元数据对象,用来获取结果集的元数据
使用:
获取结果集元数据对象: 使用ResultSet结果集的方法
public ResultSetMetaData getMetaData();
根据结果集元数据对象获取结果集的元数据:使用ResultSetMetaData的方法
- getColumnCount(); 获取结果集中列项目的个数
- getColumnName(int column); 获得数据指定列的列名
- getColumnTypeName();获取指定列的SQL类型
- getColumnClassName();获取指定列SQL类型对应于Java的类型
- 能够自定义DBUtils
封装jdbc操作--见案例