JDBC初识——API详解

294 阅读7分钟

驱动问题这里不再详述,接下的就是建立连接,建立连接最基本的方法是直接采取DriverManager直接连接数据库。但是这种方法缺陷很大,建立连接的开销很大,当有多个用户请求连接时,如果为每个用户都建立一次连接,且单个用户建立的连接在使用完毕之后直接被废弃,这样的开销是不可想象的。

//DriverManager有两个作用,注册驱动还有获取连接
// jdbc:mysql://ip:端口/数据库名称?参数键值对1&参数键值对2
//连接本机数据库时可以不写IP和端口
//可以通过禁用安全连接来解决报错警告,useSSL = false,等号前后不要加入空格
//prepareStatement预编译功能必须要在url中首先开启useServerPrepStmts=true
//Connection可以连接数据库,执行sql语句并返回结果
//DriverManager.getConnection会被连接池所取代
Connection conn = DriverManager.getConnection(url,userName,password);

为了解决该问题,我们采取连接池技术来取代DriverManager.getConnection()。 我使用的连接池为Druid,一个阿里的开源连接池技术。

//Druid连接演示1.导入jar,2.完善配置文件,配置文件放在src下面3.加载配置,但是需要根据需求改
//加载配置文件
Properties prop = new Properties();
//文件路径可能出现问题,可以使用System.getProperty()来查看当前文件位置,并对文档路径进行修正
System.out.println("path="+System.getProperty("user.dir"));
prop.load(new FileInputStream("JDBC_demo/src/druid.properties"));
//获取连接池对象
 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
 //获取连接
Connection conn = dataSource.getConnection();

配置文件具体内容如下

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true
username=root
password=//自己数据库的密码
# 初始连接数
initialSize=5
# 最大连接数
maxActive=10
# 最大连接等待时间
maxWait=3000

接下来是定义sql,和获取执行sql对象,这两步息息相关。

//Connection中有三种方法来获取执行对象
//createStatement():最为普通的获取sql对象的方法
//prepareStetement():预编译sql的执行对象,防止sql注入
//prepareCall():执行存储过程的对象,该方法不常用,本文也不做讲解。

如果采取最普通的createStatement(),那么直接完整的写出sql就可以,但是存在着被sql注入的风险。

name = "'or '1' = '1";//sql注入演示,通过截断密码来绕过密码判断
sql3 = "select * from account where id ="+id+" and name = '"+name+"'";

如果想要避免这种情况,可以采用prepareStetement()方法。sql语句的定义也要随之改变。

sql3 = "select * from account where name = ? and id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql3);
Statement stmt = conn.Statement();
//在给prepareStatement()方法时,就会进行检查SQL语法和编译SQL这两步,剩下的只剩执行SQL语句
//sql中的?需要单独设置
pstmt.setString(1,name);
pstmt.setInt(2,1);
//如果多次执行该语句,那么只需要对?部分进行更换即可,不需要再次进行语法检查和编译,只需要进行执行
//PreparedStatement()会对内部值进行转译,将'变成\',恢复其原本的含义,防止字符串拼接
//PreparedStatement()还可以通过预编译来提高速度,提高性能

sql语句在MySQL中执行时可以分为三步,1.检查语法2.编译语句3.执行SQL,采取该方法可以进行预编译,节省大量时间,同时要在上文中的配置文件中使用useServerPrepStmts=true开启预编译功能。 同样的,在执行SQL语句这一步,两种方法也有着一些不同之处: PreparedStatement pstmt = conn.prepareStatement(sql3);以及获取完毕SQL语句,所以直接执行就行,当然,在执行之前需要先获取执行对象。

PreparedStatement pstmt = conn.prepareStatement(sql3);
Statement stmt = conn.Statement();
res =  pstmt.executeQuery();

而createStatement()需要在执行时传入sql

res = stmt.executeQuery(sql);

同时,JDBC也能使用数据库事务

//Connection还可以用来进行事务管理
//事务本身分为开启事务:begin/start transaction
//提交事务:commit
//回滚事务:rollback
//Connection中定义了三个对应的方法
//开启事务:conn.setAutoCommit(bollean autoCommit);autoCommit为是否自动提交事务,false则为手动提交
//提交事务:conn.commit();
//回滚事务:conn.rollback();

可以使用try——catch来配合事务

try{
    conn.setAutoCommit(false);
    //执行sql
    //成功则提交事务
    conn.commit();
}catch (){
    //失败则回滚事务
    conn.rollback();
}

然后是必备的释放资源环节

res.close();//查询返回的结果集合也要释放
stmt.close();
conn.close();

接下来是整体代码

package mysql;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mysql.jdbc.Driver;
//使用数据库连接池可以做到1.资源重用2.提升系统响应速度3.避免连接遗漏(将本来申请的但是不用的连接分配给新的用户)
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class JDBC {
    public static void main(String[] args) throws Exception{
        //用List来保存查询后的数据
        List<User> qur_res = new ArrayList<>();
        //注册驱动,这行代码可以不写了
        Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        //DriverManager有两个作用,注册驱动还有获取连接
        // jdbc:mysql://ip:端口/数据库名称?参数键值对1&参数键值对2
        //连接本机数据库时可以不写IP和端口
        //可以通过禁用安全连接来解决报错警告,useSSL = false,等号前后不要加入空格
        //prepareStatement预编译功能必须要在url中首先开启useServerPrepStmts=true
        String url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useServerPrepStmts=true";
        String userName = "root";//
        String password = "123456";
        //Connection可以连接数据库,执行sql语句并返回结果
        //DriverManager.getConnection会被连接池所取代
//        Connection conn = DriverManager.getConnection(url,userName,password);
        //Druid连接演示1.导入jar,2.完善配置文件,配置文件放在src下面3.加载配置,但是需要根据需求改
        //加载配置文件
        Properties prop = new Properties();
        //文件路径可能出现问题,可以使用System.getProperty()来查看当前文件位置,并对文档路径进行修正
        System.out.println("path="+System.getProperty("user.dir"));
        prop.load(new FileInputStream("JDBC_demo/src/druid.properties"));
        //获取连接池对象
         DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
         //获取连接
        Connection conn = dataSource.getConnection();
        //定义sql
        String sql1 = "update account set money = 2000 where name = '张三'";
        String sql2 = "select * from account";
        //Connection中有三种方法来获取执行对象
        //createStatement():最为普通的获取sql对象的方法
        //prepareStetement():预编译sql的执行对象,防止sql注入
        //prepareCall():执行存储过程的对象,该方法不常用
        //Connection还可以用来进行事务管理
        //事务本身分为开启事务:begin/start transaction
        //提交事务:commit
        //回滚事务:rollback
        //Connection中定义了三个对应的方法
        //开启事务:conn.setAutoCommit(bollean autoCommit);autoCommit为是否自动提交事务,false则为手动提交
        //提交事务:conn.commit();
        //回滚事务:conn.rollback();
        //Statement分为两种,第一种为executeUpdate(sql),负责执行DML和DDL语句,也即增删改和创建,权限变更等操作
        //一种为executeQuery(sql),负责执行查询语句
        Statement stmt = null;
        ResultSet res = null;
        int count = 0;
        try {//可以用try——catch来完成事务回滚
            conn.setAutoCommit(false);
            stmt = conn.createStatement();
            //执行sql1,返回值为受影响行数
            count = stmt.executeUpdate(sql1);
            //ResultSet为执行查询语句后返回的结果,封装了DQL查询语句的结果,其实就是保存查询返回的集合
            //boolean next() 1.将光标向下移动一行 2.判断当前行是否为有效行
            //getxxx(参数) 获取数据,比如getInt,参数可以为int,表示为列的编号,从1开始,或者给出列的名称
            res =  stmt.executeQuery(sql2);//res保存返回的部分表
            //利用循环遍历res表
            while(res.next()){
                User user = new User();
                int id = res.getInt(1);
                String name = res.getString(2);
                Double money = res.getDouble(3);
                System.out.println(id+","+name+","+money);
                user.setId(id);
                user.setName(name);
                user.setMoney(money);
                qur_res.add(user);
            }
            System.out.println(qur_res.get(0).getId());
            int id = 1;
            String name = "zhang";
            String sql3 = "select * from account where id ="+id+" and name ='"+name+"'";
            res = stmt.executeQuery(sql3);
            if(res.next()){
                System.out.println("win");
            }else {
                System.out.println("lose");
            }
            Thread.sleep(10000);
            name = "'or '1' = '1";//sql注入演示,通过截断密码来绕过密码判断
            sql3 = "select * from account where id ="+id+" and name = '"+name+"'";
            System.out.println("sql3="+sql3);
            res = stmt.executeQuery(sql3);
            if(res.next()){
                System.out.println("win");
            }else {
                System.out.println("lose");
            }

            //可以使用prepareStatement来防止sql注入
            sql3 = "select * from account where name = ? and id = ?";
            //同一个连接可以更换执行方式
            //MySQL的执行过程包括三步1.检查SQL语法,2.编译SQL,3.执行SQL
            PreparedStatement pstmt = conn.prepareStatement(sql3);
            //在给prepareStatement()方法时,就会进行检查SQL语法和编译SQL这两步,剩下的只剩执行SQL语句
            //sql中的?需要单独设置
            pstmt.setString(1,name);
            pstmt.setInt(2,1);
            //如果多次执行该语句,那么只需要对?部分进行更换即可,不需要再次进行语法检查和编译,只需要进行执行
            //PreparedStatement()会对内部值进行转译,将'变成',恢复其原本的含义,防止字符串拼接
            //PreparedStatement()还可以通过预编译来提高速度,提高性能
            res =  pstmt.executeQuery();
            System.out.println("PreparedStatement:");
            if(res.next()){
                System.out.println("win");
            }else {
                System.out.println("lose");
            }
            //int i = 3/0;//人为的制作异常
            conn.commit();
            pstmt.close();
            //自动给出的异常类为SQLException,人为造异常的时候给出更大的异常类的Exception
        } catch (Exception throwables) {
            conn.rollback();
            throwables.printStackTrace();
        }
        //释放资源
        System.out.println(count);
        res.close();
        stmt.close();

        conn.close();
    }
}