目录
四、创建sqlSessionFactory对象,生产sqlSession
五、创建SqlSession接口及实现类DefaultSession
4.完善sqlSeesion和DefaultsqlSession类
使用JDBC的问题总结:
原始jdbc开发存在的问题如下:
1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能 少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成 pojo对象解析⽐较⽅便
解决思路
①使⽤数据库连接池初始化连接资源
②将sql语句抽取到xml配置⽂件中
③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射
自定义框架设计
使⽤端:
提供核⼼配置⽂件:
sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
Mapper.xml : sql语句的配置⽂件信息
框架端:
1.读取配置⽂件,读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建 javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id
(2)MappedStatement:sql语句、statement类型、输⼊参数java类型、输出参数java类型
2.解析配置⽂件
创建sqlSessionFactoryBuilder类:
⽅法:sqlSessionFactory build():
第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中
第⼆:创建SqlSessionFactory的实现类DefaultSqlSession
3.创建SqlSessionFactory:
⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
4.创建sqlSession接⼝及实现类:主要封装crud⽅法
⽅法:selectList(String statementId,Object param):查询所有 selectOne(String statementId,Object param):查询单个 具体实现:封装JDBC完成对数据库表的查询操作
涉及到的设计模式: Builder构建者设计模式、⼯⼚模式、代理模式
自定义框架的具体实现
使用端:
1.创建Persistence_test使用端的类
2.在resource文件夹下创建sqlMapConfig.xml
内容:
-
里面写着数据源的配置信息
-
还有就是UserMapper.xml的全路径
3.在resource文件夹下创建UserMapper.xml
内容:
-
里面包含我们的sql语句,不要忘记了初始化参数的类型和返回值结果的类型
-
我们的statementId由我们的namespace+id组成
自定义持久层框架本身(工程):本质就是对JDBC代码进行了封装
首先再创建一个类Persistence作为我们的持久层框架
总览文件结构:
一、完成Resource类的定义
1.1该类的作用:就是根据xml文件的路径,将配置文件加载成字节输入流,存储在内存中
1.2代码编写:
1.3使用端创建测试类,并引入框架的依赖
选中框架类,点击Maven中的Lifecycle中的install将其打包
然后在使用类Persistence中引入依赖
Maven中显示了,表明引入成功
编写使用端的代码:
注意:xml文件的名字一定要写正确,否则字节输入流找不到文件
二、创建两个容器,存放文件解析后的内容
MappedStatement类:存放UserMapper的解析内容
2.1在框架类中创建MappedStatement类,并完成代码编写
注意:该类包含四个私有属性:id,resultType,paramterType,sql,并且生成属性的get和set方法
Configuration类:存放数据源解析后的内容以及sql的配置内容
这里我们就会思考一个问题,为什么这个类还要存放sql的配置信息呢?因为我们调用JDBC完成CRUD的时候需要传入数据源信息,sql语句信息,还有就是可变参数(用户输入的参数),而我们在Configuration类中将数据源信息和sql语句信息封装在一个Map集合中,我们就可以在形式上少传一个参数。
2.2代码的编写:
注意:要生成数据源和Map集合的get和set方法
三、解析配置文件sqlMapConfigxml
3.1创建SqlSessionFactoryBuilder类,该类用来创建工厂对象
3.2创建XMLConfigBuilder类,该类用dom4j来解析Configuration
过程:
-
首先获得文本对象
-
文本对象拿到根标签<configuration>
-
选择根标签中的属性<property>生成链表list,为了获取里面的值
-
创建Properties对象,
-
遍历list,将name和value存储在Peoperties中
-
最后创建连接池对象,设置数据源
3.3创建XMLMapperBuilder类,该类用dom4j解析sqlMapConfig.xml
过程:
-
首先获得Configuration,因为等下要将解析出来的sql属性封装到MappedStatement中,然后在封装到Configuration中。
-
解析sqlMapConfig.xml文件,还是一样获得文本对象
-
获得文本中的根对象<mapper>
-
根对象获得namesapce属性值
-
根对象获得结点<select>链表list(因为有多个select)
-
遍历list获得属性:id,resultType,paramterType,sqlText
-
将这些属性封装成MappedStatement对象
-
设置key值为:namespace.id
-
再将MappedStatement对象封装成configuration对象
3.4进一步完成XMlConfigBuilder类,完成对UserMapper的解析
过程:
-
首先获得sqlMpaconfig中的mapper属性,因为mapper属性中有UserMapper的全路径
-
获取resource中UserMapper的路径值
-
将全路径转换成字节输入流
-
调用XMLMapperBuilder类,用dom4j解析UserMapper文件
-
最后返回一个configuration对象
四、创建sqlSessionFactory对象,生产sqlSession
4.1创建SqlsessionFactory接口
接口调用Sqlsession中的openSession方法
4.2创建DefaultSqlsessionFactory类来实现SqlsessionFactory接口
这个类中要有一个有参构造,因为要传入configuration对象,进行底层的JDBC代码的实现
4.3创建SqlSessionFactory接口
五、创建SqlSession接口及实现类DefaultSession
该方法要传statementId,statementId由namespace+id组成,还要有可变参
5.1在Persistence_test创建User实体
5.2编写Executor接口,添加query方法
5.3编写simpExecutor类实现Executor接口,重写query方法
其中要编写getBoundSql方法:
方法的作用就是:1.将#{}用?代替 2.解析出#{}中值得存储
编写BoundSql类:
5.4编写DefaultSqlsession类实现SqlSession
过程:
-
获得驱动连接
-
获得转换后的sql语句
获取到全路径的实体类的对象
编写getClassType方法:
完成DefaultSqlsession类的编写
六、测试
运行结果:
六、总结遇到的问题:
-
自己编写的类中要明白形参是什么,以及返回值是什么
-
根据列数循环的时候,少了一个=,于是封装结果集的时候只封装了id的属性值,而没有封装username的属性值,所以以后写代码要会自己调试找到问题所在,还有就是要细心!
完善自定义持久层框架
添加增加删除更新方法
1. 首先编写UserMapper.xml
2. 编写IUserDao接口
3.完善XMLMapperBuilder类
为什么要完善XMLMapperBuilder类?
因为我们之前编写的这个类只是抽取了select标签,现在我们要实现增删改还要抽取insert等标签
4.完善sqlSeesion和DefaultsqlSession类
5.完成初始化参数和结果集的封装
初始化参数:
public class ParamterHander {
private Configuration configuration;
private MappedStatement mappedStatement;
private Object[] params;
public ParamterHander(Configuration configuration, MappedStatement mappedStatement, Object[] params) {
this.configuration = configuration;
this.mappedStatement = mappedStatement;
this.params = params;
}
//该方法为了得到参数已经转换好的sql预处理对象
public PreparedStatement getPreparedStatement() throws Exception {
//1.注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
//2.获取sql语句:select * from user where id = #{id} and username = #{username}
//其中要对#{}中的参数进行解析
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
//3.获得预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
//4.设置参数
//获取到参数的全路径
String paramterType = mappedStatement.getParamterType();
Class<?> paramterClass = getClassType(paramterType);//获取传入参数类型
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();//获取到#{}中的值
//反射
Field declaredField = paramterClass.getDeclaredField(content);//根据class对象中的属性名获取到具体属性
declaredField.setAccessible(true);//防止该类的属性私有的,可以暴力访问
Object o = declaredField.get(params[0]);//这里拿到的就是具体属性的值
preparedStatement.setObject(i + 1, o);
}
return preparedStatement;
}
/*
* 完成对#{}的解析工作:1、将#{}用?代替 2.解析出#{}中值得存储
*
* */
private BoundSql getBoundSql (String sql){
//标记处理类:配合标记解析器完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
// 标记解析器
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//解析出来的#{}中的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
}
结果集封装:
public class resultHander {
private MappedStatement mappedStatement;
private ResultSet resultSet;
public resultHander(MappedStatement mappedStatement, ResultSet resultSet) {
this.mappedStatement = mappedStatement;
this.resultSet = resultSet;
}
public <E> List<E> getresultHander() throws Exception {
//拿到返回结果的全路径,并获得对应的class对象
String resultType = mappedStatement.getResultType();//拿到返回结果集的全路径
Class<?> resultTypeClass = getClassType(resultType);//转换为class对象
ArrayList<Object> objects = new ArrayList<>();
//6.封装返回结果集
while(resultSet.next()){
Object o = resultTypeClass.newInstance();//获得class对象的具体实现
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <=metaData.getColumnCount() ; i++) {//根据列数进行循环,例如id,name就是两列
//字段名称
String columnName = metaData.getColumnName(i);
//根据字段名称获得值
Object value = resultSet.getObject(columnName);
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);//class对象对columnName生成读写方法
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);//将具体的value值封装在了o对象中
}
objects.add(o);
}
return (List<E>) objects;
}
}