JDBC
一、JDBC概述
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
Java 程序员使用JDBC ,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作
Java程序员实际上只要面向接口编程即可。
JDBC API
JDBC API 是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中
JDBC 程序编写步骤:
- 注册驱动 - 加载Diver类
- 获取连接 - 得到Connection
- 执行增删改查 - 发送SQL 给MySQL执行
- 释放资源 - 关闭相关
前置工作:移入驱动 jar包
二、JDBC-连接数据库
获取数据库连接的五种方式
-
方式一:
public static void main(String[] args) throws SQLException { //前置工作,在项目下创建一个文件夹,如libs // 将mysql.jar 拷贝到该目录下,点击add as libraries 加入到项目中 //1. 注册驱动 Driver driver = new Driver(); //2. 得到连接 //(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql //(2) localhost 主机,可以是 ip地址 //(3) :3306 表示mysql监听的端口 //(4) jdbc 连接到 mysql 的哪个数据库 //(5) mysql 的连接本质就是前面学过的socket连接 String url = "jdbc:mysql://localhost:3306/jdbc"; //将 用户名和密码 放入到Properties 对象 Properties properties = new Properties(); properties.setProperty("user","root"); //用户 properties.setProperty("password","15737979065"); //密码 Connection connect = driver.connect(url, properties); //3. 执行SQL // String sql = "insert into actor values(null,'张三','男','2003-10-11','110')"; // String sql = "update actor set name = '丁真' where id = 1"; String sql = "delete from actor where id = 1"; //statement 用于执行静态SQL语句并返回其生成的结果的对象 Statement statement = connect.createStatement(); int rows = statement.executeUpdate(sql); //如果是 DML 语句,返回的就是影响行数 System.out.println(rows > 0 ? "成功" : "失败"); //4. 关闭资源 statement.close(); connect.close(); }
-
方式二:
方式1的不足 会直接使用 com.mysql.jdbc.Driver(), 属于静态加载,灵活性差,依赖强。public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { //使用反射 动态 加载Driver 类,更加灵活,减少依赖性 Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); String url = "jdbc:mysql://localhost:3306/jdbc"; Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","15737979065"); Connection connect = driver.connect(url, info); System.out.println("方式二:"+connect); }
-
方式三:使用 DriverManager 替换 Driver 进行统一管理
public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { //使用反射 加载Driver Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver)clazz.newInstance(); String url = "jdbc:mysql://localhost:3306/jdbc"; String user = "root"; String password = "15737979065"; /*Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","15737979065");*/ DriverManager.registerDriver(driver); //注册Driver 驱动 //getConnection(url,user,password) 对于 给定的数据库url 建立连接 Connection connection = DriverManager.getConnection(url, user,password); //可以直接是 String 也可以是 Properties 对象 // Connection connect = driver.connect(url, info); System.out.println("方式三:"+ connection); }
-
方式四:使用 Class.forName 自动完成注册驱动 => 推荐使用
public void connect04() throws ClassNotFoundException, SQLException { //使用反射加载了 Driver 类 //在加载 Driver 类时,完成了注册 Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/jdbc"; String user = "root"; String password = "15737979065"; Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式四:"+connection); }
1.可以无需 Class.forName("com.mysql.cj.jdbc.Driver"); mysql 5.1.6 之后
2.从 jdk 1.5 以后使用了 jdbc4,不再需要显示调用 Class.forName() 注册驱动而是自动调用驱动 jar 包下 META-INF\services\java.sqlDriver 文本中的类名称去注册
3.建议还是写上 Class.forName("com.mysql.cj.jdbc.Driver") ,更加明确package com.mysql.cj.jdbc; import java.sql.DriverManager; import java.sql.SQLException; 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!"); } } }
在使用DriverManager 替换Driver 统一管理时,可以不注册 Driver 驱动
通过反射加载Class.forName("com.mysql.cj.jdbc.Driver")
时 ,在 Driver 类中(上面代码)存在静态代码块,在 加载 Driver 类时,执行静态代码块,创建 Driver 驱动DriverManager.registerDriver(new Driver());
-
方式五:创建 .properties 文件,以配置的形式,是 mysql 的连接更灵活。避免硬编码 => 推荐使用
# .properties 文件 user=root password=15737979065 url=jdbc:mysql://localhost:3306/jdbc driver=com.mysql.cj.jdbc.Driver
public void connect05() throws IOException, ClassNotFoundException, SQLException { // 通过 properties 对象获取配置文件信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式五:"+ connection); }
三、JDBC-ResultSet
ResultSet 结果集
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false, 因此可以在while循环中使用循环来遍历结果集
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
// 通过 properties 对象获取配置文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//1. 注册驱动
Class.forName(driver);
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到statement
Statement statement = connection.createStatement();
//4. 组织sql 语句
String sql = "select id,name,sex,borndate from actor";
//执行给定的 sql 语句,该语句返回一个 ResultSet 对象
ResultSet resultSet = statement.executeQuery(sql);
//5. 使用while 取出数据
while (resultSet.next()){ //让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1); //获取该行的第 1 列
String name = resultSet.getString(2); //获取该行的第 2 列
String sex = resultSet.getString(3); //获取该行的第 3 列
Date date = resultSet.getDate(4); //获取该行的第 2 列
System.out.println(id +"\t" + name + "\t" + sex + "\t" + date);
}
//6. 关闭资源
resultSet.close();
statement.close();
connection.close();
}
四、JDBC-SQL注入问题
Statement对象,用于执行SQL语句并返回其生成的结果的对象
在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
- Statement [存在 SQL注入]
- PreparedStatement [预处理]
- CallableStatement [存储过程]
Statement 对象执行SQL 语句,存在SQL 注入风险
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL 语句段或命令,恶意攻击数据库。
要防范SQL 注入,只要用 PreparedStatement (从Statement扩展而来) 取代Statement 就可以了。
例如数据库中一张表 有 name 和 pwd 字段
如果使用 Statement 对象
正常查询select * from admin where name = 'tom' and pwd = '123'
SQL 注入 用户名为 1 ' or 密码为 or '1' = '1
相当于select * from admin where name = '1 ' or ' and pwd = 'or '1' = '1'
PreparedStatement
- preparedStatement 执行的SQL 语句中的参数用问号
?
来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()有两个参数,第一个参数是要设置的SQL语句 中的参数的索引(从1 开始),第二个是设置的SQL 语句中的参数值 - 调用executeQuery(), 返回ResultSet对象
- 调用executeUpdate(), 执行更新,包括增删改
预处理的好处
- 不再使用 + 拼接SQL 语句,减少语法错误
- 有效的解决了sql 注入问题
- 大大减少了编译次数,效率较高
public static void main(String[] args) throws Exception{
Scanner in = new Scanner(System.in);
//让用户输入 管理员的名字和密码
System.out.println("输入管理员的名字:");
//nextLine() 回车作为结束的标志 next() 空格作为结束的标志
String adminName = in.nextLine();
System.out.println("输入管理员的密码:");
String adminPwd = in.nextLine();
// 通过 properties 对象获取配置文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//1. 注册驱动
Class.forName(driver);
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到PreparedStatement
//3.1 组织sql 语句, SQL 语句中的 ? 相当于 占位符
String sql = "select name,pwd from admin where name = ? and pwd = ?";
java.sql.PreparedStatement psmt = connection.prepareStatement(sql);
//3.2 给 ? 所在的index 赋值
psmt.setString(1,adminName);
psmt.setString(2,adminPwd);
//4. 执行 select 语句
// 执行 executeQuery 不需要再写 sql
ResultSet resultSet = psmt.executeQuery();
if (resultSet.next()){ //查询到一条语句登录成功
/*String name = resultSet.getString(1);*/
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
resultSet.close();
psmt.close();
connection.close();
}
五、JDBC-事务-批量处理
在默认情况下事务自动提交
@Test
public void useTransaction(){
Connection conn = null;
String sql = "update account set money = money - 100 where id = 1";
String sql2 = "update account set money = money + 100 where id = 2";
PreparedStatement psmt = null;
try {
conn = JDBCUtils.getConnection(); //默认自动提交
//将 Connection 取消自动提交
conn.setAutoCommit(false);
//执行第一条sql
psmt = conn.prepareStatement(sql);
psmt.executeUpdate();
int i = 1 / 0; //模拟异常
//执行第二条sql
psmt = conn.prepareStatement(sql2);
psmt.executeUpdate();
//如果无异常 提交事务
conn.commit();
} catch (SQLException e) {
//如果发生异常,可以捕获异常,进行回滚,即撤销执行的 SQL
//默认回滚到事务开始的状态
System.out.println("执行发生了异常");
try {
conn.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}finally {
JDBCUtils.close(psmt,conn);
}
}
批处理
当需要成批插入或更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
JDBC的批量处理语句包括下面方法:
- addBatch(): 添加需要批量处理的SQL语句或参数
- executeBatch(): 执行批量处理语句;
- clearBatch(): 清空批处理包的语句
- JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?
rewriteBatchedStatements=true
批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。
addBatch() 本质是初始化了一个 ArrayList集合,用于存放 sql 语句
六、JDBC-数据库连接池
数据库连接池的种类:
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现
- C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate,spring) //four years ago 已停止维护
- DBCP 数据库连接池,速度相对c3p0较快,但不稳定
- Proxool 数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池,速度快
- Druid 是阿里提供的数据库连接池,集DBCP、C3P0、Proxool 优点于一身的数据库连接池
Druid 实现数据库连接池:
# druid.properties 配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
username=root
password=15737979065
#initial connection Size
initialSize=10
#min idle connection size
minIdle=5
#max active connection size
maxActive=20
#max wait time
maxWait=5000
JDBCUtilsByDruid的工具类
public class JDBCUtilsByDruid {
private static DataSource ds;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
public static void close(ResultSet set, Statement smt , Connection conn){
try {
if(set != null){
set.close();
}
if (smt != null){
smt.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
七、JDBC-DBUtils工具类
Apache-DBUtils 的功能介绍
- Commons-dbutils 是 Apache 组织的提供的一个开源 JDBC 工具类库,它是对 JDBC的封装,使用dbutils 能极大简化jdbc编码的工作量
DbUtils 类 - QueryRunner 类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
- 使用QueryRunner 类实现查询
- ResultSetHandler 接口:该接口用于处理java.sql.ResultSet, 将数据按要求转换为另一种形式
ResultSetHandler
是父接口,在query()方法中可以传入
BeanListHandler<>()
=>pojo类的对象 封装到 ArrayList集合中
BeanHandler<>()
=> pojo 类的对象 数据库表的行数据
ScalarHandler()
=> 返回Object 单个数据
Object ...Params
=> 可传入多个Object 参数,给 sql 语句中的 ? 赋值
@Test
//使用apache-DBUtils 工具类 + druid 完成对表的crud 操作
public void testQueryMany() throws SQLException { //返回结果是多行的情况
//1. 得到连接(druid)
Connection conn = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口,先引入DBUtils 相关的 jar, 加入到project
//3. 创建QueryRunner
QueryRunner qr = new QueryRunner();
//4. 执行相关的方法,返回 ArrayList
String sql = "select * from actor where id >= ?";
/*String sql = "select id,name from actor where id >= ?";*/
//(1) query 方法就是执行方法就是执行 sql语句,得到resultSet ---封装到 --> ArrayList 集合中
//(2) 返回集合
//(3) conn 连接
//(4) sql:执行的sql 语句
//(5) new BeanListHandler<>(Actor.class) :将 resultSet -> Actor 对象 -> 封装到 ArrayList
// 底层使用反射机制去获取 Actor 属性,然后进行封装
//(6) 1 就是给sql 语句中的 ? 赋值,可以有多个值,因为是可变参数 Object...params
//(7) 底层得到的 resultSet, 会在 query 关闭,关闭 PreparedStatement
List<Actor> list = qr.query(conn, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("输出 actor ");
/*System.out.println(list);*/
for (Actor actor : list){
System.out.print(actor);
}
//5. 释放资源
JDBCUtilsByDruid.close(null,null ,conn);
}
分析 qr.query(conn, sql, new BeanListHandler<>(Actor.class), 1) 方法
public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
if (conn == null) {
throw new SQLException("Null connection");
} else if (sql == null) {
throw new SQLException("Null SQL statement");
} else if (rsh == null) {
throw new SQLException("Null ResultSetHandler");
} else {
Statement stmt = null;
ResultSet resultSet = null;
T result = null;
try {
if (params != null && params.length > 0) {
PreparedStatement ps = this.prepareStatement(conn, sql); //创建PreparedStatement
stmt = ps;
this.fillStatement(ps, params); //对 SQL ? 进行赋值
resultSet = this.wrap(ps.executeQuery()); //执行sql,返回resultSet
} else {
stmt = conn.createStatement();
resultSet = this.wrap(((Statement)stmt).executeQuery(sql));
}
result = rsh.handle(resultSet); //返回的 resultSet --> arrayList[使用反射]
} catch (SQLException var12) {
this.rethrow(var12, sql, params);
} finally {
this.closeQuietly(resultSet); //关闭ResultSet
this.closeQuietly((Statement)stmt); //关闭Statement
}
return result;
}
}
八、JDBC-BasicDao
Apachae-dbutils + Druid 简化了JDBC 开发,但还有不足
- SQL 语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
- 对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
DAO :data access object 数据访问对象
这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
public class BasicDAO<T> { //泛型指定具体类型
private QueryRunner qr = new QueryRunner();
//开发通用的 DML 方法,针对任意的表
public int update(String sql,Object ...params){
Connection conn = null;
try {
conn = JDBCUtilsByDruid.getConnection();
int update = qr.update(conn, sql, params);
return update;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null,null,conn);
}
}
//返回多个对象(即查询的结果是多行),针对任意表
public List<T> queryMulti(String sql, Class clazz, Object ...params){
Connection conn = null;
try {
conn = JDBCUtilsByDruid.getConnection();
return qr.query(conn,sql,new BeanListHandler<T>(clazz),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtilsByDruid.close(null,null,conn);
}
}
//返回单行对象(即查询的结果是单行),针对任意表
public T querySingle(String sql,Class clazz, Object ...params){
Connection conn = null;
try {
conn = JDBCUtilsByDruid.getConnection();
return qr.query(conn,sql,new BeanHandler<T>(clazz),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtilsByDruid.close(null,null,conn);
}
}
//返回单行单列对象(即查询的结果是单值),针对任意表
public Object queryScalar(String sql, Object ...params){
Connection conn = null;
try {
conn = JDBCUtilsByDruid.getConnection();
return qr.query(conn,sql,new ScalarHandler<>(),params);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtilsByDruid.close(null,null,conn);
}
}
}