JDBC
什么是JDBC
JDBC 概念:
使用java程序发送sql语句到数据库服务器端执行,用到了JDBC技术。
JDBC是Oracle-Sun公司设计的一套专门用于java程序操作关系型数据库的接口(API)。
全称:( Java DataBase Connectivity ) Java 数据库连接
JDBC 本质:
官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
各个数据库厂商去实现这套接口,提供数据库驱动jar包
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
JDBC 好处:
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
可随时替换底层数据库,访问数据库的Java代码基本不变(同一套Java代码,操作不同的关系型数据库)
JDBC API 详解
DriverManager(驱动管理类)
1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
查看 Driver 类源码:
注:
MySQL 5之后的驱动包,可以省略注册驱动的步骤
自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
2、获取数据库连接
static Connection getConnection(String url, String user, String password) 尝试建立与给定数据库URL的连接。
参数:
1. url:连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
示例:jdbc:mysql://127.0.0.1:3306/db1
细节:
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
参数键值对:配置 useSSL=false ,禁用安全连接方式,解决警告提示
配置前:
配置后:
2. user:用户名
3. password:密码
Connection(数据库连接对象)
1、获取执行 SQL 的对象
普通执行SQL对象
Statement createStatement()
预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement (sql)
2、管理事务
MySQL 事务管理
开启事务:BEGIN; / START TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;
MySQL默认自动提交事务
JDBC 事务管理:Connection接口中定义了3个对应的方法
开启事务:setAutoCommit(boolean autoCommit):true为自动提交事务;false为手动提交事务,即为开启事务
提交事务:commit()
回滚事务:rollback()
Statement
作用:执行SQL语句
int executeUpdate(sql):执行DML、DDL语句
返回值:(1) DML语句影响的行数 (2) DDL语句执行后,执行成功也可能返回 0
ResultSet executeQuery(sql):执行DQL 语句
返回值: ResultSet 结果集对象
ResultSet(结果集对象)
封装了DQL查询语句的结果
ResultSet stmt.executeQuery(sql):执行DQL 语句,返回 ResultSet 对象
获取查询结果
boolean next():(1) 将光标从当前位置向前移动一行 (2)判断当前行是否为有效行
返回值:
true:有效行,当前行有数据
false:无效行,当前行没有数据
xxx getXxx(参数):获取数据
xxx:数据类型;如:int getInt(参数) ; String getString(参数)
参数:
int:列的编号,从1开始
String:列的名称
使用步骤:
1.游标向下移动一行,并判断该行否有数据:next()
2.获取数据:getXxx(参数)
//循环判断游标是否是最后一行末尾
while(rs.next()){
//获取数据
rs.getXxx(参数);
}
PreparedStatement
SQL注入
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法
1 public void testLogin_Inject() throws Exception { 2 //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 3 String url = "jdbc:mysql:///db1?useSSL=false"; 4 String username = "root"; 5 String password = "1234"; 6 Connection conn = DriverManager.getConnection(url, username, password); 7 8 // 接收用户输入 用户名和密码 9 String name = "asd"; 10 String pwd = "' or '1' = '1"; 11 12 String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'"; 13 System.out.println(sql); 14 // 获取stmt对象 15 Statement stmt = conn.createStatement(); 16 // 执行sql 17 ResultSet rs = stmt.executeQuery(sql); 18 19 // 判断登录是否成功 20 if(rs.next()){ 21 System.out.println("登录成功~"); 22 }else{ 23 System.out.println("登录失败~"); 24 } 25 26 //7. 释放资源 27 rs.close(); 28 stmt.close(); 29 conn.close(); 30 }
会将输入的[' or '1' = '1]默认为sql语句:select * from tb_user where username = 'asd' and password = '' or '1' = '1'
对数据造成影响,不安全。称为sql注入行为。
PreparedStatement
作用:预编译SQL语句并执行:预防SQL注入问题
1.获取 PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
2.设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值
Xxx:数据类型 ; 如 setInt(参数1,参数2)
参数:
参数1: ?的位置编号,从1开始
参数2: ?的值
3.执行SQL
executeUpdate(); / executeQuery(); :不需要再传递sql
PreparedStatement 原理
PreparedStatement好处:
1、预编译SQL,性能更高
1.PreparedStatement预编译功能开启:useServerPrepStmts=true
String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";
2.配置MySQL执行日志(重启mysql服务后生效)
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
原理:
1.在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查SQL语法、编译SQL(这些步骤很耗时)
2.执行时就不用再进行这些步骤了,速度更快
3.如果sql模板一样,则只需要进行一次检查、编译
2、防止SQL注入:将敏感字符进行转义
会将输入的 [' or '1' = '1] 加转义字符防止SQL注入:
select * from tb_user where username = 'hfkjsfhskj' and password = '\' or \'1\' = \'1';
使用Statement执行sql语句
JDBC-新增
1 导入jdbc驱动包
2 加载驱动
3 获取数据库连接
4 获取执行sql对象
5 定义sql命令
6 执行sql
7 关闭资源
1 public class TestInsert { 2 public static void main(String[] args){ 3 //声明jdbc变量 4 Connection conn=null; 5 Statement stmt=null; 6 //声明JDBC参数 7 String driver="oracle.jdbc.driver.OracleDriver"; 8 String url="jdbc:oracle:thin:@localhost:1521:orcl"; //jdbc:jdbc协议;oracle:数据库协议;localhost:主机地址;3306:端口号;orcl:连接的数据库名称 9 String username="scott"; 10 String password="oracle"; 11 //1 加载驱动 12 try { 13 Class.forName(driver); 14 //2 获取数据库连接(连接指定的数据库) 15 conn=DriverManager.getConnection(url,username,password); 16 //3 获取执行sql对象(编译和发送sql命令给数据库) 17 stmt=conn.createStatement(); 18 //4 定义sql命令 19 String sql="insert into dept values(1,'清华','北京')"; 20 //5 执行sql 21 int i=stmt.executeUpdate(sql); 22 System.out.println("执行结果:"+i); 23 24 } catch (ClassNotFoundException e) { 25 // TODO Auto-generated catch block 26 e.printStackTrace(); 27 } catch (SQLException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 }finally{ 31 //6 关闭资源 32 try { 33 stmt.close(); 34 } catch (SQLException e) { 35 // TODO Auto-generated catch block 36 e.printStackTrace(); 37 } 38 try { 39 conn.close(); 40 } catch (SQLException e) { 41 // TODO Auto-generated catch block 42 e.printStackTrace(); 43 } 44 } 45 46 } 47 }
JDBC-修改(与JDBC-新增对比:除sql语句不同外,其他一致)
1 public class TestUpdate { 2 public static void main(String[] args) throws ClassNotFoundException, SQLException { 3 //1加载驱动 4 Class.forName("oracle.jdbc.driver.OracleDriver"); 5 //2创建连接对象 6 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott","oracle"); 7 //3创建sql命令对象 8 Statement stmt=conn.createStatement(); 9 //4创建sql命令 10 String sname="张三"; 11 String sql="update student set sname='"+sname+"' where snum=2"; 12 //5执行sql命令 13 int i=stmt.executeUpdate(sql); 14 System.out.println(i); 15 //6关闭资源 16 stmt.close(); 17 conn.close(); 18 19 } 20 }
JDBC-删除(与JDBC-新增对比:除sql语句不同外,其他一致)
1 public class TestDel { 2 public static void main(String[] args) throws ClassNotFoundException, SQLException { 3 //1 加载驱动 4 Class.forName("oracle.jdbc.driver.OracleDriver"); 5 //2创建连接对象 6 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@LocalHost:1521:orcl", "scott", "oracle"); 7 //3创建sql命令对象 8 Statement stmt=conn.createStatement(); 9 //4创建sql命令 10 String sql="delete from student where snum='2'"; 11 //5执行sql命令 12 int i=stmt.executeUpdate(sql); 13 System.out.println("删除数据量:"+i); 14 //6关闭资源 15 stmt.close(); 16 conn.close(); 17 } 18 }
JDBC-事务管理
示例:张三给李四转账1000元.
注意:JDBC中的事务是自动提交的.
问题:如果在业务的处理过程中,某条Sql语句执行失败,但是数据已经被更改了.
解决:
设置JDBC的事务为手动提交.
sql语句都执行成功后再统一提交,只要有失败的就回滚.
使用:
conn.setAutoCommit(false)//设置为手动提交,开启事务
使用try catch进行SQL命令执行的异常处理
try中是使用conn.commit() 提交数据
catch中使用conn.rollback() 回滚数据
事务4大特性:
1、原子性: 要么一起成功过,要么一起失败
2、一致性: 数据库应该从一个一致性的状态到另一个一致性的状态,保持不变
3、隔离性: 多个并发事务过程中,希望可以相互隔离
(1)多个事务并发产生以下现象:
脏读:一个事务读到了另一个事务没有提交的数据
不可重复读:一个事务读到了另一个事务已经提交的更新数据(update) ,导致在一个事务内两次读到的数据是不一样的情况。
幻读:一个事务读到了另一个事务已经提交的插入数据(insert) ,在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,好像发生了幻觉一样,称为幻读。
(2)数据库设置不同的隔离级别来防止以上现象:(以下√表示是否会导致的问题)
SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
结论: 隔离性越高,数据库的性能越差。
4、持久性: 事务一旦提交,应该永久保持下来。
1 public class TestTran { 2 public static void main(String[] args) throws ClassNotFoundException, SQLException { 3 //加载驱动 4 Class.forName("oracle.jdbc.driver.OracleDriver"); 5 //创建连接对象 6 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott","oracle"); 7 //设置事务为手动提交 8 conn.setAutoCommit(false); 9 //创建sql命令对象 10 Statement stmt=conn.createStatement(); 11 //创建sql命令 12 String sql1="update student set money=money-1000 where snum=6";//转账 13 String sql2="update student set money=money2+1000 where snum=7";//入账 14 //执行sql命令 15 try { 16 int i1=stmt.executeUpdate(sql1); 17 int i2=stmt.executeUpdate(sql2); 18 System.out.println(i1+"----"+i2); 19 conn.commit(); 20 } catch (Exception e) { 21 conn.rollback();//数据回滚 22 } 23 //关闭资源 24 stmt.close(); 25 conn.close(); 26 27 28 } 29 }
JDBC-查询
JDBC的查询:
加载驱动
创建连接对象
创建sql命令对象
创建sql命令
执行sql命令
返回存储了查询到的数据的对象(ResultSet)
ResultSet对象是基于指针进行数据存储的,类似枚举.
关闭资源
问题:
查询的返回值类型是ResultSet,是基于指针进行数据存储的,不便于数据的针对性的获取.
解决:
将数据转换到ArrayList中进行存储.
使用:
创建和表结构相同的实体类进行单条数据的存储(一条数据就是一个实例化对象)
将对象存储到ArrayList中.
创建实体类:
1 package com.su.pojo; 2 3 import java.io.Serializable; 4 5 public class Student implements Serializable{ 6 private int snum; 7 private String sname; 8 private int sage; 9 private double money; 10 public int getSnum() { 11 return snum; 12 } 13 public void setSnum(int snum) { 14 this.snum = snum; 15 } 16 public String getSname() { 17 return sname; 18 } 19 public void setSname(String sname) { 20 this.sname = sname; 21 } 22 public int getSage() { 23 return sage; 24 } 25 public void setSage(int sage) { 26 this.sage = sage; 27 } 28 public double getMoney() { 29 return money; 30 } 31 public void setMoney(double money) { 32 this.money = money; 33 } 34 @Override 35 public int hashCode() { 36 final int prime = 31; 37 int result = 1; 38 long temp; 39 temp = Double.doubleToLongBits(money); 40 result = prime * result + (int) (temp ^ (temp >>> 32)); 41 result = prime * result + sage; 42 result = prime * result + ((sname == null) ? 0 : sname.hashCode()); 43 result = prime * result + snum; 44 return result; 45 } 46 @Override 47 public boolean equals(Object obj) { 48 if (this == obj) 49 return true; 50 if (obj == null) 51 return false; 52 if (getClass() != obj.getClass()) 53 return false; 54 Student other = (Student) obj; 55 if (Double.doubleToLongBits(money) != Double.doubleToLongBits(other.money)) 56 return false; 57 if (sage != other.sage) 58 return false; 59 if (sname == null) { 60 if (other.sname != null) 61 return false; 62 } else if (!sname.equals(other.sname)) 63 return false; 64 if (snum != other.snum) 65 return false; 66 return true; 67 } 68 @Override 69 public String toString() { 70 return "Student [snum=" + snum + ", sname=" + sname + ", sage=" + sage + ", money=" + money + "]"; 71 } 72 public Student(int snum, String sname, int sage, double money) { 73 super(); 74 this.snum = snum; 75 this.sname = sname; 76 this.sage = sage; 77 this.money = money; 78 } 79 public Student() { 80 super(); 81 } 82 83 }
查询:
1 public class TestSelect { 2 public static void main(String[] args) throws ClassNotFoundException, SQLException { 3 //声明List集合 4 ArrayList<Student> list=new ArrayList<>(); 5 //加载驱动 6 Class.forName("oracle.jdbc.driver.OracleDriver"); 7 //创建连接对象 8 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott","oracle"); 9 //创建sql命令对象 10 Statement stmt=conn.createStatement(); 11 //创建Sql命令 12 String sql="select * from student"; 13 //执行Sql命令 14 ResultSet rs = stmt.executeQuery(sql); 15 while(rs.next()){ 16 //创建学生对象 17 Student stu=new Student(); 18 stu.setSnum(rs.getInt("snum")); 19 stu.setSname(rs.getString("sname")); 20 stu.setSage(rs.getInt("sage")); 21 stu.setMoney(rs.getDouble("money")); 22 //将对象存储到ArrayList中 23 list.add(stu); 24 } 25 26 System.out.println(list.get(2)); 27 28 } 29 }
使用PreparedStatement执行sql语句
特点:
防止sql注入
提升sql语句的执行效率(preparedStatement有预编译的过程)
使用:
Statement PreparedStatement
声明集合或者实体类对象(可选-查询) 声明集合或者实体类对象(可选-查询)
加载驱动 加载驱动
获取连接对象 获取连接对象
创建sql命令
获取SQL命令对象(statement) 获取SQL命令对象(preparedStatement)
创建sql命令 给占位符赋值
执行sql命令 执行sql命令
遍历结果(可选-查询) 遍历结果(可选-查询)
关闭资源 关闭资源
返回结果 返回结果
JDBC-新增
1 public int insUser2() throws ClassNotFoundException, SQLException{ 2 //加载驱动 3 Class.forName("oracle.jdbc.driver.OracleDriver"); 4 //创建连接对象 5 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "oracle"); 6 //创建sql命令 7 String sql="insert into t_user values(?,?,?)"; 8 //创建sql命令对象 9 PreparedStatement ps=conn.prepareStatement(sql); 10 //给占位符赋值 11 ps.setInt(1, 7); //1代表参数位置,代表第一个?号;7代表参数值 12 ps.setString(2,"赵六"); 13 ps.setString(3,"666"); 14 //执行sql命令 15 int i=ps.executeUpdate(); 16 //关闭资源 17 ps.close(); 18 conn.close(); 19 //返回结果 20 return i; 21 }
注:参数占位符 ? ,一个问号代表一个参数,从1开始。
JDBC-修改
1 public int upUser(String uname,int unum) throws ClassNotFoundException, SQLException{ 2 //加载驱动 3 Class.forName("oracle.jdbc.driver.OracleDriver"); 4 //获取连接对象 5 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "oracle"); 6 //创建sql命令 7 String sql="update t_user set uname=? where unum=?"; 8 //获取sql命令对象 9 PreparedStatement ps=conn.prepareStatement(sql); 10 //给占位符赋值 11 ps.setString(1, uname); 12 ps.setInt(2, unum); 13 //执行sql命令 14 int i=ps.executeUpdate(); 15 //关闭资源 16 ps.close(); 17 conn.close(); 18 //返回结果 19 return i; 20 }
JDBC-删除
1 public int delUser(int unum) throws ClassNotFoundException, SQLException{ 2 3 //加载驱动 4 Class.forName("oracle.jdbc.driver.OracleDriver"); 5 //获取连接对象 6 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "oracle"); 7 //创建sql命令 8 String sql="delete from t_user where unum=?"; 9 //获取sql命令对象 10 PreparedStatement ps=conn.prepareStatement(sql); 11 //给占位符赋值 12 ps.setObject(1, unum); 13 //执行sql命令 14 int i=ps.executeUpdate(); 15 //关闭资源 16 ps.close(); 17 conn.close(); 18 //返回结果 19 return i; 20 21 }
JDBC-查询
1 public User getUserInfo(String uname,String upwd) throws ClassNotFoundException, SQLException{ 2 //声明User对象 3 User u=null; 4 //加载驱动 5 Class.forName("oracle.jdbc.driver.OracleDriver"); 6 //创建连接对象 7 Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "oracle"); 8 //创建sql命令 9 String sql="select * from t_user where uname=? and upwd=?"; 10 //创建sql命令对象 11 PreparedStatement ps=conn.prepareStatement(sql); 12 //给占位符赋值(占位符从左到右角标从1开始) 13 ps.setString(1, uname); 14 ps.setString(2, upwd); 15 //执行sql命令 16 ResultSet rs=ps.executeQuery(); 17 //遍历查询结果 18 while(rs.next()){ 19 u=new User(); 20 u.setUnum(rs.getInt("unum")); 21 u.setUname(rs.getString("uname")); 22 u.setUpwd(rs.getString("upwd")); 23 return u; 24 } 25 //关闭资源 26 rs.close(); 27 ps.close(); 28 conn.close(); 29 //返回执行结果 30 return u; 31 }
Statement与PreparedStatement的区别
1、语法不同:Statement只能执行静态的sql。PreparedStatement既可以执行静态的sql语句,也可以执行预编译sql语句。
2、安全性不同:Statement可以被用户进行sql注入,不安全。PreparedStatement不能被用户注入sql,比Statement安全。
3、执行效率不同:Statement不能利用数据库sql缓存功能。PreparedStatement可以利用数据库sql的缓存功能。比Statement执行效率更高。
数据库连接池
数据库连接池简介
数据库连接池实现
标准接口:DataSource
官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。
功能:获取连接
Connection getConnection()
常见的数据库连接池:
DBCP
C3P0
Druid
Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一
Driud使用步骤
1、导入jar包 druid-1.1.12.jar
2、定义配置文件
1 driverClassName=com.mysql.jdbc.Driver 2 url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true 3 username=root 4 password=1234 5 # 初始化连接数量 6 initialSize=5 7 # 最大连接数 8 maxActive=10 9 # 最大等待时间 10 maxWait=3000
3、加载配置文件
4、获取数据库连接池对象
5、获取连接
1 public static void main(String[] args) throws Exception { 2 //1. 导入jar包 3 //2. 定义配置文件 4 //3. 加载配置文件 5 Properties prop = new Properties(); 6 prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); 7 //4. 获取连接池对象 8 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); 9 //5. 获取数据库连接 Connection 10 Connection connection = dataSource.getConnection(); 11 12 System.out.println(connection); 13 }