package core.framework.impl.db;
import core.framework.api.db.Query;
import core.framework.api.db.Repository;
import core.framework.api.log.ActionLogContext;
import core.framework.api.log.Markers;
import core.framework.api.util.Lists;
import core.framework.api.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author neo
*/
public final class RepositoryImpl<T> implements Repository<T> {
private final Logger logger = LoggerFactory.getLogger(RepositoryImpl.class);
private final DatabaseImpl database;
private final RepositoryEntityValidator<T> validator;
private final SelectQuery selectQuery;
private final InsertQuery<T> insertQuery;
private final UpdateQuery<T> updateQuery;
private final String deleteSQL;
private final RowMapper<T> rowMapper;
RepositoryImpl(DatabaseImpl database, Class<T> entityClass, RowMapper<T> rowMapper) {
this.database = database;
validator = new RepositoryEntityValidator<>(entityClass);
insertQuery = new InsertQuery<>(entityClass);
selectQuery = new SelectQuery(entityClass);
updateQuery = new UpdateQuery<>(entityClass);
deleteSQL = DeleteQueryBuilder.build(entityClass);
this.rowMapper = rowMapper;
}
@Override
public List<T> select(Query query) {
StopWatch watch = new StopWatch();
String sql = selectQuery.sql(query.where, query.skip, query.limit);
Object[] params = selectQuery.params(query);
try {
List<T> results = database.operation.select(sql, rowMapper, params);
checkTooManyRowsReturned(results.size());
return results;
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("select, sql={}, params={}, elapsedTime={}", sql, params, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public Optional<T> selectOne(String where, Object... params) {
StopWatch watch = new StopWatch();
String sql = selectQuery.sql(where, null, null);
try {
return database.operation.selectOne(sql, rowMapper, params);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("selectOne, sql={}, params={}, elapsedTime={}", sql, params, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public Optional<T> get(Object... primaryKeys) {
StopWatch watch = new StopWatch();
String sql = selectQuery.selectByPrimaryKeys;
try {
return database.operation.selectOne(sql, rowMapper, primaryKeys);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("get, sql={}, params={}, elapsedTime={}", sql, primaryKeys, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public Optional<Long> insert(T entity) {
StopWatch watch = new StopWatch();
validator.validate(entity);
String sql = insertQuery.sql;
Object[] params = insertQuery.params(entity);
try {
return database.operation.insert(sql, params);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("insert, sql={}, params={}, elapsedTime={}", sql, params, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public void update(T entity) {
StopWatch watch = new StopWatch();
validator.partialValidate(entity);
UpdateQuery.Query query = updateQuery.query(entity);
try {
int updatedRows = database.operation.update(query.sql, query.params);
if (updatedRows != 1)
logger.warn(Markers.errorCode("UNEXPECTED_UPDATE_RESULT"), "updated rows is not 1, rows={}", updatedRows);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("update, sql={}, params={}, elapsedTime={}", query.sql, query.params, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public void delete(Object... primaryKeys) {
StopWatch watch = new StopWatch();
try {
int deletedRows = database.operation.update(deleteSQL, primaryKeys);
if (deletedRows != 1)
logger.warn(Markers.errorCode("UNEXPECTED_UPDATE_RESULT"), "deleted rows is not 1, rows={}", deletedRows);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("delete, sql={}, params={}, elapsedTime={}", deleteSQL, primaryKeys, elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public void batchInsert(List<T> entities) {
StopWatch watch = new StopWatch();
entities.forEach(validator::validate);
String sql = insertQuery.sql;
List<Object[]> params = entities.stream()
.map(insertQuery::params)
.collect(Collectors.toList());
try {
database.operation.batchUpdate(sql, params);
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("batch insert, sql={}, size={}, elapsedTime={}", sql, entities.size(), elapsedTime);
checkSlowOperation(elapsedTime);
}
}
@Override
public void batchDelete(List<?> primaryKeys) {
StopWatch watch = new StopWatch();
List<Object[]> params = Lists.newArrayList();
for (Object primaryKey : primaryKeys) {
if (primaryKey instanceof Object[]) {
params.add((Object[]) primaryKey);
} else {
params.add(new Object[]{primaryKey});
}
}
try {
int[] deletedRows = database.operation.batchUpdate(deleteSQL, params);
for (int deletedRow : deletedRows) {
if (deletedRow != 1) {
logger.warn(Markers.errorCode("UNEXPECTED_UPDATE_RESULT"), "deleted rows is not 1, rows={}", Arrays.toString(deletedRows));
break;
}
}
} finally {
long elapsedTime = watch.elapsedTime();
ActionLogContext.track("db", elapsedTime);
logger.debug("delete, sql={}, size={}, elapsedTime={}", deleteSQL, primaryKeys.size(), elapsedTime);
checkSlowOperation(elapsedTime);
}
}
private void checkTooManyRowsReturned(int size) {
if (size > database.tooManyRowsReturnedThreshold) {
logger.warn(Markers.errorCode("TOO_MANY_ROWS_RETURNED"), "too many rows returned, returnedRows={}", size);
}
}
private void checkSlowOperation(long elapsedTime) {
if (elapsedTime > database.slowOperationThresholdInNanos) {
logger.warn(Markers.errorCode("SLOW_DB"), "slow db operation, elapsedTime={}", elapsedTime);
}
}
}