三,JDBC
–1,概述
是简称,全称是java database connectivity,专门用来完成 java程序 和 数据库 的连接的技术.
–2,使用步骤
1,导入jar包(使用JDBC提供了丰富的工具类)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
2,注册驱动
Class.forName("com.mysql.jdbc.Driver");
3,获取数据库的连接(用户名/密码)
//jdbc连接mysql数据库的协议//本机:端口号/数据库的名字
String url="jdbc:mysql://localhost:3306/cgb2108" ;
Connection c = DriverManager.getConnection(
url,"root","root");
4,获取传输器
Statement s = c.createStatement();
5,执行SQL,并返回结果集
String sql="select * from dept" ;//查询dept表的所有数据
ResultSet r = s.executeQuery(sql);//executeQuery执行查询的SQL,executeUpdate执行增删改查的SQL
6,处理数据库返回的结果
while(r.next()){//next()判断resultset中有数据吗
//getXxx()获取resultset中的数据
int a = r.getInt(1);//获取第1列的 整数值
String b = r.getString(2);//获取第2列的 字符串值
String c1 = r.getString(3);//获取第3列的 字符串值
System.out.println(a+b+c1);
}
7,释放资源
r.close();//释放结果集
s.close();//释放传输器
c.close();//释放连接器
–3,入门案例
1,创建project: File - New - Project - 选择java - next - next - 输入工程名称 - Finish
2,导入jar包:
找到磁盘里的mysql-connector-java-5.1.32.jar复制,粘贴到Project里.
在IDEA里,选中jar包,右键编译(add as library…),ok
检查是否编译成功:看到IDEA里的jar包可以被点开了
3,编写java代码
package cn.tedu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
//JDBC的入门案例:
//JDBC是java连接数据库的一个标准,本质上就是一堆的工具类
public class Test1 {
public static void main(String[] args) throws Exception {
//1,注册驱动
Class.forName("com.mysql.jdbc.Driver");//全路径
//2,获取数据库的连接(用户名/密码)
//jdbc连接mysql数据库的协议//本机:端口号/数据库的名字
String url="jdbc:mysql://localhost:3306/cgb2108" ;
Connection c = DriverManager.getConnection(
url,"root","root");
//3,获取传输器
Statement s = c.createStatement();
//4,执行SQL,并返回结果集
String sql="select * from dept" ;//查询dept表的所有数据
ResultSet r = s.executeQuery(sql);//executeQuery执行查询的SQL,executeUpdate执行增删改查的SQL
//5,处理数据库返回的结果
while(r.next()){//next()判断resultset中有数据吗
//getXxx()获取resultset中的数据
int a = r.getInt(1);//获取第1列的 整数值
String b = r.getString(2);//获取第2列的 字符串值
String c1 = r.getString(3);//获取第3列的 字符串值
System.out.println(a+b+c1);
}
//6,释放资源
r.close();//释放结果集
s.close();//释放传输器
c.close();//释放连接器
}
}
–4,JDBC的练习
package cn.tedu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
//需求:利用JDBC,查询tb_user表里的数据
/* 1,创建表,并插入数据 2,利用JDBC,查询数据
CREATE TABLE tb_user(
id int PRIMARY KEY auto_increment,
name varchar(20) default NULL,
password varchar(20) default NULL
)
insert into tb_user values(null,'jack','321')
*/
public class Test2 {
public static void main(String[] args) throws Exception {
method();//查询tb_user表里的数据
method2();//模拟用户登录
}
//查询tb_user表里的数据
private static void method() throws Exception{
//1,注册驱动
Class.forName("com.mysql.jdbc.Driver");//全路径
//2,获取数据库的连接(用户名/密码)
//jdbc连接mysql数据库的协议//本机:端口号/数据库的名字
String url="jdbc:mysql://localhost:3306/cgb2108" ;
Connection c = DriverManager.getConnection(
url,"root","root");
//3,获取传输器
Statement s = c.createStatement();
//4,执行SQL
ResultSet r = s.executeQuery("select * from tb_user");
//5,解析结果集
while(r.next()){//判断r有数据
//获取r的数据
int a = r.getInt("id");//获取表里的id字段的值
String b = r.getString("name");//获取表里的name字段的值
String c1 = r.getString("password");//获取表里的password字段的值
System.out.println(a+b+c1);
}
//6,释放资源
r.close();//释放结果集
s.close();//释放传输器
c.close();//释放连接器
}
/* 模拟用户登录
1,发起SQL:select * from tb_user where name='jack' and password='321'
2,判断result,如果有结果就登录成功,没结果就登录失败
*/
private static void method2() {
}
}
SQL注入
/*自己准备user2表(id/name/password),准备数据
CREATE TABLE `user2` (
`id` int(11) PRIMARY KEY auto_increment,
`name` varchar(10) default NULL,
`password` varchar(10) default NULL
) ;
*/
//需求:利用jdbc,根据用户名和密码查询cgb2104库里的user表
//SQL注入攻击问题
private static void login() {
try{
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///cgb2104?characterEncoding=utf8";
Connection conn = DriverManager.getConnection(url, "root", "root");
Statement st = conn.createStatement();
// String sql ="select * from user2 where name='jack' and password='123456'";//写死了
String user = new Scanner(System.in).nextLine();//用户输入jack'#
String pwd = new Scanner(System.in).nextLine();
//SQL注入攻击问题:本质上是因为SQL语句中出现了特殊符号#,改变了SQL语义
String sql ="select * from user2 where name='"+user+"' and password='"+pwd+"'";
ResultSet rs = st.executeQuery(sql);//执行查询的SQL,返回结果集
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
st.close();
conn.close();
}catch(Exception e){
e.printStackTrace();//有异常,直接打印异常信息
//System.out.println("执行失败。。。");//上线
}
}
SQL注入的解决方案
//解决SQL注入攻击的方案
private static void login2() {
try{
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///cgb2104?characterEncoding=utf8";
Connection conn = DriverManager.getConnection(url, "root", "root");
// Statement st = conn.createStatement();不行,不安全,会被SQL攻击
String user = new Scanner(System.in).nextLine();//用户输入jack'#
String pwd = new Scanner(System.in).nextLine();
//?叫占位符 ,SQL的骨架
String sql ="select * from user2 where name=? and password=?";
//先把SQL骨架发给数据库执行
PreparedStatement ps = conn.prepareStatement(sql);
//给SQL里的? 设置参数
ps.setString(1,user);//给第一个?设置值是user
ps.setString(2,pwd);//给第二个?设置值是pwd
ResultSet rs = ps.executeQuery();//执行拼接好的SQL,返回结果集
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
ps.close();
conn.close();
}catch(Exception e){
e.printStackTrace();//有异常,直接打印异常信息
//System.out.println("执行失败。。。");//上线
}
}
JDBC练习
–1,创建工具类
package cn.tedu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
//提供丰富的方法,方便的jdbc操作
public class JDBCUtils {
//1,获取数据库的连接(注册驱动+获取连接)
/**
* 获取数据库的连接
* @return 数据库的连接对象Connection
* @throws Exception
*/
static public Connection getConnection() throws Exception{
//1,注册驱动
Class.forName("com.mysql.jdbc.Driver");//全路径
//2,获取数据库的连接(用户名/密码)
//jdbc连接mysql数据库的协议//本机:端口号/数据库的名字 解决中文乱码 指定时区 关闭权限检验
String url="jdbc:mysql://localhost:3306/cgb2108?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false" ;
Connection c = DriverManager.getConnection(
url,"root","root");
return c ;//返回给调用者
}
}
–2,模拟用户登录
package cn.tedu.jdbc;
import java.sql.*;
import java.util.Scanner;
//需求:利用JDBC,查询tb_user表里的数据
/* 1,创建表,并插入数据 2,利用JDBC,查询数据
CREATE TABLE tb_user(
id int PRIMARY KEY auto_increment,
name varchar(20) default NULL,
password varchar(20) default NULL
)
insert into tb_user values(null,'jack','321')
*/
public class Test2 {
public static void main(String[] args) throws Exception {
// method();//查询tb_user表里的数据
// method2();//模拟用户登录
method3();//解决SQL攻击问题
}
//解决SQL攻击问题
private static void method3() throws Exception {
//1,注册驱动 2,获取连接
Connection c = JDBCUtils.getConnection();
//3,执行SQL
String a = new Scanner(System.in).nextLine();//用户名
String b = new Scanner(System.in).nextLine();//密码
//如果动态的拼接字符串时,数据在中间的位置 "+a+"
// String sql="select * from tb_user where name='jack' and password='321'" ;
// String sql="select * from tb_user where name='"+a+"' and password='"+b+"'" ;
//SQL骨架:用?代替了参数的位置,?叫占位符,好处:简洁(避免了SQL拼接参数)
String sql="select * from tb_user where name=? and password=?" ;
//4,获取传输器
// Statement s = c.createStatement();
PreparedStatement s = c.prepareStatement(sql);
//设置SQL参数--setXxx()设置不同类型的参数
s.setString(1,a);//?的索引,要给?设置的值
s.setString(2,b);//?的索引,要给?设置的值
//TODO 当用户名输入jack'#时还会发生SQL攻击吗???
ResultSet r = s.executeQuery();
//5,解析结果集
if(r.next()){//查到数据了吗?查到了就登录成功
System.out.println("登录成功~");
}else{
System.out.println("用户名或者密码输入错误,登录失败~");
}
//6,关闭资源
r.close();
s.close();
c.close();
}
//查询tb_user表里的数据
private static void method() throws Exception{
//调用工具类的方法
Connection c = JDBCUtils.getConnection();
//3,获取传输器
Statement s = c.createStatement();
//4,执行SQL
ResultSet r = s.executeQuery("select * from tb_user");
//5,解析结果集
while(r.next()){//判断r有数据
//获取r的数据
int a = r.getInt("id");//获取表里的id字段的值
String b = r.getString("name");//获取表里的name字段的值
String c1 = r.getString("password");//获取表里的password字段的值
System.out.println(a+b+c1);
}
//6,释放资源
r.close();//释放结果集
s.close();//释放传输器
c.close();//释放连接器
}
/* 模拟用户登录
1,发起SQL:select * from tb_user where name='jack' and password='321'
2,判断result,如果有结果就登录成功,没结果就登录失败
问题: SQL攻击/SQL注入,
本质上就是因为SQL语句中出现了特殊符号(#,注释掉了一些条件),导致了SQL语义改变了
解决方案:Statement低级的传输器,不安全,低效
换成PreparedStatement高级,安全
*/
private static void method2() throws Exception {
//1,注册驱动 2,获取连接
Connection c = JDBCUtils.getConnection();
//3,获取传输器
Statement s = c.createStatement();
//4,执行SQL
String a = new Scanner(System.in).nextLine();//用户名
String b = new Scanner(System.in).nextLine();//密码
//如果动态的拼接字符串时,数据在中间的位置 "+a+"
// String sql="select * from tb_user where name='jack' and password='321'" ;
String sql="select * from tb_user where name='"+a+"' and password='"+b+"'" ;
ResultSet r = s.executeQuery(sql);
//5,解析结果集
if(r.next()){//查到数据了吗?查到了就登录成功
System.out.println("登录成功~");
}else{
System.out.println("用户名或者密码输入错误,登录失败~");
}
//6,关闭资源
r.close();
s.close();
c.close();
}
}
–3,JDBC的练习
package cn.tedu.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
//JDBC的练习
public class Test3 {
public static void main(String[] args) throws Exception{
// method();//查询部门表的<100数据
method2();//向dept表里插入数据
}
//向dept表里插入数据
private static void method2() throws Exception {
Connection c = JDBCUtils.getConnection();
//插入数据时怎么决定要几个问号? 要看表里有几个字段需要设置值
String sql = "insert into dept values(?,?,?)" ;
PreparedStatement p = c.prepareStatement(sql);
//设置SQL的参数
p.setObject(1,666);
p.setObject(2,"软件测试部");
p.setObject(3,"大山西");
//执行SQL
p.executeUpdate();//执行增删改的SQL
//TODO 会返回结果集吗?返回了的是啥?
}
//查询部门表的<100数据
private static void method() throws Exception{
Connection c = JDBCUtils.getConnection();//利用工具类,获取数据库的连接
//获取传输器,执行SQL骨架
String sql = "select * from dept where deptno < ?";
PreparedStatement s = c.prepareStatement(sql);
//设置SQL的参数
s.setInt(1,100);//给第一个?设置100
ResultSet r = s.executeQuery();//执行查询的SQL语句
//处理结果集
while(r.next()){//next()判断有数据吗
//获取数据getXxx()--获取表里的dname字段的值,并打印
String str = r.getString("dname");
System.out.println(str);
}
//关闭资源
r.close();
s.close();
c.close();
}
}
–4,修改释放资源的代码
package cn.tedu.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//JDBC的练习
public class Test3 {
public static void main(String[] args) throws Exception{
method();//查询部门表的<100数据
// method2();//向dept表里插入数据
}
//向dept表里插入数据
//为了资源一定会被释放?
// 把释放资源的代码放入finally里+扩大变量的作用范围
// +在try里修改变量的默认值null+在finally里进行try catch
private static void method2(){
//扩大变量的作用范围?因为想让finally也使用
Connection c = null ;
PreparedStatement p = null;
try{
c = JDBCUtils.getConnection();
//插入数据时怎么决定要几个问号? 要看表里有几个字段需要设置值
String sql = "insert into dept values(?,?,?)" ;
p = c.prepareStatement(sql);
//设置SQL的参数
p.setObject(1,666);
p.setObject(2,"软件测试部");
p.setObject(3,"大山西");
//执行SQL
int rows = p.executeUpdate();//执行增删改的SQL
//TODO 会返回结果集吗?返回了的是啥?
System.out.println("影响的行数是: "+rows);
}catch (Exception e){
System.out.println("出错啦~");
}finally {//资源的释放是一定要执行的
//关闭资源
try{
p.close();
}catch (Exception e){
e.printStackTrace();
}
try {
c.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//查询部门表的<100数据
private static void method() {
Connection c =null;
PreparedStatement s =null;
ResultSet r =null;
try{
c = JDBCUtils.getConnection();//利用工具类,获取数据库的连接
//获取传输器,执行SQL骨架
String sql = "select * from dept where deptno < ?";
s = c.prepareStatement(sql);
//设置SQL的参数
s.setInt(1,100);//给第一个?设置100
r = s.executeQuery();//执行查询的SQL语句
//处理结果集
while(r.next()){//next()判断有数据吗
//获取数据getXxx()--获取表里的dname字段的值,并打印
String str = r.getString("dname");
System.out.println(str);
}
}catch (Exception e){
//项目上线阶段,给出的解决方案,比如输出
System.out.println("数据库连接出错~~");
//项目开发调试阶段,给出的解决方案,根据报错信息
e.printStackTrace();
}finally {
//关闭资源
try {
r.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
s.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
c.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
JDBC常见问题
Class.forName这句话有用没?
Class.forName可以指定class类路径进行动态创建对象实例,可JDBC这句话没有返回对象啊,那写这句有什么作用呢?看看java.sql.Driver.class的源码就找到真相了,原来它用了静态代码块创建对象。
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
写了创建了,那不写呢?怎么不写也能执行呢?
Java提供了SPI机制,用户可以自行配置类,JDBC高版本驱动就都引入了这个支持。如果用户使用了Class.forName方式就自己指定了驱动,如果未写这句话,则Java自动去META-INF/services/java.sql.Driver文件中找启动类。
驱动版本
不同版本的mysql需要不同版本的驱动
Mysql5.0x mysql-connector-java-5.1.32.jar
Mysql8.0x mysql-connector-java-8.0.21.jar
Driver变成了: com.mysql.cj.jdbc.Driver,中间多了cj
url必须加时区参数: serverTimezone=Asia/Shanghai
中文乱码
url增加参数:characterEncoding=utf8防止中文乱码
String url ="jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";
SQL注入
String condition = "陈强";
String condition = "陈强' or 1=1 or '";
String condition = "陈强' or true or '";
String sql = "select * from teachers where tname='" + condition+"'";
利用sql中’单撇是字符串的结束符,or只要一个条件成立其它就不用再判断,而恶意造成sql查询失效,本应该只展示一条数据,结果全部展现。
注入后形成的SQL:
SELECT * FROM teachers WHERE tname='陈强' OR 1=1 OR ''
大家试想如果是一个财务表,本你只能看自己的信息,结果你看了所有人的信息。结果新员工比你工资高,你说气人不。
PreparedStatement 语句
SQL注入解决方案:
Statement对象换为PreparedStatement对象
sql = "select * from teachers where tname=?"; #参数使用问号
PreparedStatement stat = cn.prepareStatement(sql); #对象换掉
stat.setString(1, condition); #对应参数类型,第几个问号
ResultSet rs = stat.executeQuery(); #去掉sql参数
PS后的结果:
SELECT * FROM teachers WHERE tname='陈强\' or 1=1 or \''
利用转义字符,屏蔽了SQL中的恶意字符。不仅解决了sql注入问题,使系统变的安全,PreparedStatement还有个极大的好处,它是预编译的语句,其主干部分mysql进行预编译后缓存,下次这部分就无需在解析,只把条件拼入,这样执行效率远高于statement每次都要编译sql语句。
常见错误
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
错误原因:
1)jar没有导入,没有builder path
2)Class.forName(“com.mysql.jdbc.Driver”); 字符串拼写错误
Unknown database mydb;
错误原因:
数据库名称拼写错误
Access denied for user ‘root123’@‘localhost’ (using password: YES)
错误原因:
数据库用户名或者密码错误
Table ‘py-school-db.mydb’ doesn’t exist
错误原因:
表不存在,也可能表名写错了
JDBC的总结
1, 什么是JDBC? java程序连接数据库的标准方案,全称是java database connectivity
2, 使用JDBC步骤? 导入jar包,注册驱动,获取数据库的连接,获取传输器,执行SQL,解析结果集(查询),关闭资源
3, 传输器Statement和PreparedStatement有什么区别? Statement不安全(可能发生SQL攻击),而且低效
4, Statement和PreparedStatement有什么关系? public interface PreparedStatement extends Statement ,是父子接口
5, SQL攻击? 原因是:SQL中出现了特殊符号#(注释符号)改变了SQL的语义 解决方案:使用新的传输器PreparedStatement
6, 执行SQL: 先执行SQL骨架,然后再给SQL设置参数
executeUpdate(): 用来执行增删改的SQL语句,并且返回了影响行数
executeQuery(): 用来执行查的SQL语句,并且返回了结果集ResultSet