JDBC


JDBC

JDBC的概述

前言

  • JDBC(Java DataBase Connectivity)就是Java数据库连接,即一套使用Java语言来操作数据库的编程接口,也可以认为是一组规范。

JDBC 的原理

  • 早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!

程序员,JDBC,JDBC驱动的关系

  • 三方关系

  • SUN公司是规范制定者,制定了规范JDBC(连接数据库规范)

    数据库厂商微软、甲骨文等分别提供实现JDBC接口的驱动jar包

    程序员学习JDBC规范来应用这些jar包里的类。

    三角关系

  • 总结:

  • 简单地说,JDBC 可做三件事:与数据库建立连接、发送指令操作数据库并处理结果。


JDBC操作数据库的步骤

总体步骤

  1. 官网下载驱动包

  2. 加载一个Driver驱动

  3. 创建数据库连接(Connection)

  4. 创建SQL命令发送器Statement

  5. 创建SQL

  6. 通过Statement发送SQL命令并得到结果

  7. 处理SQL结果(select语句)

  8. 关闭数据库资源

    • ResultSet

    • Statement

    • Connection

准备工作

创建数据库及数据表

  • 在navicat Premium 15或者dos窗口中创建一个自己的数据库
  • 在创建的数据库中创建自己的数据表

创建项目

  • 在idea中新建一个自己的项目

创建lib目录并引入MySQL驱动包

  • 在项目下新建一个lib目录
  • 在目录中引入MySQL驱动包
  • 在把lib包引入项目环境中

使用JDBC完成数据的添加操作

步骤

  1. 加载MySQL的JDBC驱动

  2. 建立数据的连接

  3. 创建SQL命令的发送器

  4. 编写SQL

  5. 使用SQL命令发送器发送SQL命令并得到结果

  6. 处理结果

  7. 关闭数据库资源

演示代码

  • Java

    package com.abc.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class Test01Add {
        // 驱动器路径
        private static final String DRIVER = "com.mysql.jdbc.Driver";
        //连接数据库地址
        private static final String URL = "jdbc:mysql://localhost:3306/whpowernode?useUnicode=true&useSSL=false&characterEncoding=UTF8";
        //数据库用户名
        private static final String USER_NAME = "root";
        //数据库密码
        private static final String USER_PASSWORD = "123456";
        
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            // 加载JDBC访问Oracle的驱动
            Class.forName(DRIVER);
            // 建立和数据库的连接
            Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
            // 创建SQL命令发送器
            Statement stmt = conn.createStatement();
            // 使用SQL命令发送器发送SQL命令并得到结果
            String sql = "insert into student values(1,'小刚',32,'男','湖北省武汉市')";
            int n = stmt.executeUpdate(sql);
            // 处理结果
            if (n > 0) {
                System.out.println("添加成功");
            } else {
                System.out.println("添加失败");
            }
            // 关闭数据库资源
            stmt.close();
            conn.close();
        }
    }
    

URL详解

  • 为什么要定义URL

    • Java和MySQL是厂商的,Java程序和MySQL数据库此时不在同一个进程下,此时Java程序需要向MySQL发送请求。
  • 如何发送请求

    jdbc:mysql://localhost:3306/whpowernode?useUnicode=true&useSSL=false&characterEncoding=UTF8
    

使用JDBC完成更新和删除操作

修改数据

  • 代码

    package com.abc.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    
    public class Test02Update {
        // 驱动器路径
        private static final String DRIVER = "com.mysql.jdbc.Driver";
        //连接数据库地址
        private static final String URL = "jdbc:mysql://localhost:3306/whpowernode?useUnicode=true&useSSL=false&characterEncoding=UTF8";
        //数据库用户名
        private static final String USER_NAME = "root";
        //数据库密码
        private static final String USER_PASSWORD = "123456";
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            // 加载Oracle的JDBC驱动
            Class.forName(DRIVER);
            // 建立数据的连接
            Connection conn=DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
            // 创建SQL命令的发送器
            Statement stat=conn.createStatement();
            // 编写SQL
            String sql="update student set name='小明',age=23,sex='女',address='武汉' where id=1";
            // 使用SQL命令发送器发送SQL命令并得到结果
            int res=stat.executeUpdate(sql);
            // 处理结果
            if(res>0){
                System.out.println("修改成功");
            }
            else{
                System.out.println("处理失败");
            }
            // 关闭数据库资源
            stat.close();
            conn.close();
        }
    }
    

删除数据

  • 代码

    package com.abc.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class Test03Delete {
        // 驱动器路径
        private static final String DRIVER = "com.mysql.jdbc.Driver";
        //连接数据库地址
        private static final String URL = "jdbc:mysql://localhost:3306/whpowernode?useUnicode=true&useSSL=false&characterEncoding=UTF8";
        //数据库用户名
        private static final String USER_NAME = "root";
        //数据库密码
        private static final String USER_PASSWORD = "123456";
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            // 加载Oracle的JDBC驱动
            Class.forName(DRIVER);
            // 建立数据的连接
            Connection conn=DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
            // 创建SQL命令的发送器
            Statement stat=conn.createStatement();
            // 编写SQL
            String sql="delete from student where id=1";
            // 使用SQL命令发送器发送SQL命令并得到结果
            int res=stat.executeUpdate(sql);
            // 处理结果
            if(res>0){
                System.out.println("删除成功");
            }
            else{
                System.out.println("删除失败");
            }
            // 关闭数据库资源
            stat.close();
            conn.close();
        }
    }
    

DBUtils的简单封装

封装

  • 我们为什么要封装,从以上代码可以看出,每一次写我们创建一个连接,创建一个发送SQL的对象,最后还要关闭,那么我们可以考虑把这重复的代码提取出来!

  • 封装代码

    package com.abc.utils;
    
    import java.io.Closeable;
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    public class DBUtils {
        // 驱动器路径
        private static final String DRIVER = "com.mysql.jdbc.Driver";
        // 连接数据库地址
        private static final String URL = "jdbc:mysql://localhost:3306/whpowernode?useUnicode=true&useSSL=false&characterEncoding=UTF8";
        // 数据库用户名
        private static final String USER_NAME = "root";
        // 数据库密码
        private static final String USER_PASSWORD = "123456";
    
        /**
         * 静态加载驱动程序
         */
        static {
            try {
                Class.forName(DRIVER);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        /**
         * @return 连接对象
         */
        public static Connection getConn() {
            try {
                return  DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
            } catch (SQLException e) {
                e.printStackTrace();
                System.out.println("创建连接对象异常");
            }
            return null;
        }
    
        /**
         * 关闭资源
         */
        public static void close(Connection conn,Statement statement) {
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

使用JDBC完成查询

  • 代码

    package com.abc.jdbc;
    
    import com.abc.utils.DBUtils;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class Test04Query {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            Connection conn = DBUtils.getConn();
            // 创建SQL命令发送器
            Statement stmt = conn.createStatement();
            // 编写SQL
            String sql="select * from student";
            // 使用SQL命令发送器发送SQL命令并得到结果
            ResultSet rs=stmt.executeQuery(sql);
            // 处理结果
            while(rs.next()){
                int id=rs.getInt(1);
                String name=rs.getString(2);
                int age=rs.getInt(3);
                String sex=rs.getString(4);
                String address=rs.getString(5);
                System.out.println(id+"  "+name+"  "+age+"   "+sex+"   "+address);
            }
            // 关闭数据库资源
            DBUtils.close(rs);
            DBUtils.close(stmt);
            DBUtils.close(conn);
        }
    }
    

查询的扩展内容

  • 查询多占位符时,使用非暴力获取属性

  • 代码

    //获取所有成员的名称,封装到beanInfo中
     BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
     //从beanInfo中提取出属性成员
     PropertyDescriptor[] pd = beanInfo.getPropertyDescriptors();
     //创建用户对象
     T t = clz.newInstance();
     //遍历,取出属性的name,然后通过name取出对应的setter方法
     for (PropertyDescriptor descriptor : pd) {
         //取出字段的名称
         String name = descriptor.getName();
         //通过字段名,获取对应的值
         Object value = rs.getObject(name);
    
         //获取set方法的方法对象
         Method method = descriptor.getWriteMethod();
         //执行set方法
         method.invoke(t,value);
     }
    

分页查询案例

  • 代码:封转分页类

    /**
     * 封装分页类
     */
    public class Paging {
        private int pageSize;//每页显示的条数
        private int currentPage;//当前页
        private List listData;//当前页显示的数据
        private int totalSize;//总条数
    
        private int prePage;//上一页(计算)
        private int nextPage;//下一页(计算)
        private int totalPage;//总页数(计算)
    
        //重载构造方法,用于没有数据的时候
        public Paging(int currentPage,int pageSize) {
            this(currentPage,0,pageSize,null);
        }
    
        public Paging(int currentPage,int totalSize,int pageSize,List listData) {
            this.pageSize = pageSize;
            this.currentPage = currentPage;
            this.listData = listData;
            this.totalSize = totalSize;
    
            //如果总条数为0,那么就不用计算了
            if (totalSize == 0){
                this.totalPage = 1;
                this.prePage = 1;
                this.nextPage = 1;
                return;
            }
    
            //将需要计算的数据先算出来
            //计算上一页
            this.prePage = currentPage - 1 > 0 ? currentPage - 1 : currentPage;
            //计算总页数
            this.totalPage = totalSize % pageSize > 0 ? totalSize /pageSize + 1 : totalSize /pageSize;
            //计算下一页
            this.nextPage = currentPage + 1 > totalPage ? totalPage : currentPage + 1;
        }
    
        public int getPageSize() {
            return pageSize;
        }
    
        public int getCurrentPage() {
            return currentPage;
        }
    
        public List getListData() {
            return listData;
        }
    
        public int getPrePage() {
            return prePage;
        }
    
        public int getNextPage() {
            return nextPage;
        }
    
        public int getTotalPage() {
            return totalPage;
        }
    
        public int getTotalSize() {
            return totalSize;
        }
    
        @Override
        public String toString() {
            return "Paging{" +
                    "pageSize=" + pageSize +
                    ", currentPage=" + currentPage +
                    ", listData=" + listData +
                    ", totalSize=" + totalSize +
                    ", prePage=" + prePage +
                    ", nextPage=" + nextPage +
                    ", totalPage=" + totalPage +
                    '}';
        }
    }
    
  • 实现分页

    public Paging pagingSelect(PagingObject pag) {
            //先查询总条数
            String sql = "select * from t_student";
            List users = DMLUtil.executeDql(sql, User.class);
            if (users.size() == 0){
                return new Paging(pag.getCurrentPage(),pag.getPageSize());
            }
            //如果有数据,开始查询
            sql = "select * from t_student limit ?,?";
            List list = DMLUtil.executeDql(sql, User.class,pag.getindex(),pag.getPageSize());
            return new Paging(pag.getCurrentPage(),users.size(),pag.getPageSize(),list);
        }
    
  • 工具类

    package com.abc.util;
    
    //执行查询的sql工具类
    public static  List executeDql(String sql,Class clz, Object...objs){
            List list = new ArrayList<>();
            try {
                conn = ds.getConnection();
                ps = conn.prepareStatement(sql);
                for (int i = 0; i < objs.length; i++) {
                    ps.setObject(i+1,objs[i]);
                }
                rs = ps.executeQuery();
                while (rs.next()){
                    //获取所有成员的名称,封装到beanInfo中
                    BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
                    //从beanInfo中提取出属性成员
                    PropertyDescriptor[] pd = beanInfo.getPropertyDescriptors();
                    //创建用户对象
                    T t = clz.newInstance();
                    //遍历,取出属性的name,然后通过name取出对应的setter方法
                    for (PropertyDescriptor descriptor : pd) {
                        //取出字段的名称
                        String name = descriptor.getName();
                        //通过字段名,获取对应的值
                        Object value = rs.getObject(name);
    
                        //获取set方法的方法对象
                        Method method = descriptor.getWriteMethod();
                        //执行set方法
                        method.invoke(t,value);
                    }
                    list.add(t);
                }
                return list;
            } catch (Exception throwables) {
                throwables.printStackTrace();
            } finally {
                dqlRelease(conn,ps,rs);
            }
            return null;
        }
    
    
    
    //分页查询用户操作的工具类
    package com.abc.paging;
    
    public class PagingObject {
        private int currentPage = 1;//用户操作的当前页
        private int pageSize = 3;//用户操作的每页显示条数
    
    
        public int getindex(){
            return (currentPage - 1 ) * pageSize;
        }
    
        public int getCurrentPage() {
            return currentPage;
        }
    
        public void setCurrentPage(int currentPage) {
            this.currentPage = currentPage;
        }
    
        public int getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }
    }
    

事务的四大特性

原子性

  • 事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

一致性

  • 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

隔离性

  • 一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性

  • 也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

事务的运用

  • 代码

    public class Test08Transaction {
        public static void main(String[] args) {
            //声明连接对象
            Connection conn=null;
            //声明发送SQL的接口对象
            Statement stmt=null;
            try {
                //创建连接对象
                conn = DBUtils.getConn();
                // 开启事务,关闭自动提交事务
                conn.setAutoCommit(false);
                // 编写SQL
                String sql1 = "update account set amount = amount-1000 where aid=1";
                String sql2 = "update account set amount = amount+1000 where aid=2";
                // 创建SQL命令发送器
                stmt = conn.createStatement();
                stmt.executeUpdate(sql1);
                stmt.executeUpdate(sql2);
                conn.commit();//手动提交
            }catch (Exception e){
                e.printStackTrace();
                try {
                    //回滚事务
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally {
                // 关闭数据库资源
                DBUtils.close(stmt);
                DBUtils.close(conn);
            }
    
        }
    }
    
    • 注意:不管事务是否成功,最后都需要回滚。当执行中报异常使代码无法手动提交事务时,也需要回滚,此时回滚是起到释放锁的作用。

JDBC批处理

什么是批处理

  • 批处理是建立一次连接(创建一个Connection对象)的情况下批量执行多个DML语句,这些DML语句要么全部成功要么全部失败。如何确保全部成功or全部失败呢?在JDBC中开启事务,使用事务管理DML语句。

  • 例如:使用批处理根据id批量的删除student表中的数据

    1. 定义SQL配置文件
    2. 创建Connection对象
    3. 创建PreparedStatement对象
    4. 将提交方式设置为手动提交,开启事务
    5. 设置占位符
    6. 将占位符添加到批处理中(相当于收集若干个本子,放入包包中)
    7. 执行批处理
    8. 提交事务
    9. 如果批处理失败,在catch块中回滚事务
    10. 关闭资源
    public class Test09Batch {
        public static void main(String[] args) {
            //模拟要删除的数据
            List ids= Arrays.asList(1,2,3,4,5);
            //声明连接对象
            Connection conn=null;
            //声明发送SQL的接口对象
            PreparedStatement pstmt=null;
            try {
                //创建连接对象
                conn = DBUtils.getConn();
                // 关闭自动提交事务
                conn.setAutoCommit(false);
                // 编写SQL
                String sql = "delete from student where id = ?;";
                // 创建SQL命令发送器
                pstmt = conn.prepareStatement(sql);
                for (Integer id : ids) {
                    pstmt.setInt(1,id);
                    pstmt.addBatch();
                }
                int[] rows = pstmt.executeBatch();
                System.out.println("受影响的行数为:"+Arrays.toString(rows));
                conn.commit();
            }catch (Exception e){
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally {
                // 关闭数据库资源
                DBUtils.close(pstmt);
                DBUtils.close(conn);
            }
    
        }
    }
    

连接池

连接池概念

  • 连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

    连接池是装有连接的容器,使用连接的话,可以从连接池中进行获取,使用完成之后将连接归还给连接池。

为什么使用连接池

  • 连接对象创建和销毁是需要耗费时间的,在服务器初始化的时候就初始化一些连接。把这些连接放入到内存中,使用的时候可以从内存中获取,使用完成之后将连接放入连接池中。从内存中获取和归还的效率要远远高于创建和销毁的效率。(提升性能)。

创建一个连接池

  • 代码

    //创建连接池对象
    DruidDataSource ds = new DruidDataSource();
    //从连接池中获取驱动
    ds.setDriverClassName(properties.getProperty("driverClassName"));
    //从连接池中获取链接
    ds.setUrl(properties.getProperty("url"));
    ds.setUsername(properties.getProperty("username"));
    ds.setPassword(properties.getProperty("password"));
    

使用配置文件封装连接池

  • 代码

  • 编写配置文件

    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql:///test
    username=root
    password=123456
    
    • 注意:使用配置文件封装连接池时,配置名必须是以上名称,否则,连接池读取不到配置信息
  • 完整封装代码

    package com.abc.util;
    
    import com.alibaba.druid.pool.DruidDataSource;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    
    public class DruidUtil {
        private DruidUtil() {
        }
    
        private static DruidDataSource ds = null;
        /**
         * 获取驱动,链接的方法
         */
        static{
    
            InputStream is = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("db.properties");
            Properties properties = new Properties();
            //创建连接池对象
            ds = new DruidDataSource();
            try {
                properties.load(is);
                //从连接池中获取驱动
                ds.setDriverClassName(properties.getProperty("driverClassName"));
                //从连接池中获取链接
                ds.setUrl(properties.getProperty("url"));
                ds.setUsername(properties.getProperty("username"));
                ds.setPassword(properties.getProperty("password"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        /**
         * 返回一个Druid的方法
         */
        public static DruidDataSource druidDS(){
            return ds;
        }
    }
    
  • 注:以上代码中的连接池部分可以继续封装成工厂来调用


文章作者: 勾魂大猩猩
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 勾魂大猩猩 !
  目录