JDBC-Java与数据库之间的桥梁

news2024/10/6 18:26:03

1、JDBC


1.1、数据的持久化

持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用, 数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成

持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中

1.2、 Java中的数据操作技术

在Java中,数据库存取数据的技术可分为如下几类:

  • JDBC直接访问数据库
  • 第三方O/RM框架,如Hibernate,Mybatis,Spring Data JPA等

JDBC是Java访问数据库的基石,Hibernate、MyBatis、Spring Data JPA等只是更好的封装了JDBC

1.3、JDBC介绍

  • 全称:(Java DataBase Connectivity)Java数据库连接

  • JDBC就是使用Java语言操作关系型数据库的一套API

JDBC本质

  • 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
  • 面向接口编程,真正执行的代码是驱动jar包中的实现类

同一套Java代码,操作不同的关系型数据库

在这里插入图片描述

JDBC的好处

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

1.4、JDBC程序编写步骤

  • 导入数据库驱动包mysql-connector-java-8.0.28.jar
  • 加载驱动
  • 获取连接
  • 定义SQL
  • 执行SQL语句
  • 处理返回结果
  • 释放资源
public static void main(String[] args) throws ClassNotFoundException, SQLException {
    // 1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    // 2.获取数据库连接对象
    String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    String username = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, username, password);

    // 3.定义sql
    String sql = "insert front_db.users values (1005,'小黑','1314',19,1)";

    // 4.获取执行sql的对象  Statement
    Statement stmt = conn.createStatement();

    // 5.执行sql
    int affectRow = stmt.executeUpdate(sql);

    // 6.处理结果
    System.out.println("affectRow = " + affectRow);

    // 7.释放资源
    if (stmt != null) {
        stmt.close();
    }
    if (conn != null) {
        conn.close();
    }
}

连接四要素

  • 加载与注册JDBC驱动
  • URL
  • 用户名
  • 密码

编写jdbc.properties配置文件

# 驱动包路径
driver=com.mysql.cj.jdbc.Driver
# 协议://主机地址:端口号/数据库名?参数1&参数2&参数3&参数n
url=jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 用户名
username=root
# 密码
password=root

获取连接

使用配置文件的方式保存配置信息,在代码中加载配置文件

public static void main(String[] args) throws Exception {
    // 获取连接
    Connection conn = getConnection();
    // 获取执行SQL对象
    Statement stmt = conn.createStatement();
    // 定义SQL
    String sql = "select id, username, password, age, status from  front_db.users";
    // 执行SQL
    ResultSet rs = stmt.executeQuery(sql);
    List<User> userList = new ArrayList<>();
    // 处理结果
    while (rs.next()) {
        int id = rs.getInt("id");
        String username = rs.getString("username");
        String password = rs.getString("password");
        int age = rs.getInt("age");
        int status = rs.getInt("status");
        userList.add(new User(id, username, password, age, status));
    }
    System.out.println(userList);
    if (rs != null) rs.close();
    if (stmt != null) stmt.close();
    if (conn != null) conn.close();
}


public static Connection getConnection() throws Exception {
    // 1.读取配置文件中4个连接数据库的基本信息
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
    Properties prop = new Properties();
    prop.load(is);
    String driver = prop.getProperty("driver");
    String url = prop.getProperty("url");
    String username = prop.getProperty("username");
    String password = prop.getProperty("password");
    System.out.println(driver + "\n" + url + "\n" + username + "\n" + password);

    // 2.加载驱动
    Class.forName(driver);

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

使用配置文件的好处:

①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,无需深入代码

②如果修改了配置信息,省去重新编译的过程

1.5、JDBC API作用

  • DriverManager(驱动管理类):注册驱动,获取数据库连接
  • Connection(数据库连接对象)
    • 获取执行 SQL 的对象
      • 普通执行SQL对象:createStatement()
      • 预编译SQL的执行SQL对象:prepareStatement(sql)
      • 执行存储过程的对象:prepareCall(sql)
    • 管理事务
      • 开启事务:setAutoCommit(boolean autoCommit);true为自动提交事务,false为手动提交事务(即开启事务)
      • 提交事务:commit()
      • 回滚事务:rollback()
  • Statement:用来执行SQL语句
    • 执行DDL、DML语句:excuteUpdate
    • 执行DQL语句:executeQuery
  • ResultSet(结果集对象):封装了SQL查询语句的结果
    • next():将光标从当前位置向后移动一行,判断当前行是否有数据
    • getXxx(参数):获取数据
  • PreparedStatement:预编译SQL语句并执行(性能更好),预防SQL注入问题(将敏感字符进行转义)

1.6、SQL注入

SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法

使用Statement操作数据库表存在弊端:

  • 问题一:存在拼串操作,较为繁琐
  • 问题二:存在SQL注入问题
public static void main(String[] args) throws Exception {
    Scanner input = new Scanner(System.in);
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    String username = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, username, password);

    System.out.print("请输入用户名:");
    String uName = input.nextLine();
    System.out.print("请输入密码:");
    String uPwd = input.nextLine();     // ' or '1' = '1

    String sql = "select * from front_db.users where username = '" + uName + "' and password = '" + uPwd + "'";
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    if (rs.next()) {
        System.out.println("登录成功");
    } else {
        System.out.println("登录失败");
    }
    if (rs != null) rs.close();
    if (stmt != null) stmt.close();
    if (conn != null) conn.close();
}

对于 Java 而言,要防止 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了

public static void main(String[] args) throws Exception {
    Scanner input = new Scanner(System.in);

    // MySQL5之后的驱动包,可以省略注册驱动的步骤
    // Class.forName("com.mysql.cj.jdbc.Driver");
    // useServerPrepStmts=true 参数开启预编译功能
    String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useServerPrepStmts=true";
    String username = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, username, password);
    // 预编译SQL
    String sql = "select * from front_db.users where username = ? and password = ?";
    PreparedStatement ps = conn.prepareStatement(sql);

    System.out.print("请输入用户名:");
    String uName = input.nextLine();
    System.out.print("请输入密码:");
    String uPwd = input.nextLine();		// ' or '1' = '1

    // 填充占位符
    ps.setString(1, uName);
    ps.setString(2, uPwd);
    ResultSet rs = ps.executeQuery();
    if (rs.next()) {
        System.out.println("登录成功");
    } else {
        System.out.println("登录失败");
    }
    if (rs != null) rs.close();
    if (ps != null) ps.close();
    if (conn != null) conn.close();
}

使用PreparedStatement实现CRUD操作

可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象,PreparedStatement接口是Statement的子接口,它表示一条预编译过的SQL语句

PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement 对象的setXxx() 方法来设置这些参数,setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从1开始),第二个是设置的 SQL语句中参数的值

Query

public static void main(String[] args) throws Exception {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    String username = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, username, password);
    String sql = "select * from front_db.users";
    PreparedStatement ps = conn.prepareStatement(sql);
    ResultSet rs = ps.executeQuery();

    List<User> userList = new ArrayList<>();
    while (rs.next()) {
        int id = rs.getInt("id");
        String uName = rs.getString("username");
        String uPwd = rs.getString("password");
        int age = rs.getInt("age");
        int status = rs.getInt("status");
        User user = new User(id, uName, uPwd, age, status);
        userList.add(user);
    }
    System.out.println(userList);
    if (rs != null) rs.close();
    if (ps != null) ps.close();
    if (conn != null) conn.close();
}

Insert、Update、Delete 只需修改对应的SQL语句,以及填充对应占位符

public static void main(String[] args) throws Exception {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    String username = "root";
    String password = "root";
    Connection conn = DriverManager.getConnection(url, username, password);
    // 预编译SQL
    String sql = "update front_db.users set username = ? ,password = ?,age = ?,status = ? where id = ?";
    PreparedStatement ps = conn.prepareStatement(sql);

    // 填充占位符
    ps.setString(1, "小萌");
    ps.setString(2, "meng@.");
    ps.setInt(3, 18);
    ps.setInt(4, 1);
    ps.setInt(5, 1001);

    int affectRow = ps.executeUpdate();

    if (affectRow == 0) System.out.println("修改失败");

    if (ps != null) ps.close();
    if (conn != null) conn.close();
}

1.7、使用IDEA连接数据库

连接成功后,选择数据库

双击数据库,查看相应的表

修改表中数据

打开编写代码控制台

连接失败,检查原因

1.8、数据库连接池

数据库连接—>执行完毕—>释放

连接、释放十分浪费系统资源

  • 数据库连接池是个容器,负责分配、管理数据库连接 (Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 超过最大空闲时间的数据库连接会自动归还到连接池中,来避免因为没有释放数据库连接而引起的数据库连接遗漏

好处

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏

池化技术:准备一些预先的资源,过来就连接预先准备好的

标准接口:DataSource

  • 官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能
  • 那么以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象

常见的数据库连接池

  • C3P0
  • DBCP
  • Druid

C3P0

C3P0是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用

需要用到的jar包:c3p0-0.9.5.5.jar

  1. 编写c3p0-config.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <c3p0-config>
        <named-config name="helloC3P0">
            <!--提供获取连接的四个基本信息-->
            <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
            <!--注:在xml中 & 符号 需要使用 &amp;特殊转义-->
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/front_db?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai</property>
            <property name="user">root</property>
            <property name="password">root</property>
    
            <!--进行数据库连接池管理的基本信息-->
            <!--当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数-->
            <property name="acquireIncrement">5</property>
            <!--c3p0数据库连接池中初始化时的连接数-->
            <property name="initialPoolSize">10</property>
            <!--c3p0数据库连接池维护的最少连接数-->
            <property name="minPoolSize">10</property>
            <!--c3p0数据连接池维护的最多连接数-->
            <property name="maxPoolSize">100</property>
            <!--c3p0数据连接池最多维护的Statement的个数-->
            <property name="maxStatements">50</property>
            <!--每个连接中可以最多使用的Statement的个数-->
            <property name="maxStatementsPerConnection">2</property>
        </named-config>
    </c3p0-config>
    
  2. 获取连接

    @Test
    public void testC3p0Connection() throws SQLException {
        ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");
        Connection conn = cpds.getConnection();
        System.out.println(conn);
    }
    

DBCP

DBCP是Apache软件基金组织下的开源连接池实现,tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持

需要用到的jar包

  • commons-dbcp-1.4.jar:连接池的实现
  • commons-pool-1.6.jar:连接池实现的依赖库
  1. 编写dbcp.properties配置文件

    # 连接四要素
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username=root
    password=root
    
    # 其他设置
    initialSize=10
    maxActive=20
    # ...
    
  2. 获取连接

    @Test
    public void testDBCPConnection() throws Exception {
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
        pros.load(is);
        DataSource ds = BasicDataSourceFactory.createDataSource(pros);
        Connection conn = ds.getConnection();
        System.out.println(conn);
    }
    

Druid(德鲁伊)

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

需要用到的jar包:druid-1.2.8.jar

  1. 编写druid.properties配置文件

    # 连接四要素
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username=root
    password=root
    
    # 其他设置
    # 初始化连接数量
    initialSize=10
    # 最大连接数
    maxActive=15
    # 最大等待时长
    maxWait=3000
    
  2. 获取连接

    @Test
    public void testDruidConnection() throws Exception {
        Properties prop = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        prop.load(is);
        DataSource ds = DruidDataSourceFactory.createDataSource(prop);
        Connection conn = ds.getConnection();
        System.out.println(conn);
    }
    

结论:无论使用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变!

1.9、DAO模式

为什么进行JDBC封装?

业务代码和数据访问代码耦合

  • 可读性差
  • 不利于后期修改和维护
  • 不利于代码复用

什么是DAO?

非常流行的数据访问模式——DAO模式

  • Data Access Object(数据访问对象)
  • 位于业务逻辑层和持久化数据层之间
  • 实现对持久化数据的访问

DAO模式的组成部分

  1. 实体类
  2. DAO接口
  3. DAO实现类
  4. 数据库连接和关闭工具类

DAO的优势

  • 隔离业务逻辑代码和数据访问代码
  • 隔离不同数据库的实现

为解决业务代码和数据访问代码的紧耦合,给修改和维护代码带来的不便,推荐使用DAO模式封装JDBC

// DAO:Data Access Object(数据访问对象)
public abstract class BaseDao<T> {
    private Class<T> clazz;

    {
        // 获取当前BaseDao的子类继承父类中的泛型
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        Type[] typeArguments = paramType.getActualTypeArguments();      // 获取父类的泛型参数
        clazz = (Class<T>) typeArguments[0];   // 泛型的第一个参数
    }


    /**
     * 获取连接
     */
    public Connection getConnection() {
        DataSource dataSource = null;
        try {
            // 1.读取配置文件中连接数据库的基本信息
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
            Properties prop = new Properties();
            prop.load(is);

            // 获取数据库连接池
            dataSource = DruidDataSourceFactory.createDataSource(prop);
            return dataSource.getConnection();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 关闭资源 查询
     */
    public void closeResource(Connection conn, PreparedStatement ps, ResultSet rs) {
        try {
            if (rs != null) rs.close();
            if (ps != null) ps.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭资源  增、删、改
     *
     * @param conn 连接对象
     * @param ps   执行SQL语句对象
     */
    public void closeResource(Connection conn, PreparedStatement ps) {
        try {
            if (ps != null) ps.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通用增、删、改  version 2.0(考虑到事务)
     *
     * @param sql
     * @param args
     * @return
     */
    public int modifyTbTx(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        try {
            // 1.预编译sql 返回prepareStatement对象
            ps = conn.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                // 从1开始
                ps.setObject(i + 1, args[i]);
            }
            // 3.执行操作
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4.关闭资源
            closeResource(null, ps);
        }
        return 0;
    }

    /**
     * 通用的查询操作,返回数据表的一条记录 version 2.0(考虑到事务)
     *
     * @param conn 连接对象
     * @param sql  sql语句
     * @param args 执行sql所需的填充参数
     * @return 单条记录
     */
    public T getDataOneWithTx(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 预编译sql
            ps = conn.prepareStatement(sql);
            // 填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            // 执行操作
            rs = ps.executeQuery();
            // 获取结果集元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            // 获取列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    // 获取每一个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);
                    // 获取每一个列的列名:通过ResultSetMetaData
                    // getColumnLabel:获取列的别名 推荐使用
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 通过反射,将对象的指定columnLabel属性名的属性赋值为指定值columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeResource(null, ps, rs);
        }
        return null;
    }

    /**
     * 通用查询方法 返回多条记录的查询操作  version 2.0(考虑到事务)
     *
     * @param sql  sql语句
     * @param args 执行sql所需的填充参数
     * @return 多条记录
     */
    public List<T> getDataListWithTx(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            // 创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object columnValue = rs.getObject(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 通过反射,将对象的指定columnLabel属性名的属性赋值为指定值columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeResource(null, ps, rs);
        }
        return null;
    }

    /**
     * 用于查询特殊值的通用方法
     *
     * @param conn 连接对象
     * @param sql  sql语句
     * @param args 执行sql所需的填充参数
     */
    public <E> E getValue(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            if (rs.next()) {
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeResource(null, ps, rs);
        }
        return null;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/555301.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IO流详解

IO流 1. 文件 1.1 什么是文件 文件对大家来说都不陌生&#xff1a; 文件是保存数据的地方&#xff0c;它可以保存文字、图片、视频等等例如大家平时使用的word文档、Excel文档、PPT文档等都是文件 1.2 文件流 文件在程序中是以流的形式来操作的流是指数据在数据源&#x…

mydumper - 备份恢复工具

文章目录 介绍部署安装功能 参数详解mydumpermyloader配置文件 实际示例操作性能验证 补充definerlock 总结相关链接 介绍 部署安装 # 当前最新 release wget https://github.com/mydumper/mydumper/releases/download/v0.14.4-8/mydumper-0.14.4-8.el7.x86_64.rpm# 安装 rpm…

Linux 之 firewalld 防火墙

目录 firewalld概述firewalld 与 iptables 的区别&#xff1a;firewalld 区域的概念firewalld防火墙预定义了9个区域:firewalld 数据处理的流程 firewalld防火墙的配置方法&#xff1a;永久保存配置 firewalld概述 firewalld防火墙是Centos7系统默认的防火墙管理工具&#xff…

数据分析与预处理常用的图和代码

1.训练集和测试集统计数据描述之间的差异作图&#xff1a; def diff_color(x):color red if x<0 else (green if x > 0 else black)return fcolor: {color}(train.describe() - test.describe())[features].T.iloc[:,1:].style\.bar(subset[mean, std], alignmid, colo…

国内做校园信息化的龙头企业公司有哪些?

随着数字化转型的加速&#xff0c;越来越多的学校开始寻求校园信息化的解决方案&#xff0c;相比于传统信息化模式&#xff0c;国内有哪些做校园信息化做得比较好的企业&#xff1f;他们采用的又是什么样的方式&#xff1f; 一文带你了解&#xff0c;零代码平台搭建校园信息化…

Android 14 又来了?别扶!抬起我来吧!

Android 14 又来了&#xff1f;别扶&#xff01;抬起我来吧&#xff01; 大家好&#xff0c;好久不见&#xff0c;从去年底写完年终总结之后就再也没有更新过文章&#xff0c;之前最多也就间隔一两个月时间&#xff0c;但这回间隔时间确实有点长&#xff0c;基本快半年了&…

每日一练 | 网络工程师软考真题 Day7

1、如果希望别的计算机不能通过ping命令测试效劳器的连通情况&#xff0c;可以 &#xff08;1&#xff09; 。如果希望通过默认的Telnet端口连接效劳器&#xff0c;那么下面对防火墙配置正确的选项是 &#xff08;2&#xff09; 。 &#xff08;1&#xff09; A&#xff0…

为什么说2023年是学华为认证的最佳时机?学华为认证有什么优势?

华为认证数通方向2016年以前叫数通&#xff0c;2017年开始改为R&S&#xff08;路由交换&#xff09;&#xff0c;2021年正式升级为Datacom。 新版数通认证更聚焦于技术在现实工作场景中的应用&#xff0c;并融入了大量新技术新应用&#xff0c;实用价值更高。 通过系统学…

用java带你了解IO模型

目录 1.BIO1.1 简述1.2 代码示例1.3优点和缺点1.4 思考 2. NoBlockingIO2.1 简述2.2 代码示例2.3 优点和缺点2.4 思考 3. NIO&#xff08;NewIO&#xff09;3.1 简述3.2 代码示例3.3 优点和缺点3.3 思考 4. 扩展select/poll、epoll4.1 简述4.2 select/poll4.3 epoll4.4 扩展话题…

设计模式之【备忘录模式】,“后悔药”是可以有的

文章目录 一、什么是备忘录模式1、备忘录模式使用场景2、备忘录模式优缺点3、备忘录模式的三大角色4、白箱备忘录和黑箱备忘录5、思考&#xff1a;备份频率快&#xff0c;备份对象大的备忘录应该如何设计 二、实例1、备忘录模式的一般写法2、使用栈管理富文本编辑器3、游戏状态…

【Linux】shell编程—sed编辑器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、sed编辑器1.sed工作流程2.sed工作场景3.sed常用选项 二、sed编辑器基本用法1.打印操作2.打印行号3.增加操作4.插入操作5.替换操作7.字符转换 一、sed编辑器 sed…

RabbitMQ(2)

一、MQ的问题 基于上篇存在的问题 1. 问题说明 MQ在分布式项目中是非常重要的&#xff0c; 它可以实现异步、削峰、解耦&#xff0c;但是在项目中引入MQ也会带来一系列的问题。 今天我们要解决以下几个常见的问题&#xff1a; 消息可靠性问题&#xff1a;如何确保消息被成功送…

编译flink1.6源码并打包成CDH6.3.2的parcel

说明&#xff1a;scala &#xff1a;2.12&#xff0c;maven:3.6.1, java:1.8&#xff0c;macOS 1、指定scala,maven的环境变量 sudo vi ~/.bash_profile export PATH$PATH:$M2_HOME/bin:/Users/admin/Documents/softwares/scala-2.12.17/bin2、克隆flink代码 git clone https…

smb配置,详细图文及配置

samba :网络文件共享服务 ​ Samba是一个能让Linux系统应用Microsoft网络通讯协议的软件&#xff0c;而SMB是Server Message Block的缩写&#xff0c;即为服务器消息块&#xff0c;SMB主要是作为Microsoft的网络通讯协议&#xff0c;后来Samba将SMB通信协议应用到了Linux系统上…

【Android入门到项目实战-- 11.4】—— ExoPlayer视频播放器框架的详细使用

目录 什么是ExoPlayer 一、基本使用 1、添加依赖项 2、布局 3、Activity 二、自定义播放暂停 1、首先如何隐藏默认的开始暂停和快进&#xff1f; 2、自定义 三、控制视频画面旋转和比例调整 四、全屏放大和缩小 1、双击视频放大缩小 2、按钮放大缩小 五、完整的实现…

JS 面试整理

1.JS运行机制&#xff08;EventLoop事件循环&#xff09; Js是单线程&#xff0c;每次只能执行一项任务&#xff0c;其他任务按照顺序排队等待&#xff0c;使用eventloop来解决线程阻塞的问题。在执行栈过程中&#xff0c;有同步代码和异步代码时&#xff0c;首先会执行完所有…

Linux学习笔记 --- 初识Linux

PC操作系统&#xff0c;也就是个人电脑所使用的操作系统 一. 操作系统概述 导学&#xff1a;了解操作系统的作用&#xff0c;了解常见的操作系统有哪些 1.1 硬件和软件 说白了&#xff0c;硬件就是你看得见&#xff0c;摸得到的。 1.2 操作系统 1.3 常见操作系统 PC操作系统…

分布式消息队列-RabbitMQ从入门到精通

文章目录 分布式消息队列认知提升分布式消息队列&#xff08;MQ&#xff09;应用场景分布式消息队列&#xff08;MQ&#xff09;应用思考点MQ本身的一些思考点业界主流的分布式消息队列&#xff08;MQ&#xff09;MQ的技术选型关注点初识 JMS 与其专业术语 RabbitMQ四种集群架构…

程序员:面试造火箭,入职拧螺丝?太难了···

刚开始工作的时候&#xff0c;我也想不通这个问题&#xff0c;甚至很鄙视这种现象。后面当了面试官&#xff0c;做到了公司中层管理&#xff0c;也会站在公司以及行业角度去重新思考这个问题。 为什么这种现象会越来越普遍呢&#xff1f;尤其在 IT 行业愈加明显。 面试看的是…

抖音SEO源码开发:技术分享与实践

抖音SEO源码开发一直是数字营销领域的重要技术之一。它可以帮助企业在搜索引擎结果页面上获得更好的排名&#xff0c;从而吸引更多的用户。为了帮助更多的企业提升在抖音平台上的曝光率&#xff0c;开发者们分享了自己的技术并进行了实践。 在抖音SEO源码开发的过程中&#xf…