EclipseLink / JPA: How to programmatically get the number of SQL queries that have been performed

▼魔方 西西 提交于 2021-02-07 13:34:48

问题


I'm using JPA, by way of EclipseLink. In my unit tests, I'd like to test how many SQL queries were performed during an operation. That way, if a later modification causes the query count to explode (if lazy loading is triggered, for instance), the unit test will flag it as potentially needing optimization.

I'm striking out in finding the correct API to do this. A pure-JPA solution would be ideal, but I'm fine with using EclipseLink-specific APIs in my unit tests. I looked at the EclipseLink profiler, but it doesn't seem to give me a way to count the number of SQL queries.

Thanks in advance for the help!


回答1:


I didn't find a proper tool for such validation and created my own. It is called sniffy and available under MIT license.

You can assert the number of generated queries like shown below:

// 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");
}

More information about integration with JUnit available in project wiki




回答2:


Most databases have built-in statistics, you might consider using those.

E.g. MySQL has SHOW STATUS LIKE 'Queries' command which dumps total amount of queries run.




回答3:


Sniffy looks like a nice tool and maybe I will give it try in a future.

Below you can find how to handle profiling using only eclipseLink. Whole code can be found 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>


来源:https://stackoverflow.com/questions/22662833/eclipselink-jpa-how-to-programmatically-get-the-number-of-sql-queries-that-ha

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!