【问题标题】:EclipseLink / JPA: How to programmatically get the number of SQL queries that have been performedEclipseLink / JPA:如何以编程方式获取已执行的 SQL 查询数
【发布时间】:2014-05-04 23:07:36
【问题描述】:

我正在通过 EclipseLink 使用 JPA。在我的单元测试中,我想测试在操作期间执行了多少 SQL 查询。这样,如果以后的修改导致查询计数爆炸(例如,如果触发延迟加载),单元测试会将其标记为可能需要优化。

我正在努力寻找正确的 API 来执行此操作。纯 JPA 解决方案将是理想的,但我可以在单元测试中使用 EclipseLink 特定的 API。我查看了 EclipseLink 分析器,但它似乎没有给我一种方法来计算 SQL 查询的数量。

提前感谢您的帮助!

【问题讨论】:

  • 难道你不能通过一些保持计数的类来执行所有的查询吗?还是我在描述中遗漏了什么?
  • @Cruncher 在整个 JPA 实现中跟踪这将是极其困难的,因为您可能拥有数十个(或数百个)全部被持久化的实体对象。由于大多数 JPA 也执行一些“魔术”(即智能缓存),因此您必须进行一些挖掘才能获得实际的查询计数。
  • @Cruncher 在我的情况下,当引用延迟获取的外部实体时,大多数查询都是在幕后执行的。这就是我想添加测试的原因:如果我进行了导致查询计数增加的更改(例如访问我以前没有的子实体),我希望测试能够捕获它以便我可以采取适当的操作 - 修改测试(如果查询的增加是可以接受的),将该类的提取从惰性更改为急切,等等。

标签: java sql jpa eclipselink sniffy


【解决方案1】:

Sniffy 看起来是个不错的工具,也许我以后会尝试一下。

您可以在下面找到仅使用 eclipseLink 处理分析的方法。 完整代码可以找到here

import io.github.ssledz.domain.{Employee, Project}
import javax.persistence.{EntityManagerFactory, Persistence, PersistenceUnitUtil}
import org.eclipse.persistence.annotations.BatchFetchType
import org.eclipse.persistence.config.QueryHints
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate
import org.eclipse.persistence.queries.LoadGroup
import org.eclipse.persistence.sessions.SessionProfiler
import org.eclipse.persistence.tools.profiler.PerformanceMonitor

import scala.jdk.CollectionConverters._

/**
 * This example requires that all entities are properly waved by eclipseLink
 *
 * In order to make eclipseLink happy download java agent
 * wget -O /tmp/eclipselink.jar https://repo1.maven.org/maven2/org/eclipse/persistence/eclipselink/2.7.7/eclipselink-2.7.7.jar
 * pass following parameter -javaagent:/tmp/eclipselink.jar to jvm
 * and set 'eclipselink.weaving' to true in persistance.xml
 *
 */
object JpaQueryProfilingExample extends App {

  val emf: EntityManagerFactory = Persistence.createEntityManagerFactory("default")
  val pUtil: PersistenceUnitUtil = emf.getPersistenceUnitUtil
  val emfd: EntityManagerFactoryDelegate = emf.unwrap(classOf[EntityManagerFactoryDelegate])
  val profiler: PerformanceMonitor = emfd.getServerSession.getProfiler.asInstanceOf[PerformanceMonitor]

  val em = emf.createEntityManager()

  clearDb()

  val johny = createEmployee("Johny", "Bravo", "eclipse-link-playground")
  val tom = createEmployee("Tom", "Tip", "eclipse-link-profiling")

  em.clear()
  emf.getCache.evictAll()

  val stats = QueryStatistics(profiler)

  val employees = findAll()

  def numberOfDbQueries: Int = QueryStatistics(profiler).diff(stats).numberOfDbQueries

  assert(employees.size == 2)
  assert(numberOfDbQueries == 1)

  assertNotLoaded(employees.head, "projects")

  employees.foreach(_.getProjects) // trigger lazy loading

  assert(numberOfDbQueries == 3, "+2 for lazy loaded project")

  private def clearDb(): Unit = {
    val tx = em.getTransaction
    tx.begin()
    em.createQuery("delete from Employee").executeUpdate()
    em.createQuery("delete from Project").executeUpdate()
    tx.commit()
  }

  private def findAll(): List[Employee] =
    em.createQuery("select e from Employee e", classOf[Employee]).getResultList.asScala.toList

  private def findAllWithProjects(): List[Employee] = {
    val query = em.createQuery("select e from Employee e", classOf[Employee])
    query.setHint(QueryHints.LOAD_GROUP, loadGroup("projects"))
    query.setHint(QueryHints.BATCH_TYPE, BatchFetchType.EXISTS)
    query.setHint(QueryHints.BATCH, "e.projects")
    query.getResultList.asScala.toList
  }

  private def createEmployee(firstName: String, lastName: String, projectName: String): Employee = {
    val employee = new Employee
    employee.setFirstName(firstName)
    employee.setLastName(lastName)
    val project = employee.addProject(Project(projectName))
    val tx = em.getTransaction
    tx.begin()
    em.persist(employee)
    em.persist(project)
    tx.commit()
    employee
  }

  private def loadGroup(attributes: String*): LoadGroup = {
    val lg = new LoadGroup()
    attributes.foreach(lg.addAttribute)
    lg
  }

  private def assertLoaded(obj: AnyRef, attributeName: String): Unit = assertLoadedOrNot(obj, attributeName, true)

  private def assertNotLoaded(obj: AnyRef, attributeName: String): Unit = assertLoadedOrNot(obj, attributeName, false)

  private def assertLoadedOrNot(obj: AnyRef, attributeName: String, loaded: Boolean): Unit = {
    val message = s"$attributeName property should be ${if (loaded) "eagerly" else "lazy"} loaded"
    assert(pUtil.isLoaded(obj, attributeName) == loaded, s"because $message")
  }

}

case class QueryStatistics(readAllQuery: Int, readObjectQuery: Int, cacheHits: Int) {

  def numberOfDbQueries: Int = (readObjectQuery + readAllQuery) - cacheHits

  def diff(other: QueryStatistics): QueryStatistics =
    QueryStatistics(
      readAllQuery - other.readAllQuery,
      readObjectQuery - other.readObjectQuery,
      cacheHits - other.cacheHits
    )
}

object QueryStatistics {
  def nullToZero(a: Any): Int = Option(a).map(_.toString.toInt).getOrElse(0)

  def apply(pm: PerformanceMonitor): QueryStatistics =
    new QueryStatistics(
      nullToZero(pm.getOperationTimings.get("Counter:ReadAllQuery")),
      nullToZero(pm.getOperationTimings.get("Counter:ReadObjectQuery")),
      nullToZero(pm.getOperationTimings.get(SessionProfiler.CacheHits))
    )
} 

persistance.xml

<persistence>
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>io.github.ssledz.domain.Employee</class>
        <class>io.github.ssledz.domain.Project</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <!-- https://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/persistenceproperties_ref.htm#CACDCCEG2  -->
        <properties>
            <property name="eclipselink.profiler" value="PerformanceMonitor"/>
            <property name="eclipselink.weaving" value="true"/>
            <property name="eclipselink.logging.thread" value="true"/>
            <property name="eclipselink.logging.session" value="true"/>
            <property name="eclipselink.logging.timestamp" value="true"/>
            <property name="eclipselink.logging.level" value="ALL"/>
            <property name="eclipselink.logging.level.sql" value="ALL"/>
            <property name="eclipselink.logging.parameters" value="true"/>
<!--            <property name="eclipselink.logging.logger" value="org.eclipse.persistence.logging.slf4j.SLF4JLogger"/>-->
            <property name="eclipselink.ddl-generation" value="create-tables"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/eclipse_link_test?serverTimezone=UTC"/>
            <property name="javax.persistence.jdbc.user" value="test"/>
            <property name="javax.persistence.jdbc.password" value="test"/>
        </properties>
    </persistence-unit>
</persistence>

【讨论】:

    【解决方案2】:

    我没有找到合适的工具来进行此类验证,并创建了自己的工具。它被称为sniffy,在 MIT 许可下可用。

    您可以断言生成查询的数量,如下所示:

    // Integrate Sniffy to your test using @Rule annotation and a QueryCounter field
    @Rule
    public final QueryCounter queryCounter = new QueryCounter();
    
    // Now just add @Expectation or @Expectations annotations to define number of queries allowed for given method
    @Test
    @Expectation(1)
    public void testJUnitIntegration() throws SQLException {
        // Just add sniffer: in front of your JDBC connection URL in order to enable sniffer
        final Connection connection = DriverManager.getConnection("sniffer:jdbc:h2:mem:", "sa", "sa");
        // Do not make any changes in your code - just add the @Rule QueryCounter and put annotations on your test method
        connection.createStatement().execute("SELECT 1 FROM DUAL");
    }
    

    有关与 JUnit 集成的更多信息,请参阅 project wiki

    【讨论】:

      【解决方案3】:

      大多数数据库都有内置的统计信息,您可以考虑使用这些。

      例如MySQL 有 SHOW STATUS LIKE 'Queries' 命令转储运行的查询总量。

      【讨论】:

      • 有趣的想法 - 谢谢。尽管 JUnit 测试使用的是内存 HSQLDB,但我正在努力寻找 HSQL 文档中的类似功能。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-01
      • 1970-01-01
      • 2021-02-15
      相关资源
      最近更新 更多