jdbc学习笔记
尚硅谷https://www.bilibili.com/video/BV1sK411B71e?share_source=copy_web
概述
-
JDBC是连接Java和数据库的必要纽带
-
MyBatis、HIBERNATE等都是封装了JDBC的应用层框架
JDBC的实现
-
JDBC规范和接口(Java)
-
java语言提供规范(接口),规定了数据库的操作方法。
-
类库在java.sql,javax.sql包下
-
-
JDBC
-
java连接数据库技术的统称
-
-
第三方数据库厂商(数据库软件)
-
根据java的jdbc规范,完成集体的实现驱动(jar),实现代码可以不同,但方法都相同
-
总结
-
jdbc是Java连接数据库技术的统称
-
jdbc由两部分组成
-
Java提供的jdbc规范(接口)
-
各个数据库厂商的实现驱动jar包
-
-
jdbc技术是一门面向接口编程
核心API
导入jar包
-
创建lib文件
-
导入依赖jar包
-
jar包右键-添加为项目依赖
jdbc使用基本步骤
-
注册驱动(依赖的jar包)
-
获取连接(connection)
-
创建发送sql语句对象(statement对象 )
-
发送sql语句,并获取返回结果(statement发送sql语句到数据库,并获取返回 结果)
-
结果集解析(resultset结果对象)
-
资源关闭(connection释放,statement释放,resultset释放)
基于statement查询演示
public static void main(String[] args) throws SQLException {
//1.注册驱动
/**
* 注册驱动
* 依赖:驱动版本8.+ com.mysql.cj.jdbc.Driver
* 5.+ com.mysql.jdbc.Driver
*/
DriverManager.registerDriver(new Driver());
//2.获取连接
/**
* java程序要与数据库连接
* 要连接数据库,肯定需要调用某些方法,且需要填入数据库基本信息作为参数
* 数据库ip地址:127.0.0.1
* 数据库端口号:3306
* 账号:root
* 密码:123456
* 连接数据库的名称:atguigu
*/
/**
* 参数1:url
* jdbc:数据库厂商名://ip地址:port/数据库名
*/
//java.sql 接口=实现类
Connection connection=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC","root","123456");
//3.创建statement
Statement statement=connection.createStatement();
//4.发送sql语句,且获取返回结果
String sql="select * from t_user;";
ResultSet resultSet=statement.executeQuery(sql);
//5.进行结果集解析
//看看有没有数据,有就可以获取
while (resultSet.next()){
int id=resultSet.getInt("id");
String account=resultSet.getString("account");
String password=resultSet.getString("password");
String nickname=resultSet.getString("nickname");
System.out.println(id+"-"+account+"-"+password+"-"+nickname);
}
//6.关闭资源
resultSet.close();
statement.close();
connection.close();
}
基于statement执行流程
驱动注册
-
使用DriverManager.registerDriver()注册驱动,会注册两次
-
DriverManager.registerDriver() 方法本身会注册一次
-
Driver.static{DriverManager.registerDriver()}静态代码块,也会注册一次
-
-
想只触发一次:只触发静态代码块
-
静态代码块
-
类加载时,触发
-
加载[class文件->jvm虚拟机的class对象]
-
连接[验证->准备(静态变量的默认值)->解析(触发静态代码块)]
-
初始化
-
-
触发类加载
-
new关键字
-
调用静态方法
-
调用静态属性
-
接口 default默认实现
-
反射
-
子类触发弗雷
-
程序的人口mian方法
-
-
连接
-
getConnection是一个重载方法,允许开发者以不同的形式传入数据库的核心参数
-
核心属性
-
数据库所在主机的ip地址:localhost/127.0.0.1
-
数据库所在主机的端口号:3306
-
具体要连接的库
-
连接的账号
-
连接的密码
-
-
三个参数
-
String url
-
数据库软件所在的信息,连接的库,及其他可选信息
-
语法:jdbc:数据库管理软件名称://ip地址或主机名:port端口号/数据库名?key=value&key=value可选信息
-
-
String username
-
String password
-
-
二个参数
-
String url 与三个参数的url作用相同
-
Properties info:存储账号和密码
-
-
一个参数
-
String url数据库软件所在的信息,连接的库,及其他可选信息(账号、密码) jdbc:mysql://127.0.0.1:3306/atguigudb?user=root&password=123456
-
-
创建发送sql语句的statement对象
-
可以发送sql语句到数据库,并且获取返回结果
发送sql语句
-
编写sql语句
-
发送sql语句
-
SQL分类:DDL(创建,修改,删除) DML(插入,修改,删除) DQL(查询) DCL(权限控制) TPL(事务控制)
-
参数:sql 非DQL
-
返回:int
-
情况1:DML 返回影响的行数
-
情况2:非DML 返回0
-
row=executeUpdate(sql)
-
-
参数:sql DQL
-
返回:resultSet 结果封装对象
-
ResultSet resultSet=executeQuery(sql);
-
-
查询结果集解析
-
Java是面向对象的思维,所以将查询结果封装成了ResultSet对象,内部一定是有行和列的
-
resultSet->逐行获取数据,行->行内的列的信息
-
有一指针指向数据行前,next()会使指针向后移动一位,搭配while使用,指向第一行,以此类推,直至指向最后一行
-
获取列的数据(获取指针指向的行的列的数据)
-
resultSet.get类型(String columnLabel | int columnIndex)
-
columnLabel:列名 如果 有别名 写别名
-
columnIndex:列的下标 从1开始
-
-
-
关闭资源
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//获取输入信息
Scanner scanner=new Scanner(System.in);
System.out.println("请输入账号:");
String accout=scanner.nextLine();
System.out.println("请输入密码:");
String password=scanner.nextLine();
//方案一
//DriverManager.registerDriver(new Driver());
//方案二 new关键字
//new Driver();
//方案三 反射 触发类加载,触发静态代码块
Class.forName("com.mysql.cj.jdbc.Driver");//可以提取作为参数的字符串到外部的配置文件,可以在不改变代码的情况下,完成数据库驱动的切换
Connection connection=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC","root","123456");
//创建发送sql语句的statement对象
//可以发送sql语句到数据库,并且获取返回结果
Statement statement=connection.createStatement();
//发送sql语句
String sql="select * from t_user where account='"+accout+"' and password='"+password+"';";
ResultSet resultSet=statement.executeQuery(sql);
//查询结果集解析 resultSet
if (resultSet.next()){
System.out.println("cg");
}else
System.out.println("sb");
//6.关闭资源
resultSet.close();
statement.close();
connection.close();
}
基于preparedStatement优化
statement存在的问题
-
可能发生注入攻击
-
SQL语句需要字符串拼接,比较麻烦
-
只能拼接字符类型,其他数据库类型无法处理
statement和preparedStatement
-
statement
-
创建statement
-
拼接sql语句
-
发送sql语句,且获取返回结果集
-
statement.executeQuery(sql)/executeUpdate(sql);
-
-
preparedStatement
-
编写sql语句结果 不包含动态值的部分,动态值部分用占位符?替代(注意:?只能替代动态值部分)
-
创建preparestatement,并且传入动态值
-
动态值给占位符?赋值,单独赋值即可
-
发送sql语句,且获取返回结果集
-
preparedStatement.executeQuery()/executeUpdate();
-
不需要sql,因为它已经知道sql语句,语句动态值
-
-
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//获取输入信息
Scanner scanner=new Scanner(System.in);
System.out.println("请输入账号:");
String accout=scanner.nextLine();
System.out.println("请输入密码:");
String password=scanner.nextLine();
//ps的数据库流程
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC","root","123456");
//3.编写sql语句
String sql="select * from t_user where account = ? and password = ? ;";
//4.创建预编译statement并且设置sql语句
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//5.单独的占位符赋值
/**
* 参数1:index 占位符的位置,从1开始
* 参数2:Object 占位符的值 可以设置任何类型的值
*/
preparedStatement.setObject(1,accout);
preparedStatement.setObject(2,password);
//6.发送sql语句,并获取返回结果
ResultSet resultSet=preparedStatement.executeQuery();
//7.结果集解析
if (resultSet.next()){
System.out.println("登录成功");
}else
System.out.println("登录失败");
//8.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
基于preparedStatement的CURD
Insert
Update
Delete
Select
/**
* 查询所有用户,且封装到List<Map> list集合中
* 实现思路
* 遍历数据,一行对应一个map,获取一行的列名和对应属性
* 将map装到一个集合中
*/
获取结果集列的信息:
装的当前结果集列的信息对象(可以获取列的名称,列的数量) ResultSetMetaData metaData=resultSet.getMetaData();
int columnCount=metaData.getColumnCount();
//获取列名 //getColumnLabel有别名获取别名,没有就获取名称 //getColumnName只会获取名称 String columnLabel=metaData.getColumnLabel(i);
JDBC拓展提升
自增长主键回显
-
创建preparedStatement时,告知携带回数据库自增长的主键
-
获取装主键的结果集对象,一行一列
批量插入
-
路径后添加rewriteBatchedStatements=true 允许批量插入
-
insert into values【必须带s】,语句不能添加;结束
-
不是执行,是添加addBatch()
-
遍历添加结束后,统一批量执行executeBatch()
数据库事务实现
事务的概念
-
数据库事务就是SQL语句执行的缓存机制,不会单条执行完就更新数据库数据,而是最终根据缓存内的多条语句执行结果统一判定
-
一个事务内所有sql语句都成功代表事务成功,可以触发commit提交事务来结束事务以及更新数据
-
一个事务内任意一条sql语句失败代表事务失败,触发rollback回滚事务
事务的特性
-
原子性:事务是一个不可分割的单位,内部的操作要么都成功,要么都失败
-
一致性:必须使数据库从一个一致性状态变换到另一个一致性状态
-
隔离性:一个事务执行不能被其他事务干扰
-
持久性:事务一旦提交,它对数据库的数据的改变就是永久性的
事务类型
-
自动提交:每条sql语句都是一个事务,执行成功自动提交,失败自动回滚
-
手动提交:手动开启事务,执行成功手动提交,失败手动回滚
操作流程
-
事务添加是在业务方法中
-
利用try catch开启事务、提交事务、事务回滚
-
将connection传入dao层。dao只负责使用,不要close
DAO层
/**
* 加钱的数据库操作
* @param account
* @param money
*/
public void add(String account,int money,Connection connection) throws Exception {
//1.注册驱动
//Class.forName("com.mysql.cj.jdbc.Driver");
//2.创建连接
//Connection connection =DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC","root","123456");
//3.编写sql语句
String sql="update t_bank set money = money+? where account=?;";
//4.创建preparedStatement,传入sql
PreparedStatement statement=connection.prepareStatement(sql);
//5.占位符赋值
statement.setObject(1,money);
statement.setObject(2,account);
//6.发送sql语句
statement.executeUpdate();
//7.关闭资源
statement.close();
//connection.close();
System.out.println("加钱成功");
}
/**
* 减钱的数据库操作
* @param account
* @param money
*/
public void sub(String account,int money,Connection connection) throws Exception{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.创建连接
//Connection connection =DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC","root","123456");
//3.编写sql语句
String sql="update t_bank set money = money - ? where account=?;";
//4.创建preparedStatement,传入sql
PreparedStatement statement=connection.prepareStatement(sql);
//5.占位符赋值
statement.setObject(1,money);
statement.setObject(2,account);
//6.发送sql语句
statement.executeUpdate();
//7.关闭资源
statement.close();
//connection.close();
System.out.println("减钱成功");
}
Service层
一次connection,相当于一个事务
连接池
节约了创建和销毁连接的性能消耗,提升了响应时间
连接池的作用
-
不使用连接池,每次通过DriverManager获取新连接,使用完后,直接断开,利用率太低,极其浪费。
-
每次通过DriverManager获取新连接,对于数据库服务器来说,压力太大。服务器和Java程序对连接数也无法控制,容易导致服务器崩溃。
管理连接
-
建立连接池,池中可以容纳一定数量的连接对象,用户要连接时,可以从池中获取连接对象,不需要新建。使用完毕后,将连接对象放回池中,可以让其他用户使用。
-
可以提高连接使用率。当池中现有连接都用完后,连接池可以向服务器申请新的连接放到池中
-
当池中连接到达“最大连接数”,则不能申请新连接,如有没有拿到连接的用户,则只能等待
javax.sql.DataSource接口
-
规范了连接池获取连接的方法
-
规范了连接池回收连接的方法
-
市面上的连接池都是对接口的实现
硬编码方式
-
直接使用代码设置连接池连接参数方式
-
创建druid对象
-
设置连接池参数【必须|非必须】
-
获取连接(通用)
-
回收连接(通用)
/**
* 硬编码
*/
public void testHard() throws SQLException {
//连接池对象
DruidDataSource dataSource=new DruidDataSource();
//设置参数
//必须 连接数据库驱动类,url,username/password)
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//非必须 初始化连接数量,最大连接数量...
dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大连接数量
//获取连接
Connection connection=dataSource.getConnection();
//回收连接
connection.close();//连接池提供的连接,close就是回收连接
}
软编码
-
配置文件
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://127.0.0.1:3306/atguigudb?serverTimezone=UTC
-
实例化连接池(Java)
/**
* 通过读取外部配置文件的方式,实例化druid连接池
*/
封装工具类
ThreadLocal可以为一个线程存储共享变量
-
工具类
/**
* 内部包含一个连接池对象
* 对外提供获取连接和回收连接的方法
*
* 工具类的方法推荐静态的,方便外部调用
* 实现
* 属性:连接池对象【实例化一次】
* 单例模式
*
* static{
* 全局调用一次
* }
*
* TODO:
* 利用线程本地变量,存储连接信息。确保一个线程的多个方法可以获取同一个连接connection
* 优势:事务操作时,service和dao属于同一线程,不用再传递参数了
* 都可以调用getConnection自动获取相同的连接
*/
public class JdbcUtilsV2 {
private static DataSource dataSource=null;//连接池对象
private static ThreadLocal<Connection> tl=new ThreadLocal<>();
static {
//初始化连接池对象
Properties properties=new Properties();
InputStream ips =JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(ips);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource= DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//对外提供连接
public static Connection getConnection() throws SQLException {
//线程本地变量是否存在
Connection connection=tl.get();
//第一次执行
if (connection==null){
connection=dataSource.getConnection();
tl.set(connection);
}
return connection;
}
//回收连接
public static void freeConnection() throws SQLException {
Connection connection=tl.get();
if (connection!=null){
tl.remove();//清空线程本地变量
connection.setAutoCommit(true);//事务状态回到false
connection.close();//回收到连接池
}
}
}
-
DAO层
public class BankDao {
/**
* 加钱的数据库操作
* @param account
* @param money
*/
public void add(String account,int money) throws Exception {
Connection connection=JdbcUtilsV2.getConnection();
//3.编写sql语句
String sql="update t_bank set money = money+? where account=?;";
//4.创建preparedStatement,传入sql
PreparedStatement statement=connection.prepareStatement(sql);
//5.占位符赋值
statement.setObject(1,money);
statement.setObject(2,account);
//6.发送sql语句
statement.executeUpdate();
//7.关闭资源
statement.close();
//connection.close();
System.out.println("加钱成功");
}
/**
* 减钱的数据库操作
* @param account
* @param money
*/
public void sub(String account,int money) throws Exception{
Connection connection =JdbcUtilsV2.getConnection();
//3.编写sql语句
String sql="update t_bank set money = money - ? where account=?;";
//4.创建preparedStatement,传入sql
PreparedStatement statement=connection.prepareStatement(sql);
//5.占位符赋值
statement.setObject(1,money);
statement.setObject(2,account);
//6.发送sql语句
statement.executeUpdate();
//7.关闭资源
statement.close();
//connection.close();
System.out.println("减钱成功");
}
}
-
Service
/**
* 银行卡业务方法,调用dao方法
*/
public class BankService {
BaseDao
/**
* 封装数据库重复代码
* TODO:
* 封装两个方法
* 一个简化非DQL
* 一个简化DQL
*/
public abstract class BaseDao {
/**
* 封装简化非DQL语句
* @param sql 带占位符的SQL语句
* @param param 占位符的值
* @return 影响的行数
*/
public int executeUpdate(String sql,Object... param) throws SQLException {
Connection connection=JdbcUtilsV2.getConnection();
//4.创建preparedStatement,传入sql
PreparedStatement statement=connection.prepareStatement(sql);
//5.占位符赋值
//可变参数可以当作数组使用
for (int i = 1; i <= param.length; i++) {
statement.setObject(i,param[i-1]);
}
//6.发送sql语句
int rows=statement.executeUpdate();
//7.关闭资源
statement.close();
//是否回收连接,需考虑是不是事务
if (connection.getAutoCommit()){
//没有开启事务
JdbcUtilsV2.freeConnection();
}//connection.getAutoCommit(false)//开启了事务,不需要管连接,业务层来处理
return rows;
}
/**
* DQL语句
* 返回值无法使用List<Map>
* map 没有数据校验机制
* map不支持反射
*
* 数据库数据->java的实体类
* 表中一行数据->Java实体类一个对象->多行->List<Java实体类>
* public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params);
* <T>:声明一个不确定类型的泛型
* Class<T>好处
* 1.确定泛型
* 2.要使用发射激素属性赋值
*/
/**
* 将查询结果封装到实体类集合
* @param clazz 要接收值的实体类集合的模板对象
* @param sql 查询语句,要求列名或别名对于实体类对象
* @param params 占位符的值
* @param <T> 声明的结果的类型
* @return 查询的实体类集合
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Connection connection=JdbcUtilsV2.getConnection();
//4.创建preparedStatement,且传入sql语句
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//5.占位符赋值
//6.发送sql语句
if (params!=null&¶ms.length!=0){
for (int i=1;i<=params.length;i++){
preparedStatement.setObject(i,params[i-1]);
}
}
ResultSet resultSet