JDBC技术

目录

一、什么是JDBC ?

二、JDBC的本质?

三、JDBC连接数据库的基本步骤     

四、使用JDBC连接数据库完成增删改查

五、SQL防注入问题 

六、JDBC中的事务 

七、JDBCUtils 工具类封装 数据库的连接 

八、通用的增删改操作

九、通用的查询操作

十、BaseDao 的封装以及实现

十一、数据库连接池技术

十二、Apache-DBUtils实现CRUD操作


一、什么是JDBC ?

        Java DataBase Connectivity(Java语言连接数据库)

二、JDBC的本质?

        JDBC是SUN公司制定的一套接口(interface), java.sql.*; (这个软件包下有很多接口。)          接口都有调用者和实现者:    面向接口调用、面向接口写实现类,这都属于面向接口编程。

为什么要面向接口编程?
         解耦合:降低程序的耦合度,提高程序的扩展力。
         多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)

为什么SUN制定一套JDBC接口呢?
        因为每一个数据库的底层实现原理都不一样。
        Oracle数据库有自己的原理。
        MySQL数据库也有自己的原理。
        MS SqlServer数据库也有自己的原理。
        ....
        每一个数据库产品都有自己独特的实现原理。所以在数据库出厂时需要一些特定的接口来实现某些功能

三、JDBC连接数据库的基本步骤     

   ★ ★ ★ ★ ★
        1. 注册驱动

        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        /*
            "驱动在jar包的位置"
            Driver.class字节码文件中有注册驱动的方法,所以只需采用类加载的方式加载一次即可。
         */


        2. 获取连接

             /**
                 * 2.获取连接
                 * getConnection("jdbc:哪个品牌数据库://ip地址:端口号/数据库名称","用户名","密码")
                 * url:统一资源定位符(网络中某个资源的绝对路径)
                 * url包括哪几个部分:
                 *      协议:jdbc:mysql://
                 *      IP:127.0.0.1  本机地址
                 *      PORT:端口号 3306
                 *      资源:test  具体的数据库的实例名
                 */  
Connection  conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");


        3.获取sql语句执行对象

                 //    3.获取sql执行对象
                Statement  st = conn.createStatement();


        4.执行sql语句

                //    4.执行sql语句()
                String sql = "insert into dept(deptno,dname,loc) values(51,'RenShiBu','TangShan')";
                //以下方法专门执行sql语句的。返回值是 "影响数据库中记录的条数"
                int count = st.executeUpdate(sql);
                System.out.println(count == 1 ? "增加成功" : "增加失败");


        5.查询处理结果集(只有sql语句时select语句时才会执行这一步 )
        6.释放资源

            //    6.释放资源
            //遵循从小到大依次关
            finally {
                if (conn !=null){
                    try {
                        conn.close() ;
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (st !=null){
                    try {
                        st.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }

使用JDBC连接数据库前的准备工作: 

         从官网上下载相关jar包,引入项目中【idea为例】:

 找到下载好的jar包,增加进去,如果右边出现这个包说明引入成功了。

         (一)java.sql.* 下的接口:

                * Statement:表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。        

               * ResultSet:表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。

              * Connection:与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果。

                * DriverManage:驱动管理器

四、使用JDBC连接数据库完成增删改查

        (一)对数据库的增加

public class InsertData {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;

        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
            //3、获取sql语句执行对象
            st = conn.createStatement();
            //4、创建sql语句并执行
            String sql = "insert into t_user(userName,passwd,realName) value('ZS','789','张三')";
            int count = st.executeUpdate(sql);
            System.out.println(count > 0 ? "增加成功" : "增加失败");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

演示结果: 

        (二)对数据库的修改,删除和增加一样,只需要修改sql语句 .

//修改sql语句
String sql = "update t_user set userName='LS',passwd='999',realName='李四' where id=11";

演示结果:

 //删除sql语句
 String sql = "delete from t_user where id=11";

演示结果:

         (三)对数据库的查询需要处理查询结果集

public class SelectData {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        //查询结果集
        ResultSet rs = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
            st = conn.createStatement();
            String sql = "select * from t_user";
            rs = st.executeQuery(sql);
            while (rs.next()) {
                //这里的 1 2 3 4 代表数据库中的字段
                // System.out.print(rs.getString(1) + "\t");
                // System.out.print(rs.getString(2) + "\t");
                // System.out.print(rs.getString(3) + "\t");
                // System.out.print(rs.getString(4) + "\t");
                // System.out.println();

                //还可以这样学,"" 里面的名字必须和sql 语句里的字段名一样
                int id = rs.getInt("id");
                String userName = rs.getString("userName");
                String passwd = rs.getString("passwd");
                String realName = rs.getString("realName");
                System.out.println(id + "\t" + userName + "\t" + passwd + "\t" + realName + "\t");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

演示结果:

五、SQL防注入问题 

        通过 一个实例演示sql 语句注入问题:

         例:       使用JDBC来模拟一个用户登录界面,将用户输入的用户名和密码与数据库中的用户名密码进行比对,相等登陆成功,否则,登录失败!!

 数据库用户登录表:

 代码:

public class JDBCTest4 {
    public static void main(String[] args) {
        /*
            使用jDBC模拟用户登录系统
         */
        HashMap<String, String> init = userLoginInfo();
        boolean loginSuccess = jugeLoginInfo(init);
        System.out.println(loginSuccess ? "登录成功" : "登录失败");

    }

    //连接数据库进行用户信息判断
    public static boolean jugeLoginInfo(HashMap<String, String> userLoginInfo) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        boolean loginSuccess = false;
        try {
            //1 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
            //3 获取sql语句执行对象
            st = conn.createStatement();
            // 4 执行sql语句
            //将输入的用户名与密码与数据库中进行查询
            String sql = "select * from t_user where userName ='" + userLoginInfo.get("userName") + "' and passwd = '" + userLoginInfo.get("passwd") + "' ";
            //5 处理查询结果集,结果集只有一条所以不必用循环
            rs = st.executeQuery(sql);
            if (rs.next()) {
                loginSuccess = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6 释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (st != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return loginSuccess;
    }

    //接受用户输入信息
    public static HashMap<String, String> userLoginInfo() {
        HashMap<String, String> userInfo = new HashMap<>();
        Scanner in = new Scanner(System.in);

        //输入用户和密码并增加到集合中
        System.out.println("请输入用户名:");
        String userName = in.nextLine();
        userInfo.put("userName", userName);
        System.out.println("请输入密码:");
        String passwd = in.nextLine();
        userInfo.put("passwd", passwd);

        return userInfo;
    }
}

 演示:

但是我们看下面这俩种情况,即使用户名和密码不对的情况下,我们还是能够登录成功?这就是sql注入问题。

          

 1、什么是sql注入问题呢?

        * 用户输入的关键信息,并且这些关键信息参与了sql语句的编译。

        * 导致sql语句编译错误

        * 当我们输入jack'# 和 jack'-- 时, "#" 和"-- "后面的sql语句都变成了注释,其实并没有判断passwd

  2、怎么解决sql注入问题?

        只要用户输入的信息不参与sql语句编译的过程,问题就解决了

       即使用户提供的信息中含有sql语句的关键字,但是没有参与编译过程,也没有用了

         * 要想输入的信息不参与sql语句编译,使用 java.sql.PrepareStatement代替Statement

        * PrepareStatement继承了 java.sql.Statement

         * PrepareStatement属于预编译的数据库操作。

解决sql 注入问题:

 只需对部分代码进行改动就行。

//连接数据库进行用户信息判断
    public static boolean jugeLoginInfo(HashMap<String, String> userLoginInfo) {
        Connection conn = null;
        ResultSet rs = null;
        //代替Statement
        PreparedStatement ps = null;
        //标记用户输入信息否正确
        boolean loginSuccess = false;
        try {
            //1 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
            String sql = "select * from t_user where userName =? and passwd = ?";
            // 3 预编译数据库操作对象
            ps = conn.prepareStatement(sql);
            //给?传值,1代表第一个? 2代表第二个?
            ps.setString(1, userLoginInfo.get("userName"));
            ps.setString(2, userLoginInfo.get("passwd"));
            //由于在获取数据库操作对象的时候已经将sql语句进行编译,所以这里不要再执行了
            rs = ps.executeQuery();
            if (rs.next()) {
                loginSuccess = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (ps != null) {
                    try {
                        ps.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return loginSuccess;
    }

演示:

          

 这样就防止了用户入侵数据库,但是用户输入参与sql语句编译有时候还是有用的!!

代码中的 ?代表占位符,一个?接收一个值,不能用" "括起来!!!!!

程序执行到第三步的时候,DBMS会预先编译sql语句命令,后面用户传入的数据不影响编译

 ps.setString(1, userLoginInfo.get("userName"));  :给?传值,1代表第一个?,2代表第二个?,以此类推

六、JDBC中的事务 

* JDBC中的事务,是自动提交的,每执行一条DML语句,就会提交
* 这种方式在实际开发中并不好用。因为实际开发中都是多条DML语句配合完成的。

设置手动提交的方法:

* conn.setAutoCommit(false); 关闭自动提交

* conn.commit(); 提交

* conn.rollback(); 回滚

 通过一个银行转账的实例演示事务自动提交的局限性:

   数据库中的表:

 代码:

public class JDBCTest05 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        //    第一步:注册驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
            //第二步:获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

            //    第三步:获取预编译sql语句执行对象
            //    编写sql语句
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            //    传值
            ps.setDouble(1, 20000);
            ps.setInt(2, 111);
            //    执行sql语句
            int count = ps.executeUpdate();


            //这里会出现空指针异常,下面的代码不会执行,直接执行catch中的代码
            String s = null;
            s.toString();

            ps.setDouble(1, 10000);
            ps.setInt(2, 222);
            count += ps.executeUpdate();

            System.out.println(count == 2 ? "转账成功" : "转账失败");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

当执行到   String s = null;  时,会报空指针异常程序会直接跳到下面的catch语句,

            ps.setDouble(1, 10000);
            ps.setInt(2, 222);
            count += ps.executeUpdate();

这几条语句并没有执行,这个时候就出现了问题,转账操作只执行了一部分。

 这个时候就需要设置手动提交,当整个转账操作完成才能执行sql语句

设置手动提交: 

public class JDBCTest05 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        //    第一步:注册驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
            //第二步:获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
            //在这里设置 手动提交事务
                conn.setAutoCommit(false);

            //    第三步:获取预编译sql语句执行对象
            //    编写sql语句
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            //    传值
            ps.setDouble(1, 20000);
            ps.setInt(2, 111);
            //    执行sql语句
            int count = ps.executeUpdate();


            //这里会出现空指针异常,下面的代码不会执行,直接执行catch中的代码
            String s = null;
            s.toString();

            ps.setDouble(1, 10000);
            ps.setInt(2, 222);
            count += ps.executeUpdate();

            System.out.println(count == 2 ? "转账成功" : "转账失败");


            // 程序能够走到这里说明程序没有异常,手动提交事务
            // 不执行这个语句,sql语句就不执行
            conn.commit();
        } catch (Exception e) {
            if (conn!=null){
            // 手动回滚
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 当设置手动提交,只有当执行 conn.commit(); 这条语句时才会完整的提交sql 语句。

七、JDBCUtils 工具类封装 数据库的连接 

由于我们每次进行增删改查都需要注册驱动,连接数据库.....都是重复的操作,那么我们可以将这些重复的操作封装到一个类中。 

 提前将 注册驱动,连接数据库需要的一些参数封装到 jdbc.resource 文件中。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
password=root

封装 JDBCUtils工具类:

/*封装连接数据库的操作*/
public class JDBCUtils {
    private static ResourceBundle resourcee = ResourceBundle.getBundle("jdbc");
    private static String driver = resourcee.getString("driver");
    private static String url = resourcee.getString("url");
    private static String user = resourcee.getString("user");
    private static String password = resourcee.getString("password");

    /*静态代码块:注册驱动只需注册一次*/
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /*连接数据库*/
    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /*释放资源*/
    public static void close(Connection conn, ResultSet rs, PreparedStatement ps) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

八、通用的增删改操作

在进行 对数据库 进行 增删改 操作时,其实只是 sql 语句不一样,其他代码都是重复的,所以我们可以将增删改进行封装,只需要传递sql语句和修改的数据即可

 /*
    * Object...args :可变长参数,用于对 占位符 传值。
    * */
    public static void update(String sql,Object...args){
        PreparedStatement ps = null ;
        Connection conn =null ;
        try {
            //获取连接
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            //为占位符传值
            for (int i = 0; i < args.length; i++) {
                //注意参数别写错
                ps.setObject(i+1,args[i]);
            }
            //执行 sql 语句
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //释放资源
            JDBCUtils.close(conn,null,ps);
        }
    }
}

九、通用的查询操作

处理结果集的时候通常有三种方式:

               
                 //第一种方式
                String id = rs.getString(1);
                String userName = rs.getString(2);
                String passwd = rs.getString(3);
                String realName = rs.getString(4);

                System.out.println(id + userName + passwd + realName);
                 //封装成数组
                String id = rs.getString(1);
                String userName = rs.getString(2);
                String passwd = rs.getString(3);
                String realName = rs.getString(4);
                
                Object[] data = new Object[]{id,userName,passwd,realName};
                //第三种方式:封装成一个对象【推荐这种方式】
                int id = rs.getInt(1);
                String userName = rs.getString(2);
                String passwd = rs.getString(3);
                String realName = rs.getString(4);
                UserBean userBean = new UserBean(id,userName,passwd,realName);

 User对象:

public class UserBean {
    private int id ;
    private  String userName;
    private String passwd ;
    private String realName ;

    public UserBean() {
    }

    public UserBean(int id, String userName, String passwd, String realName) {
        this.id = id;
        this.userName = userName;
        this.passwd = passwd;
        this.realName = realName;
    }

    @Override
    public String toString() {
        return "pojo.UserBean{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", passwd='" + passwd + '\'' +
                ", realName='" + realName + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }
}

针对于一张表的通用查询操作:

 public static UserBean query(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]);
            }
            //处理查询结果集
            //需要直到的:列名 和 列数 和 数据
            //metaDate:获取结果集中的元数据
            ResultSetMetaData metaData = ps.getMetaData();
            //获取查询的列数
            int columnCount = metaData.getColumnCount();
            rs = ps.executeQuery();
            if (rs.next()) {
                UserBean user = new UserBean();
                for (int i = 0; i < columnCount; i++) {
                    //获取数据
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射机制向 UserBean 对象传值
                    Field field = UserBean.class.getDeclaredField(columnName);
                    //打破封装
                    field.setAccessible(true);
                    field.set(user, columnValue);
                }
                return user;

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放资源*/
            JDBCUtils.close(conn, rs, ps);
        }
        return null;
    }
}

针对所有的表的通用查询方法,次方法仅返回一条记录:

 /*用泛型代替 具体的某个对象
    * Class<T>:表示某个对象的类
    * */
    public static <T> T query(Class<T> tClass,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]);
            }
            //处理查询结果集
            //需要知道查询的:列名 和 列数
            //metaDate:获取元数据
            ResultSetMetaData metaData = ps.getMetaData();
            //获取查询的列数
            int columnCount = metaData.getColumnCount();
            rs = ps.executeQuery();
            if (rs.next()) {
                //调用这个类的无参构造方法
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取数据
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射机制向 t 对象传值
                    Field field = tClass.getDeclaredField(columnName);
                    //打破封装
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放资源*/
            JDBCUtils.close(conn, rs, ps);
        }
        return null;
    }

针对所有的表的通用查询方法,次方法仅返回多条记录:

 //针对不同的表 返回多条记录
    public static <T> List<T> getForList(Class<T> tClass,String sql,Object...args){
        /*获取连接*/
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<T> list = new ArrayList<>();
        try {
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            /*占位符传值*/
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //处理查询结果集
            //需要知道查询的:列名 和 列数
            //metaDate:获取元数据
            ResultSetMetaData metaData = ps.getMetaData();
            //获取查询的列数
            int columnCount = metaData.getColumnCount();
            rs = ps.executeQuery();
            while (rs.next()) {
                //调用这个类的无参构造方法
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取数据
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射机制向 t 对象传值
                    Field field = tClass.getDeclaredField(columnName);
                    //打破封装
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
               // 查到的数据增加到集合中
               list.add(t);

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放资源*/
            JDBCUtils.close(conn, rs, ps);
        }
        return list;
    }

测试:

十、BaseDao 的封装以及实现

 BaseDAO:一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的底层数据操作自定义类,简单来说BaseDao里封装了对数据的增删改查的方法。

BaseDao类的创建:将上面编写好的 增删改查 方法 封装到BaseDao类中。

由于BaseDao不需要实例化,只需要提供一些方法,设置成私有即可。

/*封装了所有表的通用操作【增删改查】,不需要实例化*/
public abstract class BaseDao {

    /*增删改
     * Object...args :可变长参数,用于对 占位符 传值。
     * */
    public static void update(String sql, Object... args) {
        PreparedStatement ps = null;
        Connection conn = null;
        try {
            //获取连接
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            //为占位符传值
            for (int i = 0; i < args.length; i++) {
                //注意参数别写错
                ps.setObject(i + 1, args[i]);
            }
            //执行 sql 语句
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            JDBCUtils.close(conn, null, ps);
        }
    }

    /* 查询:仅能返回一条记录
       用泛型代替 具体的某个对象
     * Class<T>:表示某个对象的类
     * */
    public static <T> T query(Class<T> tClass, 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]);
            }
            //处理查询结果集
            //需要知道查询的:列名 和 列数
            //metaDate:获取元数据
            ResultSetMetaData metaData = ps.getMetaData();
            //获取查询的列数
            int columnCount = metaData.getColumnCount();
            rs = ps.executeQuery();
            if (rs.next()) {
                //调用这个类的无参构造方法
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取数据
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射机制向 t 对象传值
                    Field field = tClass.getDeclaredField(columnName);
                    //打破封装
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放资源*/
            JDBCUtils.close(conn, rs, ps);
        }
        return null;
    }
    //针对不同的表 返回多条记录
    public static <T> List<T> getForList(Class<T> tClass, String sql, Object...args){
        /*获取连接*/
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<T> list = new ArrayList<>();
        try {
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            /*占位符传值*/
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //处理查询结果集
            //需要知道查询的:列名 和 列数
            //metaDate:获取元数据
            ResultSetMetaData metaData = ps.getMetaData();
            //获取查询的列数
            int columnCount = metaData.getColumnCount();
            rs = ps.executeQuery();
            while (rs.next()) {
                //调用这个类的无参构造方法
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取数据
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射机制向 t 对象传值
                    Field field = tClass.getDeclaredField(columnName);
                    //打破封装
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                // 查到的数据增加到集合中
                list.add(t);

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放资源*/
            JDBCUtils.close(conn, rs, ps);
        }
        return list;
    }
}

BaseDao 的实现:需要指定的接口和实现类,接口用于对某张表进行的增删改查操作,实现类实现具体的方法。

接口:描述对某张数据库表的操作【增删改查】,一般以:.....Dao 命名。

实现类:需要继承BaseDao,通过BaseDao中的方法,实现接口中的方法,一般以:.....ImplDao 命名。

接口类:

/*
* 此接口仅对于t_user表的增删改查操作
* */
public interface UserDao {
    //增加
    void insert(UserBean user);

    //通过ID删除
    void delete(int id);

    //修改
    void update(UserBean user);

    //通过id查询
    UserBean findByID(int id);

    //查询多条记录
    List<UserBean> findAll() ;
}

实现类:

//实现类
public class UserImplDao extends BaseDao implements UserDao{
    @Override
    public void insert(UserBean user) {
        String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
        update(sql,user.getUserName(),user.getPasswd(),user.getRealName());
    }

    @Override
    public void delete(int id) {
        String sql = "delete from t_user where id=?";
        update(sql,id);
    }

    @Override
    public void update(UserBean user) {
        String sql = "update t_user set userName=?,passwd=?,realName=? where id=?";
        update(sql,user.getUserName(),user.getPasswd(),user.getRealName(),user.getId());
    }

    @Override
    public UserBean findByID(int id) {
        String sql = "select * from t_user where id=?";
        UserBean user = query(UserBean.class, sql, id);
        return user;
    }

    @Override
    public List<UserBean> findAll() {
        String sql = "select * from t_user";
        List<UserBean> list = getForList(UserBean.class, sql);
        return list;
    }
}

测试:

public class UserDaoTest {
    private UserImplDao userImplDao = new UserImplDao();
    @Test
    public void testInsert(){
        /*这个ID无所谓,前面没有用到*/
        UserBean user = new UserBean(5,"zhangsan","111","张三");
        userImplDao.insert(user);
    }
    @Test
    public void testUpdate(){
        UserBean user = new UserBean(5,"lisi","222","李四");
        userImplDao.update(user);
    }
    @Test
    public void testDelete(){
        userImplDao.delete(5);
    }
    @Test
    public void testFindById(){
        UserBean user = userImplDao.findByID(1);
        System.out.println(user);
    }
    @Test
    public void testFindAll(){
        List<UserBean> list = userImplDao.findAll();
        list.forEach(System.out::println);
    }
}

十一、数据库连接池技术

普通连接数据库的弊端?

普通的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码( 得花费 0.05s 1s 的时间 ) 。需要数据库连接的时候,就向数据库要求 一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很 好的重复利用。 若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严 重的甚至会造成服务器的崩溃。
数据库连接池技术
  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想:就是为数据库连接建立一个缓冲池。预先在缓冲池中放入一定数量的连接,当需要 建立数据库连接时,只需从缓冲池中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重 新建立一个

 数据库连接池技术的优点

  • 1. 资源重用
    • 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
  • 2. 更快的系统反应速度
    • 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均 已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销, 从而减少了系统的响应时间
  • 3. 新的资源分配手段
    • 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库 连接数的限制,避免某一应用独占所有的数据库资源
  • 4. 统一的连接管理,避免数据库连接泄漏
    • 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据 库连接操作中可能出现的资源泄露
多种开源的数据库连接池

JDBC的数据库连接池使用 java.sql.DataSource 来表示,DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接 池。

DataSource 用来取代 DriverManager 来获取 Connection ,获取速度快,同时可以大幅度提高数据库访问速 度。DataSource 是一个接口。由服务器实现: 
  • DBCP Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因 自身存在BUGHibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
  • Proxool sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一
  • BoneCP 是一个开源组织提供的数据库连接池,速度快
  • Druid 是阿里提供的数据库连接池,据说是集DBCP C3P0 Proxool 优点于一身的数据库连接池,但是 速度不确定是否有BoneCP

使用 C3P0数据库连接池获取连接: 

将 jar 包 导入项目中的 lib 目录下

 第一种方式:使用C3P0 数据库连接池

        /*获取 C3P0 数据库连接池*/
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
        cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
        cpds.setUser("root");
        cpds.setPassword("root");

        //设置连接池初始连接数
        cpds.setInitialPoolSize(10);

        Connection conn = cpds.getConnection();
        System.out.println(conn);

第二种方式:使用 xml 配置文件【推荐使用】

<c3p0-config>

   <!--连接数据库的四个基本信息-->
    <named-config name="helloC3P0">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!--当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数-->
        <property name="acquireIncrement">5</property>
        <!--初始化的连接数-->
        <property name="initialPoolSize">10</property>
        <!--维护的最少连接数-->
        <property name="minPoolSize">1</property>
        <!--维护的最多连接数-->
        <property name="maxPoolSize">5</property>
        <!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
        <property name="maxStatements">20</property>
        <!-- 每个连接同时可以使用的 Statement 对象的个数 -->
        <property name="maxStatementsPerConnection">50</property>

    </named-config>
</c3p0-config>
        // " " 里面的名字和 配置文件中 <named-config name="helloC3P0"> 一致。
        ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");
        Connection conn = cpds.getConnection();
        System.out.println(conn);

 使用C3P0的帮助文档:doc目录下 ----> index.html。帮助文档中有提供连接代码以及配置文件代码。

 Dbcp 数据库连接池技术:

将俩个 jar 包导入项目中的 lib 目录下。

 

 第一种方式:

    //第一种方式
    @Test
    public void DbcpTest() throws SQLException {
        BasicDataSource source = new BasicDataSource();
        source.setDriverClassName("com.mysql.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:3306/test");
        source.setUsername("root");
        source.setPassword("root");

        Connection conn = source.getConnection();
        System.out.println(conn);
    }

第二种方式:配置文件【推荐使用】

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
  //第二种方式:配置文件
    @Test
    public void DbcpTest02() throws Exception {
        //加载流
        Properties pr = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
        pr.load(is);

        DataSource source = BasicDataSourceFactory.createDataSource(pr);
        Connection conn = source.getConnection();
        System.out.println(conn);
    }

 

Druid(德鲁伊)数据库连接池【目前使用较多的】

增加 jar 包 

 

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
//第一种方式
    @Test
    public  void DruidTest() throws SQLException {
        DruidDataSource source = new DruidDataSource();
        source.setDriverClassName("com.mysql.jdbc.Driver" );
        source.setUrl("jdbc:mysql://localhost:3306/test");
        source.setUsername("root");
        source.setPassword("root");

        DruidPooledConnection conn = source.getConnection();
        System.out.println(conn);
    }
    //第二种方式:配置文件
    @Test
    public void DruidTest02() throws Exception {
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        pros.load(is);

        DataSource source = DruidDataSourceFactory.createDataSource(pros);
        Connection conn = source.getConnection();
        System.out.println(conn);
    }

十二、Apache-DBUtils实现CRUD操作

DBUtils 介绍:

commons-dbutils Apache 组织提供的一个开源 JDBC 工具类库,它是对 JDBC 的简单封装,学习成本极低, 并且使用dbutils 能极大简化 jdbc 编码的工作量,同时也不会影响程序的性能。
  • org.apache.commons.dbutils.QueryRunner                封装了增删改查的方法
  • org.apache.commons.dbutils.ResultSetHandler         处理查询结果集

 增删改查直接封装到了QueryRunner类        中,使用更方便。

 演示数据库增加数据:

    //增加
    @Test
    public void update() throws SQLException {
        QueryRunner runner = new QueryRunner();
        //获取连接
        Connection conn = JDBCUtils.getConnection();
        String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
        int count = runner.update(conn, sql, "wangwu", "2344", "王五");
        System.out.println("影响数据库条数:" + count);
    }

演示查询数据: 

 ResultSetHandler 接口中的实现类是为了方便 查询返回的结果。

  //查询多条记录
    @Test
    public void query() throws SQLException {
        QueryRunner runner = new QueryRunner();
        //获取连接
        Connection conn = JDBCUtils.getConnection();
        BeanListHandler<UserBean> listHandler = new BeanListHandler<>(UserBean.class);
        String sql = "select * from t_user where id>?";
        List<UserBean> list = runner.query(conn, sql, listHandler, 2);
        list.forEach(System.out::println);
    }

 当查询特殊值的时候使用 ScalarHandler 类

 /*
    *ScalarHandler() {} 查询特殊值的时候使用。
    * 查询表中的数据条数
    * */
    @Test
    public void queryTest02() throws SQLException {
        QueryRunner runner = new QueryRunner();
        //获取连接
        Connection conn = JDBCUtils.getConnection();
        ScalarHandler<Object> handler = new ScalarHandler<>();
        String sql = "select count(*) from t_user";
        Object count = runner.query(conn, sql, handler);
        System.out.println("数据库中记录总条数:" + count);
    }

    /*
     *ScalarHandler() {} 查询emp表中入职最晚的
     * */
    @Test
    public void queryTest03() throws SQLException {
        QueryRunner runner = new QueryRunner();
        //获取连接
        Connection conn = JDBCUtils.getConnection();
        ScalarHandler<Date> handler = new ScalarHandler<>();
        String sql = "select max(HIREDATE) from emp";
        Date date =  runner.query(conn, sql, handler);
        System.out.println("入职最晚的:" + date);
    }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲨瓜2号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值