手撸Mybatis(三)——收敛SQL操作到SqlSession

本专栏的源码:https://gitee.com/dhi-chen-xiaoyang/yang-mybatis。

引言

在上一章中,我们实现了读取mapper配置并构造相关的mapper代理对象,读取mapper.xml文件中的sql信息等操作,现在,在上一章的基础上,我们接着开始链接数据库,通过封装JDBC,来实现我们数据库操作。

数据库准备

我们创建一个user表,用于后续进行测试,user表的结构如下图所示:

user表的内容如下:

添加User类

我们根据表结构,创建对应的user类,user类的结构如下:

json 复制代码
package com.yang.mybatis.test;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private Integer id;
    
    private String userName;
    
    private String password;
    
    private Integer age;
    
    private Date createTime;

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
JDBC基础操作

在使用jdbc之前,我们先引入mysql的依赖

json 复制代码
<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

jdbc的操作,一般分为下面几个步骤:

1)加载JDBC驱动程序

2)创建数据库链接

3)创建一个preparedStatement

4)执行SQL语句

5)遍历数据集

6)处理异常,关闭JDBC对象资源

示例代码如下:

json 复制代码
 public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false";
        String username = "用户名";
        String password = "密码";

        Connection conn = null;
        try {
           // 1. 加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
           // 2. 创建数据库链接
            conn = DriverManager.getConnection(url, username, password);
            conn.setAutoCommit(false);

           // 3. 创建preparedStatement
            String sql = "select user_name from user where id = ?";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setInt(1, 1);
            // 4. 执行sql
           ResultSet resultSet = preparedStatement.executeQuery();
           // 5. 遍历结果
           if (resultSet.next()) {
                System.out.println(resultSet.getString(1));
            }
            conn.commit();
        } catch (SQLException | ClassNotFoundException e) {
            conn.rollback();
            e.printStackTrace();
        } finally {
          // 6. 释放连接
            conn.close();
        }
    }

执行上述代码,结果如下:

将sql操作,收敛到SqlSession

在上一章,当我们调用mapper的方法时,最终是通过在MapperProxy中获取对应的MybatisStatement,然后打印出sql信息的,但是如果后续操作数据库是,也在MapperProxy中执行sql的话,不太方便管理。因此,我们添加一个IMybatisSqlSession类,后续对于数据库的操作,收敛到此类进行。

首先,我们添加IMybatisSqlSession:

json 复制代码
package com.yang.mybatis.session;

public interface IMybatisSqlSession {

    <T> T execute(String method, Object parameter);

    <T> T getMapper(Class<T> type);
}

然后添加其默认实现:

json 复制代码
package com.yang.mybatis.session;

import com.yang.mybatis.proxy.MapperProxyFactory;

public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSession(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public <T> T execute(String method, Object parameter) {
        return (T)("你被代理了!" + method + ",入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return (T) mapperProxyFactory.newInstance(type, this);
    }
}

添加IMybatisSqlSession的工厂接口

json 复制代码
package com.yang.mybatis.session;

public interface MybatisSqlSessionFactory {
    IMybatisSqlSession openSession();
}

添加工厂的默认实现:

json 复制代码
package com.yang.mybatis.session;

import com.yang.mybatis.proxy.MapperProxyFactory;

public class DefaultMybatisSqlSessionFactory implements IMybatisSqlSessionFactory {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSessionFactory(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public IMybatisSqlSession openSession() {
        return new DefaultMybatisSqlSession(mapperProxyFactory);
    }
}

修改MapperProxyFactory,在新建MapperProxy的时候,将imybatisSqlSession传递给MapperProxy。

json 复制代码
package com.yang.mybatis.proxy;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.session.IMybatisSqlSession;

import java.lang.reflect.Proxy;

public class MapperProxyFactory {
    private MybatisConfiguration mybatisConfiguration;


    public MapperProxyFactory(MybatisConfiguration mybatisConfiguration) {
        this.mybatisConfiguration = mybatisConfiguration;
    }

    public Object newInstance(Class mapperType, IMybatisSqlSession mybatisSqlSession) {
        MapperProxy mapperProxy = new MapperProxy(mapperType, mybatisSqlSession);
        return Proxy.newProxyInstance(mapperType.getClassLoader(),
                new Class[]{mapperType},
                mapperProxy);
    }

    public MybatisConfiguration getMybatisConfiguration() {
        return mybatisConfiguration;
    }

    public void setMybatisConfiguration(MybatisConfiguration mybatisConfiguration) {
        this.mybatisConfiguration = mybatisConfiguration;
    }
}

然后修改MapperProxy,在真正执行的时候,通过iMybatisSqlSession的execute,来执行sql操作,以此实现sql操作由iMybatisSqlSession来统一管理。

json 复制代码
package com.yang.mybatis.proxy;

import com.yang.mybatis.session.IMybatisSqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MapperProxy<T> implements InvocationHandler {

    private Class<T> mapperInterface;

    private IMybatisSqlSession sqlSession;

    public MapperProxy(Class<T> mapperInterface, IMybatisSqlSession mybatisSqlSession) {
        this.mapperInterface = mapperInterface;
        this.sqlSession = mybatisSqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        String methodName = this.mapperInterface.getName() + "." + method.getName();
        return sqlSession.execute(methodName, args);
    }
}

最后,我们添加sqlSession工厂类的创建者,通过创建者模式,来创建SqlSession工厂

json 复制代码
package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.config.parser.IMybatisConfigurationParser;
import com.yang.mybatis.config.parser.IMybatisMapperParser;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MybatisSqlSessionFactoryBuilder {
    private IMybatisConfigurationParser mybatisConfigurationParser;

    private IMybatisMapperParser mybatisMapperParser;

    private String configPath;

    public IMybatisSqlSessionFactory buildSqlSessionFactory() {
        if (configPath == null || configPath.isEmpty()) {
            throw new RuntimeException("配置文件路径不合法==========");
        }
        if (this.mybatisMapperParser == null || this.mybatisConfigurationParser == null) {
            throw new RuntimeException("缺少解析器=======");
        }
        MybatisConfiguration mybatisConfiguration = mybatisConfigurationParser.parser(configPath);
        List<String> mapperPaths = mybatisConfiguration.getMapperPaths();
        for (String mapperPath : mapperPaths) {
            List<MybatisSqlStatement> mybatisSqlStatements = this.mybatisMapperParser.parseMapper(mapperPath);
            Map<String, List<MybatisSqlStatement>> mapperNameGroupMap = mybatisSqlStatements.stream()
                    .collect(Collectors.groupingBy(MybatisSqlStatement::getNamespace));

            for (Map.Entry<String, List<MybatisSqlStatement>> entry : mapperNameGroupMap.entrySet()) {
                String mapperName = entry.getKey();
                List<MybatisSqlStatement> sqlSessionList = entry.getValue();
                mybatisConfiguration.putMybatisSqlStatementList(mapperName, sqlSessionList);
            }
        }

        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(mybatisConfiguration);
        return new DefaultMybatisSqlSessionFactory(mapperProxyFactory);
    }

    public MybatisSqlSessionFactoryBuilder setConfigPath(String configPath) {
        this.configPath = configPath;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisConfigurationParser(IMybatisConfigurationParser iMybatisConfigurationParser) {
        this.mybatisConfigurationParser = iMybatisConfigurationParser;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisMapperParser(IMybatisMapperParser iMybatisMapperParser) {
        this.mybatisMapperParser = iMybatisMapperParser;
        return this;
    }
}

添加测试代码,进行测试:

json 复制代码
package com.yang.mybatis.test;

import com.yang.mybatis.config.parser.XmlMybatisConfigurationParser;
import com.yang.mybatis.config.parser.XmlMybatisMapperParser;
import com.yang.mybatis.session.IMybatisSqlSession;
import com.yang.mybatis.session.IMybatisSqlSessionFactory;
import com.yang.mybatis.session.MybatisSqlSessionFactoryBuilder;


public class Main {
    public static void main(String[] args) {
        String configPath = "mybatis-config.xml";
        IMybatisSqlSessionFactory mybatisSqlSessionFactory = new MybatisSqlSessionFactoryBuilder()
                .setMybatisMapperParser(new XmlMybatisMapperParser())
                .setMybatisConfigurationParser(new XmlMybatisConfigurationParser())
                .setConfigPath(configPath)
                .buildSqlSessionFactory();

        IMybatisSqlSession mybatisSqlSession = mybatisSqlSessionFactory.openSession();

        IUserMapper userMapper = mybatisSqlSession.getMapper(IUserMapper.class);
        System.out.println(userMapper.queryUserName(1));
    }
}

SqlSession获取执行方法对应的sql语句

首先,修改MybatisConfiguration,之前是将每一个mapper和他所拥有的MybatisSqlStatement列表关联起来,现在感觉粒度太大,因此,该为每一个mapper的方法和对应的MybatisSqlStatement关联。

json 复制代码
package com.yang.mybatis.config;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MybatisConfiguration implements Serializable {
    private Map<String, MybatisEnvironment> id2EnvironmentMap = new HashMap<>();

    private MybatisEnvironment defaultMybatisEnvironment;

    private List<String> mapperPaths = new ArrayList<>();

    private Map<String, MybatisSqlStatement> mapperMethod2SqlStatementsMap = new HashMap<>();

    public void putMapperMethod2MybatisSqlStatement(String mapperMethod, MybatisSqlStatement mybatisSqlStatement) {
        this.mapperMethod2SqlStatementsMap.put(mapperMethod, mybatisSqlStatement);
    }

    public MybatisSqlStatement getMybatisSqlStatement(String mapperMethod) {
        return this.mapperMethod2SqlStatementsMap.get(mapperMethod);
    }

    public Map<String, MybatisSqlStatement> getMapperMethod2SqlStatementsMap() {
        return this.mapperMethod2SqlStatementsMap;
    }


    public void addEnvironment(String id, MybatisEnvironment mybatisEnvironment) {
        this.id2EnvironmentMap.put(id, mybatisEnvironment);
    }

    public MybatisEnvironment getEnvironment(String id) {
        return id2EnvironmentMap.get(id);
    }

    public MybatisEnvironment getDefaultMybatisEnvironment() {
        return defaultMybatisEnvironment;
    }

    public void setDefaultMybatisEnvironment(MybatisEnvironment defaultMybatisEnvironment) {
        this.defaultMybatisEnvironment = defaultMybatisEnvironment;
    }

    public void addMapperPath(String mapperPath) {
        this.mapperPaths.add(mapperPath);
    }

    public List<String> getMapperPaths() {
        return this.mapperPaths;
    }

    public List<MybatisEnvironment> getEnvironments() {
        return new ArrayList<>(id2EnvironmentMap.values());
    }

}

然后修改MybatisSqlSessionFactoryBuilder:

json 复制代码
package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.config.parser.IMybatisConfigurationParser;
import com.yang.mybatis.config.parser.IMybatisMapperParser;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.util.List;

public class MybatisSqlSessionFactoryBuilder {
    private IMybatisConfigurationParser mybatisConfigurationParser;

    private IMybatisMapperParser mybatisMapperParser;

    private String configPath;

    public IMybatisSqlSessionFactory buildSqlSessionFactory() {
        if (configPath == null || configPath.isEmpty()) {
            throw new RuntimeException("配置文件路径不合法==========");
        }
        if (this.mybatisMapperParser == null || this.mybatisConfigurationParser == null) {
            throw new RuntimeException("缺少解析器=======");
        }
        MybatisConfiguration mybatisConfiguration = mybatisConfigurationParser.parser(configPath);
        List<String> mapperPaths = mybatisConfiguration.getMapperPaths();
        for (String mapperPath : mapperPaths) {
            List<MybatisSqlStatement> mybatisSqlStatements = this.mybatisMapperParser.parseMapper(mapperPath);
            for (MybatisSqlStatement mybatisSqlStatement : mybatisSqlStatements) {
                String methodName = mybatisSqlStatement.getNamespace() + "." + mybatisSqlStatement.getId();
                mybatisConfiguration.putMapperMethod2MybatisSqlStatement(methodName, mybatisSqlStatement);
            }
        }

        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(mybatisConfiguration);
        return new DefaultMybatisSqlSessionFactory(mapperProxyFactory);
    }

    public MybatisSqlSessionFactoryBuilder setConfigPath(String configPath) {
        this.configPath = configPath;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisConfigurationParser(IMybatisConfigurationParser iMybatisConfigurationParser) {
        this.mybatisConfigurationParser = iMybatisConfigurationParser;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisMapperParser(IMybatisMapperParser iMybatisMapperParser) {
        this.mybatisMapperParser = iMybatisMapperParser;
        return this;
    }
}

修改DefaultMybatisSqlSession类,获取方法对应的MybatisStatement

json 复制代码
package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.proxy.MapperProxyFactory;


public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSession(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public <T> T execute(String method, Object parameter) {
        MybatisSqlStatement mybatisSqlStatement = this.mapperProxyFactory.getMybatisConfiguration().getMybatisSqlStatement(method);
        return (T)("method:" + method + "sql:"+ mybatisSqlStatement.getSql() + ",入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return (T) mapperProxyFactory.newInstance(type, this);
    }
}

添加测试方法进行测试:

json 复制代码
 public static void main(String[] args) {
        String configPath = "mybatis-config.xml";
        MybatisSqlSessionFactory mybatisSqlSessionFactory = new MybatisSqlSessionFactoryBuilder()
                .setMybatisMapperParser(new XmlMybatisMapperParser())
                .setMybatisConfigurationParser(new XmlMybatisConfigurationParser())
                .setConfigPath(configPath)
                .buildSqlSessionFactory();

        IMybatisSqlSession mybatisSqlSession = mybatisSqlSessionFactory.openSession();

        IUserMapper userMapper = mybatisSqlSession.getMapper(IUserMapper.class);
        System.out.println(userMapper.queryUserAge(1));
    }

测试结果如下:

参考文章

https://blog.csdn.net/weixin_43520450/article/details/107230205

相关推荐
Bald Baby18 分钟前
JWT的使用
java·笔记·学习·servlet
刘大浪19 分钟前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
魔道不误砍柴功24 分钟前
实际开发中的协变与逆变案例:数据处理流水线
java·开发语言
dj24429457071 小时前
JAVA中的Lamda表达式
java·开发语言
工业3D_大熊1 小时前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
szc17671 小时前
docker 相关命令
java·docker·jenkins
程序媛-徐师姐1 小时前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
yngsqq1 小时前
c#使用高版本8.0步骤
java·前端·c#
尘浮生1 小时前
Java项目实战II基于微信小程序的校运会管理系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea