DataBatch优化
This commit is contained in:
@@ -1294,6 +1294,18 @@ public abstract class AbstractDataSource extends AbstractService implements Data
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override //entitys不一定是同一表的数据
|
||||||
|
public <T> DataBatch insert(Collection<T> entitys) {
|
||||||
|
for (T t : entitys) {
|
||||||
|
Objects.requireNonNull(t);
|
||||||
|
if (t.getClass().getAnnotation(Entity.class) == null) {
|
||||||
|
throw new SourceException("Entity Class " + t.getClass() + " must be on Annotation @Entity");
|
||||||
|
}
|
||||||
|
this.actions.add(new InsertBatchAction1(t));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override //entitys不一定是同一表的数据
|
@Override //entitys不一定是同一表的数据
|
||||||
public <T> DataBatch delete(T... entitys) {
|
public <T> DataBatch delete(T... entitys) {
|
||||||
for (T t : entitys) {
|
for (T t : entitys) {
|
||||||
@@ -1306,6 +1318,18 @@ public abstract class AbstractDataSource extends AbstractService implements Data
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override //entitys不一定是同一表的数据
|
||||||
|
public <T> DataBatch delete(Collection<T> entitys) {
|
||||||
|
for (T t : entitys) {
|
||||||
|
Objects.requireNonNull(t);
|
||||||
|
if (t.getClass().getAnnotation(Entity.class) == null) {
|
||||||
|
throw new SourceException("Entity Class " + t.getClass() + " must be on Annotation @Entity");
|
||||||
|
}
|
||||||
|
this.actions.add(new DeleteBatchAction1(t));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> DataBatch delete(Class<T> clazz, Serializable... pks) {
|
public <T> DataBatch delete(Class<T> clazz, Serializable... pks) {
|
||||||
Objects.requireNonNull(clazz);
|
Objects.requireNonNull(clazz);
|
||||||
@@ -1349,6 +1373,18 @@ public abstract class AbstractDataSource extends AbstractService implements Data
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override //entitys不一定是同一表的数据
|
||||||
|
public <T> DataBatch update(Collection<T> entitys) {
|
||||||
|
for (T t : entitys) {
|
||||||
|
Objects.requireNonNull(t);
|
||||||
|
if (t.getClass().getAnnotation(Entity.class) == null) {
|
||||||
|
throw new SourceException("Entity Class " + t.getClass() + " must be on Annotation @Entity");
|
||||||
|
}
|
||||||
|
this.actions.add(new UpdateBatchAction1(t));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> DataBatch update(Class<T> clazz, Serializable pk, String column, Serializable value) {
|
public <T> DataBatch update(Class<T> clazz, Serializable pk, String column, Serializable value) {
|
||||||
return update(clazz, pk, ColumnValue.mov(column, value));
|
return update(clazz, pk, ColumnValue.mov(column, value));
|
||||||
|
|||||||
@@ -566,17 +566,14 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected boolean isTableNotExist(EntityInfo info, String sqlCode) {
|
protected boolean isTableNotExist(EntityInfo info, String sqlCode) {
|
||||||
return sqlCode != null && !sqlCode.isEmpty() && tableNotExistSqlstates.contains(';' + sqlCode + ';');
|
return sqlCode != null && !sqlCode.isEmpty() && tableNotExistSqlstates.contains(';' + sqlCode + ';');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected String getTableCopySQL(EntityInfo info, String newTable) {
|
protected String getTableCopySQL(EntityInfo info, String newTable) {
|
||||||
return tablecopySQL.replace("#{newtable}", newTable).replace("#{oldtable}", info.table);
|
return tablecopySQL.replace("#{newtable}", newTable).replace("#{oldtable}", info.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Serializable getSQLAttrValue(EntityInfo info, Attribute attr, Serializable val) {
|
protected <T> Serializable getSQLAttrValue(EntityInfo info, Attribute attr, Serializable val) {
|
||||||
if (val != null && !(val instanceof Number) && !(val instanceof CharSequence) && !(val instanceof java.util.Date)
|
if (val != null && !(val instanceof Number) && !(val instanceof CharSequence) && !(val instanceof java.util.Date)
|
||||||
&& !val.getClass().getName().startsWith("java.sql.") && !val.getClass().getName().startsWith("java.time.")) {
|
&& !val.getClass().getName().startsWith("java.sql.") && !val.getClass().getName().startsWith("java.time.")) {
|
||||||
@@ -587,7 +584,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Map<String, PrepareInfo<T>> getInsertQuestionPrepareInfo(EntityInfo<T> info, T... entitys) {
|
protected <T> Map<String, PrepareInfo<T>> getInsertQuestionPrepareInfo(EntityInfo<T> info, T... entitys) {
|
||||||
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
||||||
for (T entity : entitys) {
|
for (T entity : entitys) {
|
||||||
@@ -597,7 +593,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Map<String, PrepareInfo<T>> getInsertDollarPrepareInfo(EntityInfo<T> info, T... entitys) {
|
protected <T> Map<String, PrepareInfo<T>> getInsertDollarPrepareInfo(EntityInfo<T> info, T... entitys) {
|
||||||
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
||||||
for (T entity : entitys) {
|
for (T entity : entitys) {
|
||||||
@@ -607,7 +602,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Map<String, PrepareInfo<T>> getUpdateQuestionPrepareInfo(EntityInfo<T> info, T... entitys) {
|
protected <T> Map<String, PrepareInfo<T>> getUpdateQuestionPrepareInfo(EntityInfo<T> info, T... entitys) {
|
||||||
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>(); //一定要是LinkedHashMap
|
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>(); //一定要是LinkedHashMap
|
||||||
for (T entity : entitys) {
|
for (T entity : entitys) {
|
||||||
@@ -617,7 +611,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Map<String, PrepareInfo<T>> getUpdateDollarPrepareInfo(EntityInfo<T> info, T... entitys) {
|
protected <T> Map<String, PrepareInfo<T>> getUpdateDollarPrepareInfo(EntityInfo<T> info, T... entitys) {
|
||||||
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
Map<String, PrepareInfo<T>> map = new LinkedHashMap<>();//一定要是LinkedHashMap
|
||||||
for (T entity : entitys) {
|
for (T entity : entitys) {
|
||||||
@@ -627,7 +620,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> Serializable getEntityAttrValue(EntityInfo info, Attribute attr, T entity) {
|
protected <T> Serializable getEntityAttrValue(EntityInfo info, Attribute attr, T entity) {
|
||||||
Serializable val = info.getSQLValue(attr, entity);
|
Serializable val = info.getSQLValue(attr, entity);
|
||||||
Class clazz = attr.type();
|
Class clazz = attr.type();
|
||||||
@@ -648,12 +640,11 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Local
|
|
||||||
public <T> void compile(Class<T> clazz) {
|
public <T> void compile(Class<T> clazz) {
|
||||||
EntityInfo.compile(clazz, this);
|
EntityInfo.compile(clazz, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
public final String dbtype() {
|
public final String dbtype() {
|
||||||
if (dbtype == null) {
|
if (dbtype == null) {
|
||||||
throw new NullPointerException("dbtype is null");
|
throw new NullPointerException("dbtype is null");
|
||||||
@@ -661,12 +652,10 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return dbtype;
|
return dbtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
public final boolean autoddl() {
|
public final boolean autoddl() {
|
||||||
return autoDDL;
|
return autoDDL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//是否异步
|
//是否异步
|
||||||
protected abstract boolean isAsync();
|
protected abstract boolean isAsync();
|
||||||
|
|
||||||
@@ -801,7 +790,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return node == null ? null : node.createSQLExpress(this, info, joinTabalis);
|
return node == null ? null : node.createSQLExpress(this, info, joinTabalis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "sql";
|
return "sql";
|
||||||
@@ -812,13 +800,11 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public EntityInfo apply(Class t) {
|
public EntityInfo apply(Class t) {
|
||||||
return loadEntityInfo(t);
|
return loadEntityInfo(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
}
|
}
|
||||||
@@ -2209,7 +2195,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T, N extends Number> CompletableFuture<Map<String, N>> getNumberMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, FilterFuncColumn... columns) {
|
protected <T, N extends Number> CompletableFuture<Map<String, N>> getNumberMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, FilterFuncColumn... columns) {
|
||||||
return future.thenApply((DataResultSet dataset) -> {
|
return future.thenApply((DataResultSet dataset) -> {
|
||||||
final Map map = new HashMap<>();
|
final Map map = new HashMap<>();
|
||||||
@@ -2285,7 +2270,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> CompletableFuture<Number> getNumberResultDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, Number defVal, String column) {
|
protected <T> CompletableFuture<Number> getNumberResultDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, Number defVal, String column) {
|
||||||
return future.thenApply((DataResultSet dataset) -> {
|
return future.thenApply((DataResultSet dataset) -> {
|
||||||
Number rs = defVal;
|
Number rs = defVal;
|
||||||
@@ -2371,7 +2355,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K, N>> queryColumnMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, final String keyColumn) {
|
protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K, N>> queryColumnMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, final String keyColumn) {
|
||||||
return future.thenApply((DataResultSet dataset) -> {
|
return future.thenApply((DataResultSet dataset) -> {
|
||||||
Map<K, N> rs = new LinkedHashMap<>();
|
Map<K, N> rs = new LinkedHashMap<>();
|
||||||
@@ -2502,7 +2485,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K[], N[]>> queryColumnMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, final ColumnNode[] funcNodes, final String[] groupByColumns) {
|
protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K[], N[]>> queryColumnMapDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, final ColumnNode[] funcNodes, final String[] groupByColumns) {
|
||||||
return future.thenApply((DataResultSet dataset) -> {
|
return future.thenApply((DataResultSet dataset) -> {
|
||||||
Map rs = new LinkedHashMap<>();
|
Map rs = new LinkedHashMap<>();
|
||||||
@@ -2704,7 +2686,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> CompletableFuture<T> findDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk, SelectColumn selects) {
|
protected <T> CompletableFuture<T> findDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk, SelectColumn selects) {
|
||||||
return future.thenApply((DataResultSet pgset) -> {
|
return future.thenApply((DataResultSet pgset) -> {
|
||||||
T rs = pgset.next() ? (onlypk && selects == null ? getEntityValue(info, null, pgset) : getEntityValue(info, selects, pgset)) : null;
|
T rs = pgset.next() ? (onlypk && selects == null ? getEntityValue(info, null, pgset) : getEntityValue(info, selects, pgset)) : null;
|
||||||
@@ -2826,7 +2807,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> CompletableFuture<Serializable> findColumnDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk, String column, Serializable defValue) {
|
protected <T> CompletableFuture<Serializable> findColumnDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk, String column, Serializable defValue) {
|
||||||
return future.thenApply((DataResultSet dataset) -> {
|
return future.thenApply((DataResultSet dataset) -> {
|
||||||
Serializable val = defValue;
|
Serializable val = defValue;
|
||||||
@@ -2954,7 +2934,6 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected <T> CompletableFuture<Boolean> existsDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk) {
|
protected <T> CompletableFuture<Boolean> existsDBApply(EntityInfo<T> info, CompletableFuture<? extends DataResultSet> future, boolean onlypk) {
|
||||||
return future.thenApply((DataResultSet pgset) -> {
|
return future.thenApply((DataResultSet pgset) -> {
|
||||||
boolean rs = pgset.next() ? (((Number) pgset.getObject(1)).intValue() > 0) : false;
|
boolean rs = pgset.next() ? (((Number) pgset.getObject(1)).intValue() > 0) : false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package org.redkale.source;
|
package org.redkale.source;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
import org.redkale.util.SelectColumn;
|
import org.redkale.util.SelectColumn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,8 +15,8 @@ import org.redkale.util.SelectColumn;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
* @since 2.8.0
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public interface DataBatch {
|
public interface DataBatch {
|
||||||
|
|
||||||
public static DataBatch create() {
|
public static DataBatch create() {
|
||||||
@@ -26,8 +27,12 @@ public interface DataBatch {
|
|||||||
|
|
||||||
public <T> DataBatch insert(T... entitys);
|
public <T> DataBatch insert(T... entitys);
|
||||||
|
|
||||||
|
public <T> DataBatch insert(Collection<T> entitys);
|
||||||
|
|
||||||
public <T> DataBatch delete(T... entitys);
|
public <T> DataBatch delete(T... entitys);
|
||||||
|
|
||||||
|
public <T> DataBatch delete(Collection<T> entitys);
|
||||||
|
|
||||||
public <T> DataBatch delete(Class<T> clazz, Serializable... pks);
|
public <T> DataBatch delete(Class<T> clazz, Serializable... pks);
|
||||||
|
|
||||||
public <T> DataBatch delete(Class<T> clazz, FilterNode node);
|
public <T> DataBatch delete(Class<T> clazz, FilterNode node);
|
||||||
@@ -36,6 +41,8 @@ public interface DataBatch {
|
|||||||
|
|
||||||
public <T> DataBatch update(T... entitys);
|
public <T> DataBatch update(T... entitys);
|
||||||
|
|
||||||
|
public <T> DataBatch update(Collection<T> entitys);
|
||||||
|
|
||||||
public <T> DataBatch update(Class<T> clazz, Serializable pk, String column, Serializable value);
|
public <T> DataBatch update(Class<T> clazz, Serializable pk, String column, Serializable value);
|
||||||
|
|
||||||
public <T> DataBatch update(Class<T> clazz, Serializable pk, ColumnValue... values);
|
public <T> DataBatch update(Class<T> clazz, Serializable pk, ColumnValue... values);
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
super.close();
|
super.close();
|
||||||
@@ -112,12 +111,10 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected ConnectionPool readPool() {
|
protected ConnectionPool readPool() {
|
||||||
return readPool;
|
return readPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Local
|
|
||||||
protected ConnectionPool writePool() {
|
protected ConnectionPool writePool() {
|
||||||
return writePool;
|
return writePool;
|
||||||
}
|
}
|
||||||
@@ -491,7 +488,7 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
if (info.getTableStrategy() == null) { //单库单表
|
if (info.getTableStrategy() == null) { //单库单表
|
||||||
conn.offerUpdateStatement(prestmt);
|
conn.offerUpdateStatement(prestmt);
|
||||||
prestmt = prepareInsertEntityStatement(conn, presql, info, entitys);
|
prestmt = prepareInsertEntityStatement(conn, presql, info, entitys);
|
||||||
c = Utility.sum(prestmt.executeBatch());;
|
c = Utility.sum(prestmt.executeBatch());
|
||||||
conn.offerUpdateStatement(prestmt);
|
conn.offerUpdateStatement(prestmt);
|
||||||
} else { //分库分表
|
} else { //分库分表
|
||||||
for (PreparedStatement stmt : prestmts) {
|
for (PreparedStatement stmt : prestmts) {
|
||||||
@@ -2406,7 +2403,6 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
*
|
*
|
||||||
* @return 结果数组
|
* @return 结果数组
|
||||||
*/
|
*/
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public int executeUpdate(String sql) {
|
public int executeUpdate(String sql) {
|
||||||
return executeUpdate(new String[]{sql})[0];
|
return executeUpdate(new String[]{sql})[0];
|
||||||
@@ -2420,7 +2416,6 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
*
|
*
|
||||||
* @return 结果数组
|
* @return 结果数组
|
||||||
*/
|
*/
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public int[] executeUpdate(String... sqls) {
|
public int[] executeUpdate(String... sqls) {
|
||||||
if (sqls.length == 0) {
|
if (sqls.length == 0) {
|
||||||
@@ -2463,7 +2458,6 @@ public class DataJdbcSource extends AbstractDataSqlSource {
|
|||||||
*
|
*
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Local
|
|
||||||
@Override
|
@Override
|
||||||
public <V> V executeQuery(String sql, Function<DataResultSet, V> handler) {
|
public <V> V executeQuery(String sql, Function<DataResultSet, V> handler) {
|
||||||
final long s = System.currentTimeMillis();
|
final long s = System.currentTimeMillis();
|
||||||
|
|||||||
Reference in New Issue
Block a user