自定义持久层框架
自定义持久层框架
基于原生jdbc的缺点,通过持久层框架,实现对应问题的解决。
自定义持久层框架需要解决如下问题:
1. 数据库配置信息硬编码问题
2. 频繁创建数据库连接
3. 需要手动释放连接
4. sql语句、参数设置、获取结果集参数存在硬编码
5. 需要手动封装进行结果集解析
分析
- 使用端
- 提供配置信息
- 数据库配置信息
- sqlmapconfig.xml
- 对应sql配置
- sql语句、参数类型、返回值类型
- mapper.xml
- 对应的类型的javaBean
- 定义mapper接口
- 定义数据库操作函数用于调用
- 持久层:封装jdbc代码
- 加载配置文件
- 将配置文件加载为内存数据
- 通过Resource.getResourceAsStream(String path)进行文件加载
- 创建配置信息存储javaBean、
- Configuration
- 数据库配置信息
- MapperStatement
- sql配置信息
- 解析配置文件
- DOM4J
- 定义SqlSessionFactory接口
- 接口实现类:DefaultSqlSessionFactory
- 根据获取的配置信息内容,生产sqlSession
- 使用工厂模式
- 定义SqlSession接口
- 接口实现类:DefaultSession
- 定义数据操作接口

解决思路

代码
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>customize-persistence</artifactId>
<groupId>com.learn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistence</artifactId>
<dependencies>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version> 8.0.23</version>
</dependency>
<!--c3p0连接池依赖-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!--dom4j依赖-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!--测试单元依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
</project>
配置文件
-
sqlMapConfig.xml
<configuration> <dataSource> <property name="dirverClass" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.1.101:3306/mytest?characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> <mappers> <!--此处引入mapper对应配置,则代码编写时不需要关注mapper配置位置--> <mapper resource="PersonMapper.xml"/> </mappers> </configuration> -
PersonMapper.xml
<!--nameSpace:主要用于区分不同mapper--> <mapper nameSpace="com.test.mapper.PersonMapper"> <!--sql全局唯一id:nameSpace + . + sqlId组成:statementId--> <!--id: nameSpace下sql唯一标识--> <!--parameterType:请求参数类型--> <!--resultType:结果集类型--> <select id="selectOne" resultType="com.test.model.Person" parameterType="com.test.model.Person"> select t.* from person t where t.id = #{id} and t.name = #{name} </select> <select id="selectList" resultType="com.test.model.Person"> select t.* from person t </select> </mapper>
配置文件解析
package resources;
import java.io.InputStream;
public class Resources {
/**
* 根据文件路径,将配置文件加载为stream流
* @param path
* @return
*/
public static InputStream getResourceAsStream(String path) {
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
配置解析结果对象
-
Configuration
package model; import lombok.Getter; import lombok.Setter; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Getter @Setter public class Configuration { //数据源对象 private DataSource dataSource; //key:statementId;value:sql配置解析结果对象 private Map<String, MappedStatement> mappedStatementMap = new HashMap(); } -
MapperStatement
package model; import lombok.Getter; import lombok.Setter; @Getter @Setter public class MapperStatement { //sqlid private String id; //参数类型 private String parameterType; //结果集类型 private String resultType; //具体sql private String sql; }
配置解析对象
-
XMLConfigBuilder
package configbuilder; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.AllArgsConstructor; import model.Configuration; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import resources.Resources; import java.io.InputStream; import java.util.List; import java.util.Properties; /** 解析sqlMapConfig.xml配置文件 */ public class XMLConfigBuilder { private Configuration configuration; /** 构造函数默认创建configuration */ public XMLConfigBuilder() { this.configuration = new Configuration(); } /** * 解析config配置文件流数据 * * @param inputStream * @return * @throws Exception */ public Configuration parseConfig(InputStream inputStream) throws Exception { // 解析流文件,获取document对象 Document document = new SAXReader().read(inputStream); // 获取根节点,对应<configuration> Element rootElement = document.getRootElement(); // 获取对应属性<property>: database配置 // 添加//表示向内部查找 List<Element> list = rootElement.selectNodes("//property"); // 创建properties封装解析结果 Properties properties = new Properties(); // 遍历解析,获取属性配置 for (Element element : list) { String name = element.attributeValue("name"); String value = element.attributeValue("value"); properties.setProperty(name, value); } // 创建数据源,此处使用c3p0连接池,并设置对应连接属性 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(properties.get("dirverClass").toString()); dataSource.setJdbcUrl(properties.get("url").toString()); dataSource.setUser(properties.get("username").toString()); dataSource.setPassword(properties.get("password").toString()); // 将数据源设置到configuration configuration.setDataSource(dataSource); // 解析对应mapper配置 List<Element> mapperList = rootElement.selectNodes("//mapper"); for (Element element : mapperList) { // 获取mapper配置对应路径 String mapperConfigPath = element.attributeValue("resource"); // 解析配置文件为stream流 InputStream mapperStream = Resources.getResourceAsStream(mapperConfigPath); // 创建XMLMapperBuilder用于解析mapper配置文件流 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); // 解析文件流 xmlMapperBuilder.parseMapper(mapperStream); } return configuration; } } -
XMLMapperBuilder
package configbuilder; import lombok.AllArgsConstructor; import model.Configuration; import model.MapperStatement; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.InputStream; import java.util.List; /** 解析mapper配置文件 */ @AllArgsConstructor public class XMLMapperBuilder { private Configuration configuration; /** * 解析mapper文件流数据 * * @param inputStream * @throws Exception */ public void parseMapper(InputStream inputStream) throws Exception { // 解析流文件,获取document对象 Document document = new SAXReader().read(inputStream); // 获取根节点,对应<mapper> Element rootElement = document.getRootElement(); // 获取nameSpace String nameSpace = rootElement.attributeValue("nameSpace"); // 获取<select>配置,此处仅实现select查询功能 List<Element> list = rootElement.selectNodes("//select"); // 解析select标签 for (Element element : list) { // 获取sqlId String id = element.attributeValue("id"); String parameterType = element.attributeValue("parameterType"); String resultType = element.attributeValue("resultType"); String sql = element.getTextTrim(); // 获取sql全局唯一id String statementId = nameSpace + "." + id; // 创建MapperStatement对象 MapperStatement mapperStatement = MapperStatement.builder() .id(id) .parameterType(parameterType) .resultType(resultType) .sql(sql) .build(); // 将解析结果保存 configuration.getMappedStatementMap().put(statementId, mapperStatement); } } }
sqlSession工厂
-
SqlSessionFactory
package sessionfactory; import sqlsession.SqlSession; /** * SqlSessionFactory接口:用于创建SqlSession */ public interface SqlSessionFactory { /** * 获取SqlSession * @return */ SqlSession openSession(); } -
DefaultSqlSessionFactory
package sessionfactory; import lombok.AllArgsConstructor; import model.Configuration; import sqlsession.DefaultSqlSession; import sqlsession.SqlSession; /** * SqlSessionFactory默认实现 */ @AllArgsConstructor public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; /** * 获取sqlSession * @return */ public SqlSession openSession() { //返回sqlSession默认实现 return new DefaultSqlSession(configuration); } } -
SqlSessionFactoryBuilder
package sessionfactory; import configbuilder.XMLConfigBuilder; import model.Configuration; import resources.Resources; import java.io.InputStream; /** 用与创建SqlSessionFactory,工厂模式 */ public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream inputStream) throws Exception { // 通过配置文件解析对象,获取configuration XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(inputStream); // 返回默认实现 return new DefaultSqlSessionFactory(configuration); } }
sqlSession
-
SqlSession
package sqlsession; public interface SqlSession { /** 自定义的查询方法,有兴趣可以扩展crud */ <T> T select(String statementId, boolean isList, Object... params) throws Exception; /** 通过jdk动态代理生成mapper的代理对象,则无需自定义mapper实现类 */ <T> T getMapper(Class<?> mapperClass); } -
DefaultSqlSession
package sqlsession; import executor.SimpleExecutor; import lombok.AllArgsConstructor; import model.Configuration; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.util.List; @AllArgsConstructor public class DefaultSqlSession implements SqlSession { private Configuration configuration; public <T> T select(String statementId, boolean isList, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); List<Object> resultList = simpleExecutor.select(configuration, statementId, params); if (!isList) { if (resultList.size() > 1) { throw new RuntimeException("查询结果过多"); } else if (resultList.size() < 1) { // 无查询结果,返回null return null; } else { return (T) resultList.get(0); } } return (T) resultList; } /** 通过jdk动态代理生成mapper的代理对象,则无需自定义mapper实现类 */ public <T> T getMapper(Class<?> mapperClass) { Object proxyInstance = Proxy.newProxyInstance( DefaultSqlSession.class.getClassLoader(), new Class[] {mapperClass}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取方法名称 String methodName = method.getName(); // 获取方法所在类名称 String className = method.getDeclaringClass().getName(); // 获取statementId String statementId = className + "." + methodName; // 获取返回值类型 Type genericReturnType = method.getGenericReturnType(); // 根据类型是否是泛型判断返回结果是否list boolean isList = genericReturnType instanceof ParameterizedType; return select(statementId, isList, args); } }); return (T) proxyInstance; } }
Executor
-
Executor
package executor; import model.Configuration; import java.util.List; public interface Executor { /** * 查询执行方法 */ <T> List<T> select(Configuration configuration, String statementId, Object... params) throws Exception; } -
SimpleExecutor
package executor; import model.BoundSql; import model.Configuration; import model.MapperStatement; import utils.GenericTokenParser; import utils.ParameterMapping; import utils.ParameterMappingTokenHandler; import javax.sql.DataSource; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.List; public class SimpleExecutor implements Executor { /** 真正的jdbc操作 */ public <T> List<T> select(Configuration configuration, String statementId, Object... params) throws Exception { // 获取数据库连接 Connection connection = configuration.getDataSource().getConnection(); // 获取sql对应mapperStatement对象 MapperStatement mapperStatement = configuration.getMappedStatementMap().get(statementId); // 获取原始sql:select t.* from person t where t.id = #{id} and t.name = #{name} String sql = mapperStatement.getSql(); // 进行sql解析 // 将#{content} 替换为 占位符 ? // 将#{content} 中参数名称获取 BoundSql boundSql = getBoundSql(sql); // sql预编译 PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql()); // 进行参数设置 List<ParameterMapping> mappingList = boundSql.getMappingList(); // 获取入参类型 String parameterType = mapperStatement.getParameterType(); Class<?> parameterClass = getClassType(parameterType); // 此处使用fori,需要获取下标使用 for (int i = 0; i < mappingList.size(); i++) { ParameterMapping parameterMapping = mappingList.get(i); // 获取目标属性id String content = parameterMapping.getContent(); // 通过反射,获取参数中对应id的值 Field declaredField = parameterClass.getDeclaredField(content); declaredField.setAccessible(true); Object o = declaredField.get(params[0]); // 设置到参数中 // jdbc中,占位符从1开始 preparedStatement.setObject(i + 1, o); } // 进行结果获取 ResultSet resultSet = preparedStatement.executeQuery(); // 获取结果集类型 String resultType = mapperStatement.getResultType(); Class<?> resultClass = getClassType(resultType); // 进行结果集封装 List resultList = new ArrayList(); while (resultSet.next()) { // 获取元数据 ResultSetMetaData metaData = resultSet.getMetaData(); // 创建响应结果对象 Object result = resultClass.newInstance(); for (int i = 1; i < metaData.getColumnCount(); i++) { // 获取字段名称 String columnName = metaData.getColumnName(i); // 获取字段值 Object value = resultSet.getObject(columnName); // 通过内省方式设置值 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(result, value); } // 将结果入列表 resultList.add(result); } return resultList; } /** * 获取BoundSql对象 * * @param sql * @return */ private BoundSql getBoundSql(String sql) { // 标记处理类 ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler); // 解析之后的sql String parse = genericTokenParser.parse(sql); // 解析出的参数名称 List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings(); return new BoundSql(parse, parameterMappings); } /** * 获取clasName对应class对象 * * @param clasName * @return */ private Class<?> getClassType(String clasName) throws Exception { if (clasName != null) { return Class.forName(clasName); } return null; } }
sql解析工具
-
BoundSql
package model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import utils.ParameterMapping; import java.util.List; @Getter @Setter @AllArgsConstructor public class BoundSql { //解析后sql private String sql; //对应sql参数 private List<ParameterMapping> mappingList; } -
GenericTokenParser
/** * Copyright 2009-2017 the original author or authors. * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package utils; /** * @author Clinton Begin */ public class GenericTokenParser { private final String openToken; //开始标记 private final String closeToken; //结束标记 private final TokenHandler handler; //标记处理器 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } /** * 解析${}和#{} * @param text * @return * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。 * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现 */ public String parse(String text) { // 验证参数问题,如果是null,就返回空字符串。 if (text == null || text.isEmpty()) { return ""; } // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。 int start = text.indexOf(openToken, 0); if (start == -1) { return text; } // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder, // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码 char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; while (start > -1) { // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理 if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //重置expression变量,避免空指针或者老数据干扰。 if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + openToken.length(); int end = text.indexOf(closeToken, offset); while (end > -1) {////存在结束标记时 if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时 // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else {//不存在转义字符,即需要作为参数进行处理 expression.append(src, offset, end - offset); offset = end + closeToken.length(); break; } } if (end == -1) { // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { //首先根据参数的key(即expression)进行参数处理,返回?作为占位符 builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } } -
ParameterMapping
package utils; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @Getter @Setter @AllArgsConstructor public class ParameterMapping { //对应sql #{content} private String content; } -
TokenHandler
/** * Copyright 2009-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package utils; /** * @author Clinton Begin */ public interface TokenHandler { String handleToken(String content); } -
ParameterMappingTokenHandler
package utils; import java.util.ArrayList; import java.util.List; public class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是参数名称 #{id} #{username} public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { ParameterMapping parameterMapping = new ParameterMapping(content); return parameterMapping; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public void setParameterMappings(List<ParameterMapping> parameterMappings) { this.parameterMappings = parameterMappings; } } -
源码下载地址
https://files-cdn.cnblogs.com/files/u1314/customize-persistence.zip

浙公网安备 33010602011771号