Database Routing Multi Read-Write
Database Routing Multi Read-Write
simpler. the main key is to set to delay the getting of the connection and preparing it for the current
transaction.
1. set the prepareconnection property of the jpadialect to false. if you don't then
the jpatransactionmanager will eagerly get connection and prepare it for the transaction. this is
even before it had time to set the current state of the transaction onto
the transactionsynchronizationmanager. which will make the call
to transactionsynchronizationmanager.iscurrenttransactionreadonly always return false (as it is set at
the end of the dobegin method in the jpatransactionmanager.
2. set the hibernate.connection.handling_mode to delayed_acquisition_and_release_after_transaction. this
will delay the getting of a connection and close the connection after the transaction. without
spring this is also the default for hibernate 5.2+ (see the hibernate user guide) but for legacy
reasons spring switches this to delayed_acquisition_and_hold.
either of these solutions will work as the preparing of the connection is delayed and
the jpatransactionmanager has thus time to sync the state in the transactionsynchronizationmanager.
@bean
public beanpostprocessor dialectprocessor() {
spring.jpa.properties.hibernate.connection.handling_mode=delayed_acquisition_and_release_after_transaction
with either one of these solutions you can now ditch your transaction configuration, jpa etc. there is
also an easier way to configure multiple datasources. it is described in the spring boot reference
guide which will reuse as much of the spring auto-configuration as possible.
first make sure the following is in your application.properties
now with this and the following @configuration class you will have 2 datasources, the routing one and
the beanpostprocessor as mentioned above (if you choose to use the property you can remove
said beanpostprocessor.
@configuration
public class datasourceconfiguration {
@bean
@configurationproperties("master.datasource")
public datasourceproperties masterdatasourceproperties() {
return new datasourceproperties();
}
@bean
@configurationproperties("master.datasource.configuration")
public hikaridatasource masterdatasource(datasourceproperties masterdatasourceproperties) {
return masterdatasourceproperties.initializedatasourcebuilder().type(hikaridatasource.class).build();
}
@bean
@configurationproperties("slave.datasource")
public datasourceproperties slavedatasourceproperties() {
return new datasourceproperties();
}
@bean
@configurationproperties("slave.datasource.configuration")
public hikaridatasource slavedatasource(datasourceproperties slavedatasourceproperties) {
return slavedatasourceproperties.initializedatasourcebuilder().type(hikaridatasource.class).build();
}
@bean
@primary
public transactionroutingdatasource routingdatasource(datasource masterdatasource, datasource slavedatasource)
{
transactionroutingdatasource routingdatasource = new transactionroutingdatasource();
routingdatasource.settargetdatasources(datasourcemap);
routingdatasource.setdefaulttargetdatasource(masterdatasource);
return routingdatasource;
}
@bean
public beanpostprocessor dialectprocessor() {
finally here is a test to test this with (you can test both scenarios). the read-only one will fail because
there is no schema there, the save will succeed as there is a schema on the read_write side of things.
@test
void testdatabaseswitch() {
assertions.assertthatthrownby(() -> billionaireservice.findall())
.isinstanceof(dataaccessexception.class);
billionaire newbillionaire = new billionaire(null, "marten", "deinum", "spring nerd.");
billionaireservice.save(newbillionaire);
M. Deinum 104382
SCORE:0
i configured the data source for using the setautocommit(false) configuration and added the
property hibernate.connection.provider_disables_autocommit with value true.
@configuration
@enabletransactionmanagement
public class routingconfiguration {
@bean
public localcontainerentitymanagerfactorybean entitymanagerfactory(@qualifier("routingdatasource") datasource
routingdatasource) {
localcontainerentitymanagerfactorybean entitymanagerfactorybean = new
localcontainerentitymanagerfactorybean();
entitymanagerfactorybean.setpersistenceunitname(getclass().getsimplename());
entitymanagerfactorybean.setpersistenceprovider(new hibernatepersistenceprovider());
entitymanagerfactorybean.setdatasource(routingdatasource);
entitymanagerfactorybean.setpackagestoscan(billionaires.class.getpackagename());
jpadialect.setprepareconnection(false);
entitymanagerfactorybean.setjpavendoradapter(vendoradapter);
entitymanagerfactorybean.setjpaproperties(additionalproperties());
return entitymanagerfactorybean;
}
@bean
public jpatransactionmanager transactionmanager(entitymanagerfactory entitymanagerfactory){
jpatransactionmanager transactionmanager = new jpatransactionmanager();
transactionmanager.setentitymanagerfactory(entitymanagerfactory);
return transactionmanager;
}
@bean
public transactiontemplate transactiontemplate(entitymanagerfactory entitymanagerfactory) {
return new transactiontemplate(transactionmanager(entitymanagerfactory));
}
@bean
public transactionroutingdatasource routingdatasource(
@qualifier("masterdatasource") datasource masterdatasource,
@qualifier("slavedatasource") datasource slavedatasource
){
transactionroutingdatasource routingdatasource = new transactionroutingdatasource();
routingdatasource.settargetdatasources(datasourcemap);
routingdatasource.setdefaulttargetdatasource(masterdatasource());
return routingdatasource;
}
@bean
public datasource masterdatasource() {
drivermanagerdatasource datasource = new drivermanagerdatasource();
datasource.seturl(environment.getproperty("master.datasource.url"));
datasource.setusername(environment.getproperty("master.datasource.username"));
datasource.setpassword(environment.getproperty("master.datasource.password"));
return connectionpooldatasource(datasource, determinepoolname(datasourcetype.read_write));
}
@bean
public datasource slavedatasource() {
drivermanagerdatasource datasource = new drivermanagerdatasource();
datasource.seturl(environment.getproperty("slave.datasource.url"));
datasource.setusername(environment.getproperty("slave.datasource.username"));
datasource.setpassword(environment.getproperty("slave.datasource.password"));
return connectionpooldatasource(datasource, determinepoolname(datasourcetype.read_only));
}
hikariconfig.setpoolname(poolname);
hikariconfig.setmaximumpoolsize(runtime.getruntime().availableprocessors() * 4);
hikariconfig.setdatasource(datasource);
hikariconfig.setautocommit(false);
return hikariconfig;
}
properties.setproperty("hibernate.dialect", environment.getproperty("spring.jpa.database-platform"));
properties.setproperty("hibernate.connection.provider_disables_autocommit", "true");
return properties;
}
}
the hibernate.connection.provider_disables_autocommit allows the connection is acquired prior to calling
the determinecurrentlookupkey method.