[MyBatis学习笔记] 一、仿造Mybatis实现一个简单的自定义持久层框架
一、问题
使用原生Jdbc进行数据库操作时,主要存在以下几点问题:
1、数据库驱动、连接参数硬编码;
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.0.101:3306/ipersistance?useUnicode=true&characterEncoding=utf8", "root", "123456");
2、频繁创建、关闭连接,造成资源浪费;
if (connection != null) {
connection.close();
}
3、查询参数、查询结果硬编码;
PreparedStatement ps = connection.prepareStatement("select id,username,password,birthday from user where id = ? and username = ?");
ps.setInt(1, 3);
ps.setString(2, "gavin");
4、需手动进行结果集映射,操作繁琐。
List<User> users = new ArrayList<>();
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String birthday = rs.getString("birthday");
users.add(new User(id, username, password, birthday));
}
基于以上各个问题,可分别采用以下的解决方案:
问题1、3:可采用配置文件的方式解决硬编码问题;
问题2:可引入数据库连接池对连接进行管理;
问题4:可采用反射及动态代理来解决。
二、自定义持久层实现
本节将仿造Mybatis,实现一个简单的持久层框架。
(一)相关环境及依赖说明
数据库版本(mariadb:10.4.16
):
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 61
Server version: 10.4.16-MariaDB MariaDB Server
Jdk版本(jdk8
):
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
依赖包:
<!-- 用于解析xml文件 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- 用于支持xpath操作 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!-- mysql连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- c3p0连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
(二)实现步骤分析
1、设计对应的容器类
各配置文件含义如下:
(1)db-config.xml及Configuration类
db-config.xml
主要存储数据库配置信息,为其设定的结构如下:
<configuration>
<!-- 数据源 -->
<datasource>
<!-- 数据库连接参数 -->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://192.168.0.101:3306/ipersistance?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="123456" />
<!-- 以下为连接池相关参数 -->
<property name="initPoolSize" value="3" />
<property name="maxPoolSize" value="10" />
<property name="maxIdleTime" value="60000" />
</datasource>
<!-- *Mapper.xml文件位置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
与db-config.xml
对应的存储类Configuration
:
public class Configuration {
private DataSource ds;
private Map<String, MappedStatement> statements = new HashMap<>();
}
(2)*Mapper.xml与MappedStatement类:
*Mapper.xml
: 存放sql信息。
<!-- namespace为本模块(user模块)的唯一标识 -->
<mapper namespace="com.gavin11.study.mybatis.ipersistance.test.mapper.UserMapper">
<!-- id属性指定本模块的唯一标识, resultType指定结果集要映射到的对象 -->
<select id="findAll" resultType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
select * from user
</select>
<!-- paramType指定了携带条件参数值得对象 -->
<select id="findByIdName" resultType="com.gavin11.study.mybatis.ipersistance.test.pojo.User" paramType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
select * from user where username=#{username} and id=#{id}
</select>
......
</mapper>
对应的存储类MappedStatement
:
public class MappedStatement {
/** statementId, 由namespace.id构成 */
private String id;
/** 参数类型 */
private String paramType;
/** 结果类型 */
private String resultType;
/** sql语句 */
private String sql;
/** sql类型 */
private SqlCommandType sqlCommandType;
}
2、解析xml文件,并将解析的内容封装到相应得对象中
通过SqlSessionFactoryBuilder
类对xml
文件进行解析,并获取SqlSessionFactory
对象,其方法如下:
public class SqlSessionFactoryBuilder {
/** 根据传入的字节流加载 */
public static SqlSessionFactory build(InputStream in) throws Exception;
/** 根据指定的路径加载 */
public static SqlSessionFactory build(String path) throws Exception;
/** 默认加载db-config.xml */
public static SqlSessionFactory build() throws Exception;
}
上述build方法中,需借助XmlConfigurationBuilder类及XmlMappedStatementBuilder类对xml文件进行解析。方法定义分别如下:
// XmlConfigurationBuilder
public class XmlConfigurationBuilder {
public Configuration getConfiguration(InputStream in) throws Exception:
}
// XmlMappedStatementBuilder
public class XmlMappedStatementBuilder {
/** 有参构造传入configuration对象 */
public XmlMappedStatementBuilder(Configuration configuration);
/** 根据路径解析对应的*Mapper.xml文件,并封装到MappedStatement对象中 */
public void parse(String path) throws Exception;
}
解析完成后,需通过DefaultSqlSessionFactory
类创建SqlSessionFactory
对象。
3、SqlSessionFactory接口及其实现
接口SqlSessionFactory
的方法定义:
public interface SqlSessionFactory {
SqlSession openSession();
}
其实现类`DefaultSqlSessionFactory:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
public DefaultSqlSessionFactory(Configuration configuration);
}
4、SqlSession接口及其实现类DefaultSqlSession
定义接口方法:
public interface SqlSession {
/**
* 根据条件查询一条记录
* statementId的形式为:namespace.id
*/
<T> T selectOne(String statementId, Object... params) throws Exception;
/**
* 根据条件查询所有
*/
<T> List<T> selectList(String statementId, Object... params) throws Exception;
/**
* 新增操作
*/
int insert(String statementId, Object... params) throws Exception;
/**
* 修改操作
*/
int update(String statementId, Object... params) throws Exception;
/**
* 删除操作
*/
int delete(String statementId, Object... params) throws Exception;
/**
* 获取持久层对象(使用代理生成)
*/
<T> T getMapper(Class<T> clazz);
}
其实现类DefaultSqlSession
:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration);
}
5、定义Executor接口,进行executeQuery及execute操作
数据库访问及查询参数解析、查询结果映射等操作均通过Executor
接口完成。
public interface Executor {
/**
* 读操作
*/
<T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/**
* 写操作
*/
int execute(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
实现类SimpleExecutor
:
public class SimpleExecutor implements Executor {
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
@Override
public int execute(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
(三)具体代码实现
1、解析xml文件
(1)SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
/**
* 解析xml配置文件
* @param in in
* @return SqlSessionFactory对象
* @throws Exception e
*/
public static SqlSessionFactory build(InputStream in) throws Exception {
// 解析db-config.xml, 将其中的内容封装Configuration中
XmlConfigurationBuilder xcb = new XmlConfigurationBuilder();
Configuration configuration = xcb.getConfiguration(in);
// 创建一个SqlSessionFactory对象
return new DefaultSqlSessionFactory(configuration);
}
/**
* 解析配置文件
* @param path 配置文件路径
* @return SqlSessionFactory对象
* @throws Exception e
*/
public static SqlSessionFactory build(String path) throws Exception {
try (
InputStream in = Resources.getResourceAsStream(path)
) {
return build(in);
}
}
/**
* 解析配置文件
* @return SqlSessionFactory对象
* @throws Exception e
*/
public static SqlSessionFactory build() throws Exception {
String path = "db-config.xml";
return build(path);
}
}
(2)XmlConfigurationBuilder
public class XmlConfigurationBuilder {
private final Configuration configuration;
public XmlConfigurationBuilder() {
this.configuration = new Configuration();
}
@SuppressWarnings("unchecked")
public Configuration getConfiguration(InputStream in) throws Exception {
Document doc = new SAXReader().read(in);
Element rootElement = doc.getRootElement();
DataSource cpds = getDataSourceConfig(rootElement);
Element mappersEle = rootElement.element("mappers");
List<Element> mapperEles = mappersEle.selectNodes("//mapper");
XmlMappedStatementBuilder msBuilder = new XmlMappedStatementBuilder(configuration);
for (Element me : mapperEles) {
String path = me.attributeValue("resource");
msBuilder.parse(path);
}
configuration.setDs(cpds);
return configuration;
}
private DataSource getDataSourceConfig(Element rootElement) throws PropertyVetoException {
Element dsEle = rootElement.element("datasource");
@SuppressWarnings("unchecked")
List<Element> propEles = dsEle.selectNodes("//property");
Properties dsProperties = new Properties();
for (Element propEle : propEles) {
dsProperties.setProperty(propEle.attributeValue("name"), propEle.attributeValue("value"));
}
// 设置设置连接池参数
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass(dsProperties.getProperty("driverClass"));
cpds.setJdbcUrl(dsProperties.getProperty("jdbcUrl"));
cpds.setUser(dsProperties.getProperty("username"));
cpds.setPassword(dsProperties.getProperty("password"));
cpds.setInitialPoolSize(Integer.parseInt(dsProperties.getProperty("initPoolSize")));
cpds.setMaxPoolSize(Integer.parseInt(dsProperties.getProperty("maxPoolSize")));
cpds.setMaxIdleTime(Integer.parseInt(dsProperties.getProperty("maxIdleTime")));
return cpds;
}
}
(3)XmlMappedStatementBuilder
public class XmlMappedStatementBuilder {
private final Configuration configuration;
public XmlMappedStatementBuilder(Configuration configuration) {
this.configuration = configuration;
}
@SuppressWarnings("unchecked")
public void parse(String path) throws Exception {
try (
InputStream in = Resources.getResourceAsStream(path)
) {
Document doc = new SAXReader().read(in);
Element rootElement = doc.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> statementEles = rootElement.selectNodes("//select | //insert | //delete | //update");
Map<String, MappedStatement> statementMap = new HashMap<>();
for (Element statementEle : statementEles) {
SqlCommandType sqlCommandType = getSqlCommandType(statementEle);
String id = statementEle.attributeValue("id");
MappedStatement mappedStatement = new MappedStatement(
id,
statementEle.attributeValue("paramType"),
statementEle.attributeValue("resultType"),
statementEle.getTextTrim(),
sqlCommandType
);
statementMap.put(namespace + "." + id, mappedStatement);
}
configuration.setStatements(statementMap);
}
}
/**
* 获取sql操作类型
* @param statementEle 标签对象
* @return 操作类型
*/
private SqlCommandType getSqlCommandType(Element statementEle) {
String name = statementEle.getName();
SqlCommandType sqlCommandType;
switch (name) {
case "select":
sqlCommandType = SqlCommandType.SELECT;
break;
case "insert":
sqlCommandType = SqlCommandType.INSERT;
break;
case "update":
sqlCommandType = SqlCommandType.UPDATE;
break;
case "delete":
sqlCommandType = SqlCommandType.DELETE;
break;
default:
throw new NonSupportedOperationException("operation not support: " + name);
}
return sqlCommandType;
}
}
(4)枚举类型SqlCommandType
public enum SqlCommandType {
/** select操作 */
SELECT,
/** insert操作 */
INSERT,
/** update操作 */
UPDATE,
/** delete操作 */
DELETE,
/** 未知操作 */
UNKNOWN
}
2、DefaultSqlSessionFactory及DefaultSqlSession实现
(1)DefaultSqlSessionFactory
// DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
(2)DefaultSqlSession
该实现的主要难点在于getMapper
方法,需采用jdk动态代理来获取持久层对象,并需针对不同的查询类型(增、删、查、改),执行不同的方法。
// DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
this.executor = new SimpleExecutor();
}
@Override
@SuppressWarnings("unchecked")
public <T> T selectOne(String statementId, Object... params) throws Exception {
// fixme 简单实现
List<Object> results = selectList(statementId, params);
int size = results.size();
if (size > 1) {
throw new WrongResultException("too many results!");
}
return size == 1 ? (T) results.get(0) : null;
}
@Override
public <T> List<T> selectList(String statementId, Object... params) throws Exception {
return executor.query(configuration, configuration.getStatements().get(statementId), params);
}
@Override
public int insert(String statementId, Object... params) throws Exception {
return executor.execute(configuration, configuration.getStatements().get(statementId), params);
}
@Override
public int update(String statementId, Object... params) throws Exception {
return executor.execute(configuration, configuration.getStatements().get(statementId), params);
}
@Override
public int delete(String statementId, Object... params) throws Exception {
return executor.execute(configuration, configuration.getStatements().get(statementId), params);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> clazz) {
return (T) Proxy.newProxyInstance(
DefaultSqlSession.class.getClassLoader(),
new Class[]{clazz},
(proxy, method, args) -> {
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
String statementId = className + "." + methodName;
MappedStatement mappedStatement = configuration.getStatements().get(statementId);
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
// 根据不同的查询类型,执行对应的方法
switch (sqlCommandType) {
case SELECT:
Type grt = method.getGenericReturnType();
if (grt instanceof ParameterizedType) { // 返回类型是否参数化, 以此来简单判断返回值是集合或者简单的对象
// 返回值是集合, 调用selectList方法
return selectList(statementId, args);
} else {
// 返回值是简单对象, 调用selectOne方法
return selectOne(statementId, args);
}
case INSERT:
return insert(statementId, args);
case UPDATE:
return update(statementId, args);
case DELETE:
return delete(statementId, args);
default:
throw new NonSupportedOperationException("operation not support");
}
}
);
}
}
3、SimpleExecutor实现类
(1)BoundSql
该类主要存放解析后的sql
信息:
public class BoundSql {
/** 解析后的sql形式, 如select * from user where id = ? and username = ?*/
private String parsedSql;
/** 存放该条sql中, 占位符#{}中的参数名 */
private List<ParameterMapping> parameterMappings = new ArrayList<>();
public BoundSql() {
}
public BoundSql(String parsedSql, List<ParameterMapping> parameterMappings) {
this.parsedSql = parsedSql;
this.parameterMappings = parameterMappings;
}
}
(2)TokenHandler接口及实现类ParamMappingTokenHandler
public interface TokenHandler {
String handleToken(String content);
}
public class ParamMappingTokenHandler implements TokenHandler {
/** 存放该条sql中位于占位符#{}中的所有参数 */
private List<ParameterMapping> parameterMappings = new ArrayList<>();
@Override
public String handleToken(String content) {
parameterMappings.add(buildParamMapping(content));
return "?";
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
private ParameterMapping buildParamMapping(String content) {
return new ParameterMapping(content);
}
}
(3)SimpleTokenParser
该类主要负责解析sql语句中的占位符#{}
及${}
,并替换成jdbc中的占位符?,该类的实现摘自Mybatis
:{@link org.apache.ibatis.parsing.GenericTokenParser}
public class SimpleTokenParser {
private String openToken;
private String closeToken;
private TokenHandler tokenHandler;
public SimpleTokenParser(String openToken, String closeToken, TokenHandler tokenHandler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.tokenHandler = tokenHandler;
}
/**
* #{}/${}解析
* fixme 本方法代码摘自Mybatis{@link org.apache.ibatis.parsing.GenericTokenParser}
* @param text 输入的字符串
* @return 解析后的字符串
*/
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
do {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(tokenHandler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
(4)ClassBuilder
该类主要使用反射操作获取类信息:
public class ClassBuilder {
private static final String METHOD_PREFIX_SET = "set";
private static final String METHOD_PREFIX_GET = "get";
private final Class<?> clazz;
private final Map<String, Method> setMethods = new HashMap<>();
private final Map<String, Method> getMethods = new HashMap<>();
private ClassBuilder(Class<?> clazz) {
this.clazz = clazz;
}
/**
* 根据类名获取类对象
* @param className 类名
* @return 类对象
* @throws ClassNotFoundException e
*/
public static ClassBuilder getClassBuilder(String className) throws ClassNotFoundException {
/*if (className == null) {
return null;
}*/
Class<?> clazz = Class.forName(className);
ClassBuilder cb = new ClassBuilder(clazz);
cb.differMethods(clazz);
return cb;
}
public Object newInstance() throws Exception {
return clazz.newInstance();
}
public Map<String, Method> getSetMethods() {
return setMethods;
}
public Map<String, Method> getGetMethods() {
return getMethods;
}
private void differMethods(Class<?> clazz) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.startsWith(METHOD_PREFIX_SET)) {
String fieldName = fieldNameByMethod(name, METHOD_PREFIX_SET);
setMethods.put(fieldName, method);
} else if (name.startsWith(METHOD_PREFIX_GET)) {
String fieldName = fieldNameByMethod(name, METHOD_PREFIX_GET);
getMethods.put(fieldName, method);
}
}
}
/**
* 根据get/set方法名, 获取相应的字段名称
* @param methodName get/set方法名
* @param prefix 需要移除的前缀
* @return 方法名对应的字段名称
*/
private String fieldNameByMethod(String methodName, String prefix) {
char[] chars = methodName.replace(prefix, "").toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
}
(5)核心类SimpleExecutor
public class SimpleExecutor implements Executor {
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 获取连接
Connection connection = configuration.getDs().getConnection();
// 获取sql语句
String sql = mappedStatement.getSql();
// 对sql进行解析
BoundSql boundSql = getBoundSql(sql);
PreparedStatement ps = null;
ResultSet rs = null;
List<T> results;
try {
ps = connection.prepareStatement(boundSql.getParsedSql());
// fixme 设置查询参数
setQueryArgs(ps, mappedStatement, boundSql, params);
rs = ps.executeQuery();
// fixme 遍历结果集
results = getResults(rs, mappedStatement);
} finally {
JdbcResourceUtils.close(null, ps, rs);
}
return results;
}
@Override
public int execute(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 获取连接
Connection connection = configuration.getDs().getConnection();
// 获取sql语句
String sql = mappedStatement.getSql();
// 对sql进行解析
BoundSql boundSql = getBoundSql(sql);
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = connection.prepareStatement(boundSql.getParsedSql());
// fixme 设置查询参数
setQueryArgs(ps, mappedStatement, boundSql, params);
boolean executed = ps.execute();
if (executed) {
rs = ps.getResultSet();
return rs.getFetchSize();
} else {
return ps.getUpdateCount();
}
} finally {
JdbcResourceUtils.close(null, ps, rs);
}
}
/**
* 将mapper文件sql中的占位符#{} 替换为jdbc中的占位符?
* @param sql sql
* @return boundSql
*/
private BoundSql getBoundSql(String sql) {
ParamMappingTokenHandler ptk = new ParamMappingTokenHandler();
// fixme 此处的openToken和closeToken暂时写死为#{和}
SimpleTokenParser stp = new SimpleTokenParser("#{", "}", ptk);
String parsedSql = stp.parse(sql);
return new BoundSql(parsedSql, ptk.getParameterMappings());
}
/**
* 为PrepareStatement对象设置查询条件
* @param ps ps
* @param mappedStatement mappedStatement
* @param boundSql 解析后的sql信息
* @param params 查询条件
* @throws Exception e
*/
private void setQueryArgs(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object... params) throws Exception {
List<ParameterMapping> paramMappings = boundSql.getParameterMappings();
String paramType = mappedStatement.getParamType();
if (paramType == null) { // fixme 标签中未设置paramType属性时,认为可能是传入了多个值,只进行了简单的一一对应
if (paramMappings.size() != params.length) {
throw new SqlParamException("expected args: " + paramMappings + ", actual args: " + Arrays.toString(params));
}
for (int i = 0; i < paramMappings.size(); i++) {
ps.setObject(i + 1, params[i]);
}
} else { // fixme 传参为paramType中设置的类型时(暂未考虑基本数据类型, 如:java.lang.Integer, java.lang.String等)
ClassBuilder cb = ClassBuilder.getClassBuilder(paramType);
Object pojo = params[0];
for (int i = 0; i < paramMappings.size(); i++) {
ParameterMapping pm = paramMappings.get(i);
String fieldName = pm.getContent();
Method method;
// fixme 没有做出严谨的判断, 当前只是根据是否有相应的get方法,来判断是否可以为该属性(fieldName)赋值;
// fixme 同时, 也不支持自动驼峰映射, 此处只是简单的进行了一一对应的处理
if ((method = cb.getGetMethods().get(fieldName)) == null) {
throw new MappedException("no such get method for field: " + fieldName);
}
ps.setObject(i + 1, method.invoke(pojo));
}
}
}
/**
* 将查询获取的结果集封装到相应的对象中
* @param rs 查询结果集
* @param mappedStatement 该条查询对应的参数信息
* @return 结果
* @throws Exception e
*/
@SuppressWarnings("unchecked")
private <T> List<T> getResults(ResultSet rs, MappedStatement mappedStatement) throws Exception {
String resultType = mappedStatement.getResultType();
ClassBuilder cb = ClassBuilder.getClassBuilder(resultType);
// 获取当前类的所有set方法
Map<String, Method> setMethods = cb.getSetMethods();
List<Object> results = new ArrayList<>();
while (rs.next()) {
Object obj = cb.newInstance();
ResultSetMetaData metaData = rs.getMetaData();
// 获取字段数目
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
// 获取字段名
String columnName = metaData.getColumnName(i);
Method method;
// fixme 没有做出严谨的判断, 当前只是根据是否有相应的set方法,来判断是否可以为该属性(columnName)赋值;
// fixme 同时, 也不支持自动驼峰映射, 此处只是简单的进行了一一对应的处理
if ((method = setMethods.get(columnName)) == null) {
throw new MappedException("no such set name for field: " + columnName);
}
method.invoke(obj, rs.getObject(i));
}
results.add(obj);
}
return (List<T>) results;
}
}
三、简单的测试
1、配置文件UserMapper.xml
<!-- namespace为本模块(user模块)的唯一标识 -->
<mapper namespace="com.gavin11.study.mybatis.ipersistance.test.mapper.UserMapper">
<!-- id属性指定本模块的唯一标识, resultType指定结果集要映射到的对象 -->
<select id="findAll" resultType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
select * from user
</select>
<!-- paramType指定了携带条件参数值得对象 -->
<select id="findByIdName" resultType="com.gavin11.study.mybatis.ipersistance.test.pojo.User" paramType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
select * from user where username=#{username} and id=#{id}
</select>
<select id="simpleFindByIdName" resultType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
select * from user where username=#{username} and id=#{id}
</select>
<insert id="insertOne" paramType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
insert into user(id,username,password,birthday) values(#{id},#{username},#{password},#{birthday})
</insert>
<update id="updateById" paramType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
update user set username=#{username} where id=#{id}
</update>
<delete id="deleteById" paramType="com.gavin11.study.mybatis.ipersistance.test.pojo.User">
delete from user where id=#{id}
</delete>
</mapper>
2、接口UserMapper
public interface UserMapper {
List<User> findAll();
User findByIdName(User user);
User simpleFindByIdName(String username, int id);
int insertOne(User user);
int updateById(User user);
int deleteById(User user);
}
3、测试程序Test
public class Test
{
public static void main( String[] args ) throws Exception
{
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build();
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(1);
user.setUsername("lucy");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
/*User byIdName = userMapper.findByIdName(user);
System.out.println(byIdName);*/
User byIdName = userMapper.simpleFindByIdName("lucy", 1);
System.out.println(byIdName);
/*int i = userMapper.insertOne(new User(4, "hhh", "skdjflsikd", "2020-01-09"));*/
/*int i = userMapper.updateById(new User(4, "gavin", "skdjflsikd", "2020-01-09"));*/
int i = userMapper.deleteById(new User(4, "gavin", "skdjflsikd", "2020-01-09"));
System.out.println(i);
}
}