Simple implementation of custom persistence layer framework, handwriting MyBatis to achieve basic functions

Simple implementation of custom persistence layer framework, handwriting MyBatis to achieve basic functions

For the source code, please click -> Handwriting Persistence Framework Source Code

1. Analyze JDBC operation problems

public static void main(String[] args) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
	try {
		// 加载数据库驱动
		Class.forName("com.mysql.jdbc.Driver");
		// 通过驱动管理类获取数据库链接
		connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
						characterEncoding=utf-8", "root", "root");
		// 定义sql语句?表示占位符
		String sql = "select * from user where username = ?";
		// 获取预处理statement
		preparedStatement = connection.prepareStatement(sql);
		// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
		preparedStatement.setString(1, "tom");
		// 向数据库发出sql执行查询,查询出结果集
		resultSet = preparedStatement.executeQuery();
		// 遍历查询结果集
		while (resultSet.next()) {
			int id = resultSet.getInt("id");
			String username = resultSet.getString("username");
			// 封装User
            user.setId(id);
            user.setUsername(username);
		}
		System.out.println(user);
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		// 释放资源
		if (resultSet != null) {
			try {
				resultSet.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (preparedStatement != null) {
			try {
				preparedStatement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (connection != null) {
			try {
				connection.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

Summary of JDBC problems:

The problems of original jdbc development are as follows:

  1. Frequent database connection creation and release cause waste of system resources, thereby affecting system performance.
  2. Sql statements are hard-coded in the code, which makes the code difficult to maintain. In actual applications, SQL may change a lot. SQL changes require changing the java code.
  3. Using preparedStatement to pass parameters to the occupied position symbol is hard-coded, because the where condition of the SQL statement is not necessarily, it may be more or less, and the code must be modified to modify the sql, and the system is not easy to maintain.
  4. There is hard coding (query column name) for the result set analysis. SQL changes lead to changes in the analysis code, and the system is not easy to maintain. It is more convenient if the database records can be encapsulated into pojo object analysis.

2. Problem solving ideas

  1. Use database connection pool to initialize connection resources
  2. Extract the sql statement into the xml configuration file
  3. Use underlying technologies such as reflection and introspection to automatically map entities and tables to attributes and fields

3. Custom frame design

Use end:

Provide core configuration files:

sqlMapConfig.xml: store data source information, import mapper.xml

Mapper.xml: sql statement configuration file information

Frame end:

1. Read the configuration file

After the reading is completed, it exists in the form of a stream. We cannot store the read configuration information in the form of a stream in the memory. It is not easy to operate. You can create javaBean to store

Configuration: Store basic information of the database, Map<Unique ID, Mapper> Unique ID: namespace + "." + id

MappedStatement: sql statement, statement type, input parameter java type, output parameter java type

2. Parse the configuration file

Create the sqlSessionFactoryBuilder class:

Method: sqlSessionFactory build():

First: Use dom4j to parse the configuration file and encapsulate the parsed content into Configuration and MappedStatement

Second: Create the implementation class DefaultSqlSession of SqlSessionFactory

3. Create SqlSessionFactory:

Method: openSession(): Obtain an instance object of the implementation class of the sqlSession interface

4. Create sqlSession interface and implementation class: mainly encapsulate the crud method

Method: selectList(String statementId,Object param): query all

selectOne(String statementId,Object param): query a single

Specific implementation: Encapsulate JDBC to complete query operations on database tables

Design patterns involved:

Builder design mode, factory mode, agency mode

4. Implementation of custom framework

Create a configuration profile in the consumer project

Create sqlMapConfig.xml

<configuration>
    <!--数据库连接信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
    <! --引入sql配置信息-->
    <mapper resource="mapper.xml"></mapper>
</configuration>

mapper.xml

<mapper namespace="User">
    <select id="selectOne" paramterType="com.tao.pojo.User" resultType="com.tao.pojo.User">
    	select * from user where id = #{id} and username =#{username}
    </select>
    <select id="selectList" resultType="com.tao.pojo.User">
    	select * from user
    </select>
</mapper>

User entity

public class User {
    //主键标识
    private Integer id;
    //用户名
    private String username;
    
    public Integer getId() {
    return id;
    }
    
    public void setId(Integer id) {
    this.id = id;
    }
    
    public String getUsername() {
    return username;
    }
    
    public void setUsername(String username) {
    this.username = username;
    }
    
    @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' + '}';
    }
}

Create another Maven subproject and import the dependent coordinates that need to be used

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <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>
</dependencies>

Configuration

public class Configuration {
    //数据源
    private DataSource dataSource;
    //map集合: key:statementId value:MappedStatement
    private Map<String,MappedStatement> mappedStatementMap = new HashMap<String,
    MappedStatement>();
    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;
    }
}

MappedStatement

public class MappedStatement {
    //id
    private Integer id;
    //sql语句
    private String sql;
    //输入参数
    private Class<?> paramterType;
    //输出参数
    private Class<?> resultType;
    public Integer getId() {
    	return id;
    }
    public void setId(Integer id) {
    	this.id = id;
    }
    public String getSql() {
    	return sql;
    }
    public void setSql(String sql) {
    	this.sql = sql;
    }
    public Class<?> getParamterType() {
    	return paramterType;
    }
    public void setParamterType(Class<?> paramterType) {
    	this.paramterType = paramterType;
    }
    public Class<?> getResultType() {
    	return resultType;
    }
    public void setResultType(Class<?> resultType) {
    	this.resultType = resultType;
    }
}

Resources

public class Resources {
	public static InputStream getResourceAsSteam(String path){ 
    	InputStream resourceAsStream = Resources.class.getClassLoader.getResourceAsStream(path);
		return resourceAsStream;
	}
}

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {
	private Configuration configuration;
	public SqlSessionFactoryBuilder() {
		this.configuration = new Configuration();
	}
	public SqlSessionFactory build(InputStream inputStream) throws 
        	DocumentException, PropertyVetoException, ClassNotFoundException {
		//1.解析配置文件,封装Configuration XMLConfigerBuilder
		xmlConfigerBuilder = new XMLConfigerBuilder(configuration);
		Configuration configuration =
					xmlConfigerBuilder.parseConfiguration(inputStream);
		//2.创建 sqlSessionFactory
		SqlSessionFactory sqlSessionFactory = new
					DefaultSqlSessionFactory(configuration);
		return sqlSessionFactory;
	}
}

XMLConfigerBuilder

public class XMLConfigerBuilder {
    private Configuration configuration;
    public XMLConfigerBuilder(Configuration configuration) {
    	this.configuration = new Configuration();
    }
    public Configuration parseConfiguration(InputStream inputStream) throws
        DocumentException, PropertyVetoException, ClassNotFoundException {
        Document document = new SAXReader().read(inputStream); //<configuation>
        Element rootElement = document.getRootElement();
        List<Element> propertyElements = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element propertyElement : propertyElements) {
            String name = propertyElement.attributeValue("name");
            String value = propertyElement.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
        configuration.setDataSource(comboPooledDataSource);
        //mapper 部分
        List<Element> mapperElements = rootElement.selectNodes("//mapper");
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
        for (Element mapperElement : mapperElements) {
            String mapperPath = mapperElement.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
		return configuration;
	}
}

XMLMapperBuilder

public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sqlText);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        }

    }
}

sqlSessionFactory interface and D efaultSqlSessionFactory implementation class

public interface SqlSessionFactory {
	public SqlSession openSession();
}


public class DefaultSqlSessionFactory implements SqlSessionFactory {
	private Configuration configuration;
    
    public DefaultSqlSessionFactory(Configuration configuration) {
    	this.configuration = configuration;
    }
    
    public SqlSession openSession(){
    	return new DefaultSqlSession(configuration);
    }
}

sqlSession interface and DefaultSqlSession implementation class

public interface SqlSession {
	public <E> List<E> selectList(String statementId, Object... param) throws Exception;
	
    public <T> T selectOne(String statementId,Object... params) throws Exception;
	
    public void close() throws SQLException;
}

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    public DefaultSqlSession(Configuration configuration) {
		this.configuration = configuration;
    }
	//处理器对象
	private Executor simpleExcutor = new SimpleExecutor();
	public <E > List < E > selectList(String statementId, Object...param) throws Exception {
		MappedStatement mappedStatement =
					configuration.getMappedStatementMap().get(statementId);
		List<E> query = simpleExcutor.query(configuration, mappedStatement, param);
		return query;
	}
	//selectOne 中调用 selectList
	public <T > T selectOne(String statementId, Object...params) throws Exception {
		List<Object> objects = selectList(statementId, params);
		if (objects.size() == 1) {
			return (T) objects.get(0);
		} else {
			throw new RuntimeException("返回结果过多");
		}
	}
	public void close () throws SQLException {
		simpleExcutor.close();
	}
}

Executor

public interface Executor {
	<E> List<E> query(Configuration configuration, MappedStatement
			mappedStatement,Object[] param) throws Exception;
	void close() throws SQLException;
}

SimpleExecutor

public class SimpleExecutor implements Executor {
	private Connection connection = null;
	public <E> List<E> query(Configuration configuration, MappedStatement
			mappedStatement, Object[] param) throws SQLException, NoSuchFieldException,
			IllegalAccessException, InstantiationException, IntrospectionException,
			InvocationTargetException {
		//获取连接
		connection = configuration.getDataSource().getConnection();
        // select * from user where id = #{id} and username = #{username} String
        sql = mappedStatement.getSql();
        //对sql进行处理
        BoundSql boundsql = getBoundSql(sql);
        // select * from where id = ? and username = ?
        String finalSql = boundsql.getSqlText();
        //获取传入参数类型
        Class<?> paramterType = mappedStatement.getParamterType();
        //获取预编译preparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(finalSql);
        List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String name = parameterMapping.getName();
            //反射
            Field declaredField = paramterType.getDeclaredField(name);
            declaredField.setAccessible(true);
            //参数的值
            Object o = declaredField.get(param[0]);
            //给占位符赋值
            preparedStatement.setObject(i + 1, o);
		}
        ResultSet resultSet = preparedStatement.executeQuery();
        Class<?> resultType = mappedStatement.getResultType();
        ArrayList<E> results = new ArrayList<E>();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            (E) resultType.newInstance();
            int columnCount = metaData.getColumnCount();
			for (int i = 1; i <= columnCount; i++) {
                //属性名
                String columnName = metaData.getColumnName(i);
                //属性值
                Object value = resultSet.getObject(columnName);
                //创建属性描述器,为属性生成读写方法
                PropertyDescriptor propertyDescriptor = new
                PropertyDescriptor(columnName, resultType);
                //获取写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //向类中写入值
                writeMethod.invoke(o, value);
			}
			results.add(o);
        }
		return results;
	}
    
    @Override
    public void close() throws SQLException {
    	connection.close();
    }
    
    private BoundSql getBoundSql(String sql) {
        //标记处理类:主要是配合通用标记解析器GenericTokenParser类完成对配置文件等的解析工作,其中TokenHandler主要完成处理
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        //GenericTokenParser :通用的标记解析器,完成了代码片段中的占位符的解析,然后再根 据给定的标记处理器(TokenHandler)来进行表达式的处理
        //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处理器)
        GenericTokenParser genericTokenParser = new GenericTokenParser("# {", "}", parameterMappingTokenHandler);
        String parse = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings =
        		parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parse, parameterMappings);
        return boundSql;
    }
}

BoundSql

public class BoundSql {
    //解析过后的sql语句
    private String sqlText;
    //解析出来的参数
    private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();
    public BoundSql(String sqlText, List<ParameterMapping>
    			parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }
    public String getSqlText() {
    	return sqlText;
    }
    public void setSqlText(String sqlText) {
    	this.sqlText = sqlText;
    }
    public List<ParameterMapping> getParameterMappingList() {
    	return parameterMappingList;
    }
    public void setParameterMappingList(List<ParameterMapping>
   			 parameterMappingList) {
    	this.parameterMappingList = parameterMappingList;
    }
}

1.5, custom framework optimization

Through the above-mentioned custom framework, we have solved some of the problems caused by JDBC operating database: for example, frequent creation and release of database connections, hard coding, manual packaging and return of result sets, etc., but now we have just completed the custom framework code, but also There are some problems:

  • There are duplicate codes in the implementation class of dao, and the process template of the entire operation is duplicated (create sqlsession, call sqlsession method, close sqlsession)
  • There is hard-coded in the implementation class of dao. When calling the sqlsession method, the id of the parameter statement is hard-coded

Solution: Use the proxy mode to create the proxy object of the interface

Add method in sqlSession

public interface SqlSession {
	public <T> T getMappper(Class<?> mapperClass);
}

Implementation class

@Override
public <T> T getMappper(Class<?> mapperClass) {
	T o = (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new
				Class[] {mapperClass}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
        				throws Throwable {
            // selectOne
            String methodName = method.getName();
            // className:namespace
            String className = method.getDeclaringClass().getName();
            //statementid
            String key = className+"."+methodName;
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(key);
            Type genericReturnType = method.getGenericReturnType();
            ArrayList arrayList = new ArrayList<> ();
            //判断是否实现泛型类型参数化
            if(genericReturnType instanceof ParameterizedType){
            	return selectList(key,args);
            }
            return selectOne(key,args);
        }
    });
    return o;
}

Only the query function was implemented this time, and the addition, deletion, and modification functions of the framework were added later. See the source link below for the detailed process

Please click -> Handwritten persistence layer framework source code