1.数据库连接池

什么是数据库连接池

简单来说:数据库连接池就是提供连接的。

为什么我们要使用数据库连接池

  • 数据库的连接的建立和关闭是非常消耗资源的
  • 频繁地打开、关闭连接造成系统性能低下

编写连接池

  1. 编写连接池需实现java.sql.DataSource接口
  2. 创建批量的Connection用LinkedList保存【既然是个池,当然用集合保存、、LinkedList底层是链表,对增删性能较好】
  3. 实现getConnetion(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
  4. 调用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的

解决思路:

  1. 写一个Connection子类,覆盖close()方法
  2. 写一个Connection包装类,增强close()方法
  3. 用动态代理,返回一个代理对象出去,拦截close()方法的调用,对close()增强

分析第一个思路:

  • Connection是通过数据库驱动加载的,保存了数据的信息。写一个子类Connection,new出对象,子类的Connction无法直接继承父类的数据信息,也就是说子类的Connection是无法连接数据库的,更别谈覆盖close()方法了。

分析第二个思路:

  • 写一个Connection包装类。

    1. 写一个类,实现与被增强对象的相同接口【Connection接口】
    2. 定义一个变量,指向被增强的对象
    3. 定义构造方法,接收被增强对象
    4. 覆盖想增强的方法
    5. 对于不想增强的方法,直接调用被增强对象的方法
  • 这个思路本身是没什么毛病的,就是实现接口时,方法太多了!,所以我们也不使用此方法

分析第三个思路代码实现:


@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数据源的步骤:

  1. 导入两个jar包【Commons-dbcp.jar和Commons-pool.jar】
  2. 读取配置文件
  3. 获取BasicDataSourceFactory对象
  4. 创建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配置文件配置信息!

步骤:

  1. 导入开发包【c3p0-0.9.2-pre1.jar】
  2. 【mchange-commons-0.2.jar】开发包
  3. 导入XML配置文件【可以在程序中自己一个一个配,C3P0的doc中的Configuration有XML文件的事例】
  4. 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

步骤:

  1. 在META-INF目录下配置context.xml文件【文件内容可以在tomcat默认页面的 JNDI Resources下Configure Tomcat’s Resource Factory找到】
  2. 导入Mysql或oracle开发包到tomcat的lib目录下
  3. 初始化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先获取currentPage*lineSize条数据,从(currentPage-1)*lineSize开始取数据

使用JDBC连接数据库实现分页

下面是常见的分页图片


配合图片,看下我们的需求是什么:

  1. 算出有多少页的数据,显示在页面上
  2. 根据页码,从数据库显示相对应的数据

分析:

  1. 算出有多少页数据这是非常简单的【在数据库中查询有多少条记录,你每页显示多少条记录,就可以算出有多少页数据了】
  2. 使用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;
}

JDBC第四篇--【数据库连接池、DbUtils框架、分页】的更多相关文章

  1. JDBC(四)----数据库连接池

    ##  数据库连接池 *  概念:其实就是一个容器(集合) *  当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后会将连接对象归还给容 ...

  2. 初识数据库连接池开源框架Druid

    Druid是阿里巴巴的一个数据库连接池开源框架,准确来说它不仅仅包括数据库连接池这么简单,它还提供强大的监控和扩展功能.本文仅仅是在不采用Spring框架对Druid的窥探,采用目前最新版本druid ...

  3. Python数据库连接池---DBUtils

    Python数据库连接池DBUtils   DBUtils是Python的一个用于实现数据库连接池的模块. 此连接池有两种连接模式: 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不 ...

  4. Python数据库连接池DBUtils

    Python数据库连接池DBUtils   DBUtils是Python的一个用于实现数据库连接池的模块. 此连接池有两种连接模式: 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不 ...

  5. JDBC(11)—数据库连接池

    在实际开发过程中,特别是在web应用系统中,如果程序直接访问数据库中的数据,每一次数据访问请求丢必须经历建立数据库连接.打开数据库.存取数据和关闭数据库连接.而连接并打开数据库是一件既消费资源又费时的 ...

  6. 十四 数据库连接池&DBUtils

    关于数据库连接池: 1 数据库的连接对象创建工作,比较消耗性能. 2 一开始在内存中开辟一块空间,往池子里放置多个连接对象,需要连接的时候从连接池里面调用, 使用完毕归还连接,确保连接对象能够循环利用 ...

  7. javaweb学习总结(四十一)——Apache的DBUtils框架学习

    一.commons-dbutils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化 ...

  8. 事务&数据库连接池&DBUtils

    事务的特性 原子性 指的是 事务中包含的逻辑,不可分割. 一致性 指的是 事务执行前后.数据完整性 隔离性 指的是 事务在执行期间不应该受到其他事务的影响 持久性 指的是 事务执行成功,那么数据应该持 ...

  9. JDBC(三)数据库连接池(DBCP、C3P0)

    前言 这段时间状态有一点浮躁,希望自己静下心来.还有特别多的东西还没有学懂.需要学习的东西非常的多,加油! 一.JDBC复习 Java Data Base Connectivity,java数据库连接 ...

随机推荐

  1. 一个基于ES5的vue小demo

    由于现在很多vue项目都是基于ES6开发的,而我学vue的时候大多是看vue官网的API,是基于ES5的,所以对于刚接触项目的我来说要转变为项目的模块化写法确实有些挑战.因此,我打算先做一个基于ES5 ...

  2. Charles使用(一)

    Charles使用(一)   1.什么是Charles Charles是在MAC下常用的网络封包截取工具,在做移动开发时,是为了调试与服务端的网络通信协议,常常需要截取网络包来分析.Charles是通 ...

  3. 解决WebUploader在谷歌浏览器中反应缓慢迟钝

    修改  初始化webuploader的 js accept: null/*{ title: 'Images', extensions: 'gif,jpg,jpeg,bmp,png', mimeType ...

  4. Zepto源码分析(一)核心代码分析

    本文只分析核心的部分代码,并且在这部分代码有删减,但是不影响代码的正常运行. 目录 * 用闭包封装Zepto * 开始处理细节 * 正式处理数据(获取选择器选择的DOM) * 正式处理数据(添加DOM ...

  5. 使用XMLHttpRequest异步通信

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 用notepad如何在每行结尾处添加特殊字符

    在处理关键词的时候,有时候需要将每一行的末尾添加某个特殊字符,较常用的一种方法就是用excel拼接起来.今天要分享是一种简单的方法,用notepad就可以很容易实现,主要用到notepad中的扩展匹配 ...

  7. js小知识-数组去重

    查看zepto源码时,看到它的数组去重写法非常简单.下面是代码 var uq = function(array){ return [].filter.call(array,function(item, ...

  8. Mysql数据库索引

    今天,我们来讲讲Mysql数据库的索引的一些东西,想必大家都知道索引能干吗?必然是查找数据表的时候,查找的速度快啊,尤其是那些几百万行的数据库,不建立索引,你是想考验用户的耐心吗?Mysql有多种存储 ...

  9. switch case异常处理机制

    public class T3{ public static void main(String[] args) { try{ String kc=""; System.out.pr ...

  10. CSS基础用法

    [CSS常用选择器] 标签选择器 写法: HTML标签名{}作用: 可以选中页面中,所有与选择器同名的HTML标签. 类选择器(class选择器)写法: .class名{}调用: 在需要调用选择器样式 ...