JDBC 概述
数据的持久化
- 把数据存储在可掉电设备中以供以后使用
- 主要是存储在关系型数据库中
Java中的数据存储技术
- JDBC
- JDO
- 第三方O/R工具,例如Hibernate,Mybatis等
- JDBC是Java访问数据库的基石,其他方式只是更好的封装了JDBC
JDBC技术
- JDBC主要提供了一组用于访问数据库的通用接口(规范)
- JDBC会调用相应数据库的驱动来完成对数据库的操作
- JDBC包含了面向应用的API和面向数据库的API
- 面向接口的编程思想
- 具体过程

- Statement完成具体的操作
- Driver(驱动)接口提供了取得链接的方法
实现
实现方式1:
@Test
public void testConnection1() throws SQLException {
Driver driver = new com.mysql.cj.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password","123456Asdfgh");
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
注:未加?serverTimezone=UTC时可能会出现报错
实现方式2:
//使用反射获得实现类对象,增加可修改性
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password","123456Asdfgh");
Connection conn = driver.connect(url, info);
System.out.println(conn);
实现方式3:
//使用反射获得实现类对象
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/JDBCLearning";
String user = "root";
String password = "123456Asdfgh";
//注册驱动
DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
实现方式4::boom:
String url = "jdbc:mysql://localhost:3306/JDBCLearning";
String user = "root";
String password = "123456Asdfgh";
//加载驱动,对应的静态代码块会被执行
Class.forName("com.mysql.cj.jdbc.Driver");
// Driver driver = (Driver)clazz.newInstance();
// //注册驱动
// DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
通过查看com.mysql.cj.jdbc.Driver的源代码
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
不难发现,在此段代码中的静态代码块会在Driver对象被创建时执行,也即会自行创建一个驱动。
其实这里的Class.forName(“com.mysql.cj.jdbc.Driver”)甚至也可以省略,由于,MySQL的驱动META-INF/service/java.sql.Driver中已经记录了MySQL的驱动名称,但是,极其不推荐这样做,因为这样的做法不一定对所有的数据库(如Oracle)均有效。
实现方式5:
//读取配置文件的信息
//用户自定义类获取的是系统类加载器
InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("src/jdbc.properties");
Properties properties = new Properties();
properties.load(inputStream);
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String url=properties.getProperty("url");
String driverClass=properties.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
通过将参数单独配置在配置文件中,进而可以进一步提高代码的可重用性,实现了数据与代码的分离,降低了代码耦合性。
同时,这种方式也避免了修改文件时重新打包造成的时间浪费。
使用PreparedStatement实现CRUD操作
三种调用方式的区别:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PreparedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
Statement的弊端:
SQL注入问题:SELECT user,password FROM user_table WHERE user = ‘“ + user + “‘ AND PASSWORD = ‘“ + “password”‘
SELECT user,password FROM user_table WHERE user = ‘1’ or ‘ AND PASSWORD = ‘=1 or ‘1’ = ‘1’
注意这里的and逻辑被改为了or逻辑,导致SQL注入问题
字符串拼接问题
具体实现代码
insert:
public void testInsert() throws IOException, ClassNotFoundException, SQLException, ParseException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//读取配置文件的信息
InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(inputStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
//预编译sql语句,返回PreparedStatement实例
String sql = "insert into customers(name,email,birth) values (?,?,?)";
preparedStatement = connection.prepareStatement(sql);
//填充占位符,注意索引是从1开始
preparedStatement.setString(1, "ysz001");
preparedStatement.setString(2, "ysz001@ysz.com");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = simpleDateFormat.parse("2000-01-01");
preparedStatement.setDate(3, new Date(date.getTime()));
//执行SQL
preparedStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
try {
if (preparedStatement != null) {
preparedStatement.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
这里请注意数据库的下标是从1开始的,而数组的下标仍然是从0开始的。
具体在书写时可以先将Exceptions全部抛出,由于是涉及到资源关闭的问题,所以需要在完成主体代码后将throws改为try-catch-finally结构。
注意到这里的建立连接与关闭连接和statement均为可复用结构,故将这两个部分单独抽取出来。
JDBCUtil:
//获取数据库的连接
public static Connection getConnection() throws ClassNotFoundException, SQLException, IOException {
//读取配置文件的信息
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(inputStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
//关闭资源
public static void closeResource(Connection connection, Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
注:这里将ConnectionTest.class.getClassLoader().getResourceAsStream(“jdbc.properties”)改为了ClassLoader.getSystemClassLoader().getResourceAsStream(“jdbc.properties”),是因为对于用户自定义类,ConnectionTest.class.getClassLoader()的结果就是ClassLoader.getSystemClassLoader()
update:
public void testUpdate() {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//获取数据库连接
connection = JDBCUtils.getConnection();
//预编译SQL,返回PreparedStatement实例
String sql = "update customers set name=? where id=?";
preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setObject(1, "ysz002");
preparedStatement.setObject(2, "19");
//执行
preparedStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//资源的关闭
JDBCUtils.closeResource(connection, preparedStatement);
}
}
update,最后这里给出一个通用的增删改操作函数:
//通用增删改操作
public void update(String sql, Object... args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//获取连接
connection = JDBCUtils.getConnection();
//预编译SQL语句
preparedStatement = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);//数据的下标是从1开始的哦
}
//执行
preparedStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//资源的关闭
JDBCUtils.closeResource(connection, preparedStatement);
}
}
ORM的编程思想
一个数据表对应一个Java类
表中一条记录对应Java类的一个对象
表中一个字段对应Java类的一个属性
| Java类型 | SQL类型(MySQL) |
|---|---|
| boolean | BIT |
| byte | TINYINT |
| short | SMALLINT |
| int | INTEGER |
| long | BIGINT |
| String | CHAR,VARCHAR,LONGVARCHAR |
| byte array | BINARY,VAR BINARY |
| java.sql.Date | DATE |
| java.sql.Time | TIME |
| java.sql.Timestamp | TIMESTAMP |
在如下位置构建bean对象,并生成其有参无参构造器以及getter,setter方法(代码就不贴了,alt+insert就出来了)

这里先给出一个基本的代码示例
public void testQuery1() throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id =?";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
//判断结果集中是否还有数据
if (resultSet.next()) {
//获取结果集中的各个字段
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
////使用数组封装数据
//Object[] data = {id, name, email, birth};
//将数据封装为一个对象
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
}
观察后我们可以发现上面的代码具有较强的耦合性,为了降低耦合性,我们可以联想到学习数据库时提到的**元数据**的概念,根据元数据中记录的属性值,我们可以获得结果的列数以及特定列的列名。
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//获取结果的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//可以通过元数据获取结果的列数
int columnCount = metaData.getColumnCount();
同时,对于结果集中的每一行记录,我们可以尝试使用**反射**的机制来为其对应属性赋值
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer的某一属性赋值为value,**反射**
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(customer, value);
总的代码如下所示
public Customer queryForCustomers(String sql,Object... args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//获取结果的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//可以通过元数据获取结果的列数
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
Customer customer = new Customer();
//处理某一行数据
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer的某一属性赋值为value,**反射**
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(customer, value);
}
return customer;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
return null;
}
数据库字段名称与Java程序属性名不一致时
声明SQL时,要使用类的属性名来命名字段的别名
使用ResultSetMetaData时,要使用getColumnLable()来取代getColumnName()
若没有别名时,getColumnLable()返回列名
也即,对于SQL “select Name name” ,该语句中Name为数据库中的字段名称,而name为Java程序中的属性名。使用getColumnName()返回Name,而使用getColumnLable()时返回name。
//获取每个列的列名 //getColumnName():获取列的列名 //getColumnLabel():获取列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //通过反射将指定名的属性赋值为指定值 Field declaredField = Order.class.getDeclaredField(columnLabel); declaredField.setAccessible(true); declaredField.set(order, value);
通用单个对象get
通过进一步观察我们可以发现,在Customer customer = new Customer()时的代码耦合性依然较强,这里我们可以采用反射+范型的方法来进一步改进代码。
public <T> T getInstance(Class<T> clazz, String sql, Object... args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//获取结果的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//可以通过元数据获取结果的列数
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
T t = clazz.newInstance();
//处理某一行数据
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer的某一属性赋值为value,**反射**
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, value);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
return null;
}
通用的查询操作,返回一个List集合
//通用的查询操作
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//获取结果的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//可以通过元数据获取结果的列数
int columnCount = metaData.getColumnCount();
//用于保存结果集合
ArrayList<T> list = new ArrayList<>();
while (resultSet.next()) {
T t = clazz.newInstance();
//处理某一行数据
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer的某一属性赋值为value,**反射**
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, value);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
return null;
}
解决SQL注入问题
由于PreparedStatement采用预编译的方式,所以原有SQL逻辑并不会发生改变,所以也能很好的解决SQL注入问题。
同时PreparedStatement还可以解决拼串问题。
此外,PreparedStatement还具有一些其他方面的优势。
存储Blob数据类型
Blob类型
最大支持4G的单个文件
类型 大小max TinyBlob 255B Blob 65KB MediumBlob 16MB LongBlob 4GB 只能使用PreparedStatement操作Blob类型
若出现文件过大报错(PacketTooBigException),则应找到目录的安装路径下对应的./MySQL Serverx.x/my.ini文件,在其中加入max_allowed_packet=16M(大小按照自己需求),并重启服务器即可。
插入Blob
//向数据表customers中插入Blob类型的字段
@Test
public void testInsert() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1, "ysz003");
preparedStatement.setObject(2, "ysz003@ysz.com");
preparedStatement.setObject(3, "2000-01-01");
FileInputStream fileInputStream = new FileInputStream(new File("src/main/resources/testPhoto.jpg"));
preparedStatement.setObject(4, fileInputStream);
preparedStatement.execute();
JDBCUtils.closeResource(connection, preparedStatement);
}
查询并查看Blob内容
注意getInt这样的方法中的参数也可以直接写参数的名称
public void testQuery() throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
InputStream binaryStream = null;
FileOutputStream yszPhoto = null;
try {
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth,photo from customers where id=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1, 21);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
Date birth = resultSet.getDate("birth");
Customer customer = new Customer(id, name, email, birth);
Blob photo = resultSet.getBlob("photo");
binaryStream = photo.getBinaryStream();
yszPhoto = new FileOutputStream("src/main/resources/yszPhoto.jpg");
byte[] bytes = new byte[1024];
int length;
while ((length = binaryStream.read(bytes)) != -1) {
yszPhoto.write(bytes, 0, length);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (binaryStream != null) {
binaryStream.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
binaryStream.close();
}
try {
if (yszPhoto != null) {
yszPhoto.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
yszPhoto.close();
}
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
}
支持批量操作
利用PreparedStatement进行批量插入操作
PreparedStatement预编译SQL语句,加快了一定的访问速度
public void testInsert1() throws Exception {
//使用PreparedStatement操作
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "insert into goods(name)values(?)";
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
preparedStatement.setObject(1, "name_" + i);
preparedStatement.execute();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,preparedStatement);
}
使用Batch
使用Batch可以减少与数据库交互的次数,将多次的SQL请求一次发送,再由数据库进行统一的处理。
public void testInsert2() throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "insert into goods(name)values(?)";
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
preparedStatement.setObject(1, "name_" + i);
//积累SQL
preparedStatement.addBatch();
if (i % 500 == 0) {
//执行Batch
preparedStatement.executeBatch();
//清空Batch
preparedStatement.clearBatch();
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,preparedStatement);
}
}
同时,数据库默认情况下是不支持Batch的,这是需要在jdbc.properties的url字段后加入?rewriteBatchedStatements=true
再加入了Batch以后,插入两万条数据的时间由59s变为了0.6s
此外,还可以通过批量提交来进一步加快速度
connection = JDBCUtils.getConnection();
//关闭自动提交
connection.setAutoCommit(false);
String sql = "insert into customers(name)values(?)";
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
preparedStatement.setObject(1, "name_" + i);
//积累SQL
preparedStatement.addBatch();
if (i % 500 == 0) {
//执行Batch
preparedStatement.executeBatch();
//清空Batch
preparedStatement.clearBatch();
}
}
//一次性提交所有数据
connection.commit();
PreparedStatement与Statement的对比
- PreparedStatement是Statement的一个子接口,Statement仅负责将SQL语句传送到数据库,而PreparedStatement会先预编译SQL,在开发中大都使用PreparedStatement
- Statement不支持参数只可以通过字符串拼接,而PreparedStatement可以使用?来代表一个参数
- PreparedStatement可以很好的解决SQL注入问题
- PreparedStatement可以实现对Blob类型的操作
- PreparedStatement支持Batch批量操作,且仅编译一次
数据库事务
关于数据库事务的基本信息在学校的数据库课程中都有讲过,所以在这里就不再赘述。在此仅涉及JDBC的事务处理
- set autocommit=false对DDL语句无效
- 关闭连接时会自动提交
- DML语言执行完毕后会自动提交
- set autocommit=false可以取消DML自动提交
事务回滚
具体过程
- connection.setAutoCommit(false)
- connection.commit()
- catch到Exception时调用connection.rollback()
- 如果连接是从外部传入的,则不要在内部关闭连接,并且在操作完毕后应尽量将连接恢复到传入时的状态
事务隔离级别的JDBC实现
connection.setTransactionIsolation(Conntion.TRANSACTION_READ_COMMITTED)
具体的隔离级别可以在Connection源码中找到
DAO封装
- 在工作目录下创建如下的文件

其中BaseDAO里面放置了通用的CRUD操作函数,CustomerDAO接口定义了通用的规范,CustomerDAOImpl中为CustomerDAO的具体实现类,由于代码片段几乎与前述相同,故在此不再赘述
数据库连接池技术
概述
我们之前已经学习了线程池与字符串常量池的概念,这里,我们将学习一门相似的技术 – 数据库连接池
现有数据库连接池存在的问题
反复创建断开与数据库的连接会造成反复的申请与释放资源
若有的连接未被正常释放,时间长了以后容易造成内存泄漏
不易控制连接的线程数目
数据库连接池技术的基本思想
- 在一个缓冲池中放入一定数量的连接,当需要建立与数据库的连接时,程序向连接池发送请求
- 连接池负责分配,管理连接,允许连接的复用
- 连接池有着最小数据库连接数与最大数据库连接数
分类
DBCP:速度较快,但是稳定性不好
C3P0:速度较慢,稳定性好
Druid:阿里提供的数据库连接池,在速度和稳定性方面有着比较好的表现
实现
C3P0实现
public void testGetConnection1() throws Exception {
//简单测试
//获取数据库连接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("123456Asdfgh");
//设置初始数据库连接池中连接数
comboPooledDataSource.setInitialPoolSize(10);
Connection connection = comboPooledDataSource.getConnection();
System.out.println("connection = " + connection);
//销毁连接池
DataSources.destroy(comboPooledDataSource);
}
//获取数据库连接池,推荐写法
private static ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("helloc3p0");
//使用文件进行配置
@Test
public void testGetConnection2() throws Exception {
Connection connection = comboPooledDataSource.getConnection();
System.out.println("connection = " + connection);
}
配置文件信息
<c3p0-config>
<named-config name="helloc3p0">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">******</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
</c3p0-config>
Druid实现
private static DataSource dataSource1;
static{
try {
Properties properties = new Properties();
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
properties.load(resourceAsStream);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public void getConnection() throws Exception {
Connection connection = dataSource1.getConnection();
System.out.println(connection);
}
Apache-DBUtils实现CRUD操作
常用API
- QueryRunner
- ResultSetHandler
- DbUtils
实现
QueryRunner进行修改
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = DruidTest.getConnection();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int queryCount = runner.update(connection, sql, "ysz004", "", "");
System.out.println(queryCount);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, null);
}
QueryRunner的内部实现其实与我们自己写的封装是十分相似的,例如以下是QueryRunner中update方法中的部分代码
stmt = this.prepareStatement(conn, sql);
this.fillStatement(stmt, params);
rows = stmt.executeUpdate();
可见QueryRunner内部也是通过创建PrepareStatement实例来完成对数据库的各种操作
QueryRunner进行查询
public void testQuery1() {
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = DruidTest.getConnection();
String sql = "select id,name,email,birth from customers where id=?";
BeanHandler<Customer> customerBeanHandler = new BeanHandler<Customer>(Customer.class);
Customer customer = runner.query(connection, sql, customerBeanHandler, 23);
System.out.println(customer.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
此外,ResultSetHandler还有众多的实现类,如BeanListHandler可以封装多条结果的集合,MapHandler可以封装一个结果集,ScalaHandler查询特殊值。
自定义ResultSetHandler的实现类
public void testQuery3() throws Exception{
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = DruidTest.getConnection();
String sql = "select id,name,email,birth from customers where id=?";
ResultSetHandler<Customer> handler=new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet resultSet) throws SQLException {
System.out.println("handler");
return null;
}
};
Customer customer = runner.query(connection, sql, handler, 23);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
通过重写ResultSetHandler中的handler方法可以自定义ResultSetHandler的实现类
一些其他方面的注意事项
- 使用PreparedStatement.execute()时,返回值的Boolean并不是执行的成功与否,而是当前操作是否是查询操作。
- if判断时,使用”a”.equals(object),可以避免一些空指针异常
- 注意try-catch与throws的使用
- 内部被调用的方法使用throws,而外部的方法使用try-catch
- 涉及到资源关闭往往需要try-catch
本次遇到的错误与总结
private static DataSource dataSource1; static{ try { Properties properties = new Properties(); InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); properties.load(resourceAsStream); DataSource dataSource1 = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { Connection connection = dataSource1.getConnection(); return connection; }
在本代码中由于采用“先throws再try-catch”的方法,导致在修改代码时try语句内的DataSource忘记删除,而且直接覆盖了外层的DataSource,这个问题并不会引起报错。