Recently, during project merging, projects (sub-projects) are separated from IDM projects (parent projects). Independent development is considered, but too many projects depend on IDM, so now we need to merge ....
The sub-project has an SaaS module, which is used to access different databases based on different domain names. It mainly uses the domain name interceptor and spring data source switching (abstractroutingdatasource ).
1: databases are divided into sub-databases and master databases. Sub-databases store business data and management sub-database information.
Mainly two tables
Database connection information
The correspondence between the domain name and the database connection information. Here, dbconnid corresponds to the ID of the rising table.
In this way, messages from multiple databases are stored in the master database.
2 domain name interceptor
The common interceptor intercepts domain names of level 2 for all requests and saves them to a basic class threadlocalutil.
For example: http://xl.test.com/test here to obtain the XL Level 2 domain name, save to the basic class threadlocalutil, convenient to connect to the database according to this domain name.
@ Override
Public void dofilter (servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception {
Try {
String servername = request. getservername ();
String sec = "";
If (iputil. isip (servername) {// here, if the IP address is the default master database
SEC = iconstant. WWW;
} Else {
SEC = domainutil. getsecond (servername );
}
List <string> alldomain = domainutil. getalldomain ();
If (alldomain. Contains (SEC )){
Threadlocalutil. setsecdomain (SEC );
If (threadlocalutil. isdefsecdomain ()){
Log. debug ("default domain name {}, url = {}", SEC, servername );
}
Chain. dofilter (request, response );
} Else {
Httpservletrequest Req = (httpservletrequest) request;
Httpservletresponse resp = (httpservletresponse) response;
String requri = Req. getrequesturi ();
For (string exclude: Excludes ){
If (requri. Contains (exclude )){
Chain. dofilter (request, response );
Return;
}
}
Resp. setstatus (httpservletresponse. SC _not_found );
Resp. sendredirect (req. getcontextpath () + "/error/404.jsp ");
}
} Finally {
Threadlocalutil. delsecdomain ();
}
}
3. Initialization Method
Here is an initialization method, which obtains all the SAAs data of the database and adds it to multidatasource. The map, key domain name, and value database information passed in here is adddatasource.
4: The Key multidatasource. This class inherits the abstractroutingdatasource.
The abstractroutingdatasource class is also the key to data source switching.
This class is connected to the data source based on this method, and the key is the lookupkey object, that is, the determinecurrentlookupkey method. This method of this class is abstract, and the specific implementation is determined by the subclass; next, we will refer
The map of resolveddatasources is obtained here. Of course, the map method of set is provided here, but it is not direct or indirect.
Source:
Map variables in the class
Private Map <object, Object> targetdatasources;
Private Map <object, datasource> resolveddatasources;
How to connect to a data source
Protected datasource determinetargetdatasource (){
Assert. notnull (this. resolveddatasources, "datasource router not initialized ");
Object lookupkey = determinecurrentlookupkey ();
Datasource = This. resolveddatasources. Get (lookupkey );
If (datasource = NULL & (this. lenientfallback | lookupkey = NULL )){
Datasource = This. resolveddefadatasource datasource;
}
If (datasource = NULL ){
Throw new illegalstateexception ("cannot determine target datasource for lookup key [" + lookupkey + "]");
}
Return datasource;
}
Abstract Method
Protected abstract object determinecurrentlookupkey ();
The Set resolveddatasources method uses the targetdatasources map. Here is the method to set targetdatasources directly.
@ Override
Public void afterpropertiesset (){
If (this.tar getdatasources = NULL ){
Throw new illegalargumentexception ("Property 'targetdatasources 'is required ");
}
This. resolveddatasources = new hashmap <object, datasource> (this.tar getdatasources. Size ());
For (Map. Entry <object, Object> entry: this.tar getdatasources. entryset ()){
Object lookupkey = resolvespecifiedlookupkey (entry. getkey ());
Datasource = resolvespecifieddatasource (entry. getvalue ());
This. resolveddatasources. Put (lookupkey, datasource );
}
If (this. defaulttargetdatasource! = NULL ){
This. resolveddefadatasource datasource = resolvespecifieddatasource (this. defaulttargetdatasource );
}
}
Set targetdatasources Method
Public void settargetdatasources (Map <object, Object> targetdatasources ){
This.tar getdatasources = targetdatasources;
}
Here we implement this.
Here, the domain name stored by the domain name interceptor is used to determine which database to connect.
@ Override
Protected object determinecurrentlookupkey (){
String secdomain = threadlocalutil. getsecdomain (); Save it here.
If (! Iconstant. www. Equals (secdomain )){
Return secdomain;
} Else {
Log. debug ("default domain name {}", secdomain );
}
Return def;
}
We must have called its set method here.
Public void settargetdatasources (Map <object, Object> targetdatasources ){
This.tar getdatasources = targetdatasources;
Super. settargetdatasources (targetdatasources); call the Set Method of the parent class.
Tdsversion ++;
}
Method called during initialization
/**
* Multiple database connection pools are added.
*
* @ Param Conns
*/
Public void adddatasource (Map <string, dbconninfo> Conns ){
If (null! = Conns & Conns. Size ()> 0 ){
Linkedhashmap <object, Object> newtargetdatasources = new linkedhashmap <> (targetdatasources );
Set <string> keys = Conns. keyset ();
For (string key: Keys ){
Dbconninfo conn = Conns. Get (key );
Datasource DS = builddatasource (conn );
Newtargetdatasources. Put (Key, DS );
Domaindbconnmap. Put (Key, Conn );
}
Settargetdatasources (newtargetdatasources); // here, the key is the map of the domain name value as the database connection information, set to targetdatasources.
Afterpropertiesset (); // This method needs to be called after the connection information is changed.
}
}
In this way, the database is switched based on the domain name.
0 system initialization --- 1 user request -- 2 domain name interceptor -- 3 Switch data source.
The write is a bit messy ..
For details about how to switch the data source, refer to this address carefully.
53965552
Data Source Switching Based on different domain names