主頁 > 後端開發 > 小練手:手寫Mybatis實作動態增刪改查

小練手:手寫Mybatis實作動態增刪改查

2021-04-01 17:38:34 後端開發

一、Mybatis 流程簡介

最近在看 Mybatis 的原始碼,大致了解整個框架流程后便手寫了一個特別簡單的SimpMybatis的小Demo,來鞏固這整個框架的學習,下圖是我所畫的框架大致執行流程:

mybatis流程圖

??對上圖分析后得出結論:

  1. Mybatis 的組態檔分為兩種,且這兩個組態檔會被封裝到 Configuration 中
    • 主組態檔(MybatisConfig.xml):配置 jdbc 等環境資訊,全域唯一;
    • 映射檔案(xxxMapper.xml):配置多個 Sql ,可有多個,
  2. 通過 Mybatis 組態檔得到 SqlSessionFactory ;
  3. 通過 SqlSessionFactory 得到 SqlSession,它就相當于 Request 請求;
  4. SqlSession 呼叫底層的 Executor 執行器來操作資料庫,同時執行器有兩類實作
    • 基本實作
    • 帶有快取功能的實作
  5. 決議傳入的引數,對其進行封裝,執行并回傳結果;

以上就是我梳理的 Mybatis 大致流程,看似簡單,卻很精妙,

二、手寫簡化版 Mybatis 設計思路

2.1 簡化后的思路

簡化流程圖

2.2 讀取 XML 檔案,建立連接

從圖中可以看出,MyConfig 負責與人互動,待讀取xml后,將屬性和連接資料庫的操作封裝在 MyConfig 物件中供后面的組件呼叫,本專案將使用 dom4j 來讀取xml檔案,它具有性能優異和非常方便使用的特點,

2.3 創建SqlSession,搭建 Configuration 和 Executor 之間的橋梁

從流程圖中的箭頭可以看出,MySqlSession 的成員變數中必須得有 MyExecutorImpl 和 MyConfig 去集中做調配,一個Session僅擁有一個對應的資料庫連接,類似于一個前段請求Request,它負責直接呼叫對應 execute(sql) 來做 CRUD 操作,

2.4 創建 MyExecutor,封裝 JDBC 操作資料庫

MyExecutor 是一個執行器,負責SQL陳述句的生成和查詢快取的維護,也就是 Jdbc 的代碼將在這里完成,不過本文只實作了單表,查詢快取并未實作,

2.5 創建 MySqlSessionProxy,使用動態代理生成 Mapper 物件

只是希望對指定的介面生成一個物件,使得執行它的時候能運行一句 sql,而介面無法直接呼叫方法,所以這里使用動態代理生成物件,在執行時還是回到 MySqlSession 中呼叫查詢,最終由 MyExecutorImpl 做 JDBC查詢,這樣設計是為了單一職責,可擴展性更強,

三、實作自己的Mybatis

這次會將其打成 Jar 包,并將其匯入專案實作,做一個 Mybatis 的還原,

工程檔案及目錄:

image-20201125151214685

3.1 匯入兩個所需 Jar 包:資料庫連接和XML決議

Maven 匯入如下:

<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
<!-- xml決議 -->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- Mysql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

3.2 創建 MyConfig 類,對兩大 XML 組態檔進行決議,并建立連接

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:17
 */
public class MyConfig {
    /**
     * 啟動應用程式類加載器
     */
    private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
    /**
     * 資料庫建立連接
     * @return 回傳資料庫連接物件
     *
     */
    public Connection build() {

        // Mybatis主組態檔名
        String resource = "mybatis-config.xml";

        // 獲取檔案根節點
        Element root = parseXML(resource);
        // 獲取檔案對應的資訊
        Map<String, String> jdbcMap = parseNodes(root);
        try {
            Class.forName(jdbcMap.get("driverClassName"));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("驅動器未找到,請重新檢查!");
        }
        Connection connect = null;
        try {
            connect = DriverManager.getConnection(jdbcMap.get("url"), jdbcMap.get("username"), jdbcMap.get("password"));
        } catch (SQLException throwables) {
            throw new RuntimeException("資料庫連接錯誤,請檢查路徑、用戶名、密碼是否輸入正確!");
        }
        return connect;
    }
    /**
     * 決議資料庫組態檔
     * @param resource 資料庫組態檔路徑
     * @return 獲取到的檔案根節點
     */
    public static Element parseXML(String resource) {
        try {
            // 回傳用于讀取指定資源的輸入流
            InputStream stream = loader.getResourceAsStream(resource);
            // 使用dom4j決議XML
            SAXReader reader = new SAXReader();
            // 使用SAX從給定流中讀取檔案
            Document doc = reader.read(stream);
            // 獲取檔案的根節點
            return doc.getRootElement();
        } catch (DocumentException e) {
            throw new RuntimeException("決議 XML 時發生錯誤!" + resource);
        }
    }
    /**
     * 決議主xml檔案標簽節點
     * @param node 組態檔根節點
     * @return 回傳從組態檔中拿到的開啟資料庫對應值
     */
    private Map<String, String> parseNodes(Element node) {
        // 判斷根標簽名稱
        if (!node.getName().equals("database")) {
            throw new RuntimeException("資料庫組態檔根標簽名稱必須為【database】");
        }

        // 存放組態檔取得的值
        Map<String, String> map = new HashMap<String, String>();
        map.put("driverClassName", null);
        map.put("url", null);
        map.put("username", null);
        map.put("password", null);

        // 讀取property的屬性內容
        for (Element item : node.elements()) {
            // 獲取標簽中存放的值,并洗掉其前導和結尾的空格
            String value = https://www.cnblogs.com/-Koos-/archive/2021/04/01/getValue(item);
            // 獲取標簽中 name 的名稱
            String name = item.attributeValue("name");
            // 如果name或value為空則有對應值未輸入
            if (name == null || "".equals(value)) {
                throw new RuntimeException("[database]: <property> 中應該包含名稱和值");
            }
            switch (name) {
                case "driverClassName" : map.put("driverClassName", value); break;
                case "url" : map.put("url", value); break;
                case "username" : map.put("username", value); break;
                case "password" : map.put("password", value); break;
                default: throw new RuntimeException("[database]: <property> 中有未知屬性");
            }
        }
        return map;
    }
    /**
     * 獲取property屬性中的值
     * @param node 組態檔根節點
     * @return 如果有value值,則讀取;沒有設定value,則讀取內容
     */
    private static String getValue(Element node) {
        return node.hasContent() ? node.getText().trim() : node.attributeValue("value").trim();
    }
    /**
     *
     * @param path
     * @return
     */
    @SuppressWarnings(value = "https://www.cnblogs.com/-Koos-/archive/2021/04/01/rawtypes")
    public MappingBean readMapper(String path) {
        MappingBean bean = new MappingBean();
        try {
            InputStream stream = loader.getResourceAsStream(path);
            SAXReader reader = new SAXReader();
            Document doc = reader.read(stream);
            Element root = doc.getRootElement();
            // 把mapper節點的nameSpace值存為介面名
            bean.setInterfaceName(root.attributeValue("nameSpace").trim());
            // 用來存盤方法的List
            List<Mapping> list = new ArrayList<Mapping>();
            //遍歷根節點下所有子節點
            for(Iterator rootIter = root.elementIterator(); rootIter.hasNext();) {
                // 存盤一條方法的資訊
                Mapping fun = new Mapping();
                Element e = (Element) rootIter.next();
                String sqlType = e.getName().trim();
                String funcName = e.attributeValue("id").trim();
                String sql = e.getText().trim();
                String resultType = e.attributeValue("resultType").trim();
                fun.setSqlType(sqlType);
                fun.setFuncName(funcName);
                Object newInstance = null;
                try {
                    newInstance = Class.forName(resultType).newInstance();
                } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e1) {
                    e1.printStackTrace();
                }
                fun.setResultType(newInstance);
                fun.setSql(sql);
                list.add(fun);
            }
            bean.setList(list);

        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return bean;
    }
    /**
     * 決議mapper映射xml檔案
     * @param element mapper檔案路徑
     * @return
     */
    public MappingBean parseMapper(Element element) {

        MappingBean bean = new MappingBean();
        String namespace = element.attributeValue("namespace");
        if (namespace == null) {
            throw new RuntimeException("映射檔案namespace不存在");
        }
        bean.setInterfaceName(namespace);
        List<Mapping> list = new ArrayList<>();
        Iterator<Element> it = element.elementIterator();
        while (it.hasNext()) {
            Element ele=(Element) it.next();
            Mapping mapping =new Mapping();
            String funcName =ele.attributeValue("id");
            if (funcName==null){
                throw new RuntimeException("mapper映射檔案中id不存在");
            }
            String sqlType = ele.getName();
            String paramType = ele.attributeValue("parameterType");
            String resultType=ele.attributeValue("resultType");
            String sql=ele.getText().trim();
            mapping.setFuncName(funcName);
            mapping.setSqlType(sqlType);
            mapping.setParameterType(paramType);
            mapping.setSql(sql);
            Object object=null;
            try {
                object=Class.forName(resultType).newInstance();
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            mapping.setResultType(object);
            list.add(mapping);
        }
        bean.setList(list);
        return bean;
    }
}

?由 MyConfig類 代碼可以得知:

  1. Mybatis 主配置類名稱必須為:mybatis-config.xml
  2. mybatis-config.xml 的根標簽必須為: <database></database>
  3. Mapper.xml 必須包括:namespace
  4. Sql 是否有回傳值都應包括:resultType(個人偷懶,沒做判斷);
  5. ... ...

3.3 MySqlSession 代理

MySqlSession 肯定不會自己去執行,因為不能寫死所以使用動態代理來使代理類去實作具體方法,

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:52
 */
public class MySqlSession {
    private final MyExcutor excutor= new MyExcutorImpl();

    private final MyConfig config = new MyConfig();

    public <T> T selectValue(Mapping statement, List<Object> parameter){
        return excutor.queryValue(statement, parameter);
    }

    public <T> T selectNull(Mapping statement){
        return excutor.queryNull(statement);
    }

    public int deleteValue(Mapping statement, List<Object> parameter) {
        return excutor.deleteValue(statement, parameter);
    }

    public int updateValue(Mapping statement, List<Object> parameter) {
        return excutor.updateValue(statement, parameter);
    }

    public int insertValue(Mapping mapping, List<Object> parameter) {
        return excutor.insertValue(mapping, parameter);
    }

    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> clas){
        //動態代理呼叫
        return (T) Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas},
                new MySqlSessionProxy(config,this));
    }
}

撰寫代理類,把mapper映射檔案決議進來

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:55
 */
public class MySqlSessionProxy implements InvocationHandler {
    private MyConfig config;
    private MySqlSession sqlSession;

    public MySqlSessionProxy(MyConfig config, MySqlSession sqlSession) {
        this.config = config;
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method,Object[] args) {
        String name = method.getDeclaringClass().getName();
        String mapperName = name.substring(name.lastIndexOf(".")+1);
        MappingBean bean=config.parseMapper(MyConfig.parseXML(mapperName+".xml"));

        if (bean!=null && (bean.getList()!=null && bean.getList().size()>0)){
            for (Mapping mapping : bean.getList()){
                if (mapping.getFuncName().equals(method.getName())) {
                    // 判斷是否為查詢陳述句
                    if ("select".equals(mapping.getSqlType().toLowerCase())) {
                        System.out.println("執行查詢方法:" + mapping.getSql());
                        if (args!=null) {
                            System.out.println("引數:"+ Arrays.toString(args));
                            return sqlSession.selectValue(mapping, Arrays.asList(args));
                        } else {
                            System.out.println("引數:null");
                            return sqlSession.selectNull(mapping);
                        }
                    }
                    // 判斷是否為洗掉陳述句
                    if ("delete".equals(mapping.getSqlType().toLowerCase())){
                        System.out.println("執行查詢方法:"+mapping.getSql());
                        System.out.println("引數:"+ Arrays.toString(args));
                        return sqlSession.deleteValue(mapping, Arrays.asList(args));
                    }
                    // 判斷是否為更新陳述句
                    if ("update".equals(mapping.getSqlType().toLowerCase())) {
                        System.out.println("執行查詢方法:"+mapping.getSql());
                        System.out.println("引數:"+ Arrays.toString(args));
                        return sqlSession.updateValue(mapping, Arrays.asList(args));
                    }
                    // 判斷是否為插入陳述句
                    if ("insert".equals(mapping.getSqlType().toLowerCase())) {
                        System.out.println("執行查詢方法:" + mapping.getSql());
                        System.out.println("引數:" + Arrays.toString(args));
                        return sqlSession.insertValue(mapping, Arrays.asList(args));
                    }
                }
            }
        }
        return null;
    }
}

?注意:通過上段代碼可知,映射檔案必須和介面名稱保持一致,

3.4 創建對應物體類和XML映射檔案Sql物體類

a. 介面物體類

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:38
 */
public class MappingBean {
    /**
     * 介面名
     */
    private String interfaceName;
    /**
     * 介面下所有方法
     */
    private List<Mapping> list;
    // setter、getter略
}

b. 映射檔案中 Sql 的物體類

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:38
 */
public class Mapping {
    private String sqlType;
    private String funcName;
    private String sql;
    private Object resultType;
    private String parameterType;
     // setter、getter略
}

3.5 創建 MyExcutor 介面以及實作類

MyExcutor 介面

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:42
 */
public interface MyExcutor {
    // 無參查詢
    <T> T queryNull(Mapping mapping);
	// 有參查詢
    <T> T queryValue(Mapping mapping, List<Object> params);
	// 洗掉
    int deleteValue(Mapping mapping, List<Object> params);
	// 更新
    int updateValue(Mapping mapping, List<Object> params);
	// 插入
    int insertValue(Mapping mapping, List<Object> params);
}

MyExcutorImpl 實作類

這里通過反射將結果轉換成物件

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 12:42
 */
public class MyExcutorImpl implements MyExcutor {

    private MyConfig config = new MyConfig();

    @Override
    public <T> T queryNull(Mapping mapping) {
        Connection conn = config.build();
        PreparedStatement preparedStatement;
        ResultSet resultSet;
        Object obj;
        List<Object> list = new ArrayList<>();
        try {
            preparedStatement=conn.prepareStatement(mapping.getSql());
            if (mapping.getResultType() == null){
                throw new RuntimeException("回傳的映射結果不能為空!");
            }
            resultSet = preparedStatement.executeQuery();
            int row = 0;
            ResultSetMetaData rd = resultSet.getMetaData();
            while (resultSet.next()){
                obj=resultToObject(resultSet,mapping.getResultType());
                row++;
                list.add(obj);
            }
            System.out.println("記錄行數:"+row);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return (T) list;
    }

    @Override
    public <T> T queryValue(Mapping mapping, List<Object> params) {
        Connection conn = config.build();
        PreparedStatement preparedStatement;
        ResultSet resultSet;
        Object obj;
        List<Object> list = new ArrayList<>();
        try {
            preparedStatement=conn.prepareStatement(mapping.getSql());
            for (int i=0; i<params.size(); i++) {
                preparedStatement.setString(i+1, params.get(i).toString());
            }
            if (mapping.getResultType() == null){
                throw new RuntimeException("回傳的映射結果不能為空!");
            }
            resultSet = preparedStatement.executeQuery();
            int row = 0;
            ResultSetMetaData rd = resultSet.getMetaData();
            while (resultSet.next()){
                obj=resultToObject(resultSet,mapping.getResultType());
                row++;
                list.add(obj);
            }
            System.out.println("記錄行數:"+row);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return (T) list;
    }

    @Override
    public int deleteValue(Mapping mapping, List<Object> params) {
        Connection conn = config.build();
        int rows = 0;
        PreparedStatement preparedStatement=null;
        try {
            preparedStatement = conn.prepareStatement(mapping.getSql());
            for (int i=0; i<params.size(); i++) {
                preparedStatement.setString(i+1, params.get(i).toString());
            }
            rows = preparedStatement.executeUpdate();
            if (rows != 0) {
                System.out.println("洗掉成功,受影響行數:"+rows);
            } else {
                System.out.println("洗掉失敗,資料庫無相應資料...");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rows;
    }

    @Override
    public int updateValue(Mapping mapping, List<Object> params) {
        Connection conn = config.build();
        int rows = 0;
        PreparedStatement preparedStatement=null;
        try {
            preparedStatement = conn.prepareStatement(mapping.getSql());
            for (int i=0; i<params.size(); i++) {
                preparedStatement.setString(i+1, params.get(i).toString());
            }
            rows = preparedStatement.executeUpdate();
            if (rows != 0) {
                System.out.println("修改成功,受影響行數:"+rows);
            } else {
                System.out.println("修改失敗,資料庫無相應資料...");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rows;
    }

    @Override
    public int insertValue(Mapping mapping, List<Object> params) {
        Connection conn = config.build();
        int rows = 0;
        PreparedStatement preparedStatement=null;
        try {
            preparedStatement = conn.prepareStatement(mapping.getSql());
            for (int i=0; i<params.size(); i++) {
                preparedStatement.setString(i+1, params.get(i).toString());
            }
            try {
                rows = preparedStatement.executeUpdate();
                if (rows != 0) {
                    System.out.println("插入成功,受影響行數:"+rows);
                } else {
                    System.out.println("插入失敗...");
                }
            } catch (SQLException throwables) {
                throw new RuntimeException("插入重復 \"Key\" 值資料");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rows;
    }

    private <T> T resultToObject(ResultSet rs, Object object) {
        Object obj=null;

        try {
            Class<?> cls = object.getClass();
        /*
            這里為什么要通過class再new一個物件?
            因為如果不new一個新的物件,每次回傳的都是形參上的object,
            而這個object都是同一個,會導致list串列后面覆寫前面值,
         */
            obj=cls.newInstance();
            //獲取結果集元資料(獲取此 ResultSet 物件的列的編號、型別和屬性,)
            ResultSetMetaData rd=rs.getMetaData();
            for (int i = 0; i < rd.getColumnCount(); i++) {
                //獲取列名
                String columnName=rd.getColumnLabel(i+1);
                //組合方法名
                String methodName="set"+columnName.substring(0, 1).toUpperCase()+columnName.substring(1);
                //獲取列型別
                int columnType=rd.getColumnType(i+1);
                Method method=null;
                switch(columnType) {
                    case java.sql.Types.VARCHAR:
                    case java.sql.Types.CHAR:
                        method=cls.getMethod(methodName, String.class);
                        method.invoke(obj, rs.getString(columnName));
                        break;
                    case java.sql.Types.INTEGER:
                        method=cls.getMethod(methodName, Integer.class);
                        method.invoke(obj, rs.getInt(columnName));
                        break;
                    default:
                        break;
                }
            }
        }  catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException | SQLException e) {
            e.printStackTrace();
        }
        return (T) obj;
    }
}

四、打包測驗

4.1 將其打成 Jar 包

打包

4.2 創建一個Maven專案,因為需要匯入對應的包

<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
<!-- xml決議 -->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- Mysql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<!-- 自己寫的Mybatis,首先要將其放入本地倉庫 -->
<dependency>
    <groupId>top.kk233</groupId>
    <artifactId>SimpMybatis</artifactId>
    <version>1.0.0</version>
</dependency>

?Maven匯入本地Jar包方法自行百度,這里就不贅述,

4.3 創建資料庫

這里提供一個我測驗的,你們可以自行創建其他的

CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;
CREATE TABLE `user` (
	`id` INT ( 10 ) NOT NULL,
	`sex` VARCHAR ( 2 ) NOT NULL,
	`password` VARCHAR ( 255 ) DEFAULT NULL,
	`username` VARCHAR ( 255 ) DEFAULT NULL,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;
INSERT INTO `test`.`user` ( `id`, `sex`, `password`, `username` )
VALUES
	( 1, '男', '12344', '五六' ),
	( 2, '女', '12643', '張三' ),
	( 3, '男', '1245453', '李四' );

4.4 創建物體類

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 16:17
 */
public class User {
    private Integer id;
    private String sex;
    private String password;
    private String username;
    // setter、getter略
}

4.5 創建 UserMapper 介面

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 16:17
 */
public interface UserMapper {
    List<User> getUsers();

    List<User> getUserBySexAndName(String sex, String username);

    int deleteUserById(Integer id);

    int updateUserByName(String username, String password);

    int insertUser(int id, String sex, String password, String username);
}

4.6 創建 UserMapper.xml 映射檔案

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="top.kk233.mapper.UserMapper">
    <select id="getUsers" resultType="top.kk233.pojo.User">
        SELECT * FROM user
    </select>
    <select id="getUserBySexAndName" resultType="top.kk233.pojo.User">
        select * from user where sex=? and username=?
    </select>

    <delete id="deleteUserById" resultType="top.kk233.pojo.User">
        delete from user where id=?
    </delete>

    <update id="updateUserByName" resultType="top.kk233.pojo.User">
        update user set password=? where username=?
    </update>

    <insert id="insertUser" resultType="top.kk233.pojo.User">
        insert into user values(?,?,?,?)
    </insert>
</mapper>

4.7 創建 mybatis-config.xml 資料庫組態檔

<?xml version="1.0" encoding="UTF-8"?>
<database>
    <property name="driverClassName">com.mysql.jdbc.Driver</property>
    <property name="url">jdbc:mysql://localhost:3306/test?useSSL=false</property>
    <property name="username">root</property>
    <property name="password">124760</property>
</database>

4.8 創建啟動類測驗

/**
 * @author Kenelm
 * @version 1.0
 * @date 2020/11/22 16:24
 */
public class app {

    public static void main(String[] args) {

        MySqlSession sql = new MySqlSession();
        UserMapper mapper = sql.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();
        users.forEach(System.out::println);
        System.out.println("==========================");
        List<User> users1 = mapper.getUserBySexAndName("女", "張三");
        users1.forEach(System.out::println);
        System.out.println("==========================");
        mapper.deleteUserById(1);
        System.out.println("==========================");
        mapper.updateUserByName("五六", "女");
        System.out.println("==========================");
        mapper.insertUser(10, "男", "123123", "五七");

    }
}

4.9 測驗結果

success

??測驗成功,這就是本人所手寫的Mybatis,雖然比較簡單,但還是學習到了很多東西,

??專案放在 Gitee 上有需要自行下載,覺得可以還請點個Star

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/270649.html

標籤:其他

上一篇:go time/rate 介面-賬戶/IP 限流

下一篇:c/c++編程日記:用C語言實作消消樂游戲(附原始碼)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more