我竟然把JDBC忘了,一篇适合新手和老手的JDBC完整介绍(详细的不要不要的)

宁愿辛苦一阵子,不要辛苦一辈子
在这里插入图片描述

JDBC

什么是JDBC?

Java Database Connectivity:java连接数据库

JDBC是代表一组API,一个独立于特定数据库管理系统,通用的SQL数据库存取和操作的公共接口(一组API).

SUN公司为了使java代码可以跨数据库,既是指数据库换了,我们JDBC的代码不改变(或少改变)设计了一组公共的接口(标准),规定了所有操作数据库的代码,应使用哪些类型,哪些方法

这些操作数据库的具体代码由数据库厂商来实现,这些实现类,我们称之为数据库驱动,这就意味着,你要连接和操作数据库,就必须加载数据库的驱动程序

那么java代码就可以通过接口+驱动+标准的SQL语句实现java代码和各种数据库的连接和操作.

JDBC新手(实现最普通功能的JDBC)

操作步骤详解:

1.加载数据库驱动和注册驱动

有两种方式:

(1)是直接在源码库中引入,相当于绝对路径,但是是引入型的,将项目打包时,并不会打包驱动的jar文件(占得内存小,但是打包时,会丢失驱动)

(2)创建文件,导入型,在项目中创建目录并导入,这是相当于把jar驱动复制在了项目中,打包时会携带(不会丢失驱动,但是占的内存比较大)

注册驱动:

Class.forName("com.mysql.jdbc.Driver");

2.获取连接

url = "jdbc:mysql://localhost:3306/test";

​ 主协议 协议 主机名 端口号 数据库名

user = "数据库账号";

password = "数据库密码";

Connection ct =  DriverManger.getConnection(url,user,password);

3.执行sql语句:

sql = "insert into dept values(6,'张三',15)";

Statement st = ct.createStatement();

int len  = st.executeUpdate(sql);
System.out.println(len>0?"添加成功":"添加失败");

如上是添加数据,凡是添加,删除,修改都是修改操作,都调用executeUpdate()方法,方法的参数是sql语句,返回值是len(int类型),凡是select语句都调用executeQuery()方法,遍历查询,返回值是一个set,要用ResultSet接受

4.关闭连接

st.close();
ct.close();

分开代码,各位客官可能不容易记忆和查看

整体代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
 *
 * 1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象
 * 2.获取连接,即登录
 * 3.执行sql
 *      (1)编写sql语句
 *      (2)创建Statement对象
 *      (3)用Statement来执行sql操作,并接受结果
 *  4.关闭连接
 *
 * API:
 * java.sql.Connection;接口,代表连接
 * java.sql.DriverManager, 代表类,驱动管理类
 * java.sql.Statement;
 *
 *协议,主机地址,端口号,路径(查询字串)
 * 用java添加一个人到test1库中dept(id,name,money)表中
 */
public class dome1 {

    //先导入驱动数据库驱动
    
    public static void main(String[] args) throws Exception {
        
        //1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象
        //Class.forName("org.git.mm.mysql.Driver");//旧版驱动注册方式
        Class.forName("com.mysql.jdbc.Driver");//新版驱动注册方式
        
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/test1";
        String user = "root";
        String password = "123456";
        //驱动已经加载了,现在用驱动管理类DriverManager
		Connection  connection = DriverManager.getConnection(url,user,password);
        
        //3.执行sql语句
        	//3.1编写sql语句
        String sql = "insert into dept values(6,'雷',15)";
        //要把sql语句发给服务器端执行,并接受他返回的结果
          	//3.2创建Statement对象
        Statement statement = connection.createStatement();
        	//3.3用Statement来执行sql操作,并接受结果
        //凡是insert,delete,update语句都是更新数据库
        //凡是select都是查询query
        int len =statement.executeUpdate(sql);//返回一个整数,表示多少行收到影响

        System.out.println(len>0?"添加成功":"添加失败");


        //4.关闭连接
        statement.close();
        connection.close();

    }


}

JDBC强者(对普通JDBC优化)

强者就是能一次干到多个敌人(sql语句),先讲个开胃菜,和优化无关,但是我没放到基础里面,怕让新手感到恐惧(其实我多想了,大家都是很强的)

关键的方法"摘"出来(有些方法,我感觉必须得对数据库事务有些了解,才能食用)

  • commit()提交事务
  • rollback()回滚事务
  • setAutoCommit() 参数为false时为手动提交模式,为true时当然是自动提交sql代码模式
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author 雷雨
 * @date 2020/4/22 16:44
 *
 * 用数据库来处理事务
 * mysql中默认是自动提交事务的,执行一句,提交一句
 *
 * 需求
 * java中JDBC操作实现:
 * 1.在数据库中修改名字为小雷的名字修改为胡歌
 * 2.在数据库中插入新的数据name为张翰,薪资为21
 *
 * 要求这是一个数据库事务的操作,
 * 即要么都操作,要么都不操作
 *
 */
public class TestTransAction {

    public static void main(String[] args) {
        Connection connection = null;
        //1.注册驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");

            //2.创建连接(因为是数据库事务,所以我们要实现的是在执行多个sql语句时,保证是一个连接)
            String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8";
            String user = "root";
            String password ="123456";
            connection = DriverManager.getConnection(url, user, password);

            connection.setAutoCommit(false);
            updata(connection);
            insert(connection);

            connection.commit();
        } catch (Exception e) {
            System.out.println("失败");
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                connection.setAutoCommit(true);
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }



    }
    public static void updata(Connection connection)throws Exception{

        String sql = "UPDATE dept set name=? where name=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,"胡歌");
        preparedStatement.setObject(2,"小白");
        int len = preparedStatement.executeUpdate();

        preparedStatement.close();

    }
    public static void insert(Connection connection)throws Exception{
        String sql = "INSERT into dept values(NULL ,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,"张翰");
        preparedStatement.setObject(2,21);
        int len = preparedStatement.executeUpdate();

        preparedStatement.close();

    }

}

Statement中可能遇到的问题

1.在需要控制台输入多个参数来进行操作是,往往需要sql语句的拼接,而sql语句的拼接是很麻烦的,很容易出错.

2.在控制台输入参数作为sql语句执行(查询中可能遇到导出数据库的情况)的一部分时,可能造成sql语句的注入,也是由于引号问题造成的.造成盗窃信息(关键)

3.sql拼接不支持blob等二进制类型(比如我们需要给数据库中存储一个照片,一般不把照片存入数据库,因为照片占用的内存很大,影响效率,但是的确存在的情况,这时我们的sql拼接是不支持二进制路劲插入到sql语句的操作,会导致sql语句错误)

这里对前面所说的在执行sql语句中可能出现的问题做一个实例

下面这个例子我们可以看到在数据库只有三列属性的时候,我们的sql语句的拼接,已经很麻了,很容易发生错误.(虽然这不是很关键)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Scanner;

/**
 *
 * 描述Statement的第一类问题:sql拼接
 *需求:在test数据库中,从java控制台中传入多个参数(id和money),来插入这个人的信息
 * Statement中的第一个问题sql语句的拼接,虽然能够正常的运行,但是在sql语句的拼接过程
 * 中因为引号很复杂,所以很容易发生错误
 */
public class question1 {

    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要插入的用户的id");
        String id = scanner.nextLine();
        System.out.println("请输入要插入的用户的姓名");
        String name = scanner.nextLine();
        System.out.println("请输入要插入的用于的money");
        String money = scanner.nextLine();

        //Register Database Driver
        Class.forName("com.mysql.jdbc.Driver");

        //Get A Database Connection
        String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8";
        String user = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, user, password);

        //Execute sql statement
        String sql = "insert into  dept(id,name,money) values ('"+id+"','"+name+"','"+money+"')";
        Statement statement = connection.createStatement();
        int len = statement.executeUpdate(sql);
        System.out.println(len>0?"添加成功":"添加失败");

        //Close connection
        statement.close();
        connection.close();
        scanner.close();
    }

}

第二个实例:sql注入


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

/**
 * @author 雷雨
 * @date 2020/4/21 23:07
 *
 * 需求:从键盘输入name,根据这个name查询,属于这个id的其他信息
 *
 *Statement中可能出现的第2个问题就是:sql语句的注入
 *
 */
public class question2 {

    public static void main(String[] args) throws Exception{

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要查询的id");
        String name =scanner.nextLine();
        Class.forName("com.mysql.jdbc.Driver");

        String url = "jdbc:mysql://localhost:3306/test1";
        String user = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url,user,password);

        String sql = "select * from dept where  name ='"+name+"'";
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()){
            System.out.print(resultSet.getObject(1)+"\t");
            System.out.print(resultSet.getObject(2)+"\t");
            System.out.print(resultSet.getObject(3)+"\t");
        }

        resultSet.close();
        statement.close();
        connection.close();
        scanner.close();
    }
}

这个很重要,当我们输入一个正确的人名的时候,想必各位客官都知道能正常的输出,但是如果输入一个:胡歌' or '1'='1(数据库中已有name为胡歌的),这时是会报异常呢,还是怎样呢?

在这里插入图片描述

各位可能看了截图就明白了:哦,原来说的sql注入是这样啊,其实我们在控制台的这个输入,就是用了sql语句写在字符串中的特性,将其引号,根据我们的想法拆解,最终得到了一个sql语句运行永远都是true,所以我们轻松的把数据库中的所有人的信息都盗取出来了.

听我解释的还有点懵的客官,返回代码中认真的分析引号,可以试着把我的输入带进去拆解,看是不是会有新的思维碰撞呢.

对于这些令人烦心的麻烦,我们接下来就要看怎么解决

用PreparedStatement来解决sql拼接sql注入sql无法解析二进制文件的问题

这一部分,我将操作的步骤都总结出来,因为比较简单,我不做多的赘述,实例代码中也有部分注释,会给大家一些提示和思考.

需要修改的部分

1.sql语句中用?占位符来代替需要传入的字符(数据库中的属性列,或者二进制的路径)

2.用连接对象(connection)创建preparedStatement对象,并传入sql语句

3.perparedStatement对象调用setobject()方法传入属性列或二进制

4.调用执行sql语句的方法,这时不用再传入sql语句了

import java.sql.*;
import java.util.Scanner;

/**
 *这里只列举一种,其他的解决方式是类似的,不做赘述
 */
public class resolve1 {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要插入的用户的id");
        String id = scanner.nextLine();
        System.out.println("请输入要插入的用户的姓名");
        String name = scanner.nextLine();
        System.out.println("请输入要插入的用于的money");
        String money = scanner.nextLine();

        //Register Database Driver
        Class.forName("com.mysql.jdbc.Driver");

        //Get A Database Connection
        String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8";
        String user = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, user, password);

        //Execute sql statement
        String sql = "insert into  dept(id,name,money) values (?,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,id);
        preparedStatement.setObject(2,name);
        preparedStatement.setObject(3,money);
        int len = preparedStatement.executeUpdate();
        System.out.println(len>0?"添加成功":"添加失败");

        //Close connection
        preparedStatement.close();
        connection.close();
        scanner.close();
    }
}

优化后JDBC代码仍然有的缺陷

1.注册驱动,获取连接的代码我们重复了很多遍(加大了程序员的工作量和代码复用率很低)

2.每次我们都从mysql中获取连接

  • mysql是一个YCP/IP的网络程序,每次获取连接的成本很高(需要三次握手,四次挥手等)

  • 每个客户端都有单独的线程来维护它的请求,这是会出现的问题:

    (1)如果很多的客户端都同时去访问服务器,那么mysql的并发量增大,可能会挂,特别是遇到一些程序员,获取连接后不关闭,那么服务器的并发太高会挂

    (2)每次高成本获取的链接只使用了一次,太奢侈

我们需要的是:一次获取,多次使用

JDBC大神(高级)

(1)解决问题一:我们把注册驱动,获取连接封装到一个工具类,不仅可以减少代码量,也为后期代码的重构提供了便利

(2)解决问题二:我们可以使用"数据库连接池"来解决

什么叫数据库连接池技术?

(1)先创建一个连接池pool,然后在池中先放一些对象,然后程序需要的使用,先去连接池pool中看有没有对象,如果有,就不用创建.

(2)我们还可以设置连接池的最大连接数量,如果池中所有连接都在使用的话,那么就让客户端"等待",这样虽然有等待的情况,但是比服务器"挂"了更好一点.

(3)在创建连接池时,可以先创建少量的连接,等用户连接数高时,再创建多个连接,直到最大连接数量为止.

(4)之前connection.close()相当于真的与服务器断开连接,而在连接池中connection.close的方法是把连接的对象返回给连接池.

总结一下使用数据库连接池的特点:

1.资源重用

由于数据库连接是重用的,避免了频繁的创建,释放连接引起的重大的性能开销,在减少了系统开销的基础了,还增加了系统运行的平稳性

2.更快的反映速度

数据库连接池在初始化中已经创建了少量的连接置于连接池中备用.此时连接的初始化工作均已完成,对于业务请求而言,直接利用现有的连接,而不用自己去创建连接,避免了在创建和释放连接过程中的时间开销,从而减少系统反应时间

3.新的资源分配手段

对于多应用同享一个数据库的系统而言,可在应用层通过连接池的配置,实现某一应用最大连接数的限制,避免某一应用独占所有的资源.

4.统一的资源管理,避免数据库连接泄露

在较为完善的数据库连接池中实现,可根据预先的占用超时的设定,强制收回被占用的资源,从而避免了常规数据库中的连接泄露(练级泄露简单来讲就是只连接,而不释放).

有些客官会问了如何使用数据库连接池?

使用数据库连接池(德鲁伊)

在这里插入图片描述

步骤

1.引入"德鲁伊"jar包

2.加一个配置文件,配置德鲁伊连接池的参数

数据库连接池的作用:管理连接,

所以我,们从这点出发我们需要配置的参数有:

主机名,端口号,用户名,密码,驱动类名

其他需要配置的参数:初始化连接数,最多连接数…

3.怎么写配置:

(1) 在src文件下(其实并不局限于,但是为了我们后期代码方便操作,否则会抛出java.lang.NullPointerException异常)创建一个.properties的文件写入

url =jdbc:mysql://localhost:3306/test1?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8" 
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall

4.创建连接池

5.创建一个方法:可以在数据库连接池中拿对象

哎呀,成堆的概念看的人头疼,对于程序员来讲,没有什么是实战更有用的学习方式了

于是,小二,上代码:

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 *
 *
 * 连接池的使用
 */
public class JDBCUtils {

    private static DataSource ds;//这个的作用在后面,相当加载连接
    private static ThreadLocal<Connection> local;

    static {
        //静态代码块可以用来初始化我们的静态变量
        //(1)把druid.properties文件的数据加载到一个properties的对象中

        try {
            Properties properties = new Properties();
            properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));//这里要传一个类的加载器的对象
            ds = DruidDataSourceFactory.createDataSource(properties);
            local = new ThreadLocal<>();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    // 这段代码可以使用,但是不能保证在同一线程中(客户端)能共享同一个连接对象
    //后面的代码如果用到数据库事务的处理时,就发生问题,不能回滚等
//    public  static Connection getConnection() throws SQLException {
//        return ds.getConnection();
//    }



    //这里修改为ThreadLocal来保存同一线程的共享变量:
    public  static Connection getConnection()  throws SQLException{
        //如果能够在local中能拿到一个连接对象,那么说明当前线程已经拿过了
        Connection connection = local.get();
        //如果不能获取连接对象,说明之前没拿过
        if(connection == null){
            connection=ds.getConnection();
            local.set(connection);
        }
        return connection;
    }


    //提供一个关闭连接的方法:
    public static void free(){
        Connection connection =local.get();
        if(connection!=null){

            local.remove();
            //还原数据库为自动提交模式,这样下次再拿到这个连接时,就默认是自动提交
            try {
                connection.setAutoCommit(true);
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


}

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Scanner;

/**
 * 需求:
 * 从键盘输入一个dept库的信息(id,name,money),存储到dept表中
 *
 */
public class TestJdbcUtils {

    public static void main(String[] args) throws Exception{

        //1.键盘输入
        Scanner scanner = new Scanner(System.in);

//         System.out.println("请输入要插入的id");
//         String id = scanner.nextLine();

        System.out.println("请输入要插入的name");
        String name = scanner.nextLine();

        System.out.println("请输入要插入的money");
        String money = scanner.nextLine();

        //2.获取连接,这里我们使用的是我们自己写的工具类,不使用原来的方法
        Connection connection = JDBCUtils.getConnection();

        //3.编写sql语句
        String sql ="insert into  dept(name,money) values(?,?)";

        //4.创建PreparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.set?的值
//        preparedStatement.setObject(1,id);
        preparedStatement.setObject(1,name);
        preparedStatement.setObject(2,money);

        //6.执行更新
        int len = preparedStatement.executeUpdate();
        System.out.println(len>0?"添加成功":"添加失败");

        //7.关闭
        preparedStatement.close();
        JDBCUtils.free();
        scanner.close();
    }

}

还剩一个封装自己的BasicDAO,但是夜深了,肝不动了,明天还有网课,我太难了,今天的文章就写到这吧,下次专门写一篇博客吧.最后再把今天的毒鸡汤和各位共食:宁愿辛苦一阵子,不要辛苦一辈子,一起努力吧.

发布了83 篇原创文章 · 获赞 28 · 访问量 5367

猜你喜欢

转载自blog.csdn.net/qq_40742223/article/details/105697955
今日推荐