{"id":307,"date":"2011-06-13T13:25:00","date_gmt":"2011-06-13T20:25:00","guid":{"rendered":"http:\/\/www.dragishak.com\/?p=307"},"modified":"2020-08-28T13:13:44","modified_gmt":"2020-08-28T20:13:44","slug":"mysql-masterslave-load-balancing-with-jpa-and-spring","status":"publish","type":"post","link":"https:\/\/www.dragishak.com\/?p=307","title":{"rendered":"MySQL Master\/Slave Load Balancing with JPA and Spring"},"content":{"rendered":"<p>MySQL <a href=\"http:\/\/www.mysql.com\/downloads\/connector\/j\/\">Connector\/J<\/a> driver has built-in feature for load balancing.<\/p>\n<p>If you have a cluster of read\/write MySQL servers. Putting <code><b>loadbalance:<\/b><\/code> in the JDBC URL will ensure both read and write operations are distributed across servers.<br \/>\n<strong>jdbc:mysql:loadbalance:\/\/master1,master2,master3&#8230;\/database?loadBalanceBlacklistTimeout=5000&amp;loadBalanceConnectionGroup=cgroup&amp;loadBalanceEnableJMX=true&amp;autoReconnect=true&amp;autoReconnectForPools=true<\/strong><\/p>\n<p>What we needed is all write operations to go to master server and read-only operations to be equally distributed among multiple read-only slaves.<\/p>\n<p>For that you need to:<\/p>\n<ol>\n<li>Use special JDBC driver: <code><b>com.mysql.jdbc.ReplicationDriver<\/b><\/code><\/li>\n<li>Set <code><b>replication:<\/b><\/code> in the URL:<br \/>\n<strong>jdbc:mysql:replication:\/\/master,slave1,slave2&#8230;\/database?loadBalanceBlacklistTimeout=5000&amp;loadBalanceConnectionGroup=ugc&amp;loadBalanceEnableJMX=true&amp;autoReconnect=true&amp;autoReconnectForPools=true<\/strong><\/li>\n<\/ol>\n<p>After setting our connection pool like this, all load still ended up going to our single read\/write master server.<\/p>\n<p>The reason is that, for the ReplicationDriver to know that queries can go to read-only slaves, two conditions need to be met:<\/p>\n<ol>\n<li>Auto commit needs to be turned off. (*)<\/li>\n<li>Connection needs to be set to read-only.<\/li>\n<\/ol>\n<p><sup>(*)<\/sup><i>There is a workaround to allow auto commit: <a href=\"http:\/\/mysqlblog.fivefarmers.com\/2010\/08\/03\/connectorj-load-balancing-for-auto-commit-enabled-deployments\/\">Connector\/J load-balancing for auto-commit-enabled deployments<\/a><br \/>\n<\/i><\/p>\n<p>Turns out, even if transaction is set to read-only, neither Spring nor JPA providers like Hibernate or EclipseLink will set JDBC connection to readOnly.<\/p>\n<p>To ensure JDBC Connection is set to read-only, I created an annotation and a simple AOP interceptor.<\/p>\n<p>Here is an example code:<\/p>\n<pre><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Aspect\npublic class ReadOnlyConnectionInterceptor implements Ordered {\n\n    private int order;\n    private EntityManager entityManager;\n\n    public void setOrder(int order) {\n        this.order = order;\n    }\n\n    @Override\n    public int getOrder() {\n        return order;\n    }\n\n    @PersistenceContext\n    public void setEntityManager(EntityManager entityManager) {\n        this.entityManager = entityManager;\n    }\n\n    @Around(&quot;@annotation(readOnlyConnection)&quot;)\n    public Object proceed(ProceedingJoinPoint pjp, ReadOnlyConnection readOnlyConnection) throws Throwable {\n\n        Connection connection = entityManager.unwrap(java.sql.Connection.class);\n\n        boolean autoCommit = connection.getAutoCommit();\n        boolean readOnly = connection.isReadOnly();\n\n        try {\n            connection.setAutoCommit(false);\n            connection.setReadOnly(true);\n\n            return pjp.proceed();\n\n        } finally {\n            \/\/ restore state\n            connection.setReadOnly(readOnly);\n            connection.setAutoCommit(autoCommit);\n        }\n    }\n}\n<\/pre>\n<p>One important thing is to set priority of this interceptor to be lower than the Spring&#8217;s transaction interceptor. That way we want to make sure transaction is created before we start modifying the connection. In other words, you would set order for @Transactional to be less than order for your new interceptor:<\/p>\n<pre><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;tx:annotation-driven order=&quot;10&quot;&gt;\n    &lt;bean id=&quot;readOnlyInterceptor&quot; class=&quot;com.myproject.ReadOnlyConnectionInterceptor&quot;&gt;\n        &lt;property name=&quot;order&quot; value=&quot;20&quot;&gt;&lt;\/property&gt;\n    &lt;\/bean&gt;\n&lt;\/tx:annotation-driven&gt;\n<\/pre>\n<p>Now, if you want queries to execute on your pool of slave databases, you can just set an annotation on the service method:<\/p>\n<pre><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Transactional(readOnly=true)\n@ReadOnlyConnection\npublic Result serviceMethod(...) {\n    ...\n}\n<\/pre>\n<p>References:<\/p>\n<ul>\n<li><a href=\"http:\/\/dev.mysql.com\/doc\/refman\/5.6\/en\/connector-j-reference-configuration-properties.html\">Driver\/Datasource Class Names, URL Syntax and Configuration Properties for Connector\/J<\/a><\/li>\n<li><a href=\"http:\/\/mysqlblog.fivefarmers.com\/tag\/connector-j\/\">Todd Farmer&#8217;s blog about Connector\/J<\/a><\/li>\n<li><a href=\"http:\/\/gabrito.com\/post\/load-balancing-across-mysql-servers-using-jdbc\">Load balancing across MySQL servers using JDBC by Todd Huss<\/a><\/li>\n<li><a href=\"http:\/\/static.springsource.org\/spring\/docs\/current\/reference\/aop.html\">Aspect Oriented Programming with Spring<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>MySQL Connector\/J driver has built-in feature for load balancing. If you have a cluster of read\/write MySQL servers. Putting loadbalance: in the JDBC URL will ensure both read and write operations are distributed across servers. jdbc:mysql:loadbalance:\/\/master1,master2,master3&#8230;\/database?loadBalanceBlacklistTimeout=5000&amp;loadBalanceConnectionGroup=cgroup&amp;loadBalanceEnableJMX=true&amp;autoReconnect=true&amp;autoReconnectForPools=true What we needed is all write operations to go to master server and read-only operations to be equally distributed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15],"tags":[29,54,55,56,52,57,53],"class_list":["post-307","post","type-post","status-publish","format-standard","hentry","category-software","tag-java","tag-jdbc","tag-jpa","tag-load-balancing","tag-mysql","tag-scaling","tag-spring"],"_links":{"self":[{"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/posts\/307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=307"}],"version-history":[{"count":60,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/posts\/307\/revisions"}],"predecessor-version":[{"id":411,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=\/wp\/v2\/posts\/307\/revisions\/411"}],"wp:attachment":[{"href":"https:\/\/www.dragishak.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=307"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=307"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dragishak.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}