InsertOperation.java

/*
 * Copyright 2025-present the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.simplejdbcmapper.core;

import java.util.Arrays;

import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;

import io.github.simplejdbcmapper.exception.MapperException;

/**
 * The insert operation.
 *
 * @author Antony Joseph
 */
class InsertOperation {
	private final SimpleJdbcMapperSupport sjmSupport;

	private final SimpleCache<Class<?>, SimpleJdbcInsert> insertSqlCache = new SimpleCache<>();

	public InsertOperation(SimpleJdbcMapperSupport sjmSupport) {
		this.sjmSupport = sjmSupport;
	}

	public void insert(Object object) {
		Assert.notNull(object, "object must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(object.getClass());
		EntityWrapper ew = new EntityWrapper(object);
		validateId(ew, tableMapping);
		populateAutoAssignProperties(ew, tableMapping);
		MapSqlParameterSource mapSqlParameterSource = createMapSqlParameterSource(ew, tableMapping);
		SimpleJdbcInsert simpleJdbcInsert = insertSqlCache.get(object.getClass());
		if (simpleJdbcInsert == null) {
			simpleJdbcInsert = createSimpleJdbcInsert(tableMapping);
			// Spring's SimpleJdbcInsert is thread safe. cache it
			insertSqlCache.put(object.getClass(), simpleJdbcInsert);
		}
		if (tableMapping.isIdAutoGenerated()) {
			KeyHolder kh = simpleJdbcInsert.executeAndReturnKeyHolder(mapSqlParameterSource);
			// set the generated id on the object
			ew.setPropertyValue(tableMapping.getIdPropertyMapping(), kh.getKeyAs(Object.class),
					sjmSupport.getConversionService());
		} else {
			simpleJdbcInsert.execute(mapSqlParameterSource);
		}
	}

	SimpleCache<Class<?>, SimpleJdbcInsert> getInsertSqlCache() {
		return insertSqlCache;
	}

	private MapSqlParameterSource createMapSqlParameterSource(EntityWrapper ew, TableMapping tableMapping) {
		MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
		for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
			Object val = ew.getPropertyValue(propMapping);
			Integer columnSqlType = propMapping.getColumnSqlType();
			String columnName = propMapping.getColumnName();
			if (propMapping.isBinaryLargeObject()) {
				InternalUtils.assignBlobMapSqlParameterSource(mapSqlParameterSource, val, columnName, columnSqlType);
			} else if (propMapping.isCharacterLargeObject()) {
				InternalUtils.assignClobMapSqlParameterSource(mapSqlParameterSource, val, columnName, columnSqlType);
			} else if (propMapping.isEnum()) {
				if (val == null) {
					mapSqlParameterSource.addValue(columnName, null, columnSqlType);
				} else {
					mapSqlParameterSource.addValue(columnName, ((Enum<?>) val).name(), columnSqlType);
				}
			} else {
				mapSqlParameterSource.addValue(columnName, val, columnSqlType);
			}
		}
		return mapSqlParameterSource;

	}

	private void validateId(EntityWrapper ew, TableMapping tableMapping) {
		Object idValue = ew.getPropertyValue(tableMapping.getIdPropertyMapping());
		if (tableMapping.isIdAutoGenerated()) {
			if (idValue != null) {
				throw new MapperException("For insert() the property " + ew.getWrappedClass().getSimpleName() + "."
						+ tableMapping.getIdPropertyName()
						+ " has to be null since this insert is for an object whose id is auto generated");
			}
		} else {
			if (idValue == null) {
				throw new MapperException("For insert() the property " + ew.getWrappedClass().getSimpleName() + "."
						+ tableMapping.getIdPropertyName() + " must not be null since it is not an auto generated id");
			}
		}
	}

	private void populateAutoAssignProperties(EntityWrapper ew, TableMapping tableMapping) {
		if (tableMapping.hasAutoAssignProperties()) {
			PropertyMapping createdOnPropMapping = tableMapping.getCreatedOnPropertyMapping();
			if (createdOnPropMapping != null && sjmSupport.getRecordAuditedOnSupplier() != null) {
				ew.setPropertyValue(createdOnPropMapping, sjmSupport.getRecordAuditedOnSupplier().get());
			}
			PropertyMapping updatedOnPropMapping = tableMapping.getUpdatedOnPropertyMapping();
			if (updatedOnPropMapping != null && sjmSupport.getRecordAuditedOnSupplier() != null) {
				ew.setPropertyValue(updatedOnPropMapping, sjmSupport.getRecordAuditedOnSupplier().get());
			}
			PropertyMapping createdByPropMapping = tableMapping.getCreatedByPropertyMapping();
			if (createdByPropMapping != null && sjmSupport.getRecordAuditedBySupplier() != null) {
				ew.setPropertyValue(createdByPropMapping, sjmSupport.getRecordAuditedBySupplier().get());
			}
			PropertyMapping updatedByPropMapping = tableMapping.getUpdatedByPropertyMapping();
			if (updatedByPropMapping != null && sjmSupport.getRecordAuditedBySupplier() != null) {
				ew.setPropertyValue(updatedByPropMapping, sjmSupport.getRecordAuditedBySupplier().get());
			}
			PropertyMapping versionPropMapping = tableMapping.getVersionPropertyMapping();
			if (versionPropMapping != null) {
				// version property value defaults to 1 on inserts
				ew.setPropertyValue(versionPropMapping, 1);
			}
		}
	}

	private SimpleJdbcInsert createSimpleJdbcInsert(TableMapping tableMapping) {
		SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(sjmSupport.getJdbcTemplate())
				.withCatalogName(tableMapping.getCatalogName()).withSchemaName(tableMapping.getSchemaName())
				.withTableName(tableMapping.getTableName());
		// make sure SimpleJdbcInsert does not access database table meta data.
		simpleJdbcInsert.setAccessTableColumnMetaData(false);
		String[] columns = null;
		if (tableMapping.isIdAutoGenerated()) {
			// remove id from list of columns
			columns = Arrays.stream(tableMapping.getPropertyMappings()).filter(e -> !e.isIdAnnotation())
					.map(e -> e.getColumnName()).toArray(String[]::new);
		} else {
			columns = Arrays.stream(tableMapping.getPropertyMappings()).map(e -> e.getColumnName())
					.toArray(String[]::new);
		}
		simpleJdbcInsert.usingColumns(columns);
		if (tableMapping.isIdAutoGenerated()) {
			simpleJdbcInsert.usingGeneratedKeyColumns(tableMapping.getIdColumnName());
		}
		return simpleJdbcInsert;
	}

}