让我们来谈谈JDBC

1.JDBC
    1)JDBC简介
        - JDBC就是Java中连接数据库方式
        - 我们可以通过JDBC来执行SQL语句。
 
    2)获取数据库连接

 

  - java.sql.Connection 数据库连接
        - 我们对数据库的一切操作都是从获取Connection开始
        - 获取数据库连接的四个参数:
            1.数据库的地址 url
                语法:jdbc:子协议:厂商内容
                MySQl的格式:jdbc:mysql://主机名:端口号/数据库名字
                例子:jdbc:mysql://localhost:3306/test
 
            2.用户名 user 连接数据库使用的用户名
            3.密码 password 数据库的密码
            4.数据库驱动全类名 driverClass
 
        - 基本步骤:
            1.导入数据库驱动的jar包
                mysql-connector-java-5.1.37-bin.jar
            2.准备四个参数
                - url
                - user
                - password
                - driverClass
            3.加载数据库驱动
                Class.forName(driverClass)
            4.通过DriverManager来获取数据库连接   
                static Connection getConnection(String url, String user, String password)
 
        - 问题:
            - 在JDBC的代码中有如下一行代码:
                Class.forName("com.mysql.jdbc.Driver");
                - 我们发现这行代码和其他的代码没有上下文关系,就是不写他编译也不会出错。
                    而且,这行代码不写也好使,这是为什么呢?
                    - Class.forName() 主要是将一个类加载进的虚拟机。
                    - 通过观察mysql的Driver的源码,发现类中有一个静态代码块:
                        static {
                            try {
                                java.sql.DriverManager.registerDriver(new Driver());
                            } catch (SQLException E) {
                                throw new RuntimeException("Can't register driver!");
                            }
                        }
                    - 静态代码块会在类被加载进虚拟机时就会执行,也就是当我们调用          Class.forName("com.mysql.jdbc.Driver");
                        以后,该静态代码块会立即执行。
                        而静态代码块中,有如下代码:
                            java.sql.DriverManager.registerDriver(new Driver());
                        这个代码会将数据库驱动注册进DriverManager中。
 
                    - 但是我们这个东西不写也好使,因为在JDBC4以后,程序会自动加载数据库驱动。
                    - 虽然是这样,但是我们要求这行代码必须写。尤其是在web工程,更是必须写。   
 
 
        - 核心类:
            - java.sql.DriverManager
                - 数据库驱动的管理器,负责加载数据库的驱动获取数据库连接
                - static Connection getConnection(String url, String user, String password) 
                    - getConnection方法用来通过url地址,用户名,密码等参数来获取数据库连接的
 
            - java.sql.Connection
                - 数据库连接
                - Statement createStatement() 
                    - 创建一个Statement对象,通过Statement对象来执行SQL语句
 
            - java.sql.Statement
                - SQL语句的执行器
                    - boolean execute(String sql) 
                        - 执行一条SQL语句,并返回一个布尔值,执行成功返回true,执行失败返回false。用的不多
                    - ResultSet executeQuery(String sql) 
                        - 执行查询的SQL语句,并返回一个结果集
                    - int executeUpdate(String sql) 
                        - 执行修改数据的SQL语句(增删改),并返回受影响的行数
 
            - java.sql.ResultSet
                - 查询到的数据的结果集,我们通过JDBC查询数据库获得的数据,都封装在ResultSet中
                - boolean next() 
                    - 控制光标向下移动一行,如果光标当前位置是afterLast则返回false,告诉你没数据了,就别读了。
                        如果光标移动以后,没有在afterLast则返回true,可以读取数据。
 
                - 在ResultSet有很多getXxx(int),比如getString(),getInt(),getByte()。
                    通过这些方法可以读取当前行的数据,它们需要一个int值作为参数,
                        int指的是读取数据的列数。
                    列数是从1开始的。
 
                - 在ResultSet中还有很多getXxx(String),它和上边的方法的作用一致,
                    只不过它们需要的都是String类型的参数,参数代表的是当前的列名,
                        比如:我们要获取id的值
                            getInt("id")
                        要获取name的值
                            getString("name")
                    注意:如果查询的SQL使用了别名,则列名以别名为准。
 
    3)数据的增删改
        //创建一个SQL执行器
        Statement stat = conn.createStatement();
 
        //创建一个SQL语句
        String sql = "INSERT INTO t_stu(`name` , age) VALUES('沙僧',28)";
 
        //执行SQL语句
        //executeUpdate用来执行一条修改SQL的语句
 
        //它需要一个String类型sql作为参数,并会返回一个int型的值,该值表示SQL语句执行以后影响到的行数
        int count = stat.executeUpdate(sql);
 
    4)数据的查询
        //创建Statement对象
        Statement stmt = conn.createStatement();
        //创建一个SQL语句
        String sql = "SELECT id, name sname, age FROM t_stu WHERE id=2";
        //执行查询
        ResultSet rs = stmt.executeQuery(sql);
        //控制光标下移一行
        //如果当前行有数据,则读取
        if(rs.next()){
 
            //获取id,name,age
            int id = rs.getInt("id");
            String name = rs.getString("sname");
            int age = rs.getInt("age");
 
            System.out.println(id+"--"+name+"--"+age);
        }
 
        > 查询操作和修改的主要不同的是,查询使用executeQuery(),
            它会返回ResultSet结果集,我们需要对结果集进行读取。
 
        > 当我们只需要读取一个数据时,用if。
            当需要读取全部数据时,用while
 
        > 代码的规范:
            - Connection、Statement、ResultSet,这些资源都是需要和数据建立连接的
                这些资源我们并不是总需要使用,当我们不适用这些资源,需要将这些资源关闭。
            - 关闭资源顺序:
                从后往前关:
                    先关 ResultSet
                    在关 Statement
                    最后关 Connection
            - 示例代码:
                //定义三个变量
                Connection conn = null;
                Statement stmt = null;
                ResultSet rs = null;
 
                try{
 
                }catch(Exception e){
                    e.printStackTrace();
                }finally{
 
                    if(rs!=null){
                        //关闭ResulSet
                        try {
                            rs.close();
                        } catch (SQLException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
 
                    if(stmt != null){
                        try {
                            stmt.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
 
                    if(conn != null){
                        try {
                            conn.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
 
                }
 

 

 

2.JDBC
    1) SQL注入
        > 目前我们的使用的是Statement来执行SQL语句
            而我们传递的参数时通过拼接字符串的形式添加进SQL语句
        > "SELECT * FROM t_user WHERE username='"+username+"' AND password='"+password+"'"   
        > 这种形式如果正常用户的访问,问题不大,但是一旦出现恶意的用户,
            他如果传递了这样一组参数 用户名 : a' OR 'a'='a  密码:a' OR 'a'='a
        > 这两个参数如果拼接进SQL语句,SQL语句会变成如下的状态:
            "SELECT * FROM t_user WHERE username='a' OR 'a'='a' AND password='a' OR 'a'='a'"   
        > 通过这种特殊的参数,导致我们SQL语句的语义完全改变,这样即使用户名和密码不正确也可以登录
            这样就对我们的网站带来很大的安全隐患
        > 解决:
            1.在用户输入时进行验证,验证用户名中是否包含特殊字符 ' " 等等
            2.如果我们一直使用Statement则永远都会有SQL注入的隐患,所以最佳解决方案是不使用Statement
                而是使用PreparedStatement
 
    2) PreparedStatement
        > java.sql.PreparedStatement --> 预编译的Statement
        > PreparedStatement是Statement的子接口,使用方式和Statement类似,但是它在Statement做了一些扩展。
        > 获取PreparedStatement是通过Connection的prepareStatement()的方法获取的,这个方法需要传一个SQL语句,
            当我们通过SQL语句获取一个PreparedStatement时,JDBC会将SQL语句先发送给MySQL,
                让我们的数据库对SQL语句进行预编译操作。
        > 使用PreparedStatement的好处:
            1.PreparedStatement使用占位符来替换SQL语句中的变量,更方便编写和阅读,维护起来更加的简单。
            2.MySQL在执行一条SQL语句时,要分两个步骤:
                1) 编译SQL语句,检查SQL语句是否有语法错误
                2) 执行SQL语句
                使用Statement时,每一次都需要进行这两个步骤,而MySQL可以对已经编译过的语句进行缓存,
                    使用PreparedStatement,执行一次以后,会对SQL语句进行缓存,下次再次执行时效率更高。
                这个优点体现的不明显。
            3.使用PreparedStatement时SQL语句会交给MySQL进行预编译,预编译的过程MySQL会将SQL语句,
                转换为一个类似于Java中方法的东西,而我们的那些填充的占位符,会以方法的参数的形式发送给MySQL
              这样即使传递在奇怪的参数,也不能改变SQL语义,所以它有效的避免了SQL注入的问题。
 
        > 所以我们实际的使用中绝对不能使用Statement而是使用PreparedStatement
 
    3) 大数据
        - 比较大的数据
        - 大数据主要分两种:
            1.大文本数据
            2.大的字节数据
        - mysql对文件的大小有限制,也就是超过一定的大小以后,文件将不能插入进数据库,插入时会抛出异常
        - 我们可以修改mysql对文件的大小的限制,修改my.ini文件
            在my.ini文件中添加如下配置
                max_allowed_packet = 10M
 
    4) 批处理(batch)
        - 批处理指的是一次操作中执行多条SQL语句
        - 批处理相比于一次一次执行效率会提高很多
        - 批处理主要是分两步:
            1.将要执行的SQL语句保存
            2.执行SQL语句
        - Statement和PreparedStatement都支持批处理操作,这里我们只需要掌握PreparedStatement的批处理方式:
            - 方法:
                void addBatch()
                    - 将要执行的SQL先保存起来,先不执行
                    - 这个方法在设置完所有的占位符之后调用
                int[] executeBatch()
                    - 这个方法用来执行SQL语句,这个方法会将批处理中所有SQL语句执行
 
        - mysql默认批处理是关闭的,所以我们还需要去打开mysql的批处理:
                rewriteBatchedStatements=true
            我们需要将以上的参数添加到mysql的url地址中
 
        - 注意:低版本的mysql-jdbc驱动也不支持批处理
 
    5) 事务(Transaction)
        - 在开发中我们的一个业务往往需要同时操作多个表,这些操作往往是不可分割,业务中的对数据库的多次操作,
            要么同时成功,要么全都失败。
        - 注意:我们在同一个事务中使用的数据库连接(Connection)必须是同一个。   
 
        - 事务的特性(ACID):
             原子性(atomicity)
                一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
 
            一致性(consistency)
                事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
 
            隔离性(isolation)
                一个事务的执行不能被其他事务干扰。
                    即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
 
            持久性(durability)
                持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
                    接下来的其他操作或故障不应该对其有任何影响。
 
        - 操作事务的基本步骤:
            1.开启事务
                - 开启事务以后,我们只后的所有操作将都会在同一个事务当中
            2.操作数据库
                - 开启事务以后再去操作数据库,所有操作将不会直接提交到数据库中
            3.提交事务
                - 将修改应用到数据库
            4.回滚事务
                - 数据库操作过程中出现异常了,回滚事务,回滚事务以后,数据库变成开启事务之前的状态
 
        - mysql中的事务控制
            #开启事务
            START TRANSACTION
 
            #回滚事务
            ROLLBACK
 
            #提交事务
            COMMIT
 
        - JDBC中的事务主要通过Connection对象来控制的
            1.开启事务
                void setAutoCommit(boolean autoCommit) throws SQLException;
                - 设置事务是否自动提交,默认是自动提交
                - 设置事务手动提交
                    conn.setAutoCommit(false);
 
            2.提交事务
                void commit() throws SQLException;
                - 提交事务
                conn.commit()
 
            3.回滚事务
                void rollback() throws SQLException;
                - 回滚事务
                conn.rollback()
 
        - 事务控制的格式:
            //创建一个Connection
            Connection conn = null;
 
            try{
 
                //获取Connection
                conn = JDBCUtils.getConnection();
 
                //开启事务
                conn.setAutoCommit(false);
 
                //对数据库进行操作
 
                //操作成功,提交事务
                conn.commit();
 
            }catch(Exception e){
                e.printStackTrace();
 
                //回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
 
            }finally{
                JDBCUtils.close(conn, null, null);
            }
 
    6) 数据库连接池
        > 数据库连接池就是存放数据库连接(Connection)的集合
        > 我们获取一个数据库连接是一个相对很麻烦的过程,
            如果我们获取一个数据库连接,使用一次以后就给它关闭了
                下一次再去使用的时候就要重新创建一个新的数据库连接。
        > 所以我们提出了一个数据库连接池的概念,数据库连接池放的都是数据库连接(Connection)
            我们在去使用数据库连接时候,不用再去重新创建数据库连接,而是直接从池中获取,
                使用完的数据库连接,也不是直接销毁,而是要放回到连接池。
        > 数据库连接池的常见的属性:
 
                初始连接数量:数据连接池创建以后,保存数据库连接的数量
 
                最小空闲连接数:数据库连接池最少得未使用的数据库连接的数量
 
                最大空闲连接数:数据库连接池最大闲置连接数,当闲置连接数满了以后,将不会有其他连接进入池
 
                每次增加连接数:当数据库连接都被占用以后,一次性增加的数据库连接的个数
 
                最大连接数:数据库连接池的最大容量,当最大连接数饱和了,则不再创建新的数据库连接
 
                最大等待时间:当数据库连接池饱和以后,等待获取数据库连接的时间
 
        > 常见的数据库连接池
            - 所有的数据库连接池都需要实现DataSource,当使用数据库连接池时,我们便不再需要使用DriverManger获取数据库连接
                而是使用DataSource。
                 - Connection getConnection()
                    - 从数据库连接池中获取数据库连接对象
 
            1.DBCP
                - DBCP是Apache出品的一款数据库连接
                - DBCP依赖于commons-pool
                - 使用DBCP需要导入两个jar包:
                    commons-dbcp-1.4.jar
                    commons-pool-1.5.5.jar
                - 当我们通过数据库连接池获取数据库连接以后,我们所获取到数据库连接已经不是我们熟悉的那个Connection
                    数据库连接池对Connection对象进行了包装,它修改Connection的close()方法,
                        再去调用close()数据库连接将不会真的关闭,而是要放回到数据库连接池中,供其他线程使用。
                - 核心类:
                    BasicDataSourceFactory
 
            2.C3P0(重点)
                - C3P0使用的是XML作为配置文件
                - 使用c3p0需要导入一个jar包:
                    c3p0-0.9.1.2.jar
                - 导入c3p0的配置文件:
                    1.配置文件的名字:c3p0-cofig.xml
                    2.配置文件要求放到类路径下(src)
                - 核心类:   
                    ComboPooledDataSource
                - 注意:
                    DataSource就相当于池子,我们的数据库连接都是从DataSource中获取的,
                        如果程序中有多个DataSource的实例,那么我们说你还不如不用数据库连接池。
                    所以我们的DataSource在项目中应该只有一个实例。   
 
 
 

转载请注明出处!

http://www.cnblogs.com/libingbin/

感谢您的阅读。如果文章对您有用,那么请轻轻点个赞,以资鼓励。

 
 
 
原文地址:https://www.cnblogs.com/libingbin/p/5977388.html