1.自定义持久层框架思路分析
JDBC问题分析:
1.数据库配置信息存在硬编码问题。
2.频繁创建释放数据库链接
3.sql语句、设置参数、获取结果集,存在硬编码问题
4.手动封装结果集,较为繁琐。
解决方法:
- 利用配置文件解决数据库硬编码问题。
- 利用连接池解决频繁创建释放问题。
- 利用配置文件解决sql语句、设置参数、获取结果集硬编码问题。
- 利用反射和内省解决繁琐的封装问题。
思路:
使用端:
- 引入自定义持久层框架的jar包
- 提供两部分配置信息:数据库配置信息、sql配置信息(SQL语句,参数类型,返回值类型)
- 使用配置文件来提供这两部分配置信息:
(1)sqlMapConfig.xml : 存放数据库配置信息
(2)mapper.xml :存放sql配置信息
自定义持久层框架:
本质就是对JDBC代码进行了封装。 - 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。创建Resources类,方法:InputSteam getResourceAsSteam(String path)
- 创建两个javaBean:存放的就是对配置文件解析出来的内容。
Configuration:核心配置类,存放sqlMapConfig.xml解析出来的内容。
MappedStatement:映射配置类,存放mapper.xml解析出来的内容。 - 解析配置文件(dom4j):
创建类:SqlSessionFactoryBuilder,方法:build(InputSteam in)
(1)使用dom4j解析配置文件,将解析出来的内容封装到容器对象中。
(2)创建SqlSessionFactory对象;生产sqlSession(会话对象[工厂模式]) - 创建sqlSessionFactory接口及实现DefaultSqlSessionFactory。
第一:openSession():生产sqlSession - 创建SqlSession接口及实现DefaultSqlSession。定义对数据库CRUD的操作。
- 创建Executer接口及实现类SimpleExecuter实现类。
Query(Configuration,MappedStatement,Object... params):执行的就是JDBC代码
2.编写测试类
- 引入自定义持久层框架的依赖
com.learn
IPersistence
1.0-SNAPSHOT
- 创建sqlMapConfig.xml
- 创建UserMapper.xml
注意:namespace.id作为映射xml的唯一标识。例如:user.selectUserList
3. Resources类定义
新建一个模块,封装JDBC的代码。
创建一个Resources类
public class Resources {
// 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public static InputStream getResourceAsSteam(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
4. 容器对象定义
- 定义Configuration容器对象
public class Configuration {
private DataSource dataSource;
/*
* key: statementid value:封装好的mappedStatement对象
* */
Map mappedStatementMap = new HashMap<>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map getMappedStatementMap() {
return mappedStatementMap;
}
public void setMappedStatementMap(Map mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
}
- 定义MappedStatement映射对象
public class Configuration {
private DataSource dataSource;
/*
* key: statementid value:封装好的mappedStatement对象
* */
Map mappedStatementMap = new HashMap<>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map getMappedStatementMap() {
return mappedStatementMap;
}
public void setMappedStatementMap(Map mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
}
5. 解析配置文件mapper.xml 和 数据库连接配置文件sqlMapConfig.xml
(1)实现DefaultSqlSessionFactory的build()方法,返回一个SqlSessionFactory接口对象
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
// 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(in);
// 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
(2)创建类XMLConfigBuilder,对sqlMapConfig.xml配置文件进行解析
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
/**
* 该方法就是使用dom4j对配置文件进行解析,封装Configuration
*/
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
//
Element rootElement = document.getRootElement();
List list = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : list) {
String name = element.attributeValue("name");
String value = element.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.setDataSource(comboPooledDataSource);
//mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
List mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsSteam);
}
return configuration;
}
}
(3)创建类XMLMapperBuilder,对mapper.xml映射配置文件进行解析
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 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.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
String key = namespace+"."+id;
configuration.getMappedStatementMap().put(key,mappedStatement);
}
}
}
(4) 创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象,返回一个SqlSession的对象
public interface SqlSessionFactory {
public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
6. 会话对象sqlSession类定义
定义SqlSession接口并且实现接口DefaultSqlSession类
实现查询所有数据、根据条件查询耽搁数据、为Dao接口生成代理实现类
public interface SqlSession {
//查询所有
public List selectList(String statementid,Object... params) throws Exception;
//根据条件查询单个
public T selectOne(String statementid,Object... params) throws Exception;
//为Dao接口生成代理实现类
public T getMapper(Class> mapperClass);
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public List selectList(String statementid, Object... params) throws Exception {
//将要去完成对simpleExecutor里的query方法的调用
simpleExecutor simpleExecutor = new simpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List
最后调用jdbc底层执行sql,然后封装结果集,将数据进行返回。
创建执行器接口
public interface Executor {
public List query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;
}
实现query方法
public class simpleExecutor implements Executor {
@Override //user
public List query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. 设置参数
//获取到了参数的全路径
String paramterType = mappedStatement.getParamterType();
Class> paramtertypeClass = getClassType(paramterType);
List parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
//反射
Field declaredField = paramtertypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i+1,o);
}
// 5. 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class> resultTypeClass = getClassType(resultType);
ArrayList objects = new ArrayList<>();
// 6. 封装返回结果集
while (resultSet.next()){
Object o =resultTypeClass.newInstance();
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名
String columnName = metaData.getColumnName(i);
// 字段的值
Object value = resultSet.getObject(columnName);
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
objects.add(o);
}
return (List) objects;
}
private Class> getClassType(String paramterType) throws ClassNotFoundException {
if(paramterType!=null){
Class> aClass = Class.forName(paramterType);
return aClass;
}
return null;
}
/**
* 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
//标记处理类:配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//#{}里面解析出来的参数名称
List parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
return boundSql;
}
}
解析sql的工具类
- ParameterMappingTokenHandler:参数映射处理类,配置标记解析器来完成对占位符的解析处理工作
public class ParameterMappingTokenHandler implements TokenHandler {
private List parameterMappings = new ArrayList();
// 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 getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
- GenericTokenParser :这里的通用标记解析器处理的是SQL脚本中#{parameter}、${parameter}参数,根据给定TokenHandler(标记处理器)来进行处理,TokenHandler是标记真正的处理器,而本篇的解析器只是处理器处理的前提工序——解析,本类重在解析,而非处理,具体的处理会调用具体的TokenHandler的handleToken()方法来完成。
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:
(1)解析传进来的content内容,看是否有变量,如果有从对应参数类中获取这个属性的类型,
(2)解析其存在他其他如javaType等参数设置到ParameterMapping内部的Builder方法中
(3)通过Builder构建ParameterMapping
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- TokenHandler接口
public interface TokenHandler {
String handleToken(String content);
}
BoundSql工具类,存储xml中解析过后的sql字符串
public class BoundSql {
private String sqlText; //解析过后的sql
private List parameterMappingList = new ArrayList<>();
public BoundSql(String sqlText, List parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText() {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
public List getParameterMappingList() {
return parameterMappingList;
}
public void setParameterMappingList(List parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}
7. Client端运行测试
- 实体类User
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 + '\'' +
'}';
}
}
- IUserDao类
public interface IUserDao {
//查询所有用户
public List findAll() throws Exception;
//根据条件进行用户查询
public User findByCondition(User user) throws Exception;
}
- 测试运行
public class IPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用
User user = new User();
user.setId(1);
user.setUsername("张三");
/* User user2 = sqlSession.selectOne("user.selectOne", user);
System.out.println(user2);*/
/* List users = sqlSession.selectList("user.selectList");
for (User user1 : users) {
System.out.println(user1);
}*/
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List all = userDao.findAll();
for (User user1 : all) {
System.out.println(user1);
}
}
}