原生JDBC操作数据库

JDBC操纵数据库

在各种ORM框架盛行的时代,想必我们很多都已经忘了如何在不借助框架的基础上,使用原生的JDBC来和数据库打交道了,对于初级程序员来说,使用原生的JDBC编码可以使我们更加全面的了解框架底层下的操作逻辑,在原生编码中我们也需要自己去封装一些工具类,使用一些设计模式来简化我们操纵数据库的步骤,在这一步步封装的过程中可以提高我们的编程思想,所以了解原生JDBC操控数据库也是很有必有得,接下来就让我们从一份最原始的JDBC到自己封装的工具类及模板来实现的全步骤,话不多说直接上代码!


JDBC 初体验

   public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            String sql = "select count(0) as total from n_student where name=?";
            //1,注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2,获取连接
             conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/noone_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC","root","root");
            //3,获取语句会话对象,这里我们获取的是预编译处理会话,可以防止SQL注入
             ps = conn.prepareStatement(sql);
            //给占位符? 赋值,setObject() 第一个参数为占位符位置,第二个参数为实际传入的值
            ps.setObject(1, "小虎");
            //执行语句得到返回值
             rs = ps.executeQuery();
            int total = 0;
            if (rs.next()) {
                 total = rs.getInt("total");
            }
            System.out.println(total);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if (rs!=null){rs.close();}
                if (ps!=null){ps.close();}
                if (conn!=null){conn.close();}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

JDBC 1.0

下面我们要使用原生JDBC模拟真实开发场景,我们现在要对学生表进行增删改查操作

学生表

@Data
public class Student {
    private Integer age;
    private String name;

    public Student(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
}

Dao

/**
 * 传统操控jdbc接口方式
 */
public class StudentDao1 {

    public void save(Student stu) {
        String sql = "insert into n_student(age,name) values(?,?) ";
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
             conn = DriverManager.getConnection("jdbc:mysql://192.168.232.128:3306/noone_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC", "root", "root");
            //创建语句对象
             ps = conn.prepareStatement(sql);
            //给占位符赋值
            ps.setObject(1, stu.getAge());
            ps.setObject(2, stu.getName());
            ps.executeUpdate();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }


    public void delete(Student stu) {
        String sql = "delete from n_student where name =?";
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            conn = DriverManager.getConnection("jdbc:mysql://192.168.232.128:3306/noone_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC", "root", "root");
            //创建语句对象
            ps = conn.prepareStatement(sql);
            //给占位符赋值
            ps.setObject(1, stu.getName());
            ps.executeUpdate();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }

    public void selectOne(Student stu) {
        String sql = "select * from n_student where name =?";
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            conn = DriverManager.getConnection("jdbc:mysql://192.168.232.128:3306/noone_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC", "root", "root");
            //创建语句对象
            ps = conn.prepareStatement(sql);
            //给占位符赋值
            ps.setObject(1, stu.getName());
            ps.executeUpdate();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

从上述代码中我们发现,dao中有着大量的冗余代码,不利于开发,也不利于维护,所以我们可以将这些重复的代码抽取成一个工具类JdbcUtils

package com.xzq.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public  class JdbcUtils {
    private static Properties prop;

    public JdbcUtils() {}

    static {
        try {
            //1,加载注册驱动
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            InputStream is = loader.getResourceAsStream("db.properties");
            prop = new Properties();
            prop.load(is);
            Class.forName(prop.getProperty("driverClassName"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
	//获取连接
    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn=DriverManager.getConnection(prop.getProperty("url"),prop.getProperty("username"),prop.getProperty("password"));
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }
	//关闭资源
    public static void closedResource(PreparedStatement ps,Connection conn) {
        try {
            if (ps != null) {
                ps.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
	//关闭资源
    public static void closedResource(ResultSet rs,PreparedStatement ps, Connection conn) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

使用我们的jdbcUtils使用再来对dao进行改造

JDBC 2.0

package com.xzq.dao;

import com.xzq.domain.Student;
import com.xzq.util.JdbcUtils;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 传统操控jdbc接口方式
 */
public class StudentDao2 {

    public void save(Student stu) {
        String sql = "insert into n_student(age,name) values(?,?) ";
        Connection conn = JdbcUtils.getConnection();
        PreparedStatement ps = null;
        try {
             ps = conn.prepareStatement(sql);
            ps.setObject(1, stu.getAge());
            ps.setObject(2, stu.getName());
            ps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closedResource(ps,conn);
        }

    }


    public void delete(Student stu) {
        String sql = "delete from n_student where name =?";
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            ps.executeUpdate();
            ps.setObject(1, stu.getName());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public Student selectOne(Student stu) {
        String sql = "select * from n_student where name =?";
        Connection conn = null;
        PreparedStatement ps = null;
        Student db_stu = null;
        try {
            conn=JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            ps.setObject(1, stu.getName());
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                int age = rs.getInt("age");
                String name = rs.getString("name");
                db_stu = new Student(age, name);
            }
            JdbcUtils.closedResource(rs,ps,conn);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return db_stu;
    }

    public List<Student> selectAll() {
        String sql = "select*from n_student";
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<Student> list = new ArrayList<>();
        try {
            conn = JdbcUtils.getConnection();
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while (rs.next()) {
                int age = rs.getInt("age");
                String name = rs.getString("name");
                Student student = new Student(age,name);
                list.add(student);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return list;
    }

}

这样看起来我们的代码确实简介了许多,但仔细观察,可以发现代码中还是有很多冗余代码,例如获取连接,关闭连接,构建返回值,DML中不一样的只有SQL语句和参数,
我们可以创建一个模板类JdbcTemplate,创建一个DML和DQL模板来进行重构

模板类
package com.xzq.template;

import com.xzq.domain.Student;
import com.xzq.util.IRowMapper;
import com.xzq.util.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/*
模板类
 */
public class JdbcTemplate {
    //DQL查询统一模板
    public static List  query( String sql, Object... params) {
       List<Student> list = new ArrayList<>();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                ps.setObject(i+1, param);
            }
            rs=ps.executeQuery();
            while (rs.next()) {
               	int age = rs.getInt("age");
                String name = rs.getString("name");
                Student student = new Student(age, name);
                list.add(student);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closedResource(rs,ps,conn);
        }
        return list;
    }

    //DML操作统一模板
    public static void DMLOperator(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                ps.setObject(i, param);
            }
            ps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closedResource(ps,conn);
        }
    }
}

这样的重复代码基本就处理好了,但是有一个很严重的问题就是,这个程序的DQL操作中只能处理Student类和n_student表相关数据,无法处理其他数据,不同表有不同列,不同处理结果集的代码也就不一样,处理结果集的操作只有调用方Dao知道,所以我们不应该把处理结果的方法放在模板方法中
应该由每个DAO自己来处理,因此我们可以创建一个IRowMapper接口处理结果集
IRowMapper接口

package com.xzq.util;

import java.sql.ResultSet;
import java.util.List;

public interface IRowMapper {
    //处理结果集
    List mapping(ResultSet rs) throws Exception;
}

实现类自己去实现IRowMapper接口的 mapping方法,想要处理什么类型数据在里面定义即可
学生结果集处理类

public class StudentMapping implements IRowMapper {
    @Override
    public List mapping(ResultSet rs) throws Exception {
        ArrayList<Object> list = new ArrayList<>();
        while (rs.next()) {
            int age = rs.getInt("age");
            String name = rs.getString("name");
            Student student = new Student(age, name);
            list.add(student);
        }
        return list;
    }

}

模板类,通过高层调用传来的IRowMapper接口实现来进行结果集映射

  //DQL查询统一模板
    public static List  query(IRowMapper rm, String sql, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                ps.setObject(i+1, param);
            }
            rs=ps.executeQuery();
            return rm.mapping(rs);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closedResource(rs,ps,conn);
        }
     	return null;
    }

Dao

    public Student selectOne(Student stu) {
        String sql = "select * from n_student where name =?";
        Object params[] = new Object[]{stu.getName()};
        List<Student> list = JdbcTemplate.query( new StudentMapping(),sql, params);
        return list.get(0);
    }

    public List<Student> selectAll() {
        String sql = "select*from n_student";
        return JdbcTemplate.query( new StudentMapping(),sql);
    }

看起来似乎没什么毛病了,但是如果这时我们想要查询学生数量,就只能通过泛型来解决了
IRowMapper接口

//指定泛型接口
public interface IRowMapper<T> {
    //出来结果集
    T mapping(ResultSet rs) throws Exception;
}

JdbcTemplate模板类

    //DQL查询统一模板 <T> T 第一个<T>表明这是一个泛型方法,第二个T才是指定返回值为泛型
    public static <T> T  query(IRowMapper<T> rm, String sql, Object... params) {
        List<Student> list = new ArrayList<>();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            ps=conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                ps.setObject(i+1, param);
            }
            rs=ps.executeQuery();
            return rm.mapping(rs);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closedResource(rs,ps,conn);
        }
        return null;
    }

JDBC 3.0

经过上面的各种抽象,3.0版本也基本差不多了,看一下现在的客户端调用代码

public class StudentDao3 {

    public void save(Student stu) {
        String sql = "insert into n_student(age,name) values(?,?) ";
        Object params[] = new Object[]{stu.getAge(), stu.getName()};
        JdbcTemplate.DMLOperator(sql,params);
    }


    public void delete(Student stu) {
        String sql = "delete from n_student where name =?";
        Object[] params = new Object[]{stu.getName()};
        JdbcTemplate.DMLOperator(sql, params);
    }

    public Student selectOne(Student stu) {
        String sql = "select * from n_student where name =?";
        Object params[] = new Object[]{stu.getName()};
        List<Student> list = JdbcTemplate.query( new StudentMapping(),sql, params);
        return list.get(0);
    }

    public List<Student> selectAll() {
        String sql = "select*from n_student";
        return JdbcTemplate.query( new StudentMapping(),sql);
    }

    public Long getCount() {
        String sql = "select count(0) as total from n_student";
        Long total = JdbcTemplate.query(new IRowMapper<Long>() {
            @Override
            public Long mapping(ResultSet rs) throws Exception {
                long count = 0L;
                if (rs.next()) {
                     count= rs.getLong("total");
                }
                return count;
            }
        }, sql);
        return total;
    }
}

总结

通过原生的JDBC编写,在1.0版本我们发现注册驱动,获取连接,关闭资源的冗余代码太多,因此我们选择封装工具类JdbcUtils来进行抽象出2.0版本,但是在2.0版本中我们发现还是有很多冗余代码,我们还是要在客户端代码(Dao层)中直面接触连接,会话对象,返回结果等操作,其实对于DML只有SQL语句和参数不同,其他都相同,在DQL中只有SQL语句,参数,处理结果集的代码不同,因此我们可以创建模板类针对DQL和DML创建模板,通过创建模板之后,DQL模板中有处理结果集的操作,但是我们抽象模板类不就是相当万能模板,我不想操纵学生表了,想要对老师进行CRUD,我还要在重新写一个模板吗?其实,对于结果集的处理,高层调用是明白什么类型的,因此,处理结果集的操作就不应该放置在模板中,而是抽象一个处理结果集的接口,在调用模板类时传过来它的具体实现。现在模板类对于什么类型的返回集都无所谓了,但是我想要查询数量的时候又不行了,此时模板类上是List返回类型,现在得兼容List,Long以后说不定还要查询其他类型字段,我们把返回类型写死就不行了,那返回类型应该有谁来决定呢?应该有高层调用方也就是客户端(Dao)来决定,因此需要泛型 了,我们将处理结果集的接口IRowMapper改造成泛型接口,在客户端调用时指定泛型类型,在将模板类改造为泛型方法,这样只需要在客户端调用时指定返回结果的类型,经过这一步一步的操作,终于将最终版搞定,其实这也不是太过方便,能不能在封装结果集的时候在进行抽象呢?其实可以通过反射来解决,在讲到ORM框架时再说。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值