Doma et Doma2 sont populaires dans SIer [source], et ce sont des frameworks d'accès à la base de données que vous voyez souvent dans les projets SI.
Cependant, Doma2 a des restrictions, telles que le fait d'être inaccessible à partir de plusieurs threads (une exception est levée lorsque vous le faites).
La fonction d'insertion avec plusieurs enregistrements dans la clause values n'est pas fournie en standard ... (@ BatchInsert
est une instruction d'insertion Spécifications à lancer plusieurs fois).
Donc, cette fois, je l'ai écrit pour que la fonction d'insertion lorsqu'il y a plusieurs enregistrements dans la clause values puisse être utilisée à partir de Doma.
Au fur et à mesure que je l'écrivais, j'ai appris dans les commentaires que si vous définissez rewriteBatchedStatements = true
dans le JDBC MySQL, il semble que plusieurs inserts seront combinés en un seul insert, je publierai donc un exemple de réglage.
Dans l'environnement que j'ai essayé cette fois, je devais en mettre deux.
--1. Définissez rewriteBatchedStatements = true
dans JDBC
--2 Changer BatchSize
par défaut
En ce qui concerne le nombre 2, selon cet article, 100 ou moins, c'est bien, alors je l'ai réglé à 100 cette fois. J'ai essayé d'obtenir et d'ajouter 1 million d'éléments via le journal de JDBC \ -Kagamihoge
batchSize peut être défini. Il n'est pas nécessaire sauf s'il est défini dans Config décrit plus loin ou que vous souhaitez le définir pour chaque requête.
@BatchInsert(batchSize = 100)
int[] batchInsert(List<Main.Entity2> entityList);
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.seasar.doma.SingletonConfig;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.Naming;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MysqlDialect;
import org.seasar.doma.jdbc.tx.LocalTransactionDataSource;
import org.seasar.doma.jdbc.tx.LocalTransactionManager;
import org.seasar.doma.jdbc.tx.TransactionManager;
import javax.sql.DataSource;
@SingletonConfig
public class DBConfig implements Config {
private static final DBConfig CONFIG = new DBConfig();
private final Dialect dialect;
private final LocalTransactionDataSource dataSource;
private final TransactionManager transactionManager;
private DBConfig() {
dialect = new MysqlDialect();
var config = new HikariConfig();
config.setDriverClassName("org.mariadb.jdbc.Driver");
config.setJdbcUrl("jdbc:mariadb://localhost:3306");
config.addDataSourceProperty("user", "root");
config.addDataSourceProperty("password", "password");
config.addDataSourceProperty("cachePrepStmts", true);
config.addDataSourceProperty("prepStmtCacheSize", 250);
config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
config.addDataSourceProperty("useServerPrepStmts", true);
config.addDataSourceProperty("useLocalSessionState", true);
config.addDataSourceProperty("rewriteBatchedStatements", true);//【ici】
config.addDataSourceProperty("cacheResultSetMetadata", true);
config.addDataSourceProperty("cacheServerConfiguration", true);
config.addDataSourceProperty("elideSetAutoCommits", true);
config.addDataSourceProperty("maintainTimeStats", false);
config.addDataSourceProperty("maintainTimeStats", false);
var hikariDataSource = new HikariDataSource(config);
dataSource = new LocalTransactionDataSource(hikariDataSource);
transactionManager = new LocalTransactionManager(
dataSource.getLocalTransaction(getJdbcLogger()));
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public Naming getNaming() {
return Naming.LENIENT_SNAKE_LOWER_CASE;
}
@Override
public int getBatchSize() {
return 100;//【ici】
}
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public TransactionManager getTransactionManager() {
return transactionManager;
}
public static DBConfig singleton() {
return CONFIG;
}
}
À propos, ↓ semble être le paramètre recommandé de MySQL JDBC par l'auteur HikariCP. MySQL Configuration · brettwooldridge/HikariCP Wiki
Puisqu'un insert est assemblé dans JDBC, il y a plusieurs insertions dans le journal Doma, mais si vous regardez le general_log du côté DB, il est correctement rassemblé dans un insert.
import org.seasar.doma.Dao;
import org.seasar.doma.jdbc.Config;
import java.sql.SQLException;
import java.util.List;
@Dao
public interface BulkDao {
DBUtils dbUtils = new DBUtils();
/**
*Exécuter l'insertion avec des données à valeurs multiples.Si le nombre d'entrées est différent de 0, l'instruction d'insertion assemblée est mise en cache..
*S'il n'y a aucune entrée, l'assembly d'instruction d'insertion et le traitement du cache ne seront pas exécutés..
*
* @param entityList Liste des entités à définir dans les valeurs.
* @param <E>insérer le type de données
* @return Renvoie la valeur de résultat de executeUpdate.S'il y a 0 entrées, executeUpdate n'est pas exécuté et 0 est renvoyé..
*/
default <E> int bulkInsertMultiValues(List<E> entityList) {
return dbUtils.bulkInsertMultiValues(
() -> {
try {
return Config.get(this).getDataSource().getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
},
entityList);
}
}
import lombok.RequiredArgsConstructor;
import org.seasar.doma.Table;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DBUtils {
/**
*Exécuter l'insertion avec des données à valeurs multiples.Si le nombre d'entrées est différent de 0, l'instruction d'insertion assemblée est mise en cache..
*S'il n'y a aucune entrée, l'assembly d'instruction d'insertion et le traitement du cache ne seront pas exécutés..
*
* @Param connectionSupplier Connection acquisition fonction
* @param entityList Liste des entités à définir dans les valeurs.
* @param <E>insérer le type de données
* @return Renvoie la valeur de résultat de executeUpdate.S'il y a 0 entrées, executeUpdate n'est pas exécuté et 0 est renvoyé..
*/
<E> int bulkInsertMultiValues(
Supplier<Connection> connectionSupplier, List<E> entityList) {
if (!entityList.isEmpty()) {
var clazz = entityList.get(0).getClass();
var cachedData = _BulkDaoMethod.getSqlWithMethod(clazz);
var sql = new StringBuilder(cachedData.sql);
var params = _BulkDaoMethod.getParams(entityList, cachedData, sql);
return _BulkDaoMethod.executeUpdate(connectionSupplier, sql, params);
}
return 0;
}
}
/**Contenu à mettre en cache*/
@RequiredArgsConstructor
class _SQLWithMethod {
final CharSequence sql;
final CharSequence values;
final List<Method> methodsList;
}
/**Méthodes utilisées dans BulkDao*/
class _BulkDaoMethod {
static ConcurrentHashMap<Class, _SQLWithMethod> cache = new ConcurrentHashMap<>();
static _SQLWithMethod getSqlWithMethod(Class<?> clazz) {
return cache.computeIfAbsent(
clazz,
_c -> {
Table tableAnnotation = clazz.getAnnotation(Table.class);
StringBuilder sql = new StringBuilder();
sql.append("insert into ").append(tableAnnotation.name());
var methodsList =
Arrays.stream(clazz.getDeclaredMethods())
.filter(e -> e.getName().startsWith("get"))
.collect(Collectors.toList());
var columuns =
methodsList.stream()
.map( //Convertir du nom de la méthode au nom de la colonne
e -> camelToSnake(e.getName().substring(3)))
.collect(Collectors.toList());
sql.append(" (").append(String.join(",", columuns)).append(")").append("values ");
var values = new StringBuilder("(");
IntStream.range(0, columuns.size() - 1).forEach((i) -> values.append("?,"));
values.append("?),");
return new _SQLWithMethod(sql, values, methodsList);
});
}
static <E> List<Object> getParams(
List<E> entityList, _SQLWithMethod cachedData, StringBuilder sql) {
var params = new ArrayList<>();
entityList.forEach(
values -> {
cachedData.methodsList.forEach(
method -> {
try {
var p = method.invoke(values);
params.add(p);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
sql.append(cachedData.values);
});
sql.deleteCharAt(sql.length() - 1);
return params;
}
static int executeUpdate(
Supplier<Connection> connectionSupplier, StringBuilder sql, List<Object> params) {
Connection conn = connectionSupplier.get();
try (var preparedStatement = conn.prepareStatement(sql.toString());){
var i = 0;
for (var p : params) {
++i;
preparedStatement.setObject(i, p);
}
return preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private static String camelToSnake(String snake) {
var sb = new StringBuilder(snake.length()<<1);
var input = snake.toCharArray();
sb.append(Character.toLowerCase(input[0]));
for (int i = 1; i < input.length; i++) {
var c = input[i];
if (Character.isUpperCase(c)) {
sb.append('_').append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
}
Ah, depuis que je l'ai écrit dans l'environnement java11, j'écris tout le temps var
, donc ça ne fonctionne pas avec Java 8 tel quel ...
En outre, il est supposé que vous utilisez lombok.
db est une hypothèse de cas de serpent inférieur.
@org.seasar.doma.Table(name="kintai.work_time")
@org.seasar.doma.Entity
@Data
public static class Entity2{
int workTimeId;
String remarks;
}
var list = new ArrayList<ExampleEntity>()
list.add(exampleEntity0);
list.add(exampleEntity1);
BulkDao.bulkInsertMultiValues(list);
Objet de classe mis en cache et instruction Insert. Comme la clé du cache est générique, je dois la prendre à partir de l'objet réel (il peut y avoir d'autres moyens, mais je pense que c'est ennuyeux) ...
Par exemple, pour spécifier un index tel que prepareStatement # setObject
Le type fonctionnel est incompatible avec le String Buider
conçu pour avoir un état.
Il existe également des exceptions de contrôle.
Recommended Posts