EclipseLink / JPA:如何以编程方式获取已执行的SQL查询数

时间:2014-03-26 13:42:43

标签: java sql jpa eclipselink sniffy

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

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

提前感谢您的帮助!

3 个答案:

答案 0 :(得分:3)

我没有找到适合此类验证的工具并创建了我自己的工具。它被称为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");
}

project wiki

中提供了有关与JUnit集成的更多信息

答案 1 :(得分:1)

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

E.g。 MySQL有SHOW STATUS LIKE'查询'命令,它会转储运行的查询总数。

答案 2 :(得分:0)

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>