• 后端关卡18 了解JDBC


    一、思维导图

    在这里插入图片描述

    二、重点知识点

    1、PreparedStatement 与 Statement 的异同?

    • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

    • PreparedStatement 能最大可能提高性能:

      • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
      • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
      • (语法检查,语义检查,翻译成二进制命令,缓存)
    • PreparedStatement 可以防止 SQL 注入 (通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。)

    2、Druid(德鲁伊)数据库连接池

    Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

    package com.atguigu.druid;
    
    import java.sql.Connection;
    import java.util.Properties;
    
    import javax.sql.DataSource;
    
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    
    public class TestDruid {
    	public static void main(String[] args) throws Exception {
    		Properties pro = new Properties();		 pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
    		DataSource ds = DruidDataSourceFactory.createDataSource(pro);
    		Connection conn = ds.getConnection();
    		System.out.println(conn);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中,src下的配置文件为:【druid.properties】

    url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
    username=root
    password=123456
    driverClassName=com.mysql.cj.jdbc.Driver
    
    initialSize=10
    maxActive=20
    maxWait=1000
    filters=wall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、数据库连接方式(最终版)

    public void testConnection5() throws Exception {
        	//1.加载配置文件
            InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(is);
            
            //2.读取配置信息
            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");
    
            //3.加载驱动
            Class.forName(driverClass);
    
            //4.获取连接
            Connection conn = DriverManager.getConnection(url,user,password);
            System.out.println(conn);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    配置文件【jdbc.properties】

    user=root
    password=123456
    url=jdbc:mysql://localhost:3306/test
    driverClass=com.mysql.cj.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4

    使用配置文件的好处:
    ①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
    ②如果修改了配置信息,省去重新编译的过程。

    4、考虑上事务的通用增删改操作

    public void update(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行
                ps.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    测试事务的增删改操作

    public void testJDBCTransaction() {
            Connection conn = null;
            try {
                // 1.获取数据库连接
                conn = JDBCUtil.getConnection();
                // 2.开启事务,取消事务数据自动提交
                conn.setAutoCommit(false);
                // 3.进行数据库操作
                String sql1 = "update user_table set balance = balance - 100 where user = ?";
                update(conn, sql1, "AA");
    
                // 模拟网络异常
                //System.out.println(10 / 0);
    
                String sql2 = "update user_table set balance = balance + 100 where user = ?";
                update(conn, sql2, "BB");
                // 4.若没有异常,则提交事务
                conn.commit();
            } catch (Exception e) {
                e.printStackTrace();
                // 5.若有异常,则回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            } finally {
                try {
                    //6.恢复每次DML操作的自动提交功能,主要针对数据库连接池的使用
                    conn.setAutoCommit(true);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                //7.关闭连接
                JDBCUtil.closeResource(conn, null, null);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    5、考虑上事务的针对于不同表的通用的查询操作,返回一条记录

    public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行,获取结果集
                rs = ps.executeQuery();
                //获取结果集的元数据:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //通过ResultSetMetaData获取结果集中的列数
                int columnCount = rsmd.getColumnCount();
    
                //多个数据用while(rs.next())
                if (rs.next()) {
                    T t = clazz.getDeclaredConstructor().newInstance();
                    //处理结果集一行数据中的每一列
                    for (int i = 0; i < columnCount; i++) {
                        //获取每一个列的列值:通过ResultSet
                        Object columnValue = rs.getObject(i + 1);
                        /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                        String columnName = rsmd.getColumnName(i + 1);*/
                        //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                        String columnLabel = rsmd.getColumnLabel(i + 1);
                        //给cust对象指定的columnName属性赋值为columnValue:通过反射
                        Field field = clazz.getDeclaredField(columnLabel);
                        //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                        field.setAccessible(true);
                        field.set(t, columnValue);
                    }
                    return t;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps, rs);
            }
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    6、考虑上事务针对于不同表的通用的查询操作,返回多条数据集合

    public <T> List<T> getForList(Connection conn, Class<T> clazz, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行,获取结果集
                rs = ps.executeQuery();
                //获取结果集的元数据:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //通过ResultSetMetaData获取结果集中的列数
                int columnCount = rsmd.getColumnCount();
    
                //创建集合对象
                ArrayList<T> list = new ArrayList<>();
                //多个数据用while(rs.next())
                while (rs.next()) {
                    T t = clazz.getDeclaredConstructor().newInstance();
                    //处理结果集一行数据中的每一列:给t对象指定的属性赋值
                    for (int i = 0; i < columnCount; i++) {
                        //获取每一个列的列值:通过ResultSet
                        Object columnValue = rs.getObject(i + 1);
                        /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                        String columnName = rsmd.getColumnName(i + 1);*/
                        //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                        String columnLabel = rsmd.getColumnLabel(i + 1);
                        //给cust对象指定的columnName属性赋值为columnValue:通过反射
                        Field field = clazz.getDeclaredField(columnLabel);
                        //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                        field.setAccessible(true);
                        field.set(t, columnValue);
                    }
                    list.add(t);
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps, rs);
            }
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    7、使用PreparedStatement实现批量数据的操作(批量插入)

    //update、delete本身就具有批量操作的的效果
        //1、使用 addBatch() / executeBatch() / clearBatch()
        //2、mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true写在配置文件的url后面
        //3、使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
        public static void testInsert(){
            Connection conn = null;
            PreparedStatement ps = null;
            try {
                conn = JDBCUtil.getConnection();
                //设置不允许自动提交数据
                conn.setAutoCommit(false);
                String sql = "insert into goods(name) values(?);";
                ps = conn.prepareStatement(sql);
                for(int i = 1;i <= 20000;i++){
                    ps.setString(1, "name_" + i);
                    //1.“攒”sql
                    ps.addBatch();
                    if (i % 500 == 0){
                        //2.执行batch
                        ps.executeBatch();
                        //3.清空batch
                        ps.clearBatch();
                    }
    
                }
                //提交数据
                conn.commit();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                JDBCUtil.closeResource(conn,ps);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    8、BaseDAO.java 封装了针对数据表的通用操作,写接口以及继承BaseDAO和实现接口的实现类

    public abstract class BaseDAO<T> {
        private Class<T> clazz = null;
    
        //    public BaseDAO(){}
        {
            //获取当前BaseDAO的子类继承的父类中泛型
            Type genericSuperclass = this.getClass().getGenericSuperclass();
            ParameterizedType paramType = (ParameterizedType) genericSuperclass;
            Type[] typeArguments = paramType.getActualTypeArguments();  //获取了父类的泛型参数
            clazz = (Class<T>) typeArguments[0];    //泛型的第一个参数
        }
    
        //考虑上事务的通用增删改操作
        public void update(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行
                ps.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps);
            }
        }
    
        //考虑上事务的针对于不同表的通用的查询操作,返回一条记录
        public T getInstance(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行,获取结果集
                rs = ps.executeQuery();
                //获取结果集的元数据:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //通过ResultSetMetaData获取结果集中的列数
                int columnCount = rsmd.getColumnCount();
    
                //多个数据用while(rs.next())
                if (rs.next()) {
                    T t = clazz.getDeclaredConstructor().newInstance();
                    //处理结果集一行数据中的每一列
                    for (int i = 0; i < columnCount; i++) {
                        //获取每一个列的列值:通过ResultSet
                        Object columnValue = rs.getObject(i + 1);
                        /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                        String columnName = rsmd.getColumnName(i + 1);*/
                        //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                        String columnLabel = rsmd.getColumnLabel(i + 1);
                        //给cust对象指定的columnName属性赋值为columnValue:通过反射
                        Field field = clazz.getDeclaredField(columnLabel);
                        //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                        field.setAccessible(true);
                        field.set(t, columnValue);
                    }
                    return t;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps, rs);
            }
            return null;
        }
    
        //考虑上事务的针对于不同表的通用的查询操作,返回多条数据构成的集合
        //list.forEach(System.out::println);
        public List<T> getForList(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行,获取结果集
                rs = ps.executeQuery();
                //获取结果集的元数据:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //通过ResultSetMetaData获取结果集中的列数
                int columnCount = rsmd.getColumnCount();
    
                //创建集合对象
                ArrayList<T> list = new ArrayList<>();
                //多个数据用while(rs.next())
                while (rs.next()) {
                    T t = clazz.getDeclaredConstructor().newInstance();
                    //处理结果集一行数据中的每一列:给t对象指定的属性赋值
                    for (int i = 0; i < columnCount; i++) {
                        //获取每一个列的列值:通过ResultSet
                        Object columnValue = rs.getObject(i + 1);
                        /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                        String columnName = rsmd.getColumnName(i + 1);*/
                        //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                        String columnLabel = rsmd.getColumnLabel(i + 1);
                        //给cust对象指定的columnName属性赋值为columnValue:通过反射
                        Field field = clazz.getDeclaredField(columnLabel);
                        //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                        field.setAccessible(true);
                        field.set(t, columnValue);
                    }
                    list.add(t);
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                JDBCUtil.closeResource(null, ps, rs);
            }
            return null;
        }
    
        //用于查询特殊值的通用方法
        public <E> E getValue(Connection conn, String sql, Object... args) {
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                //1.预编译sql语句,返回PreparedStatement实例
                ps = conn.prepareStatement(sql);
                //2.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);   //小心参数声明错误!
                }
                //3.执行,获取结果集
                rs = ps.executeQuery();
                if (rs.next()) {
                    return (E) rs.getObject(1);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCUtil.closeResource(null, ps, rs);
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152

    接口 【CustomerDAO】

    //此接口用于规范对于customers表的常用操作
    public interface CustomerDAO {
        //将cust对象添加到数据库中
        void insert(Connection conn, Customer cust);
    
        //根据指定的id,删除数据表中的一条记录
        void deleteById(Connection conn, int id);
    
        //根据内存中的cust对象,修改数据表中指定记录
        void update(Connection conn, Customer cust);
    
        //根据指定的id,查询得到对应的Customer对象
        Customer getCustomerById(Connection conn, int id);
    
        //查询表中所有记录构成的集合
        List<Customer> getAll(Connection conn);
    
        //返回数据表中的数据的条目数
        Long getCount(Connection conn);
    
        //返回数据表中的最大的生日
        Date getMaxBirth(Connection conn);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    实现类 【CustomerDAOImpl】

    public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {
    
        @Override
        public void insert(Connection conn, Customer cust) {
            String sql = "insert into customers(name,email,birth) values(?,?,?);";
            update(conn, sql, cust.getName(), cust.getEmail(), cust.getBirth());
        }
    
        @Override
        public void deleteById(Connection conn, int id) {
            String sql = "delete from customers where id = ?;";
            update(conn, sql, id);
        }
    
        @Override
        public void update(Connection conn, Customer cust) {
            String sql = "update customers set id = ?, email = ?, birth = ? where id = ?;";
            update(conn, sql, cust.getName(), cust.getEmail(), cust.getBirth(), cust.getId());
        }
    
        @Override
        public Customer getCustomerById(Connection conn, int id) {
            String sql = "select id,email,birth from customer where id = ?;";
            Customer customer = getInstance(conn, sql, id);
            return customer;
        }
    
        @Override
        public List<Customer> getAll(Connection conn) {
            String sql = "select id,email,birth from customer;";
            List<Customer> list = getForList(conn, sql);
            return list;
        }
    
        @Override
        public Long getCount(Connection conn) {
            String sql = "select count(*) from customer;";
            return getValue(conn, sql);
        }
    
        @Override
        public Date getMaxBirth(Connection conn) {
            String sql = "select max(birth) from customer;";
            return getValue(conn, sql);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
  • 相关阅读:
    【机器学习11】强化学习
    线性反向传播(机器学习)
    【Linux】NUC977移植使用MQTT(基于mosquitto)
    springboot整合极验实现登陆
    写书写到一半,强迫症发作跑去给HotChocolate修bug
    corrplot包画相关性图详解
    2022-06-30 工作记录--JS-ios的Date对象不支持 yyyy-MM-dd 的格式
    用Notepad++写java代码
    使用百度EasyDL实现智能垃圾箱
    HBase查询一张表的数据条数的方法
  • 原文地址:https://blog.csdn.net/wdywxwxld/article/details/126475666