us.abstracta.jmeter.javadsl.jdbc.DslJdbcConnectionPool Maven / Gradle / Ivy
Show all versions of jmeter-java-dsl-jdbc Show documentation
package us.abstracta.jmeter.javadsl.jdbc;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Driver;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.jmeter.protocol.jdbc.config.DataSourceElement;
import org.apache.jmeter.testbeans.gui.TestBeanGUI;
import org.apache.jmeter.testelement.TestElement;
import us.abstracta.jmeter.javadsl.codegeneration.MethodCall;
import us.abstracta.jmeter.javadsl.codegeneration.MethodCallContext;
import us.abstracta.jmeter.javadsl.codegeneration.MethodParam;
import us.abstracta.jmeter.javadsl.codegeneration.SingleTestElementCallBuilder;
import us.abstracta.jmeter.javadsl.codegeneration.TestElementParamBuilder;
import us.abstracta.jmeter.javadsl.core.configs.BaseConfigElement;
/**
* Defines a JDBC connection pool used by other elements to interact with a database.
*
* @since 0.38
*/
public class DslJdbcConnectionPool extends BaseConfigElement {
private static final Map TRANSACTION_ISOLATION_TO_PROPERTY_VALUE =
buildTransactionIsolationToPropertyValueMapping();
private static final Duration DEFAULT_MAX_CONNECTION_WAIT = Duration.ofSeconds(10);
private static final int DEFAULT_TRANSACTION_ISOLATION = -1;
private static final String DEFAULT_PROPERTY_VALUE = "DEFAULT";
protected Class extends Driver> driverClass;
protected String url;
protected String user;
protected String password;
protected boolean autoCommit = true;
protected int maxConnections;
protected Duration maxConnectionWait = DEFAULT_MAX_CONNECTION_WAIT;
protected int transactionIsolation = DEFAULT_TRANSACTION_ISOLATION;
public DslJdbcConnectionPool(String name, Class extends Driver> driverClass, String url) {
super(name, TestBeanGUI.class);
this.driverClass = driverClass;
this.url = url;
}
private static Map buildTransactionIsolationToPropertyValueMapping() {
HashMap ret = new HashMap<>();
ret.put(DEFAULT_TRANSACTION_ISOLATION, DEFAULT_PROPERTY_VALUE);
ret.put(Connection.TRANSACTION_NONE, "TRANSACTION_NONE");
ret.put(Connection.TRANSACTION_READ_COMMITTED, "TRANSACTION_READ_COMMITTED");
ret.put(Connection.TRANSACTION_READ_UNCOMMITTED, "TRANSACTION_READ_UNCOMMITTED");
ret.put(Connection.TRANSACTION_REPEATABLE_READ, "TRANSACTION_REPEATABLE_READ");
ret.put(Connection.TRANSACTION_SERIALIZABLE, "TRANSACTION_SERIALIZABLE");
return ret;
}
/**
* Allows setting the username required to connect to the database.
*
* @param user is the username to connect to the database with.
* @return the config element for further configuration or usage.
*/
public DslJdbcConnectionPool user(String user) {
this.user = user;
return this;
}
/**
* Allows setting the password required to connect to the database.
*
* @param password is the password to connect to the database with.
* @return the config element for further configuration or usage.
*/
public DslJdbcConnectionPool password(String password) {
this.password = password;
return this;
}
/**
* Allows setting if auto-commit is enabled or not in pool connections.
*
* When enabled, auto-commits avoid having to explicitly commit each modification query (insert,
* update, delete, etc.), but on the other hand, when doing several queries in batch, it is not as
* performant as committing several queries at once. Additionally, you might want to disable
* auto-commit when you want a set of queries to execute in transaction mode (rolling back
* previous modifications on some particular conditions).
*
* When auto-commit is disabled, you will need to use {@link DslJdbcSampler#commit()} for changes
* to take effect, and might optionally use {@link DslJdbcSampler#rollback()} to cancel the
* transaction.
*
* @param enabled specifies whether auto-commit is enabled or disabled by default. The connection
* behavior can be changed at any point in time by
* {@link DslJdbcSampler#autoCommit(boolean)}. By default, this is enabled.
* @return the config element for further configuration or usage.
*/
public DslJdbcConnectionPool autoCommit(boolean enabled) {
this.autoCommit = enabled;
return this;
}
/**
* Allows setting max number of connections to keep with the database.
*
* The number of connections has a direct impact on database performance and in JMeter required
* memory. In general the default setting, 0, is good enough, assigning one connection to each
* JMeter thread. If you set it to another value then the pool of connections will be shared by
* all threads in JMeter and you should be careful to set it to a number that avoids threads
* waiting for each other on a connection to be available (eg: setting it to the max number of
* threads).
*
* @param maxConnections specifies the maximum number of connections to use to connect to the
* Database. 0 means one connection per thread. When set to a value
* different from 0, then connections are shared by JMeter threads.
* @return the config element for further configuration or usage.
*/
public DslJdbcConnectionPool maxConnections(int maxConnections) {
this.maxConnections = maxConnections;
return this;
}
/**
* Specifies the time to wait for a connection to be available.
*
* When this time is surpassed waiting for a connection, then an error will be generated.
*
* Is usually a good practice to set this to a value that can detect potential unexpected
* behavior. I.e.: set it to a value that is big enough for usual behavior not to fail, but low
* enough for detecting unusual/unexpected behavior.
*
* @param maxConnectionWait duration to wait for a connection until an error arises. By default,
* this is set to 10 seconds. If set to 0 seconds, it means that it will
* wait indefinitely for the connection (which is not advisable).
* @return the config element for further configuration or usage.
*/
public DslJdbcConnectionPool maxConnectionWait(Duration maxConnectionWait) {
this.maxConnectionWait = maxConnectionWait;
return this;
}
/**
* Allows specifying the transaction isolation level to use for queries executed by this pool.
*
* Transaction isolation level are usually required to be tuned either to improve performance or
* avoid potential conflicts between concurrent queries.
*
* @param transactionIsolation specifies a transaction level which value is -1 or one of
* {@link Connection#TRANSACTION_NONE},
* {@link Connection#TRANSACTION_READ_COMMITTED},
* {@link Connection#TRANSACTION_READ_UNCOMMITTED},
* {@link Connection#TRANSACTION_REPEATABLE_READ} or
* {@link Connection#TRANSACTION_SERIALIZABLE}. By default is set to
* -1, which means that it will use the default level for the
* connection string, session or database.
* @return the config element for further configuration or usage.
* @see java.sql.Connection
*/
public DslJdbcConnectionPool transactionIsolation(int transactionIsolation) {
this.transactionIsolation = transactionIsolation;
return this;
}
@Override
protected TestElement buildTestElement() {
DataSourceElement ret = new DataSourceElement();
ret.setDataSource(name);
ret.setDriver(driverClass.getName());
ret.setDbUrl(url);
ret.setUsername(user);
ret.setPassword(password);
ret.setAutocommit(autoCommit);
ret.setPoolMax(String.valueOf(maxConnections));
ret.setTimeout(String.valueOf(maxConnectionWait.toMillis()));
ret.setTransactionIsolation(transactionIsolationToPropertyValue(transactionIsolation));
ret.setPreinit(true);
return ret;
}
private String transactionIsolationToPropertyValue(int val) {
/*
this mapping is not strictly needed (since jmeter supports also int value), but makes
resulting JMX easier to read.
*/
return Optional.ofNullable(TRANSACTION_ISOLATION_TO_PROPERTY_VALUE.get(val))
.orElseThrow(() -> new IllegalArgumentException(
"Unknown transaction level " + val + " for pool " + name));
}
public static class CodeBuilder extends SingleTestElementCallBuilder {
public CodeBuilder(List builderMethods) {
super(DataSourceElement.class, builderMethods);
}
@Override
protected MethodCall buildMethodCall(DataSourceElement testElement, MethodCallContext context) {
TestElementParamBuilder paramBuilder = new TestElementParamBuilder(testElement);
return buildMethodCall(paramBuilder.stringParam("dataSource"),
new ClassParam(testElement.getPropertyAsString("driver")),
paramBuilder.stringParam("dbUrl"))
.chain("user", paramBuilder.stringParam("username"))
.chain("password", paramBuilder.stringParam("password"))
.chain("autoCommit", paramBuilder.boolParam("autocommit", true))
.chain("maxConnections", paramBuilder.intParam("poolMax", 0))
.chain("maxConnectionWait",
paramBuilder.durationParamMillis("timeout", DEFAULT_MAX_CONNECTION_WAIT))
.chain("transactionIsolation", new TransactionIsolationParam(
testElement.getPropertyAsString("transactionIsolation")));
}
}
private static class ClassParam extends MethodParam {
private final String className;
private ClassParam(String className) {
super(Class.class, null);
this.className = className;
}
@Override
public Set getImports() {
return Collections.singleton(className);
}
@Override
protected String buildCode(String indent) {
return className.substring(className.lastIndexOf(".") + 1) + ".class";
}
}
private static class TransactionIsolationParam extends MethodParam {
protected TransactionIsolationParam(String expression) {
super(int.class, expression);
}
@Override
public boolean isDefault() {
return expression == null || DEFAULT_PROPERTY_VALUE.equals(expression);
}
@Override
public Set getImports() {
return DEFAULT_PROPERTY_VALUE.equals(expression) ? Collections.emptySet()
: Collections.singleton(Connection.class.getName());
}
@Override
protected String buildCode(String indent) {
return DEFAULT_PROPERTY_VALUE.equals(expression) ? "-1" : "Connection." + expression;
}
}
}