笔者在上篇博客中已经详细介绍了如何简单实现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