InsertOperation.java

package io.github.simplejdbcmapper.core;

import org.springframework.beans.BeanWrapper;
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;

class InsertOperation {
	private final SimpleJdbcMapperSupport sjmSupport;

	// insert cache. Note that Spring SimpleJdbcInsert is thread safe.
	// Map key - class name
	// value - SimpleJdbcInsert
	private final SimpleCache<String, 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());
		BeanWrapper bw = sjmSupport.getBeanWrapper(object);
		validateId(tableMapping, bw);
		populateAutoAssignProperties(tableMapping, bw);
		MapSqlParameterSource mapSqlParameterSource = createMapSqlParameterSource(tableMapping, bw);
		SimpleJdbcInsert simpleJdbcInsert = insertSqlCache.get(object.getClass().getName());
		if (simpleJdbcInsert == null) {
			simpleJdbcInsert = createSimpleJdbcInsert(tableMapping);
			// SimpleJdbcInsert is thread safe. cache it
			insertSqlCache.put(object.getClass().getName(), simpleJdbcInsert);
		}
		if (tableMapping.isIdAutoGenerated()) {
			KeyHolder kh = simpleJdbcInsert.executeAndReturnKeyHolder(mapSqlParameterSource);
			// set the generated id on the object
			bw.setPropertyValue(tableMapping.getIdPropertyName(), kh.getKeyAs(Object.class));
		} else {
			simpleJdbcInsert.execute(mapSqlParameterSource);
		}
	}

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

	private MapSqlParameterSource createMapSqlParameterSource(TableMapping tableMapping, BeanWrapper bw) {
		MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
		for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
			Integer columnSqlType = propMapping.getEffectiveSqlType();
			if (propMapping.isBinaryLargeObject()) {
				InternalUtils.assignBlobMapSqlParameterSource(bw, mapSqlParameterSource, propMapping, columnSqlType,
						true);
			} else if (propMapping.isCharacterLargeObject()) {
				InternalUtils.assignClobMapSqlParameterSource(bw, mapSqlParameterSource, propMapping, columnSqlType,
						true);
			} else if (propMapping.isEnum()) {
				InternalUtils.assignEnumMapSqlParameterSource(bw, mapSqlParameterSource, propMapping, columnSqlType,
						true);
			} else {
				mapSqlParameterSource.addValue(propMapping.getColumnName(),
						bw.getPropertyValue(propMapping.getPropertyName()), columnSqlType);
			}
		}
		return mapSqlParameterSource;
	}

	private void validateId(TableMapping tableMapping, BeanWrapper bw) {
		Object idValue = bw.getPropertyValue(tableMapping.getIdPropertyName());
		if (tableMapping.isIdAutoGenerated()) {
			if (idValue != null) {
				throw new MapperException("For insert() the property " + bw.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 " + bw.getWrappedClass().getSimpleName() + "."
						+ tableMapping.getIdPropertyName() + " must not be null since it is not an auto generated id");
			}
		}
	}

	private void populateAutoAssignProperties(TableMapping tableMapping, BeanWrapper bw) {
		if (tableMapping.hasAutoAssignProperties()) {
			PropertyMapping createdOnPropMapping = tableMapping.getCreatedOnPropertyMapping();
			if (createdOnPropMapping != null && sjmSupport.getRecordAuditedOnSupplier() != null) {
				bw.setPropertyValue(createdOnPropMapping.getPropertyName(),
						sjmSupport.getRecordAuditedOnSupplier().get());
			}
			PropertyMapping updatedOnPropMapping = tableMapping.getUpdatedOnPropertyMapping();
			if (updatedOnPropMapping != null && sjmSupport.getRecordAuditedOnSupplier() != null) {
				bw.setPropertyValue(updatedOnPropMapping.getPropertyName(),
						sjmSupport.getRecordAuditedOnSupplier().get());
			}
			PropertyMapping createdByPropMapping = tableMapping.getCreatedByPropertyMapping();
			if (createdByPropMapping != null && sjmSupport.getRecordAuditedBySupplier() != null) {
				bw.setPropertyValue(createdByPropMapping.getPropertyName(),
						sjmSupport.getRecordAuditedBySupplier().get());
			}
			PropertyMapping updatedByPropMapping = tableMapping.getUpdatedByPropertyMapping();
			if (updatedByPropMapping != null && sjmSupport.getRecordAuditedBySupplier() != null) {
				bw.setPropertyValue(updatedByPropMapping.getPropertyName(),
						sjmSupport.getRecordAuditedBySupplier().get());
			}
			PropertyMapping versionPropMapping = tableMapping.getVersionPropertyMapping();
			if (versionPropMapping != null) {
				// version property value defaults to 1 on inserts
				bw.setPropertyValue(versionPropMapping.getPropertyName(), 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 = tableMapping.getPropertyMappings().stream().filter(e -> !e.isIdAnnotation())
					.map(e -> e.getColumnName()).toArray(String[]::new);
		} else {
			columns = tableMapping.getPropertyMappings().stream().map(e -> e.getColumnName()).toArray(String[]::new);
		}
		simpleJdbcInsert.usingColumns(columns);
		if (tableMapping.isIdAutoGenerated()) {
			simpleJdbcInsert.usingGeneratedKeyColumns(tableMapping.getIdColumnName());
		}
		return simpleJdbcInsert;
	}

}