
com.ninja_squad.dbsetup.operation.Insert Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of DbSetup Show documentation
Show all versions of DbSetup Show documentation
Helps you setup your database with test data
/*
* The MIT License
*
* Copyright (c) 2012, Ninja Squad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.ninja_squad.dbsetup.operation;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import com.ninja_squad.dbsetup.bind.Binder;
import com.ninja_squad.dbsetup.bind.BinderConfiguration;
import com.ninja_squad.dbsetup.bind.Binders;
import com.ninja_squad.dbsetup.util.Preconditions;
/**
* Operation which inserts one or several rows into a table. Example usage:
*
* Insert insert =
* Insert.into("CLIENT")
* .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "CLIENT_TYPE")
* .values(1L, "John", "Doe", "1975-07-19", ClientType.NORMAL)
* .values(2L, "Jack", "Smith", "1969-08-22", ClientType.HIGH_PRIORITY)
* .withDefaultValue("DELETED", false)
* .withDefaultValue("VERSION", 1)
* .withBinder(new ClientTypeBinder(), "CLIENT_TYPE")
* .build();
*
*
* The above operation will insert two rows inside the CLIENT table. For each row, the column DELETED will be set to
* false
and the column VERSION will be set to 1. For the column CLIENT_TYPE, instead of using the
* {@link Binder} associated to the type of the column found in the metadata of the table, a custom binder will be used.
*
* @author JB Nizet
*/
@Immutable
public final class Insert implements Operation {
private final String table;
private final List columnNames;
private final Map defaultValues;
private final List> rows;
private final boolean metadataUsed;
private final Map binders;
private Insert(Builder builder) {
this.table = builder.table;
this.columnNames = builder.columnNames;
this.defaultValues = builder.defaultValues;
this.rows = builder.rows;
this.binders = builder.binders;
this.metadataUsed = builder.metadataUsed;
}
/**
* Inserts the values and default values in the table. Unless useMetadata
has been set to
* false
, the given configuration is used to get the appropriate binder. Nevertheless, if a binder
* has explicitely been associated to a given column, this binder will always be used for this column.
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(
value = "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING",
justification = "The point here is precisely to compose a SQL String from column names coming from the user")
@Override
public void execute(Connection connection, BinderConfiguration configuration) throws SQLException {
StringBuilder sql = new StringBuilder("insert into ").append(table).append(" (");
List allColumnNames = new ArrayList(columnNames);
allColumnNames.addAll(defaultValues.keySet());
for (Iterator it = allColumnNames.iterator(); it.hasNext(); ) {
String columnName = it.next();
sql.append(columnName);
if (it.hasNext()) {
sql.append(", ");
}
}
sql.append(") values (");
for (Iterator it = allColumnNames.iterator(); it.hasNext(); ) {
it.next();
sql.append("?");
if (it.hasNext()) {
sql.append(", ");
}
}
sql.append(")");
PreparedStatement stmt = connection.prepareStatement(sql.toString());
try {
Map metadataBinders = new HashMap();
if (metadataUsed) {
initializeBinders(stmt, allColumnNames, configuration, metadataBinders);
}
for (List> row : rows) {
int i = 0;
for (Object value : row) {
String columnName = columnNames.get(i);
Binder binder = getBinder(columnName, metadataBinders);
binder.bind(stmt, i + 1, value);
i++;
}
for (Map.Entry defaultValue : defaultValues.entrySet()) {
String columnName = defaultValue.getKey();
Binder binder = getBinder(columnName, metadataBinders);
binder.bind(stmt, i + 1, defaultValue.getValue());
i++;
}
stmt.executeUpdate();
}
}
finally {
stmt.close();
}
}
private void initializeBinders(PreparedStatement stmt,
List allColumnNames,
BinderConfiguration configuration,
Map metadataBinders) throws SQLException {
ParameterMetaData metadata = stmt.getParameterMetaData();
int i = 1;
for (String columnName : allColumnNames) {
if (!this.binders.containsKey(columnName)) {
metadataBinders.put(columnName, configuration.getBinder(metadata, i));
}
i++;
}
}
private Binder getBinder(String columnName, Map metadataBinders) {
Binder result = binders.get(columnName);
if (result == null) {
result = metadataBinders.get(columnName);
}
if (result == null) {
result = Binders.defaultBinder();
}
return result;
}
@Override
public String toString() {
return "insert into "
+ table
+ " [columns="
+ columnNames
+ ", defaultValues="
+ defaultValues
+ ", rows="
+ rows
+ ", metadataUsed="
+ metadataUsed
+ ", binders="
+ binders
+ "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + binders.hashCode();
result = prime * result + columnNames.hashCode();
result = prime * result + defaultValues.hashCode();
result = prime * result + Boolean.valueOf(metadataUsed).hashCode();
result = prime * result + rows.hashCode();
result = prime * result + table.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Insert other = (Insert) obj;
return binders.equals(other.binders)
&& columnNames.equals(other.columnNames)
&& defaultValues.equals(other.defaultValues)
&& metadataUsed == other.metadataUsed
&& rows.equals(other.rows)
&& table.equals(other.table);
}
/**
* Creates a new Builder instance, in order to build an Insert operation into the given table
* @param table the name of the table to insert into
* @return the created Builder
*/
public static Builder into(@Nonnull String table) {
Preconditions.checkNotNull(table, "table may not be null");
return new Builder(table);
}
/**
* A builder used to create an Insert operation. Such a builder may only be used once. Once it has built its Insert
* operation, all its methods throw an {@link IllegalStateException}.
* @see Insert#into(String)
* @author JB Nizet
*/
public static final class Builder {
private final String table;
private final List columnNames = new ArrayList();
private final Map defaultValues = new LinkedHashMap();
private final List> rows = new ArrayList>();
private boolean metadataUsed = true;
private final Map binders = new HashMap();
private boolean built;
private Builder(String table) {
this.table = table;
}
/**
* Specifies the list of columns into which values wil be inserted. The values must the be specifed, after,
* using the {@link #values(Object...)} method.
* @param columns the names of the columns to insert into.
* @return this Builder instance, for chaining.
* @throws IllegalStateException if the Insert has already been built, or if this method has already been
* called, or if one of the given columns is also specified as one of the "default value" columns.
*/
public Builder columns(@Nonnull String... columns) {
Preconditions.checkState(!built, "The insert has already been built");
Preconditions.checkState(columnNames.isEmpty(), "columns have already been specified");
for (String column : columns) {
Preconditions.checkNotNull(column, "column may not be null");
Preconditions.checkState(!defaultValues.containsKey(column),
"column "
+ column
+ " has already been specified as default value column");
}
columnNames.addAll(Arrays.asList(columns));
return this;
}
/**
* Adds a row of values to insert.
* @param values the values to insert.
* @return this Builder instance, for chaining.
* @throws IllegalStateException if the Insert has already been built, or if the number of values doesn't match
* the number of columns.
*/
public Builder values(@Nonnull Object... values) {
Preconditions.checkState(!built, "The insert has already been built");
Preconditions.checkArgument(values.length == columnNames.size(),
"The number of values doesn't match the number of columns");
rows.add(new ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy