Spring - 基于JPA的动态SQL执行器

JPA动态SQL执行

通过JPA的EntityManager 实现SQL的执行。

public class DaoProxySupport {

	private EntityManager em;

	public DaoProxySupport(EntityManager em) {
		this.em = em;
	}
	
	public <T> List<T> search(String sql, Map<String, Object> condition, Class<T> clazz) {
		Query query = this.em.createNativeQuery(sql, clazz);
		if (condition != null) {
			for (String key : condition.keySet()) {
				if (SqlParserUtil.hasParam(sql, key))
					query.setParameter(key, condition.get(key));
			}
		}
		List<T> list = query.getResultList();
		return list;
	}

定义注解

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicSqlDao {
	String value() default ""; // 默认使用class名找对应的SQL文件
}

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlKey {
	String value() default "";  //接口对应的sqlKey

	Class<?> beanClass() ; 
}

使用方式如下:


@DynamicSqlDao
public interface MySqlDao {
	@SqlKey("GET_LIST",beanClass = TableA.class)
	List<TableA> getList(Map<String, Object> condition);
}

对应SQL文件

GET_LIST=
SELECT * FROM table_a
WHERE status = 1
AND (<if key="code">code like :code OR name like :code</if>)

动态sql的实现,防mybatis,根据map中的key是否存在决定是否执行其中的SQL。
如果不存在,直接转化为1=1即可。

SQL文件的加载及解析实现略过。

扫描SqlDao并注入到IOC容器

通过实现BeanDefinitionRegistryPostProcessor扫描指定包路径,并把BeanDefinition注册到BeanDefinitionRegistry 。


public class DaoDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {

	// SqlDao扫描的包路径
	private String sacnBasePackage = "";

	public String getSacnBasePackage() {
		return sacnBasePackage;
	}

	public void setSacnBasePackage(String sacnBasePackage) {
		this.sacnBasePackage = sacnBasePackage;
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		// 获取需要代理的接口的clazz列表
		Set<Class<?>> beanClazzs = scannerPackages(sacnBasePackage);
		for (Class<?> beanClazz : beanClazzs) {

			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
			GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

			// 加载 接口对应的SQL文件
			DaoSqlReader.getInstance().loadSql(beanClazz);

			// 在这里,我们可以给该对象的属性注入对应的实例。
			// 比如mybatis,就在这里注入了dataSource和sqlSessionFactory,
			// 注意,如果采用definition.getPropertyValues()方式的话,
			// 类似definition.getPropertyValues().add("interfaceType", beanClazz);
			// 则要求在FactoryBean(本应用中即ServiceFactory)提供setter方法,否则会注入失败
			// 如果采用definition.getConstructorArgumentValues(),
			// 则FactoryBean中需要提供包含该属性的构造方法,否则会注入失败
			// definition.getPropertyValues().add("interfaceClass",
			// definition.getBeanClassName());
			definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

			// 注意:这里的BeanClass是生成Bean实例的工厂,不是Bean本身。
			// FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,
			// 其返回的是该工厂Bean的getObject方法所返回的对象。
			definition.setBeanClass(DaoFactory.class);

			// 采用的是byType方式注入,类似的还有byName等
			definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
			registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
		}
	}

	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

	private MetadataReaderFactory metadataReaderFactory;

	/**
	 * 根据包路径获取包及子包下的所有类
	 * 
	 * @param basePackage basePackage
	 * @return Set<Class<?>> Set<Class<?>>
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private Set<Class<?>> scannerPackages(String basePackage) throws BeansException {
		Set<Class<?>> set = new LinkedHashSet<>();
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;

		Resource[] resources;
		try {
			resources = this.resourcePatternResolver.getResources(packageSearchPath);

			for (Resource resource : resources) {
				if (resource.isReadable()) {
					MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
					String className = metadataReader.getClassMetadata().getClassName();
					Class<?> clazz = Class.forName(className);

					// 如果不是接口,跳过
					if (!clazz.isInterface()) {
						continue;
					}

					// 如果没有DynamicSqlDao的注解,跳过
					if (!clazz.isAnnotationPresent(DynamicSqlDao.class)) {
						continue;
					}

					set.add(clazz);
				}
			}
		} catch (Exception e) {
			throw new FatalBeanException("扫描Sql Dao包下接口出错。", e);
		}

		return set;
	}

	protected String resolveBasePackage(String basePackage) {
		return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	private ResourcePatternResolver resourcePatternResolver;

	private ApplicationContext applicationContext;

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	private Environment getEnvironment() {
		return applicationContext.getEnvironment();
	}

}

动态代理

DaoFactory是SqlDao的工厂类,实现FactoryBean的接口。
负责在getObject产生具体的实例。具体的实例是一个JDK的动态代理类。
通过 @PersistenceContext private EntityManager em; 注入SQL的执行器。


public class DaoFactory<T> implements FactoryBean<T> {
	private Class<T> interfaceType;

	@PersistenceContext
	private EntityManager em;

	public DaoFactory(Class<T> interfaceType) {
		this.interfaceType = interfaceType;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getObject() throws Exception {
		// 创建接口对应的实例,便于注入到spring容器中
		return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[] { interfaceType }, new DaoProxy<>(this.em, this.interfaceType));
	}

	@Override
	public Class<T> getObjectType() {
		return interfaceType;
	}

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

动态代理的真正执行在DaoProxy中。

public class DaoProxy<T> implements InvocationHandler {
	private Logger logger = LoggerFactory.getLogger(DaoProxy.class);

	private Class<T> interfaceType;
	private EntityManager em;

	public DaoProxy(EntityManager em, Class<T> interfaceType) {
		this.em = em;
		this.interfaceType = interfaceType;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 如果是Object对象的方法,直接调用
		if (Object.class.equals(method.getDeclaringClass())) {
			return method.invoke(this, args);
		}

		logger.info("调用的接口类:{}", interfaceType);
		Object result = null;
		if (method.isAnnotationPresent(SqlKey.class)) {
			SqlKey key = method.getAnnotation(SqlKey.class);
			String sqkKey = key.value();
			Class<?> beanCalzz = key.beanClass();
			logger.info("调用的方法:{}", method);


			// 取得Condition对象
			Map<String, Object> condition = new HashMap<String, Object>();
			// 取得条件参数
			for (int i = 0; i < args.length; i++) {
				if (args[i] instanceof Map) {
					condition = (Map<String, Object>) args[i];
				}
			}
			logger.info("Sql参数:{}", condition);

			// 执行SQL
			DaoProxySupport support = new DaoProxySupport(this.em);
			String strSql = DaoSqlReader.getInstance().getSql(interfaceType, sqkKey, condition);
			result = support.search(strSql, condition, beanCalzz);
		}

		return result;
	}
}

以上,开发时像mybatis一样,编写接口和对应SQL,即可实现简单的动态SQL的执行了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值