JavaWeb——MyBatis框架之执行过程原理与解析(通过自定义MyBatis查询所有操作的实现来观察整个过程)
目录
2.1 根据MyBatisTest测试类中缺少的创建接口和类
2.3 补充SqlSessionFactoryBuilder类创建SqlSessionFactory工厂
1 MyBatis执行过程分析
通过上一博文,我们了解了MyBatis的入门,知道了怎么搭建环境及最基本的使用,那么,本次我们结合上一博文的案例实战,进行更深入的分析MyBatis的执行过程,MyBatis使用代理dao方式进行增删改查时做了哪些事呢?其实就是两件:
- 1)创建代理对象;
- 2)在代理对象中调用selectList。
1.1 MyBatis执行查询所有的过程分析
1.2 MyBatis创建代理对象的分析
2 自定义MyBatis
在入门实战案例基础上修改,删除掉pom.xml中MyBatis的坐标,下面按步骤搞起:
2.1 根据MyBatisTest测试类中缺少的创建接口和类
MyBatisTest测试类中涉及的类如下,我们需要自定义,先创建,让MyBatisTest测试类不报错,然后再下一步中填充内容:
- Class Resources
- Class SqlSessionFactoryBuilder
- Interface SqlSessionFactory
- Interface SqlSession
为了便于观察,将MyBatisTest中的代码贴一下,整体实现思路就是如下注释的:
-
public class MyBatisTest {
-
//MyBatis入门案例
-
@Test
-
public void test() throws Exception{
-
//1、读取配置文件
-
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
-
//2、创建SqlSessionFactory工厂
-
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
-
SqlSessionFactory factory = builder.build(in);
-
//3、使用工厂生产SqlSession对象
-
SqlSession session = factory.openSession();
-
//4、使用SqlSession创建Dao接口的代理对象
-
UserDao userDao = session.getMapper(UserDao.class);
-
//5、使用代理对象执行方法
-
List<User> users = userDao.findAll();
-
for (User user : users) {
-
System.out.println(user);
-
}
-
//6、释放资源
-
session.close();
-
in.close();
-
}
-
}
1)创建Resources类
-
//使用类加载器读取配置文件的类
-
public class Resources {
-
//根据传入的参数获取一个字节输入流
-
public static InputStream getResourceAsStream(String filePath){
-
return Resources.class.getClassLoader().getResourceAsStream(filePath);
-
}
-
}
2)创建SqlSessionFactoryBuilder类
-
//用于创建一个SqlSessionFactory对象
-
public class SqlSessionFactoryBuilder {
-
//根据参数字节输入流,构建一个SqlSessionFactory工厂
-
public SqlSessionFactory build(InputStream config){
-
return null;
-
}
-
}
3)创建SqlSessionFactory接口
-
public interface SqlSessionFactory {
-
//用于打开一个新的SqlSession对象
-
public SqlSession openSession();
-
}
4)创建SqlSession接口
-
//自定义MyBatis中和数据库交互的核心类
-
//可以创建dao接口的代理对象
-
public interface SqlSession {
-
//根据参数创建一个代理对象
-
//daoInterfaceClass为dao的接口字节码
-
<T> T getMapper(Class<T> daoInterfaceClass);
-
//释放资源
-
void close();
-
}
2.2 解析XML工具类
此处不作为重点,主要用来解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方:
1)在com.winter.mybatis.utils包下新建XMLConfigBuilder类:
-
//用于解析配置文件
-
public class XMLConfigBuilder {
-
-
/**
-
* 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
-
* 使用的技术:
-
* dom4j+xpath
-
*/
-
public static Configuration loadConfiguration(InputStream config){
-
try{
-
//定义封装连接信息的配置对象(mybatis的配置对象)
-
Configuration cfg = new Configuration();
-
-
//1.获取SAXReader对象
-
SAXReader reader = new SAXReader();
-
//2.根据字节输入流获取Document对象
-
Document document = reader.read(config);
-
//3.获取根节点
-
Element root = document.getRootElement();
-
//4.使用xpath中选择指定节点的方式,获取所有property节点
-
List<Element> propertyElements = root.selectNodes("//property");
-
//5.遍历节点
-
for(Element propertyElement : propertyElements){
-
//判断节点是连接数据库的哪部分信息
-
//取出name属性的值
-
String name = propertyElement.attributeValue("name");
-
if("driver".equals(name)){
-
//表示驱动
-
//获取property标签value属性的值
-
String driver = propertyElement.attributeValue("value");
-
cfg.setDriver(driver);
-
}
-
if("url".equals(name)){
-
//表示连接字符串
-
//获取property标签value属性的值
-
String url = propertyElement.attributeValue("value");
-
cfg.setUrl(url);
-
}
-
if("username".equals(name)){
-
//表示用户名
-
//获取property标签value属性的值
-
String username = propertyElement.attributeValue("value");
-
cfg.setUsername(username);
-
}
-
if("password".equals(name)){
-
//表示密码
-
//获取property标签value属性的值
-
String password = propertyElement.attributeValue("value");
-
cfg.setPassword(password);
-
}
-
}
-
//取出mappers中的所有mapper标签,判断他们使用了resource还是class属性
-
List<Element> mapperElements = root.selectNodes("//mappers/mapper");
-
//遍历集合
-
for(Element mapperElement : mapperElements){
-
//判断mapperElement使用的是哪个属性
-
Attribute attribute = mapperElement.attribute("resource");
-
if(attribute != null){
-
System.out.println("使用的是XML");
-
//表示有resource属性,用的是XML
-
//取出属性的值
-
String mapperPath = attribute.getValue();//获取属性的值"com/winter/dao/IUserDao.xml"
-
//把映射配置文件的内容获取出来,封装成一个map
-
Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
-
//给configuration中的mappers赋值
-
cfg.setMappers(mappers);
-
}else{
-
// System.out.println("使用的是注解");
-
// //表示没有resource属性,用的是注解
-
// //获取class属性的值
-
// String daoClassPath = mapperElement.attributeValue("class");
-
// //根据daoClassPath获取封装的必要信息
-
// Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
-
// //给configuration中的mappers赋值
-
// cfg.setMappers(mappers);
-
}
-
}
-
//返回Configuration
-
return cfg;
-
}catch(Exception e){
-
throw new RuntimeException(e);
-
}finally{
-
try {
-
config.close();
-
}catch(Exception e){
-
e.printStackTrace();
-
}
-
}
-
-
}
-
-
/**
-
* 根据传入的参数,解析XML,并且封装到Map中
-
* @param mapperPath 映射配置文件的位置
-
* @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
-
* 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
-
*/
-
private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
-
InputStream in = null;
-
try{
-
//定义返回值对象
-
Map<String,Mapper> mappers = new HashMap<String,Mapper>();
-
//1.根据路径获取字节输入流
-
in = Resources.getResourceAsStream(mapperPath);
-
//2.根据字节输入流获取Document对象
-
SAXReader reader = new SAXReader();
-
Document document = reader.read(in);
-
//3.获取根节点
-
Element root = document.getRootElement();
-
//4.获取根节点的namespace属性取值
-
String namespace = root.attributeValue("namespace");//是组成map中key的部分
-
//5.获取所有的select节点
-
List<Element> selectElements = root.selectNodes("//select");
-
//6.遍历select节点集合
-
for(Element selectElement : selectElements){
-
//取出id属性的值 组成map中key的部分
-
String id = selectElement.attributeValue("id");
-
//取出resultType属性的值 组成map中value的部分
-
String resultType = selectElement.attributeValue("resultType");
-
//取出文本内容 组成map中value的部分
-
String queryString = selectElement.getText();
-
//创建Key
-
String key = namespace+"."+id;
-
//创建Value
-
Mapper mapper = new Mapper();
-
mapper.setQueryString(queryString);
-
mapper.setResultType(resultType);
-
//把key和value存入mappers中
-
mappers.put(key,mapper);
-
}
-
return mappers;
-
}catch(Exception e){
-
throw new RuntimeException(e);
-
}finally{
-
in.close();
-
}
-
}
-
}
2)在com.winter.cfg包下创建XMLConfigBuilder类需要的Configuration类和Mapper类:
【Configuration】:
-
public class Configuration {
-
private String driver;
-
private String url;
-
private String username;
-
private String password;
-
private Map<String,Mapper> mappers;
-
public String getDriver() {
-
return driver;
-
}
-
-
public void setDriver(String driver) {
-
this.driver = driver;
-
}
-
-
public String getUrl() {
-
return url;
-
}
-
-
public void setUrl(String url) {
-
this.url = url;
-
}
-
-
public String getUsername() {
-
return username;
-
}
-
-
public void setUsername(String username) {
-
this.username = username;
-
}
-
-
public String getPassword() {
-
return password;
-
}
-
-
public void setPassword(String password) {
-
this.password = password;
-
}
-
-
public Map<String, Mapper> getMappers() {
-
return mappers;
-
}
-
-
public void setMappers(Map<String, Mapper> mappers) {
-
this.mappers.putAll(mappers); //此处需要使用追加方式,避免覆盖掉
-
}
-
}
【Mapper类】:用于封装执行的SQL语句和结果类型的全限定类名
-
//用于封装执行的SQL语句和结果类型的全限定类名
-
public class Mapper {
-
private String queryString;//sql
-
private String resultType;// 实体类的全限定类名
-
-
public String getQueryString() {
-
return queryString;
-
}
-
-
public void setQueryString(String queryString) {
-
this.queryString = queryString;
-
}
-
-
public String getResultType() {
-
return resultType;
-
}
-
-
public void setResultType(String resultType) {
-
this.resultType = resultType;
-
}
-
}
2.3 补充SqlSessionFactoryBuilder类创建SqlSessionFactory工厂
补充的SqlSessionFactory类如下:
-
//用于创建一个SqlSessionFactory对象
-
public class SqlSessionFactoryBuilder {
-
//根据参数字节输入流,构建一个SqlSessionFactory工厂
-
public SqlSessionFactory build(InputStream config){
-
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
-
return new DefaultSqlSessionFactory(cfg);
-
}
-
}
这里涉及了两个实现类:
【DefaultSqlSessionFactory】:SqlSessionFactory接口的实现类,openSession方法用于创建一个新的操作数据库对象
-
//SqlSessionFactory接口的实现类
-
public class DefaultSqlSessionFactory implements SqlSessionFactory {
-
private Configuration cfg;
-
-
public DefaultSqlSessionFactory(Configuration cfg) {
-
this.cfg = cfg;
-
}
-
-
//用于创建一个新的操作数据库对象
-
@Override
-
public SqlSession openSession() {
-
return new DefaultSqlSession(cfg);
-
}
-
}
【DefaultSqlSession】:SqlSession接口的实现类,用于创建代理对象及关闭资源
-
public class DefaultSqlSession implements SqlSession {
-
private Configuration cfg;
-
-
public DefaultSqlSession(Configuration cfg) {
-
this.cfg = cfg;
-
}
-
-
//用于创建代理对象
-
@Override
-
public <T> T getMapper(Class<T> daoInterfaceClass) {
-
return null;
-
}
-
-
//释放资源
-
@Override
-
public void close() {
-
-
}
-
}
到这里上述类之间就建立起来了联系:
- 1)读取配置文件,用到io里面的Resources类;
- 2)读出来的流交给构建者;
- 3)构建者使用工具类构建了工厂对象;
- 4)工厂对象的openSession提供了Session方法。
接下来需要在方法中实现创建代理对象,和查询所有的操作。
2.4 实现基于XML的查询所有操作
1)DefaultSqlSession实现类中getMapper方法创建代理对象
-
public class DefaultSqlSession implements SqlSession {
-
private Configuration cfg;
-
private Connection connection;
-
-
public DefaultSqlSession(Configuration cfg) {
-
this.cfg = cfg;
-
this.connection = DataSourceUtil.getConnection(cfg);
-
}
-
-
//用于创建代理对象
-
@Override
-
public <T> T getMapper(Class<T> daoInterfaceClass) {
-
return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
-
new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
-
}
-
-
//释放资源
-
@Override
-
public void close() {
-
if(connection!=null){
-
try {
-
connection.close();
-
} catch (SQLException throwables) {
-
throwables.printStackTrace();
-
}
-
}
-
-
}
-
}
【MapperProxy类】:用于进行具体的方法增强,通过key获取Mapper对象,再创建Executor执行查询操作
-
public class MapperProxy implements InvocationHandler {
-
//key 是全限定类名+方法名
-
private Map<String, Mapper> mappers;
-
private Connection conn;
-
-
public MapperProxy(Map<String, Mapper> mappers, Connection conn) {
-
this.mappers = mappers;
-
this.conn = conn;
-
}
-
-
//用于对方法进行增强
-
//调用selectList方法
-
@Override
-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
//1、获取方法名
-
String methodName = method.getName();
-
//2、获取方法所在类的名称
-
String className = method.getDeclaringClass().getName();
-
//3、组合key
-
String key = className+"."+methodName;
-
//4、获取mappers中的Mapper对象
-
Mapper mapper = mappers.get(key);
-
//5、判断是否有mapper
-
if(mapper==null){
-
throw new IllegalArgumentException("传入的参数有误");
-
}
-
//6、调用工具类执行查询所有
-
return new Executor().selectList(mapper,conn);
-
}
-
}
【Executor类】:
-
//负责执行SQL语句,并且封装结果集
-
public class Executor {
-
-
public <E> List<E> selectList(Mapper mapper, Connection conn) {
-
PreparedStatement pstm = null;
-
ResultSet rs = null;
-
try {
-
//1.取出mapper中的数据
-
String queryString = mapper.getQueryString();//select * from user
-
String resultType = mapper.getResultType();//com.itheima.domain.User
-
Class domainClass = Class.forName(resultType);
-
//2.获取PreparedStatement对象
-
pstm = conn.prepareStatement(queryString);
-
//3.执行SQL语句,获取结果集
-
rs = pstm.executeQuery();
-
//4.封装结果集
-
List<E> list = new ArrayList<E>();//定义返回值
-
while(rs.next()) {
-
//实例化要封装的实体类对象
-
E obj = (E)domainClass.newInstance();
-
-
//取出结果集的元信息:ResultSetMetaData
-
ResultSetMetaData rsmd = rs.getMetaData();
-
//取出总列数
-
int columnCount = rsmd.getColumnCount();
-
//遍历总列数
-
for (int i = 1; i <= columnCount; i++) {
-
//获取每列的名称,列名的序号是从1开始的
-
String columnName = rsmd.getColumnName(i);
-
//根据得到列名,获取每列的值
-
Object columnValue = rs.getObject(columnName);
-
//给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
-
PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
-
//获取它的写入方法
-
Method writeMethod = pd.getWriteMethod();
-
//把获取的列的值,给对象赋值
-
writeMethod.invoke(obj,columnValue);
-
}
-
//把赋好值的对象加入到集合中
-
list.add(obj);
-
}
-
return list;
-
} catch (Exception e) {
-
throw new RuntimeException(e);
-
} finally {
-
release(pstm,rs);
-
}
-
}
-
-
private void release(PreparedStatement pstm,ResultSet rs){
-
if(rs != null){
-
try {
-
rs.close();
-
}catch(Exception e){
-
e.printStackTrace();
-
}
-
}
-
-
if(pstm != null){
-
try {
-
pstm.close();
-
}catch(Exception e){
-
e.printStackTrace();
-
}
-
}
-
}
-
}
【DataSourceUtil类】:用于创建数据源的工具类
-
//用于创建数据源的工具类
-
public class DataSourceUtil {
-
//用于获取连接
-
public static Connection getConnection(Configuration cfg){
-
try {
-
Class.forName(cfg.getDriver());
-
return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
-
}catch (Exception e){
-
throw new RuntimeException(e);
-
}
-
}
-
}
【实现效果】:
终于搞完了,以上主要用来深入了解MyBatis的执行过程,博主也是根据教程一步步做下来的,需要源码的话传送门在此。
作于202009081645,已归档
———————————————————————————————————
本文为博主原创文章,转载请注明出处!
若本文对您有帮助,轻抬您发财的小手,关注/评论/点赞/收藏,就是对我最大的支持!
祝君升职加薪,鹏程万里!
文章来源: winter.blog.csdn.net,作者:Winter_world,版权归原作者所有,如需转载,请联系作者。
原文链接:winter.blog.csdn.net/article/details/108462406
- 点赞
- 收藏
- 关注作者
评论(0)