我正在使用JPA,通过EclipseLink。在我的单元测试中,我想测试在操作期间执行了多少SQL查询。这样,如果稍后的修改导致查询计数爆炸(例如,如果触发延迟加载),则单元测试会将其标记为可能需要优化。
我正在寻找正确的API来做到这一点。纯JPA解决方案是理想的,但我可以在单元测试中使用特定于EclipseLink的API。我查看了EclipseLink分析器,但它似乎没有给我一种方法来计算SQL查询的数量。
提前感谢您的帮助!
答案 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");
}
中提供了有关与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>