因项目中需要对表的一些列进行全文索引,但是jpa是不带全文索引创建的功能,所以自己写了一个创建全文索引注解
来自动创建全文索引,需要配合spring 或 springboot使用,一定要spring扫描到如下类:
ApplicationUtil.java
ScanSupport.java
FulltextDelegator.java
1.获取spring环境的上下文
package cc.zeelan.mall.common.application; import java.util.Objects; import javax.persistence.EntityManager; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; /** * 获取spring-context上下文环境和EntityManager相关对象 * * @project common-utils * @fileName ApplicationUtil.java * @Description * @author light-zhang * @date 2019年4月29日 * @version 1.0.0 */ @Configuration public class ApplicationUtil implements ApplicationContextAware { /** * 事务超时时间 */ public static final int TransactionalTimeOut = 3000; /** * 上下文环境 */ private static ApplicationContext applicationContext; /** * jpa对象管理器 */ private static EntityManager entityManager; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } @Autowired public ApplicationUtil(EntityManager entityManager) { ApplicationUtil.entityManager = entityManager; } public static EntityManager getEntityManager() { return entityManager; } /** * 关闭实体管理 */ public static void closeEntityManager() { if (!Objects.isNull(ApplicationUtil.getEntityManager())) { getEntityManager().close(); } } }
2.扫描指定包下面的所有类信息
/** * SPRING扫描包下面的类 * * @project common-utils * @fileName Scaner.java * @Description * @author light-zhang * @date 2019年5月5日 * @version 1.0.0 */ public class ScanSupport implements ResourceLoaderAware { /** * 注入ResourceLoader让Spring管理 */ private ResourceLoader resourceLoader; private ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); private static final String FULLTEXT_SACN_PACKAGE_PATH = "fulltext.scan.package"; /** * set注入对象 */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } /** * 利用spring提供的扫描包下面的类信息,再通过classfrom反射获得类信息 * * @param scanPath * @return * @throws IOException */ public Set<Class<?>> doScan(String scanPath) throws IOException { Set<Class<?>> classes = new HashSet<Class<?>>(); String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX .concat(ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(scanPath)) .concat("/**/*.class")); Resource[] resources = resolver.getResources(packageSearchPath); MetadataReader metadataReader = null; for (Resource resource : resources) { if (resource.isReadable()) { metadataReader = metadataReaderFactory.getMetadataReader(resource); try { if (metadataReader.getClassMetadata().isConcrete()) {// 当类型不是抽象类或接口在添加到集合 classes.add(Class.forName(metadataReader.getClassMetadata().getClassName())); } } catch (Exception e) { e.printStackTrace(); } } } return classes; } /** * 指定包下面的类信息 * * @return 多个类信息 */ public static Set<Class<?>> classInfos() { try { String scanPath = ApplicationUtil.getApplicationContext().getEnvironment() .getProperty(FULLTEXT_SACN_PACKAGE_PATH); return new ScanSupport().doScan(scanPath); } catch (Exception e) { Assert.RuntimeException("扫描包类信息错误"); } return null; } }
3.创建全文索引注解
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 全文索引注解 * * @作者 light-zhang * @时间 2019年3月13日 * @product order * @package cc.zeelan.mall.order.common * @file FruitProvider.java * */ @Target(FIELD) @Retention(RUNTIME) @Documented public @interface Fulltext { /** * 索引名称 * * @return */ String IndexesName() default ""; /** * 对应的列 */ String columnName(); }
4.注解委托实现
import java.lang.reflect.Field; import java.util.Objects; import java.util.Set; import javax.persistence.Query; import javax.persistence.Table; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.Modifying; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import cc.zeelan.mall.common.application.ApplicationUtil; /** * 全文索引注解实现 * * @作者 light-zhang * @时间 2019年3月13日 * @product order * @package cc.zeelan.mall.order.common.util * @file FulltextKeyImpl.java * @DLL */ @Configuration @Transactional public class FulltextDelegator implements CommandLineRunner { @Override public void run(String... args) throws Exception { this.getAnnotation(ScanSupport.classInfos()); } /** * 处理全文注解相关 * * @param clazz */ public void getAnnotation(Set<Class<?>> clazzs) { String indexName = null; for (Class<?> fclass : clazzs) { Field[] fields = fclass.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Fulltext.class)) { Table table = fclass.getAnnotation(Table.class); if (Objects.isNull(table) || StringUtils.isEmpty(table.name())) { Assert.notNull(table, "@table(name='') specified for entity"); } Fulltext fulltextKey = field.getAnnotation(Fulltext.class); if (!Objects.isNull(fulltextKey) && !StringUtils.isEmpty(fulltextKey.IndexesName())) {// 用户自定义的全文索引名称 indexName = fulltextKey.IndexesName(); } else {// 策略创建的索引index名称 indexName = table.name().concat("_").concat(fulltextKey.columnName()).concat("_idx"); } if (isExist(table.name(), indexName)) {// 当前索引不存在在创建索引,存在跳过 createIndex(table.name(), indexName, fulltextKey.columnName()); } } } } ApplicationUtil.closeEntityManager(); } /** * 创建全文索引 */ private void createIndex(String tableName, String indexName, String columnName) { final StringBuffer createIndex = new StringBuffer("CREATE FULLTEXT INDEX "); createIndex.append(indexName); createIndex.append(" ON "); createIndex.append(tableName); createIndex.append("("); createIndex.append(columnName); createIndex.append(")"); createIndex.append(" WITH PARSER ngram");// 采用MYSQ Lngram全文解析器 excute(createIndex.toString());// 在执行创建全文索引操作 createIndex.setLength(0);// 清空 } /** * 删除索引 */ public void deleteIndex(String tableName, String indexName) { final StringBuffer deleteIndex = new StringBuffer("ALTER TABLE "); deleteIndex.append(tableName); deleteIndex.append(" DROP INDEX "); deleteIndex.append(indexName); excute(deleteIndex.toString());// 先执行删除索引操作 deleteIndex.setLength(0);// 清空 } /** * 索引是否存在 * * @return */ private boolean isExist(String tableName, String indexName) { final StringBuffer existIndex = new StringBuffer("SHOW INDEX FROM "); existIndex.append(tableName); existIndex.append(" WHERE key_name LIKE '"); existIndex.append(indexName.concat("%'")); boolean result = excuteQuery(existIndex.toString()); existIndex.setLength(0); return result; } /** * CURD执行器 * * @param sql * @return */ @Modifying(clearAutomatically = true) private int excute(String sql) { Query query = ApplicationUtil.getEntityManager().createNativeQuery(sql); return query.executeUpdate(); } /** * 查询执行器 * * @param sql * @return */ private boolean excuteQuery(String sql) { Query query = ApplicationUtil.getEntityManager().createNativeQuery(sql); return CollectionUtils.isEmpty(query.getResultList()); } }
5.使用方法
1.先在application.properties下面配置:fulltext.scan.package=xxx.xxx.entity 要扫描类的包路径
IndexesName:表示要生成的全文索引名称,如果不赋值的情况下默认的生成策略为:表名_列名_idx || columnName:对应的列名
@Column(name = "order_ids", columnDefinition = "text COLLATE utf8_unicode_ci COMMENT '订单ids' ") @Fulltext(columnName = "order_ids") private String orderIds;