因項目中需要對表的一些列進行全文索引,但是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;