MyBatis a sqlsessionfactory represents a data source, configuring multiple data sources requires just injecting multiple sqlsessionfactory.
The first thing to note is that mybatis-spring-1.1.0 seems unable to configure multiple data sources (this is not the case, you should not be able to use ${in the configuration data source.) PLACEHOLDER), it's probably been a whole day of my time. Later I thought it might be a version issue, so the latest 1.2.2, the problem is solved.
The following is a record of the process of analyzing this problem:
First I configured the Org.springframework.beans.factory.config.PropertyPlaceholderConfigurer in the spring configuration file, which is a beanfactorypostproc in the bean. Essor will run after all beandefinition are loaded, and then use the specified properties file to replace the ${defined in the beandefinition ...} Placeholder, spring's configuration file is as follows:
<?xml version= "1.0" encoding= "UTF-8"? ><beans xmlns= "Http://www.springframework.org/schema/beans" xmlns: Xsi= "Http://www.w3.org/2001/XMLSchema-instance" xmlns:p= "http://www.springframework.org/schema/p" xsi: schemalocation= "Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/ Spring-beans-3.0.xsd "><bean id=" Pmsdatasource "class=" Org.apache.ibatis.datasource.pooled.PooledDataSource "><property name=" Driver "value=" ${pms.driver} "/><property name=" url "value=" ${pms.url} "/>< Property name= "username" value= "${pms.username}"/><property name= "password" value= "${pms.password}"/>< /bean><bean id= "Pmssqlsessionfactory" class= "Org.mybatis.spring.SqlSessionFactoryBean" ><property name = "DataSource" ref= "Pmsdatasource"/></bean><bean id= "Pmsmapperconfigurer" class= " Org.mybatis.spring.mapper.MapperScannerConfigurer "><property name=" Basepackage "value=" Com.westsoft.pms.dao "/><property name="Sqlsessionfactorybeanname "value=" pmssqlsessionfactory "></property></bean><bean id=" Kftdatasource "class=" Org.apache.ibatis.datasource.pooled.PooledDataSource "><property name=" Driver "value=" ${kft.driver} "/><property name=" url "value=" ${kft.url} "/><property name=" username "value=" ${ Kft.username} "/><property name=" password "value=" ${kft.password} "/></bean><bean id=" Kftsqlsessionfactory "class=" Org.mybatis.spring.SqlSessionFactoryBean "><property name=" DataSource "ref=" Kftdatasource "/></bean><bean id=" Kftmapperconfigurer "class=" Org.mybatis.spring.mapper.MapperScannerConfigurer "><property name=" Basepackage "value=" Com.westsoft.kft.dao "/><property name=" Sqlsessionfactorybeanname "value=" Kftsqlsessionfactory "></ Property></bean><bean id= "Propertyplaceholderconfigurer" class= " Org.springframework.beans.factory.config.PropertyPlaceholderConfigurer "><property name=" Locations "> <lIst><value>classpath:/meta-inf/config/kft-ds.properties</value><value>classpath:/meta-inf /config/pms-ds.properties</value></list></property></bean></beans>
But when the container is started, the error is: Cannot find class: ${kft.driver}
Because I remember a few months ago also got this, but did not succeed, then I changed to not read the properties file, but directly written in the XML, is a kind of escape it
But this time, I'm not going to let it go. Asked the degree Niang, above the overwhelming is said not to inject Mapperscannerconfigurer sqlsessionfactory attribute, but should use Sqlsessionfactorybeanname. But the problem is that I've used sqlsessionfactorybeanname here, which is obviously not the problem. But the degree Niang on only such an answer, is a little pit ah ~ No one used 1.0.0 version? Well, then I'm the first person, and if someone comes across something like that, it doesn't have to be that long.
Although the Niang did not give a solution to the answer, but the analysis is reasonable: said is because the Mapperscannerconfigurer initialization and also initialize the Sqlsessionfactorybean, And since Mapperscannerconfigurer is initialized prior to Propertyplaceholderconfigurer, the placeholder for the configuration in datasource cannot be replaced, so the above error is caused.
According to the above ideas, tracking Mapperscannerconfigurer source to see:
public void Postprocessbeandefinitionregistry (Beandefinitionregistry beandefinitionregistry) throws BeansException { processpropertyplaceholders (); Scanner Scanner = new Scanner (beandefinitionregistry); Scanner.setresourceloader (this.applicationcontext); Scanner.scan (Stringutils.tokenizetostringarray (This.basepackage, configurableapplicationcontext.config_location _delimiters)); }
Discover that Mapperscannerconfigurer implements the Beandefinitionregistrypostprocessor interface, which will be loaded after beandefinition Beanpostprocessor before initialization. And when initialized, the Postprocessbeandefinitionregistry (Beandefinitionregistry beandefinitionregistry) method is executed.
One of the next few lines we see, is automatic scanning mapper. And the first line executes the Processpropertyplaceholders () method, looks at the code comment, This method is designed to prevent Mapperscannerconfigurer from being initialized prior to Propertyplaceholderconfigurer and cannot be converted to ${. Of But why doesn't it work? Let's take a look at the source code:
private void Processpropertyplaceholders () {map<string, propertyresourceconfigurer> PRCs = ApplicationContext. Getbeansoftype (Propertyresourceconfigurer.class); if (!prcs.isempty () && applicationcontext instanceof genericapplicationcontext) {beandefinition MapperScanne Rbean = ((Genericapplicationcontext) applicationcontext). Getbeanfactory (). Getbeandefinition (BeanName); Propertyresourceconfigurer does not expose any methods to explicitly perform//property placeholder substitution. Instead, create a beanfactory that just//contains this mapper scanner and post process the factory. Defaultlistablebeanfactory factory = new Defaultlistablebeanfactory (); Factory.registerbeandefinition (Beanname, Mapperscannerbean); For (Propertyresourceconfigurer prc:prcs.values ()) {prc.postprocessbeanfactory (factory); } propertyvalues values = Mapperscannerbean.getpropertyvalues (); This.basepackage = UpdatepropErtyvalue ("Basepackage", values); This.sqlsessionfactorybeanname = Updatepropertyvalue ("Sqlsessionfactorybeanname", values); This.sqlsessiontemplatebeanname = Updatepropertyvalue ("Sqlsessiontemplatebeanname", values); } }
First of all, in terms of name or meaning, it is not difficult for us to think that he is the Propetyresourceconfigurer class used to get all the definitions.
Secondly, have you noticed the above conditions? The need for ApplicationContext here is genericapplicationcontext to work, and our ApplicationContext implementation class here is Xmlwebapplicationcontext, So this step is completely ineffective (no, it should be said that there are side effects, because the above error is related to this method) can find this method is actually handed to Defaultlistablebeanfactory Getbeansoftype (...) To achieve:
Public <T> map<string, t> getbeansoftype (class<t> type, Boolean includenonsingletons, Boolean Alloweagerinit) throws Beansexception {string[] beannames = Getbeannamesfortype (type, includenonsingletons, Alloweagerinit); map<string, t> result = new linkedhashmap<string, t> (beannames.length); for (String Beanname:beannames) {try {Result.put (Beanname, Getbean (Beanname, type));} catch (Beancreationexception ex) {throwable rootcause = Ex.getmostspecificcause (); if (Rootcause instanceof beancurrentlyincreationexception) {beancreationexception BCE = (beancreationexception) rootcause;if ( Iscurrentlyincreation (Bce.getbeanname ())) {if (this.logger.isDebugEnabled ()) {This.logger.debug ("ignoring match to Currently created Bean ' "+ beanname +" ': "+ex.getmessage ());} Onsuppressedexception (ex);//ignore:indicates a circular reference when autowiring constructors.//We want to find Matche s other than the currently created Bean Itself.continue;}} Throw ex;}} return result;}
Here first to find the corresponding type of beanname, continue to trace getbeannamefortype (...) :
Public string[] Getbeannamesfortype (class<?> type, Boolean includenonsingletons, Boolean alloweagerinit) {if ( Type = = NULL | | !alloweagerinit) {return This.dogetbeannamesfortype (type, includenonsingletons, alloweagerinit);} Map<class<?> string[]> cache = includenonsingletons? This.nonsingletonbeannamesbytype: This.singletonbeannamesbytype; string[] Resolvedbeannames = Cache.get (type); if (resolvedbeannames! = null) {return resolvedbeannames;} Resolvedbeannames = This.dogetbeannamesfortype (Type, includenonsingletons, alloweagerinit); Cache.put (Type, Resolvedbeannames); return resolvedbeannames;}
The latter two Boolean arguments are true here, and the bean is not in the cache and continues to trace Dogetbeannamesfortype (...).
Private string[] Dogetbeannamesfortype (class<?> type, Boolean includenonsingletons, Boolean alloweagerinit) { list<string> result = new arraylist<string> ();//Check All bean definitions. string[] Beandefinitionnames = Getbeandefinitionnames (); for (String beanname:beandefinitionnames) {//Only consider BEA N as eligible if the Bean name//is isn't defined as alias for some other Bean.if (!isalias (beanname)) {try {Rootbeandefinit Ion mbd = Getmergedlocalbeandefinition (beanname);//Only check beans definition if it is Complete.if (!mbd.isabstract () &am p;& (Alloweagerinit | | ((Mbd.hasbeanclass () | |!mbd.islazyinit () | | this.alloweagerclassloading)) &&!requireseagerinitfortype ( Mbd.getfactorybeanname ()))) {//In case of Factorybean, match object created by Factorybean.boolean Isfactorybean = Isfact Orybean (Beanname, mbd); Boolean matchfound = (Alloweagerinit | |!isfactorybean | | containssingleton (beanname)) & & (Includenonsingletons | | Issingleton (beanname)) && Istypematch (Beanname, type), if (!matchfound && Isfactorybean) {//In case of Factorybean, try to match facto Rybean instance itself Next.beanname = Factory_bean_prefix + beanname;matchfound = (Includenonsingletons | | mbd.isSinglet On ()) && Istypematch (beanname, type);} if (matchfound) {result.add (beanname);}}} catch (Cannotloadbeanclassexception ex) {if (alloweagerinit) {throw ex;} Probably contains a placeholder:let ' s ignore it for type matching Purposes.if (this.logger.isDebugEnabled ()) {This.log Ger.debug ("Ignoring bean class loading failure for Bean '" + beanname + "'", Ex);} Onsuppressedexception (ex);} catch (Beandefinitionstoreexception ex) {if (alloweagerinit) {throw ex;} Probably contains a placeholder:let ' s ignore it for type matching Purposes.if (this.logger.isDebugEnabled ()) {This.log Ger.debug ("Ignoring unresolvable metadata in bean definition '" + beanname + "'", Ex);} Onsuppressedexception (ex);}} Check singletons too, to catch manually registered sinGletons. string[] Singletonnames = Getsingletonnames (); for (String beanname:singletonnames) {//Only check if manually registered . if (!containsbeandefinition (beanname)) {//In case of Factorybean, the match object created by Factorybean.if (Isfactorybean ( Beanname) {if ((Includenonsingletons | | Issingleton (beanname)) && Istypematch (Beanname, type)) {Result.add ( Beanname);//match found for this bean:do not match Factorybean itself anymore.continue;} In case of Factorybean, try to match Factorybean itself next.beanname = Factory_bean_prefix + beanname;} Match Raw Bean instance (might be raw factorybean). if (Istypematch (Beanname, type)) {Result.add (beanname);}}} return Stringutils.tostringarray (result);}
You can see that you want to get the class of the specified type, in fact, by traversing all the beandefinition. Which determines whether the type is consistent with Istypematch (...)
public boolean Istypematch (string name, Class<?> targetType) throws Nosuchbeandefinitionexception {string Beanname = transformedbeanname (name); Class<?> Typetomatch = (TargetType! = null? targetType:Object.class);//Check manually registered singletons. Object beaninstance = Getsingleton (Beanname, False), if (beaninstance! = null) {<pre name= "code" class= "Java" >< Span style= "font-family:arial, Helvetica, Sans-serif;" >//omit part of code .....</span>
else {
<span style= "font-family:arial, Helvetica, Sans-serif;" >//omit part of code .....</span>
Check Bean class Whether we ' re dealing with a factorybean.if (FactoryBean.class.isAssignableFrom (Beanclass)) {if (! Beanfactoryutils.isfactorydereference (name)) {//If it ' s a factorybean, we want to look at what it creates, not the factor Y class. class<?> type = Gettypeforfactorybean (Beanname, mbd); return (type! = NULL && typetomatch.isassignablefrom (type));} else {return typetomatch.isassignablefrom (Beanclass);}} else {return! Beanfactoryutils.isfactorydereference (name) &&typetomatch.isassignablefrom (BeanClass);}}
In my simulation, I found that when the Mapper class comes in, it causes sqlsessionfactory initialization, which is why?
Since mapper is represented by MyBatis, Mapper is defined in the following form:
<bean id= "Usermapper" class= "Org.mybatis.spring.mapper.MapperFactoryBean" > <property name= " Mapperinterface "value=" Org.mybatis.spring.sample.mapper.UserMapper "/> <property name=" Sqlsessionfactory "ref=" Sqlsessionfactory "/></bean>
So when you want to get the type of usermapper, because you know it is factorybean, and not dereference (not the type of factorybean that starts with ' & '), Execute Gettypeforfactorybean ( Beanname, mbd) This method:
Protected class<?> Gettypeforfactorybean (String beanname, rootbeandefinition mbd) {if (!mbd.issingleton ()) { return null;} Try {factorybean<?> Factorybean = Dogetbean (Factory_bean_prefix + beanname, Factorybean.class, NULL, true); return Gettypeforfactorybean (Factorybean);} catch (Beancreationexception ex) {//Can only happen when getting a factorybean.if (logger.isdebugenabled ()) {Logger.debug ("Ignoring bean creation exception on Factorybean type check:" + ex);} Onsuppressedexception (ex); return null;}}
Follow up on Dogetbean (...) Method View:
Protected <T> T Dogetbean (final String name, Final class<t> requiredtype, final object[] args, Boolean typeche Ckonly) throws Beansexception {<pre name= "code" class= "java" ><span style= "font-family:arial, Helvetica, Sans-serif; " >//omit part of code .....</span>
}else { Omit part of the code ...//Create Bean Instance.if (Mbd.issingleton ()) {///finally found, here is going to create sharedinstance = Getsingleton (Beanname, New Objectfactory<object> () {public Object getObject () throws Beansexception {try {return Createbean (Beanname, MBD , args);} catch (Beansexception ex) {//explicitly remove instance from Singleton Cache:it might has been put there//eagerly by T He creation process, to-allow-circular reference resolution.//Also Remove any beans that received a temporary referen Ce to the Bean.destroysingleton (beanname); throw ex;}}); Bean = getobjectforbeaninstance (sharedinstance, name, Beanname, mbd);}//Omit part of the code .....}
<span style= "font-family:arial, Helvetica, Sans-serif;" >//omit part of code .....</span>
Return (T) bean;
So the key reason is because to get the type of Factorybean (which is actually the type of object that was created), we first have to initialize it and then use the Getobjecttype method to get its type. So it caused the sqlsessionfactory early initialization! In addition, the analysis process should be backward rather than forward.
And looked at the source of the next mybatis-spring-1.2.2, found that mapperscannerconfigurer initialization of the Processpropertyplaceholders () method plus a condition. In other words, default is not performed:
public void Postprocessbeandefinitionregistry (Beandefinitionregistry registry) throws Beansexception {if (this.proces Spropertyplaceholders) {processpropertyplaceholders (); } Classpathmapperscanner scanner = new Classpathmapperscanner (registry); Scanner.setaddtoconfig (This.addtoconfig); Scanner.setannotationclass (This.annotationclass); Scanner.setmarkerinterface (This.markerinterface); Scanner.setsqlsessionfactory (this.sqlsessionfactory); Scanner.setsqlsessiontemplate (this.sqlsessiontemplate); Scanner.setsqlsessionfactorybeanname (This.sqlsessionfactorybeanname); Scanner.setsqlsessiontemplatebeanname (This.sqlsessiontemplatebeanname); Scanner.setresourceloader (This.applicationcontext); Scanner.setbeannamegenerator (This.namegenerator); Scanner.registerfilters (); Scanner.scan (Stringutils.tokenizetostringarray (This.basepackage, configurableapplicationcontext.config_location _delimiters)); }
And as you can see, it provides more configuration items for automatic discovery of mapper.
Cannot find class: ${jdbc.driver}--configuration of sqlsessionfactorybeanname also error analysis