After analyzing the startup and execution process of Mybatis last time, this time we will implement a simple Mybatis framework according to the ideas of last time.

Git address: github.com/dp33221/my_…

First of all, we need to analyze the situation in which ORM framework appeared. In the early days, we used database link steps as follows

You can see that each time you need to register the driver, establish the connection, perform the query, get the results, and close the connection. Obviously there are high coupling and hard coding issues. So we take it step by step.

The first four problems: registering the driver and establishing a database connection

On this side, we can encapsulate a connection pool to solve the coupling problem, and then we need to provide the user for database connection configuration. On this side, we learn from the way of Mybatis and use XML for configuration. So you need a Configuration object.

Second and third issues: Executing queries

In order to achieve decoupling and configurability, we also think of using configuration files for configuration. Refer to mapper. XML file of Mybatis, we need a mapperStatement object to store the corresponding XML information. Then you log in the parameter, which returns the result, the execution type (executorType), and the corresponding ID, which by default is the ID in the tag.

mappedStatement

Each tag in XML is parsed into an MappedStatement object, so we need to have a mapping to store it. The most obvious one is a Map, or key-value pair. The key takes namespace+ ID from XML, and the value is the corresponding MappedStatement object. Stored in the Configuration object. So the Configuration object holds a pool of connections and parsed XML data.

@Data @Builder public class MappedStatement { private String id; private Class<? > paramType; private Class<? > resultType; private String sql; private String executorType; }Copy the code
Conifguration

To solve the first two problems, the first step is to configure the configuration file SQLmapconfig.xml, where I set a root tag, and a sub-tag property to configure the database connection properties to solve the hard coding problem. Mapper. XML can also be parsed when the Configuration file is parsed, so set a mapper tag to set the mapper path to be parsed.

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Configuration {
    private DataSource dataSource;
    @Builder.Default
    private Map<String,MappedStatement> mappedStatementList=new HashMap<String, MappedStatement>();

}
Copy the code
sqlMapConfig.xml

This is the basic configuration configuration, where property is the basic configuration for the database connection and mapper is the mapper.xml to parse

The mapper. XML file contains SQL information, including namespace, ID, paramType, resultType, and SQL information.

Parsing configuration files

Before parsing a configuration file, you need to understand the general flow. You first need to create a factory for SqlSession objects to generate SqlSession, parse the configuration file, and then generate the SqlSession objects, followed by the Excutor operation.

The first step is to initially dissolve the configuration file

Resources.Java

1. Start parsing the XML configuration file, which I do with dom4J and JAXen. The first step is to read the XML configuration file as an input stream, using the getResourcesAsStream method of the ClassLoader.

Public class Resources {/** * @description: get Resources into byte InputStream * @param path resource path * @return java.io.InputStream * @author: dingpei */ public static InputStream getResourcesAsStream(String path){ return Resources.class.getClassLoader().getResourceAsStream(path); }}Copy the code
SqlSessionFactoryBuild

Parse the input stream with SqlSessionFactoryBuild

public class SqlSessionFactoryBuild { private Configuration configuration; public SqlSessionFactoryBuild(){ this.configuration=new Configuration(); } / * * * @ Description: parsing the input stream * @ param in the input stream * @ return sqlsession. SqlSessionFactory * @ the Author: dingpei */ public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {// Parse mapper and SqlMapConfig final Configuration Configuration = new XmlConfigBuild().buildConfiguration(in); DefaultBeanFactory.singletonObjects.put("dateSource",configuration.getDataSource()); // Create sqlsessionFactory return new DefaultSqlSessionFactory(configuration); }}Copy the code
XmlConfigBuild.java

2. Start parsing the XML file by passing the byte input forward. Use the buildConfiguration method of XmlConfigBuild to get the byte stream first, then the XML and tags, then the node information obtained by xpath expression, and then stored in properties. Create a connection pool, then set up the data source and store it in a Configuration object. Then get all the Mapper tags and iterate through the parsing.

public class XmlConfigBuild { private Configuration configuration; public XmlConfigBuild(){ this.configuration=new Configuration(); } /** * @Description: * @param inputStream byte inputStream * @return config.Configuration * @author: dingpei */ public Configuration buildConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {Document Document =new SAXReader().read(inputStream); Final Element rootElement = document.getrootelement (); Final List<Element> propertyList = rootElement. SelectNodes ("//property"); K v Properties resources=new Properties(); for (Element element : propertyList) { final String name = element.attributeValue("name"); final String value = element.attributeValue("value"); resources.put(name,value); } ComboPooledDataSource =new ComboPooledDataSource(); comboPooledDataSource.setJdbcUrl(resources.getProperty("jdbcUrl")); comboPooledDataSource.setDriverClass(resources.getProperty("driverClass")); comboPooledDataSource.setUser(resources.getProperty("username")); comboPooledDataSource.setPassword(resources.getProperty("password")); / / set up the data source configuration. SetDataSource (comboPooledDataSource); XmlMapperBuild xmlMapperBuild=new XmlMapperBuild(configuration); // Load mapper. XML final List<Element> mapperList = rootElement. SelectNodes ("//mapper"); for (Element element : mapperList) { final String mapperPath = element.attributeValue("resource"); InputStream in= Resources.getResourcesAsStream(mapperPath); xmlMapperBuild.build(in); } return configuration; }}Copy the code
XmlMapperBuild.java

3. Call the build method of xmlMapperBuild. When you create a new XMLMapperBuild object, you pass in the Configuration that has the data source set. Mapper = id, parameter type, parameter type, and SQL statement Statementid (Namespace + ID) and MappedStatement are generated and stored in the Map collection in the Configuration object. At this point you are done parsing the file.

public class XmlMapperBuild { private Configuration configuration; public XmlMapperBuild(Configuration configuration){ this.configuration=configuration; } /** * @description: read a mapper file and wrap it into an mappedStatement object. dingpei */ public void build(InputStream inputStream) throws DocumentException, ClassNotFoundException {final Document Document = new SAXReader().read(inputStream); ClassNotFoundException {final Document Document = new SAXReader().read(inputStream); Final Element rootElement = document.getrootelement (); / / get the mapper file namespace final String namespace = rootElement. AttributeValue (" namespace "); Final List<Element> selectList = rootElement. SelectNodes ("//select"); iteratorElement(selectList,namespace,"select"); Final List<Element> updateList = rootElement. SelectNodes ("//update"); iteratorElement(updateList,namespace,"update"); // Query all insert tags final List<Element> insertList = rootElement. SelectNodes ("//insert"); iteratorElement(insertList,namespace,"insert"); Final List<Element> deleteList = rootElement. SelectNodes ("//delete"); iteratorElement(deleteList,namespace,"delete"); } /** * @Description: The traversal tag is stored in configuraation's mappedStatement collection * @param elementList Element collection * @param Namespace full name * @return void * @author: dingpei */ private void iteratorElement(List<Element> elementList,String namespace,String executorType) throws ClassNotFoundException { for (Element element : elementList) { final String id = element.attributeValue("id"); final String resultType = element.attributeValue("resultType"); final String paramType = element.attributeValue("paramType"); Class<? > paramClassType=getClass(paramType); Class<? > resultClassType= getClass(resultType); final String sql = element.getTextTrim(); String statementId=namespace+id; configuration.getMappedStatementList().put(statementId, MappedStatement.builder().id(id).paramType(paramClassType).resultType(resultClassType).executorType(executorType).sql(sq l).build()); } } private Class<? > getClass(String s) throws ClassNotFoundException { if(null==s||s.equals("")){ return null; } return Class.forName(s); }}Copy the code

The second step is to generate a reply object

Generate the SQLSession from the factory
public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration){ this.configuration=configuration; } @Override public DefaultSqlSession openSession() { return new DefaultSqlSession(configuration); }}Copy the code

The next step is to generate the SQLSession object, which is basically an SQL conversation object that performs SQL operations

sqlsession.java

Defines the interface

public interface SqlSession {
    <E> List<E> selectAll(String statementId,Object... param) throws NoSuchFieldException, SQLException, InvocationTargetException, IntrospectionException, InstantiationException, IllegalAccessException;
    <E> E selectOne(String statementId,Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException;
    <E> E getMapper(Class c);
 }

Copy the code
defaultSqlSession.java

You define what methods an interface needs to implement, and then generate a default implementation class to implement those methods, mainly a query SELECT method and an update update method. GetMapper (JDK dynamic proxy) ¶ getMapper (JDK dynamic proxy) ¶ getMapper (JDK dynamic proxy) ¶ getMapper (JDK dynamic proxy) ¶ So the namespace needs to be the fully qualified path of the class, and the ID is the method of the class, which also causes methods to not be overloaded. The return value type and operation type are used to determine whether the query or update operation is performed. If the update operation is performed, the parameter array of the method is recorded. Sqlsession is an external session object in which the execution of SQL statements is performed through an Executor object.

public class DefaultSqlSession implements SqlSession { public DefaultSqlSession(){ } private Configuration configuration; public DefaultSqlSession(Configuration configuration){ this.configuration=configuration; } /** * @description: query all * @param statementId id * @param params parameter * @return java.util.List<E> * @author: dingpei */ @Override public <E> List<E> selectAll(String statementId, Object... params) throws NoSuchFieldException, SQLException, InvocationTargetException, IntrospectionException, InstantiationException, IllegalAccessException { final MappedStatement mappedStatement = configuration.getMappedStatementList().get(statementId); return new DefaultExcutor().query(configuration,mappedStatement,params); } /** * @description: Query a single * @param statementId tag id * @param params parameter * @return java.util.List<E> * @author: dingpei */ @Override public <E> E selectOne(String statementId, Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException { final List<Object> objects = selectAll(statementId, params); if(objects.size()==0){ return (E) new Object(); } if(objects.size()>1){ throw new RuntimeException("out of 2"); } return (E) objects.get(0); } /** * @Description: * @param statementId tag ID * @param parameters sequence * @param params parameter * @return java.lang.Integer * @author: dingpei */ public Integer update(String statementId,Parameter[] parameters, Object... params) throws ClassNotFoundException, SQLException, IllegalAccessException, NoSuchFieldException { final MappedStatement mappedStatement = configuration.getMappedStatementList().get(statementId); return new DefaultExcutor().update(configuration,mappedStatement,parameters,params); } /** * @description: agent * @author: dingpei * @date: */ @override public <E> E getMapper(Class c) {final Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{c}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable {// Get the class fully qualified name + method name final String methodName = method.getName(); final String className = method.getDeclaringClass().getName(); String statementId=className+methodName; final MappedStatement mappedStatement = configuration.getMappedStatementList().get(statementId); final Type genericReturnType = method.getGenericReturnType(); if(genericReturnType instanceof ParameterizedType){ return selectAll(statementId,objects); } // Query a single else if(! (genericReturnType.getClass().getClassLoader() ==null)|| "select".equals(mappedStatement.getExecutorType())) { return selectOne(statementId,objects); } final Parameter[] parameters = method.getParameters(); return update(statementId,parameters,objects); }}); return (E) o; }}Copy the code

The third step

Performing SQL operations

Excutor.java

Interface definition specification

Public interface Excutor {/ / query < E > List < E > query (Configuration Configuration, MappedStatement MappedStatement, Object... params) throws SQLException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException; Int update(Configuration Configuration, MappedStatement MappedStatement, Parameter[] parameters, Object... params) throws SQLException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException; }Copy the code
DefaultExecutor.java

As with SQLSession, create an interface definition rule and then create a defaultExecutor for default implementation. There is a query function, an update function, I note here to write more comprehensive, will not repeat.

public class DefaultExcutor implements Excutor { /** * @Description: Select * @param configuration * @param mappedStatement mapper * @param params input * @return java.util.List<E> * @Author: dingpei */ @Override public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException {/ / for a link Connection Connection = Resource. Conn. The get (); if(connection==null){ connection=configuration.getDataSource().getConnection(); Resource.conn.set(connection); } // getSql final String SQL = mappedstatement.getsql (); / / the new processor final ParameterMappingTokenHandler ParameterMappingTokenHandler = new ParameterMappingTokenHandler (); // Parse SQL to convert #{} to? final String parse = new GenericTokenParser("#{", "}", parameterMappingTokenHandler).parse(sql); / / SQL # {} contains a collection of final List < ParameterMapping > parameterMappings = parameterMappingTokenHandler. GetParameterMappings (); / / the precompiled SQL PreparedStatement PreparedStatement = connection. The prepareStatement (parse); Final Class<? > paramType = mappedStatement.getParamType(); if(paramType! =null){ int index=1; For (ParameterMapping ParameterMapping: parameterMappings) {// Field name final String name = parameterMapping.getName(); / / reflection for Field final Field declaredField = paramType. GetDeclaredField (name); / / open access declaredField. SetAccessible (true); Final Object o = declaredField.get(params[0]); preparedStatement.setObject(index,o); index++; } } final ResultSet resultSet = preparedStatement.executeQuery(); // Get the return type final Class<? > resultType = mappedStatement.getResultType(); List<E> res=new ArrayList<>(); While (resultSet.next()){final ResultSetMetaData metaData = resultSet.getMetadata (); Final int columnCount = metadata.getColumnCount (); final Object o = resultType.newInstance(); For (int I =1; i<=columnCount; I ++){// Final String columnName = metadata.getColumnName (I); Resultset.getobject (columnName); // Final Object Value = resultSet.getobject (columnName); // Introspection PropertyDescriptor PropertyDescriptor =new PropertyDescriptor(columnName,resultType); / / create the final written Method Method writeMethod = propertyDescriptor. GetWriteMethod (); writeMethod.invoke(o,value); } res.add((E) o); } return res; } /** * @Description: Add, delete, or modify operations * @param Configuration Configuration information * @param mappedStatement XML parsing information * @param params entry parameter * @return int * @author: dingpei */ @Override public int update(Configuration configuration, MappedStatement mappedStatement, Parameter[] parameters, Object... params) throws SQLException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {// Obtain the Connection Connection = resource-conn.get (); if(connection==null){ connection=configuration.getDataSource().getConnection(); Resource.conn.set(connection); } // getSql final String SQL = mappedstatement.getsql (); / / the new processor final ParameterMappingTokenHandler ParameterMappingTokenHandler = new ParameterMappingTokenHandler (); // Parse SQL to convert #{} to? final String parse = new GenericTokenParser("#{", "}", parameterMappingTokenHandler).parse(sql); / / SQL # {} contains a collection of final List < ParameterMapping > parameterMappings = parameterMappingTokenHandler. GetParameterMappings (); / / the precompiled SQL PreparedStatement PreparedStatement = connection. The prepareStatement (parse); Final Class<? > paramType = mappedStatement.getParamType(); If (paramType==null){// Sort the values passed in for (int j=0; j<parameterMappings.size(); For (int I = 0; i < parameters.length; {// parameterMappings is the same as #{} in SQL. If (parameterMappings.get(j).getName().equals(parameters[I].getName())){ preparedStatement.setObject(j+1,params[i]); }}}} // The request type is not null. If (paramType! =null){ int index=1; For (ParameterMapping ParameterMapping: parameterMappings) {// Field name final String name = parameterMapping.getName(); / / reflection for Field final Field declaredField = paramType. GetDeclaredField (name); / / open access declaredField. SetAccessible (true); Final Object o = declaredField.get(params[0]); preparedStatement.setObject(index,o); index++; } } return preparedStatement.executeUpdate(); }}Copy the code

One of them is the processing class I borrowed from Mybatis for processing request parameters. And simplified it

utils

Parameter processing GenericTokenParser, ParameterMappingTokenHandler, TokenHandler, if interested can look up to git

The test results

This is the query result of the custom ORM framework. To view the add, delete, and insert methods, call the update/delete/insert methods

Mapper Chinese approach

public interface PaymentChannelMapper {
    List<PaymentChannel> selectAll();
    PaymentChannel selectOne(PaymentChannel paymentChannel);
    int update(Integer id,String channel_name);
    int insert(PaymentChannel paymentChannel);
    int delete(int id);
}
Copy the code