自定义实现mybatis框架的简单实现-搭建注解开发

发布于 2020-07-26  1175 次阅读


笔者在上篇博客中已经详细介绍了如何简单实现mybatis框架基于XML的实现,其博客地址为:自定义实现mybatis框架的简单实现-搭建XML开发
下面继续实现自定义的mybatis搭建注解开发,在原来XML搭建的基础上继续实现。
1.将mybatis-config.xml中的mappers标签改为:

<mappers>
        <!--  指定Userdao的位置,使用XML配置时使用resource标签-->
        <!--使用注解配置的时候使用class标签,使用UserDao的全限定类名   -->
        <mapper class="dao.UserDao"></mapper>
    </mappers>

2.在包mybatis中创建一个新的注解接口类Select.java:

package mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Classname:mybatis-self
 *
 * @description:{description}
 * @author: 陌意随影
 * @Date: 2020-07-26 09:59
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    /**
     * @Description :配置SQL语句
     * @Date 10:06 2020/7/26 0026
     * @Param * @param  :
     * @return java.lang.String
     **/
    String value();
}

3.然后在UserDao的每个方法上写上注解:

package dao;

import entity.User;
import mybatis.Select;

import java.util.List;

/**
 * Classname:mybatisdemo
 * @description:User的dao接口
 * @author: 陌意随影
 * @Date: 2020-07-24 10:35
 */
public interface UserDao {
    /**
     * @date: 2020/7/24 0024 10:41
     * @description:获取所有的用户信息
     * @return: 返回包含所有的用户的list
     */
    @Select("select *from user")
    List<User> getAll();
    /**
     * @Description :根据id获取制定对象
     * @Date 0:32 2020/7/26 0026
     * @Param * @param id :
     * @return entity.User
     **/
    @Select("select* from user where id=?")
   User getOne(int id);

}

4.在工具类MybatisUtil.java中添加一个解析dao接口注解的函数annotationLoader(String daoPath) :

/**
 * @Description :通过反射获取dao中注解的value以及全限定方法名
 * @Date 10:09 2020/7/26 0026
 * @Param * @param daoPath :
 * @return java.util.Map<java.lang.String,mybatis.Mapper>
 **/
    private static Map<String, Mapper> annotationLoader(String daoPath) throws Exception {
        Map<String, Mapper> mapperMap = new HashMap<>();
        //获取dao的字节码文件
        Class<?> aClass = Class.forName(daoPath);
        //获取所有的方法
        Method[] methods = aClass.getMethods();
        //遍历每一个方法
        for (Method method : methods){
            //判断是否是注解
            boolean isAnnotation = method.isAnnotationPresent(Select.class);
            if (isAnnotation){
                Mapper mapper = new Mapper();
                //是select注解
                Select select = method.getAnnotation(Select.class);
                //获取select注解的值,即获取sql语句
                String sql = select.value();
                //获取方法名
                String methodName = method.getName();
                //组建key
                String key = daoPath+"."+methodName;
                //获取返回类型
                Type returnType = method.getGenericReturnType();
                //判断是否是参数化类型
                if (returnType instanceof ParameterizedType){
                    //强转成泛型参数类型,比如 List<User>
                    ParameterizedType parameterizedType = (ParameterizedType) returnType;
                    //获取实际参数类型数组
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    //获取List<User>中的User泛型实际参数
                   Class actualType = (Class) actualTypeArguments[0];
                   //获取实际参数类型全限定类名
                    String actualTypeName = actualType.getName();
                   mapper.setResultTypePath(actualTypeName);
                }else{
                    //不是泛型化参数,是实体类
                    Class<?> methodReturnType = method.getReturnType();
                    //获取返回 类型的全限定类名
                    String methodReturnTypeName = methodReturnType.getName();
                    mapper.setResultTypePath(methodReturnTypeName);
                }
             mapper.setSql(sql);
             mapper.setSqlType(Mapper.SELECT);
             mapperMap.put(key,mapper);

            }
        }

        return mapperMap;
    }

对UserDao注解进行解析的时候,比较难的点是如何获取函数的返回值的实际参数类型,为此我特意写了一篇文章:
Java通过反射获取方法的返回值的类型以及参数化的实际类型(上篇)
MybatisUtil.java文件更新后的完整代码为:

package utils;

import mybatis.Configurantion;
import mybatis.Mapper;
import mybatis.Resources;
import mybatis.Select;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Classname:MybatisUtil
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-07-25 15:44
 * @Version: 1.0
 **/
public class MybatisUtil {
    /**
     * @Description :通过资源文件流加载配置信息
     * @Date 15:45 2020/7/25 0025
     * @Param * @param inputStream :
     * @return mybatis.Configurantion
     **/
    public static Map<String, Mapper> XMLLoader(String mapperPath) throws DocumentException {
        InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
        //获取读取流对象
        SAXReader reader = new SAXReader();
        //获取document对象
        Document document = reader.read(resourceAsStream);
        //获取根节点,根节点就是mapper节点
        Element rootElement = document.getRootElement();
        //获取mapper的namespace的属性值
        String namespace = rootElement.attributeValue("namespace");
        //获取mapper下的每个select节点
        List nodes = rootElement.selectNodes("//select");
        Map<String, Mapper> mapperMap = new HashMap<>();
        if (nodes!=null && nodes.size() != 0){
            Map<String, Mapper> mappers = getMapperMap(namespace, nodes,Mapper.SELECT);
            mapperMap.putAll(mappers);
        }
        //获取mapper下的每个insert节点
         nodes = rootElement.selectNodes("//insert");
        if (nodes!=null && nodes.size() != 0){
            Map<String, Mapper> mappers = getMapperMap(namespace, nodes,Mapper.INSERT);
            mapperMap.putAll(mappers);
        }
        //获取mapper下的每个delete节点
         nodes = rootElement.selectNodes("//delete");
        if (nodes!=null && nodes.size() != 0){
            Map<String, Mapper> mappers = getMapperMap(namespace, nodes,Mapper.DELETE);
            mapperMap.putAll(mappers);
        }
        //获取mapper下的每个update节点
        nodes = rootElement.selectNodes("//update");
        if (nodes!=null && nodes.size() != 0){
            Map<String, Mapper> mappers = getMapperMap(namespace, nodes,Mapper.UPDATE);
            mapperMap.putAll(mappers);
        }

        return mapperMap;
    }
    /**
     * @Description :根据namespace,操作类型节点以及操作类型获取封装的Map
     * @Date 20:19 2020/7/25 0025
     * @Param * @param namespace: dao接口的全限定类名
     * @param nodes :操作类型的节点集合
     * @param sqlType :操作类型
     * @return java.util.Map<java.lang.String,mybatis.Mapper>
     **/
    private static Map<String, Mapper> getMapperMap(String namespace, List nodes,int sqlType) {
        Map<String,Mapper> mapperMap = new HashMap<>();
        for (Object o:nodes){
            Element element = (Element) o;
            //获取select的id属性值,相当于dao接口的方法名
            String daoMethodName = element.attributeValue("id");
            //获取select的resultType的属性值,相当于dao接口的返回值类型
            String resultType = element.attributeValue("resultType");
            //组建一个dao全限定类名和方法名组成的key,比如  dao.UserDao.getAll,标识dao包下的USerDao接口中的getAll方法
            String key = namespace+"."+daoMethodName;
            //获取select标签的sql语句
            String sql = element.getText();
            Mapper mapper = new Mapper();
            //设置返回值类型
            mapper.setResultTypePath(resultType);
            //设置sql语句
            mapper.setSql(sql);
            //将mapper存入Map中
            mapperMap.put(key,mapper);
            //设置sql操作类型
            mapper.setSqlType(sqlType);
        }
        return mapperMap;
    }

    /**
   * @Description :加载XML的配置信息
   * @Date 15:45 2020/7/25 0025
   * @Param * @param inputStream :资源文件流
   * @return mybatis.Configurantion
   **/
    public static Configurantion LoaderConfiguration(InputStream inputStream) throws Exception {
        //获取读取流对象
        SAXReader reader = new SAXReader();
        Configurantion configurantion = new Configurantion();
        try {
            //根据资源文件流获取document对象
            Document document = reader.read(inputStream);
            //获取文档的根节点
            Element rootElm = document.getRootElement();
            //获取默认的数据库配置
            List list = rootElm.selectNodes("environments");
            if (list == null || list.size() ==0){
                throw  new RuntimeException("尚未配置数据库连接环境信息!");
            }
            //获取environments的id
            Element environmentsEle = (Element) list.get(0);
            String defaultEnvironmentId = environmentsEle.attributeValue("default");
            //获取所有的已经配置的数据库环境
            List environmentNodes = environmentsEle.elements();
            if (environmentNodes == null || environmentNodes.size() ==0){
                throw  new RuntimeException("尚未配置数据库连接环境信息!");
            }
            //获取与默认的environments的id相同的environment节点
            List propertyList = null;
            for (Object o: environmentNodes){
                Element environmentEle = (Element) o;
                //找到默认的数据库连接环境
                if (defaultEnvironmentId.equals(environmentEle.attributeValue("id"))){
                   //获取数据库的链接信息,driver,username,password,url
                    //获取该环境下的property标签
                     propertyList = environmentEle.selectNodes("//property");
                     //跳出循环
                    break;
                }
            }
            if(propertyList  == null){
                throw  new RuntimeException("尚未配置数据库连接环境信息!");
            }
            for (Object p:propertyList  ) {
                Element propertyEle = (Element) p;
                //获取property标签的name属性值
                String name = propertyEle.attributeValue("name");
                if ("url".equals(name)){
                    //设置URL的值
                    configurantion.setUrl(propertyEle.attributeValue("value"));
                }
                if ("driver".equals(name)){
                    //设置driver的值
                    configurantion.setDriver(propertyEle.attributeValue("value"));
                }
                if ("username".equals(name)){
                    //设置username的值
                    configurantion.setUsername(propertyEle.attributeValue("value"));
                }
                if ("password".equals(name)){
                    //设置password的值
                    configurantion.setPassword(propertyEle.attributeValue("value"));
                }
            }
            //定位到mybatis配置文件中的 mappers 中的mapper标签的所有节点
            List mapperNodes = rootElm.selectNodes("mappers/mapper");
            if (mapperNodes == null || mapperNodes.size() == 0){
                throw  new RuntimeException("您未配置mapper的信息!");
            }
            //逐个遍历mapper节点然后获取其中resource的值
            for (Object e:mapperNodes){
                Element element = (Element) e;
                String  daoPath = element.attributeValue("resource");
                if (daoPath != null){
                    //是XML配置
                    Map<String, Mapper> mapperMap = XMLLoader(daoPath);
                    configurantion.setMapperMap(mapperMap);
                }else{

                    //是注解配置
                    daoPath = element.attributeValue("class");
                    //从dao接口中获取对应的配置
                    Map<String,Mapper> mapperMap = annotationLoader(daoPath);
                    configurantion.setMapperMap(mapperMap);
                }
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        }

        return  configurantion;
    }
/**
 * @Description :通过反射获取dao中注解的value以及全限定方法名
 * @Date 10:09 2020/7/26 0026
 * @Param * @param daoPath :
 * @return java.util.Map<java.lang.String,mybatis.Mapper>
 **/
    private static Map<String, Mapper> annotationLoader(String daoPath) throws Exception {
        Map<String, Mapper> mapperMap = new HashMap<>();
        //获取dao的字节码文件
        Class<?> aClass = Class.forName(daoPath);
        //获取所有的方法
        Method[] methods = aClass.getMethods();
        //遍历每一个方法
        for (Method method : methods){
            //判断是否是注解
            boolean isAnnotation = method.isAnnotationPresent(Select.class);
            if (isAnnotation){
                Mapper mapper = new Mapper();
                //是select注解
                Select select = method.getAnnotation(Select.class);
                //获取select注解的值,即获取sql语句
                String sql = select.value();
                //获取方法名
                String methodName = method.getName();
                //组建key
                String key = daoPath+"."+methodName;
                //获取返回类型
                Type returnType = method.getGenericReturnType();
                //判断是否是参数化类型
                if (returnType instanceof ParameterizedType){
                    //强转成泛型参数类型,比如 List<User>
                    ParameterizedType parameterizedType = (ParameterizedType) returnType;
                    //获取实际参数类型数组
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    //获取List<User>中的User泛型实际参数
                   Class actualType = (Class) actualTypeArguments[0];
                   //获取实际参数类型全限定类名
                    String actualTypeName = actualType.getName();
                   mapper.setResultTypePath(actualTypeName);
                }else{
                    //不是泛型化参数,是实体类
                    Class<?> methodReturnType = method.getReturnType();
                    //获取返回 类型的全限定类名
                    String methodReturnTypeName = methodReturnType.getName();
                    mapper.setResultTypePath(methodReturnTypeName);
                }
             mapper.setSql(sql);
             mapper.setSqlType(Mapper.SELECT);
             mapperMap.put(key,mapper);

            }
        }

        return mapperMap;
    }
}

5.其余代码不需要改动,和XML配置的代码一样,UserDaoTest.java文件也不需要改动,直接运行得到测试:

测试结果

本次原码已经打包上传到服务器,如有需要请自行下载后解压导入Idea即可:http://moyisuiying.com/wp-content/uploads/2020/07/mybatis-self_2.rar


繁华落尽,雪花漫天飞舞。