MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
至于怎么使用,源码分析就不讲了,来直接实现一个基于Spring Boot的注解版MyBatis。
老规矩,先上效果图
项目采用个人惯用的Maven多模块方式,一个核心模块zbatis-core,一个测试模块zbatis-test。
因为使用Spring的JdbcTemplate作为Sql执行工具,所以zbatis-core的pom.xml文件需要引入Spring Boot的jdbc起步依赖。
org.springframework.boot
spring-boot-starter-jdbc
相关说明
1、项目启动时,ZBatisMapperScan注解触发ZBatisMapperScannerRegistrar使用ClassPathMapperScanner类将指定包里的接口声明为注入容器的候选对象,并指定这些候选对象的工厂类(创建它们的类)为实现了FactoryBean接口的MapperFactoryBean类。
2、MapperFactoryBean同时实现了InitializingBean接口:候选对象在初始化的时候,根据自己的接口创建一个MapperProxyFactory实例并存放于全局单例对象MapperRegister中。
3、在Mapper候选对象需要实例化的时候,就会调用MapperFactoryBean的getObject方法,从MapperRegister中根据接口名获取对应的MapperProxyFactory,然后调用MapperProxyFactory创建一个代理对象返回。代理对象的InvocationHandler为MapperProxy,MapperProxy存储了一个Mapper接口的所有方法,封装为MapperMethod类。
首先,启动类上面的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 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 doScan(String... basePackages) {
Set 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 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 implements FactoryBean, InitializingBean {
private Class mapperInterface;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class 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, MapperProxyFactory>> knownMappers = new HashMap<>();
@SuppressWarnings("unchecked")
public T getMapper(Class type) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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 boolean hasMapper(Class type) {
return knownMappers.containsKey(type);
}
/**
* 添加一个Mapper
*
* @param type
*/
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
}
当需要创建候选对象的实例时,会调用MapperFactoryBean的getObject方法来获取。
这个方法首先又会调用成员变量MapperRegistry实例的getMapper方法,根据接口拿到了MapperProxyFactory对象。然后Mapper ProxyFactory创建一个代理对象返回。
/**
* Mapper代理工厂,每一个Mapper对应此类的一个实例
* @author z_hh
* @time 2019年1月25日
*/
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return mapperInterface;
}
public Map getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
mapperProxy);
}
public T newInstance(JdbcTemplate jdbcTemplate) {
final MapperProxy mapperProxy = new MapperProxy<>(jdbcTemplate, methodCache);
return newInstance(mapperProxy);
}
}
在创建代理对象的时候,所指定的InvocationHandler参数为实现了该接口的MapperProxy类实例。当方法调用的时候,会调用MapperProxy的invoke方法。
当方法为Object的方法时,直接调用;当方法为默认方法时,创建Class类的实例调用;(这两个直接摘自MyBatis源码)
除此之外,剩下的就是Mapper的抽象方法了。这时候,从缓存中获取方法对应的MapperMehod对象,调用它的execute方法。
/**
* Mapper代理的InvocationHandler
*
* @author z_hh
* @time 2019年1月25日
*/
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final JdbcTemplate jdbcTemplate;
private final Map methodCache;
public MapperProxy(JdbcTemplate jdbcTemplate, Map 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 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
}