You are on page 1of 6

i would strongly suggest to use autoconfiguration as-much as you can, it will make things a little

simpler. the main key is to set to delay the getting of the connection and preparing it for the current
transaction.

this can be achieved in 2 different ways.

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() {

return new beanpostprocessor() {


@override
public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception {
if (bean instanceof hibernatejpavendoradapter) {
((hibernatejpavendoradapter) bean).getjpadialect().setprepareconnection(false);
}
return bean;
}
};
}
however adding this property to your application.properties will also work:

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

# database master properties


master.datasource.url=jdbc:h2:mem:masterdb;db_close_delay=-1
master.datasource.username=sa
master.datasource.password=sa
master.datasource.configuration.pool-name=master-db

# database slave properties


slave.datasource.url=jdbc:h2:mem:slavedb;db_close_delay=-1
slave.datasource.username=sa
slave.datasource.password=sa
slave.datasource.configuration.pool-name=slave-db

# jpa properties settings


spring.jpa.database-platform=org.hibernate.dialect.h2dialect
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
spring.jpa.open-in-view=false

# enable errors in deserialization of missing or ignored properties


spring.jackson.deserialization.fail-on-unknown-properties=true
spring.jackson.deserialization.fail-on-ignored-properties=true

# enable errors on requests for non-existent resources


spring.mvc.throw-exception-if-no-handler-found=true

# disable mappings of static resources (is not usable in development of apis)


spring.web.resources.add-mappings=false
note: removed the driver for jdbc (not needed) only set spring.jpa.database-platform you set
either database or database-platform not both.

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();

map<object, object> datasourcemap = new hashmap<>();


datasourcemap.put(datasourcetype.read_write, masterdatasource);
datasourcemap.put(datasourcetype.read_only, slavedatasource);

routingdatasource.settargetdatasources(datasourcemap);
routingdatasource.setdefaulttargetdatasource(masterdatasource);

return routingdatasource;
}

@bean
public beanpostprocessor dialectprocessor() {

return new beanpostprocessor() {


@override
public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception {
if (bean instanceof hibernatejpavendoradapter) {
((hibernatejpavendoradapter) bean).getjpadialect().setprepareconnection(false);
}
return bean;
}
};
}
}
this will set up everything you need for this to work and still be able to use as much of the auto-
configuration and detection as you can. with this, the only configuration you need to do is
this datasource setup. no jpa, transaction management etc. as that will be done automatically.

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 solved this problem by changing my implementation of the routingconfiguration.java class.

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 {

private final environment environment;

public routingconfiguration(environment environment) {


this.environment = environment;
}

@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());

hibernatejpavendoradapter vendoradapter = new hibernatejpavendoradapter();


hibernatejpadialect jpadialect = vendoradapter.getjpadialect();

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();

map<object, object> datasourcemap = new hashmap<>();


datasourcemap.put(datasourcetype.read_write, masterdatasource);
datasourcemap.put(datasourcetype.read_only, slavedatasource);

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));
}

private hikaridatasource connectionpooldatasource(datasource datasource, string poolname) {


return new hikaridatasource(hikariconfig(datasource, poolname));
}

private hikariconfig hikariconfig(datasource datasource, string poolname) {


hikariconfig hikariconfig = new hikariconfig();

hikariconfig.setpoolname(poolname);
hikariconfig.setmaximumpoolsize(runtime.getruntime().availableprocessors() * 4);
hikariconfig.setdatasource(datasource);
hikariconfig.setautocommit(false);

return hikariconfig;
}

private properties additionalproperties() {


properties properties = new properties();

properties.setproperty("hibernate.dialect", environment.getproperty("spring.jpa.database-platform"));
properties.setproperty("hibernate.connection.provider_disables_autocommit", "true");

return properties;
}

private string determinepoolname(datasourcetype datasourcetype) {


return datasourcetype.getpoolname().concat("-").concat(datasourcetype.name());
}

}
the hibernate.connection.provider_disables_autocommit allows the connection is acquired prior to calling
the determinecurrentlookupkey method.

You might also like