Mybatis是目前开发中最常用的一款基于ORM思想的半自动持久层框架,平时我们都仅仅停留在使用阶段,对mybatis是怎样运行的并不清楚,今天抽空找到一些资料自学了一波,自己写了一个mybatis的雏形,在此对学习过程做一个记录
首先,我们新建一个提供mybatis框架功能的工程IMybatis,这个工程中主要完成mybatis整个初始化和执行过程的功能开发。

该工程中用到的依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6.  
  7. <groupId>com.my</groupId>
  8. <artifactId>IMybatis</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. <build>
  11. <plugins>
  12. <plugin>
  13. <groupId>org.apache.maven.plugins</groupId>
  14. <artifactId>maven-compiler-plugin</artifactId>
  15. <configuration>
  16. <source>6</source>
  17. <target>6</target>
  18. </configuration>
  19. </plugin>
  20. </plugins>
  21. </build>
  22.  
  23. <properties>
  24. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  25. <maven.compile.encoding>UTF-8</maven.compile.encoding>
  26. <java.version>1.8</java.version>
  27. <maven.compile.source>1.8</maven.compile.source>
  28. <maven.compile.target>1.8</maven.compile.target>
  29. </properties>
  30. <dependencies>
  31. <dependency>
  32. <groupId>mysql</groupId>
  33. <artifactId>mysql-connector-java</artifactId>
  34. <version>8.0.19</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>log4j</groupId>
  38. <artifactId>log4j</artifactId>
  39. <version>1.2.12</version>
  40. </dependency>
  41. <dependency>
  42. <groupId>junit</groupId>
  43. <artifactId>junit</artifactId>
  44. <version>4.10</version>
  45. </dependency>
  46. <dependency>
  47. <groupId>dom4j</groupId>
  48. <artifactId>dom4j</artifactId>
  49. <version>1.6.1</version>
  50. </dependency>
  51. <dependency>
  52. <groupId>jaxen</groupId>
  53. <artifactId>jaxen</artifactId>
  54. <version>1.1.6</version>
  55. </dependency>
  56. <dependency>
  57. <groupId>com.alibaba</groupId>
  58. <artifactId>druid</artifactId>
  59. <version>1.0.26</version>
  60. </dependency>
  61. </dependencies>
  62.  
  63. </project>

我们在完成上面第一步中框架的编写后会进行打包发布到本地仓库,再新建一个测试工程IMybatis-test,这个工程的pom文件中会引入IMybatis工程的依赖,完成测试

该工程的依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6.  
  7. <groupId>org.my</groupId>
  8. <artifactId>IMybatis-test</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10.  
  11. <dependencies>
  12. <dependency>
  13. <groupId>com.my</groupId>
  14. <artifactId>IMybatis</artifactId>
  15. <version>1.0-SNAPSHOT</version>
  16. </dependency>
  17. </dependencies>
  18.  
  19. </project>

mybatis要完成对数据库的连接,增删改查功能,需要有两个配置文件(这里先不管以注解的形式在mapper接口中编写的sql),一个是配置的数据库的连接信息,我这里是datasourceConfig.xml,

  1. <configuration>
  2. <!-- 数据库配置信息 -->
  3. <dataSource>
  4. <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
  5. <property name="url" value="jdbc:mysql:///test?serverTimezone=Asia/Shanghai"></property>
  6. <property name="username" value="root"></property>
  7. <property name="password" value="123456"></property>
  8. </dataSource>
  9.  
  10. <mapper resource="UserMapper.xml"></mapper>
  11. </configuration>

另一个是提供sql的mapper文件,这里是UserMapper.xml,这两个文件都在IMybatis-test工程中提供

  1. <mapper namespace="com.my.dao.UserMapper">
  2.  
  3. <!-- sql的唯一表示由 namespace.id 来组成statementId -->
  4. <select id="findAll" resultType="com.my.pojo.User">
  5. select * from user
  6. </select>
  7.  
  8. <select id="findOne" parameterType="com.my.pojo.User" resultType="com.my.pojo.User">
  9. select * from user where id = #{id}
  10. </select>
  11.  
  12. <select id="findById" parameterType="java.lang.Long" resultType="com.my.pojo.User">
  13. select * from user where id = #{id}
  14. </select>
  15.  
  16. <delete id="delete" parameterType="com.my.pojo.User">
  17. delete from user where id = #{id}
  18. </delete>
  19.  
  20. <delete id="deleteById" parameterType="java.lang.Long">
  21. delete from user where id = #{id}
  22. </delete>
  23.  
  24. <update id="update" parameterType="com.my.pojo.User">
  25. update user set name = #{name} where id = #{id}
  26. </update>
  27.  
  28. <insert id="insert" parameterType="com.my.pojo.User">
  29. insert into user(id, name) VALUES(#{id}, #{name})
  30. </insert>
  31. </mapper>

下面就要完成IMybatis的功能开发。

一、新建Resource类完成对datasourceConfig.xml文件的加载,将其以流的形式加载到内存中

  1. package com.my.io;
  2.  
  3. import java.io.InputStream;
  4.  
  5. /**
  6. * @Description: 配置文件读取
  7. * @Author lzh
  8. * @Date 2020/12/6 16:01
  9. */
  10. public class Resource {
  11.  
  12. /**
  13. * 根据传递的路径path去读取到该路径下的配置文件datasourceConfig.xml,并将其读成字节流返回
  14. * @param path
  15. * @return InputStream
  16. */
  17. public static InputStream getResourceAsStream(String path){
  18. InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);
  19. return resourceAsStream;
  20. }
  21. }

二、新建SqlSessionFactoryBuilder类,编写build()方法,一步一步构建SqlSessionFactory对象

  1. package com.my.sqlSession;
  2.  
  3. import com.my.config.XMLConfigBuilder;
  4. import com.my.pojo.Configuration;
  5.  
  6. import java.io.InputStream;
  7.  
  8. /**
  9. * @Description: 解析配置文件
  10. * @Author lzh
  11. * @Date 2020/12/6 16:23
  12. */
  13. public class SqlSessionFactoryBuilder {
  14.  
  15. /**
  16. * 根据字节流解析出配置文件中各个标签的值,并封装到Configuration中,创建DefaultSqlSessionFactory对象
  17. * @param in
  18. * @return
  19. * @throws Exception
  20. */
  21. public SqlSessionFactory build(InputStream in) throws Exception {
  22.  
  23. //创建一个XMLConfigBuilder对象
  24. XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  25.  
  26. //对配置文件进行解析
  27. Configuration configuration = xmlConfigBuilder.parseConfig(in);
  28.  
  29. //创建DefaultSqlSessionFactory对象
  30. DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
  31.  
  32. return defaultSqlSessionFactory;
  33. }
  34. }

三、在build方法中可以看到首先要创建一个XMLConfigBuilder 对象,在该对象中编写了一个parseConfig()方法完成对配置文件的解析,并完成对Configuration 对象的封装,Configuration 是我们这个工程中的一个非常核心的对象,里面存储了对配置文件解析后的结果,同样在真正的Mybatis框架中也有该对象,当然功能比我这里的更强大。

  1. package com.my.config;
  2.  
  3. import com.alibaba.druid.pool.DruidDataSource;
  4. import com.my.io.Resource;
  5. import com.my.pojo.Configuration;
  6. import org.dom4j.Document;
  7. import org.dom4j.Element;
  8. import org.dom4j.io.SAXReader;
  9.  
  10. import java.io.InputStream;
  11. import java.util.List;
  12. import java.util.Properties;
  13.  
  14. /**
  15. * @Description:
  16. * @Author lzh
  17. * @Date 2020/12/6 16:26
  18. */
  19. public class XMLConfigBuilder {
  20.  
  21. private Configuration configuration;
  22.  
  23. public XMLConfigBuilder() {
  24. this.configuration = new Configuration();
  25. }
  26.  
  27. /**
  28. * 解析dataSourceConfig.xml
  29. * @param in
  30. * @return
  31. * @throws Exception
  32. */
  33. public Configuration parseConfig(InputStream in) throws Exception {
  34.  
  35. //利用dom4j技术对配置文件进行解析
  36. Document document = new SAXReader().read(in);
  37. Element rootElement = document.getRootElement();
  38. //查找dataSourceConfig.xml中的property标签
  39. List<Element> list = rootElement.selectNodes("//property");
  40. Properties properties = new Properties();
  41. for (Element element : list) {
  42. //取出每个property标签中的值存到Properties对象中
  43. String name = element.attributeValue("name");
  44. String value = element.attributeValue("value");
  45. properties.setProperty(name, value);
  46. }
  47. //从Properties中取出各个属性构建一个连接池,来提供对数据库连接的管理,避免资源浪费,提高性能
  48. DruidDataSource druidDataSource = new DruidDataSource();
  49. druidDataSource.setDriverClassName(properties.getProperty("driverClass"));
  50. druidDataSource.setUrl(properties.getProperty("url"));
  51. druidDataSource.setUsername(properties.getProperty("username"));
  52. druidDataSource.setPassword(properties.getProperty("password"));
  53. //将连接池对象放入Configuration对象中
  54. configuration.setDataSource(druidDataSource);
  55.  
  56. //解析dataSourceConfig.xml中的mapper标签,mapper标签中的resource属性值存放的就是UserMapper.xml的文件位置
  57. List<Element> mapperList = rootElement.selectNodes("//mapper");
  58. for (Element element : mapperList) {
  59. String mapperPath = element.attributeValue("resource");
  60. InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
  61. //解析UserMapper.xml文件,进一步封装Configuration对象
  62. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
  63. xmlMapperBuilder.parse(resourceAsStream);
  64. }
  65. return configuration;
  66. }
  67. }

上图红色的地方创建了一个XMLMapperBuilder对象,该对象提供了一个parse()方法,就是完成对UserMapper.xml文件的解析,并完成对Configuration封装

  1. package com.my.config;
  2.  
  3. import com.my.config.eunm.SqlCommandType;
  4. import com.my.pojo.Configuration;
  5. import com.my.pojo.MappedStatement;
  6. import org.dom4j.Document;
  7. import org.dom4j.DocumentException;
  8. import org.dom4j.Element;
  9. import org.dom4j.io.SAXReader;
  10.  
  11. import java.io.InputStream;
  12. import java.util.List;
  13.  
  14. /**
  15. * @Description:
  16. * @Author lzh
  17. * @Date 2020/12/6 17:03
  18. */
  19. public class XMLMapperBuilder {
  20.  
  21. private Configuration configuration;
  22.  
  23. public XMLMapperBuilder(Configuration configuration) {
  24. this.configuration = configuration;
  25. }
  26.  
  27. /**
  28. * 解析UserMapper.xml配置文件中得内容,将每一个标签构建成一个MappedStatement,并赋值到Configuration中
  29. * @param in
  30. * @throws DocumentException
  31. */
  32. public void parse(InputStream in) throws DocumentException {
  33. Document document = new SAXReader().read(in);
  34. Element rootElement = document.getRootElement();
  35. String namespace = rootElement.attributeValue("namespace");
  36. //解析select标签
  37. List<Element> selectList = rootElement.selectNodes("//select");
  38. this.parseElement(selectList, namespace, SqlCommandType.SELECT);
  39.  
  40. //解析insert标签
  41. List<Element> insertList = rootElement.selectNodes("//insert");
  42. this.parseElement(insertList, namespace, SqlCommandType.INSERT);
  43.  
  44. //解析update标签
  45. List<Element> updateList = rootElement.selectNodes("//update");
  46. this.parseElement(updateList, namespace, SqlCommandType.UPDATE);
  47.  
  48. //解析delete标签
  49. List<Element> deleteList = rootElement.selectNodes("//delete");
  50. this.parseElement(deleteList, namespace, SqlCommandType.DELETE);
  51. }
  52.  
  53. /**
  54. * 解析mapper.xml文件中增删改查标签
  55. * @param elementList
  56. * @param namespace
  57. * @param sqlCommandType
  58. */
  59. private void parseElement(List<Element> elementList, String namespace, SqlCommandType sqlCommandType) {
  60. for (Element element : elementList) {
  61. String id = element.attributeValue("id");
  62. String resultType = element.attributeValue("resultType");
  63. String parameterType = element.attributeValue("parameterType");
  64. String sql = element.getTextTrim();
  65.  
  66. MappedStatement mappedStatement = new MappedStatement();
  67. mappedStatement.setSqlCommandType(sqlCommandType);
  68. mappedStatement.setId(id);
  69. mappedStatement.setParameterType(parameterType);
  70. mappedStatement.setResultType(resultType);
  71. mappedStatement.setSql(sql);
  72.  
  73. configuration.getMappedStatementMap().put(namespace + "." + id, mappedStatement);
  74. }
  75. }
  76. }

该类中用到的SqlCommandType是一个枚举类,就是列举的UserMapper.xml中的几个主要的sql标签类型增删改查,也是借鉴的原Mybatis框架中的写法

  1. package com.my.config.eunm;
  2.  
  3. public enum SqlCommandType {
  4. INSERT,
  5. UPDATE,
  6. DELETE,
  7. SELECT;
  8.  
  9. private SqlCommandType(){
  10.  
  11. }
  12. }

还有一个MappedStatement对象,这个对象中就是封装的每一个insert、update、delete、select标签中的信息(包括每个标签中的id、parameterType、resutType、sql语句等等),每个标签就是一个MappedStatement对象

  1. package com.my.pojo;
  2.  
  3. import com.my.config.eunm.SqlCommandType;
  4.  
  5. /**
  6. * @Description:
  7. * @Author lzh
  8. * @Date 2020/12/6 16:17
  9. */
  10. public class MappedStatement {
  11.  
  12. private SqlCommandType sqlCommandType;
  13.  
  14. private String id;
  15.  
  16. private String resultType;
  17.  
  18. private String parameterType;
  19.  
  20. private String sql;
  21.  
  22. public SqlCommandType getSqlCommandType() {
  23. return sqlCommandType;
  24. }
  25.  
  26. public void setSqlCommandType(SqlCommandType sqlCommandType) {
  27. this.sqlCommandType = sqlCommandType;
  28. }
  29.  
  30. public String getId() {
  31. return id;
  32. }
  33.  
  34. public void setId(String id) {
  35. this.id = id;
  36. }
  37.  
  38. public String getResultType() {
  39. return resultType;
  40. }
  41.  
  42. public void setResultType(String resultType) {
  43. this.resultType = resultType;
  44. }
  45.  
  46. public String getParameterType() {
  47. return parameterType;
  48. }
  49.  
  50. public void setParameterType(String parameterType) {
  51. this.parameterType = parameterType;
  52. }
  53.  
  54. public String getSql() {
  55. return sql;
  56. }
  57.  
  58. public void setSql(String sql) {
  59. this.sql = sql;
  60. }
  61. }

封装好MappedStatement对象后,再将其放入Configuration对象的mappedStatementMap属性中,该属性就是一个Map集合,key就是UserMapper.xml文件中的namespace的值+ "." +每一个标签的id值(例如我们这里的com.my.dao.UserMapper.findAll),因为一个Mapper接口对应一个Mapper.xml文件,而每个Mapper.xml文件中的namespace的值就是Mapper接口的全限定类名,每个标签的id值就是Mapper接口中对应的方法名,所以通过这个组合key就能和Mapper接口产生关联,当我们在调用Mapper接口中的方法时,就可以通过Mapper接口的全限定类名和调用的方法名在Configuration中的Map集合中找到对应的MappedStatement对象,也就是能拿到需要执行的sql、参数类型、返回值类型等等。

  1. package com.my.pojo;
  2.  
  3. import javax.sql.DataSource;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6.  
  7. /** 核心对象
  8. * @Description:
  9. * @Author lzh
  10. * @Date 2020/12/6 16:18
  11. */
  12. public class Configuration {
  13.  
  14. /**
  15. * 数据源
  16. */
  17. private DataSource dataSource;
  18.  
  19. /**
  20. * key:statementId vlaue:封装好的MappedStatement
  21. */
  22. private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
  23.  
  24. public DataSource getDataSource() {
  25. return dataSource;
  26. }
  27.  
  28. public void setDataSource(DataSource dataSource) {
  29. this.dataSource = dataSource;
  30. }
  31.  
  32. public Map<String, MappedStatement> getMappedStatementMap() {
  33. return mappedStatementMap;
  34. }
  35.  
  36. public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
  37. this.mappedStatementMap = mappedStatementMap;
  38. }
  39. }

到这里我们的Configuration对象就封装完毕。

四、然后我们可以在第二步中的SqlSessionFactoryBuilder类的build()方法中看到,根据Configuration对象构造出了DefaultSqlSessionFactory工厂对象,整个构建DefaultSqlSessionFactory的过程就是一个构建者模式的体现(通过多个小的对象构建出一个大的对象)

  1. package com.my.sqlSession;
  2.  
  3. public interface SqlSessionFactory {
  4.  
  5. SqlSession createSqlSession();
  6. }
  1. package com.my.sqlSession;
  2.  
  3. import com.my.pojo.Configuration;
  4.  
  5. /**
  6. * @Description: SqlSession的工厂对象,用于生产SqlSession
  7. * @Author lzh
  8. * @Date 2020/12/6 17:17
  9. */
  10. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  11.  
  12. private Configuration configuration;
  13.  
  14. public DefaultSqlSessionFactory(Configuration configuration) {
  15. this.configuration = configuration;
  16. }
  17.  
  18. /**
  19. * 创建SqlSession会话
  20. * @return
  21. */
  22. public SqlSession createSqlSession() {
  23. return new DefaultSqlSession(configuration);
  24. }
  25. }

五、利用DefaultSqlSessionFactory工厂对象 的createSqlSession()方法来获取一个SqlSession对象,就是一个我们所说的一个会话对象,该对象也是一个非常重要的对象

  1. package com.my.sqlSession;
  2.  
  3. import java.util.List;
  4.  
  5. /**
  6. * @Description: SqlSession
  7. * @Author lzh
  8. * @Date 2020/12/6 17:18
  9. */
  10. public interface SqlSession {
  11.  
  12. <E> List<E> selectList(String statementId, Class<?> methodParameterType, Object... param) throws Exception;
  13.  
  14. <T> T selectOne(String statementId, Class<?> methodParameterType, Object... param) throws Exception;
  15.  
  16. <T> T getMapper(Class<?> mapperClass);
  17. }
  1. package com.my.sqlSession;
  2.  
  3. import com.my.config.eunm.SqlCommandType;
  4. import com.my.pojo.Configuration;
  5. import com.my.pojo.MappedStatement;
  6.  
  7. import java.lang.reflect.*;
  8. import java.util.List;
  9.  
  10. /**
  11. * @Description: SqlSession会话的实现
  12. * @Author lzh
  13. * @Date 2020/12/6 17:21
  14. */
  15. public class DefaultSqlSession implements SqlSession, InvocationHandler {
  16.  
  17. private Configuration configuration;
  18.  
  19. public DefaultSqlSession(Configuration configuration) {
  20. this.configuration = configuration;
  21. }
  22.  
  23. /**
  24. * 多条查询
  25. * @param statementId
  26. * @param param
  27. * @param <E>
  28. * @return
  29. * @throws Exception
  30. */
  31. public <E> List<E> selectList(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
  32.  
  33. SimpleExecutor simpleExecutor = new SimpleExecutor();
  34. List<Object> query = simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), methodParameterType, param);
  35. return (List<E>) query;
  36. }
  37.  
  38. /**
  39. * 单条查询
  40. * @param statementId
  41. * @param param
  42. * @param <T>
  43. * @return
  44. * @throws Exception
  45. */
  46. public <T> T selectOne(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
  47. List<Object> objects = selectList(statementId, methodParameterType, param);
  48. if (objects.size() == 1){
  49. return (T) objects.get(0);
  50. }else if (objects.size() <= 0){
  51. return null;
  52. }else {
  53. throw new RuntimeException("Result more than one");
  54. }
  55. }
  56.  
  57. /**
  58. * 新增
  59. * @param statementId
  60. * @param param
  61. * @return
  62. */
  63. public int insert(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
  64. return update(statementId, methodParameterType, param);
  65. }
  66.  
  67. /**
  68. * 修改
  69. * @param statementId
  70. * @param param
  71. * @return
  72. */
  73. public int update(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
  74. SimpleExecutor simpleExecutor = new SimpleExecutor();
  75. return simpleExecutor.update(configuration, configuration.getMappedStatementMap().get(statementId), methodParameterType, param);
  76. }
  77.  
  78. /**
  79. * 删除
  80. * @param statementId
  81. * @param param
  82. * @return
  83. */
  84. public int delete(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
  85. return update(statementId, methodParameterType, param);
  86. }
  87.  
  88. /**
  89. * 创建代理对象
  90. * @param mapperClass
  91. * @param <T>
  92. * @return
  93. */
  94. @Override
  95. public <T> T getMapper(Class<?> mapperClass) {
  96. Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, this);
  97. return (T) proxyInstance;
  98. }
  99.  
  100. @Override
  101. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  102. String methodName = method.getName();
  103. Class<?> methodParameterType = null;
  104. if (null != method.getParameterTypes() && 0 < method.getParameterTypes().length){
  105. methodParameterType = method.getParameterTypes()[0];
  106. }
  107. String className = method.getDeclaringClass().getName();
  108. String statementId = className + "." + methodName;
  109. MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
  110. SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
  111. if (SqlCommandType.SELECT == sqlCommandType){
  112. Type genericReturnType = method.getGenericReturnType();
  113. if (genericReturnType instanceof ParameterizedType){
  114. return selectList(statementId, methodParameterType, args);
  115. }
  116. return selectOne(statementId, methodParameterType, args);
  117. }else if (SqlCommandType.INSERT == sqlCommandType){
  118. return insert(statementId, methodParameterType, args);
  119. }else if (SqlCommandType.UPDATE == sqlCommandType){
  120. return update(statementId, methodParameterType, args);
  121. }else if (SqlCommandType.DELETE == sqlCommandType){
  122. return delete(statementId, methodParameterType, args);
  123. }else {
  124. throw new RuntimeException("Unknown SqlCommandType For: " + sqlCommandType);
  125. }
  126. }
  127. }

六、在SqlSession中,就提供了增删改查方法,用于操作数据库,我们另外还可以看到一个getMapper()方法,该方法需要传入一个Class参数,那么这个方法是干什么的呢?我们有过开发经验的朋友都知道很早以前在用spring+Mybatis框架开发的时候,对每一个Dao(也就是这里我们说的Mapper层)层的接口都会去写一个实现类DaoImpl,在实现类中通过JDBC来完成对数据库的操作,这样的编码方式会存在很多问题,比如:

  1. 每次执行一个方法都会区获取一个Connection对象,也就是创建一个数据库连接
  2. sql语句和业务代码融合在一起,增加代码耦合度,也不便于维护
  3. 封装返回结果麻烦,不够智能

所以针对第一个问题我们引入了连接池来管理数据库连接,每次都是从池子里面去获取,减少了资源消耗,提高了效率,针对后面两个问题,首先Mybatis去调了DaoImpl实现类,其次,通过Java反射技术完成对参数的赋值和对返回结果的动态封装(这一步后面代码中会有体现)。那么去掉了DaoImpl实现类,Dao接口中需要做的事总是需要有人来做的,否则无法完成对数据库的操作,因此Mybatis中会为每个Dao接口(也就是这里我们说的Mapper接口)生成一个代理对象,去完成之前DaoImpl做的事。这里的getMapper()方法就是去获取传入参数对象的代理对象,我们这里就是获取UserMapper接口的代理对象,创建代理对象时我们可以看到在getMapper()方法中的Proxy.newProxyInstance(),需要传递三个参数,第一个参数就是一个类加载器,第二参数就是我们需要为哪个对象产生代理对象,也就是getMapper()方法的参数,重点是第三个参数,需要传入一个InvocationHandler对象,而InvocationHandler是一个接口,我们这里的DefaultSqlSession实现了这个接口,所以第三个参数传的就是this,该类本身。实现了InvocationHandler接口就需要重写invoke()方法,而我们知道调用代理对象的方法,都会走到该invoke()方法中,所以我们这里调用UserMapper接口中的方法时,同样会执行这里的invoke方法,这样在invoke()方法中就可以完成我们以前在DaoImpl中需要完成的事。

七、下面我们具体来看下invoke()中做了什么,首先看下三个参数,第一个就是一个代理对象,第二个就是我们调用的方法Method,第三个就是调用方法时传入的参数args,那么我们根据Method对象就可以获取到该方法的全限定类名和该方法的名称,从而组合一个statemenId,而我们在上面第五步中通过createSqlSession()方法创建SqlSession对象时,是将我们封装的Configuration对象传入了,所有这里我们可以通过statementId在Configuration对象的mappedStatementMap这个Map集合中找到我们封装的MappedStatement对象,通过MappedStatement对象中的SqlCommandType的值我们可以判断出我们需要执行增删改查中的哪个方法,从而去调用该类具体的增删改查方法,在执行具体方法时,我们这里并没有在SqlSession对象中直接去操作数据库,而是将这些crud操作交给了一个SimpleExecutor执行器去完成真正对数据库的操作。

  1. package com.my.sqlSession;
  2.  
  3. import com.my.pojo.Configuration;
  4. import com.my.pojo.MappedStatement;
  5.  
  6. import java.util.List;
  7.  
  8. public interface Executor {
  9.  
  10. <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object... param) throws Exception;
  11.  
  12. int update(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception;
  13.  
  14. }
  1. package com.my.sqlSession;
  2.  
  3. import com.my.config.BoundSql;
  4. import com.my.pojo.Configuration;
  5. import com.my.pojo.MappedStatement;
  6. import com.my.utils.GenericTokenParser;
  7. import com.my.utils.ParameterMapping;
  8. import com.my.utils.ParameterMappingTokenHandler;
  9.  
  10. import java.beans.PropertyDescriptor;
  11. import java.lang.reflect.Field;
  12. import java.lang.reflect.Method;
  13. import java.sql.Connection;
  14. import java.sql.PreparedStatement;
  15. import java.sql.ResultSet;
  16. import java.sql.ResultSetMetaData;
  17. import java.util.ArrayList;
  18. import java.util.Date;
  19. import java.util.List;
  20.  
  21. /**
  22. * @Description: Executor执行器
  23. * @Author lzh
  24. * @Date 2020/12/6 17:32
  25. */
  26. public class SimpleExecutor implements Executor {
  27.  
  28. /**
  29. * 真正的查询方法,负责完成JDBC的操作
  30. * @param configuration
  31. * @param mappedStatement
  32. * @param param
  33. * @param <E>
  34. * @return
  35. * @throws Exception
  36. */
  37. public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement,Class<?> methodParameterType, Object... param) throws Exception {
  38. PreparedStatement preparedStatement = this.createPreparedStatement(configuration, mappedStatement, methodParameterType, param);
  39. //执行sql,返回结果集ResultSet
  40. ResultSet resultSet = preparedStatement.executeQuery();
  41. //对结果封装,映射出对应得返回类型
  42. String resultType = mappedStatement.getResultType();
  43. Class<?> resultClass = getClassType(resultType);
  44. List<Object> result = new ArrayList<Object>();
  45. while (resultSet.next()){
  46. Object o = resultClass.newInstance();
  47. ResultSetMetaData metaData = resultSet.getMetaData();
  48. for (int i = 1; i <= metaData.getColumnCount(); i++) {
  49. String columnName = metaData.getColumnName(i);
  50. Object object = resultSet.getObject(columnName);
  51. PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass);
  52. Method writeMethod = propertyDescriptor.getWriteMethod();
  53. writeMethod.invoke(o, object);
  54. }
  55. result.add(o);
  56. }
  57. return (List<E>) result;
  58. }
  59.  
  60. @Override
  61. public int update(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception {
  62. PreparedStatement preparedStatement = this.createPreparedStatement(configuration, mappedStatement, methodParameterType, param);
  63. preparedStatement.execute();
  64. int row = preparedStatement.getUpdateCount();
  65. return row;
  66. }
  67.  
  68. /**
  69. * 获取PreparedStatement对象
  70. * @param configuration
  71. * @param mappedStatement
  72. * @param param
  73. * @return
  74. * @throws Exception
  75. */
  76. private PreparedStatement createPreparedStatement(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception {
  77.  
  78. //获取数据库连接
  79. Connection connection = configuration.getDataSource().getConnection();
  80. //从MappedStatement中取出sql,现在的sql就是userMapper.xml中我们编写的带有#{}的sql语句
  81. String sql = mappedStatement.getSql();
  82. //处理sql语句,解析出sql语句中#{}中的属性值,并将#{}替换为?,封装到BoundSql对象中
  83. BoundSql boundSql = getBoundSql(sql);
  84.  
  85. //获取PreparedStatement对象
  86. PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
  87. //如果有参数,给参数赋值
  88. String parameterType = mappedStatement.getParameterType();
  89. if (null != parameterType){
  90. Class<?> parameterClass = getClassType(parameterType);
  91.  
  92. List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
  93. for (int i = 0; i < parameterMappingList.size(); i++) {
  94. if (isObject(methodParameterType)){
  95. preparedStatement.setObject(i + 1, param[0]);
  96. }else {
  97. ParameterMapping parameterMapping = parameterMappingList.get(i);
  98. //该content就是我们sql中#{id}中的id
  99. String content = parameterMapping.getContent();
  100. //利用反射在parameterClass中取出content这个属性的值,并完成sql的赋值
  101. Field declaredField = parameterClass.getDeclaredField(content);
  102. declaredField.setAccessible(true);
  103. Object o = declaredField.get(param[0]);
  104. preparedStatement.setObject(i + 1, o);
  105. }
  106. }
  107. }
  108. return preparedStatement;
  109. }
  110.  
  111. /**
  112. * 根据参数类型或者返回值类型获取该对象的Class
  113. * @param parameterType
  114. * @return
  115. * @throws ClassNotFoundException
  116. */
  117. private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
  118. Class<?> aClass = Class.forName(parameterType);
  119. return aClass;
  120. }
  121.  
  122. /**
  123. * 解析sql,封装成BoundSql
  124. * @param sql
  125. * @return
  126. */
  127. private BoundSql getBoundSql(String sql) {
  128. ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
  129. GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
  130. //解析出来的sql
  131. String parseSql = genericTokenParser.parse(sql);
  132. //解析出来的id和name
  133. List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
  134. BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
  135. return boundSql;
  136. }
  137.  
  138. private Boolean isObject(Class<?> methodParameterType){
  139. if (null == methodParameterType){
  140. return false;
  141. }
  142. if (Integer.class.getName().equals(methodParameterType.getName())
  143. || Long.class.getName().equals(methodParameterType.getName())
  144. || String.class.getName().equals(methodParameterType.getName())
  145. || Double.class.getName().equals(methodParameterType.getName())
  146. || Float.class.getName().equals(methodParameterType.getName())
  147. || Byte.class.getName().equals(methodParameterType.getName())
  148. || Short.class.getName().equals(methodParameterType.getName())
  149. || Character.class.getName().equals(methodParameterType.getName())
  150. || Boolean.class.getName().equals(methodParameterType.getName())
  151. || Date.class.getName().equals(methodParameterType.getName())){
  152. return true;
  153. }
  154. return false;
  155. }
  156. }

八、在这个执行器中就是真正完成对数据库的操作,从连接池中获取一个Connection连接,从MappedStatement中获取到要执行的sql,这里注意这时候的sql还是从UserMapper.xml中解析出来的sql(select * from user where id = #{id}),需要对其进行处理用?替换掉#{},并记录大括号中的参数,因为JDBC中参数的占位符是?,所以这里的getBoundSql()方法就是在做这些事情,最终封装成一个BoundSql对象。

  1. package com.my.config;
  2.  
  3. import com.my.utils.ParameterMapping;
  4.  
  5. import java.util.ArrayList;
  6. import java.util.List;
  7.  
  8. /**
  9. * @Description: sql
  10. * @Author lzh
  11. * @Date 2020/12/6 17:42
  12. */
  13. public class BoundSql {
  14.  
  15. private String sqlText;
  16.  
  17. private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();
  18.  
  19. public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
  20. this.sqlText = sqlText;
  21. this.parameterMappingList = parameterMappingList;
  22. }
  23.  
  24. public String getSqlText() {
  25. return sqlText;
  26. }
  27.  
  28. public void setSqlText(String sqlText) {
  29. this.sqlText = sqlText;
  30. }
  31.  
  32. public List<ParameterMapping> getParameterMappingList() {
  33. return parameterMappingList;
  34. }
  35.  
  36. public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
  37. this.parameterMappingList = parameterMappingList;
  38. }
  39. }

这其中用到的几个工具类我也贴在这里,这也是从Mybatis源码中拿到的,就是对sql解析处理,这里不用过大关注。

  1. package com.my.utils;
  2.  
  3. /**
  4. * @author lzh
  5. */
  6. public interface TokenHandler {
  7. String handleToken(String content);
  8. }
  1. package com.my.utils;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. public class ParameterMappingTokenHandler implements TokenHandler {
  7. private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
  8.  
  9. // context是参数名称 #{id} #{username}
  10.  
  11. public String handleToken(String content) {
  12. parameterMappings.add(buildParameterMapping(content));
  13. return "?";
  14. }
  15.  
  16. private ParameterMapping buildParameterMapping(String content) {
  17. ParameterMapping parameterMapping = new ParameterMapping(content);
  18. return parameterMapping;
  19. }
  20.  
  21. public List<ParameterMapping> getParameterMappings() {
  22. return parameterMappings;
  23. }
  24.  
  25. public void setParameterMappings(List<ParameterMapping> parameterMappings) {
  26. this.parameterMappings = parameterMappings;
  27. }
  28.  
  29. }
  1. package com.my.utils;
  2.  
  3. /**
  4. * @author Clinton Begin
  5. */
  6. public class GenericTokenParser {
  7.  
  8. private final String openToken; //开始标记
  9. private final String closeToken; //结束标记
  10. private final TokenHandler handler; //标记处理器
  11.  
  12. public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
  13. this.openToken = openToken;
  14. this.closeToken = closeToken;
  15. this.handler = handler;
  16. }
  17.  
  18. /**
  19. * 解析${}和#{}
  20. * @param text
  21. * @return
  22. * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
  23. * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
  24. */
  25. public String parse(String text) {
  26. // 验证参数问题,如果是null,就返回空字符串。
  27. if (text == null || text.isEmpty()) {
  28. return "";
  29. }
  30.  
  31. // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
  32. int start = text.indexOf(openToken, 0);
  33. if (start == -1) {
  34. return text;
  35. }
  36.  
  37. // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
  38. // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
  39. char[] src = text.toCharArray();
  40. int offset = 0;
  41. final StringBuilder builder = new StringBuilder();
  42. StringBuilder expression = null;
  43. while (start > -1) {
  44. // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
  45. if (start > 0 && src[start - 1] == '\\') {
  46. builder.append(src, offset, start - offset - 1).append(openToken);
  47. offset = start + openToken.length();
  48. } else {
  49. //重置expression变量,避免空指针或者老数据干扰。
  50. if (expression == null) {
  51. expression = new StringBuilder();
  52. } else {
  53. expression.setLength(0);
  54. }
  55. builder.append(src, offset, start - offset);
  56. offset = start + openToken.length();
  57. int end = text.indexOf(closeToken, offset);
  58. while (end > -1) {////存在结束标记时
  59. if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
  60. // this close token is escaped. remove the backslash and continue.
  61. expression.append(src, offset, end - offset - 1).append(closeToken);
  62. offset = end + closeToken.length();
  63. end = text.indexOf(closeToken, offset);
  64. } else {//不存在转义字符,即需要作为参数进行处理
  65. expression.append(src, offset, end - offset);
  66. offset = end + closeToken.length();
  67. break;
  68. }
  69. }
  70. if (end == -1) {
  71. // close token was not found.
  72. builder.append(src, start, src.length - start);
  73. offset = src.length;
  74. } else {
  75. //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
  76. builder.append(handler.handleToken(expression.toString()));
  77. offset = end + closeToken.length();
  78. }
  79. }
  80. start = text.indexOf(openToken, offset);
  81. }
  82. if (offset < src.length) {
  83. builder.append(src, offset, src.length - offset);
  84. }
  85. return builder.toString();
  86. }
  87. }
  1. package com.my.utils;
  2.  
  3. public class ParameterMapping {
  4.  
  5. private String content;
  6.  
  7. public ParameterMapping(String content) {
  8. this.content = content;
  9. }
  10.  
  11. public String getContent() {
  12. return content;
  13. }
  14.  
  15. public void setContent(String content) {
  16. this.content = content;
  17. }
  18. }

解析完sql后就是创建PreparedStatement对象,并通过MappedStatement对象中记录的参数类型,利用java反射技术进行赋值,然后执行sql,最后再通过MappedStatement对象中记录的返回值类型对结果进行封装,同样是用java反射,这样就实现了参数的动态赋值和结果的动态封装。这就是整个Mybatis的执行流程,到这里也就完成了IMybatis框架的编写,下面我们进行测试。

九、将IMybatis打包到本地仓库,在IMybatis-test中引入依赖,编写一个用户Pojo类、UserMapper接口和一个测试类,UserMapper.xml在上面已经提供

  1. package com.my.pojo;
  2.  
  3. /**
  4. * @Description:
  5. * @Author lzh
  6. * @Date 2020/12/6 15:57
  7. */
  8. public class User {
  9.  
  10. private Long id;
  11.  
  12. private String name;
  13.  
  14. public Long getId() {
  15. return id;
  16. }
  17.  
  18. public void setId(Long id) {
  19. this.id = id;
  20. }
  21.  
  22. public String getName() {
  23. return name;
  24. }
  25.  
  26. public void setName(String name) {
  27. this.name = name;
  28. }
  29.  
  30. @Override
  31. public String toString() {
  32. return "User{" +
  33. "id=" + id +
  34. ", name='" + name + '\'' +
  35. '}';
  36. }
  37. }
  1. package com.my.dao;
  2.  
  3. import com.my.pojo.User;
  4. import java.util.List;
  5.  
  6. public interface UserMapper {
  7.  
  8. /**
  9. * 查询所有
  10. * @return
  11. */
  12. List<User> findAll() ;
  13.  
  14. /**
  15. * 查询单条
  16. * @param user
  17. * @return
  18. */
  19. User findOne(User user);
  20.  
  21. /**
  22. * 根据id查询单条
  23. * @param id
  24. * @return
  25. */
  26. User findById(Long id);
  27.  
  28. /**
  29. * 根据id删除用户
  30. * @param id
  31. * @return
  32. */
  33. int deleteById(Long id);
  34.  
  35. /**
  36. * 删除用户
  37. * @param user
  38. * @return
  39. */
  40. int delete(User user);
  41.  
  42. /**
  43. * 新增用户
  44. * @param user
  45. * @return
  46. */
  47. int insert(User user);
  48.  
  49. /**
  50. * 修改用户
  51. * @param user
  52. * @return
  53. */
  54. int update(User user);
  55. }
  1. package com.my.test;
  2.  
  3. import com.my.dao.UserMapper;
  4. import com.my.io.Resource;
  5. import com.my.pojo.User;
  6. import com.my.sqlSession.SqlSession;
  7. import com.my.sqlSession.SqlSessionFactory;
  8. import com.my.sqlSession.SqlSessionFactoryBuilder;
  9. import org.junit.Before;
  10. import org.junit.Test;
  11.  
  12. import java.io.InputStream;
  13. import java.util.List;
  14.  
  15. /**
  16. * @Description:
  17. * @Author lzh
  18. * @Date 2020/12/6 16:05
  19. */
  20. public class IMybatisTest {
  21.  
  22. private SqlSession sqlSession;
  23.  
  24. @Before
  25. public void before() throws Exception {
  26. InputStream resourceAsStream = Resource.getResourceAsStream("dataSourceConfig.xml");
  27. SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  28. SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
  29. sqlSession = sqlSessionFactory.createSqlSession();
  30. }
  31.  
  32. @Test
  33. public void test1() {
  34. User user = new User();
  35. user.setId(2L);
  36. user.setName("王五");
  37.  
  38. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  39. User user1 = mapper.findOne(user);
  40. System.out.println(user1);
  41. }
  42.  
  43. @Test
  44. public void test2() {
  45.  
  46. User user = new User();
  47. user.setId(1L);
  48. user.setName("王五");
  49. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  50.  
  51. List<User> all = mapper.findAll();
  52. for (User user1 : all) {
  53. System.out.println(user1);
  54. }
  55. }
  56.  
  57. @Test
  58. public void test3() {
  59.  
  60. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  61. User user1= mapper.findById(2L);
  62. System.out.println(user1);
  63. }
  64.  
  65. @Test
  66. public void test4() {
  67.  
  68. User user = new User();
  69. user.setId(3L);
  70. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  71.  
  72. int row = mapper.delete(user);
  73. System.out.println(row);
  74. }
  75.  
  76. @Test
  77. public void test5() {
  78.  
  79. User user = new User();
  80. user.setId(3L);
  81. user.setName("王五");
  82. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  83.  
  84. int row = mapper.update(user);
  85. System.out.println(row);
  86. }
  87.  
  88. @Test
  89. public void test6() {
  90.  
  91. User user = new User();
  92. user.setId(3L);
  93. user.setName("张三");
  94. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  95.  
  96. int row = mapper.insert(user);
  97. System.out.println(row);
  98. }
  99. }

这里就不把全部的测试结果贴出来了,贴一个看下效果就行,可以看到控制台正常输出,说明我们自己写的IMybatis没问题,可以成功执行。

总结:我们可以看到最后仍然是通过JDBC完成的数据库操作。所以到这里我们可以知道Mybatis最终仍然是调用的JDBC去操作数据库,它只不过在执行JDBC之前还多去做了这一系列解析配置文件,封装各个对象等等这些操作,Mybatis就是对JDBC的包装。

Mybatis执行流程学习之手写mybatis雏形的更多相关文章

  1. 手写MyBatis流程

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  2. Mybatis执行流程浅析(附深度文章推荐&面试题集锦)

    首先推荐一个简单的Mybatis原理视频教程,可以作为入门教程进行学习:点我 (该教程讲解的是如何手写简易版Mybatis) 执行流程的理解 理解Mybatis的简单流程后自己手写一个,可以解决百分之 ...

  3. 手写mybatis框架笔记

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  4. 浅析MyBatis(二):手写一个自己的MyBatis简单框架

    在上一篇文章中,我们由一个快速案例剖析了 MyBatis 的整体架构与整体运行流程,在本篇文章中笔者会根据 MyBatis 的运行流程手写一个自定义 MyBatis 简单框架,在实践中加深对 MyBa ...

  5. 要想精通Mybatis?从手写Mybatis框架开始吧!

    1.Mybatis组成 动态SQL Config配置 Mapper配置 2.核心源码分析 Configuration源码解析 SqlSessionFactory源码解析 SqlSession源码解析 ...

  6. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

  7. mybatis 执行流程以及初用错误总结

    mappper 配置文件  头文件: 1.   <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" &q ...

  8. Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)

    前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...

  9. Mybatis(一):手写一套持久层框架

    作者 : 潘潘 未来半年,有幸与导师们一起学习交流,趁这个机会,把所学所感记录下来. 「封面图」 自毕业以后,自己先创业后上班,浮沉了近8年,内心着实焦躁,虽一直是走科班路线,但在技术道路上却始终没静 ...

随机推荐

  1. 多个HDFS集群的fs.defaultFS配置一样,造成应用一直连接同一个集群的问题分析

    背景 应用需要对两个集群中的同一目录下的HDFS文件个数和文件总大小进行比对,在测试环境中发现,即使两边HDFS目录下的数据不一样,应用日志显示两边始终比对一致,分下下来发现,应用连的一直是同一个集群 ...

  2. Java后端使用socketio,实现小程序答题pk功能

    在使用socket.io跟前端通信过程中,出现了一系列问题,现做下记录. 一.功能需求是,在小程序端,用户可相互邀请,进入房间后进行答题PK.实现方法是,用户点击邀请好友,建立连接,查询当前是否有房间 ...

  3. ECharts的下载和安装(图文详解)

    首先搜索找到ECharts官网,点击进入. 找到下载 进入就看到第三步,就点击在线制作 点击进入之后就自己可以选择里面的形状图,就在线制作.最后生成echarts.min.js 点击下载后就会生成js ...

  4. numpy的好处

    python是很慢的,因为python在执行代码的时候会执行很多复杂的check功能,比如 b=1; a=b/0.5 这个运算看起来很简单,但是在计算机的内部.b要先从一个整数integer转化成一个 ...

  5. EF Core 三 、 骚操作 (导航属性,内存查询...)

    EF Core 高阶操作 本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解 我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择 1.EF 内存查找 wh ...

  6. java 数字和日期处理

    static int abs(int a) 返回 a 的绝对值 static long abs(long a) 返回 a 的绝对值 static float abs(float a) 返回 a 的绝对 ...

  7. 通过naa在esxi主机上找到物理磁盘的位置

    因为有一块磁盘告警,需要找到这个块磁盘.通过网络搜索就找到了这个shell脚本. 感谢 Jorluis Perales, VxRail TSE 2 shell脚本: # Script to obtai ...

  8. 简单谈谈网络抓包,特别是thrift 接口

    按照惯例先谈谈最近情况,最近不是刚好跨年吗?看到很多人都在写年度总结,所以我也在写年度总结文章(其实之前我基本没有写过的,今年有点感触,也想记录一下),结果发现写起来有点多,之前还想着元旦前发出来,结 ...

  9. 安卓手机使用Termux及搭建FTP服务器

    Termux安装配置设置参见: 国光:Termux高级终端使用配置教程 搭建FTP服务器参见: Termux安装使用FTP服务器

  10. Facetoobject_encapsulation

    面向对象程序设计思想 一.思想 处处皆对象. 当提到某一功能时,首先应该想有没有实现该功能的对象,有则调用,没有则创建类.当提到数据时,应该想到属于哪个对象. 1.求1~n的累加和 public cl ...