[MyBatis学习笔记] 一、仿造Mybatis实现一个简单的自定义持久层框架

7 篇文章 0 订阅

一、问题

使用原生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&amp;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);
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值