文章目录
1. JDBC技术概述
1.1. 概念和理解
-
Java Database Connectivity – java连接数据库技术 是java程序连接数据库的技术统称
- 在java代码中,使用JDBC提供的方法,可以发送字符串类型的sql语句到数据库管理软件,并获取语句执行结果,进而实现数据库数据CURD操作
- 在java代码中,使用JDBC提供的方法,可以发送字符串类型的sql语句到数据库管理软件,并获取语句执行结果,进而实现数据库数据CURD操作
-
由**java语言的规范(接口)和各个数据库厂商的实现驱动(jar)**组成
-
是一种典型的面向接口编程
-
优势
- 只需要学习jdbc接口规定方法,即可操作所有数据库软件
- 项目中切换数据库只需要更新第三方jar包,不需更改代码
1.2. 核心概念和使用路线
1.2.1. 具体核心类和接口
- DriverManager
- 将第三方数据库厂商的实现驱动jar注册到程序中
- 可以根据数据库连接信息获取 connection
- Connection
- 和数据库建立的连接,在连接对象上,可以多次执行数据库curd动作
- 可以获取 statement 和 preparedStatement ,callableStatement 对象
- Statement 和 PreparedStatement ,CallableStatement
- 具体发送SQL语句到数据库管理软件对象
- 不同方式发送稍有不同,PreparedStatement为重点
- Result
- 面向对象思维的产物(抽象成数据库的查询结果类)
- 存储DQL查询数据库结果的对象
- 需要我们进行解析,获取具体的数据库数据
1.2.2. 路线
2. 核心API
2.1. 总体使用6步骤
- 1.注册驱动
- 2.创建连接
- 3.创建发送SQL语句的对象
- 4.发送SQL语句并获取结果集对象
- 5.结果解析
- 6.释放资源
- 流程示意
2.2 基本演示
-
初始化数据库和表信息
-
USE jdbc_learn; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'id', account VARCHAR ( 20 ) NOT NULL UNIQUE COMMENT '账号', `password` VARCHAR ( 64 ) NOT NULL COMMENT '密码', nickname VARCHAR ( 20 ) NOT NULL COMMENT '昵称' ); INSERT INTO t_user ( account, `password`, nickname ) VALUES ( 'root', '123456', '经理' ),( 'admin', '666666', '管理员' );
-
-
创建java工程并导入jar包
-
建立连接并查询
-
1、通过java.sql包下的 DriverManager类调用 registerDriver() 传入 java.sql.Driver接口 的实现类来 注册驱动
- new Driver() 是mysql厂商 对java.sql.Driver类的实现类
- 依赖:
- 驱动版本 8+ import com.mysql.cj.jdbc.Driver;
- 驱动版本 5+ import com.mysql.jdbc.Driver;
- 依赖:
- new Driver() 是mysql厂商 对java.sql.Driver类的实现类
-
2、通过 DriverManager.getConnection(String url, String username, String password) 建立连接获取连接对象
-
Connection connection = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/jdbc_learn", "root", "root_isymikasan0415" );
-
url: jdbc:数据库厂商名://ip地址:port/数据库名 username: 数据库账号 password: 数据库密码
-
-
3、创建发送SQL语句的Statement对象
-
Statement statement = connection.createStatement();
-
-
4、发送sql语句
-
ResultSet resultSet = statement.executeQuery("select * from t_user");
-
-
5、解析结果集
-
6、释放资源
- 由内到外
- resultSet.close()
- statement.close();
- connection.close();
- 由内到外
-
2.3.DriverManager
DriverManager(驱动管理类)作用:
-
注册驱动
我们查询MySQL提供的Driver类,看它是如何实现的
在该类中的静态代码块中已经执行了
DriverManager
对象的registerDriver()
方法进行驱动的注册了,此时如果再次调用会注册两个驱动-
静态代码块会在类加载的时候自动执行,因此只要触发类加载即可
-
通过反射加载
Class.forName("com.mysql.jdbc.Driver");
-
-
-
创建连接对象 DriverManager.getConnection()
-
三个参数
- url :jdbc:数据库厂商名://ip地址:port/数据库名?key=value&key=value
- username
- Password
-
两个参数
- url: 同上
- Properties info : 存储账号和密码
- Properties 类似于 Map 以 key=value 形式 但都是字符串类型
- Properties对象至少包含:
- Key user
- Key password
- Properties对象至少包含:
- Properties 类似于 Map 以 key=value 形式 但都是字符串类型
Properties info = new Properties(); info.put("user", "root"); info.put("password", "root_isymikasan0415"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_learn", info);
-
一个参数
- url:同上
-
2.4. Statement
发送SQL语句并返回结果集
根据SQL分类不同,调用不同的方法
- sql分类
- DDL:容器创建,修改,删除
- DML:数据插入、修改、删除
- DQL:查询
- DCL:权限控制
- TCL:事务控制
- executeUpdate()
- return : int
- sql 语句为DDL、 DML时使用
- executeQuery()
- return: resultSet 结果集
- sql 语句为 DQL时使用
- …
2.5.ResultSet
要想进行数据解析,需要:
-
1、移动游标指定获取数据行
- resultSet内部包含游标默认指定第一行数据前
- 通过调用next()方法向后移动
- 返回 ture: 有更多行并且向下移动一行
- 返回 false: 没有更多行数据
- 通过 while(resultSet.next()){ 获取每一行数据 }
-
2、获取指定数据行的列数据
- resultSet.get类型(String columnLabel | int columnIndex);
- columnLabel 根据列名获取数据 有别名写别名
- columnIndex 根据下标获取,从左至右 从1开始
- resultSet.get类型(String columnLabel | int columnIndex);
-
获取结果集列的对象
- ResultSetMetaData metaData = resultSet.getMetaData
- metaData.getColumnCount(): 获取列数量
- metaData.getColumnLabel(int index) : 根据下角标获取列名(有别名获取别名,没有别名获取列名) 从1开始
2.6.PreparedStatement
预编译SQL语句并执行:预防SQL注入问题
2.6.1. Sql 注入:
用户名和密码拼接到sql语句中,拼接后的sql语句如下
select * from tb_user where username = 'admin' and password = ''or '1' = '1'
从上面语句可以看出条件 username = 'admin' and password = ''
不管是否满足,而 or
后面的 '1' = '1'
是始终满足的,最终条件是成立的,就可以正常的进行登陆了。
2.6.2. 执行
-
1、编写SQL语句结构,动态值部分使用 ? 替代
String sql = "select * from t_user where account= ? and password = ? ;"
-
2、创建 PrepareStatement 并设置SQL结构
PrepareStatement prepareStatement = connection.prepareStatement(sql);
-
3、给占位符单独赋值
- 参数1: index 占位符的位置 从左到右 从1开始
- 参数2: object 占位符的值
prepareStatement.setObject(1,account); prepareStatement.setObject(1,password);
-
4、发送SQL语句,获取返回值
- executeUpdate()
- return : int
- sql 语句为DDL、 DML时使用
- executeQuery()
- return: resultSet 结果集
- sql 语句为 DQL时使用
- executeUpdate()
3.扩展提升
3.1.主键回显
需求:插入数据后需要获取新增数据的主键用以做为关联表的外键
-
创建preparedStatement时需传入第二个参数
PreparedStatement preparedStatement = connection.prepareStatement( sql, Statement.RETURN_GENERATED_KEYS);
-
获取主键值结果集对象
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
3.2.批量插入
优化原理:只建立一次连接
- 逐条循环插入示例
String insertSql =
"INSERT INTO t_user(account,password,nickname) values(?,?,?)";
// 创建发送SQL语句的Statement对象
PreparedStatement statement = connection.prepareStatement(insertSql);
for (int i = 0; i < 1000; i++) {
// 给占位符赋值
statement.setObject(1, "acount" + i);
statement.setObject(2, "password" + i);
statement.setObject(3, "nickname" + i);
// 执行语句
statement.executeUpdate();
}
-
批量插入示例
String insertSql = "INSERT INTO t_user(account,password,nickname) values(?,?,?)"; // 创建发送SQL语句的Statement对象 PreparedStatement statement = connection.prepareStatement(insertSql); for (int i = 0; i < 1000; i++) { statement.setObject(1, "acount" + i); statement.setObject(2, "password" + i); statement.setObject(3, "nickname" + i); // 不执行,追加到values的后面 // 即:sql: values(?,?,?),(?,?,?),()... statement.addBatch(); } // 执行批量操作 statement.executeBatch();
- 需在获取连接时传入 url 添加可选参数 ?rewriteBatchStatement=ture
-
总结流程
- 创建连接时开启批量插入
- insert into 必须是 values 且语句不能以 ; 结束(追加需要)
- 不是执行每句,而是批量添加 addBatch()
- 追加完成后 执行 executeBatch()
3.3.事务
-
同一事务中的多个 sql语句执行不会立即更新数据库而是存入内存
- 全成功时才统一执行提交更新数据库
- 有一个失败则回滚事务
-
使用场景
- 设计多条修改数据库的语句都必须开启事务
- 转账(加钱-减钱)
- 批量修改
- 批量添加
- 设计多条修改数据库的语句都必须开启事务
-
事务类型
- 自动提交:mysql默认将每个语句单独开启事务,执行成功提交,失败回滚(不符合我们需求)
- 手动提交:手动开启事务,添加语句,手动提交和回滚
-
Sql开启事务方式
- 关闭当前连接的自动提交事务
- SET autocommit = off;
- Sql1
- Sql2
- Sql3
- Commit /Rollback :手动提交或者回滚
- 三个sql全在一个事务里
- 关闭当前连接的自动提交事务
-
一个事务的基本要求是在同一个连接上
4.Druid连接池
4.1.连接性能消耗问题分析
- 连接生命周期:创建 --> 使用 -->销毁
- 连接的创建和销毁时间远大于执行时间
4.2.连接池作用
连接池原理就是将创建好的连接放入容器中,使用就获取,用完放入,节省创建和销毁的时间
4.3.连接池的选择
- Javax.sql.DataSources
- 规定了连接池需实现连接方法和回收方法
- 因此选择的连接池不仅考虑性能外要看是否有其他扩展方法
4.4.Druid连接池的使用
需要导入jar包
4.4.1.硬编码(基本不用)
- 创建一个连接池对象
- 设置连接池参数
- 必须
- DriverClassName 注册驱动
- url
- user
- password
- 非必需
- InitialSze 初始化连接数量
- …
- 必须
- 获取连接
- 回收连接
4.4.2.软编码
-
创建druid.properties文件
# key 固定命名 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql:///dbName?key=value&key=value username=root password=1234 # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000
-
/** * 通过读取外部配置文件方法,实例化连接池对象 */ public class DruidDemo { public static void main(String[] args) throws Exception { //1. 加载配置文件 Properties prop = new Properties(); // src下的文件可以通过类加载器加载 // 当前类名.class.getClassLoader().getResourceAsStream("druid.properties") // 如果在src包下的别的包下按层级 即可 "xx/druid.properties" InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties") prop.load(ips) //2. 使用连接池的工具类的工厂模式创建连接池 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //3. 获取数据库连接 Connection Connection connection = dataSource.getConnection(); } }