[JAVA] A story about making Spring + Hibernate + MySQL apps replication compatible

Overview

Here is a summary of the findings when a legacy Java application with a standard configuration about 10 years ago was made compatible with DB replication. The technology and version used are as follows.

About replication

Before we get into the specifics, let's review replication.

Database replication is a concept adopted by many database management systems and means a master-slave relationship between the original and a copy of a database. The master records the update and notifies the slaves. The slave side sends a message that the update has been received correctly, notifying that the next update is ready to be accepted.

Source: [Wikipedia](https://en.wikipedia.org/wiki/%E3%83%AC%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3 % 82% B7% E3% 83% A7% E3% 83% B3)

Configuring a DB with a master that can be referenced and updated and a slave that is always in sync with the master [^ 1] and can only be referenced is called replication. When update processing such as ʻINSERT or ʻUPDATE is performed on the master and reference processing such as SELECT is performed on the slave to distribute the load of the DB or the master goes down for some reason In addition, you can minimize downtime and achieve high availability by preparing a failover replica to replace the master. You also have a copy of the master, which makes your data redundant.

[^ 1]: Actually, there is a synchronization delay of several milliseconds, so it may be necessary to consider it on the application side.

You can enjoy various benefits by using a replication configuration, but the application must also be made appropriately, such as distributed queries and measures against synchronization time lag.

Implementation

By specifying the readOnly option in Spring @Transactional, the contact is determined for each transaction.

As mentioned earlier, the requirements for implementation were "distributed query" and "synchronization time lag countermeasures".

Of these, the latter was addressed by expanding the scope of transactions. Specifically, in the previous implementation, transactions were created for each Repository layer (= 1 query), but changed to be created for each Service layer (= multiple queries). This means that even if there are both update and reference systems in multiple queries, the contact is determined as master or slave for each transaction. Since the slave is Readonly, it will inevitably be a query to the master if there are mixed update systems. Therefore, in a transaction in which update implementations are mixed, the reference implementation also queries the master, which means that strict load distribution is not possible, but instead it is not necessary to consider the synchronization time lag, so the load As a result of weighing the size and complexity of the implementation, we chose to simplify the implementation.

Perspective 1 transaction/1 Inquiry 1 transaction/n Inquiries
load distribution Can be strictly distributed between update system and reference system Strictly undispersed (master if there is an update system, slave if there is no update system)
Implementation simplicity It becomes complicated because it is necessary to consider the synchronization time lag (in the case of update → reference, consider that it is the latest data at the time of reference) Simple without considering synchronization time lag (even if update → reference, it will always be the latest data)

Therefore, what I write in this article is what I did for "distributed queries".

About @Transactional

I needed to know about this in order to use Spring's @Transactional, so I did a little research.

Official document

@Transactional adds transaction management related code such as begin and commit, which have been in the source code so far, to the method as annotations, and Spring AOP allows begin at the start of the method. It is a function that can expel transaction management from the source code and improve the visibility by performing commit at the end.

You can set various options for this @Transactional, but the readOnly used this time can specify true / false (default), which makes the contact a master or a slave. You can decide. The default is false, which queries the master, but you can specify true to query the slave.

How to use

If the class is bean-managed in the Spring container (= Spring AOP can be executed), just add it to the method as follows. It can also be given to the class itself, in which case it is synonymous with giving it to all methods of the class.

UserService.java


public class UserService implements Service {

    @Transactional(readOnly = false) //Beginned when this method is called and committed when exiting
    public User createUser() {
        // INSERT
    }

    @Transactional(readOnly = true) //Since it is a reference system, it is an inquiry to the slave.
    public User getUser() {
        // SELECT
    }

    @Transactional(readOnly = false) //Since it is an update system, contact the master
    public void updateUser() {
        // UPDATE
    }

    @Transactional(readOnly = false)
    public void deleteUser() {
        // DELETE
    }
}

Add the following description to the Spring configuration file and enable annotation.

Spring.xml


<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="dataSourceTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

The connection is made by MySQL Connector / J, but the DataSource is described as the connection destination as follows. → Official Document

Write the master host first, then the slave hosts separated by commas.

Spring.xml


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.ReplicationDriver"/>
    <property name="url" value="jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:13306"/>
    <property name="username" value="user"/>
    <property name="password" value="password"/>
</bean>

Summary

Since it was a support for legacy applications, it seems that the specific part will not be very helpful in the future, but I think that replication itself is a standard configuration, so I knew the concept and what kind of support is necessary. I think it's a harvest. I think DB is used in most apps, so I would like to make use of the knowledge I gained this time.

Recommended Posts

A story about making Spring + Hibernate + MySQL apps replication compatible
A story about making catkin_make of rosjava compatible offline
A story about BeanNotOfRequiredTypeException occurring after applying AOP in Spring
A story about making a calculator to calculate the shell mound rate
A story about a Spring Boot project written in Java that supports Kotlin