業(yè)務(wù)場(chǎng)景
業(yè)務(wù)場(chǎng)景:首先項(xiàng)目進(jìn)行分布式拆分之后肝断,按照模塊再分為為api層和service層,web層祝蝠。
其中訂單業(yè)務(wù)的實(shí)體類放在com.muses.taoshop.item.entity,而用戶相關(guān)的實(shí)體類放在com.muses.taoshop.user.entity逞敷。所以就這樣,通過通配符方式去setTypeAliasesPackage 灌侣,com.muses.taoshop.*.entity
Ant通配符的3中風(fēng)格:
(1) ?:匹配文件名中的一個(gè)字符 eg: com/test/entity? 匹配 com/test/entityaa
(2) * : 匹配文件名中的任意字符 eg: com//entity 匹配 com/test/entity
(3) ** : 匹配文件名中的多重路徑 eg: com/*/entity 匹配 com/test/test1/entity
mybatis配置類寫在common工程推捐,數(shù)據(jù)庫操作有些是可以共用的,不需要每個(gè)web工程都進(jìn)行重復(fù)配置侧啼。
所以寫了個(gè)Mybatis配置類:
package com.muses.taoshop.common.core.database.config;
public class BaseConfig {
/**
* 設(shè)置主數(shù)據(jù)源名稱
*/
public static final String DATA_SOURCE_NAME = "shop";
/**
* 加載配置文件信息
*/
public static final String DATA_SOURCE_PROPERTIES = "spring.datasource.shop";
/**
* repository 所在包
*/
public static final String REPOSITORY_PACKAGES = "com.muses.taoshop.**.repository";
/**
* mapper 所在包
*/
public static final String MAPPER_PACKAGES = "com.muses.taoshop.**.mapper";
/**
* 實(shí)體類 所在包
*/
public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";
...
}
貼一下配置類代碼牛柒,主要關(guān)注: factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);之前的寫法是這樣的堪簿。ENTITY_PACKAGES是個(gè)常量:public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";
,ps:注意了皮壁,這里用了通配符
package com.muses.taoshop.common.core.database.config;
//省略代碼
@MapperScan(
basePackages = MAPPER_PACKAGES,
annotationClass = MybatisRepository.class,
sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
//省略其它代碼椭更,主要看sqlSessionFactory配置
@Primary
@Bean(name = SQL_SESSION_FACTORY)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{
//SpringBoot默認(rèn)使用DefaultVFS進(jìn)行掃描,但是沒有掃描到j(luò)ar里的實(shí)體類
VFS.addImplClass(SpringBootVFS.class);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try{
factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml"));
//String typeAliasesPackage = packageScanner.getTypeAliasesPackages();
//設(shè)置一下TypeAliasesPackage
factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);
SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
...
}
ps:原先做法:在sqlSessionFactory方法里進(jìn)行TypeAliasesPackage設(shè)置蛾魄,(讓Mybatis能夠掃描到實(shí)體類虑瀑,在xml文件里就不需要寫全實(shí)體類的全包名了。)factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);
但是運(yùn)行之后都會(huì)報(bào)錯(cuò)滴须,提示實(shí)體類不能掃描到舌狗,因?yàn)槲业膕ervice工程里都是這樣寫的,ResultType進(jìn)行用別名ItemCategory扔水,例子:
<!-- 獲取所有的商品品類信息-->
<select id="listCategory" resultType="ItemCategory">
SELECT
<include refid="BaseColumnList" />
FROM item_category t
</select>
源碼簡(jiǎn)單分析
針對(duì)上面的業(yè)務(wù)場(chǎng)景痛侍,首先的分析一下,我們知道Mybatis的執(zhí)行都會(huì)通過SQLSessionFactory去調(diào)用魔市,調(diào)用前都是先用SqlSessionFactoryBean的setTypeAliasesPackage可以看一下SqlSessionFactoryBean的源碼:
/**
* Packages to search for type aliases.
*
* @since 1.0.1
*
* @param typeAliasesPackage package to scan for domain objects
*
*/
public void setTypeAliasesPackage(String typeAliasesPackage) {
this
我們看一下SqlSessionFactoryBean的初步Build方法:
/**
* Build a {@code SqlSessionFactory} instance.
*
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader.
* Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
//創(chuàng)建一個(gè)XMLConfigBuilder讀取配置文件的一些信息
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);//添加Properties屬性
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
/*
重點(diǎn)看這里主届,其它源碼先不看,這里獲取到typeAliasesPackage字符串之后待德,調(diào)用tokenizeToStringArray進(jìn)行字符串分隔返回一個(gè)數(shù)組君丁,`String CONFIG_LOCATION_DELIMITERS = ",; \t\n";`
*/
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {//遍歷,注冊(cè)到configuration對(duì)象上
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
...
//省略其它代碼
}
然后可以看到注冊(cè)所有別名的方法 磅网,registerAliases是怎么做的谈截?
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
要掃描注冊(cè)所有的別名之前先要掃描包下面的所有類:
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
//通過ResolverUtil獲取到的所有類都賦值給一個(gè)集合
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
/*遍歷集合,然后一個(gè)個(gè)注冊(cè)*/
Iterator var5 = typeSet.iterator();
while(var5.hasNext()) {
Class<?> type = (Class)var5.next();
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
this.registerAlias(type);
}
}
}
ResolverUtil是怎么通過packageName去查找的呢涧偷,可以再繼續(xù)跟一下
public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
//獲取包路徑
String path = this.getPackagePath(packageName);
try {
//VFS類用單例模式實(shí)現(xiàn),先獲取集合
List<String> children = VFS.getInstance().list(path);
Iterator var5 = children.iterator();
//遍歷,.class文件的選出來
while(var5.hasNext()) {
String child = (String)var5.next();
if (child.endsWith(".class")) {
this.addIfMatching(test, child);
}
}
} catch (IOException var7) {
log.error("Could not read package: " + packageName, var7);
}
return this;
}
獲取packPath只是獲取一下相對(duì)路徑
protected String getPackagePath(String packageName) {
return packageName == null ? null : packageName.replace('.', '/');
}
校驗(yàn)方法addIfMatching:
protected void addIfMatching(ResolverUtil.Test test, String fqn) {
try {
String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.');
ClassLoader loader = this.getClassLoader();//類加載器
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
Class<?> type = loader.loadClass(externalName);//通過類加載器加載類
if (test.matches(type)) {//校驗(yàn)是否符合
this.matches.add(type);
}
} catch (Throwable var6) {
log.warn("Could not examine class '" + fqn + "' due to a " + var6.getClass().getName() + " with message: " + var6.getMessage());
}
}
VFS類具體是怎么setInstance的毙死?繼續(xù)跟:
//這里的關(guān)鍵點(diǎn)就是getResources燎潮,Thread.currentThread().getContextClassLoader().getResources(),其實(shí)總結(jié)一下Mybatis的類掃描還是要依賴與jdk提供的類加載器
protected static List<URL> getResources(String path) throws IOException {
//獲取到資源路徑以列表形式放在集合里
return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
}
...
public List<String> list(String path) throws IOException {
List<String> names = new ArrayList();
Iterator var3 = getResources(path).iterator();
//遍歷封裝成列表
while(var3.hasNext()) {
URL url = (URL)var3.next();
names.addAll(this.list(url, path));
}
return names;
}
ok扼倘,本博客只是稍微跟一下源碼确封,并沒有對(duì)Mybatis的源碼進(jìn)行比較系統(tǒng)更高層次的分析。
跟了一下源碼知道再菊,稍微總結(jié)一下Mybatis對(duì)別名的注冊(cè)是先將從sqlSessionFactoryBean類set的別名報(bào)名進(jìn)行tokenizeToStringArray拆分成數(shù)組爪喘,然后將包名數(shù)組丟給ResolverUtil類和VFS等類進(jìn)行一系列類加載遍歷,之后將 resolverUtil.getClasses()獲取的類都賦值給Set<Class<? extends Class<?>>> typeSet 一個(gè)集合纠拔。其中也是依賴與類加載器秉剑。
支持Ant通配符方式setTypeAliasesPackage解決方案
從這個(gè)源碼比較簡(jiǎn)單的分析過程,我們并沒有找到支持所謂通配符的方法稠诲,通過類加載的話也是要傳個(gè)相對(duì)路徑去遍歷侦鹏,不過我上面描述的業(yè)務(wù)場(chǎng)景是要兼容通配符的情況的诡曙,一般不會(huì)去改包名,假如這個(gè)項(xiàng)目有一定規(guī)模的話略水。
下面給出我的解決方案:
package com.muses.taoshop.common.core.database.annotation;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;
import static com.muses.taoshop.common.core.database.config.BaseConfig.ENTITY_PACKAGES;
public class AnnotationConstants {
public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
public final static String PACKAGE_PATTERN =
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(ENTITY_PACKAGES)
+ DEFAULT_RESOURCE_PATTERN;
}
寫一個(gè)掃描類价卤,代碼參考Hibernate的AnnotationSessionFactoryBean源碼
package com.muses.taoshop.common.core.database.annotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
import java.util.*;
import org.springframework.core.io.Resource;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.util.StringUtils;
import static com.muses.taoshop.common.core.database.annotation.AnnotationConstants.PACKAGE_PATTERN;
/**
* <pre>
* TypeAlicsesPackage的掃描類
* </pre>
*
* @author nicky
* @version 1.00.00
* <pre>
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2018.12.01 18:23 修改內(nèi)容:
* </pre>
*/
@Component
public class TypeAliasesPackageScanner {
protected final static Logger LOGGER = LoggerFactory.getLogger(TypeAliasesPackageScanner.class);
private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
/* private TypeFilter[] entityTypeFilters = new TypeFilter[]{new AnnotationTypeFilter(Entity.class, false),
new AnnotationTypeFilter(Embeddable.class, false),
new AnnotationTypeFilter(MappedSuperclass.class, false),
new AnnotationTypeFilter(org.hibernate.annotations.Entity.class, false)};*/
public static String getTypeAliasesPackages() {
Set<String> packageNames = new TreeSet<String>();
//TreeSet packageNames = new TreeSet();
String typeAliasesPackage ="";
try {
//加載所有的資源
Resource[] resources = resourcePatternResolver.getResources(PACKAGE_PATTERN);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
//遍歷資源
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
//eg:com.muses.taoshop.item.entity.ItemBrand
LOGGER.info("className : {} "+className);
try{
//eg:com.muses.taoshop.item.entity
LOGGER.info("packageName : {} "+Class.forName(className).getPackage().getName());
packageNames.add(Class.forName(className).getPackage().getName());
}catch (ClassNotFoundException e){
LOGGER.error("classNotFoundException : {} "+e);
}
}
}
} catch (IOException e) {
LOGGER.error("ioException =>: {} " + e);
}
//集合不為空的情況,拼裝一下數(shù)據(jù)
if (!CollectionUtils.isEmpty(packageNames)) {
typeAliasesPackage = StringUtils.join(packageNames.toArray() , ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
}else{
LOGGER.info("set empty,size:{} "+packageNames.size());
}
return typeAliasesPackage;
}
}
然后剛才的Mybatis配置類改一下渊涝,主要改 String typeAliasesPackage = packageScanner.getTypeAliasesPackages();通過掃描類去獲取一下慎璧,重點(diǎn)代碼在TypeAliasesPackageScanner 掃描類
package com.muses.taoshop.common.core.database.config;
//省略代碼
@MapperScan(
basePackages = MAPPER_PACKAGES,
annotationClass = MybatisRepository.class,
sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
//省略其它代碼,主要看sqlSessionFactory配置
@Primary
@Bean(name = SQL_SESSION_FACTORY)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{
//SpringBoot默認(rèn)使用DefaultVFS進(jìn)行掃描跨释,但是沒有掃描到j(luò)ar里的實(shí)體類
VFS.addImplClass(SpringBootVFS.class);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try{
factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml"));
String typeAliasesPackage = packageScanner.getTypeAliasesPackages();
//設(shè)置一下TypeAliasesPackage
factoryBean.setTypeAliasesPackage(typeAliasesPackage);
SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}