IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 22-05-11 mysql(03)JDBC、java连接数据库、JDBCUtils 、Statement、PreparedStatement增删改、图片的处理、批处理、把查询结果封装对象 -> 正文阅读

[Java知识库]22-05-11 mysql(03)JDBC、java连接数据库、JDBCUtils 、Statement、PreparedStatement增删改、图片的处理、批处理、把查询结果封装对象

数据持久化:把数据永久的存储到磁盘中。

数据库厂商不同(mysql/oracle/sqlServer),内核也不同。
java应用程序提供了接口规范【jdbc】,数据库厂商针对这一套接口实现[驱动就是一大堆实现类]。


一、JDBC

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要
面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,
提供不同实现。不同的实现的集合,即为不同数据库的驱动。————面向接口编程

JDBC是一个独立于特定数据库管理系统通用的SQL数据库存取和操作的公共接口

JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统
?

jdbc:

  1. 面向应用的api;
  2. 面向数据库的api:java Driver Api,给厂商提供的

jdbc驱动程序分类:

  1. odbc
  2. jdbc-odbc
  3. 部分本地api部分Java的驱动程序
  4. 本地协议的纯 Java 驱动程序

二、java程序连接mysql四种方式

不管哪种方式有俩个一定一样的点:

1、连接字符串
? ? ?ip地址和端口号
? ? ?数据库实例名(连接哪一个数据库)
2、用户名和密码

Driver接口 ?java.sql.Driver ?是所有 JDBC 驱动程序需要实现的接口
不同数据库厂商提供这个接口不同的实现类

方式一:关键字,Properties

每次用户名和密码,都要创建 Properties ,有一点点麻烦

    @Test
    public void test1() throws Exception {
        //不同的数据库厂商的driver实现类不同,所以这里定义为null,用反射动态获取Driver对象
        Driver driver = null;

        //1. 获取驱动 这里mysql5和mysql是不一样的,这里用的是MySQL8
        String className = "com.mysql.cj.jdbc.Driver";
        //不确定的对象用反射
        Class clazz = Class.forName(className);
        //获取Driver实例
        driver = (Driver) clazz.newInstance();

        //2. 获取连接 这里mysql5和mysql8不一样,这里用的是MySQL8  myemployees是数据库实例名
        String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
        Properties info = new Properties();
        //Properties集合的key是user和password,固定死的不可以改
        info.setProperty("user", "root");
        info.setProperty("password", "123456");
        Connection conn = driver.connect(url, info);
        System.out.println(conn);
    }

方式二:关键字,DriverManager? 驱动管理类

DriverManager.registerDriver  注册驱动
DriverManager.getConnection(url, user, password); 获取连接,不再封装properties
    @Test
    public void test2() throws Exception {
        String className = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
        String user = "root";
        String password = "123456";

        //1. 注册驱动
        Class clazz = Class.forName(className);
        Driver driver = (Driver) clazz.newInstance();
        DriverManager.registerDriver(driver);

        //2. 获取连接
        Connection conn = DriverManager.getConnection(url, user, password);

        System.out.println(conn);
    }

?方式三:关键字,加载驱动不再是注册驱动

已经在静态代码块中注册完成了,在类加载的时候就会注册驱动了

@Test
public void test3() throws Exception {
    String className = "com.mysql.cj.jdbc.Driver";
    String url = "jdbc:mysql://127.0.0.1:3306/myemployees?serverTimezone=UTC";
    String user = "root";
    String password = "123456";

    //1.加载驱动
    Class.forName(className);

    //2. 获取连接
    Connection conn = DriverManager.getConnection(url, user, password);

    System.out.println(conn);

}

方式四:结合属性文件properties

Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例

?

    @Test
    public void test4() throws Exception {
        Properties props = new Properties();
        props.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties"));
        String driverClassName = props.getProperty("driverClassName");
        String url = props.getProperty("url");
        String user = props.getProperty("user");
        String password = props.getProperty("password");
        //1. 加载驱动,注册驱动的事已经在Driver中帮我们做了
        Class.forName(driverClassName);
        //2. 获取连接
        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);
    }

三、JDBCUtils?

自己封装一个类,这样方便连接数据库

  • 1.获取数据库的连接? ? JDBCUtils .getConnection()
  • 2.关闭资源? ?? JDBCUtils.close()
public class JDBCUtils {

    /**
     * 获取数据库连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        Properties props = new Properties();
        //jdbc.properties在类路径下,即src下
        props.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
        String driverClassName = props.getProperty("driverClassName");
        String url = props.getProperty("url");
        String user = props.getProperty("user");
        String password = props.getProperty("password");

        //1. 加载驱动
        Class.forName(driverClassName);

        //2. 获取连接
        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 关闭连接
     * @param conn
     * @param ps
     * @param rs
     */
    public static void close(Connection conn, PreparedStatement ps, ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException 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();
            }
        }
    }

    /**
     * 关闭连接
     * @param conn
     * @param ps
     */
    public static void close(Connection conn, PreparedStatement ps){
        if(ps != null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

四、Statement接口 和 PreparedStatement接口

俩者都可以发送sql语句给数据库,但是PreparedStatement接口可以预编译sql,且可以防止sql注入。

面试题:为什么不用Statement接口

1、Statement 要用+拼接字符串的方式拼接sql,麻烦且容易出错。
2.? 会造成sql注入,比如:1' or 1='1? ,因为是拼接字符串,会造成sql永远成立

-- 正常的使用用户名、密码 登录

SELECT USER, PASSWORD FROM user_table 
WHERE USER = 'AA' AND PASSWORD = '123456'

-- 传入的用户名和密码 1' or 1='1 也可以登录成功,拼接sql存在sql注入,应用占位符方式
SELECT USER, PASSWORD FROM user_table 
WHERE USER = '1' OR 1='1' AND PASSWORD = '1' OR 1='1'

?


PreparedStatement接口

使用占位符解决了sql注入,预编译sql:就是提前放到内存了。

每个数据库都有自己的内存:【mysql内存情况】
查询缓存:存储sql语句,不经常改变的sql 语句【???】使用占位符的预编译sql也会放在这里
数据缓冲区:
日志缓冲区:恢复备份

sql->查询缓存-》解析-》校验->生成执行计划-》执行【待完善、、、】

PreparedStatement接口 完成增删改

写一个通用的方法update(),达到使用java程序就能执行增删改数据表数据的效果

? ? @Test
? ? public void test3() {
? ? ? ? String sql = "insert into `order`(order_name, order_date) values(?,?)";
        //后俩个参数是占位符的值,注意顺序是对应的
? ? ? ? int row = update(sql, "JJ", "1999-9-9");
? ? ? ? System.out.println("已影响" + row + "行");
? ? }

这里异常会比较多,可以先不管,最后写完代码后,?close关闭资源代码留在外面,其他选中。ctrl+alt+t? ? 【?try一下】

public int update(String sql, Object... args)

这里sql即我们预编译的sql,是有?的。args是可变参数,用来传递占位符?要给的值。

public class JDBCTest {

    public int update(String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        int row = 0;
        try {
//            1.获取数据库连接
            conn = JDBCUtils.getConnection();
//            2.通过当前连接获取PreparedStatement实例ps,参数要一个String类型的sql语句
            ps = conn.prepareStatement(sql);
//            3.填充占位符。ps.setXXX();  sql中索引从1开始。 ps.setDate() 是java.sql.Date
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
//            4.发送sql给数据库,ps.excuteUpdate();//返回受影响的行数
            row = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            5.关闭连接 用工具类 JDBCUtils .close(conn,ps);
            JDBCUtils.close(conn, ps);
        }
        return row;
    }

}

五、ORM 对象关系映射

  1. 数据库中的一张表对应java一个类,
  2. 数据表中的一条数据对应Java一个对象?

查询单个对象get()---基础版本

? ? @Test
? ? public void testGet(){
? ? ? ? Customer cus = get();
? ? ? ? System.out.println(cus);
? ? }

ResultSet 结果集

next()? //返回true和false,返回true移动记录指针到下一行
getXXX()? ?//根据列的索引,从1开始
getXXX()//有别名按照别名取,没有别名按照列名取

  public class Customer {
    private int id;
    private  String name;
    private String email;
    private Date birth;
 }

  public Customer get(){
        Connection conn = null;
        PreparedStatement ps = null;
        Customer customer = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "select id, name, email, birth from customers where id = ?";
            ps = conn.prepareStatement(sql);
            customer = null;

            ps.setInt(1, 16);
//            ResultSet接口:执行查询后生成的数据表
//            返回ResultSet 结果集
            rs = ps.executeQuery();
//            next()  返回true和false,并移动记录指针到下一行,开始时记录指针在第一行之前
//            查多行时使用while   while(rs.next())
            if(rs.next()){
//                重载方法:
//                getXXX()   根据列的索引获取,从1开始
//                getXXX() 有别名按照别名取,没有别名按照列名取
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                Date birth = rs.getDate("birth");
//                ORM 对象关系映射
//                数据库中的一张表对应java一个类,
//                数据表中的一条数据对应Java一个对象  如果是属性Date类型要是java.sql下的
                customer = new Customer(id, name, email, birth);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            结果集也要关闭,jdbcutils加一个重载方法close
            JDBCUtils.close(conn, ps, rs);
        }

        return customer;
    }

泛型与反射优化

编写一个通用的查询,适用于任何表

从数据库中查到的数据封装到一个对象或对象list

优化的方向

1. 返回值类型处
? ? ①不确定的类型使用泛型
? ? ②不确定的对象使用反射

2. 结果集的处理?
? ? ? ResultSetMetaData : 描述结果集的元数据

ResultSetMetaData【元数据】

可用于获取关于 ResultSet 结果集中列的类型和属性信息

ResultSetMetaData:可以获取对应的 ResultSet 有多少列, 每一列的列名都是什么。

  1. ? ? ? getColumnCount() : 获取结果集的列的个数
  2. ? ? ? getColumnName() : 获取结果集列名
  3. ? ? ? getColumnLabel() : 获取结果集列的别名,有别名获取别名,没别名 ?getColumnName()

testQuery()和query()是我自己默写出来的,自豪

   public class Order {
       private int orderId;//对应数据库order表的order_id
       private String orderName;//对应数据库表的order_name
       private Date orderDate;//对应数据库表的order_date
   }

    @Test
    public void testQuery() throws Exception {
        //查询数据库表customers,把获取到的数据封装到Customer对象
        String sql1 = "select id, name, email, birth from customers where id = ?";
        Customer cust = query(Customer.class, sql1, 18);
        System.out.println(cust);

        //查询数据库表order,order是关键字,用``。把获取到的数据封装到Order对象
        //并且数据库中列名和Order类的属性名不一致,要起给查到的结果集取别名才行
        String sql2 =  "select order_id orderId, order_name orderName, order_date orderDate from `order` where order_id = ?";
        Order order = query(Order.class, sql2, 1);
        System.out.println(order);
    }

    public <T> T query(Class<T> clazz, String sql, Object... args) throws Exception {
        //初始化查询到的结果值(调用无参构造器)
        T t = clazz.newInstance();
        Connection conn = JDBCUtils.getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        //填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
        //向数据库发送sql,获取结果集
        ResultSet rs = ps.executeQuery();
        //拿到描述当前结果集的元数据metadata
        ResultSetMetaData rsmd = rs.getMetaData();
        //给要返回的T对象封装从结果集种拿到的值
        //移动记录指针
        while (rs.next()) {
            //在当前行上遍历列,获取一个一个结果集表中的单元格值
            for (int i = 0; i < rsmd.getColumnCount(); i++) {
                //获取列别名
                String columnName = rsmd.getColumnLabel(i + 1);
                //获取此行此列的单元格里的值
                Object columnValue = rs.getObject(columnName);
                //给T对象属性赋值。此时没有Set、get方法,用反射赋值
                //必须使结果集列的别名与对象属性名保持一致!!!!
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(t, columnValue);
            }
        }
        //从下到上关闭资源
        JDBCUtils.close(conn, ps, rs);
        return t;
    }

运行结果:


当然,我们还可以queryList()获取一堆Customer对象,而不仅仅是一个

    @Test
    public void testQueryList() throws Exception {
//        Date date = new Date(System.currentTimeMillis());
//        System.out.println(date); 2022-05-11
        String sql = "select id, name, email, birth from customers where id <= ?";
        List<Customer> customers = queryList(Customer.class, sql, 20);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }
    

    public <T> List<T> queryList(Class<T> clazz, String sql, Object... args) throws Exception {
        List<T> list = new ArrayList<>();
        //1. 获取连接
        Connection conn = JDBCUtils.getConnection();
        //2. 获取 PreparedStatement 用于发送 SQL
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
        //4. 执行 SQL,获取 ResultSet
        ResultSet rs = ps.executeQuery();
        //5. 获取当前结果集的元数据  ResultSetMetaData
        ResultSetMetaData rsmd = rs.getMetaData();
        //6. 通过结果集元数据获取,结果集的列数
        int columnCount = rsmd.getColumnCount();
        //7. 获取结果集中的数据
        while (rs.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                //7.1 根据列的索引获取列别名
                String columnName = rsmd.getColumnLabel(i + 1);
                //7.2 根据列名获取列值
                Object columnValue = rs.getObject(columnName);
                //用开源组织的类,给对象设置属性值,此处需要添加commons-beanutils-1.8.0.jar包。我们也可以自己用反射写
                BeanUtils.setProperty(t, columnName, columnValue);
            }

            list.add(t);
        }

        //8. 关闭连接
        JDBCUtils.close(conn, ps, rs);
        return list;
    }

运行结果:


六、使用?PreparedStatement 完成图片处理

mysql中的数据类型:BLOB

BLOB

二进制形式的长文本数据,最大可达4G

TEXT?

长文本数据,最大可达4G

customers表的表结构如图:

使用?PreparedStatement 完成图片添加到数据库

ps.setBlob(5, new FileInputStream("./aligeduo.jpg"));

    @Test
    //添加图片
    public void test1(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "insert into customers values(?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 21);
            ps.setString(2, "Ali嘎多");
            ps.setString(3, "aligeduo@qq.com");
            ps.setString(4, "2001-9-9");
            //使用perparestatement 完成图片处理
            //void setBlob(int parameterIndex, InputStream inputStream)
            ps.setBlob(5, new FileInputStream("./aligeduo.jpg"));
            int row = ps.executeUpdate();
            System.out.println("已影响" + row + "行");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(conn, ps, null);
        }
    }

数据库中就可以看得到了,看到我们美丽的Ali嘎多了

使用?PreparedStatement 从数据库中取出图片

ResultSet rs = ps.executeQuery();
Blob blob = rs.getBlob("photo");
//获取图片输入流
InputStream in = blob.getBinaryStream();

并把图片保存到硬盘。

    //查询图片
    @Test
    public void test2() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //多查询一个列,图片
        String sql = "select id, name, email, birth, photo from customers where id = ?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, 21);
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String email = rs.getString("email");
            Date birth = rs.getDate("birth");

            Customer cust = new Customer(id, name, email, birth);
            System.out.println(cust);
            //图片处理,获取数据库图片以Blob形式,Blob??
            //将二进制大对象 (Binary Large Object) 存储为数据库表某一行中的一个列
            Blob blob = rs.getBlob("photo");
            //获取图片输入流
            InputStream in = blob.getBinaryStream();
            FileOutputStream fos = new FileOutputStream("./lalala.jpg");
            //将查到的图片保存到磁盘中
            byte[] b = new byte[1024];
            int len = 0;
            while ((len = in.read(b)) != -1) {
                fos.write(b, 0, len);
            }
            //关闭流和连接
            fos.close();
            in.close();
            JDBCUtils.close(conn, ps, rs);
        }
    }

七、开启批量处理 要注意mysql版本号

  • 5.1.7不好使
  • 5.1.37好使
  • 8.0好使

创建一个空表:emp。

?假设我们要添加10万条数据到这个表,

1、下面这种就很慢,超级无敌变态慢

等了2分钟,才进去12000多条数据,等不下去了

    @Test
    public void test1(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "insert into emp values(?,?)";
            ps = conn.prepareStatement(sql);

            for (int i = 0; i <= 100000; i++) {
                ps.setInt(1, i+1);
                ps.setString(2, "emp_" + i);
                //填充一条,就给远程数据库发送一条
                ps.executeUpdate();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(conn, ps, null);
        }
    }

2、真正的批处理

当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理

  1. addBatch(String):添加需要批量处理的SQL语句或是参数;
  2. executeBatch():执行批量处理语句;
  3. clearBatch():清空缓存的数据

在这之前,需要在jdbc.properties中开启批处理

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&rewriteBatchedStatements=true
user=root
password=123456

?6s,就能往emp中插入10万条数据,快吗?

    //批量处理
    @Test
    public void test3() throws Exception {
            Connection  conn = JDBCUtils.getConnection();
            String sql = "insert into emp values(?,?)";
            PreparedStatement ps = conn.prepareStatement(sql);
            //这里使用批处理往emp表中插入10万条数据
            for (int i = 1; i <= 100000; i++) {
                ps.setInt(1, i+1);
                ps.setString(2, "emp_" + i);
                //积攒 SQL 语句
                ps.addBatch();
                if(i % 500  == 0){
                    //批量发送sql
                    ps.executeBatch();
                    //清空sql
                    ps.clearBatch();
                }
            }
            JDBCUtils.close(conn, ps, null);
    }

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-12 16:20:27  更:2022-05-12 16:21:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:32:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码