DataBase | 01 JDBC
JDBC
1. JDBC基本概念
1.1 JDBC的全名
Java DataBase Connectivity 即Java数据库连接
1.2 JDBC的本质
由官方定义的一套操作所有关系数据库的规则 (Java中即接口)
使得用户可以使用统一的Java代码可以操作所有的关系型数据库
其实现类由数据库厂商针对各自的数据库产品编写打包提供给用户 叫做数据库驱动
编写代码时使用JDBC接口编程,运行时执行的是指定驱动jar包中的实现类方法
-
没有JDBC时的数据库访问
直接访问数据库,对于不同的数据库使用不同的方式访问
-
有JDBC时的数据库访问
通过调用JDBC访问数据库,访问时不用考虑访问的具体实现,由数据库厂商提供具体的驱动实现
1.3 JDBC的地位
JDBC是Java访问数据库的基石,之后的许多技术框架都是对JDBC的封装
1.4 JDBC的体系结构
JDBC接口的两个层次:
- 面向应用的接口:抽象接口,供程序开发者使用
- 面向数据库的接口:供数据库厂商编写相应的驱动
2. JDBC基本使用步骤
JDBC八条
- 导入所使用数据库的驱动jar包
- 注册驱动
- 获取数据库连接对象
- 定义SQL语句
- 获取执行SQL语句的对象
- 执行SQL,接收返回结果
- 处理结果
- 释放资源
Tips:释放资源的顺序,先申请的最后释放,后申请的最先释放
示例
//JDBC的基本使用步骤
//使用Statement执行
/**
* 增删改操作
*/
public class JdbcDemo1 {
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//注册驱动,使用MySQL数据库
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接对象
conn = DriverManager.getConnection("jdbc:mysql:///testdb","root","1234");
//定义SQL语句
String sql = "update test set bonus=1500 where id=2;";
//获取执行语句的对象
stmt = conn.createStatement();
//执行SQL语句,接收返回结果
int count = stmt.executeUpdate(sql);
//处理结果
System.out.println(count);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//释放资源,先申请的后释放,后申请的先释放
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
/**
* 查询操作
*/
public class JdbcDemo2 {
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<User> users = null;
try{
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接对象
conn = DriverManager.getConnection("jdbc:mysql:///testdb","root","1234");
//定义SQL语句
String sql = "select * from test;";
//获取执行语句的对象
stmt = conn.createStatement();
//执行SQL语句,接收返回结果
rs = stmt.executeQuery(sql);
//处理结果
users = new ArrayList<User>();
while(rs.next()){
User user = new User();
user.setId(rs.getInt(1));
user.setName(rs.getString("name"));
user.setBonus(rs.getInt("bonus"));
users.add(user);
}
for(User user : users){
System.out.println(user);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//释放资源
if(rs != null){
try{
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
//使用PreparedStatement执行
public class JdbcDemo3 {
public static void main(String[] args){
Connection conn = null;
PreparedStatement stmt = null;
try{
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接对象
conn = DriverManager.getConnection("jdbc:mysql:///testdb","root","1234");
//定义SQL语句
String sql = "update test set bonus=? where id=?;";
//获取执行语句的对象
stmt = conn.prepareStatement(sql);
//给可变参数赋值
stmt.setInt(1,1000);
stmt.setInt(2,2);
//执行SQL语句,接收返回结果
int count = stmt.executeUpdate();
//处理结果
System.out.println(count);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//释放资源
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
3. JDBC中常用接口和类详解
以下所有的类和接口在Java.sql包中
3.1 Class DriveManager
- Class DriveManager:驱动管理对象
实现功能:
注册驱动,指明程序所使用的数据库驱动
方法:
获取数据库连接
方法:
参数url数据库路径的写法:jdbc:mysql:// ( IP地址 ) : ( 端口号 ) / ( 数据库名称 )
Tips 3.1:注册驱动代码
使用MySQL数据库时代码为:Class.forName(“com.mysql.cj.jdbc.Driver”)
问题:这其中并没有使用到registerDriver方法,如何注入驱动
关系:com.mysql.cj.jdbc.Driver类中包含了一个静态代码块其中执行了registerDriver方法
3.2 Interface Connection
- Interface Connection:数据库连接对象(Java和数据库之间的桥梁)
实现功能:
获取执行SQL的对象
方法:
使用Statement对象
使用PreparedStatement对象
管理事务,进行事务控制
开启事务:将自动提交改为手动提交、
提交事务:事务正常完成,提交结果
回滚事务:事务异常结束,回滚操作
3.3 Interface Statement
-
Interface Statement:执行静态SQL语句的对象
静态SQL语句:SQL语句的参数都是给定的
实现功能:
执行静态SQL语句
方法:
执行任意的SQL语句
执行DML(insert、update、delete)语句或DDL(create、alter、drop)语句
返回值表示语句影响的行数,可以判断是否执行成功
- DML语句大于0表示执行成功,数值表示语句影响的行数
- DDL语句等于0表示执行成功
- 小于0表示语句执行失败
执行DQL(select)语句
Tips 3.2:SQL注入问题
在拼接SQL时有SQL特殊关键字( OR、’ ’ 等等 )参与字符串拼接,会造成数据库的安全性问题
- SQL注入问题代码
public class JdbcDemo6 {
//客户端方法
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("用户名:");
String username = sc.nextLine();
System.out.println("密码:");
String password = sc.nextLine();
boolean flag = login(username,password);
if(flag){
System.out.println("登陆成功");
}else{
System.out.println("登陆失败");
}
}
//登录方法
public static boolean login(String username,String password){
//当用户名或密码为空时返回False
if(username == null || password == null){
return false;
}
//连接数据库查找用户名和密码是否有匹配
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///testdb","root","1234");
String sql = "select * from login_test where username='"+username+
"'and password='"+password+"';";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
//当结果集有数据就返回True,表示有匹配
return rs.next();
}catch(Exception e){
e.printStackTrace();
}finally{
if(rs != null){
try{
rs.close();
}catch(SQLException throwables){
throwables.printStackTrace();
}
}
if(stmt != null){
try{
stmt.close();
}catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
stmt.close();
}catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return false;
}
}
测试
-
数据库信息
-
用户名为"aaa",密码为"1234",登陆成功
-
用户名为"aaa",密码为"2345",登陆失败
-
用户名为任意值,密码为"a’ or ‘a’ = 'a",登陆成功
问题:
当密码为"a’ or ‘a’ = 'a" 时
实际的SQL语句为select * from account where username=‘example’ and password=‘a’ or ‘a’ = ‘a’
这时可以获取到数据库中所有的记录,由于返回值不为空所以可以登录成功
这就是SQL注入问题,会对于数据库的安全性产生影响
解决方式:使用下面的对象PreparedStatement执行预编译SQL语句
3.4 Interface PreparedStatement
-
Interface PreparedStatement:执行预编译SQL语句的对象 常用来解决SQL注入问题
预编译SQL语句:SQL语句中参数可变,需要参数的位置在语句中使用占位符"?"
实现功能:
给预编译SQL语句中的参数赋值
方法:
形如:setType( parameterIndex ,x )
参数parameterIndex表示SQL语句中占位符的位置,计数从1开始参数x表示对应占位符的值,Type表示参数x的数据类型
执行预编译SQL语句
方法:
Tips 3.3:Statement and PreparesdStatement
注:以增删改操作为例,只关注执行语句的部分
//Statement
//定义SQL语句
String sql = "update test set bonus=1500 where id=2;";
//获取执行语句的对象
stmt = conn.createStatement();
//执行SQL语句,接收返回结果
int count = stmt.executeUpdate(sql);
//PreparesdStatement
//定义SQL语句
String sql = "update test set bonus=? where id=?;";
//获取执行语句的对象
stmt = conn.prepareStatement(sql);
//给可变参数赋值
stmt.setInt(1,1500);
stmt.setInt(2,2);
//执行SQL语句,接收返回结果
int count = stmt.executeUpdate();
对比:
-
两个对象针对于不同的SQL语句
静态语句使用Statement执行
预编译语句使用PreparedStatement执行,在执行前要使用setType给可变参数赋值
-
使用Statement对象时,在执行方法时注入SQL语句
使用PreparedStatement对象时,在使用连接创建对象时注入SQL语句,执行时无参数
3.5 Interface ResultSet
- Interface ResultSet:结果集对象
实现功能:
封装查询结果
方法:
游标向下移动一行
Tips:游标最开始指向表头行
返回值判断当前行是否为最后一行末尾即是否还有数据,True表示有数据
获取数据
形如:getType( int columnIndex )或getType( String columnLabel )
Type指定获取数据的类型
参数为int类型时表示按列的编号获取数据(列数从1开始)
参数为String类型时表示按列的名称获取数据
遍历结果集
使用while循环遍历结果集 while(rs.next( ))
4. JDBC工具类抽取
JdbcUtil–抽取实现注册驱动、获取连接、释放资源功能的重复代码,降低代码重复度
- 工具类代码
Tips 4.1:几点注意
- 见注释
//1.工具类的方法多使用静态方法,方便进行调用
public class JdbcUtil {
private static Properties prop;
private static String url;
private static String username;
private static String password;
/**
* 4.使用静态代码块读配置文件、注册驱动
* 配置文件只需要读取一次即可获得属性值,驱动也只需要注册一次
* 这两步操作在初始化时直接执行一次即可
*/
static{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
prop = new Properties();
//3.获取src下的资源文件路径
//获取类加载器:用于加载类的字节码文件进内存的工具
ClassLoader classLoader = JdbcUtil.class.getClassLoader();
//获取对应名称的资源文件,返回值文件的绝对路径URL
URL path = classLoader.getResource("jdbcConfig.properties");
//获取字符串路径
String stringPath = path.getPath();
//读取配置文件prop.load方法
prop.load(new FileReader(stringPath));
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 2.使用配置文件.properties 可以使方法更加通用
* 属性可以在配置文件中定义,运行时从配置文件中读取属性值
* 对于不同的用户使用时只需要修改配置文件中的值不用更改源码
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}
//close方法重载来适应不同的关闭场景
public static void close(ResultSet rs, Statement stmt,Connection conn){
if(rs != null){
try{
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(PreparedStatement stmt, Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
配置文件 jdbcConfig.properties
url=jdbc:mysql:///testdb
username=root
password=1234
- 使用工具类
可以看出代码简化的效果十分显著
public class JdbcDemo4 {
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//创建连接对象
conn = JdbcUtil.getConnection();
//执行的SQL语句
String sql = "update test set bonus=1500 where id=2;";
//获取执行语句的对象
stmt = conn.createStatement();
//执行SQL语句
int count = stmt.executeUpdate(sql);
System.out.println(count);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//释放资源
JdbcUtil.close(stmt,conn);
}
}
}