zoukankan      html  css  js  c++  java
  • java----jdbc

    JDBC简单操作

    jdbc编程步骤

    1. 加载数据库驱动
    2. 创建并获取数据库链接
    3. 创建jdbc statement对象
    4. 设置sql语句
    5. 设置sql语句中的参数(使用preparedStatement)
    6. 通过statement执行sql并获取结果
    7. 对sql执行结果进行解析处理
    8. 释放资源(resultSet、preparedstatement、connection)

    问题总结

    1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。

    设想:使用数据库连接池管理数据库连接。

    2.将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护。

    设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。

    3.向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。

    设想:将sql语句及占位符号和参数全部配置在xml中。

    4.从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。

    设想:将查询的结果集,自动映射成java对象。

    需要下载jar包mysql-connector-java-8.0.16,可以通过maven仓库进行下载;

    import java.sql.*;

    public static void main(String[] args) {
            //数据库连接
            Connection conn = null;
            //预编译的Statement,使用预编译的Statement提高数据库性能(当我们发送给数据库一段sql语句,数据库会编译sql语句,并将编译的结果放到数据库的缓存中,当下次发送sql语句的时候,如果sql语句一样,就不会再编译了)
            PreparedStatement preparedStatement = null;
            //返回结果集
            ResultSet resultSet = null;
            try {
                //加载驱动,通常不需要手动加载,会自动注册
                Class.forName("com.mysql.cj.jdbc.Driver");
                //Class.forName("com.mysql.jdbc.Driver"); //老版本的驱动
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javas1?serverTimezone=UTC", "root", "xx");  //由于jar包版本是最新的,需要加上?servletTimezone=UTC,否则报错
                String sql = "SELECT * FROM t_user WHERE username=?";
                preparedStatement = conn.prepareStatement(sql);
                //给占位符赋值
                preparedStatement.setString(1,"yy");
                resultSet = preparedStatement.executeQuery();
                System.out.println(resultSet);
                while (resultSet.next()){
                    String username = resultSet.getString("username");
                    System.out.println(username);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    //反向释放资源
                    resultSet.close();
                    preparedStatement.close();
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

      补充

    int i = preparedStatement.executeUpdate();  //表示执行更新(插入)操作,返回int类型数据

    使用Statement引发sql注入的例子

    public static void main(String[] args) {
            String str= "name1" or "1" = "1";
            test(str);
        }
        public static void test(String str){
            //数据库连接
            Connection conn = null;
            try {
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javas1?serverTimezone=UTC", "root", "123456");  //由于jar包版本是最新的,需要加上?servletTimezone=UTC,否则报错
                //sql语句在拼接的时候就会造成sql注入
                String sql = "SELECT * FROM USER WHERE NAME="" + str + """;
    //            String sql = "SELECT * FROM USER WHERE NAME="user1"";
                System.out.println(sql);//SELECT * FROM USER WHERE NAME="user1" or 1=1
                Statement statement = conn.createStatement();
                ResultSet resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                    System.out.println(resultSet.getString("name"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    使用Dbutils简化JDBC

    去官网上下载Dbutils的jar包

    数据库连接池示例

    class ConnectionPool implements DataSource {
        private static final String driver = "com.mysql.jdbc.Driver";
        private static final String dbUrl = "jdbc:mysql://localhost:3306/db_blog?useSSL=true";
        private static final String userName  = "root";
        private static final String password= "123456";
    
        private LinkedList<Connection> pool;
    
        private Connection getOneConnection() {
            Connection connection = null;
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(dbUrl,userName,password);
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }
        @Override
        public Connection getConnection() throws SQLException {
            //初始化最大容量是2个连接
            if(pool==null){
                pool = new LinkedList<>();
                for(int i=0;i<2;i++){
                    pool.add(getOneConnection());
                }
            }
            //最大的连接数没有上限
            if(pool.size()<=0){
                return getOneConnection();
            }
            return pool.remove();
        }
        public void close(Connection connection){
            //通过这个保证连接池的空间固定
            if (pool.size()>2){
                try {
                    connection.close();
                    return;
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            pool.add(connection);
        }
    }

    JDBC常见面试题

    JDBC是如何实现Java程序和JDBC驱动的松耦合的?

    JDBC API使用Java的反射机制来实现Java程序和JDBC驱动的松耦合。随便看一个简单的JDBC示例,你会发现所有操作都是通过JDBC接口完成的,而驱动只有在通Class.forName反射机制来加载的时候才会出现。

    JDBC中的Statement 和PreparedStatement的区别?

    • PreparedStatement是预编译的SQL语句,效率高于Statement。
    • PreparedStatement支持?操作符,相对于Statement更加灵活。
    • PreparedStatement可以防止SQL注入,安全性高于Statement。
    • CallableStatement适用于执行存储过程。
    • PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。如果查询一次性可以使用Statement(但是Statement会造成sql注入)

    PreparedStatement又是如何避免SQL注入攻击的?

      避免的原理不细说:原理就是会将参数值中的特殊符号进行转译;

      preparedStatement也存在sql注入的问题:https://www.cnblogs.com/iyangyuan/archive/2015/09/15/4809494.html

    使用PreparedStatement的Batch功能 

    PreparedStatement的缺点是什么,怎么解决这个问题?

    PreparedStatement的一个缺点是,我们不能直接用它来执行in条件语句;需要执行IN条件语句的话,下面有一些解决方案:

    • 分别进行单条查询——这样做性能很差,不推荐。
    • 使用存储过程——这取决于数据库的实现,不是所有数据库都支持。
    • 动态生成PreparedStatement——这是个好办法,但是不能享受PreparedStatement的缓存带来的好处了。
    • 在PreparedStatement查询中使用NULL值——如果你知道输入变量的最大个数的话,这是个不错的办法,扩展一下还可以支持无限参数。

    JDBC中大数据量的分页解决方法?

      最好的办法是利用sql语句进行分页,这样每次查询出的结果集中就只包含某页的数据内容。

    说说数据库连接池工作原理和实现方案?

      JAVA EE服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。

      实现方案:连接池使用集合来进行装载,返回的Connection是原始Connection的代理,代理Connection的close方法,当调用close方法时,不是真正关连接,而是把它代理的Connection对象放回到连接池中,等待下一次重复利用。

      具体代码:

    @Override
    public Connection getConnection() throws SQLException {
    
            if (list.size() > 0) {
                final Connection connection = list.removeFirst();
    
                //看看池的大小
                System.out.println(list.size());
    
                //返回一个动态代理对象
                return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                        //如果不是调用close方法,就按照正常的来调用
                        if (!method.getName().equals("close")) {
                            method.invoke(connection, args);
                        } else {
    
                            //进到这里来,说明调用的是close方法
                            list.add(connection);
    
                            //再看看池的大小
                            System.out.println(list.size());
    
                        }
                        return null;
                    }
    
                });
            }
            return null;
        }

    Java中如何进行事务的处理?

      在JDBC编程模式中,一个数据库连接建立时,就处于一个自动提交模式,每一个SQL语句被执行完成后就会被自动提交,反映到数据库中。当需要把几条逻辑上相关的SQL组成的一个事务执行时,就需要关闭事务自动提交模式。如下面的语句所示: con.setAutoCommit(false); // 关闭自动提交模式 一旦关闭了事务自动提交模式,不会有任何SQL语句被提交至数据库系统执行,除非显式的调用提交方法。

      Connection类中提供了4个事务处理方法:

    setAutoCommit(Boolean autoCommit):设置是否自动提交事务,默认为自动提交,即为true,通过设置false禁止自动提交事务;
    commit():提交事务;
    rollback():回滚事务.
    savepoint:保存点
      注意:savepoint不会结束当前事务,普通提交和回滚都会结束当前事务的
    

    修改JDBC代码质量

      原来的代码:

    public void printProducts(){
        Connection c = null;
        Statements s = null;
        ResultSet r = null;
        try{
    
    c=DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:sid","username","password");
            s=c.createStatement();
            r=s.executeQuery("select id, name, price from product");
            System.out.println("Id	Name	Price");
            while(r.next()){
                int x = r.getInt("id");
                String y = r.getString("name");
                float z = r.getFloat("price");
                System.out.println(x + "	" + y + "	" + z);
            }
        } catch(Exception e){
    
        }
    }
    

      修改后的代码:

    class Constant{
        public static final String URL="jdbc:oracle:thin:@127.0.0.1:1521:sid";
        public static final String USERNAME="username";
        public static final String PASSWORD="password";
    }
    
    class DAOException extends Exception{
        public DAOException(){
            super();
        }
        public DAOException(String msg){
            super(msg);
        }
    }
    
    public class Test{
    
        public void printProducts() throws DAOException{
            Connection c = null;
            Statement s = null;
            ResultSet r = null;
            try{
                c = DriverManager.getConnection(Constant.URL,Constant.USERNAME,Constant.PASSWORD);
                s = c.createStatement();
                r = s.executeQuery("select id,name,price from product");
                System.out.println("Id	Name	Price");
                while(r.next()){
                    int x = r.getInt("id");
                    String y = r.getString("name");
                    float z = r.getFloat("price");
                    System.out.println(x + "	" + y + "	" + z);
                }
            } catch (SQLException e){
                throw new DAOException("数据库异常");
            } finally {
                try{
                    r.close();
                    s.close();
                    c.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    修改点:

    • url、password等信息不应该直接使用字符串“写死”,可以使用常量代替
    • catch中应该回滚事务,抛出RuntimeException也是回滚事务的一种方法
    • 关闭资源

    execute,executeQuery,executeUpdate的区别是什么?

    • Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpdateCount()方法来获取更新的记录条数。
    • Statement的executeQuery(String query)接口用来执行select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。我们通常使用executeQuery来执行查询语句,这样的话如果传进来的是insert或者update语句的话,它会抛出错误信息为 “executeQuery method can not be used for update”的java.util.SQLException。
    • Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者 什么也不返回DDL语句。返回值是int类型,如果是DML语句的话,它就是更新的条数,如果是DDL的话,就返回0。
    • 只有当你不确定是什么语句的时候才应该使用execute()方法,否则应该使用executeQuery或者executeUpdate方法。

    JDBC的ResultSet是什么?

    • 在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。
    • ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了ResultSet的next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回false。可以在for循环中用它来遍历数据集。
    • 默认的ResultSet是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet
    • 当生成ResultSet的Statement对象要关闭或者重新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。
    • 可以通过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。

    一共有三种ResultSet对象。

    • ResultSet.TYPE_FORWARD_ONLY:这是默认的类型,它的游标只能往下移。
    • ResultSet.TYPE_SCROLL_INSENSITIVE:游标可以上下移动,一旦它创建后,数据库里的数据再发生修改,对它来说是透明的。
    • ResultSet.TYPE_SCROLL_SENSITIVE:游标可以上下移动,如果生成后数据库还发生了修改操作,它是能够感知到的。

    JDBC的RowSet是什么,有哪些不同的RowSet?

      RowSet用于存储查询的数据结果,和ResultSet相比,它更具灵活性。RowSet继承自ResultSet,因此ResultSet能干的,它们也能,而ResultSet做不到的,它们还是可以。RowSet接口定义在javax.sql包里。

      RowSet提供的额外的特性有 

    • 提供了Java Bean的功能,可以通过setter和getter方法来设置和获取属性。RowSet使用了JavaBean的事件驱动模型,它可以给注册的组件发送事件通知,比如游标的移动,行的增删改,以及RowSet内容的修改等。
    • RowSet对象默认是可滚动,可更新的,因此如果数据库系统不支持ResultSet实现类似的功能,可以使用RowSet来实现

    JDBC中存在哪些不同类型的锁?

    从广义上讲,有两种锁机制来防止多个用户同时操作引起的数据损坏。

    • 乐观锁——只有当更新数据的时候才会锁定记录
    • 悲观锁——从查询到更新和提交整个过程都会对数据记录进行加锁。

    java.util.Date和java.sql.Date有什么区别?

      java.sql.Date  Sun Oct 06 22:57:53 CST 2019

      java.sql.Date  1970-01-01 (不包含时间)

      如果需要将时间存如到数据库,推荐使用 Timestamp

        public static void main(String[] args) {
            Date date = new Date();
            long time = date.getTime();
            Timestamp timestamp = new Timestamp(time);
            System.out.println(timestamp); //2019-10-06 23:00:42.65
        }
    

      

  • 相关阅读:
    poj2240
    poj1135
    poj1062
    poj3278
    2218 补丁vs错误
    必做: 1041、1024、1077、2218、1183(较难)
    poj2828
    poj3253
    洛谷P1122 最大子树和
    1074 食物链
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/11157266.html
Copyright © 2011-2022 走看看