JDBC的概述
JDBC----------------------
上面是需要的包
可以理解为驱动包确定是那种数据库,确定后,就由对应的管理数据库的类来提供实现类(设置了指定数据库),用接口获得
这样就确定了连接哪个数据库(运行这代码后,可以理解为就是连接,底层实现类会通过参数来连接)
这时,我们就可以通过该接口,得到一个语句对象,可以对数据库使用语句
由于是用来操作语句的,对数据库使用语句,相当于他帮你在sql里写上分号的内容
所以分号里面的sql代码也必须符合sql的格式,若不符合,则会报错
如字符串不加单引号
该对象使用语句,就相当于在对应数据库里使用该语句,返回具体内容(包括影响多少行,和结果集等等
数据准备
我们可以在idea里面,创建一个空的项目
基本上会到这个界面
上面的External Libraries是存放jar包的地方
点击File的Project Structure…项目结构
Project:可以设置jdk
Modules:java模块的地方,可以对该模块添加jar包
Libraries:仓库,可以指定java的仓库,加号添加,如jar包的所在目录,这样就可以不用在模块那里一个一个添加了
package com.lagou.jdbc01;
import com.mysql.jdbc.NonRegisteringDriver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo01 {
public static void main(String[] args) throws Exception {
//1.注册驱动 (可以省略,jdbc3开始,因为可以根据你的连接信息就可以知道是哪种数据库)
// 单独的这个,类一定会加载,否则获得不了Class对象,因为Class对象在该类的class文件里
// 只有加载该对象的class文件,就是加载类,才可以获得对应Class对象
//Class.forName(数据库驱动实现类)
//加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供 "com.mysql.jdbc.Driver"
Class.forName("com.mysql.jdbc.Driver");
//底层代码
//2.获取连接 connection连接对象
//utf8的作用,是java的数据到mysql的数据的编码操作
//JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔
//第一部分是协议 jdbc,这是固定的
//第二部分是子协议,就是数据库名称,连接mysql数据库,第二部分当然是mysql了
//第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求
//mysql的第三部分分别由数据库服务器的IP地址(localhost)、端口号(3306)
//以及要使用的 数据库名称 组成。
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
//Connection getConnection(String url, String user, String password)
//通过连接字符串和用户名,密码来获取数据库连接对象
//user,登录用户名
//password,登录密码
//url,mySql URL的格式 jdbc:mysql://localhost:3306/db4
Connection con = DriverManager.getConnection(url, "root", "123456");
//打印 连接对象 com.mysql.jdbc.JDBC4Connection@7c729a55
//System.out.println(con);
//3.获取语句执行平台 Statement
//Statement createStatement(),创建 SQL语句执行对象
Statement statement = con.createStatement();
//3.1 通过 statement对象的 executeUpdate 方法 创建一张表
// sql语句后面的分号可以不写
String sql = "create table test(id int,name varchar(20),age int);";
//int executeUpdate(String sql),执行insert update delete等语句,其他语句也可以
//如创建表create等,基本上只要不是查询语句就可以
//返回int类型,代表受影响的行数
//ResultSet executeQuery(String sql),执行select语句, 返回ResultSet结果集对象
int i = statement.executeUpdate(sql);
// 返回值是int类型 表示受影响的行数,像创建等,基本就是0,因为没有对一行操作
// 而增删改都会对行操作,所以基本不是0
System.out.println(i);
//4.关闭流
statement.close();
con.close();
}
}
类的加载就是将类的.class文件中的二进制数据读进内存之中,将其放进JVM运行时内存的方法区中
package com.lagou.jdbc01;
import java.sql.*;
public class JDBCDemo02 {
public static void main(String[] args) throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db",
"root", "123456");
//3.获取语句执行平台对象
Statement statement = con.createStatement();
//4.执行查询操作 使用executeQuery()
String sql = "SELECT * FROM jdbc_user;";
//resultSet 是结果集对象
ResultSet resultSet = statement.executeQuery(sql);
//通过while循环 遍历获取 resultSet中的数据
//与迭代器不同,迭代器是先获取值,在下标后移,即先判断有值后,取出了,然后后移,先取后移
// 而这个结果集
// 是先后移再获取值,即先判断下一个是否有值后,再后移,然后取值,后移再取
//ResultSet executeQuery(String sql),执行select语句, 返回ResultSet结果集对象
while(resultSet.next()){
//resultSet的get方式有很多,如
// getInt,返回int
// getString,返回String
// getDate,返回Date
// 或者其他getxxx方式
//且参数可以是列名,如getxxx("id"),或者列号
// 假设表里有 id name age等字段,从前往后,那么列号就是1,2,3
// 列号的方式,如getxxx(1);
//xxx表示上述的很多种类型
//注意:有些类型只能是特定的,否则有可能会出现错误或者结果错误
//如使用getInt时,表数据,必须为整型类型(只包含数字的字符串也可)
// 若为日期类型,那么只取第一个,如1999-01-23,取1999,这是第一个
//而getString类型,任意的类型都可以
//获取id
int id = resultSet.getInt("id");
//由于mysql大小写可以随便,那么在这里id也可以写成ID
//对于很多知识,若没有说明大小写不可以随便写的,基本上都可以大小写随便写
//获取姓名
String username = resultSet.getString("username");
//获取密码
String password = resultSet.getString("password");
//获取生日
Date birthday = resultSet.getDate("birthday");
System.out.println(id+" : " + username+" : " + password + " : " + birthday);
}
//5.关闭流操作
resultSet.close();
statement.close();
con.close();
}
}
//处理结果集对象 resultSet
// boolean next = resultSet.next(); //判断是否有下一条数据
// System.out.println(next);
//获取id
// int id = resultSet.getInt("id");
// System.out.println("通过列名的方式获取 "+ id);
// int id = resultSet.getInt(1);
// System.out.println("通过列号获取id " + id);
注意:对于关闭操作,必须重视,虽然有些不关闭,不会造成错误,但有些会,要养成关闭资源的习惯
对于两资源连接的,通常会占有地方,即必须关闭资源,否则下次运行时
可能会报错(如没有自动关闭,但通常都会自动关闭),造成阻塞,形成错误
两资源连接:不在同一区域,即不同时在代码里的内存,如java和mysql之间的联系
在这里强调一下,对于IO流中用缓冲流操作时,没有使用flush()方法或者没有关闭close()方法(默认刷新,即flush()方法)
解释:flush()刷新缓冲区,输出流,因为java默认的缓冲区是8kb,就是说只有每填满8kb才会提交一次
当少于这个值时就不会提交(这里大多数的情况所在)
package com.lagou.jdbc01;
import java.sql.*;
public class JDBCDemo03 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.注册驱动 省略
//2.获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db4",
"root", "123456");
//3.获取语句执行对象
statement = connection.createStatement();
//4.执行SQL
String sql = "select * from jdbc_user";
resultSet = statement.executeQuery(sql);
//5.处理结果集对象
} catch (SQLException e) {
e.printStackTrace();
}finally {
//finally 中的代码始终会执行,而没有finally的话,出现异常,后面代码不执行
// 所以finally用来操作关闭资源最合适
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
补充:在IO之间的操作时,有可能读取的数,会让输出时的编码解释错误,特别是字符流写入图片,视频等等
package com.lagou.utils;
import java.sql.*;
public class JDBCUtils {
//1. 将连接信息定义为 字符串常量
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
//基本上数据流通都是字节,设置UTF-8是为了覆盖很多编码
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
public static final String USER = "root";
public static final String PASSWORD = "123456";
//2.静态代码块
static{
try {
//1.注册驱动
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.获取连接的 静态方法
public static Connection getConnection(){
try {
//获取连接对象 并返回
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
//4.关闭资源的方法
public static void close(Connection con, Statement statement){
if(con != null && statement != null){
try {
statement.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement statement, ResultSet resultSet){
if(con != null && statement != null){
try {
resultSet.close();
statement.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
当你运行代码时,并不会立即加载代码里使用到的类,只有当你需要这个类时,才会加载
如JVM在执行某段代码时,遇到了class A,然而此时内存中并没有class A的相关信息
于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程
注意:在sql里只所以不用分号的另外一种原因是,在java里使用分号就会变成断句,即断开,有可能会报错,而单引号不会
而对于时间类型的字段,如date,他只会识别整数,即20201020,那么插入时,结果是2020-10-20
且要符合日历的操作,如16月,不可,即报错,如2020-*10–*20,他还是2020-10-20,因为只识别整数,且只识别八位整数
多一位或者少一位都不可以,即报错
package com.lagou.jdbc02;
import com.lagou.utils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TestDML {
@Test //这个是用来测试的注解,可以直接运行,而不使用main方法
//对一些操作可以很方便的进行测试,而不用写在main方法里,使得全部运行
//即他可以理解为实现main方法的局部测试
public void testInsert() throws SQLException {
//1.通过JDBCUtils工具类 获取连接
Connection con = JDBCUtils.getConnection();
//2.获取Statement对象
Statement statement = con.createStatement();
//2.1 编写SQL
String sql = "insert into jdbc_user values(null,'张百万','123','2020/11/11')";
//2.2 执行SQL
int i = statement.executeUpdate(sql);
System.out.println(i);
//3.关闭流
JDBCUtils.close(con,statement);
}
@Test
public void testUpdate() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "update jdbc_user set username = '刘能' where id = 1";
statement.executeUpdate(sql);
JDBCUtils.close(connection,statement);
}
@Test
public void testDelete() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "delete from jdbc_user where id in(1,2)";
statement.executeUpdate(sql);
JDBCUtils.close(connection,statement);
}
}
package com.lagou.jdbc02;
import com.lagou.utils.JDBCUtils;
import java.sql.*;
public class TestDQL {
// 查询姓名为张百万的一条记录
public static void main(String[] args) throws SQLException {
//1.获取连接
Connection connection = JDBCUtils.getConnection();
//2.创建Statement对象
Statement statement = connection.createStatement();
//3. 编写SQL
String sql = "select * from jdbc_user where username = '张百万'";
ResultSet resultSet = statement.executeQuery(sql);
//4.处理结果集
while(resultSet.next()){
// 通过列名的方式获取
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
Date birthday = resultSet.getDate("birthday");
//若getDate用getInt表示的话,只会取整数的有效,到无效不取,且停止取
//如1998-12-24,取1998,到-无效不取,且停止取
System.out.println(id + " : " + username + " : " + password + " : " + birthday );
}
//5.释放资源
JDBCUtils.close(connection,statement,resultSet);
}
}
注意:sql注入,就是让原来查询时的false变为true的,通常情况下,由于sql语句里用单引号来连接
那么可以利用这些单引号来操作,就如双引号类似,如
//""
//在上述双引号里,输入数据使得变成两个双引号
//那么输入123" + "456
//结果为"123" + "456",就是"(123" + "456)",括号里就是你输入的,这是利用符合的连接
//但是在java里打印时,不会有双引号的连接,会转义,即会打印双引号出去,如打印123" + "456,而不是123456
//而sql就不会
//所以sql注入,就是利用符号的连接
//id = ''
//如在上述单引号里,输入数据使得多出来一个单引号
//那么输入12' or '1=1
//结果为id = '12' or '1=1',就是id = '(12' or '1=1)',可以发现进行了逻辑判断,使得一直为true
//但是要注意,只所以用or是因为优先级低于and,所以基本上是and先判断,最后判断or,所以基本为真(true)
//最后,对于单引号,是从外到里识别过去(即外部的单引号必须先配对,然后才配对里面的)
//如'1''2',中间的两个单引号不是一对,1两边是一对,2两边是一对
//而对于数字的逻辑运算,只要单引号能包括他们,那么就相当于没有单引号
//如'12''3.1'='12''3.1',结果还是为真(true),类似于123.1=123.1,但必须要数字(包括小数)
那么无论怎样返回都为真(true)
从上述可知sql也是有优先级的,优先级如下,分为关键字优先级和逻辑优先级
sql关键字优先级:先执行from关键字后面的语句,明确数据的来源,它是从哪张表取来的
接着执行where关键字后面的语句,对数据进行筛选
再接着执行group by后面的语句,对数据进行分组分类
然后执行select后面的语句,也就是对处理好的数据,具体要取哪一部分
最后执行order by后面的语句,对最终的结果进行排序
即这个优先级有些决定了写在谁的后面,如分组和排序写在where后面
sql逻辑优先级:
注意:有时双引号会写成分号,那么当成双引号处理,具体看当时情况,是解释双引号("")还是分号(;)
在对于扫描器的next()方法和nextLine()方法的介绍
next()方法:当开始识别到有效字符(任意数据,因为返回字符串)时,前后空格(tab也一样)都会过滤
用enter(回车键)结束输入
//如:
22 2;
那么结果就是22
nextLine()方法:读取一行,这一行的所有数据(任意数据,因为返回字符串)都读取,包括空格(tab也一样)
用enter(回车键)结束输入
//如:
22 2;
那么结果就是 22 2
nextInt()方法:当开始识别到整数时,前后空格(tab也一样)都会过滤,用enter(回车键)结束输入,且必须为整数,否则报错
package com.lagou.jdbc03;
import com.lagou.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class TestLogin01 {
public static void main(String[] args) throws SQLException {
//1.获取连接
Connection con = JDBCUtils.getConnection();
//2.获取Statement对象
Statement statement = con.createStatement();
//3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String pass = sc.nextLine();
//4.拼接SQL语句
String sql = "select * from jdbc_user where username = '" + name + "' and password = '" +
pass +"'";
System.out.println(sql);
//5.执行查询 获取结果集对象
ResultSet resultSet = statement.executeQuery(sql);
//6.处理结果集
if(resultSet.next()){
System.out.println("登录成功! 欢迎您: " + name);
}else{
System.out.println("登录失败! ");
}
//7.关闭流
JDBCUtils.close(con,statement,resultSet);
}
}
对于参数,由于参数有判断机制,那么在转义双引号时,需要两个转义的双引号
否则他会以为单个的转义双引号是用来连接的,而不是转义的
package com.lagou.jdbc03;
import com.lagou.utils.JDBCUtils;
import java.sql.*;
import java.util.Scanner;
public class TestLogin02 {
public static void main(String[] args) throws SQLException {
//1.获取连接
Connection con = JDBCUtils.getConnection();
//2.获取PrepareStatement 预处理对象
//使用 ? 占位符的方式来设置参数
String sql = "select * from jdbc_user where username = ? and password = ?";
//PreparedStatement prepareStatement(String sql)
//指定预编译的 SQL 语句, SQL 语句中使用占位符 ? 创建一个语句对象
PreparedStatement ps = con.prepareStatement(sql);
//预先处理语句
//3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名: ");
String name = "1";
System.out.println("请输入密码: ");
String pass = "2";
//4.设置参数 使用setXXX(占位符的位置(整数),要设置的值)的方法设置占位符的参数
//来处理语句,使得不可以使用sql注入的方式
ps.setString(1,name); // 设置第一个问号值 为 name
ps.setString(2,pass);//数字代表着第几个问号,从左至右,从上到下排列
//与结果集类似,但是对应的setxxx只包括对应的值
// 如setInt只可以用来设置整数
// setString只可以用来设置字符串
//与getxxx不同,是获得值,需要类型,它里面会先变为对应的返回值,而a变为整数当然不可
//所以getString都可以,可以包容
//而setxxx是设置值,那么设置的类型,即参数是固定的,即必须要对应类型,不可包容,必须对应
// 以此类推
//5.执行查询
//int executeUpdate(),执行insert update delete语句
//ResultSet executeQuery(),执行select语句返回结果集对象 Resulet
ResultSet resultSet = ps.executeQuery();
//上面已经写了sql了,所以这里不用写,即先将sql进行约束,防止sql注入,如上面的占位符
//就是先处理语句,在底层中,也会执行处理的代码(存放好的),所以这里不写sql语句,即没有参数了
//因为在这里写sql语句的话,来不及处理,即还是会有sql注入的操作
//在setxxx代码里,会对符号进行操作,使得你使用sql注入的方式,不可以为真了,如单个单引号'会变成'
//即'=>',使得sql注入失败,即查询不到结果,因为在sql里,''',就是'(')',没有进行连接了
//方法在PreparedStatement类里面,ctrl+n查找类
//在com.mysql.jdbc里面
//6.处理结果集
if(resultSet.next()){
System.out.println("登录成功! 欢迎您: " + name);
}else{
System.out.println("登录失败! ");
}
//7.关闭流
JDBCUtils.close(con,ps,resultSet);
}
}
Statement与PreparedStatement的区别----------------------------------------
Statement:用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句,编译并执行
PreparedStatement:是预编译的SQL语句对象,语句中可以包含动态参数"?",在执行时可以为"?"动态设置参数值
当你又要用到这个语句时,可以直接设置值,覆盖掉上一次设置的值,再执行,就不需要重新写一遍了
即同一个sql语句可以重复使用,编译一次,后执行
PreparedStatement:可以减少编译次数提高数据库性能
package com.lagou.jdbc04;
import com.lagou.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestJDBCTransaction {
//使用JDBC操作事务
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
try {
//1.获取连接
con = JDBCUtils.getConnection();
//2.开启事务
//void setAutoCommit(boolean autoCommit)
//参数是 true 或 false 如果设置为 false,表示关闭自动提交,相当于开启事务
con.setAutoCommit(false); //手动提交事务
//3.获取预处理对象 执行SQL (两次修改操作)
//3.1 tom账户 - 500
ps = con.prepareStatement("update account set money = money - ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"tom");
ps.executeUpdate();
//模拟 tom转账之后出现异常
System.out.println(1 / 0);
//3.2 jack账户 + 500
ps = con.prepareStatement("update account set money = money + ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"jack");
ps.executeUpdate();
//4.提交事务 (正常情况)
//void commit(),提交事务
con.commit();
System.out.println("转账成功! !");
} catch (SQLException e) {
e.printStackTrace();
//5.出现异常就回滚事务
try {
//void rollback(),回滚事务
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
//6.释放资源
JDBCUtils.close(con,ps);
}
}
}
注意:在JDBC里的sql语句,只能是一条语句,多写的话,就会报错,但也可以在一条语句最后,写上分号(加与不加,都可)
因为语句对象会给你加,若有则不加,没有则加