JDBC第四篇「数据库连接池、DbUtils框架、分页」(修订版)
mhr18 2024-10-11 12:43 22 浏览 0 评论
1.数据库连接池
什么是数据库连接池
简单来说:数据库连接池就是提供连接的。。。
为什么我们要使用数据库连接池
- 数据库的连接的建立和关闭是非常消耗资源的
- 频繁地打开、关闭连接造成系统性能低下
编写连接池
- 编写连接池需实现java.sql.DataSource接口
- 创建批量的Connection用LinkedList保存【既然是个池,当然用集合保存、、LinkedList底层是链表,对增删性能较好】
- 实现getConnetion(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
- 调用Connection.close()方法,Connction返回给LinkedList
private static LinkedList<Connection> list = new LinkedList<>(); //获取连接只需要一次就够了,所以用static代码块 static { //读取文件配置 InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); try { properties.load(inputStream); String url = properties.getProperty("url"); String username = properties.getProperty("username"); String driver = properties.getProperty("driver"); String password = properties.getProperty("password"); //加载驱动 Class.forName(driver); //获取多个连接,保存在LinkedList集合中 for (int i = 0; i < 10; i++) { Connection connection = DriverManager.getConnection(url, username, password); list.add(connection); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } //重写Connection方法,用户获取连接应该从LinkedList中给他 @Override public Connection getConnection() throws SQLException { System.out.println(list.size()); System.out.println(list); //先判断LinkedList是否存在连接 return list.size() > 0 ? list.removeFirst() : null; }
我们已经完成前三步了,现在问题来了。我们调用Conncetion.close()方法,是把数据库的物理连接关掉,而不是返回给LinkedList的
解决思路:
- 写一个Connection子类,覆盖close()方法
- 写一个Connection包装类,增强close()方法
- 用动态代理,返回一个代理对象出去,拦截close()方法的调用,对close()增强
分析第一个思路:
- Connection是通过数据库驱动加载的,保存了数据的信息。写一个子类Connection,new出对象,子类的Connction无法直接继承父类的数据信息,也就是说子类的Connection是无法连接数据库的,更别谈覆盖close()方法了。
分析第二个思路:
- 写一个Connection包装类。
- 写一个类,实现与被增强对象的相同接口【Connection接口】
- 定义一个变量,指向被增强的对象
- 定义构造方法,接收被增强对象
- 覆盖想增强的方法
- 对于不想增强的方法,直接调用被增强对象的方法
- 这个思路本身是没什么毛病的,就是实现接口时,方法太多了!,所以我们也不使用此方法
分析第三个思路代码实现:
@Override public Connection getConnection() throws SQLException { if (list.size() > 0) { final Connection connection = list.removeFirst(); //看看池的大小 System.out.println(list.size()); //返回一个动态代理对象 return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //如果不是调用close方法,就按照正常的来调用 if (!method.getName().equals("close")) { method.invoke(connection, args); } else { //进到这里来,说明调用的是close方法 list.add(connection); //再看看池的大小 System.out.println(list.size()); } return null; } }); } return null; }
我们上面已经能够简单编写一个线程池了。下面我们来使用一下开源数据库连接池
DBCP
使用DBCP数据源的步骤:
- 导入两个jar包【Commons-dbcp.jar和Commons-pool.jar】
- 读取配置文件
- 获取BasicDataSourceFactory对象
- 创建DataSource对象
private static DataSource dataSource = null; static { try { //读取配置文件 InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); Properties properties = new Properties(); properties.load(inputStream); //获取工厂对象 BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory(); dataSource = basicDataSourceFactory.createDataSource(properties); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } //这里释放资源不是把数据库的物理连接释放了,是把连接归还给连接池【连接池的Connection内部自己做好了】 public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } rs = null; } if (st != null) { try { st.close(); } catch (Exception e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } }
C3P0
C3P0数据源的性能更胜一筹,并且它可以使用XML配置文件配置信息!
步骤:
- 导入开发包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
- 导入XML配置文件【可以在程序中自己一个一个配,C3P0的doc中的Configuration有XML文件的事例】
- new出ComboPooledDataSource对象
private static ComboPooledDataSource comboPooledDataSource = null; static { //如果我什么都不指定,就是使用XML默认的配置,这里我指定的是oracle的 comboPooledDataSource = new ComboPooledDataSource("oracle"); } public static Connection getConnection() throws SQLException { return comboPooledDataSource.getConnection(); }
Tomcat数据源
Tomcat服务器也给我们提供了连接池,内部其实就是DBCP
步骤:
- 在META-INF目录下配置context.xml文件【文件内容可以在tomcat默认页面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
- 导入Mysql或oracle开发包到tomcat的lib目录下
- 初始化JNDI->获取JNDI容器->检索以XXX为名字在JNDI容器存放的连接池
context.xml文件的配置:
<Context> <Resource name="jdbc/EmployeeDB" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/zhongfucheng" maxActive="8" maxIdle="4"/> </Context> try { //初始化JNDI容器 Context initCtx = new InitialContext(); //获取到JNDI容器 Context envCtx = (Context) initCtx.lookup("java:comp/env"); //扫描以jdbc/EmployeeDB名字绑定在JNDI容器下的连接池 DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB"); Connection conn = ds.getConnection(); System.out.println(conn); }
使用dbutils框架
dbutils它是对JDBC的简单封装,极大简化jdbc编码的工作量
DbUtils类
提供了关闭连接,装载JDBC驱动,回滚提交事务等方法的工具类【比较少使用,因为我们学了连接池,就应该使用连接池连接数据库】
QueryRunner类
该类简化了SQL查询,配合ResultSetHandler使用,可以完成大部分的数据库操作,重载了许多的查询,更新,批处理方法。大大减少了代码量
ResultSetHandler接口
该接口规范了对ResultSet的操作,要对结果集进行什么操作,传入ResultSetHandler接口的实现类即可。
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler 将ResultSet的一个列到一个对象中。
使用DbUtils框架对数据库的CRUD
/* * 使用DbUtils框架对数据库的CRUD * 批处理 * * */ public class Test { @org.junit.Test public void add() throws SQLException { //创建出QueryRunner对象 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "INSERT INTO student (id,name) VALUES(?,?)"; //我们发现query()方法有的需要传入Connection对象,有的不需要传入 //区别:你传入Connection对象是需要你来销毁该Connection,你不传入,由程序帮你把Connection放回到连接池中 queryRunner.update(sql, new Object[]{"100", "zhongfucheng"}); } @org.junit.Test public void query()throws SQLException { //创建出QueryRunner对象 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "SELECT * FROM student"; List list = (List) queryRunner.query(sql, new BeanListHandler(Student.class)); System.out.println(list.size()); } @org.junit.Test public void delete() throws SQLException { //创建出QueryRunner对象 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "DELETE FROM student WHERE id='100'"; queryRunner.update(sql); } @org.junit.Test public void update() throws SQLException { //创建出QueryRunner对象 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "UPDATE student SET name=? WHERE id=?"; queryRunner.update(sql, new Object[]{"zhongfuchengaaa", 1}); } @org.junit.Test public void batch() throws SQLException { //创建出QueryRunner对象 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "INSERT INTO student (name,id) VALUES(?,?)"; Object[][] objects = new Object[10][]; for (int i = 0; i < 10; i++) { objects[i] = new Object[]{"aaa", i + 300}; } queryRunner.batch(sql, objects); } }
分页
分页技术是非常常见的,在搜索引擎下搜索页面,不可能把全部数据都显示在一个页面里边。所以我们用到了分页技术。
Oracle实现分页
/* Oracle分页语法: @lineSize---每页显示数据行数 @currentPage----当前所在页 */ SELECT *FROM ( SELECT 列名,列名,ROWNUM rn FROM 表名 WHERE ROWNUM<=(currentPage*lineSize)) temp WHERE temp.rn>(currentPage-1)*lineSize;
Oracle分页原理简单解释:
/* Oracle分页: Oracle的分页依赖于ROWNUM这个伪列,ROWNUM主要作用就是产生行号。 分页原理: 1:子查询查出前n行数据,ROWNUM产生前N行的行号 2:使用子查询产生ROWNUM的行号,通过外部的筛选出想要的数据 例子: 我现在规定每页显示5行数据【lineSize=5】,我要查询第2页的数据【currentPage=2】 注:【对照着语法来看】 实现: 1:子查询查出前10条数据【ROWNUM<=10】 2:外部筛选出后面5条数据【ROWNUM>5】 3:这样我们就取到了后面5条的数据 */
Mysql实现分页
/* Mysql分页语法: @start---偏移量,不设置就是从0开始【也就是(currentPage-1)*lineSize】 @length---长度,取多少行数据 */ SELECT * FROM 表名 LIMIT [START], length; /* 例子: 我现在规定每页显示5行数据,我要查询第2页的数据 分析: 1:第2页的数据其实就是从第6条数据开始,取5条 实现: 1:start为5【偏移量从0开始】 2:length为5 */
总结:
- Mysql从(currentPage-1)*lineSize开始取数据,取lineSize条数据
- Oracle先获取currentPagelineSize条数据,从(currentPage-1)lineSize开始取数据
使用JDBC连接数据库实现分页
下面是常见的分页图片
配合图片,看下我们的需求是什么:
- 算出有多少页的数据,显示在页面上
- 根据页码,从数据库显示相对应的数据。
分析:
- 算出有多少页数据这是非常简单的【在数据库中查询有多少条记录,你每页显示多少条记录,就可以算出有多少页数据了】
- 使用Mysql或Oracle的分页语法即可
通过上面分析,我们会发现需要用到4个变量
- currentPage--当前页【由用户决定的】
- totalRecord--总数据数【查询表可知】
- lineSize--每页显示数据的数量【由我们开发人员决定】
- pageCount--页数【totalRecord和lineSize决定】
//每页显示3条数据 int lineSize = 3; //总记录数 int totalRecord = getTotalRecord(); //假设用户指定的是第2页 int currentPage = 2; //一共有多少页 int pageCount = getPageCount(totalRecord, lineSize); //使用什么数据库进行分页,记得要在JdbcUtils中改配置 List<Person> list = getPageData2(currentPage, lineSize); for (Person person : list) { System.out.println(person); } //使用JDBC连接Mysql数据库实现分页 public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException { //从哪个位置开始取数据 int start = (currentPage - 1) * lineSize; QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "SELECT name,address FROM person LIMIT ?,?"; List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize}); return persons; } //使用JDBC连接Oracle数据库实现分页 public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException { //从哪个位置开始取数据 int start = (currentPage - 1) * lineSize; //读取前N条数据 int end = currentPage * lineSize; QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "SELECT " + " name, " + " address " + "FROM ( " + " SELECT " + " name, " + " address , " + " ROWNUM rn " + " FROM person " + " WHERE ROWNUM <= ? " + ")temp WHERE temp.rn>?"; List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start}); return persons; } public static int getPageCount(int totalRecord, int lineSize) { //简单算法 //return (totalRecord - 1) / lineSize + 1; //此算法比较好理解,把数据代代进去就知道了。 return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1; } public static int getTotalRecord() throws SQLException { //使用DbUtils框架查询数据库表中有多少条数据 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "SELECT COUNT(*) FROM person"; Object o = queryRunner.query(sql, new ScalarHandler()); String ss = o.toString(); int s = Integer.parseInt(ss); return s; }
原文地址:https://dwz.cn/aldrP0zw
作者:Java3y
相关推荐
- 订单超时自动取消业务的 N 种实现方案,从原理到落地全解析
-
在分布式系统架构中,订单超时自动取消机制是保障业务一致性的关键组件。某电商平台曾因超时处理机制缺陷导致日均3000+订单库存锁定异常,直接损失超50万元/天。本文将从技术原理、实现细节、...
- 使用Spring Boot 3开发时,如何选择合适的分布式技术?
-
作为互联网大厂的后端开发人员,当你满怀期待地用上SpringBoot3,准备在项目中大显身手时,却发现一个棘手的问题摆在面前:面对众多分布式技术,究竟该如何选择,才能让SpringBoot...
- 数据库内存爆满怎么办?99%的程序员都踩过这个坑!
-
你的数据库是不是又双叒叕内存爆满了?!服务器监控一片红色警告,老板在群里@所有人,运维同事的电话打爆了手机...这种场景是不是特别熟悉?别慌!作为一个在数据库优化这条路上摸爬滚打了10年的老司机,今天...
- springboot利用Redisson 实现缓存与数据库双写不一致问题
-
使用了Redisson来操作Redis分布式锁,主要功能是从缓存和数据库中获取商品信息,以下是针对并发时更新缓存和数据库带来不一致问题的解决方案1.基于读写锁和删除缓存策略在并发更新场景下,...
- 外贸独立站数据库炸了?对象缓存让你起死回生
-
上周黑五,一个客户眼睁睁看着服务器CPU飙到100%——每次页面加载要查87次数据库。这让我想起2024年Pantheon的测试:Redis缓存能把WooCommerce查询速度提升20倍。跨境电商最...
- 手把手教你在 Spring Boot3 里纯编码实现自定义分布式锁
-
为什么要自己实现分布式锁?你是不是早就受够了引入各种第三方依赖时的繁琐?尤其是分布式锁这块,每次集成Redisson或者Zookeeper,都得额外维护一堆配置,有时候还会因为版本兼容问题头疼半...
- 如何设计一个支持百万级实时数据推送的WebSocket集群架构?
-
面试解答:要设计一个支持百万级实时数据推送的WebSocket集群架构,需从**连接管理、负载均衡、水平扩展、容灾恢复**四个维度切入:连接层设计-**长连接优化**:采用Netty或Und...
- Redis数据结构总结——面试最常问到的知识点
-
Redis作为主流的nosql存储,面试时经常会问到。其主要场景是用作缓存,分布式锁,分布式session,消息队列,发布订阅等等。其存储结构主要有String,List,Set,Hash,Sort...
- skynet服务的缺陷 lua死循环
-
服务端高级架构—云风的skynet这边有一个关于云风skynet的视频推荐给大家观看点击就可以观看了!skynet是一套多人在线游戏的轻量级服务端框架,使用C+Lua开发。skynet的显著优点是,...
- 七年Java开发的一路辛酸史:分享面试京东、阿里、美团后的心得
-
前言我觉得有一个能够找一份大厂的offer的想法,这是很正常的,这并不是我们的饭后谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司...
- mysql mogodb es redis数据库之间的区别
-
1.MySQL应用场景概念:关系型数据库,基于关系模型,使用表和行存储数据。优点:支持ACID事务,数据具有很高的一致性和完整性。缺点:垂直扩展能力有限,需要分库分表等方式扩展。对于复杂的查询和大量的...
- redis,memcached,nginx网络组件
-
1.理解阻塞io,非阻塞io,同步io,异步io的区别2.理解BIO和AIO的区别io多路复用只负责io检测,不负责io操作阻塞io中的write,能写多少是多少,只要写成功就返回,譬如准备写500字...
- SpringBoot+Vue+Redis实现验证码功能
-
一个小时只允许发三次验证码。一次验证码有效期二分钟。SpringBoot整合Redis...
- AWS MemoryDB 可观测最佳实践
-
AWSMemoryDB介绍AmazonMemoryDB是一种完全托管的、内存中数据存储服务,专为需要极低延迟和高吞吐量的应用程序而设计。它与Redis和Memcached相似,但具有更...
- 从0构建大型AI推荐系统:实时化引擎从工具到生态的演进
-
在AI浪潮席卷各行各业的今天,推荐系统正从幕后走向前台,成为用户体验的核心驱动力。本文将带你深入探索一个大型AI推荐系统从零起步的全过程,揭示实时化引擎如何从单一工具演进为复杂生态的关键路径。无论你是...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
Java SE Development Kit 8u441下载地址【windows版本】
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
Oracle如何创建用户,表空间(oracle19c创建表空间用户)
-
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (83)
- php redis (97)
- redis 存储 (67)
- redis 锁 (74)
- 启动 redis (73)
- redis 时间 (60)
- redis 删除 (69)
- redis内存 (64)
- redis并发 (53)
- redis 主从 (71)
- redis同步 (53)
- redis结构 (53)
- redis 订阅 (54)
- redis 登录 (62)
- redis 面试 (58)
- redis问题 (54)
- 阿里 redis (67)
- redis的缓存 (57)
- lua redis (59)
- redis 连接池 (64)