MyBatis詳解(一)

来源:https://www.cnblogs.com/chafry/archive/2022/11/27/16876309.html
-Advertisement-
Play Games

MyBatis簡單介紹 【1】MyBatis是一個持久層的ORM框架【Object Relational Mapping,對象關係映射】,使用簡單,學習成本較低。可以執行自己手寫的SQL語句,比較靈活。但是MyBatis的自動化程度不高,移植性也不高,有時從一個資料庫遷移到另外一個資料庫的時候需要自 ...


MyBatis簡單介紹

【1】MyBatis是一個持久層的ORM框架【Object Relational Mapping,對象關係映射】,使用簡單,學習成本較低。可以執行自己手寫的SQL語句,比較靈活。但是MyBatis的自動化程度不高,移植性也不高,有時從一個資料庫遷移到另外一個資料庫的時候需要自己修改配置,所以稱只為半自動ORM框架。

 

傳統JDBC介紹

【1】簡單使用

@Test
public  void test() throws SQLException {
    Connection conn=null;
    PreparedStatement pstmt=null;
    try {
        // 1.載入驅動,其實這一步可以不加因為DriverManager裡面會有自動載入驅動的一步
        Class.forName("com.mysql.jdbc.Driver");

        // 2.創建連接
        conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_example", "root", "123456");

        //開啟事務
        conn.setAutoCommit(false);

        // SQL語句
        String sql="select id,user_name,create_time from t_user where id=? ";

        // 獲得sql執行者
        pstmt=conn.prepareStatement(sql);
        pstmt.setInt(1,1);

        // 執行查詢
        //ResultSet rs= pstmt.executeQuery();
        pstmt.execute();
        ResultSet rs= pstmt.getResultSet();

        rs.next();
        User user =new User();
        user.setId(rs.getLong("id"));
        user.setUserName(rs.getString("user_name"));
        user.setCreateTime(rs.getDate("create_time"));
        System.out.println(user.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
    finally{
        // 關閉資源
        try {
            if(conn!=null){
                conn.close();
            }
            if(pstmt!=null){
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

 

【1.1】DriverManager如何自動載入驅動【利用靜態代碼塊調用初始化方法,至於為什麼要使用SPI機制,主要是為了實現解耦和可插拔,因為驅動有多種】

  註:驅動展示【位於mysql-connector-java-5.1.22.jar/META-INF/services/java.sql.Driver  內容為:com.mysql.jdbc.Driver】

static {
    // 啟動類載入器載入DriverManager類,觸發靜態方法執行
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }

    // 載入java.sql.Driver驅動的實現
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // 1、創建一個 ServiceLoader對象,【這裡就將上下文類載入器設置到ServiceLoader對象的變數上了】
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // 2、創建一個迭代器對象
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            try{
                // 3、這裡調用driversIterator.hasNext()的時候,觸發將 META-INF/services 下的配置文件中的數據讀取進來,方便下麵的next方法使用
                while(driversIterator.hasNext()) {
                    // 4、【關鍵】:觸發上面創建的迭代器對象的方法調用。這裡才是具體載入的實現邏輯,非常不好找
                    driversIterator.next();
                }
            } catch(Throwable t) {}
            return null;
        }
    });

    //判斷有沒有載入到
    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {...}
    }
}

//ServiceLoader類#hasNext方法
public boolean hasNext() {
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            // 1、拼湊要讀取的文件的全名
            // final String PREFIX = "META-INF/services/";
            String fullName = PREFIX + service.getName();

            // 2、根據 fullName 去到META-INF/services/目錄下尋找配置文件
            // 如果類載入器為空,則使用系統類載入器,如果不為空則使用指定的類載入器
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }

        // 3、使用parse方法解析配置文件中的每一行數據
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

 

【2】當然正常情況一般是封裝成類來使用的,如

//數據訪問基類
public class BaseDao {
    // 驅動
    private static String DRIVER = null;
    // 鏈接字元串
    private static String URL = null;
    // 用戶名
    private static String USERNAME = null;
    // 密碼
    private static String PASSWORD = null;

    //初始化
    static {  init();  }

    // 初始化
    private static void init() {
        try {
            // 使用Properties對象讀取資源文件屬性
            Properties pro = new Properties();
            // 獲得資源文件輸入流
            InputStream inStream = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
            // 載入輸入流
            pro.load(inStream);
            DRIVER = pro.getProperty("mysql.driverClass");
            URL = pro.getProperty("mysql.jdbcUrl");
            USERNAME = pro.getProperty("mysql.user");
            PASSWORD = pro.getProperty("mysql.password");

            Class.forName(DRIVER);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //獲取資料庫連接對象
    protected Connection getConnection() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 關閉所有鏈接
     * @param conn
     * @param stmt
     * @param rs
     */
    protected void CloseAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (conn != null) {
                conn.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 執行 增、刪、 改的公共方法
     * @param sql     SQL語句
     * @param prams   參數
     * @return           受影響的行數
     */
    protected int executeUpdate(String sql, Object... prams) {
        // 獲得資料庫鏈接對象
        Connection conn = getConnection();
        // 聲明SQL執行者
        PreparedStatement pstmt = null;
        try {
            // 獲得SQL執行者
            pstmt = conn.prepareStatement(sql);

            // 迴圈載入參數
            for (int i = 0; i < prams.length; i++) {
                pstmt.setObject(i + 1, prams[i]);
            }
            // 執行executeUpdate 返回受影響行數
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 關閉所有需要關閉的對象
            CloseAll(conn, pstmt, null);
        }

        return 0;
    }

    /**
     * 執行查詢 返回單個值
     * @param sql         SQL語句
     * @param prams     參數
     * @return             OBJECT
     */
    protected Object executeScaler(String sql, Object... prams) {
        // 獲得資料庫鏈接對象
        Connection conn = getConnection();
        // 聲明SQL執行者
        PreparedStatement pstmt = null;
        // 聲明查詢結果集
        ResultSet rs = null;
        // 接收單個值
        Object value = null;
        try {
            // 獲得SQL執行者
            pstmt = conn.prepareStatement(sql);

            // 迴圈載入參數
            for (int i = 0; i < prams.length; i++) {
                pstmt.setObject(i + 1, prams[i]);
            }
            // 執行executeUpdate 返回受影響行數
            rs = pstmt.executeQuery();

            if (rs.next()) {
                value = rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            CloseAll(conn, pstmt, rs);
        }
        return value;
    }

    /**
     * 執行查詢返回list
     * 
     * @param sql         SQL語句
     * @param clazz     類的類型
     * @return List
     */
    public <T> List<T> executeList(String sql, Class<T> clazz, Object... prams) {
        // 數據集合
        List<T> list = new ArrayList<T>();
        // 獲得資料庫連接
        Connection conn = getConnection();
        // 聲明SQL執行者
        PreparedStatement pstmt = null;
        // 聲明查詢結果集
        ResultSet rs = null;
        try {

            // 3. 通過鏈接創建一個SQL執行者
            pstmt = conn.prepareStatement(sql);

            // 迴圈載入參數
            for (int i = 0; i < prams.length; i++) {
                //內部會通過instance of判斷這個參數到底是哪個類型的具體對象
                pstmt.setObject(i + 1, prams[i]);
            }

            // 4 執行查詢SQL 返回查詢結果
            rs = pstmt.executeQuery();

            // 獲得結果集的列信息對象
            ResultSetMetaData rsmd = rs.getMetaData();

            while (rs.next()) {
                // 通過類反射實例化
                T obj = clazz.newInstance();

                // 迴圈所有的列
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    /* 通過屬性名稱使用反射給泛型實例賦值 Begin */

                    // 獲得每一列的列名
                    String cloName = rsmd.getColumnName(i);
                    // 根據列名反射到類的欄位
                    Field filed = clazz.getDeclaredField(cloName);
                    // 設置私有屬性的訪問許可權
                    filed.setAccessible(true);

                    // 給泛型實例的某一個屬性賦值
                    filed.set(obj, rs.getObject(cloName));
                    /* 通過屬性名稱使用反射給泛型實例賦值 End */
                }
                // 將泛型實例添加到 泛型集合中
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    /**
     * 執行查詢返回JavaBean
     * 
     * @param sql         SQL語句
     * @param clazz     類的類型
     * @return JavaBean
     */
    public <T> T executeJavaBean(String sql, Class<T> clazz, Object... prams) {
        // 聲明數據對象
        T obj=null;
        // 獲得資料庫連接
        Connection conn = getConnection();
        // 聲明SQL執行者
        PreparedStatement pstmt = null;
        // 聲明查詢結果集
        ResultSet rs = null;
        try {

            // 3. 通過鏈接創建一個SQL執行者
            pstmt = conn.prepareStatement(sql);

            // 迴圈載入參數
            for (int i = 0; i < prams.length; i++) {
                pstmt.setObject(i + 1, prams[i]);
            }

            // 4 執行查詢SQL 返回查詢結果
            rs = pstmt.executeQuery();

            // 獲得結果集的列信息對象
            ResultSetMetaData rsmd = rs.getMetaData();

            if (rs.next()) {
                // 通過類反射實例化
                obj = clazz.newInstance();

                // 迴圈所有的列
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    /* 通過屬性名稱使用反射給泛型實例賦值 Begin */

                    // 獲得每一列的列名
                    String cloName = rsmd.getColumnName(i);
                    // 根據列名反射到類的欄位
                    Field filed = clazz.getDeclaredField(cloName);
                    // 設置私有屬性的訪問許可權
                    filed.setAccessible(true);

                    // 給泛型實例的某一個屬性賦值
                    filed.set(obj, rs.getObject(cloName));
                    /* 通過屬性名稱使用反射給泛型實例賦值 End */
                } 
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }
}

 

【3】總結JDBC的四大核心對象

1DriverManager(驅動管理對象):獲取資料庫連接;
2Connection(資料庫連接對象):獲取執行sql對象;管理事務;
3Statement(執行sql對象):executeUpdate執行DML語句(增刪改)DDL語句;executeQuery 執行DQL語句;
4ResultSet(結果集對象)

 

【4】傳統JDBC的問題

1)資料庫連接創建,釋放頻繁造成西戎資源的浪費,從而影響系統性能,使用資料庫連接池可以解決問題。
2)sql語句在代碼中硬編碼,造成代碼的不已維護,實際應用中sql的變化可能較大,sql代碼和java代碼沒有分離開來維護不方便。
3)使用preparedStatement向有占位符傳遞參數存在硬編碼問題因為sql中的where子句的條件不確定,同樣是修改不方便。
4)對結果集中解析存在硬編碼問題,sql的變化導致解析代碼的變化,系統維護不方便。

【5】針對問題的優化

1、資料庫連接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用資料庫連接池可解決此問題。
優化部分,如mybatis:在SqlMapConfig.xml中配置數據連接池,使用連接池管理資料庫鏈接。

2、Sql語句寫在代碼中造成代碼不易維護,實際應用sql變化的可能較大,sql變動需要改變java代碼。
優化部分,如mybatis:將Sql語句配置在XXXXmapper.xml文件中與java代碼分離。

3、向sql語句傳參數麻煩,因為sql語句的where條件不一定,可能多也可能少,占位符需要和參數一一對應。
優化部分,如mybatis:自動將java對象映射至sql語句,通過statement中的parameterType定義輸入參數的類型。

4、對結果集解析麻煩,sql變化導致解析代碼變化,且解析前需要遍歷,如果能將資料庫記錄封裝成pojo對象解析比較方便。
優化部分,如mybatis:自動將sql執行結果映射至java對象,通過statement中的resultType定義輸出結果的類型。

 

Mybaits整體體系圖

【1】圖示

          

【2】分析

把Mybatis的功能架構分為三層:
1基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置載入和緩存處理,這些都是共用的東西,將他們抽取出來作為最基礎的組件。為上層的數據處理層提供最基礎的支撐。
對應示例中的部分為:  SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

2API介面層:提供給外部使用的介面API,開發人員通過這些本地API來操縱資料庫。介面層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
對應示例中的部分為:  SqlSession session = sqlMapper.openSession();

3數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次資料庫操作。
對應示例中的部分為:  
User user = (User)session.selectOne("com.mapper.UserMapper.selectById", 1);
或者
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);

 

【3】簡單示例

public static void main(String[] args) {
    String resource = "mybatis-config.xml";
    Reader reader;
    try {
        //將XML配置文件構建為Configuration配置類
        reader = Resources.getResourceAsReader(resource);
        // 通過載入配置文件流構建一個SqlSessionFactory  DefaultSqlSessionFactory
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        // 數據源 執行器  DefaultSqlSession
        SqlSession session = sqlMapper.openSession();
        try {
            // 執行查詢 底層執行jdbc
            //User user = (User)session.selectOne("com.mapper.UserMapper.selectById", 1);

            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.selectById(1L);
            
            session.commit();
            System.out.println(user.getUserName());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            session.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

Mybaits插件的分析

【1】插件主要作用於四⼤組件對象

1)執⾏器 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed 等⽅法)
2)參數處理器 ParameterHandler (getParameterObject, setParameters 等⽅法)
3)結果集處理器 ResultSetHandler (handleResultSets, handleOutputParameters 等⽅法)
4)SQL語法構建處理器 StatementHandler (prepare, parameterize, batch, update, query 等⽅法)    

 

源碼分析部分

MyBatis解析全局配置文件的源碼分析

【1】分析怎麼通過載入配置文件流構建一個SqlSessionFactory

public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
 }

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

// 到這裡配置文件已經解析成了Configuration
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

 

【1.1】分析XMLConfigBuilder類怎麼將xml的資源文件流解析成Configuration

【1.1.1】新建XMLConfigBuilder過程【所以說XMLConfigBuilder並不負責解析,解析的是它裡面的XPathParser類,】

/**
* 創建一個用於解析xml配置的構建器對象
* @param inputStream 傳入進來的xml的配置
* @param environment 我們的環境變數
* @param props:用於保存我們從xml中解析出來的屬性
*/
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    /**
     * 該方法做了二個事情
     * 第一件事情:創建XPathParser 解析器對象,在這裡會把我們的
     * 把我們的mybatis-config.xml解析出一個Document對象
     * 第二節事情:調用重寫的構造函數來構建我XMLConfigBuilder
     */
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //調用父類的BaseBuilder的構造方法:給configuration賦值,typeAliasRegistry別名註冊器賦值,TypeHandlerRegistry賦值
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");

    //把props綁定到configuration的props屬性上
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

//又因為class XMLConfigBuilder extends BaseBuilder,所以看一下他的父類
public abstract class BaseBuilder {
    // mybatis的全局配置文件
    protected final Configuration configuration;
    // 用於保存我們的Entity的別名
    protected final TypeAliasRegistry typeAliasRegistry;
    // 用戶保存我們java類型和jdbc資料庫類型的
    protected final TypeHandlerRegistry typeHandlerRegistry;

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }
    ....
}

 

【1.1.2】parser.parse()解析過程【本質上就是從根結點開始解析

public Configuration parse() {
    //若已經解析過了 就拋出異常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //設置解析標誌位,保證只解析一次
    parsed = true;
    /**
     * 解析我們的mybatis-config.xml的節點
     * <configuration> </configuration>
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

//方法實現說明:解析我們mybatis-config.xml的 configuration節點
private void parseConfiguration(XNode root) {
    try {
      // 解析 properties節點
      // 如:<properties resource="mybatis/db.properties" />
      propertiesElement(root.evalNode("properties"));
      
      // 解析我們的mybatis-config.xml中的settings節點
      // 如:<setting name="mapUnderscoreToCamelCase" value="false"/>
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      
      // 基本沒有用過該屬性
      // VFS含義是虛擬文件系統;主要是通過程式能夠方便讀取本地文件系統、FTP文件系統等系統中的文件資源。
      // Mybatis中提供了VFS這個配置,主要是通過該配置可以載入自定義的虛擬文件系統應用程式
      loadCustomVfs(settings);
      
      // 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。
      // SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
      // 解析到org.apache.ibatis.session.Configuration#logImpl
      loadCustomLogImpl(settings);

      // 解析別名typeAliases節點
      typeAliasesElement(root.evalNode("typeAliases"));

      // 解析插件節點(比如分頁插件),解析到 interceptorChain.interceptors
      pluginElement(root.evalNode(	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1,首先創建一個普通的Java項目 2,將項目變為web項目 在項目上單機右鍵-->選擇 Add FrameWorks Support 選擇第一項 web application-->OK 此時項目已經變為web項目,已經可以添加tomcat運行,但是一般web項目都會使用maven管理,所以可以添 ...
  • 代碼1 class Base { public: Base(int data=10):ma(data){ cout<<"Base()"<<endl; } void show(){cout<<"Base Show()"<<endl;} void show(int){cout<<"Base Show(i ...
  • 7. Spring整合Drools 全套代碼及資料全部完整提供,點此處下載 7.1 Spring簡單整合Drools 在項目中使用Drools時往往會跟Spring整合來使用。具體整合步驟如下: 第一步:創建maven工程drools_spring並配置pom.xml <?xml version=" ...
  • Listener監聽器 1.Listener監聽器介紹 Listener監聽器是JavaWeb三大組件之一。JavaWeb三大組件分別是:Servlet程式,Listener監聽器,Filter過濾器。 Listener是JavaEE的規範,即介面。 監聽器的作用是,監聽某種變化(一般就是對象的創建 ...
  • 初始化 Date date = new Date(); 輸出時間字元串 System.out.println(date.toString()); 字母 描述 示例G 紀元標記 ADy 四位年份 2001M 月份 July or 07d 一個月的日期 10h A.M./P.M. (1~12)格式小時 ...
  • 如果只是想簡單地對整個程式做計算統計,通常使用UNIX下的time命令就足夠了。由於我用的是Mac系統,和Linux系統的輸出可能有不同,不過關鍵都是這三個時間:user: 運行用戶態代碼所花費的時間,也即CPU實際用於執行該進程的時間,其他進程和進程阻塞的時間不計入此數字;system: 在內核中... ...
  • 本文author:@愷龍 報錯情況 在一次使用JSP的jstl時候按照正常引入jstl和使用for each標簽出現瞭如下報錯: 分析原因 經過一番調查研究發現原因如下: JavaEE被Oracle捐獻給Apache了。目前最高版本是 JavaEE8; Apache把JavaEE換名了,以後不叫Ja ...
  • 一、概念辨析:網路延遲與網路波動 (1) 網路延遲 網路延遲是指各式各樣的數據在網路介質中通過網路協議(如TCP/IP)進行傳輸,如果信息量過大不加以限制,超額的網路流量就會導致設備反應緩慢,造成網路延遲。受限於光速,網路延遲總是有一個下限,是無法超越物理極限的。 (2) 網路波動(丟包率) 網路波 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...