Read and write separation if the framework is nothing more than the implementation of multiple data sources, the main library with a written data source, from the library with a read data source.
Because I want to study the database reading and writing separation and the design of the Sub-Library table, so I set up a SPRINGBOOT+DRUID+MYBATIS+AOP to achieve a master multi-slave design.
First step: First, you need to customize the data source configuration items, springboot default parsing is prefixed with Spring.datasource. The following configuration items, in order to not conflict, define the DataSource directly. As our prefix,
@ConfigurationProperties (prefix = "datasource.write") can be used to load configuration items with the specified prefix, which is very convenient
Because you use Druid, you need to specify a type when you need to generate datasource.
Datasourcebuilder.create (). Type (DatasourceType). Build ()
ReadSize is used to define the size from the library, how many from the library will be configured from the library datasource
The second step: Load balancing from the library, mainly Myabstractroutingdatasource this class
The third step is to create Sqlsessionfactory method from the Mybatisautoconfiguration class that writes the Springboot-mybatis rack package, Replace the data source with our custom Abstractroutingdatasource
Step four. Custom Transaction Mydatasourcetransactionmanagerautoconfiguration
Complete code and Unit tests:
Github:https://github.com/ggj2010/javabase.git
The main rack Package
<!--JDBC driver begin-->
<dependency>
<groupId>com.alibaba</groupId>
< artifactid>druid</artifactid>
</dependency>
<dependency>
<groupid>mysql </groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</ scope>
</dependency>
<!--mybatis springboot-->
<dependency>
<groupid >org.mybatis.spring.boot</groupId>
<artifactid>mybatis-spring-boot-starter</artifactid >
</dependency>
<!--jdbc driver end-->
<dependency>
<groupId> org.aspectj</groupid>
<artifactId>aspectjweaver</artifactId>
</dependency>
Custom data Source Configuration items:
#多数据源 1 main 2 from DataSource: #从库数量 readsize:2 # using Druid data source Type:com.alibaba.druid.pool.DruidDataSource #主库 write: Url:jdbc:mysql://localhost:3306/master?useunicode=true&characterencoding=utf-8 Username:root Password:roo
T driver-class-name:com.mysql.jdbc.driver filters:stat maxactive:20 initialsize:1 maxwait:60000 Minidle:1 timebetweenevictionrunsmillis:60000 minevictableidletimemillis:300000 validationQueryTimeout: 900000 validationquery:select sysdate () from dual testwhileidle:true testonborrow:false testonreturn:f Alse poolpreparedstatements:true maxopenpreparedstatements:20 Read1:url:jdbc:mysql://localhost:3306/slav E1?useunicode=true&characterencoding=utf-8 username:root Password:root Driver-class-name:com.mysql.jdbc . Driver filters:stat maxactive:20 initialsize:1 maxwait:60000 minidle:1 timebetweenevictionrun smillis:60000 MinEvictableidletimemillis:300000 validationquerytimeout:900000 validationquery:select SYSDATE () from dual test Whileidle:true testonborrow:false testonreturn:false poolpreparedstatements:true MaxOpenPreparedStatem Ents:20 Read2:url:jdbc:mysql://localhost:3306/slave2?useunicode=true&characterencoding=utf-8 Username:ro
OT password:root driver-class-name:com.mysql.jdbc.driver filters:stat maxactive:20 initialsize:1 maxwait:60000 minidle:1 timebetweenevictionrunsmillis:60000 minevictableidletimemillis:300000 Vali
dationquerytimeout:900000 Validationquery:select sysdate () from dual testwhileidle:true Testonborrow:false Testonreturn:false poolpreparedstatements:true maxopenpreparedstatements:20
Resolving configuration items:
@Configuration @Slf4j public class Datasourceconfiguration {@Value ("${datasource.type}") private class<? exte
NDS datasource> DatasourceType; @Bean (name = "Writedatasource") @Primary @ConfigurationProperties (prefix = "datasource.write") public datasour
Ce Writedatasource () {log.info ("--------------------writedatasource init---------------------");
Return Datasourcebuilder.create (). Type (DatasourceType). build (); }/** * How many * @return */@Bean (name = "ReadDataSource1") to configure from the library @ConfigurationProperties (pr Efix = "Datasource.read1") public datasource Readdatasourceone () {log.info ("--------------------readdatasour
Ceone init---------------------");
Return Datasourcebuilder.create (). Type (DatasourceType). build (); } @Bean (name = "ReadDataSource2") @ConfigurationProperties (prefix = "datasource.read2") public datasource Rea Ddatasourcetwo () {Log.info ("--------------------readdatasourcetwo init---------------------");
Return Datasourcebuilder.create (). Type (DatasourceType). build (); }
}
Overriding sqlsessionfactory
@Configuration @AutoConfigureAfter ({datasourceconfiguration.class}) @Slf4j public class Mybatisconfiguration extends
mybatisautoconfiguration {@Value ("${datasource.readsize}") Private String datasourcesize; @Bean public Sqlsessionfactory Sqlsessionfactorys () throws Exception {Log.info ("--------------------overloads the parent class SQL
Sessionfactory init---------------------");
Return Super.sqlsessionfactory (Roundrobindatasouceproxy ()); }/** * How many data sources will be configured to configure how many beans * @return */@Bean public Abstractroutingdatasource Roundrobindatas
Ouceproxy () {int size = Integer.parseint (datasourcesize);
Myabstractroutingdatasource proxy = new Myabstractroutingdatasource (size);
Map<object, object> targetdatasources = new Hashmap<object, object> ();
DataSource Writedatasource = Springcontextholder.getbean ("Writedatasource"); Write Targetdatasources.put (DataSourceType.write.getType (), SpringcontexthoLder.getbean ("Writedatasource")); for (int i = 0; i < size; i++) {Targetdatasources.put (I, Springcontextholder.getbean ("Readdatasource" + (i
+ 1));
} proxy.setdefaulttargetdatasource (Writedatasource);
Proxy.settargetdatasources (targetdatasources);
return proxy; }
}
Local Thread Global variables
public class Datasourcecontextholder {
private static final threadlocal<string> local = new threadlocal< String> ();
public static threadlocal<string> getlocal () {
return local;
}
/**
* Read may be multiple libraries
*
/public static void Read () {
local.set (DataSourceType.read.getType ());
}
/**
* Write only one library */public
static void Write () {
local.set (DataSourceType.write.getType ());
}
public static String Getjdbctype () {
return local.get ();
}
}
Multi-data source switching
public class Myabstractroutingdatasource extends Abstractroutingdatasource {
private final int datasourcenumber;
Private Atomicinteger count = new Atomicinteger (0);
Public myabstractroutingdatasource (int datasourcenumber) {
this.datasourcenumber = datasourcenumber;
}
@Override
protected Object Determinecurrentlookupkey () {
String Typekey = Datasourcecontextholder.getjdbctype ();
if (Typekey.equals (DataSourceType.write.getType ()))
return DataSourceType.write.getType ();
Read simple load balancer
int number = Count.getandadd (1);
int lookupkey = number% Datasourcenumber;
return new Integer (LookupKey);
}
}
Enum type
public enum DatasourceType {
read ("read", "from Library"), Write ("write", "Main Library");
@Getter
private String type;
@Getter
private String name;
DatasourceType (String type, string name) {
this.type = type;
this.name = name;
}
}
AOP interception set Local thread variables
@Aspect
@Component
@Slf4j public
class Datasourceaop {
@Before ("Execution (* Com.ggj.encrypt.modules.*.dao. *.find* (..)) or Execution (* Com.ggj.encrypt.modules.*.dao). *.get* (..)) ")
public void Setreaddatasourcetype () {
datasourcecontextholder.read ();
Log.info ("DataSource Switch to: Read");
}
@Before ("Execution (* Com.ggj.encrypt.modules.*.dao). *.insert* (..)) or Execution (* Com.ggj.encrypt.modules.*.dao). *.update* (..)) ")
public void Setwritedatasourcetype () {
datasourcecontextholder.write ();
Log.info ("DataSource Switch to: Write");
}
}
Custom Transactions
@Configuration @EnableTransactionManagement @Slf4j public class Mydatasourcetransactionmanagerautoconfiguration extends Datasourcetransactionmanagerautoconfiguration {/** * custom Transaction * MyBatis automatically participates in spring transaction management without additional configuration,
Transaction management will not work as long as the data source referenced by Org.mybatis.spring.SqlSessionFactoryBean is consistent with the data source referenced by Datasourcetransactionmanager.
* @return */@Bean (name = "TransactionManager") public Datasourcetransactionmanager transactionmanagers () {
Log.info ("--------------------transactionmanager init---------------------");
return new Datasourcetransactionmanager (Springcontextholder.getbean ("Roundrobindatasouceproxy")); }
}