【JDBC】二、JDBC的操作

JDBC进行数据库操作

Statement:用于执行静态SQL语句
PreparedStatement:SQL语句被预编译在此对象中,可以多次高效的使用这段语句。
CallableStatement:用于执行SQL存储过程

Statement也会因为Java语言和写法问题产生依赖注入问题,导致查询语句的WHERE条件恒成立。

使用PreparedStatement对数据库实现增删改操作

一条完整的使用PreparedStatement实现插入操作的完整代码如下:

  1. 读取配置文件
  2. 加载驱动,获取连接
  3. 预编译SQL语句(使用prepareStatement)
  4. 插入数据
  5. 进行try-catch进行资源关闭
    @Test
    public void testInsert() {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //读取配置文件
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(is);
            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");
            //加载驱动
            Class.forName(driverClass);
            //获取连接
            conn = DriverManager.getConnection(url, user, password);
            System.out.println(conn);

            //预编译SQL语句
            String sql = "insert into customers(name, email, birth)values(?, ?, ?)";
            ps = conn.prepareStatement(sql);

            //填充占位符
            ps.setString(1, "太宰治");
            ps.setString(2, "zhi@qq.com");
            //对于时间的获取
            //建立对于时间的格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = sdf.parse("2050-10-02");      //使用格式建立具体的时间,使用getTime()方法获取时间戳
            ps.setDate(3, new Date(date.getTime()));    //注意引入的包为java.sql下的Date

            ps.execute();   //执行操作
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(ps != null){
                    ps.close();     //资源关闭
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(conn != null){
                    conn.close();   //连接关闭
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

可以将某些重复的操作封装到方法中,在使用时直接调用:
将获取数据库连接操作封装

   public static Connection getConnection() throws Exception {
       //读取配置文件
       InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
       Properties pros = new Properties();
       pros.load(is);
       String user = pros.getProperty("user");
       String password = pros.getProperty("password");
       String url = pros.getProperty("url");
       String driverClass = pros.getProperty("driverClass");
       //加载驱动
       Class.forName(driverClass);
       //获取连接
       Connection conn = DriverManager.getConnection(url, user, password);

       return conn;
   }

将资源关闭操作封装

    public void closeResource(Connection conn, Statement state){
       try {
           if(state != null){
               state.close();     //资源关闭
           }
       } catch (SQLException e) {
           e.printStackTrace();
       }
       try {
           if(conn != null){
               conn.close();   //连接关闭
           }
       } catch (SQLException e) {
           e.printStackTrace();
       }
   }

使用封装好的工具类实现修改数据的操作

    @Test
   public void testUpdate() throws Exception {
       Connection conn = null;
       PreparedStatement ps = null;
       try {
           //获取连接
           conn = JDBCutils.getConnection();
           //预编译SQL
           String sql = "update customers set name = ? where id = ?";
           ps = conn.prepareStatement(sql);
           //填充占位符
           ps.setObject(1, "江户川乱步");
           ps.setObject(2, 19);

           ps.execute();
       } finally {
           JDBCutils.closeResource(conn, ps);
       }
   }

将增删改操作封装为统一的方法,使用可变形参列表进行实现。

//进行测试调用

    @Test
    public void testCommonTest(){
//        String sql = "delete from customers where id = ?";
//        update(sql, 19);
        String sql = "update `order` set order_name = ? where order_id = ?";
        update(sql, "DD", "2");

    }
    
    //通用操作的定义
    public void update(String sql, Object ...args){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCutils.getConnection();

            ps = conn.prepareStatement(sql);

            for(int i = 0; i < args.length; i++){
                ps.setObject(i + 1, args[i]);
            }
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutils.closeResource(conn, ps);
        }
    }

进行查询操作的方法

使用ORM编程思想

  • 将一个数据库表视作一个java类
  • 将一个数据库记录视作一个具体的java对象
  • 将每个对象的每个字段视为一个java对象中的一个属性

注意:查询操作与增删改不同,查询操作需要接收返回值,我们使用ResultSet类型的变量来接收返回值,并通过.executeQuery()方法来进行操作。
注意:ResultSet也需要进行关闭。

下为一个查询操作的基础实现,其中忽略了javabean的编写。

    @Test
    public void testQuery() {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //获得连接(使用创建的工具类)
            conn = JDBCutils.getConnection();
            //预编译sql
            String sql = "select id, name, email, birth from customers where id = ?";
            ps = conn.prepareStatement(sql);
            //填充占位符
            ps.setObject(1, 4);
            //保存查询结果
            resultSet = ps.executeQuery();
            //将查询结果转换为java属性
            if(resultSet.next()){
                int id = resultSet.getInt(1);   //获取第一个数据
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);  //引入sql下的Date

                //使用JAVA对象,即ORM编程思想创建对象,此处的Customer是对应customers表的javabean
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCutils.closeResource(conn, ps, resultSet);
        }
    }

进阶编写查询:
第一种方法中,sql要在代码中实现,不能封装为方法,下提供将sql封装为方法的代码实现:
将sql语句和传入的参数封装为形参,将查询封装为方法。

    @Test
    public void testQueryForCustomers(){
        String sql = "select id, birth, email, name from customers where id = ?";
        Customer cust = QueryForCustomers(sql, 18);
        System.out.println(cust);
    }


    public Customer QueryForCustomers (String sql, Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCutils.getConnection();

            ps = conn.prepareStatement(sql);
            for(int i = 0; i < args.length; i++){
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            //注意只有在云数据中才可以知道rs的列数,使用.getMetaData()来获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();      //.getColumnCount()方法来获取列数
            if(rs.next()){
                Customer cos = new Customer();
                for(int i = 0; i < columnCount; i++){
                    Object columnvalue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = rsmd.getColumnName(i + 1);
                    //利用反射动态的给Customer的实例对象赋值
                    Field field = Customer.class.getDeclaredField(columnName);      //.getDeclaredField()获取字段中指定的属性,不包括父类
                    field.setAccessible(true);      //令反射可以修改私有化的属性
                    field.set(cos, columnvalue);
                }
                return cos;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutils.closeResource(conn, ps, rs);
        }
        return null;
    }

注意:结果集中的数据是存储在ResultSet中的,结果集的属性(列名、列数等)是存储在ResultSetMetaData中的(使用.getMetaData()方法获取)

对于列名与JavaBean中属性名不一致时
.getColumnName()获取列的列名(不受别名影响)
.getColumnLabel()获取列的列名(受别名影响)

故一定要起别名,与JavaBean中的属性名相对应
通过反射在运行时类中赋值。

    @Test
    public void testQueryForOrder(){
        String sql = "select order_id as orderId, order_name as orderName, order_date as orderDate from `order` where order_id = ?";
        Order order = orderForQuery(sql, 2);
        System.out.println(order);
    }

    public Order orderForQuery(String sql, Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCutils.getConnection();

            ps = conn.prepareStatement(sql);
            for(int i = 0; i < args.length; i++){
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData rsmd =  rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if(rs.next()){
                Order order = new Order();
                for(int i = 0; i < columnCount; i++){
                    Object columnValue = rs.getObject(i + 1);   //获取数据
    //                String columnName = rsmd.getColumnName(i + 1);  //获取数据库中的列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    Field field = Order.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(order, columnValue);
                }
                return order;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutils.closeResource(conn, ps, rs);
        }
        return null;
    }

针对不同表的通用查询方法

使用泛型与反射动态的将表的获取通用化,使传入表名的运行时类就可以自动实现对不同表的读取。

    @Test
    public void testQuery(){
        String sql = "select order_id as orderId, order_name as orderName, order_date as orderDate from `order` where order_id = ?";
        Order order = QueryForCustomers(Order.class, sql, 2);
        System.out.println(order);
    }

    public <T> T QueryForCustomers (Class<T> clazz, String sql, Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCutils.getConnection();

            ps = conn.prepareStatement(sql);
            for(int i = 0; i < args.length; i++){
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            //注意只有在云数据中才可以知道rs的列数,使用.getMetaData()来获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();      //.getColumnCount()方法来获取列数
            if(rs.next()){
                T t = clazz.newInstance();
                for(int i = 0; i < columnCount; i++){
                    Object columnvalue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = rsmd.getColumnLabel(i + 1);
                    //利用反射动态的给Customer的实例对象赋值
                    Field field = clazz.getDeclaredField(columnName);      //.getDeclaredField()获取字段中指定的属性,不包括父类
                    field.setAccessible(true);      //令反射可以修改私有化的属性
                    field.set(t, columnvalue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutils.closeResource(conn, ps, rs);
        }
        return null;
    }

针对于不同表,多条数据的通用查询方法

使用ArrayList列表来存储查询到的多条数据。

    @Test
    public void testQuery(){
        String sql = "select id, name, email from customers where id < ?";
        List list = getForList(Customer.class, sql, 18);
        list.forEach(System.out::println);
    }

    public <T> List<T> getForList(Class<T> clazz, String sql, Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCutils.getConnection();

            ps = conn.prepareStatement(sql);
            for(int i = 0; i < args.length; i++){
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            //注意只有在云数据中才可以知道rs的列数,使用.getMetaData()来获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();      //.getColumnCount()方法来获取列数
            ArrayList<T> list = new ArrayList<T>();
            while(rs.next()){
                T t = clazz.newInstance();
                for(int i = 0; i < columnCount; i++){
                    Object columnvalue = rs.getObject(i + 1);
                    //获取列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //利用反射动态的给Customer的实例对象赋值
                    Field field = clazz.getDeclaredField(columnLabel);      //.getDeclaredField()获取字段中指定的属性,不包括父类
                    field.setAccessible(true);      //令反射可以修改私有化的属性
                    field.set(t, columnvalue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCutils.closeResource(conn, ps, rs);
        }
        return null;
    }

为什么PreparedStatement可以解决SQL注入问题

Statement不会预编译SQL,会在字符串拼接完成后直接执行SQL语句,在SQL语句的写法出现问题时会执行有问题的语句。
PreparedStatement会预编译SQL,逻辑关系在完整的SQL语句填充之前就确定了,只是利用占位符,将需要的信息补齐,故可以避免SQL语句问题导致的SQL注入。

另外,PreparedStatement可以利用占位符传输流数据(Blob类型的数据,非常大),而Statement作为SQL语言的连接器不可以。

PreparedStatement也实现了更为高效的批量操作,Statement需要对于每一条SQL进行SQL校验,PreparedStatement进行预编译之后就只需要进行第一次的SQL校验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值