文章目录
概述
- jdbc是为了访问不同的数据库提供的统一的接口,为使用者屏蔽了细节问题
- 使用jdbc可以连接任何提供了jdbc驱动程序的数据库系统,从而完成对数据库的各种操作
原理图
如果不同的数据库,我们的方法不统一,不利于程序管理
java厂商使用以下的规则:使用一套接口规范,不同的数据库厂商实现,在java程序中调用接口的方法
快速入门(增删改)
步骤如下
- 注册驱动:加载Dirver类
- 获取连接:得到Connection
- 执行增删改查:发送SQL给mysql执行
- 释放资源:关闭相关连接
package jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;
public class JDBC1 {
public static void main(String[] args) throws Exception{
//首先,将jar包加入库
//1.注册驱动 com.mysql.jdbc.Driver
Driver driver = new Driver();
//2.得到连接
/*
* jdbc:mysql:// 规定的协议,通过jdbc的方式连接mysql
* localhost 主机 也可以是ip地址
* 3306 端口
* testdb 连接到mysql dbms的具体的数据库
* MySQL的连接本质就是socket连接
* */
String url = "jdbc:mysql://localhost:3306/testdb";
//将用户名和密码放到Properties对象中
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","yb0os1");
//connect 网络连接
Connection connect = driver.connect(url, properties);
//3.执行sql语句
String sql = "insert into actor values(null,'刘德华'),(null,'王恬心');";
//statement 用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
//返回的是影响的行数
int rows = statement.executeUpdate(sql);
System.out.println(rows>0?"成功":"失败");
//4.注册驱动
statement.close();
connect.close();
}
}
获取数据库的五种方式
方式一:获取Driver实现类对象
属于静态加载,灵活性差,依赖性强(因为直接new的Dirver,在前面已经导入好确定的包了)
Dirver driver = new Driver();
string url = "xxx";
Properties info = new Properties();
info.setProperties("user","xxx");
info.setProperties("password","xxx");
Connection conn = driver.connect(url,info);
方式二:反射
Class<Driver> driverClass = com.mysql.jdbc.Driver.class;
Driver driver = driverClass.newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","yb0os1");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
方式三:使用DriverManager代替Driver
Class<Driver> driverClass = com.mysql.jdbc.Driver.class;
Driver driver = driverClass.newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
String user = "root";
String password = "yb0os1";
DriverManager.registerDriver(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
方式四:Class.forName自动完成注册驱动(推荐)
//使用反射加载了Driver类
/*
* 看看源码
* 静态代码块 在类加载的时候执行一次
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
* */
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/testdb";
String user = "root";
String password = "yb0os1";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
注意
mysqL驱动5.1.6可以无需CLass .forName(“com.mysql.jdbc.Driver”);
从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
方式五:使用properties
Properties properties = new Properties();
properties.load(new FileInputStream("./info.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String database = properties.getProperty("database");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url + database, user, password);
Statement statement = connection.createStatement();
statement、PreparedStatament、CallableStatement
是一个接口
用于执行静态SQL语句并返回其生成的结果的对象
在连接建立完成之后,对数据库的访问、执行命令或者SQL语句,可以通过:
- Statement:存在SQL注入问题
- PreparedStatament:预处理
- CallableStatement:存储过程
SQL注入就是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库。
在JDBC中防范SQL注入,只要使用PreparedStatement就OK了
statement存在的sql注入问题
package jdbc.statementSQL;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class testsql {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("./info.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String database = properties.getProperty("database");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url + database, user, password);
Statement statement = connection.createStatement();
//在数据库中root账户对应的密码不是输入的内容
//execute 执行任意sql语句 返回布尔值
ResultSet resultSet = statement.executeQuery("select * from actor where id = 'root' and name = ''or '1'='1';");
if (resultSet.next())
System.out.println("登录成功");
resultSet.close();
statement.close();
connection.close();
}
}
PreparedStatement的使用
- 在SQL语句中可以使用
?
代表参数,调用setxxx方法为这些?
的位置赋值,该方法是有两个参数,第一个参数为?
参数的索引(从1开始),第二个参数是?
参数所代表的具体的值 - 调用
executeQuery
返回ResultSet结果集 - 调用
executeUpdate
进行增加、删除、修改操作,返回被影响的行数
好处就是:不需要拼接sql语句、有效解决sql注入安全问题、减少编译次数 提高效率
防止sql注入的原理:预处理
在执行sql语句之前,数据库服务前先将sql语句进行编译,确定sql语句的基本“结构”,放在缓存区,当真正执行sql语句时,直接从缓存区中去拿取,而不会进行再次编译。这样,即使在执行sql语句时,发现用户传入的参数中带有sql关键字,也不会被识别编译,只会被当成参数替换占位符,拼接在sql语句中,这样就很好的避免的sql注入。
package jdbc.preparedStatement;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class demo {
public static void main (String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("./info.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String database = properties.getProperty("database");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url + database, user, password);
//需要直接使用sql语句与preparedStatement关联起来
String sql = "select * from actor where id=? and name=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"root");
// preparedStatement.setString(2,"'or '1'='1");//万能密码不可以用了
preparedStatement.setString(2,"yb0os1");
//执行
//这里不要填sql语句 因为前面已经关联起来了
//如果还是加了sql语句 那么使用的带有?的 执行的没有被预处理的sql语句
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
System.out.println("登录成功");
resultSet.close();
preparedStatement.close();
connection.close();
}
}
ResultSet结果集(查)
是一个接口
表示数据库结果集的数据表,一般通过执行查询数据库语句生成
该对象保持一个光标指向其当前的数据行,最初光标位于第一行之前,next方法可以使光标移动到下一行,当resultset对象中没有更多行时返回false(可以使用while循环遍历结果集)
Properties properties = new Properties();
properties.load(new FileInputStream("./info.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String database = properties.getProperty("database");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url + database, user, password);
Statement statement = connection.createStatement();
// 获取结果集 一开始在第一行之前 先进行next到第一行 然后继续 到最后一行继续next返回了fasle
ResultSet resultSet = statement.executeQuery("select id,name from actor;");
//还可以previous 就是向前移动一行 向前的一行不存在 就是返回false
while (resultSet.next()){
//对于每一行的每一列进行获取 也可以根据列名来获取
// getXxx(列的索引||列名)
System.out.println(resultSet.getInt(1)+"-"+resultSet.getString(2));
}
resultSet.close();
statement.close();
connection.close();
rows是一个ArrayList,存储着每行数据
封装JDBCUtils
package jdbc;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
//工具类
public class JDBCUtils {
private static String user;
private static String password;
private static String driver;
private static String url;
// 静态代码块:为了个上述的属性赋值 读取文件
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("./mysql.properties"));
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
url = properties.getProperty("url");
Class.forName(driver);//这里可以不用这样加载类 因为自动加载类
} catch (Exception e) {
//将编译异常转换为运行异常
//调用者可以选择捕获该异常,或者选择默认处理该异常的方式
throw new RuntimeException(e);
}
}
// 连接
public static Connection getConnection() {
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
//同理 抛出 如果处理交给调用者
throw new RuntimeException(e);
}
}
// 关闭连接
/*
* 这里涉及到 ResultSet、Statement、Connection
* 先判断是否存在 再选择是否要释放
* null代表不释放
* */
public static void close(ResultSet resultSet, Statement statement,Connection connection){
try {
if (resultSet!=null)resultSet.close();
if (statement!=null)statement.close();
if (connection!=null)connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
事务处理
-
JDBC程序中,当一个Connection对象创建时,默认情况下会自动提交事务:每次执行一个SQL语句,如果执行成功就会向数据库提交,不能回滚。
-
需要多个SQL语句作为一个整体执行的时候需要事务,可以使用Connection的
setAutoCommit(false)
取消自动提交事务 -
commit()
方法提交事务;rollback()
方法回滚事务
package jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Transaction {
Connection connection = null;
PreparedStatement preparedStatement=null;
String sql1="update account set balance = balance - 100 where name='马云';";
String sql2="update account set balance = balance + 100 where name='马化腾';";
@Test
public void noTransaction(){
try {
connection = JDBCUtils.getConnection();//默认情况下 connection默认自动提交
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
int i = 1/0; // 抛出异常 使后续的修改不可达
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
try {
connection.close();
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void useTransaction(){
try {
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false);//不让他自动提交 相当于开始的事务
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
int i = 1/0; // 抛出异常 使后续的修改不可达
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
connection.commit();//提交
} catch (Exception e) {
try {
//抛出以上进入这里 可以进行回滚 撤销执行的SQL
//默认回滚到事务开始的状态 也就是connection.setAutoCommit(false);这里
System.out.println("发生异常,进行回滚");
connection.rollback();
throw new RuntimeException(e);
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
try {
connection.close();
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
批处理
- 当需要一次性处理多条记录的时候,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,效率up
- JDBC批量处理语句的方法:
addBatch()
:添加需要批处理的SQL语句或者参数executeBatch()
:执行批量处理语句clearBatch()
:清空批处理包的语句
- JDBC连接MySQL时,要使用批处理的话要在url中加上
?rewriteBatchedStatements=true
- 批处理往往结合preparedStatement使用,既减少编译次数,又减少运行次数,大大提高了效率
package jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class Batch {
@Test
public void noBatch() throws Exception {
/*
* 不使用批处理添加五千条数据
* */
Connection connection = JDBCUtils.getConnection();
String sql = "insert into actor values (?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, i + "");
preparedStatement.setString(2, "tom" + i);
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("不使用批处理耗时" + (end - start) + "毫秒");//10502毫秒
JDBCUtils.close(null, preparedStatement, connection);
}
@Test
public void useBatch() throws Exception {
/*
* 使用批处理添加五千条数据
* 记得再url上加入 ?rewriteBatchedStatements=true
* */
Connection connection = JDBCUtils.getConnection();
String sql = "insert into actor values(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, i + "");
preparedStatement.setString(2, "tom" + i);
// 这里做了什么工作?
preparedStatement.addBatch();
//当加入1000条sql语句的时候 批量处理一下
if ((i + 1) % 1000 == 0) {
preparedStatement.executeBatch();
//清空
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("使用批处理耗时" + (end - start) + "毫秒");//64毫秒
JDBCUtils.close(null, preparedStatement, connection);
}
}
我们来看看addBatch
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
//进来先判断这个ArrayList数组是否存在 存在就跳过这个if 否则新建
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
//this.parameterValues.length就是?的个数
//检查每一个参数是否设置 也就是是否预处理了
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
//将预处理之后的sql语句加入数组之中
this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
数据库连接池
传统获取Connection的弊端
- 传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证IP、用户名、密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将会占用很多的系统资源,容易导致服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将会导致数据库内存泄漏,MySQL崩溃
- 传统获取连接的方式,不能控制创建的连接数量,如果一下子连接数量过多也可能导致内存泄露,MySQL崩溃
- 解决传统开发中的数据库连接问题,采用数据库连接池
// 建立5000连接
public void noUsePoll(){
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = JDBCUtils.getConnection();
//进行sql处理
//先假设没有关闭 "Too many connections" 这就是一下子涌进太多的sql连接
//进行关闭测试时间
JDBCUtils.close(null,null,connection);
}
long end = System.currentTimeMillis();
System.out.println("传统方式5000次连接,所需时间:"+(end-start)+"ms"); //5839ms 这是一个非常耗时的
}
连接池简介
- 预先在缓冲池中放入一定数量的连接,当需要建立连接时,只需要从“缓冲池”取出一个,使用完毕之后放回去(这里不是关闭仅仅时还给缓冲池)
- 数据库连接池负责分配、管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个新连接
- 当应用程序向连接池请求的连接数量超过池内最大的连接数量时,这些请求将被加入到等待队列之中
- 大白话讲:我们建立连接的时候是把连接池里面的连接“拿过来”,释放连接的时候是把“拿过来”的连接“还回去”
DataSource只是一个接口,是让第三方进行实现的
- C3P0 数据库连接池:速度相对较慢,稳定性不错
- DBCP 数据库连接池:速度较c3p0快,不稳定
- Proxool 数据库连接池:有监控连接池状态的功能,稳定性不如c3p0
- BoneCP 数据库连接池:速度快
- Druid(德鲁伊):集DBCP、Proxool 、C3P0优点于一身的数据库连接池(推荐)
C3P0
package jdbc;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.io.FileInputStream;
import java.lang.annotation.Target;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class C3P0using {
@Test
// 方式1:相关参数在程序中指定 user url password
public void testC3P0_01() throws Exception {
// 1、创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2、通过配置文件获取相关连接信息
Properties properties = new Properties();
properties.load(new FileInputStream("./mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//给数据源设置相关参数
comboPooledDataSource.setDriverClass(driver);//连接是由comboPooledDataSource管理
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setJdbcUrl(url);
//初始化数据源的连接数:初始化的连接池里面有多少个连接,可以增加到maxPoolSize
comboPooledDataSource.setInitialPoolSize(10);
//数据源的最大连接数:第51个进入等待队列
comboPooledDataSource.setMaxPoolSize(50);
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; ++i) {
//从 DataSource 接口实现
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0 5000次连接时间:"+(end-start)+"ms");//1108ms
}
@Test
// 方式2:使用配置文件模板来完成
// 1、将 c3p0-config.xml 放到src下面
public void testC3P0_02() throws SQLException {
// 2、填入的是xml中设置的连接池的名称
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("yb0os1");
Connection connection = comboPooledDataSource.getConnection();
System.out.println("连接成功");
connection.close();
}
}
c3p0-config.xml
<c3p0-config>
<!--数据源的名称 也就是 连接池-->
<named-config name="yb0os1">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/testdb</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">yb0os1</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">50</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
Druid(德鲁伊)
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/testdb?rewriteBatchedStatements=true
username=root
password=yb0os1
#初始化的连接数
initialSize=10
#最小连接数
minIdle=5
#最大连接数
maxActive=50
#最大等待时间 在等待队列的最长等待时间 ms
maxWait=5000
package jdbc;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class Druidusing {
@Test
public void useDruid() throws Exception {
//1、加入jar包
//2、配置文件 druid.properties
//3、创建properties对象 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("./src/druid.properties"));
//4、创建一个指定参数的数据库连接池 基于properties进行配置
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; ++i) {
Connection connection = dataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("druid 500000 次连接时间:"+(end-start)+"ms");//349ms
}
}
基于德鲁伊的封装
package jdbc;
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 JDBCUtilsByDruid {
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);
}
}
//数据库连接池close不是真正的释放 而是将connection放回连接池
public static void close(ResultSet resultSet, Statement statement,Connection connection){
try {
//这里的close和JDBCUtils的close是不一样的 和JDBCUtils运行类型是不一样的
//connection只是一个接口 他实现类是不一样的 一个是实现的德鲁伊 一个实现的是mysql的
if (resultSet!=null)resultSet.close();
if (statement!=null)statement.close();
if (connection!=null)connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Apache-DBUtils
分析 JDBCUtilsByDruid 存在的问题
- 关闭connection后,结果集resultSet无法使用
- resultSet不利用数据的管理
自己的方法解决
package jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class useUtilsByDruid {
private static final List<Actor> mess = new ArrayList<>();
public static void main(String[] args) throws Exception {
Connection connection = JDBCUtilsByDruid.getConnection();
String sql = "select * from actor where id <=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,10 );
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
mess.add(new Actor(resultSet.getString("id"),resultSet.getString("name")));
// System.out.println(resultSet.getString("id")+"-"+resultSet.getString("name"));
}
JDBCUtilsByDruid.close(resultSet,preparedStatement,connection);
//关闭了之后还是可以使用数据
for (Actor actor : mess) {
System.out.println(actor.getId()+"-"+actor.getName());
}
}
}
Apache-DBUtils
- commons-dbutils是Apache组织提供的一个开源的JDBC工具类库,他是对JDBC的封装,使用dbutils能极大的简化jdbc编码的工作量
- DbUtils类
QueryRunner
类:该类封装了SQL的执行,是线程安全的。可以实现增删改查、批处理ResultSetHandler
接口:该接口用于处理 java.sql.ResultRet ,将数据按要求转换成另一种形式
package jdbc;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
public class UseDbUtils {
Connection connection = null;
//查询:返回的是多行多列记录
@Test
public void testQueryMany() {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("./src/druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
connection = dataSource.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from actor where id <= ?";
/*query的参数:连接、sql语句、存放的容器、参数
* 1、query方法 就是执行sql语句 得到 ResultSet 封装到ArrayList集合中
* 2、返回集合
* 3、connection:连接
* 4、sql:执行的sql语句
* 5、new BeanListHandler<>(Actor.class):将ResultSet->Actor对象->封装到ArrayList
* 底层使用反射机制 获取Actor的属性
* 6、 10:给?赋值 是一个可变形参
* 7、底层得到的 ResultSet 会在query关闭;创建的preparedStatement也在底层关闭
* */
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 10);
for (Actor actor : list) {
System.out.println(actor.getId() + "-" + actor.getName());
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
//查询:返回的是单行多列记录
@Test
public void testQuerySingle() {
try {
connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from actor where id = ?";
Actor actor =
queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
if (actor != null) System.out.println(actor.getId() + "-" + actor.getName());
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询:返回的是单行单列记录
@Test
public void testQueryScaler() {
try {
connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select `name` from actor where id = ?";
Object obj =
queryRunner.query(connection, sql, new ScalarHandler(), 10);
System.out.println(obj);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//dml操作 增删改
@Test
public void testDML() {
try {
connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "update actor set name=? where id =?";
//返回值是受影响的行数
//dml都是update
int affectedRow = queryRunner.update(connection, sql, "张三丰", "100");
System.out.println(affectedRow > 0 ? "执行成功" : "执行未影响到表");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
query函数
public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
PreparedStatement stmt = null;
ResultSet rs = null;
T result = null;
try {
stmt = this.prepareStatement(conn, sql);
this.fillStatement(stmt, params);
rs = this.wrap(stmt.executeQuery());
result = rsh.handle(rs);
} catch (SQLException var33) {
this.rethrow(var33, sql, params);
} finally {
try {
this.close(rs);
} finally {
this.close((Statement)stmt);
}
}
return result;
}
BasicDao
apache-dbutils+druid还有一些不足
- SQL语句是固定的,不能通过参数传入,通用性不足
- 对于select操作,如果有返回值,返回值类型不能固定需要使用泛型
- 将来的表有很多,业务很复杂,不可能只靠一个java类完成
DAO:data access object 访问数据的对象
这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。在BaiscDao 的基础上,实现一张表对应一个Dao,更好的完成功能,比如 Customer表-Customer.java类(iavabean)-CustomerDao.java
BasicDAO.java
package dao_.dao;
import dao_.utils.JDBCByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class BasicDAO<T> {
private QueryRunner qr = new QueryRunner();
/***
* dml操作 也就是增删改的操作
* @param sql 要执行的sql语句
* @param params sql语句中的参数
* @return 受影响的行数
*/
public int update(String sql,Object...params){
Connection connection = JDBCByDruid.getConnection();
try {
return qr.update(connection,sql,params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCByDruid.close(null,null,connection);
}
}
/***
* 查询多行多列
* @param sql 执行的sql语句
* @param clazz 类的class对象,反射用到
* @param params sql语句中的参数
* @return 返回的数据
*/
public List<T> queryMulti(String sql,Class<T>clazz,Object...params){
Connection connection = JDBCByDruid.getConnection();
try {
return qr.query(connection,sql,new BeanListHandler<>(clazz),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCByDruid.close(null,null,connection);
}
}
/***
* 查询单行多列
* @param sql 执行的sql语句
* @param clazz 类的class对象,反射用到
* @param params sql语句中的参数
* @return 返回的数据
*/
public T querySingle(String sql,Class<T>clazz,Object...params){
Connection connection = JDBCByDruid.getConnection();
try {
return qr.query(connection,sql,new BeanHandler<>(clazz),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCByDruid.close(null,null,connection);
}
}
/***
* 查询单行单列
* @param sql 执行的sql语句
* @param params sql语句中的参数
* @return 返回的数据
*/
public Object queryScalar(String sql,Object...params){
Connection connection = JDBCByDruid.getConnection();
try {
return qr.query(connection,sql,new ScalarHandler(),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCByDruid.close(null,null,connection);
}
}
}