自己动手写MyBatis框架

版权声明:欢迎转载,记得前排留名哦☞ https://blog.csdn.net/qq_31142553/article/details/86655951

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

至于怎么使用,源码分析就不讲了,来直接实现一个基于Spring Boot的注解版MyBatis

老规矩,先上效果图

一、运行效果

1、引入核心模块

2、Spring Boot启动类加上注解,并指定扫描Mapper的包名

3、配置数据源

4、编写实体类和对应的Mapper接口

5、编写Service

6、编写测试用例

7、走你

二、项目结构

项目采用个人惯用的Maven多模块方式,一个核心模块zbatis-core,一个测试模块zbatis-test。

因为使用Spring的JdbcTemplate作为Sql执行工具,所以zbatis-core的pom.xml文件需要引入Spring Boot的jdbc起步依赖。

                <!--jdbc -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

三、整体思路

 

相关说明

1、项目启动时,ZBatisMapperScan注解触发ZBatisMapperScannerRegistrar使用ClassPathMapperScanner类将指定包里的接口声明为注入容器的候选对象,并指定这些候选对象的工厂类(创建它们的类)为实现了FactoryBean接口的MapperFactoryBean类。

2、MapperFactoryBean同时实现了InitializingBean接口:候选对象在初始化的时候,根据自己的接口创建一个MapperProxyFactory实例并存放于全局单例对象MapperRegister中。

3、在Mapper候选对象需要实例化的时候,就会调用MapperFactoryBean的getObject方法,从MapperRegister中根据接口名获取对应的MapperProxyFactory,然后调用MapperProxyFactory创建一个代理对象返回。代理对象的InvocationHandler为MapperProxy,MapperProxy存储了一个Mapper接口的所有方法,封装为MapperMethod类。

四、代码讲解

1、项目启动时将指定包名下面的Mapper接口声明为注入Spring IOC容器的候选对象

首先,启动类上面的ZbatisMapperScan注解,它使用@Import通过导入的方式将ZBatisMapperScannerRegistrar类实例加入spring IOC容器。

/**
 * 开启ZBatis功能,并且指定扫描的包路径
 * @author z_hh  
 * @date 2019年1月23日
 */
@Retention(RUNTIME)
@Target(TYPE)
@Import(ZBatisMapperScannerRegistrar.class)
public @interface ZBatisMapperScan {

	/**
	 * 扫描的包名,可多个
	 */
	String[] value();
}

然后,ZBatisMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法实现bean的动态注入。在这里使用自定义的类路径bean注册扫描器ClassPathMapperScanner将指定包名下面的接口声明为准备注入到Spring IOC容器的候选对象。

/**
 * 对应ZBatisMapperScan注解的@Import
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public class ZBatisMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		AnnotationAttributes mapperScanAttrs = AnnotationAttributes
				.fromMap(importingClassMetadata.getAnnotationAttributes(ZBatisMapperScan.class.getName()));
		if (mapperScanAttrs != null) {
			registerBeanDefinitions(mapperScanAttrs, registry);
		}
	}

	void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
		List<String> basePackages = new ArrayList<>();
		basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText)
				.collect(Collectors.toList()));
		ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
		scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
		scanner.doScan(StringUtils.toStringArray(basePackages));
	}

}

ClassPathMapperScanner重写了doScan和isCandidateComponent两个方法。isCandidateComponent重写了候选条件,只要是独立的接口即可。doScan先调用父类的doScan方法获取到符合的BeanDefinitionHolder集合,然后调整它们的一些属性,最重要的是将beanClass设置为MapperFactoryBean,这就意味着,这些GenericBeanDefinition的实例将由MapperFactoryBean的getObject方法创建产生。

/**
 * 路径Mapper扫描器,用于将指定路径下的类/接口声明为bean
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

	private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
		super(registry, false);
	}

	@Override
	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

		if (beanDefinitions.isEmpty()) {
			LOGGER.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages)
					+ "' package. Please check your configuration.");
		} else {
			processBeanDefinitions(beanDefinitions);
		}

		return beanDefinitions;
	}

	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
		GenericBeanDefinition definition;
		for (BeanDefinitionHolder holder : beanDefinitions) {
			definition = (GenericBeanDefinition) holder.getBeanDefinition();
			String beanClassName = definition.getBeanClassName();
			System.out.println("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '"
					+ beanClassName + "' mapperInterface");

			// the mapper interface is the original class of the bean
			// but, the actual class of the bean is MapperFactoryBean
			definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
			definition.setBeanClass(MapperFactoryBean.class);
			definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		}
	}

	/**
	 * 候选条件:为独立的接口即可
	 */
	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
	}

}

最后,MapperFactoryBean注入一个MapperRegistry的实例,并且实现了InitializingBean接口:重写afterPropertiesSet方法,候选对象在初始化的时候根据自己的接口创建一个MapperProxyFactory对象放到MapperRegistry实例中存储。

/**
 * 创建Mapper代理对象的工程bean
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public class MapperFactoryBean<T> implements FactoryBean<T>, InitializingBean {

	private Class<T> mapperInterface;

	public MapperFactoryBean() {
		// intentionally empty
	}

	public MapperFactoryBean(Class<T> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	/**
	 * 从这里获取指定的bean
	 */
	@Override
	public T getObject() throws Exception {
		return mapperRegistry.getMapper(mapperInterface);
	}

	@Override
	public Class<?> getObjectType() {
		return this.mapperInterface;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	/**
	 * 创建Mapper代理对象
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		mapperRegistry.addMapper(mapperInterface);
	}

	@Autowired
	private MapperRegistry mapperRegistry;
}
/**
 * Mapper注册器,全局唯一,存储Mapper信息
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
@Component
@ConditionalOnBean(annotation = ZBatisMapperScan.class)
public class MapperRegistry {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

	@SuppressWarnings("unchecked")
	public <T> T getMapper(Class<T> type) {
		final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
		if (mapperProxyFactory == null) {
			throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
		}
		try {
			return mapperProxyFactory.newInstance(jdbcTemplate);
		} catch (Exception e) {
			throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
		}
	}

	public <T> boolean hasMapper(Class<T> type) {
		return knownMappers.containsKey(type);
	}

	/**
	 * 添加一个Mapper
	 * 
	 * @param type
	 */
	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));
		}
	}
}

2、Mapper接口的代理对象创建

当需要创建候选对象的实例时,会调用MapperFactoryBean的getObject方法来获取。

这个方法首先又会调用成员变量MapperRegistry实例的getMapper方法,根据接口拿到了MapperProxyFactory对象。然后Mapper ProxyFactory创建一个代理对象返回。

/**
 * Mapper代理工厂,每一个Mapper对应此类的一个实例
 * @author z_hh
 * @time 2019年1月25日
 */
public class MapperProxyFactory<T> {

	private final Class<T> mapperInterface;
	private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

	public MapperProxyFactory(Class<T> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	public Class<T> getMapperInterface() {
		return mapperInterface;
	}

	public Map<Method, MapperMethod> getMethodCache() {
		return methodCache;
	}

	@SuppressWarnings("unchecked")
	protected T newInstance(MapperProxy<T> mapperProxy) {
		return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
				mapperProxy);
	}

	public T newInstance(JdbcTemplate jdbcTemplate) {
		final MapperProxy<T> mapperProxy = new MapperProxy<>(jdbcTemplate, methodCache);
		return newInstance(mapperProxy);
	}

}

3、Mapper代理对象的方法调用

在创建代理对象的时候,所指定的InvocationHandler参数为实现了该接口的MapperProxy类实例。当方法调用的时候,会调用MapperProxy的invoke方法。

当方法为Object的方法时,直接调用;当方法为默认方法时,创建Class类的实例调用;(这两个直接摘自MyBatis源码

除此之外,剩下的就是Mapper的抽象方法了。这时候,从缓存中获取方法对应的MapperMehod对象,调用它的execute方法。

/**
 * Mapper代理的InvocationHandler
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

	private static final long serialVersionUID = -6424540398559729838L;

	private final JdbcTemplate jdbcTemplate;
	private final Map<Method, MapperMethod> methodCache;

	public MapperProxy(JdbcTemplate jdbcTemplate, Map<Method, MapperMethod> methodCache) {
		this.jdbcTemplate = jdbcTemplate;
		this.methodCache = methodCache;
	}

	/**
	 * 方法调用
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		try {
			if (Object.class.equals(method.getDeclaringClass())) {
				return method.invoke(this, args);
			} else if (isDefaultMethod(method)) {
				return invokeDefaultMethod(proxy, method, args);
			}
		} catch (Throwable t) {
			throw t;
		}
		final MapperMethod mapperMethod = cachedMapperMethod(method);
		return mapperMethod.execute(args);
	}

	private MapperMethod cachedMapperMethod(Method method) {
		return methodCache.computeIfAbsent(method, k -> new MapperMethod(jdbcTemplate, method));
	}

	/**
	 * 调用默认方法
	 * 
	 * @param proxy
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
		final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
				.getDeclaredConstructor(Class.class, int.class);
		if (!constructor.isAccessible()) {
			constructor.setAccessible(true);
		}
		final Class<?> declaringClass = method.getDeclaringClass();
		return constructor
				.newInstance(declaringClass,
						MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE
								| MethodHandles.Lookup.PUBLIC)
				.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
	}

	/**
	 * 是否为默认方法
	 * 
	 * @param method
	 * @return
	 */
	private boolean isDefaultMethod(Method method) {
		return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
				&& method.getDeclaringClass().isInterface();
	}
}

MapperMethod类,定义了Mapper中一个方法的信息:SQL命令的类型和语句、返回值类型等,这些变量在构造函数里面进行赋值。

这里面又分查询方法比较复杂,单独抽取出来处理,自我认为写的不错,哈哈哈。

/**
 * 对应Mapper类里面的一个方法
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public class MapperMethod {

	private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	// 用于执行SQL语句
	private final JdbcTemplate jdbcTemplate;
	// SQL命令,包括语句和类型
	private final SqlCommand command;
	// 返回值类型,可以获取泛型
	private final Type rtType;
	// 返回值类型
	private final Class<?> rtClass;

	public MapperMethod(JdbcTemplate jdbcTemplate, Method method) {
		this.jdbcTemplate = jdbcTemplate;
		this.command = new SqlCommand(method);
		this.rtType = method.getGenericReturnType();
		this.rtClass = method.getReturnType();
	}

	/**
	 * 执行
	 * 
	 * @param args
	 *            参数值
	 * @return
	 */
	public Object execute(Object[] args) {
		Object result;
		switch (command.getType()) {
			case UPDATE: {// 增/删/改
				result = jdbcTemplate.update(command.getName(), args);
				break;
			}
			case QUERY: {// 查询
				result = query(args);
				break;
			}
			default:
				throw new RuntimeException("Unknown execution method for: " + command.getName());
		}
		return result;
	}

	/**
	 * 根据返回值类型调用jdbcTemplate的不同方法
	 * @param args
	 * @return
	 */
	private Object query(Object[] args) {
		// 1、返回值类型为Map
		if (Map.class.isAssignableFrom(rtClass)) {
			return jdbcTemplate.queryForMap(command.getName(), args);
		}
		// 2、返回值类型为List
		if (List.class.isAssignableFrom(rtClass)) {
			// 2.1泛型
			if (rtType instanceof ParameterizedType) {
				Type[] actualTypeArguments = ((ParameterizedType) rtType).getActualTypeArguments();
				try {
					Class<?> clazz = Class.forName(actualTypeArguments[0].getTypeName());
					// 2.1.1泛型为Java自身的类
					if (isJavaClass(clazz)) {
						return jdbcTemplate.queryForList(command.getName(), clazz, args);
					}
					// 2.1.2泛型为用户自定义类
					RowMapper<?> rm = BeanPropertyRowMapper.newInstance(clazz);
					return jdbcTemplate.query(command.getName(), rm, args);
				} catch (ClassNotFoundException e) {
					// 2.1.3异常时记录日志,并执行非泛型查询
					LOG.warn("泛型转换异常!", e);
				}
			}
			// 2.2非泛型
			return jdbcTemplate.queryForList(command.getName(), args);
		}
		// 3、返回值类型为单个对象
		// 3.1Java自身的类
		if (isJavaClass(rtClass)) {
			return jdbcTemplate.queryForObject(command.getName(), rtClass, args);
		}
		// 3.2用户自定义类
		RowMapper<?> rm = BeanPropertyRowMapper.newInstance(rtClass);
		return jdbcTemplate.queryForObject(command.getName(), rm, args);
	}

	/**
	 * 是否为Java自身类
	 * @param clz
	 * @return
	 */
	private boolean isJavaClass(Class<?> clz) {
		return clz != null && clz.getClassLoader() == null;
	}

	public static class SqlCommand {
		private final String name;
		private final SqlCommandType type;

		public SqlCommand(Method method) {
			Annotation annotation;
			if ((annotation = method.getAnnotation(Query.class)) != null) {
				type = SqlCommandType.QUERY;
				name = ((Query) annotation).value();
			} else if ((annotation = method.getAnnotation(Update.class)) != null) {
				type = SqlCommandType.UPDATE;
				name = ((Update) annotation).value();
			} else {
				type = SqlCommandType.UNKNOWN;
				name = null;
			}
		}

		public String getName() {
			return name;
		}

		public SqlCommandType getType() {
			return type;
		}
	}
}

对应的注解以及SQL命令类型枚举类

/**
 * 操作类(增加/修改/删除)语句
 * @author z_hh
 * @time 2019年1月25日
 */
@Retention(RUNTIME)
@Target(METHOD)
public @interface Update {

	String value();
}
/**
 * 查询类语句
 * @author z_hh
 * @time 2019年1月25日
 */
@Retention(RUNTIME)
@Target(METHOD)
public @interface Query {

	String value();
}
/**
 * Mapper方法对应SQL的类型
 * 
 * @author z_hh
 * @time 2019年1月25日
 */
public enum SqlCommandType {
	UPDATE, QUERY, UNKNOWN
}

项目完整代码已上传,点击下载

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/86655951