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的执行了。