本专栏源码: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());
}
测试结果如下: