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

news2024/5/18 16:13:24

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

添加数据库操作模板

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

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的依赖:

  <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

查询某个字段

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

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

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

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

测试结果如下:
image.png

查询结果封装为对象

基于resultType

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

    User queryUserById(Integer id);

其中,User类内容如下:


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查询块如下:

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

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

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值

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:

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类进行格式转化。

  <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>

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

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

测试结果如下:
image.png

基于resultMap

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

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中,我们添加下列方法:

    IdUserNameVO queryIdUserNameVOById(Integer id);

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

<?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类,该类定义如下:

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的定义如下:

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类定义如下:

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接口:

package com.yang.mybatis.config.parser;

import com.yang.mybatis.config.MybatisMapperXmlConfiguration;

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

修改MybatisStatement,加上resultMap属性:

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具体实现:

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类:

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类中,首先定义该接口:

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:

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

然后定义其具体实现:

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类:

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

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

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

测试结果如下:
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1641551.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【neteq】tgcall的调用、neteq的创建及接收侧统计

G:\CDN\P2P-DEV\Libraries\tg_owt\src\call\call.cc基本是按照原生webrtc的来的:G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\group\GroupInstanceCustomImpl.cpptg对neteq的使用 worker 线程创建call Call的config需要neteqfactory Call::CreateAu…

Boosting算法揭秘:从原理到scikit-learn实战

Boosting算法揭秘&#xff1a;从原理到scikit-learn实战 在机器学习的江湖中&#xff0c;Boosting算法以其强大的预测能力和独特的训练方式占据了一席之地。与Bagging算法并行训练的理念不同&#xff0c;Boosting算法更注重模型的串行迭代和错误修正。本文将从Boosting算法的基…

JVM笔记1--Java内存区域

1、运行时数据区域 从上图可以看出来&#xff0c;Java虚拟机运行时数据区域整体上可以分成5大块&#xff1a; 1.1、程序计数器 程序计数器是一块较小的内存空间。它可以看做当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里&#xff0c;字节码解释器工作时就是…

【热门话题】Chrome 插件研发详解:从入门到实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Chrome 插件研发详解&#xff1a;从入门到实践一、引言二、Chrome 插件基础概念…

汇编语言——输入两个字数据(16位的数)X,Y,计算Z=X+Y,并把Z的结果显示出来

文章目录 以2进制输入&#xff0c;2进制输出&#xff08;无符号&#xff09;以2进制输入&#xff0c;2进制输出&#xff08;带符号&#xff09;以8进制输入&#xff0c;8进制输出以10进制输入&#xff0c;10进制输出以16进制输入&#xff0c;16进制输出 仅供参考 X、Y的输入可…

08 - 步骤 表输出

简介 表输出&#xff08;Table Output&#xff09;步骤是用于将 Kettle 中的数据写入关系型数据库表的步骤。它允许用户将数据流中的数据插入、更新或删除到目标数据库表中。 使用 场景 我要将处理完的数据流中的sysOrgCode 跟 plateNumber 保存记录到mysql 1、拖拽表输出…

目标检测算法YOLOv5简介

没有关于YOLOv5的直接论文&#xff0c;YOLOv5由Ultralytics维护&#xff0c;源码见&#xff1a;https://github.com/ultralytics/yolov5 &#xff0c;于2020年6月发布v1.0版本&#xff0c;最新发布版本为v7.0&#xff0c;License为AGPL-3.0. 以下内容主要来自&#xff1a; 1. U…

MyScaleDB:SQL+向量驱动大模型和大数据新范式

大模型和 AI 数据库双剑合璧&#xff0c;成为大模型降本增效&#xff0c;大数据真正智能的制胜法宝。 大模型&#xff08;LLM&#xff09;的浪潮已经涌动一年多了&#xff0c;尤其是以 GPT-4、Gemini-1.5、Claude-3 等为代表的模型你方唱罢我登场&#xff0c;成为当之无愧的风口…

【R语言数据分析】卡方检验

目录 交叉卡方检验 配对卡方检验 趋势卡方检验 交叉卡方检验 交叉卡方表用于比较组间“率”的差异。适用于分类型变量&#xff0c;被检验的分类变量应该是无序分类变量&#xff0c;分组变量可以是有序分组也可以是无序分组。比如比较两种药物治疗某个疾病的效率&#xff0c;…

Bartender 5 - MacBook菜单栏图标管理软件

当 macOS 桌面图标太多时&#xff0c;既不美观又经常会相互遮盖&#xff0c;非常影响操作。 苹果现在还把「刘海屏」发扬光大&#xff0c;MacBook 的菜单栏是越来越不方便了&#xff01; 如果你希望 Mac 的菜单栏干净清爽、又方便易用&#xff0c;那「Bartender 5」你一定要试…

第15章 基于规格说明的测试技术

一、概述 &#xff08;一&#xff09;依据 《软件需求规格说明书》以及对应的模型或用户需求。 &#xff08;二&#xff09;特点 不考虑内部结构和内部特征 &#xff08;三&#xff09;测试用例满足的标准 利用黑盒测试技术导出测试用例 &#xff08;四&#xff09;测试…

基于 Wireshark 分析 ICMP 协议

一、ICMP 协议 ICMP&#xff08;Internet Control Message Protocol&#xff09;即互联网控制报文协议&#xff0c;是TCP/IP协议簇的一个子协议。它主要用于在IP主机、路由器之间传递控制消息&#xff0c;这些消息涉及网络是否通畅、主机是否可达、路由是否可用等关于网络本身…

2024年北京高校后勤餐饮博览会|北京餐饮展览会

高联采高校后勤餐饮博览会 暨第25届北京高校后勤餐饮联合招标采购大会 同期举办&#xff1a;中国北京餐饮供应链博览会 主 题&#xff1a; 因为FOOD校园GOOD / 同创高校大舞台共享精彩高联采 时 间&#xff1a;2024年9月21日-22日 地 点&#xff1a;中国国际展览中心&…

利用大模型提升个性化推荐的异构知识融合方法

在推荐系统中&#xff0c;分析和挖掘用户行为是至关重要的&#xff0c;尤其是在美团外卖这样的平台上&#xff0c;用户行为表现出多样性&#xff0c;包括不同的行为主体&#xff08;如商家和产品&#xff09;、内容&#xff08;如曝光、点击和订单&#xff09;和场景&#xff0…

【Hadoop】--基于hadoop和hive实现聊天数据统计分析,构建聊天数据分析报表[17]

目录 一、需求分析 1、背景介绍 2、目标 3、需求 4、数据内容 5、建库建表 二、ETL数据清洗 1、数据问题 2、需求 3、实现 4、扩展概念&#xff1a;ETL 三、指标计算 1、指标1&#xff1a;统计今日消息总量 2、指标2&#xff1a;统计每小时消息量、发送量和接收用…

python学习笔记----面向对象(十)

一、什么是类 类是一个抽象的模板&#xff0c;用于创建具体的实例。可以将类理解为一个蓝图&#xff0c;它定义了一系列对象共有的属性&#xff08;数据&#xff09;和方法&#xff08;函数&#xff09;。类是对一组具有相同属性和功能的对象的抽象。例如&#xff0c;你可以定…

FIFO Generate IP核使用——Native读写接口信号详解

Native FIFO接口信号是用于FIFO IP核与外部电路进行通信的信号。当FIFO支持独立的写和读时钟时&#xff0c;这些信号可以包括标准端口和可选端口。 1 当FIFO具有独立时钟时的接口信号 当FIFO具有独立的时钟时&#xff0c;其接口信号会相应地有所变化。特别是关于复位信号rst…

政安晨:【Keras机器学习示例演绎】(三十二)—— 在 Vision Transformers 中学习标记化

目录 导言 导入 超参数 加载并准备 CIFAR-10 数据集 数据扩增 位置嵌入模块 变压器的 MLP 模块 令牌学习器模块 变换器组 带有 TokenLearner 模块的 ViT 模型 培训实用程序 使用 TokenLearner 培训和评估 ViT 实验结果 参数数量 最终说明 政安晨的个人主页&…

Ubuntu TeamViewer安装与使用

TeamViewer是一款跨平台的专有应用程序&#xff0c;允许用户通过互联网连接从全球任何地方远程连接到工作站、传输文件以及召开在线会议。它适用于多种设备&#xff0c;例如个人电脑、智能手机和平板电脑。 TeamViewer在交通不便或偏远地区使用电脑问题时&#xff0c;将发挥重…

从零开始搭建Springboot项目脚手架1:新建项目

1、技术栈 SpringBoot 3.2.5&#xff1a; 2、 新建项目 使用SpringInitializr 选择Lombok、Configuration Processor、Spring Web&#xff0c;同时IDEA也要安装Lombok插件 删除多余的Maven目录、Maven文件&#xff0c;把HELP.md改成README.md。 当然前提是已经安装好Maven和配…