JDBC

一、JDBC介绍

JDBC(Java Data Base Connectivity,java数据库连接)。简单说:就是可以直接通过java语言,去操作数据库。
jdbc是一套标准,它是由一些接口与类组成的。

学习中涉及到的类与接口:(它们主要在两个包下)

java.sql
    类:DriverManger
    接口  Connection Statement ResultSet  PreparedStatement
            CallableStatement(它是用于调用存储过程)
javax.sql
    接口 DataSource

二、快速入门

1. 创建数据库

create table user(
   id int primary key auto_increment,
   username varchar(20) unique not null,
   password varchar(20) not null,
   email varchar(40) not null
);

INSERT INTO USER VALUES(NULL,'tom','123','tom@163.com');
INSERT INTO USER VALUES(NULL,'fox','123','fox@163.com');

2. 下载驱动

将驱动jar包复制到lib下

3. 创建一个JdbcDemo类

import java.sql.*;
// import com.mysql.jdbc.Driver;

public class JdbcDemo{
    public static void main(String[] args) throws SQLException, ClassNotFoundException{
        // 1.注册驱动
        // DriverManager.registerDriver(new Driver());
        Class.forName("com.mysql.jdbc.Driver");
        // 2.获取连接对象
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc?useSSL=false", "root", "pengxiaoye");
        // 3.通过连接对象获取操作sql语句Statement
        Statement st = con.createStatement();

        // 4.操作sql语句
        String sql = "select * from user";

        // 操作sql语句(select语句),会得到一个ResultSet结果集
        ResultSet rs = st.executeQuery(sql);

        // 5.遍历结果集
        // boolean flag = rs.next(); // 向下移动,返回值为true,代表有下一条记录.
        // int id = rs.getInt("id");
        // String username=rs.getString("username");
        // System.out.println(id);
        // System.out.println(username);

        while(rs.next()){
            int id=rs.getInt("id");
            String username=rs.getString("username");
            String password=rs.getString("password");
            String email=rs.getString("email");

            System.out.println(id+"  "+username+"  "+password+"  "+email);
        }

        //6.释放资源
        rs.close();
        st.close();
        con.close();

    }
}

JDBC操作详解:

1.注册驱动
    DriverManager.registDriver(new Driver());

    1.DriverManager类
        它是java.sql包下的一个驱动管理的工具类,可以理解成是一个容器(Vector),可以装入很多数据库驱动

        它的registDriver方法分析
              public static synchronized void registerDriver(java.sql.Driver driver)
              参数:java.sql.Driver
              我们传递的是  com.mysql.jdbc.Driver;

        在com.mysql.jdbc.Driver类中有一段静态代码块:

            static {
                try {
                    java.sql.DriverManager.registerDriver(new Driver());
                } catch (SQLException E) {
                    throw new RuntimeException("Can't register driver!");
                }
            }
        上述代码的问题:
            1.在驱动管理器中会装入两个mysql驱动.
                解决方案:使用反射
                        Class.forName("com.mysql.jdbc.Driver");

        分析:使用反射的方式来加载驱动有什么好处?
            1.只加载一次,装入一个驱动对象.
            2.降低耦合,不依赖于驱动.

2.可以通过DriverManager来获取连接对象
    Connection con=DriverManager.getConection(String url,String user,String password);
        url作用:就是用于确定使用哪一个驱动.
            mysql url:  jdbc:mysql://localhsot:3306/数据库名.
            oralce url: jdbc:oracle:thin:@localhost:1521:sid

总结:DriverManager作用:
    1.注册驱动
    2.获取连接Connection
-----------------------------------------------------------
关于url
        url格式
            主协议  子协议   主机 端口  数据库
            jdbc   :  mysql ://localhost:3306/day17

        mysql的url可以简写:
            前提:主机是localhost 端口是3306
            jdbc:mysql:///day17

        了解:在url后面可以带参数
            useUnicode=true&characterEncoding=UTF-8
------------------------------------------------------------------
2.Connection详解
    java.sql.Connection,它代表的是一个连接对象。简单说,就是我们程序与数据库连接。

    Connection作用:
        1.可以通过Connection获取操作sql的Statement对象。
            Statement createStatement() throws SQLException
            示例:
            Statement st=con.createStatement();

            了解:
                1.可以获取执行预处理的PreparedStatement对象.
                    PreparedStatement prepareStatement(String sql) throws SQLException
                2.可以获取执行存储过程的 CallableStatement
                    CallableStatement prepareCall(String sql) throws SQLException
        2.操作事务
            setAutoCommit(boolean flag);开启事务
            rollback();事务回滚
            commit();事务提交

------------------------------------------------------------------
3.Statement详解
    java.sql.Statement用于执行sql语句.
    Statement作用:
        1.执行sql
            DML:insert update delete
                int executeUpdate(String sql)

                利用返回值判断非0来确定sql语句是否执行成功。

            DQL:select
                ResultSet executeQuery(String sql)

            可以通过execute方法来执行任何sql语句.
                execute(String sql):用于向数据库发送任意sql语句

        2.批处理操作
            addBatch(String sql); 将sql语句添加到批处理
            executeBatch();批量执行
            clearBatch();清空批处理.
---------------------------------------------------------------------
4.ResultSet详解
    java.sql.ResultSet它是用于封装select语句执行后查询的结果。

    常用API
        1.next()方法
            public boolean next();
                用于判断是否有下一条记录。如果有返回true,并且让游标向下移动一行。
                如果没有返回false.

        2.可以通过ResultSet提供的getXxx()方法来获取当前游标指向的这条记录中的列数据。
            常用:
                getInt()
                getString()
                getDate()
                getDouble()
                参数有两种
                    1.getInt(int columnIndex);
                    2.getInt(String columnName);

                如果列的类型不知道,可以通过下面的方法来操作
                    getObject(int columnIndex);
                    getObject(String columnName);

----------------------------------------------------------------
5.关闭资源
    Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。

    特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

    为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。

对异常进行try-catch-finally:

Connection con = null;
Statement st = null;
ResultSet rs = null;
try{
    ......
} catch(ClassNotFoundException e){
    e.printStackTrace();
} catch(SQLException e){
    e.printStackTrace();
} finally{ //必须要释放资源
    try{
        if(rs!=null) rs.close();
    } catch(SQLException e){
        e.printStackTrace();
    }
    try{
        if(st!=null) st.close();
    } catch(SQLException e){
        e.printStackTrace();
    }
    try{
        if(con!=null) con.close();
    } catch(SQLException e){
        e.printStackTrace();
    }
}

三、JDBC api详解(重点)

只抽取到Connection

```java
public static Connection getConnection() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.jdbc.Driver");
    // 2.获取连接
    Connection con = DriverManager.getConnection("jdbc:mysql:///day17","root", "abc");
    return con;
}
```

JbdcUtils抽取

  1. 将关于连接数据库的信息定义到配置文件中:
    driverClass=com.mysql.jdbc.Driver
    url=jdbc:mysql:///day17
    username=root
    password=abc
    
    #driverClass=oracle.jdbc.driver.OracleDriver
    #url=jdbc:oracle:thin:@localhost:1521:XE
    #username=system
    #password=system
    
  2. 读取配置文件进行加载:
    public class JdbcUtils {
    
        private static final String DRIVERCLASS;
        private static final String URL;
        private static final String USERNAME;
        private static final String PASSWORD;
    
        static {   
            DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass"); // ResourceBundle可直接获取配置文件名
            URL = ResourceBundle.getBundle("jdbc").getString("url");
            USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
            PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
        }
    
        static {
            try {
                // 1.将加载驱动操作,放置在静态代码块中.这样就保证了只加载一次.
                Class.forName(DRIVERCLASS);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public static Connection getConnection() throws SQLException {
    
            // 2.获取连接
            Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    
            return con;
        }
    
        // 3.关闭操作
        public static void closeConnection(Connection con) throws SQLException{
            if(con!=null){
                con.close();
            }
        }
        public static void closeStatement(Statement st) throws SQLException{
            if(st!=null){
                st.close();
            }
        }
        public static void closeResultSet(ResultSet rs) throws SQLException{
            if(rs!=null){
                rs.close();
            }
        }
    }
    

1.查询

查询全部:
条件查询:根据id

public void findByIdTest() {
    // 定义sql
    String sql = "select * from user where id=1";
    Connection con = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        // 1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2.获取连接
        con = DriverManager.getConnection("jdbc:mysql:///day17", "root", "abc");
        // 3.获取操作sql语句对象Statement
        st = con.createStatement();
        // 4.执行sql
        rs = st.executeQuery(sql);
        // 5.遍历结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String username = rs.getString("username");
            String password = rs.getString("password");
            String email = rs.getString("email");
            System.out.println(id + "  " + username + "  " + password + "  " + email);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 6.释放资源
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (st != null)
                st.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (con != null)
                con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2.修改

public void update() {
    // 将id=3的人的password修改为456
    String password = "456";
    String sql = "update user set password='" + password + "' where id=3";
    // 1.得到Connection
    Connection con = null;
    Statement st = null;
    try {
        con = JdbcUtils.getConnection();
        // 3.获取操作sql语句对象Statement
        st = con.createStatement();
        // 4.执行sql
        int row = st.executeUpdate(sql);
        if (row != 0) {
            System.out.println("修改成功");
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 关闭资源
        try {
            if (st != null)
                st.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (con != null)
                con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

3.删除

public void delete() {
    // 将id=3的人删除
    String sql = "delete from user where id=2";
    // 1.得到Connection
    Connection con = null;
    Statement st = null;
    try {
        con = JdbcUtils.getConnection();
        // 3.获取操作sql语句对象Statement
        st = con.createStatement();
        // 4.执行sql
        int row = st.executeUpdate(sql);
        if (row != 0) {
            System.out.println("删除成功");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 关闭资源
        try {
            JdbcUtils.closeStatement(st);
            JdbcUtils.closeConnection(con);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4.添加

public void add() {
    // 定义sql
    String sql = "insert into user values(null,'张三','123','zs@163.com')";
    Connection con = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        // 1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2.获取连接
        con = DriverManager.getConnection("jdbc:mysql:///day17", "root",
                "abc");
        // 3.获取操作sql语句对象Statement
        st = con.createStatement();
        // 4.执行sql
        int row = st.executeUpdate(sql);
        if (row != 0) {
            System.out.println("添加成功");
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 6.释放资源
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (st != null)
                st.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (con != null)
                con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

滚动结果集:

默认得到的ResultSet它只能向下遍历(next()),对于ResultSet它可以设置成是滚动的,可以向上遍历,
或者直接定位到一个指定的物理行号.

问题:怎样得到一个滚动结果集?

    Statement st=con.createStatement();
    ResultSet rs=st.executeQuery(sql);
    这是一个默认结果集:只能向下执行,并且只能迭代一次。

     Statement stmt = con.createStatement(
                              ResultSet.TYPE_SCROLL_INSENSITIVE,
                              ResultSet.CONCUR_UPDATABLE);
    ResultSet rs = stmt.executeQuery(sql);
    这个就可以创建滚动结果集.
    简单说,就是在创建Statement对象时,不使用createStatement();
    而使用带参数的createStatement(int,int)


    Statement createStatement(int resultSetType,
                  int resultSetConcurrency)
                  throws SQLException

    resultSetType - 结果集类型,它是 ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE 之一
    resultSetConcurrency - 并发类型;它是 ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE 之一

    第一个参数值
    ResultSet.TYPE_FORWARD_ONLY    该常量指示光标只能向前移动的 ResultSet 对象的类型。
    ResultSet.TYPE_SCROLL_INSENSITIVE  该常量指示可滚动但通常不受 ResultSet 底层数据更改影响的 ResultSet 对象的类型。
    ResultSet.TYPE_SCROLL_SENSITIVE  该常量指示可滚动并且通常受 ResultSet 底层数据更改影响的 ResultSet 对象的类型。

    第二个参数值
    ResultSet.CONCUR_READ_ONLY    该常量指示不可以更新的 ResultSet 对象的并发模式。
    ResultSet.CONCUR_UPDATABLE    该常量指示可以更新的 ResultSet 对象的并发模式。

    以上五个值,可以有三种搭配方式
        ResultSet.TYPE_FORWARD_ONLY   ResultSet.CONCUR_READ_ONLY   默认
        ResultSet.TYPE_SCROLL_INSENSITIVE   ResultSet.CONCUR_READ_ONLY              
        ResultSet.TYPE_SCROLL_SENSITIVE  ResultSet.CONCUR_UPDATABLE


    常用API
        next():移动到下一行
        previous():移动到前一行
        absolute(int row):移动到指定行
        beforeFirst():移动resultSet的最前面
        afterLast() :移动到resultSet的最后面
        updateRow() :更新行数据

四、dao模式

DAO模式(Data Access Object 数据访问对象):在持久层通过DAO将数据源操作完全封装起来,业务层通过操作Java对象,完成对数据源操作
业务层无需知道数据源底层实现,通过java对象操作数据源

DAO模式结构 :

  1. 数据源(MySQL数据库)
  2. Business Object 业务层代码,调用DAO完成 对数据源操作
  3. DataAccessObject 数据访问对象,持久层DAO程序,封装对数据源增删改查,提供方法参数都是Java对象
  4. TransferObject 传输对象(值对象) 业务层通过向数据层传递 TO对象,完成对数据源的增删改查

1.使用dao模式登录操作

1.web层
    login.jsp  LoginServlet  User
2.service层
    UserService
3.dao层
    UserDao

2.sql注入

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

示例:
    在输入用户名时  tom' or '1'='1
    这时就不会验证密码了。
解决方案:
    PreparedStatement(重点)
    它是一个预处理的Statement,它是java.sql.Statement接口的一个子接口。

总结PreparedStatement使用:
    1.在sql语句中,使用"?"占位
        String sql="select * from user where username=? and password=?";
    2.得到PreparedStatement对象
        PreparedStatement pst=con.prepareStatement(String sql);
    3.对占位符赋值
        pst.setXxx(int index,Xxx obj);
        例如:
            setInt()
            setString();
        参数index,代表的是"?"的序号.注意:从1开始。

    4.执行sql
        DML:  pst.executeUpdate();
        DQL:   pst.executeQuery();
        注意:这两方法无参数

关于PreparedStatement优点:
    1.解决sql注入(具有预处理功能)
    2.不需要在拼sql语句。

五、JDBC处理大数据

mysql中有大数据
    blob 大二进制
        TINYBLOB(255)、BLOB(64kb)、MEDIUMBLOB(16m)和LONGBLOB(4g)
    text(clob) 大文本
        TINYTEXT(255)、TEXT(64kb)、MEDIUMTEXT(16m)和LONGTEXT(4g)

    对于大数据操作,我们一般只有两种 insert  select
    演示1:
        大二进制操作
        create table myblob(
            id int primary key auto_increment,
            content longblob
        )

        向表中插入数据


        问题1:java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setBinaryStream(ILjava/io/InputStream;)V
            原因:mysql驱动不支持setBinaryStream(int,InputStream);

        修改成
            pst.setBinaryStream(1, fis,file.length());
            原因:因为mysql驱动不支持setBinaryStream(int,InputStream,long);

        解决:
            mysql驱动支持setBinaryStream(int,InputStream,int);


        注意:如果文件比较大,那么需要在my.ini文件中配置
            max_allowed_packet=64M

    总结:
        存
                pst.setBinaryStream(1, fis, (int) (file.length()));
        取
                InputStream is = rs.getBinaryStream("content");
    --------------------------------------------------------------
    演示:存储大文本
        create table mytext(
            id int primary key auto_increment,
            content longtext
        )
    存储
        File file = new File("D:\\java1110\\workspace\\day17_3\\a.txt");
        FileReader fr = new FileReader(file);
        pst.setCharacterStream(1, fr, (int) (file.length()));
    获取:
        Reader r = rs.getCharacterStream("content");

六、JDBC批处理

一次可以执行多条sql语句.

在jdbc中可以执行sql语句的对象有Statement,PreparedStatement,它们都提供批处理.

1.Statement执行批处理
    addBatch(String sql);  将sql语句添加到批处理
    executeBatch(); 执行批处理
    clearBatch();

2.PreparedStatement执行批处理
    addBatch();
    executeBatch();
    clearBatch();

以上两个对象执行批处理区别?
    1.Statement它更适合执行不同sql的批处理。它没有提供预处理功能,性能比较低。        
    2.PreparedStatement它适合执行相同sql的批处理,它提供了预处理功能,性能比较高。

注意;mysql默认情况下,批处理中的预处理功能没有开启,需要开启
    1.在 url下添加参数
        url=jdbc:mysql:///day17?useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true
    2.注意驱动版本
        Mysql驱动要使用mysql-connector-java-5.1.13以上
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容