完整讲义、源码与答疑,欢迎关注我的微信公众号“守护之王觉行”,然后添加管理员微信获取,谢谢~❀❀
一、框架概念❀
1.什么是框架
框架的实质就是一项加载。框架的效果就是让项目具有了一定功能。就好像我们入手了一台摄像机去拍摄一部纪录片一样。摄像机就是框架,纪录片就是项目,具体怎么拍摄,还需要我们去操作和配置。放到编程项目里,框架就是引入的工具代码。
2.解决的问题
框架要解决的是技术整合问题。现在软件开发环境和软件规模非常大,我们不可能所有系统代码都从0开始敲。况且优秀的框架已经把基础技术都整合完了,我们只需要在它的基础上进一步开发即可。
3.怎么用框架
Java框架共性:
现成jar包,引入工程
对框架运行细节进行定制,一般通过xml配置文件(有格式规范)
在java程序中调用框架API(接口)
二、软件分层及常用框架❀
1.什么是分层
分层就是把代码按照功能不同进行分类,放入不同文件或文件夹中,常见有如下分层类型:
类型1:jsp+javabean(表现层+数据对象层,由表现层处理请求)
类型2:jsp+serlvet+javabean(表现层+请求处理曾+数据对象曾,由请求处理曾处理请求)
类型三,经典三层:表现层(jsp/html+controller层)+ 业务层(service层)+ 持久层(dao数据库交互层)
举例,MVC模式是对分层概念的一种实现,其中:
M(Model):业务模型+数据模型(service+dao)
V(View): 视图层,主要完成页面展示相关工作(jsp/html)
C(Controller): 控制层,一般处于请求层与服务层之间,只负责处理程序逻辑,完成数据的收发,不负责具体的业务实现。
2.分层的好处
代码清晰、解耦、便于维护,出现问题容易定位。
3.常见的框架
dao持久层:hibernate、mybatis
service业务层:spring框架(对象容器)
web表现层:springmvc框架
三、自定义持久层框架❀❀❀
1.原生JDBC操作数据库问题分析
JDBC(Java DataBase Connectivity) :Java数据库连接技术:具体讲就是通过Java连接广泛的数据库,并对表中数据执行增、删、改、查等操作的技术。不同的数据口需要不同的JDBC接口去连接数据库。以下是它的相关接口:
接口 | 作用 |
---|---|
Driver | 驱动接口,定义建立链接的方式 |
DriverManager | 工具类,用于管理驱动,可以获取数据库的链接 |
Connection | 表示Java与数据库建立的连接对象(接口) |
PreparedStatement | 发送SQL语句的工具 |
ResultSet | 结果集,用于获取查询语句的结果 |
package com.mybatis.dao;
import com.mybatis.pojo.User;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class UserDaoImpl implements UserDao {
/**
*问题一:数据库配置信息存在硬编码,后期想要更换需要改动代码,不方便
*解决:使用配置文件
**/
private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
private String username = "root";
private String password = "root";
public List<User> queryUserList() throws Exception {
List<User> userList = new ArrayList<User>();
Class.forName(driver);
/**
* 问题二:频繁获取/释放数据库连接,每次都要JDBC完成三段式握手,影响数据库和应用性能
* 解决:数据库连接池技术,C3P0,DRUID(阿里巴巴荣誉出品,号称前无古人后无来者世界最强没有之一)Druid德鲁伊特(古代凯尔特人的祭司)
*/
Connection connection = DriverManager.getConnection(url, username, password);
/**
* 问题三:sql语句硬编码,如果想要更换语句,后期难以维护
* 解决:若sql语句和java代码分离,比如sql写在配置文件中。
*/
String sql1 = "select * from user";
/**
* 问题四:sql语句where条件和占位符?一一对应,后期难以维护
* 解决:若sql语句和java代码分离,比如sql写在配置文件中。
*/
String sql2 = "select * from user where username=? and id=?";
//预处理sql语句
PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.setString(1, "tom");
preparedStatement2.setString(2, 2);
//执行查询
ResultSet resultSet = preparedStatement.executeQuery();
User user = null;
/**
* 问题五:结果集解析麻烦,查询列硬编码,后期维护麻烦
* 期望:如果单条记录直接返回实体对象,如果多条记录返回实体的集合。可以利用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射。
*/
//遍历结果集,封装结果实体
while(resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setSex(resultSet.getString("sex"));
user.setBirthday(resultSet.getDate("birthday"));
user.setAddress(resultSet.getString("address"));
userList.add(user);
}
//释放资源,返回结果集
resultSet.close();
preparedStatement.close();
connection.close();
return userList;
}
}
2.问题汇总与解决思路
1、问题一:数据库配置文件存在硬编码问题
解决:使用配置文件。
2、问题二:频繁获取/释放数据库连接,影响数据库和应用性能
解决:数据库连接池技术,C3P0,DRUID(阿里巴巴荣誉出品,号称前无古人后无来者世界最强没有之一)Druid,德鲁伊特(古代凯尔特人的祭司)
3、问题三:sql语句硬编码,后期难以维护
解决:sql语句和java代码分离,比如sql写在配置文件中。
4、问题四:sql语句where条件和占位符?一一对应,后期难以维护
解决:sql语句和java代码分离,比如sql写在配置文件中。
5、问题五:结果集解析麻烦,查询列硬编码,后期维护麻烦
解决:如果单条记录直接返回实体对象,如果多条记录返回实体的集合。可以利用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射。
3.自定义持久层框架设计
3.1使⽤端
提供核⼼配置⽂件:
sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
Mapper.xml : sql语句的配置⽂件信息
注意:因为数据源信息一般很少改动,查询语句一般经常改动,所以应当将两种配置信息写到两个配置文件中,方便解耦,防止管理混乱。
3.2框架端
3.2.1读取配置⽂件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可 以创建javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + “.” + id
(2)MappedStatement:存放sql语句、statement类型、输⼊参数java类型、输出参数java类型
3.2.2解析配置⽂件
创建sqlSessionFactoryBuilder类:
⽅法:sqlSessionFactory build():
第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中
第⼆:创建SqlSessionFactory的实现类DefaultSqlSession
3.2.3创建SqlSessionFactory:
⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
3.3.4创建sqlSession接⼝及实现类:
作用:封装crud(增删改查)⽅法
⽅法:selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作
涉及到的设计模式: Builder构建者设计模式、⼯⼚模式、代理模式
关于Java的反射机制、工厂模式、代理模式,推荐阅读以下文章:
Java–反射机制原理、几种Class获取方式及应用场景_吾日三省贾斯汀的博客-CSDN博客
4 自定义持久层框架优化
通过上述我们的⾃定义框架,我们解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连 接,硬编码,⼿动封装返回结果集等问题。
但是现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题?
问题如下:
- dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭 sqlsession)
- dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码
解决方案:
使用代理模式来创建DAO层接口的代理对象,代码如下。
sqlsession关键实现代码如下(完整项目代码获取方式已写在文章顶部):
package com.motalJava.sqlSession;
import com.motalJava.pojo.Configuration;
import com.motalJava.pojo.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
/**
* @author Hissen
* @date 2022/8/4 16:51
* @description
*/
public class DefaultSqlsession implements SqlSession {
private Configuration configuration;
public DefaultSqlsession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
//完成对simpleExecutor里的query方法的调用
simpleExecutor simpleExecutor = new simpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement,params);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementid, Object... params) throws Exception {
List<Object> object = selectList(statementid, params);
if(object.size() == 1){
return (T) object.get(0);
}else {
throw new RuntimeException("查询结果为空或返回过多");
}
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
//使用JDK动态代理为DAO接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlsession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*proxy:当前代理对象的应用;method:当前被调用方法的引用;args:传递的参数*/
/*底层依然是去执行JDBC代码,但是我们可以先根据传参不同,选择调用selectList或者selectOne,再让该方法去执行simpleExecutor.query完成底层调用*/
//为selectList或者selectOne准备参数,1:statementid,sql语句的唯一标识,由namespace.id组成
//由于invoke方法无法获得statementid值,所以我们需要将statementid的命名进行改动,让namespace.id = 接口全限定名.方法名,借助method获得想要的参数
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
//准备参数2: params = args
//获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
//判断是否进行了 泛型类型参数化,如果是,则调用list,否则是一个实体,调用one
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
}