Mybatis-based read-write separation plugin

I believe that many friends have tried to write read-write separation plugins, or have used them in projects. First of all, the responsibility of read-write separation should belong to the data access layer rather than the business layer, and secondly, the read-write separation should not invade our code layer. Therefore, in the service-dao-orm-database-driven call chain, if the plug-in does not invade our code, it can only be written in the orm layer and the database driver layer. If it is written in the orm layer, it is coupled with the specific orm framework and written in the database driver. The layer is coupled with the specific database.

 

Whether to achieve read-write separation at the orm layer or to achieve read-write separation at the database driver mainly depends on the higher cost and difficulty of replacing the orm framework and database. I won't discuss which one is better here. The read-write separation plug-in introduced today is based on the mybatis framework to implement one write and many read. Based on springboot configuration, it is very convenient to integrate in existing projects. Download the source code into a jar package and introduce it into the project. Add the following configuration to the springboot configuration file to enable read-write separation.

 

 

 

This plugin does three things in total: data source proxy, data source routing, and distributed transactions .

 

This plug-in has zero intrusion to existing code. To achieve zero intrusion, it benefits from the proxy mode.

 

First of all, data source proxy, read-write separation There are at least two data sources in a business, read data source, write data source, but all sql execution in a transaction is operated under the same database connection, so it is necessary to implement the DataSorce interface Proxy to read and write data source: DataSourceProxy. The DataSourceProxy class returns the connection of the write data source when the write operation is performed, and the connection of the read data source when the read operation is performed. However, the read and write operations can only be determined when the database operation is actually executed. However, before the SQL statement is actually executed, the Connection operation has been obtained. Therefore, when obtaining the Connection operation, a proxy Connection should be returned, and then the actual execution of the SQL statement should be based on The current environment gets the real Connection.

 

因此 DataSourceProxy 返回的 Connection 是一个代理类, 依赖一个 DataSourceRout 接口,在未执行sql语句之前都是由 Connection 代理类完成操作。再执行 sql 语句时,由 DataSourceRout 接口返回具体 Connection 执行 sql 语句,DataSourceRout 接口只有一个 getTargetDataSource 方法,由具体实现类根据当前环境确定目标数据源,可能是读写数据源,也可能是分表后的具体目标数据源。

 

DataSourceRout 接口目前有两个实现类,AbstractRWDataSourceRout 实现读写分离,UserDataSourceRout 实现根据不同的用户路由到不同的数据库组上。UserDataSourceRout 这个类依赖一组 AbstractRWDataSourceRout,实现读写分离。

 

具体类结构如下:

将 DataSourceProxy 注入到 org.mybatis.spring.SqlSessionTemplate 里面。Mybatis 便实现读写分离。此时对现有代码完全透明。当然也可以注入到 hibernate 框架中,只不过需要自己实现 DataSourceRout 接口,DataSourceRout 接口的实现类AbstractRWDataSourceRout 是基于 mybatis 的。

 

通过 org.mybatis.spring.SqlSessionTemplate 这个类的源码查询,org.apache.ibatis.mapping.MappedStatement 这个类里面的 org.apache.ibatis.mapping.SqlCommandType 这个域定义了 mybatis 执行 sql 语句类型,可以通过这个类确定当前操作是读操作还是写操作。

 

写一个 mybatis 的插件,在 sql 执行过程中通 SqlCommandType 这个类确定当前上下文是读操作还是写操作。把读写标记存入上下文中,在 AbstractRWDataSourceRout 这个类中拿取上下文中的读写标记返回对应的数据源,为了事务简单,保证当前上下文最多只有一个写连接和一个读连接,检查当前上下文是否有对应的数据库连接,如果没有相应的连接,获取连接,保存在当前上下文中,方便下次 sql 语句执行和事务执行。

 

数据源由原来单一数据源变成了一个读数据源和一个写数据源,事务也就变成了两个事务。Mybatis 集成 spring 后,mybatis 的事务交由 spring 管理,具体实现类是 org.mybatis.spring.transaction.SpringManagedTransaction,为了和 myabtis-spring 无缝集成,采用代理模式,RWManagedTransaction 继承 SpringManagedTransaction,把事务分别委托给读写事务,整个线程只有一个读事务和一个写事务,读事务比较弱。因此分布式事务采用 Best Efforts 1PC 模式。

 

public void commit() throws SQLException {

      Map<String, Connection> connectionMap = ConnectionHold.CONNECTION_CONTEXT.get();

      Connection writeCon = connectionMap.remove(ConnectionHold.WRITE);

      if(writeCon != null){

         writeCon.commit();

      }

      Connection readCon = connectionMap.remove(ConnectionHold.READ);

      if(readCon != null){

         try {

            readCon.commit();

         } catch (Exception e) {

            e.printStackTrace();

         }

      }

   }

 

第一个事务成功后,第二个事务有可能因为网络原因或者服务器宕机,不能执行成功,这个网络通讯的危险期虽然概率很小,但是也是个不可靠因素之一。由于整个会话中,只有一个写数据连接和一个读数据连接,读的事务性比较弱,只要写事务成功了,读事务失败影响不大,当然也可以不考虑读事务。因此先处理写事务,再处理读事务。

 

作为一个初级程序员,想要扩展现有的开源框架,其实不是那么困难,只要实现相应的接口,参考现有实现类实现接口,如果现有的实现类太复杂看不懂逻辑,其实也很好实现接口,就是把自己的实现委托给现有实现类,自己在委托前后做一些自己的业务逻辑。

 

引用:http://mp.weixin.qq.com/s/O8DfUjIw18WehILOWsR8ug

 

此插件代码托管在

https://github.com/chenlei2/spring-boot-mybatis-rw

 

欢迎大家 fork。

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326961644&siteId=291194637