Mybatis(十一):通用CRUD


通用CRUD

对于简单的应用来说,大多数数据库操作都是对单表CRUD。使用Mybatis的时候一般通过声明Mapper接口来操作数据库,大量的Mapper声明带来了大量重复工作量,当然可以通过Mybatis插件生成Mapper接口减少工作量,但是对于这种重复的东西,通过封装可能效果更好。这就是通用CRUD的需求来源。

实现方式一

通过Mybatis的Provider特性加上反射构建通用Mapper接口。

@Mapper
public interface BaseMapper<T> {

    @InsertProvider(type = BaseSqlProvider.class, method = "buildInsertSql")
    void insert(T entity);

    @UpdateProvider(type = BaseSqlProvider.class, method = "buildUpdateSql")
    void updateById(T entity);

    @DeleteProvider(type = BaseSqlProvider.class, method = "buildDeleteSql")
    void deleteById(T entity);

    @SelectProvider(type = BaseSqlProvider.class, method = "buildSelectSql")
    T selectById(T entity);

}

Provider只需指定type、method两个属性即可。type是Provider的类,method是返回Sql语句的方法,注意参数和被注解的方法保持一致,方法的返回值是String类型。

通过反射可根据传入的参数类型生成不同的SQL语句。

public class BaseSqlProvider {
    @SneakyThrows
    public static <T> String buildInsertSql(T entity) {
        Class<?> clazz = entity.getClass();
        StringBuilder sql = new StringBuilder("insert into ");
        TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            sql.append(tableNameAnnotation.value());
        } else {
            sql.append(clazz.getSimpleName());
        }
        sql.append(" (");
        Field[] fields = clazz.getDeclaredFields();
        if (fields.length == 0) {
            throw new IllegalStateException("实体类不存在属性");
        }
        String columnNames = Arrays.stream(fields).map(e -> humpToLine(e.getName())).collect(Collectors.joining(","));
        sql.append(columnNames);
        sql.append(") values (");
        for (Field field : fields) {
            if (field.getType().isAssignableFrom(String.class)) {
                sql.append("'");
                field.setAccessible(true);
                sql.append(field.get(entity));
                sql.append("'");
            } else {
                field.setAccessible(true);
                sql.append(field.get(entity));
            }
            sql.append(",");
        }
        sql.deleteCharAt(sql.length() - 1);
        sql.append(")");
        return sql.toString();
    }

    @SneakyThrows
    public static <T> String buildUpdateSql(T entity) {
        Class<?> clazz = entity.getClass();
        StringBuilder sql = new StringBuilder("update ");
        TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            sql.append(tableNameAnnotation.value());
        } else {
            sql.append(clazz.getSimpleName());
        }
        sql.append(" set ");
        Field[] fields = clazz.getDeclaredFields();
        if (fields.length == 0) {
            throw new IllegalStateException("实体类不存在属性");
        }

        Field idField = null;
        TableId tableId = null;
        for (Field field : fields) {
            TableId tableIdAnnotation = field.getAnnotation(TableId.class);
            if (tableIdAnnotation != null) {
                tableId = tableIdAnnotation;
                idField = field;
            }
            TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
            if (tableFieldAnnotation != null) {
                sql.append(tableFieldAnnotation.value());
            } else {
                sql.append(humpToLine(field.getName()));
            }

            sql.append(" = ");
            if (field.getType().isAssignableFrom(String.class)) {
                sql.append("'");
                field.setAccessible(true);
                sql.append(field.get(entity));
                sql.append("'");
            } else {
                field.setAccessible(true);
                sql.append(field.get(entity));
            }
            sql.append(",");
        }
        sql.deleteCharAt(sql.length() - 1);
        sql.append(" where ");

        if (tableId == null) {
            throw new IllegalStateException("缺少主键");
        }
        if (tableId.value().equals("")) {
            sql.append(humpToLine(idField.getName()));
        } else {
            sql.append(tableId.value());
        }
        sql.append(" = ");
        if (idField.getType().isAssignableFrom(String.class)) {
            sql.append("'");
            idField.setAccessible(true);
            sql.append(idField.get(entity));
            sql.append("'");
        } else {
            idField.setAccessible(true);
            sql.append(idField.get(entity));
        }

        return sql.toString();
    }

    @SneakyThrows
    public static <T> String buildDeleteSql(T entity) {
        Class<?> clazz = entity.getClass();
        StringBuilder sql = new StringBuilder("delete from ");
        TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            sql.append(tableNameAnnotation.value());
        } else {
            sql.append(clazz.getSimpleName());
        }
        Field[] fields = clazz.getDeclaredFields();
        if (fields.length == 0) {
            throw new IllegalStateException("实体类不存在属性");
        }
        sql.append(" where ");
        for (Field field : fields) {
            TableId tableIdAnnotation = field.getAnnotation(TableId.class);
            if (tableIdAnnotation != null) {
                if (tableIdAnnotation.value().equals("")) {
                    sql.append(humpToLine(field.getName()));
                } else {
                    sql.append(tableIdAnnotation.value());
                }
                sql.append(" = ");
                if (field.getType().isAssignableFrom(String.class)) {
                    sql.append("'");
                    field.setAccessible(true);
                    sql.append(field.get(entity));
                    sql.append("'");
                } else {
                    field.setAccessible(true);
                    sql.append(field.get(entity));
                }
                break;
            }
        }

        return sql.toString();
    }

    @SneakyThrows
    public static <T> String buildSelectSql(T entity) {
        Class<?> clazz = entity.getClass();
        StringBuilder sql = new StringBuilder("select * from ");
        TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            sql.append(tableNameAnnotation.value());
        } else {
            sql.append(clazz.getSimpleName());
        }
        Field[] fields = clazz.getDeclaredFields();
        sql.append(" where ");
        for (Field field : fields) {
            TableId tableIdAnnotation = field.getAnnotation(TableId.class);
            if (tableIdAnnotation != null) {
                if (tableIdAnnotation.value().equals("")) {
                    sql.append(humpToLine(field.getName()));
                } else {
                    sql.append(tableIdAnnotation.value());
                }
                sql.append(" = ");
                if (field.getType().isAssignableFrom(String.class)) {
                    sql.append("'");
                    field.setAccessible(true);
                    sql.append(field.get(entity));
                    sql.append("'");
                } else {
                    field.setAccessible(true);
                    sql.append(field.get(entity));
                }
                break;
            }
        }
        return sql.toString();
    }

    /**
     * 驼峰转下划线
     */
    public static String humpToLine(String str) {
        return str.replaceAll("[A-Z]", "_$0").toLowerCase();
    }

    private static final Pattern humpPattern = Pattern.compile("[A-Z]");
}

业务的Mapper接口只要继承它,就可以获得单表的CRUD方法,相当方便。

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

虽然这种方式可以实现动态CRUD,但是缺点很明显,每次操作数据库都重新生成SQL语句,性能差!

代码地址:https://github.com/onlyonezhongjinhui/mybatis-learning/tree/main/dynamic_crud

实现方式二

通过扩展Mybatis,在Mybatis启动的过程中注入动态CRUD,也就是给每个Mapper接口添加CRUD的MapperStatement。这种方式很好的解决了方式一的性能问题

代码地址:https://github.com/onlyonezhongjinhui/mybatis-learning/tree/main/dynamic_crud_x


文章作者: maybe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 maybe !