手撸Mybatis(四)——连接数据库进行简单查询

本专栏源码:yang-mybatis: 手撸简陋版的mybatis (gitee.com)

添加数据库操作模板

对于JDBC操作,一般包括以下几个步骤: 1)注册驱动 2)建立连接 3)执行sql语句 4)处理结果 5)释放资源 上面这些步骤,真正和我们处理相关的,是第三步和第四步,其他步骤,都是通用的逻辑,因此,我们可以将这些步骤抽象成一个模板方法类,其内容如下:

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

import com.yang.mybatis.config.MybatisDataSource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;


public abstract class TransactionInvoke<T> {
    public T invoke(MybatisDataSource mybatisDataSource) {
        String username = mybatisDataSource.getUsername();
        String password = mybatisDataSource.getPassword();
        String url = mybatisDataSource.getUrl();
        String driver = mybatisDataSource.getDriver();

        Connection connection = null;
        T result = null;
        try {
            Class.forName(driver);
            connection = DriverManager.getConnection(url, username, password);
            connection.setAutoCommit(false);
            result = execute(connection);
            connection.commit();
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            closeResource(connection);
        }
        return result;
    }

    private void closeResource(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public abstract T execute(Connection connection) throws SQLException;
}

因为涉及到数据库操作,所以我们要先引入mysql的依赖:

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

查询某个字段

首先,我们修改DefaultMybatisSqlSession中,在该类中,执行我们的sql语句,其中,执行sql和对sql查询结果进行处理的内容,收敛在execute方法中。

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

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

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;
    private MybatisConfiguration mybatisConfiguration;

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

    @Override
    public <T> T execute(String method, Object parameter) {
        Map<String, MybatisSqlStatement> mapperMethod2SqlStatementsMap = mapperProxyFactory.getMybatisConfiguration().getMapperMethod2SqlStatementsMap();
        MybatisSqlStatement mybatisSqlStatement = mapperMethod2SqlStatementsMap.get(method);


        MybatisEnvironment defaultMybatisEnvironment = this.mybatisConfiguration.getDefaultMybatisEnvironment();

        return new TransactionInvoke<T>() {
            @Override
            public T execute(Connection connection) throws SQLException {
                String rawSql = mybatisSqlStatement.getSql();
                List<String> parameterNameList = new ArrayList<>();
                String sql = extractRawSql(rawSql, parameterNameList);
                Object[] parameters = (Object[]) parameter;
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                if (parameterNameList.size() != parameters.length) {
                    throw new RuntimeException("SQL语句参数个数不匹配====");
                }
                int index = 1;
                for (Object o : parameters) {
                    preparedStatement.setObject(index ++, o);
                }
                ResultSet resultSet = preparedStatement.executeQuery();

                T result = null;
                if (resultSet.next()) {
                    result = (T) resultSet.getObject(1);
                }

                resultSet.close();
                return result;
            }
        }.invoke(defaultMybatisEnvironment.getMybatisDataSource());
    }

    private String extractRawSql(String rawSql, List<String> parameterNameList) {
        StringBuilder sqlBuilder = new StringBuilder();
        int start = 0;
        int end = -1;
        while ((end = rawSql.indexOf("#", start)) != -1) {
            sqlBuilder.append(rawSql.substring(start, end - 1))
                    .append(" ? ");
            int parameterStart = end + 2;
            int parameterEnd = rawSql.indexOf("}", parameterStart);
            parameterNameList.add(rawSql.substring(parameterStart, parameterEnd));
            start = parameterEnd + 1;
        }
        sqlBuilder.append(rawSql.substring(start));

        return sqlBuilder.toString();
    }


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

最后我们添加测试方法,进行测试:

json 复制代码
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));

测试结果如下:

查询结果封装为对象

基于resultType

上述的操作,只对查询某个字段有效,假设我们要获取的是一个对象,比如我们在UserMapper添加如下方法:

json 复制代码
    User queryUserById(Integer id);

其中,User类内容如下:

json 复制代码
public class User implements Serializable {
    private Integer id;

    private String userName;

    private String password;

    private Integer age;

    private LocalDateTime 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 LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }
}

该方法对于的mapperxml查询块如下:

json 复制代码
  <select id="queryUserById" resultType="com.yang.mybatis.test.User">
        select * from user
        where id = #{id}
    </select>

此时,当我们使用JDBC执行sql,获取ResultSet后,我们可以根据resultType的类型,通过反射的方式,来创建对应的结果,并将属性填充到对象中。 首先,我们修改MybatisSqlStatement,添加resultType字段

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

import java.io.Serializable;

public class MybatisSqlStatement implements Serializable {
    private String namespace;

    private String id;

    private String sql;
    
    private String resultType;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
}

然后修改XmlMybatisMapperParser的parseStatement方法,设置对应的resultType值

json 复制代码
private void parseStatement(List<MybatisSqlStatement> mybatisSqlStatements, List<Element> elements, Element root) {
        if (elements == null || elements.isEmpty()) {
            return;
        }
        String namespace = root.attributeValue("namespace");
        for (Element element : elements) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String sql = element.getText().trim();

            MybatisSqlStatement mybatisSqlStatement = new MybatisSqlStatement();
            mybatisSqlStatement.setNamespace(namespace);
            mybatisSqlStatement.setId(id);
            mybatisSqlStatement.setSql(sql);
            mybatisSqlStatement.setResultType(resultType);

            mybatisSqlStatements.add(mybatisSqlStatement);
        }
    }

最后修改DefaultMybatisSqlSession:

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

import com.google.common.base.CaseFormat;
import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisEnvironment;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;
    private MybatisConfiguration mybatisConfiguration;

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

    @Override
    public <T> T execute(String method, Object parameter) {
        Map<String, MybatisSqlStatement> mapperMethod2SqlStatementsMap = mapperProxyFactory.getMybatisConfiguration().getMapperMethod2SqlStatementsMap();
        MybatisSqlStatement mybatisSqlStatement = mapperMethod2SqlStatementsMap.get(method);


        MybatisEnvironment defaultMybatisEnvironment = this.mybatisConfiguration.getDefaultMybatisEnvironment();

        return new TransactionInvoke<T>() {
            @Override
            public T execute(Connection connection) throws SQLException {
                String rawSql = mybatisSqlStatement.getSql();
                List<String> parameterNameList = new ArrayList<>();
                String sql = extractRawSql(rawSql, parameterNameList);
                Object[] parameters = (Object[]) parameter;
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                if (parameterNameList.size() != parameters.length) {
                    throw new RuntimeException("SQL语句参数个数不匹配====");
                }
                int index = 1;
                for (Object o : parameters) {
                    preparedStatement.setObject(index ++, o);
                }
                ResultSet resultSet = preparedStatement.executeQuery();

                T result = parseResult(resultSet, mybatisSqlStatement);
                resultSet.close();
                return result;
            }
        }.invoke(defaultMybatisEnvironment.getMybatisDataSource());
    }

    private <T> T parseResult(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement) throws SQLException {
        String resultType = mybatisSqlStatement.getResultType();
        if (resultType == null || resultType.isEmpty()) {
            return (T) resultSet.getObject(1);
        }

        try {
            Class<?> aClass = Class.forName(resultType);
            Field[] fields = aClass.getDeclaredFields();
            Map<String, String> fieldName2ColumnNameMap = new HashMap<>();
            Map<String, Field> fieldName2FieldMap = new HashMap<>();
            for (Field field : fields) {
                // 驼峰命名转下划线
                String columnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
                fieldName2ColumnNameMap.put(field.getName(), columnName);
                fieldName2FieldMap.put(field.getName(), field);
            }

            Object result = aClass.newInstance();
            while (resultSet.next()) {
                for (Map.Entry<String, String> entry : fieldName2ColumnNameMap.entrySet()) {
                    String fieldName = entry.getKey();
                    String columnName = entry.getValue();

                    Object columnValue = resultSet.getObject(columnName);
                    Field field = fieldName2FieldMap.get(fieldName);
                    field.setAccessible(true);
                    field.set(result, columnValue);
                }
            }
            return (T)result;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private String extractRawSql(String rawSql, List<String> parameterNameList) {
        StringBuilder sqlBuilder = new StringBuilder();
        int start = 0;
        int end = -1;
        while ((end = rawSql.indexOf("#", start)) != -1) {
            sqlBuilder.append(rawSql.substring(start, end - 1))
                    .append(" ? ");
            int parameterStart = end + 2;
            int parameterEnd = rawSql.indexOf("}", parameterStart);
            parameterNameList.add(rawSql.substring(parameterStart, parameterEnd));
            start = parameterEnd + 1;
        }
        sqlBuilder.append(rawSql.substring(start));

        return sqlBuilder.toString();
    }


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

这里将和结果相关的解析,抽取到parseResult方法中,此外,因为数据库是字段是下划线格式,类的属性是驼峰格式,因此,这里引入了Guava依赖,方便使用它的CaseFormat类进行格式转化。

json 复制代码
  <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>

最后,我们添加测试方法,进行测试:

json 复制代码
 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);
        User user = userMapper.queryUserById(1);
        System.out.println(user);
        System.out.println(user.getId());
        System.out.println(user.getUserName());
        System.out.println(user.getPassword());
        System.out.println(user.getAge());
        System.out.println(user.getCreateTime());
    }

测试结果如下:

基于resultMap

上面的方式,是基于resultType来进行解析的,但是在mybatis中,还有另外一种将sql字段和类对象属性映射的方式,就是resultMap。 首先,我们创建一个IdUserNameVO类

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

import java.io.Serializable;

public class IdUserNameVO implements Serializable {
    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;
    }
}

在IUserMapper中,我们添加下列方法:

json 复制代码
    IdUserNameVO queryIdUserNameVOById(Integer id);

修改UserMapper.xml,加上queryIdUserNameVOById的sql语句和对应的resultMap

json 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.yang.mybatis.test.IUserMapper">
    <resultMap id="idUserName" type="com.yang.mybatis.test.IdUserNameVO">
        <id property="id" column="id" javaType="Integer" jdbcType="int"/>
        <result property="userName" column="user_name" javaType="String" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="queryUserName">
        select user_name from user where id = #{id}
    </select>
    <select id="queryUserAge">
        select age from user where id = #{id}
    </select>
    <select id="queryUserById" resultType="com.yang.mybatis.test.User">
        select * from user
        where id = #{id}
    </select>
    <select id="queryIdUserNameVOById" resultMap="idUserName">
        select id, user_name
        from user
        where id = #{id}
    </select>
</mapper>

之前,我们在解析每一个mapper.xml文件时,解析出来的结果,是一个MybatisSqlStatement列表,但是这种方式还不能更好的表达一个mapper.xml中包含的信息,因此,我们修改代码,现在对于每一个mapper.xml文件,解析出来的结果位MybatisMapperXmlConfiguration类,该类定义如下:

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 MybatisMapperXmlConfiguration implements Serializable {
    private String mapperName;

    private List<MybatisSqlStatement> mybatisSqlStatements = new ArrayList<>();

    private Map<String, MybatisResultMap> mybatisResultMaps = new HashMap<>();


    public List<MybatisSqlStatement> getMybatisSqlStatements() {
        return mybatisSqlStatements;
    }

    public void setMybatisSqlStatements(List<MybatisSqlStatement> mybatisSqlStatements) {
        this.mybatisSqlStatements = mybatisSqlStatements;
    }

    public List<MybatisResultMap> getMybatisResultMaps() {
        return new ArrayList<>(mybatisResultMaps.values());
    }


    public void addMybatisSqlStatement(MybatisSqlStatement mybatisSqlStatement) {
        this.mybatisSqlStatements.add(mybatisSqlStatement);
    }

    public void addMybatisResultMap(MybatisResultMap mybatisResultMap) {
        this.mybatisResultMaps.put(mybatisResultMap.getId(), mybatisResultMap);
    }

    public MybatisResultMap getMybatisResultMap(String id) {
        return this.mybatisResultMaps.get(id);
    }

    public String getMapperName() {
        return mapperName;
    }

    public void setMapperName(String mapperName) {
        this.mapperName = mapperName;
    }
}

MybatisResultMap的定义如下:

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

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

public class MybatisResultMap implements Serializable {
    private String id;
    private String type;
    private MybatisResultMapProperty idProperty;

    private List<MybatisResultMapProperty> properties = new ArrayList<>();

    public void addMybatisResultMapProperty(MybatisResultMapProperty mybatisResultMapProperty) {
        this.properties.add(mybatisResultMapProperty);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public MybatisResultMapProperty getIdProperty() {
        return idProperty;
    }

    public void setIdProperty(MybatisResultMapProperty idProperty) {
        this.idProperty = idProperty;
    }

    public List<MybatisResultMapProperty> getProperties() {
        return this.properties;
    }
}

MybatisResultMapProperty类定义如下:

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

import java.io.Serializable;

public class MybatisResultMapProperty implements Serializable {
    private String property;

    private String column;

    private String javaType;

    private String jdbcType;

   ... 省略getter和setter
}

我们修改IMybatisMapperParser接口:

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

import com.yang.mybatis.config.MybatisMapperXmlConfiguration;

public interface IMybatisMapperParser {
    MybatisMapperXmlConfiguration parseMapper(String path);
}

修改MybatisStatement,加上resultMap属性:

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

import java.io.Serializable;

public class MybatisSqlStatement implements Serializable {
    private String namespace;

    private String id;

    private String sql;

    private String resultType;

    private String resultMap;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getResultMap() {
        return resultMap;
    }

    public void setResultMap(String resultMap) {
        this.resultMap = resultMap;
    }
}

修改其IMybatisMapperParser具体实现:

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

import com.yang.mybatis.config.MybatisMapperXmlConfiguration;
import com.yang.mybatis.config.MybatisResultMap;
import com.yang.mybatis.config.MybatisResultMapProperty;
import com.yang.mybatis.config.MybatisSqlStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class XmlMybatisMapperParser implements IMybatisMapperParser {
    private final static Set<String> tagSet = new HashSet<>();

    private final static Set<String> resultMapTagSet = new HashSet<>();

    static {
        tagSet.add("select");
        tagSet.add("insert");
        tagSet.add("update");
        tagSet.add("delete");
        tagSet.add("SELECT");
        tagSet.add("INSERT");
        tagSet.add("UPDATE");
        tagSet.add("DELETE");

        resultMapTagSet.add("resultMap");
        resultMapTagSet.add("ResultMap");
    }
    @Override
    public MybatisMapperXmlConfiguration parseMapper(String path) {
        MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration = new MybatisMapperXmlConfiguration();
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element root = document.getRootElement();

            parseMapperName(mybatisMapperXmlConfiguration, root);
            for (String tag : tagSet) {
                List<Element> elements = root.elements(tag);
                parseStatement(mybatisMapperXmlConfiguration, elements);
            }
            for (String tag: resultMapTagSet) {
                List<Element> elements = root.elements(tag);
                parseResultMap(mybatisMapperXmlConfiguration, elements);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return mybatisMapperXmlConfiguration;
    }

    private void parseMapperName(MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration, Element root) {
        String mapperName = root.attributeValue("namespace");
        mybatisMapperXmlConfiguration.setMapperName(mapperName);

    }

    private void parseResultMap(MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration, List<Element> elements) {
        if (elements == null || elements.isEmpty()) {
            return;
        }
        for (Element element : elements) {
            String id = element.attributeValue("id");
            String type = element.attributeValue("type");

            MybatisResultMap mybatisResultMap = new MybatisResultMap();
            mybatisResultMap.setId(id);
            mybatisResultMap.setType(type);

            Element idElement = element.element("id");
            if (idElement != null) {
                MybatisResultMapProperty mybatisResultMapProperty = buildMybatisResultMapProperty(idElement);
                mybatisResultMap.addMybatisResultMapProperty(mybatisResultMapProperty);
                mybatisResultMap.setIdProperty(mybatisResultMapProperty);
            }

            List<Element> resultList = element.elements("result");
            for (Element resultElement : resultList) {
                MybatisResultMapProperty mybatisResultMapProperty = buildMybatisResultMapProperty(resultElement);
                mybatisResultMap.addMybatisResultMapProperty(mybatisResultMapProperty);
            }

            mybatisMapperXmlConfiguration.addMybatisResultMap(mybatisResultMap);
        }
    }

    private MybatisResultMapProperty buildMybatisResultMapProperty(Element element) {
        MybatisResultMapProperty mybatisResultMapProperty = new MybatisResultMapProperty();
        String property = element.attributeValue("property");
        String column = element.attributeValue("column");
        String javaType = element.attributeValue("javaType");
        String jdbcType = element.attributeValue("jdbcType");
        mybatisResultMapProperty.setProperty(property);
        mybatisResultMapProperty.setColumn(column);
        mybatisResultMapProperty.setJavaType(javaType);
        mybatisResultMapProperty.setJdbcType(jdbcType);
        return mybatisResultMapProperty;
    }

    private void parseStatement(MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration, List<Element> elements) {
        if (elements == null || elements.isEmpty()) {
            return;
        }
        String namespace = mybatisMapperXmlConfiguration.getMapperName();
        for (Element element : elements) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String resultMap = element.attributeValue("resultMap");
            String sql = element.getText().trim();

            MybatisSqlStatement mybatisSqlStatement = new MybatisSqlStatement();
            mybatisSqlStatement.setNamespace(namespace);
            mybatisSqlStatement.setId(id);
            mybatisSqlStatement.setSql(sql);
            mybatisSqlStatement.setResultType(resultType);
            mybatisSqlStatement.setResultMap(resultMap);

            mybatisMapperXmlConfiguration.addMybatisSqlStatement(mybatisSqlStatement);
        }
    }
}

修改MybatisSqlSessionFactoryBuilder类:

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

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

import java.util.List;

public class MybatisSqlSessionFactoryBuilder {
    private IMybatisConfigurationParser mybatisConfigurationParser;

    private IMybatisMapperParser mybatisMapperParser;

    private String configPath;

    public MybatisSqlSessionFactory 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) {
            MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration = this.mybatisMapperParser.parseMapper(mapperPath);
            List<MybatisSqlStatement> mybatisSqlStatements = mybatisMapperXmlConfiguration.getMybatisSqlStatements();
            for (MybatisSqlStatement mybatisSqlStatement : mybatisSqlStatements) {
                String mapperMethod = mybatisSqlStatement.getNamespace() + "." + mybatisSqlStatement.getId();
                mybatisConfiguration.putMapperMethod2MybatisSqlStatement(mapperMethod, mybatisSqlStatement);
            }
            mybatisConfiguration.putMapperXmlConfiguration(mybatisMapperXmlConfiguration.getMapperName(), mybatisMapperXmlConfiguration);
        }

        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类中进行的,但是现在我们发现,结果的类型逐渐变得多样性了,如果都放在DefaultMybatisSqlSession类中,会使这个类十分庞大,因此,我们将解析结果的职责, 提取到IMybatisResultParser类中,首先定义该接口:

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

import com.yang.mybatis.execute.request.MybatisResultParserRequest;

import java.sql.SQLException;

public interface IMybatisResultParser {
    final static int ONE_COLUMNE = 0;
    final static int RESULT_TYPE = 1;
    final static int RESULT_MAP = 2;

    <T> T parseResult(MybatisResultParserRequest mybatisResultParserRequest) throws SQLException;
}

MybatisResultParserRequest:

json 复制代码
package com.yang.mybatis.execute.request;

import com.yang.mybatis.config.MybatisMapperXmlConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;

import java.io.Serializable;
import java.sql.ResultSet;

public class MybatisResultParserRequest implements Serializable {
    private ResultSet resultSet;
    private MybatisSqlStatement mybatisSqlStatement;
    private MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration;

    public MybatisResultParserRequest() {
    }


    public ResultSet getResultSet() {
        return resultSet;
    }

    public void setResultSet(ResultSet resultSet) {
        this.resultSet = resultSet;
    }

    public MybatisSqlStatement getMybatisSqlStatement() {
        return mybatisSqlStatement;
    }

    public void setMybatisSqlStatement(MybatisSqlStatement mybatisSqlStatement) {
        this.mybatisSqlStatement = mybatisSqlStatement;
    }

    public MybatisMapperXmlConfiguration getMybatisMapperXmlConfiguration() {
        return mybatisMapperXmlConfiguration;
    }

    public void setMybatisMapperXmlConfiguration(MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration) {
        this.mybatisMapperXmlConfiguration = mybatisMapperXmlConfiguration;
    }
}

然后定义其具体实现:

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

import com.google.common.base.CaseFormat;
import com.yang.mybatis.config.MybatisMapperXmlConfiguration;
import com.yang.mybatis.config.MybatisResultMap;
import com.yang.mybatis.config.MybatisResultMapProperty;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.execute.request.MybatisResultParserRequest;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class DefaultMybatisResultParser implements IMybatisResultParser {
    @Override
    public <T> T parseResult(MybatisResultParserRequest mybatisResultParserRequest) throws SQLException {
        ResultSet resultSet = mybatisResultParserRequest.getResultSet();
        if (resultSet == null) {
            return null;
        }
        MybatisSqlStatement mybatisSqlStatement = mybatisResultParserRequest.getMybatisSqlStatement();
        MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration = mybatisResultParserRequest.getMybatisMapperXmlConfiguration();
        int resultTypeCode = parseResultTypeCode(mybatisSqlStatement);
        switch (resultTypeCode) {
            case ONE_COLUMNE:
                return parseResultOfOneColumn(resultSet, mybatisSqlStatement);
            case RESULT_TYPE:
                return parseResultOfResultType(resultSet, mybatisSqlStatement);
        }
        return parseResultOfResultMap(resultSet, mybatisSqlStatement, mybatisMapperXmlConfiguration);
    }

    private <T> T parseResultOfResultMap(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement,
                                         MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration) throws SQLException {
        String resultMap = mybatisSqlStatement.getResultMap();

        MybatisResultMap mybatisResultMap = mybatisMapperXmlConfiguration.getMybatisResultMap(resultMap);

        String classType = mybatisResultMap.getType();
        Map<String, String> fieldName2ColumnNameMap = mybatisResultMap.getProperties()
                .stream()
                .collect(Collectors.toMap(MybatisResultMapProperty::getProperty, MybatisResultMapProperty::getColumn));

        return parseResultOfClassAndFields(resultSet, classType, fieldName2ColumnNameMap);
    }

    private <T> T parseResultOfResultType(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement) throws SQLException {
        String resultType = mybatisSqlStatement.getResultType();

        try {
            Class<?> aClass = Class.forName(resultType);
            Field[] fields = aClass.getDeclaredFields();
            Map<String, String> fieldName2ColumnNameMap = new HashMap<>();
            for (Field field : fields) {
                // 驼峰命名转下划线
                String columnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
                fieldName2ColumnNameMap.put(field.getName(), columnName);
            }

            return parseResultOfClassAndFields(resultSet, resultType, fieldName2ColumnNameMap);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T parseResultOfClassAndFields(ResultSet resultSet, String classType, Map<String, String> fieldName2ColumnNameMap ) throws SQLException {
        try {
            Class<?> aClass = Class.forName(classType);
            Field[] fields = aClass.getDeclaredFields();

            Map<String, Field> fieldName2FieldMap = new HashMap<>();
            for (Field field : fields) {
                fieldName2FieldMap.put(field.getName(), field);
            }

            Object result = aClass.newInstance();
            while (resultSet.next()) {
                for (Map.Entry<String, String> entry : fieldName2ColumnNameMap.entrySet()) {
                    String fieldName = entry.getKey();
                    String columnName = entry.getValue();

                    Object columnValue = resultSet.getObject(columnName);
                    Field field = fieldName2FieldMap.get(fieldName);
                    field.setAccessible(true);
                    field.set(result, columnValue);
                }
            }
            return (T)result;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T parseResultOfOneColumn(ResultSet resultSet, MybatisSqlStatement mybatisSqlStatement) throws SQLException {
        return (T) resultSet.getObject(1);
    }

    private int parseResultTypeCode(MybatisSqlStatement mybatisSqlStatement) {
        String resultType = mybatisSqlStatement.getResultType();
        String resultMap = mybatisSqlStatement.getResultMap();
        if (StringUtils.isEmpty(resultType) && StringUtils.isEmpty(resultMap)) {
            return ONE_COLUMNE;
        }
        if (StringUtils.isNotEmpty(resultType) && StringUtils.isNotEmpty(resultMap)) {
            throw new RuntimeException("resultType和resultMap不能同时存在");
        }
        if (StringUtils.isNotEmpty(resultType)) {
            return RESULT_TYPE;
        }
        return RESULT_MAP;
    }
}

最后,我们修改DefaultMybatisSqlSession类:

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

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisEnvironment;
import com.yang.mybatis.config.MybatisMapperXmlConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.execute.DefaultMybatisResultParser;
import com.yang.mybatis.execute.IMybatisResultParser;
import com.yang.mybatis.execute.request.MybatisResultParserRequest;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;
    private MybatisConfiguration mybatisConfiguration;

    private IMybatisResultParser iMybatisResultParser = new DefaultMybatisResultParser();

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

    @Override
    public <T> T execute(String method, Object parameter) {
        Map<String, MybatisSqlStatement> mapperMethod2SqlStatementsMap = mapperProxyFactory.getMybatisConfiguration().getMapperMethod2SqlStatementsMap();
        MybatisSqlStatement mybatisSqlStatement = mapperMethod2SqlStatementsMap.get(method);


        MybatisEnvironment defaultMybatisEnvironment = this.mybatisConfiguration.getDefaultMybatisEnvironment();

        return new TransactionInvoke<T>() {
            @Override
            public T execute(Connection connection) throws SQLException {
                String rawSql = mybatisSqlStatement.getSql();
                List<String> parameterNameList = new ArrayList<>();
                String sql = extractRawSql(rawSql, parameterNameList);
                Object[] parameters = (Object[]) parameter;
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                if (parameterNameList.size() != parameters.length) {
                    throw new RuntimeException("SQL语句参数个数不匹配====");
                }
                int index = 1;
                for (Object o : parameters) {
                    preparedStatement.setObject(index ++, o);
                }
                ResultSet resultSet = preparedStatement.executeQuery();

                String mapperName = mybatisSqlStatement.getNamespace();
                MybatisMapperXmlConfiguration mybatisMapperXmlConfiguration = mybatisConfiguration.getMybatisMapperXmlConfiguration(mapperName);

                MybatisResultParserRequest mybatisResultParserRequest = new MybatisResultParserRequest();
                mybatisResultParserRequest.setResultSet(resultSet);
                mybatisResultParserRequest.setMybatisSqlStatement(mybatisSqlStatement);
                mybatisResultParserRequest.setMybatisMapperXmlConfiguration(mybatisMapperXmlConfiguration);
                T result = iMybatisResultParser.parseResult(mybatisResultParserRequest);
                resultSet.close();
                return result;
            }
        }.invoke(defaultMybatisEnvironment.getMybatisDataSource());
    }

    private String extractRawSql(String rawSql, List<String> parameterNameList) {
        StringBuilder sqlBuilder = new StringBuilder();
        int start = 0;
        int end = -1;
        while ((end = rawSql.indexOf("#", start)) != -1) {
            sqlBuilder.append(rawSql.substring(start, end - 1))
                    .append(" ? ");
            int parameterStart = end + 2;
            int parameterEnd = rawSql.indexOf("}", parameterStart);
            parameterNameList.add(rawSql.substring(parameterStart, parameterEnd));
            start = parameterEnd + 1;
        }
        sqlBuilder.append(rawSql.substring(start));

        return sqlBuilder.toString();
    }


    @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";
        IMybatisSqlSessionFactory mybatisSqlSessionFactory = new MybatisSqlSessionFactoryBuilder()
                .setMybatisConfigurationParser(new XmlMybatisConfigurationParser())
                .setMybatisMapperParser(new XmlMybatisMapperParser())
                .setConfigPath(configPath)
                .buildSqlSessionFactory();
        IMybatisSqlSession mybatisSqlSession = mybatisSqlSessionFactory.openSession();
        IUserMapper userMapper = mybatisSqlSession.getMapper(IUserMapper.class);
        IdUserNameVO idUserNameVO = userMapper.queryIdUserNameVOById(1);
        System.out.println(idUserNameVO.getId());
        System.out.println(idUserNameVO.getUserName());
    }

测试结果如下:

相关推荐
开心就好202512 分钟前
前端性能优化移动端网页滚动卡顿与掉帧问题实战
后端
语落心生15 分钟前
如何利用Paimon做流量定时检查? --- 试试标签表
后端
paopaokaka_luck20 分钟前
校园快递小程序(腾讯地图API、二维码识别、Echarts图形化分析)
vue.js·spring boot·后端·小程序·uni-app
用户2986985301425 分钟前
C# 使用Spire.XLS快速生成多表格Excel文件
后端
盖世英雄酱5813632 分钟前
国企“高级”程序员写的那些问题代码(六期)
java·后端
南囝coding1 小时前
这个Web新API让任何内容都能画中画!
前端·后端
林太白1 小时前
VitePress项目工程化应该如何做
前端·后端
字节跳跃者1 小时前
Java 中的 Stream 可以替代 for 循环吗?
java·后端
北执南念2 小时前
如何在 Spring Boot 中设计和返回树形结构的组织和部门信息
java·spring boot·后端
修仙的人2 小时前
【开发环境】 VSCode 快速搭建 Python 项目开发环境
前端·后端·python