JAVA WEB从入门到精通 day16 JDBC学习(一)

二次信任 提交于 2019-12-06 22:58:01

JDBC第一天学习

什么是JDBC?

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。 

JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序

简单的说,JDBC提供了一系列接口来进行数据库的操作,而具体的实现则由各大数据库厂商来实现。
所以我们使用某个数据库时,首先要先将这个数据库的驱动导入,驱动本质上是实现了java.sql.Driver接口的class类。  

这里写图片描述

使用JDBC第一步之加载驱动程序

驱动程序本质上是实现了java.sql.Driver接口的class类,由各大数据库厂商实现并提供。

各个数据库驱动程序的名字是不一样的,这里我们使用mysql数据库。

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

Class.forName()方法将对应的驱动程序加载到内存中,我们打开这个驱动类的源码一探究竟。

        /* 
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }  
        */

发现驱动程序有一个静态代码块中,静态代码块当类被加载到内存中时执行并只执行一次,这个静态代码块创建一个驱动Driver的实例,然后调用DriverManager的registerDriver方法来注册驱动。 

***所以加载驱动类的目的是来注册驱动。 ***

可以使用
com.mysql.jdbc.Driver driver =new  com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver); 
来代替     Class.forName("com.mysql.jdbc.Driver");

JDBC常用的类和接口

-Driver接口

        这是驱动程序必须实现的接口。与我们没太大关系,由各大厂商实现该接口并编写驱动程序。
        *加载驱动程序:Class.forName(""); 

-DriverManager

    这是实现类,它是工厂类,用来生产和管理Driver对象。
        最常用方法: 

        Connection getConnection(url,username,password):获取连接对象

-Connection类

    这个接口可以指向一个数据库连接对象。 

    数据库连接对象通过DriverManager的getConnection()方法获取。 

    *连接数据库  Connection conn=DriverManager.getConnection(url,username,password);
            >url:要连接的数据库的地址。 mysql一般是 jdbc:mysql://localhost:3306/数据库名
            >username:数据库的用户名
            >password:数据库的密码

    常用方法:

            -prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。 

            -prepareCall(sql):创建执行存储过程的callableStatement对象。 

            -setAutoCommit(boolean autoCommit):设置事务是否自动提交。 

            -commit() :在链接上提交事务。 

            -rollback() :在此链接上回滚事务。 

            -Statement createStatement():创建一个 Statement 对象,该对象将生成具有特定类型和并发性的ResultSet 对象 

-Statement

    用于执行SQL语句的接口,通过Connection的createStatement()方法获得。

        常用方法:
            -boolean execute(String sql):运行语句,返回是否有结果集为boolean值,如果execute()方法执行的是更新操作,那么还要调用int getUpdateCount()方法来获取影响的行数。如果执行的是查询操作,那么要调用ResultSet getResultSet()方法来获取查询结果。  

            -ResultSet executeQuery(String sql):运行select语句,返回ResultSet结果集。 

            -int executeUpdate(String sql):运行insert/update/delete操作,返回更新的行数。 

            -addBatch(String sql) :把多条sql语句放到一个批处理中。 

            -executeBatch():向数据库发送一批sql语句执行。 

-Resultset:

    用于指向结果集对象的接口,结果集是由Statement的excute等方法得到。 

        ResultSet提供了查找不同列的方法。
        -getString(int index)、getString(String columnName):获得在数据库里是varchar、char等类型的数据对象。 

        -getFloat(int index)、getFloat(String columnName):获得在数据库里是Float类型的数据对象。 

        -getDate(int index)、getDate(String columnName):获得在数据库里是Date类型的数据。 

        -getBoolean(int index)、getBoolean(String columnName):获得在数据库里是Boolean类型的数据。 

        -getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据  


        还有一些对行操作的方法。要看结果集是否可滚动,如果不可滚动,则只能使用next()方法来移动。如果可滚动,则可以使用beforeFirst(),previous()等方法来进行滚动。 

        默认结果集是不可滚动的。 

        -boolean next():移动到下一行
        -Previous():移动到前一行
        -absolute(int row):移动到指定行
        -beforeFirst():移动resultSet的最前面,第一行的前面。
        -afterLast() :移动到resultSet的最后面,最后一行的后面。
        -int getRow():获取当前是第几行

        获取结果集元数据
        ResultSetMetaData rs=resultset.getMetaData();

        ResultSetMetaData中有一些常用的方法 


            - int getColumnCount():获取ResultSet中的列数
            - String getColumnName(int column:获取指定列的名称 

        可以通过这个对列进行遍历。            


 我们怎么来让结果集变成可滚动的呢?  

 这就要依赖Connection类的createStatement()方法
 我们一般使用第一个无参数的方法,结果集默认不滚动。

-Statement createStatement():创建一个 Statement 对象,该对象将生成具有特定类型和并发性的ResultSet 对象 

-statement createStatement(int resultSetType,int resultSetConcurrency):创建一个statement对象,该对象将生成具有给定类型和并发性的 ResultSet结果集对象。 

         ResultSet结果集特性:是否可滚动,是否敏感,是否可更新。 

         我们一般使用第一个无参数的方法,该方法生成的结果集不可滚动,不敏感,不可更新。
         我们可以使用第二个方法来指定结果集的特性。
         两个参数
         第一个参数
         -resultSetType 结果集类型,值为三个其中之一
            *ResultSet.TYPE_FORWARD_ONLY :不滚动结果集 

            *ResultSet.TYPE_SCROLL_INSENSITIVE :滚动结果集,结果集数据不会跟随数据库而变化,也就是不敏感  

            *ResultSet.TYPE_SCROLL_SENSITIVE :滚动结果集,结果集数据跟随数据库变化,敏感。但是几乎所有数据库厂商都没有实现。  

         第二个参数
         -resultSetConcurrency 并发类型,值为以下两个其中之一
            *ResultSet.CONCUR_READ_ONLY :结果集为只读,不能修改结果集而影响数据库
            *ResultSet.CONCUR_UPDATABLE :结果集是可更新的,对结果集的更新影响数据库。

JDBC的使用步骤

        (1)注册驱动
                两种方式
                -Class.forName("com.mysql.jdbc.Driver") 推荐这种,不会对具体驱动类产生依赖。
                -DriverManager.registerDriver(com.mysql.jdbc.Driver)

        (2)建立链接
                Connection conn=DriverManager.getConnection(url,username,password);

        (3)对数据库进行操作,处理执行结果等。

        (4)释放资源
                先创建的后关闭。

案例:使用JDBC实现增删改操作

            public class Demo {

                    public static void main(String[] args) throws ClassNotFoundException, SQLException {
                        Class.forName("com.mysql.jdbc.Driver");                 //加载驱动类
                        String url="jdbc:mysql://localhost:3306/mysql1";
                        String name="root";
                        String password="123456";
                        Connection cn=DriverManager.getConnection(url, name, password); //建立连接

                        Statement st=cn.createStatement();        //获取statement对象,用来操作数据库
                        //String insertsql="INSERT INTO STU(ID,NAME,GENDER,AGE) VALUES('0003','王五','women',36)";  //插入数据
                        //st.executeUpdate(insertsql);                                                        //调用executeUpdate方法运行语句

                        //String updatesql="UPDATE STU SET NAME='赵六' WHERE ID='0003'";     //修改数据
                        //st.executeUpdate(updatesql);

                        String deletesql="DELETE FROM STU WHERE ID='0003'";                  //删除数据
                        st.executeUpdate(deletesql);
                    }

                }

案例:使用JDBC实现查询操作

public class Demo {

                public static void main(String[] args) throws ClassNotFoundException, SQLException   {      
                    Connection cn=null;                            //创建引用
                    Statement st=null;
                    ResultSet rs=null;
                    try{
                        Class.forName("com.mysql.jdbc.Driver");
                        String url="jdbc:mysql://localhost:3306/mysql1";
                        String name="root";
                        String password="123456";
                        cn=DriverManager.getConnection(url, name, password);  //创建实例

                        st=cn.createStatement();
                        String selectsql="SELECT * FROM STU";

                        rs=st.executeQuery(selectsql);   //执行查询语句
                        while(rs.next())
                        {
                            //System.out.println(rs.getString(1)+","+rs.getString(2));
                            // //可以通过列编号或者列名称来获取对应的列值,1就对应第一列
                            System.out.println(rs.getString("ID")+","+rs.getString("NAME"));
                        }
                    }catch (Exception e) {
                        throw new RuntimeException(e);
                    }finally {
                        if(rs!=null) rs.close();            //先判断是否为空,否则可能抛出空指针异常,提高代码健壮性。
                        if(st!=null) st.close();
                        if(cn!=null) cn.close();
                    }

                }

            }

PreparedStatement

    PreparedStatement:是Statement接口的子接口。
        优点:
            -防止SQL攻击
            -提高代码可读性和可维护性
            -最大可能提高性能,对SQL语句进行预编译,数据库先对SQL模板进行校验和编译,后续执行时只需要将参数传入,不用再校验和编译,大大提高了效率。 


        如何使用PreparedStatement?
            1.写好SQL模板,参数用?占位符来代替:例如 insert into stu (name) values(?);
            2.调用Connection的PreparedStatement prepareStatement(String SQL)的方法创建一个对象来将参数化的SQL语句发送到数据库 

            3.调用PreparedStatement的setXXX方法来赋值 

案例:使用PreparedStatement插入数据

public class Demo {

                    public static void main(String[] args) throws ClassNotFoundException, SQLException {        
                        Connection cn=null;
                        Statement st=null;
                        ResultSet rs=null;
                        try{
                            Class.forName("com.mysql.jdbc.Driver");
                            String url="jdbc:mysql://localhost:3306/mysql1";
                            String name="root";
                            String password="123456";
                            cn=DriverManager.getConnection(url, name, password);   //建立连接
                            //编写SQL模板,参数用?代替
                            String sql="INSERT INTO STU(ID,NAME,GENDER,AGE) VALUES(?,?,?,?)";          
                          //获取PreparedStatement对象,来发送sql语句         
                            PreparedStatement ps=(PreparedStatement) cn.prepareStatement(sql);          
                           //通过setString,setInt等方法设置值,第一个参数为第几个问号,第二个参数为值                           
                            ps.setString(1, "0003");            
                            ps.setString(2, "皮得潘");
                            ps.setString(3, "kid");
                            ps.setString(4, "6");
                            ps.executeUpdate();                 //执行语句

                        }catch (Exception e) {
                            throw new RuntimeException(e);
                        }finally {
                            if(rs!=null) rs.close();
                            if(st!=null) st.close();
                            if(cn!=null) cn.close();
                        }

                    }

                }

java加载资源文件

例如src目录下有一个 a.txt文件,我们要获取其内容并打印。

        两种方式。
        -通过ClassLoader来实现。
        -步骤
            *先获取本类对象,通过类名.class, Class.forName()等方法都能获取。 
            *再通过类的getClassLoader()方法获取ClassLoader()
            *然后通过ClassLoader的getResourseAsStream()方法获取InputStream流。就可以对文件进行操作
            例如:
            public class Test {

                public static void main(String[] args) throws IOException {
                    InputStream in=Test.class.getClassLoader().getResourceAsStream("a.txt");  //这里文件的默认路径是src目录下,如果添加包的话,则需要加上包名
                    int b;
                    while((b=in.read())!=-1)
                    {

                        System.out.println((char)b);
                    }

                }

            }


        第二种方式
        -通过Class来实现
        -步骤
            *获取本类对象
            *然后通过Class的getResourseAsStream()方法获取InputStream流。就可以对文件进行操作
        public class Test {

            public static void main(String[] args) throws IOException {
                InputStream in=Test.class.getResourceAsStream("a.txt"); //这里文件的默认路径是当前类的所在路径,如果加包名的话,这里的位置也不用加包名
                int b;
                while((b=in.read())!=-1)
                {

                    System.out.println((char)b);
                }

            }

        }

自己编写JDBCUtils小工具类


            由于获取Connection对象太过复杂,我们将获取连接方法给封装起来,需要时调用即可

            我们还可能会碰到其他数据库,而获取不同的数据库连接不同在于 驱动类名,url,数据库名和密码不一样。

            所以我们将这四个参数放在配置文件中,可以供人们修改,而代码不变,来完成获取不同数据库的连接。

            我们先创建一个dbconfig.properties的文件,里面是四个键值对。
            dbdrivername=com.mysql.jdbc.Driver
            url=jdbc:mysql://localhost:3306/mysql1
            username=root
            password=123456

            然后我们编写工具类

            public class JdbcUtils {
                public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException
                {
                    InputStream in=Demo1.class.getClassLoader().getResourceAsStream("dbconfig.properties"); //获取资源文件的流
                    Properties props=new Properties();     //创建Properties对象
                    props.load(in);                         //加载配置文件流       

                    Class.forName(props.getProperty("dbdrivername")); //通过Properties对象的get方法获取文件里面的键值对
                    Connection conn=DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"));

                    return conn;                   //返回连接
                }

            }

面向接口编程

DAO设计模式:实现了业务逻辑层和数据访问层的分离
        特点
        -数据存储逻辑的分离:业务层无需关心具体的数据库操作。而只需要调用接口的方法操作数据库。
                          这样数据库开发人员可以专心于数据库访问的优化实现,业务开发人员则可以抛开数据库专心于业务逻辑的实现。

        -数据访问底层实现的分离:通过将数据访问分为抽象层和实现层,从而分离了数据使用和数据访问的具体实现细节。
                            这样可以在上层不变的情况下,可以随意的切换数据访问的具体实现。
                            例如:我们本来是通过XML文件来存取数据,现在我们将XML文件换成数据库,服务层保持不变,底层类只要实现数据访问的抽象层的类就可以随意切换

        -还要引入工厂模式
            由于我们需要针对不同的数据库访问机制分别提供不同的实现类,而我们的服务层不需要关心使用的是什么数据库怎么实现等等,所以我们需要创建工厂类,来实例化数据库访问的对象,工厂类只需要读取配置文件的内容来确定实现哪个数据访问的实现类对象。

所以我们一般Dao层要有一个抽象类,和具体的实现类,无论是通过哪种方式实现对数据的访问,只需要实现抽象类接口即可。
还需要创建一个工厂类,来获取实现类的对象,比如我们在Service层需要获dao层的对象,我们不需要new对象,而只需要通过工厂类来获取对象即可,而不用关心dao层具体用了什么实现类。 

比如我们前几天的登陆注册案例
就可将dao层创建一个抽象类,凡是实现该抽象类的必须实现其方法。
我们用xml存取数据来实现这个接口,我们用数据库存取数据也实现这个接口。
然后再创建一个工厂类,来获取xml实现类或者jdbc实现类的对象
我们service层只需要调用工厂类的获取方法即可,而不用知道用的到底是哪个实现类对象。
这样我们就可以很轻松的切换数据层。而不影响上一层。

util包下的Date与sql包下的时间类型之间的转换

Dao层的对象不应该暴露给其他层。
        当Dao层获取数据库中的Date,time等时间值时,获取的是sql包下的时间类型。
        当我们显示层或者服务层需要用到这个时间值,肯定不能用sql包下的时间类型,而要用util包下的时间类型。  

        我们来实现他们两者之间的转换。
        sql包下的时间类型,例如 java.sql.Time,java.sql.Date都是继承自java.util.Date类。

        (1将sql包下时间类型转换成util包下的时间类型
            java.util.Date  date=new java.util.sql();

         (2)将util包下的时间类型转换成sql包下的时间类型
            1.先将util包下的时间类型转换成毫秒值
            2.再通过sql包下的时间类型的构造方法创建

            例如:
            java.util.Date date=new java.util.Date();
            long d=date.getTime();

案例:将Mp3文件从数据库中实现存取

原理:数据库里使用Blob类型来存储字节文件。所以我们先要将MP3文件转换成字节数组。
然后使用SerialBlob类将字节数组转换成Blob类型,然后存储即可。

public class Demo2 {

    public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException {
    //fun1();           
        fun2();

    }
    public static void fun1() throws ClassNotFoundException, IOException, SQLException       
    {
        //将MP3存入数据库
        Connection conn=JdbcUtils.getConnection();    //通过工具类获取连接
        String sql="INSERT INTO MUSIC VALUES(?,?)";
        PreparedStatement pst=conn.prepareStatement(sql);
        pst.setString(1, "明星-张国荣");
        FileInputStream fis=new FileInputStream("/明星.mp3"); //读取硬盘的mp3文件
        byte[] b=new byte[1024*1024*10];            //创建字节数组
        int num=0;
        while((num=fis.read(b))!=-1)
        {
            System.out.println(num);
        }
        SerialBlob sb=new SerialBlob(b);  //将字节数组转换成Blob类型
        pst.setBlob(2, sb);              //将mp3存入数据库中
        pst.executeUpdate();
    }

    public static void fun2() throws ClassNotFoundException, IOException, SQLException
    {  
        //将mp3从数据库中取出
        Connection conn=JdbcUtils.getConnection();
        String sql="SELECT * FROM music";
        PreparedStatement pst=conn.prepareStatement(sql);
        ResultSet rs=pst.executeQuery();
        FileOutputStream fos=new FileOutputStream("e://copy.mp3"); //创建输出流
        if(rs.next())
        {
            Blob blob=rs.getBlob("song");
            InputStream in=blob.getBinaryStream(); //将数据库中的Blob类型数据转换成流
            int num=0;
            while((num=in.read())!=-1)             //读取流中的内容并写入到指定位置
            {
                fos.write((byte)num);
            }

        }
    }

}

批处理

一次性向服务器发送多条sql语句,然后由服务器一次性处理。
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException {
        Connection conn=JdbcUtils.getConnection();
        String sql="INSERT INTO NUMBER VALUES(?)";
        PreparedStatement pst=conn.prepareStatement(sql);
        for (int i=0;i<10000;i++)
        {
            pst.setInt(1, i);
            pst.addBatch();          //使用preparedStatement的addBatch方法将sql语句添加到批中
        }
        pst.executeBatch();        //执行所有语句
    }

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!