基于原生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);
}
}
}
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
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
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;
}
}
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
原文:https://www.cnblogs.com/u1314/p/14618742.html