本专栏的源码: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();}@Overridepublic <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>() {@Overridepublic 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();}@Overridepublic <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));
测试结果如下:
查询结果封装为对象
基于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 userwhere 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();}@Overridepublic <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>() {@Overridepublic 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();}@Overridepublic <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());}
测试结果如下:
基于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 mapperPUBLIC "-//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 userwhere id = #{id}</select><select id="queryIdUserNameVOById" resultMap="idUserName">select id, user_namefrom userwhere 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");}@Overridepublic 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 {@Overridepublic <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();}@Overridepublic <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>() {@Overridepublic 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();}@Overridepublic <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());}
测试结果如下: