JDBC
前言
🦁 entity+Dao 模型——非常重要
Dao——表名Dao——对某一个表进行增删改查操作——之后会简化为接口
entity——与数据库中的实体表对应(表名、属性、方法)
🐅IDEA自带Database
JDBC基础
(1) JDBC原理
-
JDBC(Java Database Connectivity):Java连接数据库的技术。所有Java程序都需要用到JDBC,主流框架对JDBC进行了封装,实际上底层一定用到了JDBC。
JDBC API主要位于JDK中的java.sql包中,之后扩展的内容位于javax.sql包中。(x代表拓展,原生+拓展时JDBC的所有内容) -
JDBC处于Java Application(Java应用程序) 与 各个(数据库)Oracle、MySQL、SQL Server之间,即JDBC API + JDBC Driver Manager+ Oracle JDBC Driver(驱动程序) ……
其中驱动程序由不同数据库的厂商自己根据JDBC的规范开发的 —— ∈ 面向接口编程
JDBC Driver Manager 定义规范标准,生产不同的驱动时不确定将连接什么数据库 —— 反射机制
(2) JDBC 在IDEA配置
-
mysql 5.7 用8.0版本的驱动可以,5.1版本也可以,5.5、5.6、5.7都不可以。
这里我的MySQL5.7,JDBC5.1.38。
打开IDEA ,新建项目,在项目下新建文件夹lib(lib文件夹通常用于存放.jar包)将下载好的JDBC复制,paste到lib文件夹下。
(External Libraries中存放的是项目已经引用的,此时该项目中只引用由jdk13.——版本或项目不同不一样) -
将JDBC引用入项目
右键复制进lib内的文件,选择Add as Library
将jar放入改项目,可以选择放入哪里(全局、该项目、模块)
=> 成功后可以打开
-
补全
当做好Java应用程序、数据库表、完成JDBC Driver引用,则只需要补全 JDBC API和JDBC Driver Marager即成功连接应用程序和数据库。
4. 其余部分
①Java应用程序
② 数据库
(3)预编译
1. SQL注入
JDBC代码中的查询练习改成如下代码:
List<Student> studentList = studentDao.query("' or 1=1 or '");
可以发现sql语句和查询结果为下图所示:
因为JDBC查询使用拼接字符串的形式组织sql语句,这样当用户的名字为’ or 1=1 or '时,组成恒成立的sql语句,就可以查询出所有信息。这就是SQL注入,使用预编译可以避免。
2.预编译概念
① 预编译的原理——先将sql语句模板发送给数据库进行编译成函数,然后再将数据当参数传递给数据库,这样数据中的关键字如or 1=1就不会被理解为sql语句的一部分,就避免了SQL注入。
② 预编译的优点——使用预编译还可以提高sql的执行效率。相同的sql语句在数据库只会编译一次。但是可以多次调用执行。
- Statement —— 字符串拼接完成SQL语句,不安全
- PreparedStatement 是 Statement 的一个子类。
- 预编译 先编译,后传入参数
3 .预编译的使用
1. 使用PreparedStatement替代Statement。参数使用?占位符。 2. 创建PreparedStatement对象时将sql语句发送到数据库。 3. 使用setter方法为参数赋值。- 核心代码:
preparedStatement = conn.prepareStatement("insert into student (stu_name, stu_age) values (?,?)");
preparedStatement.setString(1,"david");
preparedStatement.setInt(2,20);
ret = preparedStatement.executeUpdate();
1>preparedStatement = conn.prepareStatement(“insert into student (stu_name, stu_age) values (?,?)”);
= > conn先执行prepareStatement,把SQL语句insert into student (stu_name, stu_age) values (?,?) 发送到服务器preparedStatement
2> preparedStatement.setString(1,“david”);
preparedStatement.setInt(2,20);
= > 设置两个值
3>ret = preparedStatement.executeUpdate();
= > 调用executeUpdate()时,把值发送到数据库中
此时把SQL语句发送到数据库,数据库对SQL语句进行预编译。遇到?时,将这里看作方法的参数,把值中的参数(1、2)替换到?处运行。
注意:在给参数赋值的时候,下标是从1开始。
(4)JDBC连接数据库的六个步骤
1.步骤内容
- 注册驱动 (仅仅做一次)
- 建立连接(Connection)
- 创建运行SQL的语句(Statement)
- 运行语句
- 处理运行结果(ResultSet)
- 释放资源
Statement可以是DML、DQL。 当Statement是DML时,返回影响条数;当Statement是DQL时,返回ResultSet。 ResultSet依存于connection连接。
当connection连接关闭后,Statement与ResultSet都要被销毁。临时变量
所以当取得ResultSet不可以直接被使用,需要转存到ArrayList中。
2. ❤ 操作 ❤ 👶
1>. 注册驱动 (仅仅做一次)
反射
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);// **反射**
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2>. 建立连接(Connection)
①
高版本需要在url后加参数,没有参数时间、文本会出现问题(时间不正确、乱码…)
②
String driver ="";//不同数据库、相同数据库不同版本的driver不同
String url="";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="";//自己数据库的密码
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
Connection connection=DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
——连接数据库Scott用户
3>. 创建运行S QL的语句(Statement)
4>. 运行语句
String driver ="";//不同数据库、相同数据库不同版本的driver不同
String url="";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="123456";//自己数据库的密码
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
Connection connection=DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
//创建运行SQL的语句(Statement)
PreparedStatement statement=connection.prepareStatement("insert into student (student_name,student_no,sex) values(?,?,?)");
statement.setObject(1 ,"king");
statement.setObject(2 ,"2022228");
statement.setObject(3 ,"男");
//运行语句
int ret = statement.executeUpdate();
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
注意:
executeQuary——DQL查询操作,返回结果集resultSet ;executeUpdate—— DML增删改,返回resultInt,影响条数
5>. 处理运行结果(ResultSet)
注意: 处理运行结果(ResultSet)——只有查询操作时有,进行增删改操作时没有这一步6>. 释放资源
①需要关闭的资源:connection、statement、(resultset)
②在try catch中不适宜关闭,应finally中关闭资源。
因为connection、statement的作用范围为: 蓝色区域
无法在范围外的区域关闭。
更改作用范围:
③ 释放
……
}finally {
//释放资源 (connection、statement、resultset)
try {
//对象不存在关不上,需要加try/catch
connection.close(); //?
statement.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
细节修改
① 多重catch最后一定要加一个父类
② 返回结果return
int ret=-1;
因为0和1为正常的返回结果
结果
import java.sql.*;
public class Text {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String driver ="com.mysql.jdbc.Driver";
//不同数据库、相同数据库不同版本的driver不同
String url="jdbc:mysql://localhost:3306/scott?useSSL=false";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="123456";//自己数据库的密码
Connection connection = null;
PreparedStatement statement = null;
int ret=-1;
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
connection=DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
//创建运行SQL的语句(Statement)
statement=connection.prepareStatement("insert into student (student_name,student_no,sex) values(?,?,?)");
statement.setObject(1 ,"king");
statement.setObject(2 ,"2022228");
statement.setObject(3 ,"男");
//运行语句
ret = statement.executeUpdate();
System.out.println(ret);
//处理运行结果(ResultSet)——只有查询时有,进行增删改操作时没有这一步
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (Exception e) { //多重catch最后一定要加一个父类
e.printStackTrace();
}finally {
//释放资源 (connection、statement、resultset)
try {//对象不存在关不上,需要加try/catch
connection.close();
statement.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
return ret;
}
}
补充
可能出现字符乱码的情况,如图
3. 📕 Dao+entity📕 🧑
1>.package 构建。包括插入、删除、修改以及查询方法
1>>插入🚩
public int insert(){//插入方法
String driver ="com.mysql.jdbc.Driver";
//不同数据库、相同数据库不同版本的driver不同
String url="jdbc:mysql://localhost:3306/scott?useSSL=false";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="123456";//自己数据库的密码
Connection connection = null;
PreparedStatement statement = null;
int ret =-1;
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
connection= DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
//创建运行SQL的语句(Statement)
statement=connection.prepareStatement("insert into student (student_name,student_no,sex) values(?,?,?)");
statement.setObject(1 ,"lily");
statement.setObject(2 ,"202205");
statement.setObject(3 ,"女");
//运行语句
ret = statement.executeUpdate();
System.out.println(ret);
//处理运行结果(ResultSet)——只有查询时有,进行增删改操作时没有这一步
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (Exception e) { //多重catch,最后要加一个父类
e.printStackTrace();
}finally {
//释放资源 (connection、statement、resultset)
try {//对象不存在关不上,需要加try/catch
connection.close();
statement.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
return ret;
}
2>>修改
public int update(){ //修改方法
String driver ="com.mysql.jdbc.Driver";
//不同数据库、相同数据库不同版本的driver不同
String url="jdbc:mysql://localhost:3306/scott?useSSL=false";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="123456";//自己数据库的密码
Connection connection = null;
PreparedStatement statement = null;
int ret =-1;
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
connection= DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
//创建运行SQL的语句(Statement)
statement=connection.prepareStatement("update student set address = ? where id = ?");
statement.setObject(1 ,"北京市");
statement.setObject(2 ,"4");
//运行语句
ret = statement.executeUpdate();
System.out.println(ret);
//处理运行结果(ResultSet)——只有查询时有,进行增删改操作时没有这一步
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (Exception e) { //多重catch,最后要加一个父类
e.printStackTrace();
}finally {
//释放资源 (connection、statement、resultset)
try {//对象不存在关不上,需要加try/catch
connection.close();
statement.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
return ret;
}
3>>删除 略
4>> 查询🚩
- 存放空间
① 查询的返回值类型为 List 结果集,存放着一个模型
entiey(类——实体 属性——列 类与类之间的关系——实体之间的关系 对应)
生成表和对应的getter&setter方法(与数据库中一一对应)
toString()方法重写,打印数据而不是内存地址
【见测试调用部分】
② 封装数据
- 查询代码
① column label——列名;column index——列的下标
②核心代码
每次结果集有记录,将记录实例化对象,把记录中的对应项添加到类的属性中(列与属性时对应的)——封装完成代表一个学生
将学生放入结果集,最终结果集是查询结果
public List<Student> queryAllStudent(){//查询方法
String driver ="com.mysql.jdbc.Driver";
//不同数据库、相同数据库不同版本的driver不同
String url="jdbc:mysql://localhost:3306/scott?useSSL=false";//连接what数据库
String name="root";//登录用户,一般是hoot
String pass="123456";//自己数据库的密码
Connection connection = null;
PreparedStatement statement = null;
ResultSet rs =null;
List<Student> list = new ArrayList<>();//list加泛型
try {
//注册驱动 (仅仅做一次)
Class.forName(driver);//反射
//建立连接(Connection)
connection= DriverManager.getConnection (url,name,pass);//建立连接需要四个参数
//创建运行SQL的语句(Statement)
statement=connection.prepareStatement("select * from student");
//运行语句
rs = statement.executeQuery();
//处理运行结果(ResultSet)——只有查询时有,进行增删改操作时没有这一步
while (rs.next()){//不知道记录条数,不知循环次数——while。结果集的指针可以向下移动一步时
Student student = new Student();//实例化对象
//把rs中的每一个值都放入Student中
student.setId(rs.getInt(1));
student.setStudent_name(rs.getString("student_name"));
list.add(student);
}
}catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (Exception e) { //多重catch,最后要加一个父类
e.printStackTrace();
}finally {
//释放资源 (connection、statement、resultset)
try {//对象不存在关不上,需要加try/catch
connection.close();
statement.close();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}
return list;
}
2>. 测试调用
forEach
此时打印出来的是对象的内存地址,要打印对象本身——重写toString() 方法
Text.java中测试
import dao.StudentDao;
public class Text {
public static void main(String[] args) {
StudentDao studentDao = new StudentDao();
studentDao.queryAllStudent().forEach(System.out::println);
}
}
结果