本章目标:
Country selectByIdAndName(@Param("id") String id, @Param("name") String name)
的调用,且参数自行匹配,不需要 mockPeople selectById(@Param("id") Long id)
只需修改配置即可执行参数绑定部分,我们直接以 Class 为 key 构建一个映射,value 是 TypeHandler
,负责执行不同类型参数与 PreparedStatement
的整合,举例如下:
public interface TypeHandler {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
public class StringTypeHandler implements TypeHandler {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
}
结果映射部分,一是信息存储的 ResultMapping
和 ResultMap
结构,二是信息使用的 DefaultResultSetHandler
,其构造实际是在 mapper 文件解析时,逻辑是节点取值,稍微繁琐但并不复杂,不再赘述
public class ResultMap {
// 全局配置信息
private Configuration configuration;
// resultMap的编号
private String id;
// 最终输出结果对应的Java类
private Class type;
// XML中的的列表,即ResultMapping列表
private List resultMappings;
}
public class ResultMapping {
private Configuration configuration;
private String property;
private String column;
}
而至于使用信息进行结果映射的部分,我们这里直接采用的最简单的方式,直接使用反射构造对象并给字段赋值(在 MyBatis 中则是创建了功能更为强大的工具类,封装的层次也更深)。
public interface ResultSetHandler {
List handleResultSets(PreparedStatement stmt) throws SQLException;
}
@Data
@AllArgsConstructor
public class DefaultResultSetHandler implements ResultSetHandler {
private String statementId;
private Configuration configuration;
@Override
public List handleResultSets(PreparedStatement stmt) throws SQLException {
MappedStatement mappedStatement = configuration.getMappedStatement(statementId);
ResultMap resultMap = mappedStatement.getResultMap();
ResultSet resultSet = stmt.executeQuery();
List ret = new ArrayList<>();
while (resultSet.next()) {
handle(resultSet, resultMap, ret);
}
return ret;
}
private void handle(ResultSet resultSet, ResultMap resultMap, List ret) {
try {
Class type = resultMap.getType();
Object item = type.newInstance();
for (ResultMapping resultMapping : resultMap.getResultMappings()) {
String column = resultMapping.getColumn();
String property = resultMapping.getProperty();
Field field = type.getDeclaredField(property);
field.setAccessible(true);
field.set(item, resultSet.getObject(column));
}
ret.add((E) item);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
现在,我们的项目结构也来到这个样子:
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── raymond
│ │ └── mybatis
│ │ ├── Executor
│ │ │ ├── DefaultResultSetHandler.java
│ │ │ ├── Executor.java
│ │ │ ├── ResultSetHandler.java
│ │ │ └── SimpleExecutor.java
│ │ ├── annotation
│ │ │ └── Param.java
│ │ ├── binding
│ │ │ ├── MapperMethod.java
│ │ │ └── MapperRegistry.java
│ │ ├── builder
│ │ │ ├── BaseBuilder.java
│ │ │ ├── MapperBuilderAssistant.java
│ │ │ ├── XMLConfigBuilder.java
│ │ │ ├── XMLMapperBuilder.java
│ │ │ └── XMLStatementBuilder.java
│ │ ├── datasource
│ │ │ ├── DataSourceFactory.java
│ │ │ └── SimpleHikariDataSourceFactory.java
│ │ ├── mapping
│ │ │ ├── Environment.java
│ │ │ ├── MappedStatement.java
│ │ │ ├── ResultMap.java
│ │ │ ├── ResultMapping.java
│ │ │ └── SqlCommandType.java
│ │ ├── proxy
│ │ │ ├── MapperProxy.java
│ │ │ └── MapperProxyFactory.java
│ │ ├── reflection
│ │ │ └── ParamNameResolver.java
│ │ ├── script
│ │ │ └── SimpleSqlSource.java
│ │ ├── session
│ │ │ ├── Configuration.java
│ │ │ ├── DefaultSqlSession.java
│ │ │ ├── DefaultSqlSessionFactory.java
│ │ │ ├── SqlSession.java
│ │ │ └── SqlSessionFactory.java
│ │ ├── testdata
│ │ │ ├── CountryMapper.java
│ │ │ ├── PeopleMapper.java
│ │ │ └── dao
│ │ │ ├── Country.java
│ │ │ └── People.java
│ │ └── type
│ │ ├── IntegerTypeHandler.java
│ │ ├── LongTypeHandler.java
│ │ ├── StringTypeHandler.java
│ │ ├── TypeHandler.java
│ │ └── TypeHandlerRegistry.java
│ └── resources
│ ├── log4j2.xml
│ └── mapper
│ ├── CountryMapper.xml
│ └── PeopleMapper.xml
└── test
├── java
│ └── com
│ └── raymond
│ └── mybatis
│ ├── MainTest.java
│ ├── PeopleTest.java
│ ├── builder
│ │ └── XMLMapperBuilderTest.java
│ └── proxy
└── resources
└── batis-config.xml
首先是我们多参数的自动绑定验证,结果不出意外
@Test
public void test_select_id_name() throws Exception {
Country country = mapper.selectByIdAndName(1L,"Canada");
System.out.println(country);
assertNull(country);
Country country2 = mapper.selectByIdAndName(2L,"Canada");
System.out.println(country2);
assertNotNull(country2);
Assert.assertTrue(2L == country2.getId());
}
/**
22:44:38.677 [main] INFO com.raymond.mybatis.builder.XMLConfigBuilder - 开始解析配置文件
22:44:38.703 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
22:44:38.881 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
22:44:39.813 [main] INFO com.raymond.mybatis.proxy.MapperProxy - MapperProxy代理执行方法:selectByIdAndName, 交由Executor执行
null
22:44:39.858 [main] INFO com.raymond.mybatis.proxy.MapperProxy - MapperProxy代理执行方法:selectByIdAndName, 交由Executor执行
Country(id=2, countryName=Canada, countryCode=CA)
*/
接下来是新构建类的测试,我们新建了 People
类,新建了对应的 PeopleMapper
接口和对应的 xml
文件,同时在配置文件中增加了对应的 mapper 扫描路径
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People {
private Long id;
private String name;
}
public interface PeopleMapper {
People selectById(@Param("id") Long id);
}
当然,结果也符合预期!
22:48:12.201 [main] INFO com.raymond.mybatis.builder.XMLConfigBuilder - 开始解析配置文件
22:48:12.227 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
22:48:12.399 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
22:48:12.747 [main] INFO com.raymond.mybatis.proxy.MapperProxy - MapperProxy代理执行方法:selectById, 交由Executor执行
People(id=1, name=Alice)