package com.ctrip.platform.dal.dao;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import com.ctrip.platform.dal.common.enums.DatabaseCategory;
import com.ctrip.platform.dal.dao.client.DalWatcher;
import com.ctrip.platform.dal.dao.helper.DalDefaultJpaParser;
import com.ctrip.platform.dal.dao.helper.DalObjectRowMapper;
import com.ctrip.platform.dal.dao.sqlbuilder.BaseTableSelectBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.DeleteSqlBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.FreeUpdateSqlBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.InsertSqlBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.SqlBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.TableSelectBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.TableSqlBuilder;
import com.ctrip.platform.dal.dao.sqlbuilder.UpdateSqlBuilder;
import com.ctrip.platform.dal.dao.task.BulkTask;
import com.ctrip.platform.dal.dao.task.DalBulkTaskRequest;
import com.ctrip.platform.dal.dao.task.DalRequestExecutor;
import com.ctrip.platform.dal.dao.task.DalSingleTaskRequest;
import com.ctrip.platform.dal.dao.task.DalSqlTaskRequest;
import com.ctrip.platform.dal.dao.task.DalTaskFactory;
import com.ctrip.platform.dal.dao.task.DeleteSqlTask;
import com.ctrip.platform.dal.dao.task.QuerySqlTask;
import com.ctrip.platform.dal.dao.task.SingleTask;
import com.ctrip.platform.dal.dao.task.TaskAdapter;
import com.ctrip.platform.dal.dao.task.UpdateSqlTask;
import com.ctrip.platform.dal.exceptions.DalException;
import com.ctrip.platform.dal.exceptions.ErrorCode;
/**
* Base table DAO wraps common CRUD for particular table. The generated table
* DAO should use this DAO to perform CRUD.
* All operations support corss-shard case. Including DB, table or DB + table sharding combination.
*
* @author jhhe
*/
public final class DalTableDao<T> extends TaskAdapter<T> {
public static final String GENERATED_KEY = "GENERATED_KEY";
private SingleTask<T> singleInsertTask;
private SingleTask<T> singleDeleteTask;
private SingleTask<T> singleUpdateTask;
private BulkTask<Integer, T> combinedInsertTask;
private BulkTask<int[], T> batchInsertTask;
private BulkTask<int[], T> batchDeleteTask;
private BulkTask<int[], T> batchUpdateTask;
private DeleteSqlTask<T> deleteSqlTask;
private UpdateSqlTask<T> updateSqlTask;
private DalRequestExecutor executor;
public DalTableDao(DalParser<T> parser) {
this(parser, DalClientFactory.getTaskFactory());
}
public DalTableDao(Class<T> entityType) throws SQLException {
this(new DalDefaultJpaParser<>(entityType));
}
public DalTableDao(Class<T> entityType, String dataBaseName) throws SQLException {
this(new DalDefaultJpaParser<>(entityType, dataBaseName));
}
public DalTableDao(Class<T> entityType, String dataBaseName, String tableName) throws SQLException {
this(new DalDefaultJpaParser<>(entityType, dataBaseName, tableName));
}
public DalTableDao(DalParser<T> parser, DalTaskFactory factory) {
this(parser, factory, new DalRequestExecutor());
}
public DalTableDao(DalParser<T> parser, DalRequestExecutor executor) {
this(parser, DalClientFactory.getTaskFactory(), executor);
}
public DalTableDao(DalParser<T> parser, DalTaskFactory factory, DalRequestExecutor executor) {
initialize(parser);
initTasks(factory);
this.executor = executor;
}
private void initTasks(DalTaskFactory factory){
singleInsertTask = factory.createSingleInsertTask(parser);
singleDeleteTask = factory.createSingleDeleteTask(parser);
singleUpdateTask = factory.createSingleUpdateTask(parser);
combinedInsertTask = factory.createCombinedInsertTask(parser);
batchInsertTask = factory.createBatchInsertTask(parser);
batchDeleteTask = factory.createBatchDeleteTask(parser);
batchUpdateTask = factory.createBatchUpdateTask(parser);
deleteSqlTask = factory.createDeleteSqlTask(parser);
updateSqlTask = factory.createUpdateSqlTask(parser);
}
public DalClient getClient() {
return client;
}
public DatabaseCategory getDatabaseCategory() {
return dbCategory;
}
/**
* Query by Primary key. The key column type should be Integer, Long, etc.
* For table that the primary key is not of Integer type, this method will
* fail.
*
* @param id The primary key in number format
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return entity of this table. Null if no result found.
* @throws SQLException
*/
public T queryByPk(Number id, DalHints hints) throws SQLException {
if (parser.getPrimaryKeyNames().length != 1)
throw new DalException(ErrorCode.ValidatePrimaryKeyCount);
StatementParameters parameters = new StatementParameters();
parameters.set(1, getColumnType(parser.getPrimaryKeyNames()[0]), id);
return queryObject(new BaseTableSelectBuilder(rawTableName, dbCategory).where(pkSql).with(parameters).requireSingle().nullable(), hints);
}
/**
* Query by Primary key, the key columns are pass in the pojo.
*
* @param pk The pojo used to represent primary key(s)
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return entity of this table. Null if no result found.
* @throws SQLException
*/
public T queryByPk(T pk, DalHints hints) throws SQLException {
StatementParameters parameters = new StatementParameters();
addParameters(parameters, parser.getPrimaryKeys(pk));
return queryObject(new BaseTableSelectBuilder(rawTableName, dbCategory).where(pkSql).with(parameters).requireSingle().nullable(), hints.setFields(parser.getFields(pk)));
}
/**
* Query against sample pojo. All not null attributes of the passed in pojo
* will be used as search criteria.
*
* @param sample The pojo used for sampling
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return List of pojos that have the same attributes like in the sample
* @throws SQLException
*/
public List<T> queryLike(T sample, DalHints hints) throws SQLException {
StatementParameters parameters = new StatementParameters();
Map<String, ?> fields = parser.getFields(sample);
Map<String, ?> queryCriteria = filterNullFileds(fields);
addParameters(parameters, queryCriteria);
String whereClause = buildWhereClause(queryCriteria);
return query(whereClause, parameters, hints.setFields(fields));
}
/**
* Query by the given where clause and parameters. The where clause can
* contain value placeholder "?". The parameter should match the index of
* the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return List of pojos that meet the search criteria
* @throws SQLException
*/
public List<T> query(String whereClause, StatementParameters parameters,
DalHints hints) throws SQLException {
return query(new BaseTableSelectBuilder(rawTableName, dbCategory).where(whereClause).with(parameters), hints);
}
/**
* Query by the given where clause and parameters. The where clause can
* contain value placeholder "?". The parameter should match the index of
* the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return List of pojos that meet the search criteria
* @throws SQLException
*/
public List<T> query(TableSelectBuilder selectBuilder, DalHints hints) throws SQLException {
DalWatcher.begin();
return commonQuery((TableSelectBuilder)selectBuilder.mapWith(parser).nullable(), hints);
}
/**
* Query by the given where clause and parameters. The where clause can
* contain value placeholder "?". The parameter should match the index of
* the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param clazz the return type, not the pojo, but simple type
* @return List of pojos that meet the search criteria
* @throws SQLException
*/
public <K> List<K> query(TableSelectBuilder selectBuilder, DalHints hints, Class<K> clazz) throws SQLException {
DalWatcher.begin();
return commonQuery((TableSelectBuilder)selectBuilder.mapWith(new DalObjectRowMapper<>(clazz)).nullable(), hints);
}
/**
* Query the first row of the given where clause and parameters. The where
* clause can contain value placeholder "?". The parameter should match the
* index of the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return Null if no result found.
* @throws SQLException
*/
public T queryFirst(String whereClause, StatementParameters parameters,
DalHints hints) throws SQLException {
return queryObject(new BaseTableSelectBuilder(rawTableName, dbCategory).where(whereClause).with(parameters).requireFirst().nullable(), hints);
}
/**
* Query pojo for the given query builder. The requireSingle or requireFirst MUST be set on bulder.
* @param selectBuilder select builder which represents the query criteria
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return
* @throws SQLException
*/
public T queryObject(TableSelectBuilder selectBuilder, DalHints hints) throws SQLException {
DalWatcher.begin();
return commonQuery((TableSelectBuilder)selectBuilder.mapWith(parser), hints);
}
/**
* Query object for the given type for the given query builder. The requireSingle or requireFirst MUST be set on bulder.
* @param selectBuilder select builder which represents the query criteria
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param clazz the class which the returned result belongs to.
* @return
* @throws SQLException
*/
public <K> K queryObject(TableSelectBuilder selectBuilder, DalHints hints, Class<K> clazz) throws SQLException {
DalWatcher.begin();
return commonQuery((TableSelectBuilder)selectBuilder.mapWith(new DalObjectRowMapper<>(clazz)), hints);
}
public Number count(String whereClause, StatementParameters parameters, DalHints hints) throws SQLException {
return count(new BaseTableSelectBuilder(rawTableName, dbCategory).where(whereClause).with(parameters).selectCount(), hints);
}
//Assume selectCount() is already invoked
public Number count(TableSelectBuilder selectBuilder, DalHints hints) throws SQLException {
DalWatcher.begin();
return commonQuery(selectBuilder, hints);
}
/**
* Query the top rows of the given where clause and parameters. The where
* clause can contain value placeholder "?". The parameter should match the
* index of the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param count
* how may rows to return
* @return The qualified list of pojo
* @throws SQLException
*/
public List<T> queryTop(String whereClause, StatementParameters parameters,
DalHints hints, int count) throws SQLException {
return query(new BaseTableSelectBuilder(rawTableName, dbCategory).where(whereClause).with(parameters).top(count), hints);
}
/**
* Query range of result for the given where clause and parameters. The
* where clause can contain value placeholder "?". The parameter should
* match the index of the placeholder.
*
* @param whereClause the where section for the search statement.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param start
* the start number. It is zero(0) based, means the index is from 0. 1 will be the 2nd row.
* @param count
* how may rows to return
* @return The qualified list of pojo
* @throws SQLException
*/
public List<T> queryFrom(String whereClause,
StatementParameters parameters, DalHints hints, int start, int count)
throws SQLException {
DalWatcher.begin();
return query(new BaseTableSelectBuilder().where(whereClause).with(parameters).range(start, count), hints);
}
private <K> K commonQuery(TableSelectBuilder builder, DalHints hints) throws SQLException {
DalSqlTaskRequest<K> request = new DalSqlTaskRequest<K>(
logicDbName, populate(builder), hints,
new QuerySqlTask<>((DalResultSetExtractor<K>)builder.getResultExtractor(hints)), (ResultMerger<K>)builder.getResultMerger(hints));
return executor.execute(hints, request, builder.isNullable());
}
/**
* Insert pojo and get the generated PK back in keyHolder.
* If the "set no count on" for MS SqlServer is set(currently set in Ctrip), the operation may fail.
* Please don't pass keyholder for MS SqlServer to avoid the failure.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* @param keyHolder
* holder for generated primary keys
* @param daoPojo
* pojo to be inserted
* @return how many rows been affected
* @throws SQLException
*/
public int insert(DalHints hints, T daoPojo)
throws SQLException {
return insert(hints, hints.getKeyHolder(), daoPojo);
}
/**
* Insert pojo and get the generated PK back in keyHolder.
* If the "set no count on" for MS SqlServer is set(currently set in Ctrip), the operation may fail.
* Please don't pass keyholder for MS SqlServer to avoid the failure.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* @param keyHolder
* holder for generated primary keys
* @param daoPojo
* pojo to be inserted
* @return how many rows been affected
* @throws SQLException
*/
public int insert(DalHints hints, KeyHolder keyHolder, T daoPojo)
throws SQLException {
return getSafeResult(executor.execute(setSize(hints, keyHolder, daoPojo), new DalSingleTaskRequest<>(logicDbName, hints, daoPojo, singleInsertTask)));
}
/**
* Insert pojos one by one. If you want to inert them in the batch mode,
* user batchInsert instead. You can also use the combinedInsert.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* DalHintEnum.continueOnError can be used
* to indicate that the inserting can be go on if there is any
* failure.
* @param daoPojos
* list of pojos to be inserted
* @return how many rows been affected
*/
public int[] insert(DalHints hints, List<T> daoPojos) throws SQLException {
return insert(hints, hints.getKeyHolder(), daoPojos);
}
/**
* Insert pojos and get the generated PK back in keyHolder.
* If the "set no count on" for MS SqlServer is set(currently set in Ctrip), the operation may fail.
* Please don't pass keyholder for MS SqlServer to avoid the failure.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* DalHintEnum.continueOnError can be used
* to indicate that the inserting can be go on if there is any
* failure.
* @param keyHolder
* holder for generated primary keys
* @param daoPojos
* list of pojos to be inserted
* @return how many rows been affected
* @throws SQLException
*/
public int[] insert(DalHints hints, KeyHolder keyHolder, List<T> daoPojos)
throws SQLException {
return executor.execute(setSize(hints, keyHolder, daoPojos), new DalSingleTaskRequest<>(logicDbName, hints, daoPojos, singleInsertTask));
}
/**
* Insert multiple pojos in one INSERT SQL and get the generated PK back in keyHolder.
* If the "set no count on" for MS SqlServer is set(currently set in Ctrip), the operation may fail.
* Please don't pass keyholder for MS SqlServer to avoid the failure.
* The DalDetailResults will be set in hints to allow client know how the operation performed in each of the shard.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param keyHolder holder for generated primary keys
* @param daoPojos list of pojos to be inserted
* @return how many rows been affected
* @throws SQLException
*/
public int combinedInsert(DalHints hints, List<T> daoPojos)
throws SQLException {
return combinedInsert(hints, hints.getKeyHolder(), daoPojos);
}
/**
* Insert multiple pojos in one INSERT SQL and get the generated PK back in keyHolder.
* If the "set no count on" for MS SqlServer is set(currently set in Ctrip), the operation may fail.
* Please don't pass keyholder for MS SqlServer to avoid the failure.
* The DalDetailResults will be set in hints to allow client know how the operation performed in each of the shard.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param keyHolder holder for generated primary keys
* @param daoPojos list of pojos to be inserted
* @return how many rows been affected
* @throws SQLException
*/
public int combinedInsert(DalHints hints, KeyHolder keyHolder, List<T> daoPojos)
throws SQLException {
return getSafeResult(executor.execute(setSize(hints, keyHolder, daoPojos), new DalBulkTaskRequest<>(logicDbName, rawTableName, hints, daoPojos, combinedInsertTask)));
}
/**
* Insert pojos in batch mode.
* The DalDetailResults will be set in hints to allow client know how the operation performed in each of the shard.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param daoPojos list of pojos to be inserted
* @return how many rows been affected for inserting each of the pojo
* @throws SQLException
*/
public int[] batchInsert(DalHints hints, List<T> daoPojos) throws SQLException {
return executor.execute(hints, new DalBulkTaskRequest<>(logicDbName, rawTableName, hints, daoPojos, batchInsertTask));
}
/**
* Insert with InsertSqlBuilder.
* @param insertBuilder sql builder that represents the insert operation
* @param hints
* @return how many rows been affected for inserting each of the pojo
* @throws SQLException
*/
public int insert(InsertSqlBuilder insertBuilder, DalHints hints) throws SQLException {
return getSafeResult(executor.execute(hints, new DalSqlTaskRequest<>(logicDbName, populate(insertBuilder), hints, updateSqlTask, new ResultMerger.IntSummary())));
}
/**
* Delete the given pojo.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param daoPojo pojo to be deleted
* @return how many rows been affected
* @throws SQLException
*/
public int delete(DalHints hints, T daoPojo) throws SQLException {
return getSafeResult(getSafeResult(executor.execute(hints, new DalSingleTaskRequest<>(logicDbName, hints, daoPojo, singleDeleteTask))));
}
/**
* Delete the given pojos list one by one.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param daoPojos list of pojos to be deleted
* @return how many rows been affected
* @throws SQLException
*/
public int[] delete(DalHints hints, List<T> daoPojos) throws SQLException {
return executor.execute(hints, new DalSingleTaskRequest<>(logicDbName, hints, daoPojos, singleDeleteTask));
}
/**
* Delete the given pojo list in batch.
* The DalDetailResults will be set in hints to allow client know how the operation performed in each of the shard.
*
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @param daoPojos list of pojos to be deleted
* @return how many rows been affected for deleting each of the pojo
* @throws SQLException
*/
public int[] batchDelete(DalHints hints, List<T> daoPojos) throws SQLException {
return executor.execute(hints, new DalBulkTaskRequest<>(logicDbName, rawTableName, hints, daoPojos, batchDeleteTask));
}
/**
* Update the given pojo . By default, if a field of pojo is null value,
* that field will be ignored, so that it will not be updated. You can
* overwrite this by set updateNullField in hints.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* DalHintEnum.updateNullField can be used
* to indicate that the field of pojo is null value will be update.
* @param daoPojo pojo to be updated
* @return how many rows been affected
* @throws SQLException
*/
public int update(DalHints hints, T daoPojo) throws SQLException {
return getSafeResult(executor.execute(hints, new DalSingleTaskRequest<>(logicDbName, hints, daoPojo, singleUpdateTask)));
}
/**
* Update the given pojo list one by one. By default, if a field of pojo is null value,
* that field will be ignored, so that it will not be updated. You can
* overwrite this by set updateNullField in hints.
*
* @param hints
* Additional parameters that instruct how DAL Client perform database operation.
* DalHintEnum.updateNullField can be used
* to indicate that the field of pojo is null value will be update.
* @param daoPojos list of pojos to be updated
* @return how many rows been affected
* @throws SQLException
*/
public int[] update(DalHints hints, List<T> daoPojos) throws SQLException {
return executor.execute(hints, new DalSingleTaskRequest<>(logicDbName, hints, daoPojos, singleUpdateTask));
}
public int[] batchUpdate(DalHints hints, List<T> daoPojos) throws SQLException {
return executor.execute(hints, new DalBulkTaskRequest<>(logicDbName, rawTableName, hints, daoPojos, batchUpdateTask));
}
/**
* Delete for the given where clause and parameters.
*
* @param whereClause the condition specified for delete operation
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return how many rows been affected
* @throws SQLException
*/
public int delete(String whereClause, StatementParameters parameters,
DalHints hints) throws SQLException {
return delete(new DeleteSqlBuilder().where(whereClause).with(parameters), hints);
}
/**
* Delete for the given delete sql builder.
*
* @param deleteBuilder the builder represents delete sql
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return how many rows been affected
* @throws SQLException
*/
public int delete(DeleteSqlBuilder deleteBuilder, DalHints hints) throws SQLException {
return getSafeResult(executor.execute(hints, new DalSqlTaskRequest<>(logicDbName, populate(deleteBuilder), hints, deleteSqlTask, new ResultMerger.IntSummary())));
}
/**
* Update for the given sql and parameters. The sql must be the standard update statement.
* E.g. "UPDATE ABC SET ....". Because it is the raw sql, table shard will not be supported
* if the table name is logic one, you can provide real table name in sql if you want to update
* certain phisical table.
*
* @param sql the statement that used to update the db.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return how many rows been affected
* @throws SQLException
*/
public int update(String sql, StatementParameters parameters, DalHints hints)
throws SQLException {
return getSafeResult(executor.execute(hints, new DalSqlTaskRequest<>(logicDbName, new FreeUpdateSqlBuilder(dbCategory).setTemplate(sql).with(parameters), hints, updateSqlTask, new ResultMerger.IntSummary())));
}
/**
* Update for the given UpdateSqlBuilder and parameters.
*
* @param updateBuilder the builder that used to update the db.
* @param parameters A container that holds all the necessary parameters
* @param hints Additional parameters that instruct how DAL Client perform database operation.
* @return how many rows been affected
* @throws SQLException
*/
public int update(UpdateSqlBuilder updateBuilder, DalHints hints)
throws SQLException {
return getSafeResult(executor.execute(hints, new DalSqlTaskRequest<>(logicDbName, populate(updateBuilder), hints, updateSqlTask, new ResultMerger.IntSummary())));
}
private SqlBuilder populate(TableSqlBuilder builder) throws SQLException {
builder.from(rawTableName).setDatabaseCategory(dbCategory);
return builder;
}
private int getSafeResult(Integer value) {
if(value == null)
return 0;
return value;
}
private int getSafeResult(int[] counts) {
if(counts == null)
return 0;
return counts[0];
}
private DalHints setSize(DalHints hints, KeyHolder keyHolder, List<T> pojos) {
if(keyHolder != null && pojos != null)
keyHolder.setSize(pojos.size());
return hints.setKeyHolder(keyHolder);
}
private DalHints setSize(DalHints hints, KeyHolder keyHolder, T pojo) {
if(keyHolder != null && pojo != null)
keyHolder.setSize(1);
return hints.setKeyHolder(keyHolder);
}
}