手撸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());
    }

测试结果如下:

相关推荐
程序猿麦小七12 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~20 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong21 分钟前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴22 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳26 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir36 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄40 分钟前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
码上一元5 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v7 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man8 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang