您现在的位置是:首页 >技术教程 >手敲MyBatis(十一章)-支持注解配置执行SQL网站首页技术教程

手敲MyBatis(十一章)-支持注解配置执行SQL

渣渣洒泪成长记 2024-06-17 10:32:20
简介手敲MyBatis(十一章)-支持注解配置执行SQL

1.前言

这一章节从题目中也看出来我们要支持注解版的增删改查,可以在Mapper层的接口类的方法上写Sql语句,如:Insert,Update,Delete,Select的这几个基础Sql,如下图,这样就不用在Xml里去写Sql了,但是这就引申出一个问题,我们怎么解析注解的Sql语句,将方法上的SQL内容取出来并按之前的既定流程去处理执行Sql呢?

 我们现在要处理以下几步才能达到目的

1.配置文件中去配置注解类,加上此在解析的时候才能知道什么情况下用XML方式,在什么情况下用注解方式

2.在配置解析时如果是注解类,直接将class值为namespce进行代理注册 

3.开始解析注解,找到Mapper类里所有的方法,每一个方法都判断下是不是加了这4种注解

   3.1.获取Sql内容,传输到以前就有的流程里得到处理过后的Sql语句,SqlSource

   3.2.通过反射获取方法的参数、返回值、参数类型、返回值类型、并将获取到的数据映射到MappedStatement以及ResultMap

4.之后的流程不变

2.Uml类图

看到类图我们知道,这一章节其实也是很简单的,涉及添加的类只有几个,涉及的修改也不多

添加了4个注解类,1个解析注解类MappernnotationBuilder

XmlConfiguration这里修改判断下如果是注解方式直接调用MapperRegistry的addMapper方法将当前的class进行代理注册,

MapperRegistry类的addMapper方法里添加调用MappernnotationBuilder解析注解数据。

MappernnotationBuilder拿到Sql原内容后调用XmlLanguageDriver进行Sql源处理,继续解析参数、返回值、调用MapperBuilderAsisstant的addMappedStatement和addResultMaps方法将所得的数据映射到MappedStatement和ResultMap类中,供后续使用,之后的就还是和之前的流程一样。

 3.代码实现

XMLConfigBuilder类:此类解析Mapper时添加从xml中获取Class,如果这个Class不为空,证明是要解析注解类,通过反射将String的class类转换为类对象,并通过configuration添加到mapper映射里。

// 处理mapper的方法,mapper里有多个sql语句,所以需要List
    private void mapperElement(Element mappers) throws Exception {
        // 得到mybatis-config-datasource.xml的mappers标签里的mapper
        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            String resource = e.attributeValue("resource");
            String mapperClass = e.attributeValue("class");
            // xml解析方式
            if (resource != null && mapperClass == null) {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
                mapperParser.parse();
            } else if (resource == null && mapperClass != null) {
                // 注解方式
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
            }
        }
    }

 MapperRegistry类:此类里就添加了解析注解类的处理调用。

public <T> void addMapper(Class<T> type) {
        // 是接口
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // 如果重复添加了报错
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));


            // 解析注解类语句配置
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
        }
    }

MapperAnnotationBuilder类:此类本节新添加的类,专门处理注解的解析,如sql,参数,返回值等操作,

1.parse方法,拿到class的所有方法,如IuserDao定义的所有方法。

2.循环遍历每一个方法并调用parseStatement(method)方法,

    2.1 获取参数类型,调用getParameterType(method);
    2.2 获取语言驱动器,目的是为了创造Sql源,调用getLanguageDriver(method)                              2.3 获取数据源,通过调用getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver)方法。

          2.3.1 通过调用getSqlAnnotationType(method)方法,获取注解类型                                                  2.3.2 根据得到的注解类型获取当前方法上的注解信息,并得到方法上的注解值。                            2.3.3 将内容Sql、参数类型、语言驱动器传递,调用buildSqlSourceFromStrings();                                   2.3.3.1 将原Sql、参数类型传递给Sql源进行处理,调用createSql();

3.生成mappedStatementId标识

4.根据注解类型处理获取Sql标签,如INSERT、DELETE,,,,,调用getSqlCommandType(method);

5.判断标签是否是查询,是去解析结果,调用parseResultMap(method);

6.所有的数据已备全,将数据添加到mappedStatement,调用助手类的addMappedStatement()方法。
  

public class MapperAnnotationBuilder {
    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
    private MapperBuilderAssistant assistant;
    private Configuration configuration;
    private Class<?> type;

    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace(".", "/") + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    }

    /**
      解析
    */
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            assistant.setCurrentNamespace(type.getName());
            // 获取类里所有的方法
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (!method.isBridge()) {
                    // 解析语句
                    parseStatement(method);
                }
            }

        }
    }

    /**
     * 解析语句
     */
    private void parseStatement(Method method) {
        // 通过方法获取参数类型
        Class<?> parameterTypeClass = getParameterType(method);
        // 获取默认驱动器XmlLanguageDriver
        LanguageDriver languageDriver = getLanguageDriver(method);
        // 根据注解上的Sql内容获取数据源
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

        if (sqlSource != null) {
            // mappedStatementId标识
            final String mappedStatementId = type.getCanonicalName() + "." + method.getName();
            // 获取SQL标签,INSERT、UPDATE...
            SqlCommandType sqlCommandType = getSqlCommandType(method);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

            String resultMapId = null;
            if (isSelect) {
                // 是查询的化解析返回值
                resultMapId = parseResultMap(method);
            }
            // 注册到mappedStatement里
            assistant.addMappedStatement(
                    mappedStatementId,
                    sqlSource,
                    sqlCommandType,
                    parameterTypeClass,
                    resultMapId,
                    getReturnType(method),
                    languageDriver
            );
        }

    }

    /**
     * 重点:DAO 方法的返回类型,如果为 List 则需要获取集合中的对象类型
     */
    private Class<?> getReturnType(Method method) {
        Class<?> returnType = method.getReturnType();
        if (Collection.class.isAssignableFrom(returnType)) {
            Type returnTypeParameter = method.getGenericReturnType();
            if (returnTypeParameter instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                    returnTypeParameter = actualTypeArguments[0];
                    if (returnTypeParameter instanceof Class) {
                        returnType = (Class<?>) returnTypeParameter;
                    } else if (returnTypeParameter instanceof ParameterizedType) {
                        returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
                    } else if (returnTypeParameter instanceof GenericArrayType) {
                        Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
                        returnType = Array.newInstance(componentType, 0).getClass();
                    }
                }
            }
        }
        return returnType;
    }

    /** 解析返回类型处理为ResultMap */
    private String parseResultMap(Method method) {
        StringBuffer suffix = new StringBuffer();
        // 拼接参数类型
        for (Class<?> c : method.getParameterTypes()) {
            suffix.append("-");
            suffix.append(c.getSimpleName());
        }
        // 拼接空返回值
        if (suffix.length() < 1) {
            suffix.append("-void");
        }
        //df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
        String resultMapId = type.getName() + "." + method.getName() + suffix;

        // 添加ResultMap
        Class<?> returnType = getReturnType(method);
        assistant.addResultMap(resultMapId,returnType,new ArrayList<>());
        return resultMapId;
    }

    /** 根据注解类型获取Sql标签 */
    private SqlCommandType getSqlCommandType(Method method) {
        Class<? extends Annotation> type = getSqlAnnotationType(method);
        if (type == null) {
            return SqlCommandType.UNKNOWN;
        }
        return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
    }

    /**
     * 根据注解的值(Sql)获取Sql源
     * */
    private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
        try {
            // 获取注解类型
            Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
            if (sqlAnnotationType != null) {
                // 根据注解类型获取注解类
                Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
                 // 获取注解类的值-原始Sql语句
               final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
                // 根据获取的注解类的值构建Sql源
               return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException("Could not find value method on SQL annotation.  Cause: " + e);
        }

    }

    /**
     * 拼接SQL,创造数据源
     * */
    private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
        final StringBuilder sql = new StringBuilder();
        for (String fragment : strings) {
            sql.append(fragment);
            sql.append(" ");
        }
        return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
    }

    /**
     * 获取注解类型
     * */
    private Class<? extends Annotation> getSqlAnnotationType(Method method) {
        for (Class<? extends Annotation> type : sqlAnnotationTypes) {
            Annotation annotation = method.getAnnotation(type);
            if (annotation != null) return type;
        }
        return null;
    }

    /**
     * 获取语言驱动器
     * */
    private LanguageDriver getLanguageDriver(Method method) {
        Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        return configuration.getLanguageRegistry().getDriver(langClass);
    }

    /**
     * 获取参数类型
     * */
    private Class<?> getParameterType(Method method) {
        Class<?> parameterType = null;
        // 根据方法参数获取参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> clazz : parameterTypes) {
            if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {
                if (parameterType == null) {
                    parameterType = clazz;
                } else {
                    parameterType = MapperMethod.ParamMap.class;
                }
            }
        }
        return parameterType;
    }
}

MapperBuilderAssistant类:

此类添加addResultMap()方法,将ResultMap存储到Configuration中的ResultMaps变量里,并修改了setStatementResultMap()方法里补充了之前的逻辑空缺,如果resultMap不为空,则将Configuration的ResultMaps变量数据取出存储到MappedStatement里。

public class MapperBuilderAssistant{
 //id=此形式 df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
    public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                configuration,
                id,
                type,
                resultMappings);
        ResultMap resultMap = inlineResultMapBuilder.build();
        // 为啥在configuration里添加,不直接用上头的方法
        configuration.addResultMap(resultMap);
        return resultMap;
    }
   
   // 封装ResultMap对象
    private void setStatementResultMap(
            String resultMap,
            Class<?> resultType,
            MappedStatement.Builder statementBuilder
    ) {
        resultMap = applyCurrentNamespace(resultMap, true);
        List<ResultMap> resultMaps = new ArrayList<>();

        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            }
        } else if (resultType != null) {
           // 省略...
        }
        statementBuilder.resultMaps(resultMaps);
    }
}

 LanguageDriver类:语言驱动器重载了createSqlSource方法,参数里script是String形式的原Sql(注解解析出来的),而原来的则是脚本文件,还需xml脚本构建器处理,进一步解析

public interface LanguageDriver {
    // 省略其他....
    SqlSource createSqlSource(Configuration configuration,String script,Class<?> parameterType);
}

XmlLanguageDriver类:

实现新添加的 createSqlSource方法,由于已经是Sql数据了,所以此处直接new RawSqlSource,这时候后续流程就都一样了,RawSqlSource只需要处理后续流程将原Sql语句的参数处理成?即可。

 /**
     * 用于处理注解配置
     * */
    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        return new RawSqlSource(configuration, script, parameterType);
    }

Delete注解类

包:package df.middleware.mybatis.annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
    String[] value();
}

INSERT注解类

​
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {
    String[] value();
}

​

Select、Update注解类都是一样的,这里就不展示了,

Configuration里的修改

    // 结果映射,存在Map里-新增
    protected final Map<String, ResultMap> resultMaps = new HashMap<>();
   
    public ResultMap getResultMap(String id) {
        return resultMaps.get(id);
    }

    public void addResultMap(ResultMap resultMap) {
        resultMaps.put(resultMap.getId(), resultMap);
    }

4.测试准备

mybatis-config-datasource.xml : 数据库环境及注解或Xml mapper配置,此处修改成注解方式

<mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- <mapper resource="mapper/User_Mapper.xml"/>-->
        <!-- 注解类配置 -->
        <mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>
    </mappers>

</configuration>

Dao层使用注解


public interface IUserDao {

    @Select("SELECT id, userId, userName, userHead
" +
            "FROM user
" +
            "where id = #{id}")
    User queryUserInfoById(Long id);

    @Select("SELECT id, userId, userName, userHead
" +
            "        FROM user
" +
            "        where id = #{id}")
    User queryUserInfo(User req);

    @Select("SELECT id, userId, userName, userHead
" +
            "FROM user")
    List<User> queryUserInfoList();

    @Update("UPDATE user
" +
            "SET userName = #{userName}
" +
            "WHERE id = #{id}")
    int updateUserInfo(User req);

    @Insert("INSERT INTO user
" +
            "(userId, userName, userHead, createTime, updateTime)
" +
            "VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")
    void insertUserInfo(User req);

    @Insert("DELETE FROM user WHERE userId = #{userId}")
    int deleteUserInfoByUserId(String userId);

}

单元测试

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void test_insertUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        User user = new User();
        user.setUserId("10002");
        user.setUserName("小白");
        user.setUserHead("1_05");
        userDao.insertUserInfo(user);
        logger.info("测试结果:{}", "Insert OK");

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_deleteUserInfoByUserId() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        int count = userDao.deleteUserInfoByUserId("10002");
        logger.info("测试结果:{}", count == 1);

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_updateUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        int count = userDao.updateUserInfo(new User(1L, "10001", "叮当猫"));
        logger.info("测试结果:{}", count);

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_queryUserInfoById() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:基本参数
        User user = userDao.queryUserInfoById(1L);
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }

    @Test
    public void test_queryUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        User user = userDao.queryUserInfo(new User(1L));
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }

    @Test
    public void test_queryUserInfoList() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        List<User> users = userDao.queryUserInfoList();
        logger.info("测试结果:{}", JSON.toJSONString(users));
    }

}

运行新增和根据id查询测试一下,说明注解方式处理Sql成功啦,大家写完也可以测试其他的增删改查方法。

 数据库数据

 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。