文章目录
- 概述
- 快速入门(增删改)
- 获取数据库的五种方式
- 方式一:获取Driver实现类对象
- 方式二:反射
- 方式三:使用DriverManager代替Driver
- 方式四:Class.forName自动完成注册驱动(推荐)
- 方式五:使用properties
- statement、PreparedStatament、CallableStatement
- statement存在的sql注入问题
- PreparedStatement的使用
- ResultSet结果集(查)
- 封装JDBCUtils
- 事务处理
- 批处理
- 数据库连接池
- 传统获取Connection的弊端
- 连接池简介
- C3P0
- Druid(德鲁伊)
- Apache-DBUtils
- BasicDao
概述
- 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.DriverDriver 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返回了fasleResultSet resultSet = statement.executeQuery("select id,name from actor;");//还可以previous 就是向前移动一行 向前的一行不存在 就是返回falsewhile (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='马化腾';";@Testpublic 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);}}}@Testpublic 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 {@Testpublic 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);}@Testpublic 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 passwordpublic 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);//初始化数据源的连接数:初始化的连接池里面有多少个连接,可以增加到maxPoolSizecomboPooledDataSource.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 {@Testpublic 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;//查询:返回的是多行多列记录@Testpublic 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);}}}//查询:返回的是单行多列记录@Testpublic 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);}}//查询:返回的是单行单列记录@Testpublic 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操作 增删改@Testpublic void testDML() {try {connection = JDBCUtilsByDruid.getConnection();QueryRunner queryRunner = new QueryRunner();String sql = "update actor set name=? where id =?";//返回值是受影响的行数//dml都是updateint 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);}}
}