笔记1:简易的持久层框架
思维逻辑:
-
前提为创建好的xml配置文件:sqlMapConfig.xml 以及IUserDao.java
<configuration> <dataSource> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///lg"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </dataSource> <!-- 指定Mapper路径,方便读取 --> <mapper resource="UserMapper.xml"></mapper> </configuration>
public interface IUserDao { List<User> findAll(); User findByCondition(User user); void insert(User user); void update(User user); void deleteById(Integer id); }
pom依赖:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.17</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>
-
开始编写简易的Mybatis, 首先创建SqlSessionFactoryBuilder.java 用于解析xml配置和jdbc操作
public class Resources { public static InputStream getResourceAsStream(String path) { InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path); return resourceAsStream; } }
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException { XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(in); return new DefaultSqlSessionFactory(configuration); } }
-
XmlConfigBuilder.java解析xml配置,将解析出来的参数存入实体类Configuration.java及MappedStatement.java
public class Configuration { private DataSource dataSource; /** * key: statementId -> namespace.id */ Map<String, MappedStatement> mappedStatementMap = new HashMap<>(16); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Map<String, MappedStatement> getMappedStatementMap() { return mappedStatementMap; } public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) { this.mappedStatementMap = mappedStatementMap; } }
public class MappedStatement { private String id; private String resultType; private String parameterType; private String 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 getParameterType() { return parameterType; } public void setParameterType(String parameterType) { this.parameterType = parameterType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } }
public class XmlConfigBuilder { private final Configuration configuration; public XmlConfigBuilder() { this.configuration = new Configuration(); } public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException { Document document = new SAXReader().read(in); Element rootElement = document.getRootElement(); List<Element> list = rootElement.selectNodes("//property"); Properties properties = new Properties(); list.forEach(element -> { String name = element.attributeValue("name"); String value = element.attributeValue("value"); properties.setProperty(name, value); }); ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(properties.getProperty("driverClass")); comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); comboPooledDataSource.setUser(properties.getProperty("username")); comboPooledDataSource.setPassword(properties.getProperty("password")); configuration.setDataSource(comboPooledDataSource); List<Element> mapperList = rootElement.selectNodes("//mapper"); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource"); InputStream mapperIn = Resources.getResourceAsStream(mapperPath); XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration); xmlMapperBuilder.parse(mapperIn); } return configuration; } }
public class XmlMapperBuilder { private final Configuration configuration; public XmlMapperBuilder(Configuration configuration) { this.configuration = configuration; } public void parse(InputStream in) throws DocumentException { Document document = new SAXReader().read(in); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> list = new ArrayList<>(); List<Element> selectList = rootElement.selectNodes("//select"); List<Element> insertList = rootElement.selectNodes("//insert"); List<Element> updateList = rootElement.selectNodes("//update"); List<Element> deleteList = rootElement.selectNodes("//delete"); if(selectList != null) { list.addAll(selectList); } if(insertList != null) { list.addAll(insertList); } if(updateList != null) { list.addAll(updateList); } if(deleteList != null) { list.addAll(deleteList); } list.forEach(element -> { String id = element.attributeValue("id"); String resultType = element.attributeValue("resultType"); String parameterType = element.attributeValue("parameterType"); String sql = element.getTextTrim(); MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParameterType(parameterType); mappedStatement.setResultType(resultType); mappedStatement.setSql(sql); configuration.getMappedStatementMap().put(namespace+"."+id, mappedStatement); }); } }
-
解析好xml配置后,将参数传递并构造sql
public interface SqlSessionFactory { public SqlSession openSession(); }
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); }
public interface SqlSession { <E> List<E> selectList(String statementId, Object... params) throws Exception; <T> T selectOne(String statementId, Object... params) throws Exception; <T> T getMapper(Class<?> mapperClass); Object insert(String statementId, Object... params) throws Exception; Object update(String statementId, Object... params) throws Exception; Object delete(String statementId, Object... params) throws Exception; }
-
然后在DefaultSqlSession里进行jdbc方法的实现,这里的getMapper为动态代理,用于IUserDao.java的调用
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } @Override public <E> List<E> selectList(String statementId, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List<Object> queryList = simpleExecutor.query(configuration, mappedStatement, params); return (List<E>) queryList; } @Override public <T> T selectOne(String statementId, Object... params) throws Exception { List<Object> objects = selectList(statementId, params); if(objects == null || objects.size() > 1) { throw new RuntimeException("结果为空或结果过多"); } else { return (T) objects.get(0); } } @Override public Object insert(String statementId, Object... params) throws Exception { return update(statementId, params); } @Override public Object update(String statementId, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return simpleExecutor.update(configuration, mappedStatement, params); } @Override public Object delete(String statementId, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); return simpleExecutor.delete(configuration, mappedStatement, params); } @Override public <T> T getMapper(Class<?> mapperClass) { //使用JDK动态代理来为Dao接口生成代理对象,并返回 Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{ mapperClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名 // 方法名:findAll String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className + "." + methodName; // 准备参数2:params:args // 获取被调用方法的返回值类型 Type genericReturnType = method.getGenericReturnType(); MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); String sql = mappedStatement.getSql(); if (genericReturnType instanceof ParameterizedType) { return selectList(statementId, args); } else if(sql.startsWith("update")) { return update(statementId, args); } else if(sql.startsWith("insert")) { return insert(statementId, args); } else if(sql.startsWith("delete")) { return delete(statementId, args); } else { return selectOne(statementId, args); } } }); return (T) proxyInstance; } }
-
对应数据库操作的实现
public interface Executor { <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception; Integer update(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception; Integer delete(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception; }
public class BoundSql { private String sqlText; private List<ParameterMapping> parameterMappings = new ArrayList<>(); public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) { this.sqlText = sqlText; this.parameterMappings = parameterMappings; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public void setParameterMappings(List<ParameterMapping> parameterMappings) { this.parameterMappings = parameterMappings; } }
public class SimpleExecutor implements Executor { @Override public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { //创建链接 Connection connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //设置参数 String parameterType = mappedStatement.getParameterType(); Class<?> parameterTypeClass = getClassType(parameterType); if(parameterTypeClass != null) { List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); String content = parameterMapping.getContent(); Field declaredField = parameterTypeClass.getDeclaredField(content); declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i + 1, o); } } //执行 ResultSet resultSet = preparedStatement.executeQuery(); //封装返回结果 List<Object> resultList = new ArrayList<>(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); Object o; ResultSetMetaData metaData = resultSet.getMetaData(); if(resultTypeClass != null) { while (resultSet.next()) { o = resultTypeClass.newInstance(); for (int i = 1; i < metaData.getColumnCount(); i++) { String columnName = metaData.getColumnName(i); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); Object value = resultSet.getObject(columnName); writeMethod.invoke(o, value); } resultList.add(o); } } return (List<E>) resultList; } @Override public Integer update(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception { //创建链接 Connection connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //设置参数 String parameterType = mappedStatement.getParameterType(); Class<?> parameterTypeClass = getClassType(parameterType); if(parameterTypeClass != null) { List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); ParameterMapping parameterMapping; for (int i = 0; i < parameterMappings.size(); i++) { parameterMapping = parameterMappings.get(i); String content = parameterMapping.getContent(); Field declaredField = parameterTypeClass.getDeclaredField(content); declaredField.setAccessible(true); Object value = declaredField.get(params[0]); preparedStatement.setObject(i + 1, value); } return preparedStatement.executeUpdate(); } else { throw new RuntimeException(mappedStatement.getId() + ": parameterType未定义"); } } @Override public Integer delete(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception { //创建链接 Connection connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); preparedStatement.setObject(1, params[0]); return preparedStatement.executeUpdate(); } private Class<?> getClassType(String parameterType) throws ClassNotFoundException { if(!StringUtils.isNullOrEmpty(parameterType)) { return Class.forName(parameterType); } return null; } private BoundSql getBoundSql(String sql) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", handler); String sqlOut = genericTokenParser.parse(sql); List<ParameterMapping> parameterMappings = handler.getParameterMappings(); return new BoundSql(sqlOut, parameterMappings); } }
其中GenericTokenParser为Mybatis的源码里的方法。
-
测试用例
public class IPersistenceTest { @Test public void test() throws Exception { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setId(3); user.setUsername("Jack Ma"); user.setPassword("123"); user.setBirthday("2020-12-11"); IUserDao userDao = sqlSession.getMapper(IUserDao.class); //insert userDao.insert(user); //update user.setPassword("1234"); userDao.update(user); //delete userDao.deleteById(3); } }
P.S. GenericTokenParser工具:
链接:https://pan.baidu.com/s/1Drec7juYHD1-Uka-v5vQWw
提取码:oukl