读取配置文件
编写Resource类,将路径转化为InputStream流。
利用SqlSesssionFactoryBuilder,来创建SqlSessionFactory对象。
public class Resources {public static InputStream getResourceAsStream(String path) {return Resources.class.getClassLoader().getResourceAsStream(path);}}public class SqlSessionFactoryBuilder {// SqlSessionFactory后续创建public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {return null;}}
创建Configuration对象和MapperStatement对象
需要了解两个容器中需要存放什么内容,根据内容来创建对象,Configuration要保存DataSource的信息,并且需要持有MapperStatement对象。
MapperStatement存放的是sqlMapper中一个标签,也就是select或者其它的标签。
/*** mapper的每一个标签都是一个mapperStatement*/public class MapperStatement {/*** id*/private String id;/*** 返回类型*/private String resultType;/*** 参数类型*/private String paramterType;/*** sql内容*/private String sql;public MapperStatement() {}public MapperStatement(String id, String resultType, String paramterType, String sql) {this.id = id;this.resultType = resultType;this.paramterType = paramterType;this.sql = sql;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public String getParamterType() {return paramterType;}public void setParamterType(String paramterType) {this.paramterType = paramterType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}}public class Configuration {/*** 数据库的连接信息*/private DataSource dataSource;/*** key是什么呢? key要能唯一标识MapperStatement,前面说到,使用namespace.id可以唯一标识一个sql映射*/private Map<String, MapperStatement> mapperStatementMap = new HashMap<>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MapperStatement> getMapperStatementMap() {return mapperStatementMap;}public void setMapperStatementMap(Map<String, MapperStatement> mapperStatementMap) {this.mapperStatementMap = mapperStatementMap;}}
dom4j解析配置文件进行封装
这个步骤非常重要,要解析Config配置文件和sql配置文件,解析应该放在 SqlSessionFactoryBuilder 类中,层层向下传递。
整个过程用到的pom依赖如下:<dependencies><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.22</version></dependency><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency></dependencies>public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {// 读取配置文件XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);return null;}}工具类进行解析:public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}/*** 封装Configuration* @param inputStream* @return* @throws DocumentException* @throws PropertyVetoException*/public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : list) {properties.put(element.attributeValue("name"), element.attributeValue("value"));}// 获取数据库连接池,使用c3p0ComboPooledDataSource comboPooledDataSource = getComboPooledDataSource(properties);configuration.setDataSource(comboPooledDataSource);// 封装mapperStatementXMLMapperConfigBuilder xmlMapperConfigBuilder = new XMLMapperConfigBuilder(configuration);List<Element> mappers = document.selectNodes("//mapper");for (Element mapper : mappers) {String resource = mapper.attributeValue("resource");xmlMapperConfigBuilder.parse(Resources.getResourceAsStream(resource));}return configuration;}/*** 根据配置信息创建c3p0连接池* @param properties* @return* @throws PropertyVetoException*/private ComboPooledDataSource getComboPooledDataSource(Properties properties) throws PropertyVetoException {ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(properties.getProperty("jdbcDriver"));comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));comboPooledDataSource.setUser(properties.getProperty("jdbcUser"));comboPooledDataSource.setPassword(properties.getProperty("jdbcPassword"));return comboPooledDataSource;}}public class XMLMapperConfigBuilder {private Configuration configuration;public XMLMapperConfigBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);String nameSpaceURI = document.getRootElement().attributeValue("namespace");List<Element> selectNodes = document.selectNodes("//select");for (Element selectNode : selectNodes) {String id = selectNode.attributeValue("id");MapperStatement mapperStatement = new MapperStatement(id, selectNode.attributeValue("resultType"), selectNode.attributeValue("paramterType"), selectNode.getTextTrim());configuration.getMapperStatementMap().put(nameSpaceURI + "." + id, mapperStatement);}}}
SqlSessionFactory
SOLID原则,面向接口编程,SqlSessionFactory的作用是生产SqlSession会话对象,如下:
/*** 工厂类,用于创建会话对象 SqlSession*/public interface SqlSessionFactory {public SqlSession openSession();}public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);}}public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {// 读取配置文件XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);// 创建SqlSessionFactorySqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);return sqlSessionFactory;}}
SqlSession
同样,面向接口编程,SqlSession要做的是就是去定义数据库的执行方法
public interface SqlSession {<E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException;<T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;}public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException {SimpleExecutor simpleExecutor = new SimpleExecutor();List<E> query = simpleExecutor.query(configuration, configuration.getMapperStatementMap().get(statementId), params);return query;}@Overridepublic <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {List<Object> objects = selectAll(statementId, params);if (objects.size() == 1) {return (T)objects.get(0);} else {throw new RuntimeException("查询结果过多或者查询结果为空");}}}
Executor执行
执行器,进行具体的执行操作,从连接 - 预处理sql - 执行sql - 封装结果集都在这一步
public interface MyExecutor {<E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;}package com.wangzhi.config.sqlsession;import com.wangzhi.config.pojo.BoundSql;import com.wangzhi.config.pojo.Configuration;import com.wangzhi.config.pojo.MapperStatement;import com.wangzhi.config.utils.GenericTokenParser;import com.wangzhi.config.utils.ParameterMapping;import com.wangzhi.config.utils.ParameterMappingTokenHandler;import java.beans.IntrospectionException;import java.beans.PropertyDescriptor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.sql.*;import java.util.ArrayList;import java.util.List;public class SimpleExecutor implements MyExecutor {@Overridepublic <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {// 封装数据库的操作步骤,获取数据库连接Connection connection = configuration.getDataSource().getConnection();// 替换占位符(这个步骤是重点,sql转换过程)BoundSql boundSql = getBoundSql(mapperStatement.getSql());// 预处理sqlPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 设置参数开始// 利用反射来获取,先要获取类的字节码文件Class<?> parameterClazz = getClassType(mapperStatement.getParamterType());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();for (int i = 0; i < parameterMappings.size(); i++) {// 需要获取的字段ParameterMapping parameterMapping = parameterMappings.get(i);Field declaredField = parameterClazz.getDeclaredField(parameterMapping.getContent());declaredField.setAccessible(true);Object obj = declaredField.get(params[0]);preparedStatement.setObject(i + 1, obj);}// 设置参数结束// 执行sqlResultSet resultSet = preparedStatement.executeQuery();// 处理结果集Class<?> resultClazzType = getClassType(mapperStatement.getResultType());List<Object> resultList = new ArrayList<>();while (resultSet.next()) {Object obj = resultClazzType.getDeclaredConstructor().newInstance();ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); i++) {// 字段名 metaDate的下标从1开始String columnName = metaData.getColumnName(i);// 字段值Object columnValue = resultSet.getObject(columnName);// 用来设置值,内省,用来获取方法的读写权限PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClazzType);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(obj, columnValue);}resultList.add(obj);}return (List<E>) resultList;}private Class<?> getClassType(String paramterType) throws ClassNotFoundException {if (paramterType != null) {Class<?> aClass = Class.forName(paramterType);return aClass;}return null;}/*** 处理sql,替换占位符,接触的工具类属于Mybatis源码的内容* @param sql 替换之前的sql* @return*/private BoundSql getBoundSql(String sql) {// 标记处理器,配置标记解析器来完成对占位符的解析处理工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 标记解析器GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);// 解析sql,返回替换占位符后的sql,也就是将占位符替换为 ?String parse = genericTokenParser.parse(sql);// 保存了占位符里面的内容,针对于示例中selectOne的id和nameList<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parse, parameterMappings);return boundSql;}}
上面用到了工具类,可以再具体的代码里面找到,放在码云中。
到这基本就结束了,目前存在的问题就是在使用方,还有硬编码的实现以及代码重复性严重。为了模仿mybatis,需要使用动态代理来进行方法的执行。
优化:
public interface UserMapper {List<User> findAll();User findByCondition(User user);}在sqlSession中添加getMapper方法,来进行动态代理执行public interface SqlSession {<E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException;<T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException;<T> T getMapper(Class<T> mapperClass);}public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <E> List<E> selectAll(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, IntrospectionException {SimpleExecutor simpleExecutor = new SimpleExecutor();List<E> query = simpleExecutor.query(configuration, configuration.getMapperStatementMap().get(statementId), params);return query;}@Overridepublic <T> T selectOne(String statementId, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, IntrospectionException {List<Object> objects = selectAll(statementId, params);if (objects.size() == 1) {return (T)objects.get(0);} else {throw new RuntimeException("查询结果过多或者查询结果为空");}}/*** 动态代理生产对象,在使用该对象调用方法的时候,全部执行改方法里面的invoke方法* @param mapperClass* @param <T>* @return*/@Overridepublic <T> T getMapper(Class<T> mapperClass) {Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {// proxy 当前代理对象的引用 method 当前被调用方法的引用 args就是传递的参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// invoke方法中来调用具体的方法,要做的事有下面两件// 封装参数开始// 获取方法名String methodName = method.getName();// 获取全限定类名String className = method.getDeclaringClass().getName();// 组合成为statementId// 封装参数结束// 确认调用的方法Type genericReturnType = method.getGenericReturnType();// 判断是否进行了参数泛型化,如果存在泛型就是集合,调用List方法if (genericReturnType instanceof ParameterizedType) {return selectAll(className + "." + methodName, args);}return selectOne(className + "." + methodName, args);}});return (T) proxy;}}优化Mapper.xml,也就是sql配置,标准化并且保证statementId的唯一性<mapper namespace="com.wangzhi.dao.UserMapper"><select id="findAll" resultType="com.wangzhi.pojo.User">select * from user</select><select id="findByCondition" resultType="com.wangzhi.pojo.User" paramterType="com.wangzhi.pojo.User">SELECT * FROM userWHERE id = #{id} AND name = #{name}</select></mapper>至此,如果安装了mybatis插件就可以看到,在xml文件中提示可以跳转到interface测试类的优化和使用如下:@Testpublic void testResources() throws PropertyVetoException, DocumentException, IllegalAccessException, InvocationTargetException, IntrospectionException, InstantiationException, NoSuchFieldException, SQLException, NoSuchMethodException, ClassNotFoundException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapperConfig.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSession sqlSession = sqlSessionFactoryBuilder.build(resourceAsStream).openSession();// 这里动态代理生成对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 调用方法实际上调用的是getMapper中的invoke方法List<User> all = mapper.findAll();all.forEach(System.out::println);User user = new User();user.setId(1);user.setName("王智");User byCondition = mapper.findByCondition(user);System.out.println(byCondition);}
整个代码放在码云,IPersistence为编写的框架类,后面有_test的为测试类。
整个过程学到了什么,回顾了什么?
- JDBC的整个操作流程,回顾细化。 从加载驱动 - 建立连接 - 预处理sql - 执行sql - 处理结果集的整个流程
- dom4j的文档解析
- 反射技术
- 工厂模式
- SOLID原则,面向接口编程
- 动态代理
- mybatis的一点点原理
