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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...