Handwritten mybatis-2 source code parsing
Article directory
- Handwritten mybatis-2 source code parsing
- 0 origin
- 1 mybatis core process
- 2 Source Code Analysis
- 2.1 SqlSessionFactoryBuilder
- 2.2 XMLConfigBuilder
- 2.3 Configuration
- 2.4 SqlSessionFactory
- 2.5 SqlSession
- 2.6 Executor & SimpleExecutor
- 2.7 DefaultSqlSession
- 2.8 MapperProxy
- 2.9 dao Implementation
- 2.10 SimpleStatementHandler
- 3 expansion
0 origin
This article follows above. Handwritten mybatis - 1 Overview & code The purpose of this article is to explain the basic composition and design idea of the code. The code in this article is based on my handwritten mybatis. The package structure and class name are very similar to mybatis, which is equivalent to a simplified version of mybatis.
1 mybatis core process
mybatis core business processes are actually quite simple 1 parse mybatis system configuration file and encapsulate it as Configuration 2 parse mapper.xml and encapsulate it as MapperStatement for later use 3 Specific business code, calling dao's proxy class Mapping and Processing of 4 Parameters 5 Dynamic generation of executed sql statements based on input parameters and apperStatement 6. Processing result set mapping through resulthandle and returning result set
2 Source Code Analysis
//Read mybatis master configuration file InputStream is = Resources.getResourceAsStream("MybatisConfig.xml"); //Create SqlSession FactoryBuilder SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //Create factories through Builder Design Patterns SqlSessionFactory factory = builder.build(is); //Creating sqlSession through Factory Mode SqlSession sqlSession = factory.openSession(); //Getting the corresponding mapper through dynamic proxy UserMapper mapper = sqlSession.getMapper(UserMapper.class); //Calling method List<User> userList = mapper.getUserList(); //Traversing print result sets for (User user : userList) { System.out.println(user); }
2.1 SqlSessionFactoryBuilder
Responsible for loading and parsing configuration files and returning to SqlSessionFactory
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream inputStream) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream); return build(parser.parse()); } catch (Exception e) { e.printStackTrace(); } return null; } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
2.2 XMLConfigBuilder
Specific parsing logic parses the input stream into a Configuration for later use
public class XMLConfigBuilder { private InputStream inputStream; public XMLConfigBuilder(InputStream inputStream) { this.inputStream = inputStream; } public Configuration parse() { return loadXmlConfiguration(inputStream); } /** * Read the main configuration file * * @param inputStream * @return */ public static Configuration loadXmlConfiguration(InputStream inputStream) { //Parsing the main configuration file using dom4j and xpath SAXReader reader = new SAXReader(); Configuration config = new Configuration(); try { //Get the document object by streaming the main configuration file binary Document document = reader.read(inputStream); //Get the property node set (the four parameters used to generate the connection object) List<Element> list = document.selectNodes("//property"); for (Element element : list) { String name = element.attributeValue("name"); String value = element.attributeValue("value"); if (name.equals("username")) config.setUsername(value); if (name.equals("driver")) config.setDriver(value); if (name.equals("password")) config.setPassword(value); if (name.equals("url")) config.setUrl(value); } //Read all mapper mapping files under the main configuration file List<Element> listMapper = document.selectNodes("//mapper"); for (Element element : listMapper) { //Traverse through each mapper tag to get the resource attribute (the full class name of the mapper mapping file) String resource = element.attributeValue("resource"); //Parsing mapper mapping files using dom4j and xpath Map<String, MappedStatement> loadMapper = loadMapper(resource, config); config.setMappedStatements(loadMapper); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(); } return config; } /** * Parsing mapper mapping files using dom4j and xpath * * @param resource * @return */ private static Map<String, MappedStatement> loadMapper(String resource, Configuration config) { Map<String, MappedStatement> mappers = new HashMap<String, MappedStatement>(); //Converting configuration files to binary streams using class loaders InputStream inputStream = Resources.getResourceAsStream(resource); SAXReader reader = new SAXReader(); try { Document document = reader.read(inputStream); //Get the root node object Element rootElement = document.getRootElement(); //Get the namespace attribute value of the root node String namespace = rootElement.attributeValue("namespace"); //Get all select nodes under the root node List<Element> selectNodes = rootElement.selectNodes("//select"); //Traverse to get the id, resultType, and sql statement equivalents of each select node for (Element selectNode : selectNodes) { String id = selectNode.attributeValue("id"); String resultType = selectNode.attributeValue("resultType"); SqlSource sqlSource = parseDynamicSqlNode(selectNode); //Store more than one value with a mapper object MappedStatement mapper = new MappedStatement(); mapper.setConfiguration(config); mapper.setId(id); mapper.setResultType(Class.forName(resultType)); mapper.setSqlSource(sqlSource); //A mapper mapping file can have more than one select (statment). In different mapping files, the id of the select can be the same. //So namespace+"."+id is used as a unique flag to distinguish and store in the map set. mappers.put(namespace + "." + id, mapper); } return mappers; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(); } } private static SqlSource parseDynamicSqlNode(Element selectNode) { String sql = selectNode.getTextTrim(); StaticSqlSource staticSqlSource = new StaticSqlSource(sql); return staticSqlSource; } }
2.3 Configuration
Specific configuration objects, mainly save connection information and parse mappedStatements mapping generated by mapper.xml
private String username; private String password; private String url; private String driver; protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected final InterceptorChain interceptorChain = new InterceptorChain(); protected Map<String, MappedStatement> mappedStatements = new HashMap(); public Executor newExecutor(ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; Executor executor = null; if (ExecutorType.BATCH == executorType) { } else if (ExecutorType.REUSE == executorType) { } else { executor = new SimpleExecutor(this); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
2.4 SqlSessionFactory
After completing configuration parsing, session factories are generated according to the Configuration object. What's the use of session factories? To put it plainly, they are constantly opening sessions.
public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } public Configuration getConfiguration() { return configuration; } public void setConfiguration(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType()); } private SqlSession openSessionFromDataSource(ExecutorType execType) { try { final Executor executor = configuration.newExecutor(execType); return new DefaultSqlSession(configuration, executor); } catch (Exception e) { e.printStackTrace(); } return null; } } // Interface of Conversation Factory public interface SqlSessionFactory { SqlSession openSession(); }
2.5 SqlSession
A session can be opened by the session factory generated above
SqlSession sqlSession = factory.openSession(); DefaultSqlSessionFactory ... @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType()); } private SqlSession openSessionFromDataSource(ExecutorType execType) { try { final Executor executor = configuration.newExecutor(execType); return new DefaultSqlSession(configuration, executor); } catch (Exception e) { e.printStackTrace(); } return null; }
2.6 Executor & SimpleExecutor
Actuator, according to the above configuration object can generate the corresponding executor, the executor is specific work, responsible for parsing MappedStatement according to parameters and above, as well as ResultSetHandler, parsing sql, processing results and returning.
public interface Executor { ResultSetHandler NO_RESULT_HANDLER = null; <E> List<E> query(MappedStatement ms, Object parameter, ResultSetHandler resultSetHandler) throws SQLException; ... } SimpleExecutor Namely Executor Simple implementation public class SimpleExecutor implements Executor { protected Configuration configuration; public SimpleExecutor(Configuration configuration) { this.configuration = configuration; } @Override public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException { Statement stmt = null; Configuration configuration = mappedStatement.getConfiguration(); StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler); return handler.query(stmt, resultSetHandler); } }
2.7 DefaultSqlSession
With the executor and the default configuration object, we can generate the default session, which is actually responsible for adding or deleting the query. Actually, the executor is also called SqlSession to execute the corresponding logic.
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; } @Override public <T> T selectOne(String statement) throws RuntimeException { return this.<T>selectOne(statement, null); } @Override public <T> T selectOne(String statement, Object parameter) throws RuntimeException { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } @Override public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw new RuntimeException("Error querying database. Cause: " + e); } } @Override public Configuration getConfiguration() { return configuration; } @Override public <T> T getMapper(Class<T> clazz){ try { T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz)); return proxyObj; } catch (Exception e) { e.printStackTrace(); } return null; } }
2.8 MapperProxy
MapperProxy is a proxy for mapper objects. In other words, you define the interface of dao and use jdk dynamic proxy to generate a proxy object.
> //Getting the corresponding mapper through dynamic proxy UserMapper mapper = sqlSession.getMapper(UserMapper.class); ... public <T> T getMapper(Class<T> clazz){ try { T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz)); return proxyObj; } catch (Exception e) { e.printStackTrace(); } return null; } MapperProxy actually zu public class MapperProxy implements InvocationHandler { private SqlSession sqlSession; private Class mapperInterface; public MapperProxy(SqlSession sqlSession, Class mapperInterface) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //If the return value is a List collection, the following code is executed Class<?> returnType = method.getReturnType(); if (returnType == List.class) { //Any method invoked by the proxy object is executed here //True execution of Sql statements //Work to execute Sql statements: selectList() of the sqlSession object, that is, I'm calling that method here. //key is the fully qualified name of the Mapper interface +"."+method name String methodName = method.getName();//Method name Class<?> clazz = method.getDeclaringClass(); String clazzName = clazz.getName();//Gets the fully qualified name of the interface String key = clazzName + "." + methodName; List<Object> list = sqlSession.selectList(key, args); return list; }else { return null; } }
2.9 dao Implementation
The invoke method of the proxy object is actually executed when the dao method is called
//Calling method List<User> userList = mapper.getUserList(); --> MapperProxy --> invoke --> sqlSession-->selectList DefaultSqlSession ... @Override public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw new RuntimeException("Error querying database. Cause: " + e); } } //Let's look at executor's query SimpleExecutor ... @Override public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException { Statement stmt = null; Configuration configuration = mappedStatement.getConfiguration(); StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler); return handler.query(stmt, resultSetHandler); } StatementHandler //After seeing it for so long, I finally saw the original jdbc. ... public class SimpleStatementHandler implements StatementHandler { private MappedStatement mappedStatement; private ResultSetHandler resultHandler; private Object parameter; public SimpleStatementHandler(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultHandler) { this.mappedStatement = mappedStatement; this.resultHandler = resultHandler; this.parameter = parameter; } @Override public <E> List<E> query(Statement statement, ResultSetHandler resultHandler) throws SQLException { Connection conn = null; PreparedStatement preparedStatement = null; try { conn = getConnection(); preparedStatement = conn.prepareStatement(mappedStatement.getBoundSql(parameter).getSql()); ParameterHandler parameterHandler = new DefaultParameterHandler(parameter); parameterHandler.setParameters(preparedStatement); preparedStatement.execute(); resultHandler = new DefaultResultSetHandler(mappedStatement.getResultType(), preparedStatement.getResultSet()); return resultHandler.handleResultSets(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (preparedStatement != null) { preparedStatement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } return null; } private Connection getConnection() throws SQLException, ClassNotFoundException { Configuration configuration = mappedStatement.getConfiguration(); Class.forName(configuration.getDriver()); return DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword()); } }
2.10 SimpleStatementHandler
It's dealing with jdbc. ParameterHandler handles the parameters, ResultSetHandler handles the returned results and returns them.
DefaultParameterHandler public class DefaultParameterHandler implements ParameterHandler { private Object parameter; public DefaultParameterHandler(Object parameter) { this.parameter = parameter; } @Override public void setParameters(PreparedStatement psmt) throws SQLException { try { Object[] parameters = (Object[]) parameter; if (parameters != null && parameters.length > 0) { for (int i = 0; i < parameters.length; i++) { if (parameters[i] instanceof Integer) { psmt.setInt(i + 1, (Integer) parameters[i]); } else if (parameters[i] instanceof Long) { psmt.setLong(i + 1, (Long) parameters[i]); } else if (parameters[i] instanceof String) { psmt.setString(i + 1, String.valueOf(parameters[i])); } else if (parameters[i] instanceof Boolean) { psmt.setBoolean(i + 1, (Boolean) parameters[i]); } else { psmt.setString(i + 1, String.valueOf(parameters[i])); } } } } catch (SQLException e) { e.printStackTrace(); } } } DefaultResultSetHandler ... public class DefaultResultSetHandler<E> implements ResultSetHandler { private Class type; private ResultSet resultSet; public DefaultResultSetHandler(Class type, ResultSet resultSet) { this.type = type; this.resultSet = resultSet; } @Override public <E> List<E> handleResultSets() { try { ArrayList<E> resultList = new ArrayList<>(); Object resultObject = null; while (resultSet.next()) { resultObject = new DefaultObjectFactory().create(type); for (Field field : resultObject.getClass().getDeclaredFields()) { setValue(resultObject, field, resultSet); } resultList.add( (E) resultObject); } return resultList; } catch (Exception e) { e.printStackTrace(); } finally { try { if (resultSet != null) { resultSet.close(); } } catch (SQLException e) { e.printStackTrace(); } } return null; } public void setValue(Object resultObject, Field field, ResultSet resultSet) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, SQLException { Method method = type.getMethod("set" + upperCapital(field.getName()), field.getType()); method.invoke(resultObject, getResult(field, resultSet)); } public Object getResult(Field field, ResultSet rs) throws SQLException { Class<?> type = field.getType(); if (Integer.class == type) { return rs.getInt(field.getName()); } else if (String.class == type) { return rs.getString(field.getName()); } else if (Long.class == type) { return rs.getLong(field.getName()); } else { return rs.getString(field.getName()); } } private String upperCapital(String name) { String first = name.substring(0, 1); String tail = name.substring(1); return first.toUpperCase() + tail; } }
3 expansion
The core logic of mybatis ends here, but mybatis goes far beyond these things, such as sql dynamic generation, plug-in mechanism, caching mechanism, transaction mechanism, which bloggers will write articles about later. Look forward to it.