JDBC笔记
批处理
基本介绍
1.当需要成批插入或者更新记录的时候,可以使用Java批处理,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交更有效率
2.JDBC的批量处理语句包括了以下的方法
方法名称 |
说明 |
addBatch |
添加需要批量处理的SQL语句或者参数 |
executeBatch |
执行批量处理语句 |
clearBatch |
清空批处理包的语句 |
3.JDBC连接MySQL时,如果要使用批处理,需要在url中添加参数
1
| ?rewriteBatchedStatements=true
|
4.批处理往往与PreparedStatement一起搭配使用,可以及减少编译次数又减少运行次数,效率大大提升
传统的添加方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test { @SuppressWarnings ({"all"}) public static void main(String[] args) throws Exception{ Connection connection = JDBCUtils.getConnection(); String sql = "insert into balance values (?,?,?)"; PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 1; i < 1000; i++) { statement.setInt(1, i); statement.setString(2, "TomCat"); statement.setDouble(3, i * 2); statement.executeUpdate(); } JDBCUtils.close(null, statement, connection); } }
|
耗时还是挺久的大概6、7秒钟完成,感兴趣可以输出运行时间测试一下
批处理运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Test { @SuppressWarnings ({"all"}) public static void main(String[] args) throws Exception{ Connection connection = JDBCUtils.getConnection(); String sql = "insert into balance values (?,?,?)"; PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 1; i < 1000; i++) { statement.setInt(1, i); statement.setString(2, "TomCat"); statement.setDouble(3, i * 2); statement.addBatch(); if((i + 1) % 1000 == 0) { statement.executeBatch(); statement.clearBatch(); } } JDBCUtils.close(null, statement, connection); } }
|
注意在JDBCUtils工具类中的url添加相关参数
1
| String url = agreement + "//" + address + ":" + port + "/" + dataBase + "?rewriteBatchedStatements=true";
|
运用批处理在处理大量的SQL语句的时候,速度相较于单条执行要快很多,大概的用时只需要1、2秒钟
连接池
连接池的引入
在传统的连接方式中往往程序和数据库进行网络的连接是多次的,这张方式的连接数上限往往是有限制的,在太多的连接的时候和数据库连接取得Connection的时候会抛出Too many connection的异常,就算及时关闭连接,连接的时长也会很长
问题分析
1.传统的JDBC数据库连接使用DriverManager来获取,每一次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名,密码(0.05-1s)。需要数据库链接的时候,就会向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃
2.每一次数据库连接,使用完后都得断开,如果程序出现异常未能关闭,将导致数据库内存泄漏,最终导致重启数据库
3.传统获取连接的方式,不能控制创建的连接数量,如果连接过多,也可能导致内存泄漏,数据库崩溃
4.解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)
基本介绍
1.预先在缓冲池中放入一定数量的连接,当需要建立数据据库连接的时候,只需要从缓冲池中取出连接,使用完毕的时候再将连接放回缓冲池中即可
2.数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立一个
3.当应用程序向连接池请求的连接数超过最大连接数的时候,这些请求将被添加入等待队列
数据库连接池种类
1.JDBC的数据库连接池使用javax.sql.DataSource表示,DataSource是一个接口,通常由第三方来实现
2.C3P0数据库连接池,速度相对较慢,但是稳定性不错(hibernate,spring)
3.DBCP数据库连接池,速度相比于C3P0较快,但是不稳定
4.Proxool数据库连接处,有监控连接池状态的功能,稳定性较C3P0差一点
5.BoneCP数据库连接池,速度快
6.Druid(德鲁伊)是由阿里提供的数据库连接池,集以上(C3P0、DBCP、Proxool)数据库优点于一体的数据库连接池
C3P0和德鲁伊连接池是目前经常会使用的连接池,我们接下来使用的也是这两个
C3P0连接池
注意在连接池中的close不是指的是将连接断开,而是将连接放回到连接池中,供下一个getConnection使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void test01() throws Exception{ ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); String agreement = "jdbc:mysql:"; String address = "localhost"; String port = "3306"; String dataBase = "jdbc"; String url = agreement + "//" + address + ":" + port + "/" + dataBase; String user = "root"; String password = "654321"; String driver = "com.mysql.cj.jdbc.Driver";
comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(user); comboPooledDataSource.setPassword(password);
comboPooledDataSource.setInitialPoolSize(10); comboPooledDataSource.setMaxPoolSize(50);
Connection connection = comboPooledDataSource.getConnection(); connection.close(); }
|
如果通过这种方式创建连接相比于JDBC的从DriverManager获取连接要快很多
除此之外你还可以使用配置文件设置连接池的基础信息(在创建连接池对象的时候指定配置文件参数),感兴趣的可以下去了解
Druid连接池
连接池基本配置
我们在使用Druid连接池的时候需要使用到配置文件
1 2 3 4 5 6 7 8 9 10 11 12
| driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/tableselect?useSSL=false&serverTimezone=UTC&characterEncoding=utf8 username=root password=654321
initialSize=10 maxActive=50 minIdle=5 maxWait=60000
|
连接池使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Druid_ { public static void main(String[] args) throws Exception { testDruid(); }
public static void testDruid() throws Exception { Properties properties = new Properties(); properties.load(new FileInputStream("src\\druid.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); connection.close(); } }
|
Druid速度非常快,目前而言能用Druid尽量别用C3P0,除非要进行兼容
工具类封装
便于使用者使用,我们将德鲁伊连接池做成一个工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;
public class JDBCUtilsDruid { private static DataSource ds;
static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { throw new RuntimeException(e); } }
public static Connection getConnection() { try { return ds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } }
public static void close(ResultSet result, Statement statement, Connection connection) { try { if (result != null) { result.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }
|
使用测试方法调用,这样做会方便很多
1 2 3 4 5
| public static void testUtils() { Connection connection = JDBCUtilsDruid.getConnection(); JDBCUtilsDruid.close(null, null, connection); }
|
再多说一嘴,其实这种情况下使用connection.close()方法关闭连接也没有问题,因为通过动态绑定机制,connection实际上调用的是德鲁伊的close方法。这个方法和通过DriverManager创建的连接调用的close方法有本质的区别,不会将链接关闭,而是将连接放回连接池中
DBUtils
问题引出
当我们关闭connection后,resultSet结果集无法使用(二者相关联),而有的时候我们需要复用resultSet中的数据,关闭connection后任需要结果集
resultSet不利于数据的管理,使用起来也不太方便,这个时候我们需要再Java程序中创建一个类(JavaBean)与数据库的记录做一个映射,再创建一个结果集记录封装在ArrayList下,每一个集合元素对应一条记录
这样的话我们使用类来获取数据就会方便一些,而且生命期更加持久,可以在不用的时候再将ArrayList集合对象释放(以上的思路可以去自己实现,以下是一个Apache提供的一个工具类,相比于自己写的更加完善方便)
commons-dbutils
基本介绍
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils可以极大地简化JDBC编码的工作量
DBUtils类
1.QueryRunner类:该类封装了SQL的执行,是线程安全的,可以实现增删改查,批处理
2.ResultSetHandler接口:该接口用于处理ResultSet,将数据按要求转化为另一种形式

构造返回记录类型
要使这个自动映射正常工作,Balance
类需要:
- 是一个标准的JavaBean(有无参构造函数)
- 有与数据库列名对应的属性
- 有这些属性的setter方法
构造一个Balance接收返回结果的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Balance { private int id; private String name; private String money; public Balance(){}
@Override public String toString() { return id + " " + name + " " + money; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setMoney(String money) { this.money = money; } }
|
查询结果
利用QueryRunner对象查询数据库返回结果
返回单条单列的记录(返回一个Object对象)
1 2 3 4 5 6 7 8 9 10 11
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "select `name` from balance where id = ?;"; String name = (String) queryRunner.query(connection, sql, new ScalarHandler(), 100); System.out.println(name); connection.close(); } }
|
返回单条记录(返回一个对应的Balance对象)
1 2 3 4 5 6 7 8 9 10 11
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "select * from balance where id = ?;"; Balance balance = queryRunner.query(connection, sql, new BeanHandler<>(Balance.class), 100); System.out.println(balance); connection.close(); } }
|
返回多条结果(返回一个存放了Balance对象的ArrayList)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "select * from balance where id = ? or id = ?;"; List<Balance> list = queryRunner.query(connection, sql, new BeanListHandler<>(Balance.class), 100, 200); Iterator<Balance> iterator = list.iterator(); while(iterator.hasNext()) { Balance balance = iterator.next(); System.out.println(balance); } connection.close(); } }
|
如果步入源码中我们可以发现在query方法执行获取ArrayList结果集的过程中用到的Statement和ResultSet就已经被关闭
而返回的结果类型取决于第三个参数的类型,其中result返回结果底层运用到了泛型来确定result的返回类型(更具不同的参数类型有不同的处理逻辑)
增删改
1 2 3 4 5 6 7 8 9 10 11
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "insert into balance values (300, 'Tom', 4000), (400, 'King', 5000);"; int update = queryRunner.update(connection, sql); System.out.println(update); connection.close(); } }
|
1 2 3 4 5 6 7 8 9 10 11
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "delete from balance;"; int update = queryRunner.update(connection, sql); System.out.println(update); connection.close(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class Druid_ { public static void main(String[] args) throws Exception { Connection connection = JDBCUtilsDruid.getConnection(); QueryRunner queryRunner = new QueryRunner(); String sql = "update balance set name = 'Cat'"; int update = queryRunner.update(connection, sql); System.out.println(update); connection.close(); } }
|
BasicDao
问题引出
1.SQL语句是固定的,不可以通过参数传入,使用起来不灵活
2.对于select操作,返回类型不确定,需要使用到泛型
3.对于很多表而言,业务负责不可能单靠一个Java类完成
Dao
data access object 数据访问对象
我们对于每一个表都会有相应的操作,我们可以将这一些操作的共有部分提到BasicDao中,这样做可以简化代码,维护可读性
1.这样的通用类我们称为BasicDao,是专门和数据库交互的,即完成数据库的增删改查操作
2.再BasicDao的基础上,实现一张表对应一个Dao,可以更好的完成功能
接下来我们就通过代码实际实现Dao
Dao实现
设计com.dao
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class JDBCUtilsDruid { private static DataSource ds;
static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { throw new RuntimeException(e); } }
public static Connection getConnection() { try { return ds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } }
public static void close(ResultSet result, Statement statement, Connection connection) { try { if (result != null) { result.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }
|
com.dao.domain
javabean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Balance { private int id; private String name; private String money; public Balance(){}
@Override public String toString() { return id + " " + name + " " + money; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setMoney(String money) { this.money = money; } }
|
存放BasicDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class BasicDao<T> { private QueryRunner qr = new QueryRunner();
public int update(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsDruid.getConnection(); return qr.update(connection, sql, parameters); } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtilsDruid.close(null, null, connection); } }
public List<T> queryMulti(String sql, Class<T> cls, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsDruid.getConnection(); return qr.query(connection, sql, new BeanListHandler<T>(cls), parameters); } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtilsDruid.close(null, null, connection); } }
public T querySingle(String sql, Class<T> cls, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsDruid.getConnection(); return qr.query(connection, sql, new BeanHandler<T>(cls), parameters); } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtilsDruid.close(null, null, connection); } }
public Object queryScalar(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsDruid.getConnection(); return qr.query(connection, sql, new ScalarHandler(), parameters); } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtilsDruid.close(null, null, connection); } } }
|
接下来我们根据BasicDao来开发针对于balance表的Dao,制定了前面我们设计的BasicDao的泛型T为Balance类
1 2 3 4
| public class BalanceDao extends BasicDao<Balance>{ }
|
可以看出在没有特殊要求的情况下,我们只需要在继承的时候指定泛型类型即可创建出新的对应表Dao,非常的方便
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TestDao { public static void main(String[] args) { BalanceDao balanceDao = new BalanceDao();
String sql = "select * from balance"; List<Balance> balances = balanceDao.queryMulti(sql, Balance.class); for(Balance balance : balances) { System.out.println(balance); }
sql = "select * from balance where id = ?"; Balance balance = balanceDao.querySingle(sql, Balance.class, 100); System.out.println(balance);
sql = "select name from balance where id = 100"; String name =(String) balanceDao.queryScalar(sql); System.out.println(name);
sql = "insert into balance values (300, 'Tom', 4000), (400, 'King', 5000);"; int update = balanceDao.update(sql); System.out.println(update); } }
|
看的出来还是非常方便的啊
补充
其实你要在方便一点(不想要传入Balance.class)的话还可以在BasicDao中用反射获取T类对象,将类对象传入
JDBC结语
两天的时间,学的还是挺快的,过了一遍代码,学会了使用方式,对于JDBC,连接池,DB工具,自建BasicDao有了初步的认识,对于底层的代码看了一眼,不算完全掌握
反思了以下自己泛型的部分掌握还不足,决定回宿舍复习一下泛型相关的内容
明天打算开始Java8特性的学习,计划三天完成,估计我的有一个面试在三天之后,面试结束再决定Java8后学哪一个开发框架,在此之前先准备下面试,背下八股之类的