jpa查询数据库时,默认会开启事务,当数据库采用master-slave的架构部署时,事务请求会默认达到master节点,这样会导致master节点的负载过高。下面是对这个问题的分析和处理。
首先,默认情况下jpa启动时,会和MySQL数据库产生如下交互:
SET NAMES utf8mb4
SET character_set_results = NULL
SET autocommit=1
autocommit=1,是应用向MySQL数据库发起自动提交设置。在自动提交模式下,所有的sql请求都会自动提交。
JpaRepository
中定义了一些通用的数据库查询方法,比如:List<T> findAll()
。这些方法,如果在具体的Repository
对象中没有重写的话,默认jpa会使用SimpleJpaRepository
类中对应方法的实现,处理相关逻辑。
在SimpleJpaRepository
类的定义上,有如下内容:
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID>
下图是运行时,对应方法的AOP chain:
Transactional(readOnly = true)
的设置,会使得jpa在执行相应方法时,关闭autocommit,采用手动提交的方式执行,对应的请求包如下:
SET autocommit=0
select xxxx from xxx
commit
SET autocommit=1
由于此时关闭了自动提交,MySQL集群会使用master处理上述请求。然而事实上,这个事务中只包含了一条select语句,并没有任何更新操作。所以,可以不用开启事务处理,这样的话,请求就可以随机地达到master和slave中的任意节点,流量可以更均衡。
解决方法也很简单,在具体的Repository中重写对应方法,并增加@Transactional(propagation = Propagation.SUPPORTS)
,如下:
@Repository
public interface DataSourceRepository extends JpaRepository<DataSource, String> {
@Transactional(propagation = Propagation.SUPPORTS)
List<DataSource> findAll();
}
Propagation.SUPPORTS
表示,如果当前请求是在事务中,则使用当前事务执行,如果不在事务中,则不开启新事务,直接处理。此时,jpa便只会发送一条select xxx from xxx的查询语句到MySQL数据库中。
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
* {@code SUPPORTS} is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),