读取配置文件

编写Resource类,将路径转化为InputStream流。
利用SqlSesssionFactoryBuilder,来创建SqlSessionFactory对象。

  1. public class Resources {
  2. public static InputStream getResourceAsStream(String path) {
  3. return Resources.class.getClassLoader().getResourceAsStream(path);
  4. }
  5. }
  6. public class SqlSessionFactoryBuilder {
  7. // SqlSessionFactory后续创建
  8. public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
  9. return null;
  10. }
  11. }

创建Configuration对象和MapperStatement对象

需要了解两个容器中需要存放什么内容,根据内容来创建对象,Configuration要保存DataSource的信息,并且需要持有MapperStatement对象。
MapperStatement存放的是sqlMapper中一个标签,也就是select或者其它的标签。

  1. /**
  2. * mapper的每一个标签都是一个mapperStatement
  3. */
  4. public class MapperStatement {
  5. /**
  6. * id
  7. */
  8. private String id;
  9. /**
  10. * 返回类型
  11. */
  12. private String resultType;
  13. /**
  14. * 参数类型
  15. */
  16. private String paramterType;
  17. /**
  18. * sql内容
  19. */
  20. private String sql;
  21. public MapperStatement() {
  22. }
  23. public MapperStatement(String id, String resultType, String paramterType, String sql) {
  24. this.id = id;
  25. this.resultType = resultType;
  26. this.paramterType = paramterType;
  27. this.sql = sql;
  28. }
  29. public String getId() {
  30. return id;
  31. }
  32. public void setId(String id) {
  33. this.id = id;
  34. }
  35. public String getResultType() {
  36. return resultType;
  37. }
  38. public void setResultType(String resultType) {
  39. this.resultType = resultType;
  40. }
  41. public String getParamterType() {
  42. return paramterType;
  43. }
  44. public void setParamterType(String paramterType) {
  45. this.paramterType = paramterType;
  46. }
  47. public String getSql() {
  48. return sql;
  49. }
  50. public void setSql(String sql) {
  51. this.sql = sql;
  52. }
  53. }
  54. public class Configuration {
  55. /**
  56. * 数据库的连接信息
  57. */
  58. private DataSource dataSource;
  59. /**
  60. * key是什么呢? key要能唯一标识MapperStatement,前面说到,使用namespace.id可以唯一标识一个sql映射
  61. */
  62. private Map<String, MapperStatement> mapperStatementMap = new HashMap<>();
  63. public DataSource getDataSource() {
  64. return dataSource;
  65. }
  66. public void setDataSource(DataSource dataSource) {
  67. this.dataSource = dataSource;
  68. }
  69. public Map<String, MapperStatement> getMapperStatementMap() {
  70. return mapperStatementMap;
  71. }
  72. public void setMapperStatementMap(Map<String, MapperStatement> mapperStatementMap) {
  73. this.mapperStatementMap = mapperStatementMap;
  74. }
  75. }

dom4j解析配置文件进行封装

这个步骤非常重要,要解析Config配置文件和sql配置文件,解析应该放在 SqlSessionFactoryBuilder 类中,层层向下传递。

  1. 整个过程用到的pom依赖如下:
  2. <dependencies>
  3. <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  4. <dependency>
  5. <groupId>mysql</groupId>
  6. <artifactId>mysql-connector-java</artifactId>
  7. <version>8.0.22</version>
  8. </dependency>
  9. <dependency>
  10. <groupId>c3p0</groupId>
  11. <artifactId>c3p0</artifactId>
  12. <version>0.9.1.2</version>
  13. </dependency>
  14. <dependency>
  15. <groupId>log4j</groupId>
  16. <artifactId>log4j</artifactId>
  17. <version>1.2.12</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>junit</groupId>
  21. <artifactId>junit</artifactId>
  22. <version>4.10</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>dom4j</groupId>
  26. <artifactId>dom4j</artifactId>
  27. <version>1.6.1</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>jaxen</groupId>
  31. <artifactId>jaxen</artifactId>
  32. <version>1.1.6</version>
  33. </dependency>
  34. </dependencies>
  35. public class SqlSessionFactoryBuilder {
  36. public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
  37. // 读取配置文件
  38. XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  39. Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);
  40. return null;
  41. }
  42. }
  43. 工具类进行解析:
  44. public class XMLConfigBuilder {
  45. private Configuration configuration;
  46. public XMLConfigBuilder() {
  47. this.configuration = new Configuration();
  48. }
  49. /**
  50. * 封装Configuration
  51. * @param inputStream
  52. * @return
  53. * @throws DocumentException
  54. * @throws PropertyVetoException
  55. */
  56. public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException {
  57. Document document = new SAXReader().read(inputStream);
  58. Element rootElement = document.getRootElement();
  59. List<Element> list = rootElement.selectNodes("//property");
  60. Properties properties = new Properties();
  61. for (Element element : list) {
  62. properties.put(element.attributeValue("name"), element.attributeValue("value"));
  63. }
  64. // 获取数据库连接池,使用c3p0
  65. ComboPooledDataSource comboPooledDataSource = getComboPooledDataSource(properties);
  66. configuration.setDataSource(comboPooledDataSource);
  67. // 封装mapperStatement
  68. XMLMapperConfigBuilder xmlMapperConfigBuilder = new XMLMapperConfigBuilder(configuration);
  69. List<Element> mappers = document.selectNodes("//mapper");
  70. for (Element mapper : mappers) {
  71. String resource = mapper.attributeValue("resource");
  72. xmlMapperConfigBuilder.parse(Resources.getResourceAsStream(resource));
  73. }
  74. return configuration;
  75. }
  76. /**
  77. * 根据配置信息创建c3p0连接池
  78. * @param properties
  79. * @return
  80. * @throws PropertyVetoException
  81. */
  82. private ComboPooledDataSource getComboPooledDataSource(Properties properties) throws PropertyVetoException {
  83. ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
  84. comboPooledDataSource.setDriverClass(properties.getProperty("jdbcDriver"));
  85. comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
  86. comboPooledDataSource.setUser(properties.getProperty("jdbcUser"));
  87. comboPooledDataSource.setPassword(properties.getProperty("jdbcPassword"));
  88. return comboPooledDataSource;
  89. }
  90. }
  91. public class XMLMapperConfigBuilder {
  92. private Configuration configuration;
  93. public XMLMapperConfigBuilder(Configuration configuration) {
  94. this.configuration = configuration;
  95. }
  96. public void parse(InputStream inputStream) throws DocumentException {
  97. Document document = new SAXReader().read(inputStream);
  98. String nameSpaceURI = document.getRootElement().attributeValue("namespace");
  99. List<Element> selectNodes = document.selectNodes("//select");
  100. for (Element selectNode : selectNodes) {
  101. String id = selectNode.attributeValue("id");
  102. MapperStatement mapperStatement = new MapperStatement(id, selectNode.attributeValue("resultType"), selectNode.attributeValue("paramterType"), selectNode.getTextTrim());
  103. configuration.getMapperStatementMap().put(nameSpaceURI + "." + id, mapperStatement);
  104. }
  105. }
  106. }

SqlSessionFactory

SOLID原则,面向接口编程,SqlSessionFactory的作用是生产SqlSession会话对象,如下:

  1. /**
  2. * 工厂类,用于创建会话对象 SqlSession
  3. */
  4. public interface SqlSessionFactory {
  5. public SqlSession openSession();
  6. }
  7. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  8. private Configuration configuration;
  9. public DefaultSqlSessionFactory(Configuration configuration) {
  10. this.configuration = configuration;
  11. }
  12. @Override
  13. public SqlSession openSession() {
  14. return new DefaultSqlSession(configuration);
  15. }
  16. }
  17. public class SqlSessionFactoryBuilder {
  18. public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
  19. // 读取配置文件
  20. XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  21. Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);
  22. // 创建SqlSessionFactory
  23. SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
  24. return sqlSessionFactory;
  25. }
  26. }

至此SqlSessionFactoryBuilder完成。

SqlSession

同样,面向接口编程,SqlSession要做的是就是去定义数据库的执行方法

  1. public interface SqlSession {
  2. <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException;
  3. <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;
  4. }
  5. public class DefaultSqlSession implements SqlSession {
  6. private Configuration configuration;
  7. public DefaultSqlSession(Configuration configuration) {
  8. this.configuration = configuration;
  9. }
  10. @Override
  11. public <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException {
  12. SimpleExecutor simpleExecutor = new SimpleExecutor();
  13. List<E> query = simpleExecutor.query(configuration, configuration.getMapperStatementMap().get(statementId), params);
  14. return query;
  15. }
  16. @Override
  17. public <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {
  18. List<Object> objects = selectAll(statementId, params);
  19. if (objects.size() == 1) {
  20. return (T)objects.get(0);
  21. } else {
  22. throw new RuntimeException("查询结果过多或者查询结果为空");
  23. }
  24. }
  25. }

Executor执行

执行器,进行具体的执行操作,从连接 - 预处理sql - 执行sql - 封装结果集都在这一步

  1. public interface MyExecutor {
  2. <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;
  3. }
  4. package com.wangzhi.config.sqlsession;
  5. import com.wangzhi.config.pojo.BoundSql;
  6. import com.wangzhi.config.pojo.Configuration;
  7. import com.wangzhi.config.pojo.MapperStatement;
  8. import com.wangzhi.config.utils.GenericTokenParser;
  9. import com.wangzhi.config.utils.ParameterMapping;
  10. import com.wangzhi.config.utils.ParameterMappingTokenHandler;
  11. import java.beans.IntrospectionException;
  12. import java.beans.PropertyDescriptor;
  13. import java.lang.reflect.Field;
  14. import java.lang.reflect.InvocationTargetException;
  15. import java.lang.reflect.Method;
  16. import java.sql.*;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. public class SimpleExecutor implements MyExecutor {
  20. @Override
  21. public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {
  22. // 封装数据库的操作步骤,获取数据库连接
  23. Connection connection = configuration.getDataSource().getConnection();
  24. // 替换占位符(这个步骤是重点,sql转换过程)
  25. BoundSql boundSql = getBoundSql(mapperStatement.getSql());
  26. // 预处理sql
  27. PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
  28. // 设置参数开始
  29. // 利用反射来获取,先要获取类的字节码文件
  30. Class<?> parameterClazz = getClassType(mapperStatement.getParamterType());
  31. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  32. for (int i = 0; i < parameterMappings.size(); i++) {
  33. // 需要获取的字段
  34. ParameterMapping parameterMapping = parameterMappings.get(i);
  35. Field declaredField = parameterClazz.getDeclaredField(parameterMapping.getContent());
  36. declaredField.setAccessible(true);
  37. Object obj = declaredField.get(params[0]);
  38. preparedStatement.setObject(i + 1, obj);
  39. }
  40. // 设置参数结束
  41. // 执行sql
  42. ResultSet resultSet = preparedStatement.executeQuery();
  43. // 处理结果集
  44. Class<?> resultClazzType = getClassType(mapperStatement.getResultType());
  45. List<Object> resultList = new ArrayList<>();
  46. while (resultSet.next()) {
  47. Object obj = resultClazzType.getDeclaredConstructor().newInstance();
  48. ResultSetMetaData metaData = resultSet.getMetaData();
  49. for (int i = 1; i <= metaData.getColumnCount(); i++) {
  50. // 字段名 metaDate的下标从1开始
  51. String columnName = metaData.getColumnName(i);
  52. // 字段值
  53. Object columnValue = resultSet.getObject(columnName);
  54. // 用来设置值,内省,用来获取方法的读写权限
  55. PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClazzType);
  56. Method writeMethod = propertyDescriptor.getWriteMethod();
  57. writeMethod.invoke(obj, columnValue);
  58. }
  59. resultList.add(obj);
  60. }
  61. return (List<E>) resultList;
  62. }
  63. private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
  64. if (paramterType != null) {
  65. Class<?> aClass = Class.forName(paramterType);
  66. return aClass;
  67. }
  68. return null;
  69. }
  70. /**
  71. * 处理sql,替换占位符,接触的工具类属于Mybatis源码的内容
  72. * @param sql 替换之前的sql
  73. * @return
  74. */
  75. private BoundSql getBoundSql(String sql) {
  76. // 标记处理器,配置标记解析器来完成对占位符的解析处理工作
  77. ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
  78. // 标记解析器
  79. GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
  80. // 解析sql,返回替换占位符后的sql,也就是将占位符替换为 ?
  81. String parse = genericTokenParser.parse(sql);
  82. // 保存了占位符里面的内容,针对于示例中selectOne的id和name
  83. List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
  84. BoundSql boundSql = new BoundSql(parse, parameterMappings);
  85. return boundSql;
  86. }
  87. }

上面用到了工具类,可以再具体的代码里面找到,放在码云中。
到这基本就结束了,目前存在的问题就是在使用方,还有硬编码的实现以及代码重复性严重。为了模仿mybatis,需要使用动态代理来进行方法的执行。


优化:

  1. public interface UserMapper {
  2. List<User> findAll();
  3. User findByCondition(User user);
  4. }
  5. sqlSession中添加getMapper方法,来进行动态代理执行
  6. public interface SqlSession {
  7. <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException;
  8. <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;
  9. <T> T getMapper(Class<T> mapperClass);
  10. }
  11. public class DefaultSqlSession implements SqlSession {
  12. private Configuration configuration;
  13. public DefaultSqlSession(Configuration configuration) {
  14. this.configuration = configuration;
  15. }
  16. @Override
  17. public <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException {
  18. SimpleExecutor simpleExecutor = new SimpleExecutor();
  19. List<E> query = simpleExecutor.query(configuration, configuration.getMapperStatementMap().get(statementId), params);
  20. return query;
  21. }
  22. @Override
  23. public <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {
  24. List<Object> objects = selectAll(statementId, params);
  25. if (objects.size() == 1) {
  26. return (T)objects.get(0);
  27. } else {
  28. throw new RuntimeException("查询结果过多或者查询结果为空");
  29. }
  30. }
  31. /**
  32. * 动态代理生产对象,在使用该对象调用方法的时候,全部执行改方法里面的invoke方法
  33. * @param mapperClass
  34. * @param <T>
  35. * @return
  36. */
  37. @Override
  38. public <T> T getMapper(Class<T> mapperClass) {
  39. Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
  40. // proxy 当前代理对象的引用 method 当前被调用方法的引用 args就是传递的参数
  41. @Override
  42. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  43. // invoke方法中来调用具体的方法,要做的事有下面两件
  44. // 封装参数开始
  45. // 获取方法名
  46. String methodName = method.getName();
  47. // 获取全限定类名
  48. String className = method.getDeclaringClass().getName();
  49. // 组合成为statementId
  50. // 封装参数结束
  51. // 确认调用的方法
  52. Type genericReturnType = method.getGenericReturnType();
  53. // 判断是否进行了参数泛型化,如果存在泛型就是集合,调用List方法
  54. if (genericReturnType instanceof ParameterizedType) {
  55. return selectAll(className + "." + methodName, args);
  56. }
  57. return selectOne(className + "." + methodName, args);
  58. }
  59. });
  60. return (T) proxy;
  61. }
  62. }
  63. 优化Mapper.xml,也就是sql配置,标准化并且保证statementId的唯一性
  64. <mapper namespace="com.wangzhi.dao.UserMapper">
  65. <select id="findAll" resultType="com.wangzhi.pojo.User">
  66. select * from user
  67. </select>
  68. <select id="findByCondition" resultType="com.wangzhi.pojo.User" paramterType="com.wangzhi.pojo.User">
  69. SELECT * FROM user
  70. WHERE id = #{id} AND name = #{name}
  71. </select>
  72. </mapper>
  73. 至此,如果安装了mybatis插件就可以看到,在xml文件中提示可以跳转到interface
  74. 测试类的优化和使用如下:
  75. @Test
  76. public void testResources() throws PropertyVetoException, DocumentException, IllegalAccessException, InvocationTargetException, IntrospectionException, InstantiationException, NoSuchFieldException, SQLException, NoSuchMethodException, ClassNotFoundException {
  77. InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapperConfig.xml");
  78. SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  79. SqlSession sqlSession = sqlSessionFactoryBuilder.build(resourceAsStream).openSession();
  80. // 这里动态代理生成对象
  81. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  82. // 调用方法实际上调用的是getMapper中的invoke方法
  83. List<User> all = mapper.findAll();
  84. all.forEach(System.out::println);
  85. User user = new User();
  86. user.setId(1);
  87. user.setName("王智");
  88. User byCondition = mapper.findByCondition(user);
  89. System.out.println(byCondition);
  90. }

整个代码放在码云,IPersistence为编写的框架类,后面有_test的为测试类。


整个过程学到了什么,回顾了什么?

  • JDBC的整个操作流程,回顾细化。 从加载驱动 - 建立连接 - 预处理sql - 执行sql - 处理结果集的整个流程
  • dom4j的文档解析
  • 反射技术
  • 工厂模式
  • SOLID原则,面向接口编程
  • 动态代理
  • mybatis的一点点原理