开发一个MyBatis通用Mapper的轮子

news2025/1/25 7:31:17

目录

一、前言

二、需求

三、实现原理

1、基于MyBatis3提供的SqlProvider构建动态Sql

2、基于自定义注解,为实体和数据库表建立对应关系

四、代码实现

1、自定义注解

2、几个pojo,用来保存实体对应的信息

3、定义开头说的BaseMapper

4、SqlProvider

5、实体类转TableInfo

6、字典字段自动翻译

7、数据权限

五、使用示例

1、数据库表

2、实体

3、Mapper

4、联合主键示例

六、总结


一、前言

程序猿为什么如此执着于造轮子?MyBatis-Plus如此强大的工具流行这么多年了,我为啥还在重复造这样的轮子?

1、公司的技术规范不允许使用MyBatis-Plus,咱也不知道什么原因;

3、以前使用SpringDataJpa惯了,今年第一次用MyBatis,必须把它打造成我想要的样子;

6、MyBatis-Plus好像不支持联合主键;

7、还有一些其它的需求,比如对字典字段自动翻译:字典可能来自枚举、字典表、Redis......

10、通用数据权限控制;

11、如果不造此轮子,就没有这篇文章。

以上12点原因,便是造这个轮子的理由。实际上,轮子不重要,重要的是掌握轮子的原理,取其精华,去其糟粕。也欢迎大家拍砖,请轻拍,数学能力被谁拍坏了谁来陪。

二、需求

通用Mapper起码应该包含以下功能:

1、增

2、删

3、改

4、批量增

5、批量删

6、只更新指定字段

7、分页查询查当前页

8、分页查询查总数

9、字典字段翻译

10、数据权限控制

大概长下面这个样子:

public interface BaseMapper<T,K> {

    int insert(T t);

    int batchInsert(List<T> entity);
    
    int deleteById(K id);
    
    int deleteBatchIds(Collection<K> ids);
    
    int updateById(T entity);
    
    int updateSelectiveById(T entity);
    
    T selectById(K id);
    
    List<T> selectBatchIds(Collection<K> ids);
    
    List<T> selectAll();
    
    List<T> selectPage(PageRequest<T> pageRequest);
    
    Long selectCount(T entity);

}

三、实现原理

1、基于MyBatis3提供的SqlProvider构建动态Sql

例如如下代码:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);

class UserSqlBuilder {
  public static String buildGetUsersByName(final String name) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      if (name != null) {
        WHERE("name like #{value} || '%'");
      }
      ORDER_BY("id");
    }}.toString();
  }
}

2、基于自定义注解,为实体和数据库表建立对应关系

例如如下代码:

@Table("user")
public class User {
    @Id(auto = true)
    @Column(value = "id")
    private Long id;

    @Column(value = "name", filterOperator = FilterOperator.LIKE)
    @OrderBy(orderPriority = 0)
    private String name;

    @OrderBy(order = Order.DESC, orderPriority = 1)
    private Integer age;

    private String email;

    @Transient
    private String test;
}

基于以上两个原理,当方法被调用时,我们便可构建出相应的动态Sql,从而实现该通用Mapper。

四、代码实现

1、自定义注解

1)@Table

了解Jpa的朋友一定很熟悉,这个就是为实体指定表名,实体不加这个注解就认为实体名与表名一致:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    //表名,不指定则使用实体类名
    String value() default "";
}

2)@Column

指定完表名,该指定列名了,同样的如果字段不指定则认为字段名与表列名一致:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    //对应数据库列名
    String value() default "";
    //查询时的过滤类型
    FilterOperator filterOperator() default FilterOperator.EQ;
    //是否查询,select是否带上该字段
    boolean selectable() default true;
    //是否插入,insert是否带上该字段
    boolean insertable() default true;
    //是否更新,update是否带上该字段
    boolean updatable() default true;
}

3)@Id

这个注解就是为了表明该字段是否是数据库主键。当然,这个注解可以与@Column合并,但为了更清晰,我还是决定单独使用这个注解。并且,也方便后期扩展。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
    //主键是否自动生成
    boolean auto() default false;
}

4)@OrderBy

这个注解来标明查询时的排序字段,同时考虑如果排序字段有多个,可定义优先级:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface OrderBy {
    //排序
    Order order() default Order.ASC;
    //多个排序字段先后顺序
    int orderPriority() default 0;
}

5)@Transient

考虑实体中有些字段在数据库中不存在的情况。使用这个注解来标注这样的字段:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Transient {
}

2、几个pojo,用来保存实体对应的信息

1)TableInfo,表示一个实体对应的数据库表信息

public class TableInfo {
    //表对应的实体类型
    private Class<?> entityClass;

    //表名
    private String tableName;

    //列
    private List<ColumnInfo> columns;

    //是否联合主键
    private boolean isUnionId;
}

2)ColumnInfo,表示实体中的一个字段对应的数据库表字段信息

public class ColumnInfo {
    //对应的java类型
    private Class<?> fieldClass;
    private Field field;
    private FilterOperator filterOperator;
    //数据库列
    private String column;
    //是否主键
    private boolean isPrimaryKey;
    //主键填充方式
    private boolean isPrimaryKeyAuto;
    //排序
    private Order orderBy;
    private int orderByPriority;
    //是否参与insert
    private boolean insertable;
    //是否参与update
    private boolean updatable;
    //是否参与select
    private boolean selectable;
}

以上只需要注意一点,如何判断一个实体是否是联合主键。这里用的比较粗暴的方法,如果有多个字段加了@Id,那么认为是联合主键。

3、定义开头说的BaseMapper

这个BaseMapper的定义模仿了SpringDataJpa,它需要两个泛型,T表示实体类型,K表示主键类型。

一般情况下K为简单数据类型,比如Long,String;

联合主键情况下,K为自定义的一个复杂数据类型,具体使用方法见文章最后章节。

public interface BaseMapper<T,K> {

    @InsertProvider(type = SqlProvider.class,method = "insert")
    @Options(useGeneratedKeys = true, keyProperty = "id",keyColumn = "id")
    int insert(T t);

    @InsertProvider(type = SqlProvider.class,method = "batchInsert")
    int batchInsert(@Param("list") List<T> entity);

    @DeleteProvider(type = SqlProvider.class,method = "deleteById")
    int deleteById(@Param("id") K id);

    @DeleteProvider(type = SqlProvider.class,method = "deleteBatchIds")
    int deleteBatchIds(@Param("ids") Collection<K> ids);

    @UpdateProvider(type = SqlProvider.class,method = "updateById")
    int updateById(T entity);

    @UpdateProvider(type = SqlProvider.class,method = "updateSelectiveById")
    int updateSelectiveById(T entity);

    @SelectProvider(type = SqlProvider.class,method = "selectById")
    T selectById(@Param("id") K id);

    @SelectProvider(type = SqlProvider.class,method = "selectBatchIds")
    List<T> selectBatchIds(@Param("ids") Collection<K> ids);

    @SelectProvider(type = SqlProvider.class,method = "selectAll")
    List<T> selectAll();

    @SelectProvider(type = SqlProvider.class,method = "selectPage")
    List<T> selectPage(PageRequest<T> pageRequest);

    @SelectProvider(type = SqlProvider.class,method = "selectCount")
    Long selectCount(T entity);

}

4、SqlProvider

public class SqlProvider<T> {
    private static Logger logger = LoggerFactory.getLogger(SqlProvider.class);
    private static Map<Class<?>, TableInfo> tableCache = new ConcurrentHashMap<>();

    public String insert(T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        String intoColumns = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isInsertable)
                .map(ColumnInfo::getColumn)
                .collect(Collectors.joining(","));
        String values = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isInsertable)
                .map(ColumnInfo::variable)
                .collect(Collectors.joining(","));
        String sql = new SQL()
                .INSERT_INTO(tableName)
                .INTO_COLUMNS(intoColumns)
                .INTO_VALUES(values).toString();
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String batchInsert(@Param("list" ) List<?> entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        String intoColumns = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isInsertable)
                .map(ColumnInfo::getColumn)
                .collect(Collectors.joining(","));
        String values = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isInsertable)
                .map(column->column.variableWithPrefix("item"))
                .collect(Collectors.joining(","));
        String sql = new SQL()
                .INSERT_INTO(tableName)
                .INTO_COLUMNS(intoColumns).toString();
        sql += " values ";
        sql += "<foreach collection=\"list\" item=\"item\" separator=\",\">" +
                "  <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">" +
                "    " + values +
                "  </trim>" +
                "</foreach>";
        sql = "<script>"+sql+"</script>";
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String deleteById(@Param("id") T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        String[] where = null;
        if (tableInfo.isUnionId()){
            where = tableInfo.getColumns()
                    .stream()
                    .filter(ColumnInfo::isPrimaryKey)
                    .map(columnInfo -> columnInfo.getColumn()+" = #{id."+columnInfo.getField().getName()+"}")
                    .toArray(String[]::new);
        }else {
            where = tableInfo.getColumns()
                    .stream()
                    .filter(ColumnInfo::isPrimaryKey)
                    .map(columnInfo -> columnInfo.getColumn()+" = #{id}")
                    .toArray(String[]::new);
        }
        String sql = new SQL()
                .DELETE_FROM(tableName)
                .WHERE(where)
                .toString();
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String deleteBatchIds(@Param("ids") Collection<?> entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        if (tableInfo.isUnionId()){
            String[] where = new String[entity.size()];
            for (int i = 0; i < entity.size(); i++){
                List<String> list = new ArrayList<>();
                String s = "%s=#{ids[%d].%s}";
                for (ColumnInfo columnInfo:tableInfo.getColumns()){
                    if (columnInfo.isPrimaryKey()){
                        list.add(String.format(s,columnInfo.getColumn(),i,columnInfo.getField().getName()));
                    }
                }
                where[i] = "("+StringUtils.join(list," and ")+")";
            }
            String sql = "delete from %s where %s ";
            sql = String.format(sql,tableName,StringUtils.join(where," or "));
            logger.info("sql->{},params->{}",sql,entity);
            return sql;
        }else {
            String idName = tableInfo.getColumns()
                    .stream()
                    .filter(ColumnInfo::isPrimaryKey)
                    .findFirst()
                    .get()
                    .getColumn();
            String sql = "DELETE FROM %s WHERE %s IN (%s) ";
            String[] arr = new String[entity.size()];
            for (int i = 0; i < entity.size(); i++){
                arr[i] = "#{ids["+i+"]}";
            }
            sql = String.format(sql,tableName,idName,StringUtils.join(arr,","));
            logger.info("sql->{},params->{}",sql,entity);
            return sql;
        }
    }

    public String updateById(T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        String[] where = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isPrimaryKey)
                .map(columnInfo -> columnInfo.getColumn()+" = "+columnInfo.variable())
                .toArray(String[]::new);
        String sql = new SQL().UPDATE(tableName).SET(tableInfo.updateSetColumn()).WHERE(where).toString();
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String updateSelectiveById(T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        String[] where = tableInfo.getColumns()
                .stream()
                .filter(ColumnInfo::isPrimaryKey)
                .map(columnInfo -> columnInfo.getColumn()+" = "+columnInfo.variable())
                .toArray(String[]::new);
        String sql = new SQL().UPDATE(tableName).SET(tableInfo.updateSetSelectiveColumn(entity)).WHERE(where).toString();
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String selectById(@Param("id")T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String[] where = null;
        if (tableInfo.isUnionId()){
            where = tableInfo.getColumns().stream().filter(ColumnInfo::isPrimaryKey)
                    .map(columnInfo -> columnInfo.getColumn()+" = #{id."+columnInfo.getField().getName()+"}")
                    .toArray(String[]::new);
        }else {
            where = tableInfo.getColumns().stream().filter(ColumnInfo::isPrimaryKey)
                    .map(columnInfo -> columnInfo.getColumn()+" = #{id}")
                    .toArray(String[]::new);
        }
        String sql = new SQL()
                .SELECT(tableInfo.selectColumnAsProperty())
                .FROM(tableInfo.getTableName())
                .WHERE(where)
                .toString();
        logger.info("sql->{},params->{}",sql,entity);
        return sql;
    }

    public String selectBatchIds(@Param("ids")Collection<?> entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        String tableName = tableInfo.getTableName();
        if (tableInfo.isUnionId()){
            String[] where = new String[entity.size()];
            for (int i = 0; i < entity.size(); i++){
                List<String> list = new ArrayList<>();
                String s = "%s=#{ids[%d].%s}";
                for (ColumnInfo columnInfo:tableInfo.getColumns()){
                    if (columnInfo.isPrimaryKey()){
                        list.add(String.format(s,columnInfo.getColumn(),i,columnInfo.getField().getName()));
                    }
                }
                where[i] = "("+StringUtils.join(list," and ")+")";
            }
            String sql = "select %s from %s where %s";
            sql = String.format(sql,tableInfo.selectColumnAsProperty(),tableInfo.getTableName(),StringUtils.join(where," or "));
            logger.info("sql->{},params->{}",sql,entity);
            return sql;
        }else {
            String idName = tableInfo.getColumns()
                    .stream()
                    .filter(ColumnInfo::isPrimaryKey)
                    .findFirst()
                    .get()
                    .getColumn();
            String sql = "select %s from %s where %s in (%s) ";
            String[] arr = new String[entity.size()];
            for (int i = 0; i < entity.size(); i++){
                arr[i] = "#{ids["+i+"]}";
            }
            sql = String.format(sql,tableInfo.selectColumnAsProperty(),tableName,idName,StringUtils.join(arr,","));
            logger.info("sql->{},params->{}",sql,entity);
            return sql;
        }
    }

    public String selectAll(T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        SQL sql =  new SQL()
                .SELECT(tableInfo.selectColumnAsProperty())
                .FROM(tableInfo.getTableName());
        String orderBy = tableInfo.orderByColumn();
        if (StringUtils.isNotEmpty(orderBy)){
            sql.ORDER_BY(orderBy);
        }
        return sql.toString();
    }

    public String selectPage(PageRequest<T> entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        SQL sql = new SQL()
                .SELECT(tableInfo.selectColumnAsProperty())
                .FROM(tableInfo.getTableName());
        String[] where = tableInfo.getColumns().stream()
                .filter(column -> {
                    Field field = column.getField();
                    T bean = entity.getPageParams();
                    Object value = Util.getFieldValue(bean, field);
                    if (value == null) {
                        return false;
                    }
                    return StringUtils.isNotEmpty(value.toString());
                })
                .map(column -> {
                    String param = " #{pageParams." + column.getField().getName()+"}";
                    if (column.getFilterOperator() == FilterOperator.LIKE){
                        param = "concat('%', "+param+", '%')";
                    }
                    if (column.getFilterOperator() == FilterOperator.LEFTLIKE){
                        param = "concat("+param+", '%')";
                    }
                    if (column.getFilterOperator() == FilterOperator.RIGHTLIKE){
                        param = "concat('%', "+param+")";
                    }
                    return column.getColumn()+column.filterOperator()+param;
                })
                .toArray(String[]::new);
        sql.WHERE(where);
        if (StringUtils.isNotEmpty(entity.getOrder())){
            ColumnInfo columnInfo = tableInfo.getColumns().stream()
                    .filter(columnInfo1 -> columnInfo1.getField().getName().equalsIgnoreCase(entity.getOrder()))
                    .findFirst().orElse(null);
            if (columnInfo != null){
                String direction = entity.getOrderDirection();
                direction = (StringUtils.isEmpty(direction) || direction.equalsIgnoreCase("asc"))?" asc ":" desc ";
                sql.ORDER_BY(columnInfo.getColumn() + direction);
            }
        }else {
            String orderBy = tableInfo.orderByColumn();
            if (StringUtils.isNotEmpty(orderBy)){
                sql.ORDER_BY(orderBy);
            }
        }
        sql.OFFSET("#{offset}").LIMIT("#{pageSize}");
        String s = sql.toString();
        logger.info("sql->{},params->{}",s,entity);
        return s;
    }

    public String selectCount(T entity, ProviderContext context){
        TableInfo tableInfo = getTableInfo(context);
        SQL sql = new SQL()
                .SELECT("count(1)")
                .FROM(tableInfo.getTableName());
        String[] where = tableInfo.getColumns().stream()
                .filter(column -> {
                    Field field = column.getField();
                    Object value = Util.getFieldValue(entity, field);
                    if (value == null) {
                        return false;
                    }
                    return StringUtils.isNotEmpty(value.toString());
                })
                .map(column -> {
                    String param = " #{" + column.getField().getName()+"}";
                    if (column.getFilterOperator() == FilterOperator.LIKE){
                        param = "concat('%', "+param+", '%')";
                    }
                    if (column.getFilterOperator() == FilterOperator.LEFTLIKE){
                        param = "concat("+param+", '%')";
                    }
                    if (column.getFilterOperator() == FilterOperator.RIGHTLIKE){
                        param = "concat('%', "+param+")";
                    }
                    return column.getColumn()+column.filterOperator()+param;
                })
                .toArray(String[]::new);
        sql.WHERE(where);
        String s = sql.toString();
        logger.info("sql->{},params->{}",s,entity);
        return s;
    }

    private TableInfo getTableInfo(ProviderContext context){
        Class<?> clz = getEntityType(context);
        return tableCache.computeIfAbsent(context.getMapperType(), t-> Util.tableInfo(clz));
    }

    private Class<?> getEntityType(ProviderContext context) {
        return Stream.of(context.getMapperType().getGenericInterfaces())
                .filter(ParameterizedType.class::isInstance)
                .map(ParameterizedType.class::cast)
                .filter(type -> type.getRawType() == BaseMapper.class)
                .findFirst()
                .map(type -> type.getActualTypeArguments()[0])
                .filter(Class.class::isInstance)
                .map(Class.class::cast)
                .orElseThrow(() -> new IllegalStateException("未找到BaseMapper的泛型类 " + context.getMapperType().getName() + "."));
    }


}

5、实体类转TableInfo

public static TableInfo tableInfo(Class<?> entityClass) {
        TableInfo info = new TableInfo();
        info.setEntityClass(entityClass);
        Table table = entityClass.getAnnotation(Table.class);
        String tableName = entityClass.getSimpleName();
        if (table != null && StringUtils.isNotEmpty(table.value())){
            tableName = table.value();
        }
        info.setTableName(tableName);
        Field[] allFields = getFields(entityClass);
        Field[] fields = Stream.of(allFields)
                //过滤@Transient注解的field
                .filter(field -> !field.isAnnotationPresent(Transient.class))
                .toArray(Field[]::new);
        List<ColumnInfo> columns = new ArrayList<>();
        int idCount = 0;
        for (Field field:fields){
            ColumnInfo columnInfo = new ColumnInfo();
            columnInfo.setFieldClass(field.getDeclaringClass());
            columnInfo.setField(field);
            Id id = field.getAnnotation(Id.class);
            idCount = idCount + (id == null?0:1);
            columnInfo.setPrimaryKey(id == null?Boolean.FALSE:Boolean.TRUE);
            columnInfo.setPrimaryKeyAuto(id == null?Boolean.FALSE:id.auto());
            Column column = field.getAnnotation(Column.class);
            String columnName = field.getName();
            if (column != null && StringUtils.isNotEmpty(column.value())){
                columnName = column.value();
            }
            columnInfo.setColumn(columnName);
            FilterOperator filterOperator = FilterOperator.EQ;
            if (column != null && column.filterOperator() != null){
                filterOperator = column.filterOperator();
            }
            columnInfo.setFilterOperator(filterOperator);

            if (columnInfo.isPrimaryKeyAuto()){
                columnInfo.setInsertable(false);
            }else {
                columnInfo.setInsertable(true);
                if (column != null){
                    columnInfo.setInsertable(column.insertable());
                }
            }
            columnInfo.setUpdatable(true);
            columnInfo.setSelectable(true);
            if (column != null){
                columnInfo.setSelectable(column.selectable());
                columnInfo.setUpdatable(column.updatable());
            }
            OrderBy orderBy = field.getAnnotation(OrderBy.class);
            if (orderBy != null){
                columnInfo.setOrderBy(orderBy.order());
                columnInfo.setOrderByPriority(orderBy.orderPriority());
            }
            columns.add(columnInfo);
        }
        if (idCount > 1){
            info.setUnionId(Boolean.TRUE);
        }
        info.setColumns(columns);
        return info;
    }

6、字典字段自动翻译

简单实现思路:对需要翻译的字段加上@FieldTrans注解来表明这个字段需要翻译,通过AOP方式对结果数据进行增强,来将字段进行翻译更新。

此部分内容留待后续实现,同时调研一下是否还有更优雅简单的实现方式。

7、数据权限

我们先来思考一下数据权限到底要干啥?一句话来概括:查一张表的数据时在where条件中追加“and 控制权限的列 in (???)”。

简单实现方法:在控制权限的字段加上@DataAuthrity注解来表明通过这个字段控制权限,而???的内容肯定是由业务代码来生成的,因此考虑给这个注解增加一个属性,用来指明权限数据由执行哪个接口或方法来获取。

此部分内容留待后续实现,同时调研一下是否还有更优雅简单的实现方式。

五、使用示例

1、数据库表

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2、实体

@Table("user")
public class User {
    @Id(auto = true)
    @Column(value = "id")
    private Long id;

    @Column(value = "name", filterOperator = FilterOperator.LIKE)
    @OrderBy(orderPriority = 0)
    private String name;

    @OrderBy(order = Order.DESC, orderPriority = 1)
    private Integer age;

    private String email;

    @Transient
    private String test;
}

3、Mapper

public interface UserMapper extends BaseMapper<User, Long> {

}

至此,不需要写任何mapper.xml,UserMapper已经具备了增删改查能力。

4、联合主键示例

public class User1 {
    @Id
    @Column(value = "id1")
    private String id1;
    
    @Id
    @Column(value = "id2")
    private String id2;
    
    @Column(value = "name", filterOperator = FilterOperator.LIKE)
    @OrderBy(orderPriority = 0)
    private String name;
    
    @OrderBy(order = Order.DESC, orderPriority = 1)
    private Integer age;
    
    private String email;
    
    @Transient
    private String test;
}


public class User1Id {
    private String id1;
    private String id2;
}


public interface User1Mapper extends BaseMapper<User1,User1Id> {
}

六、总结

本轮子目前基本上不值一提,但相信后面我再把字典翻译、通用数据权限加上的话,仍然会不值一提。

实际上轮子本身不重要,开发过程中的各种思考、试验更重要吧。

欢迎阅读,欢迎转载,转载请注明出处,求你了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx-负载均衡

负载均衡的意思是在服务器集群中&#xff0c;需要有一台服务器作为调度者&#xff0c;客户端所有的请求都有调度者接收&#xff0c;调度者再根据每台服务器的负载情况&#xff0c;将请求分配给对应的服务器去处理 配置过程 1、需要在nginx.conf配置文件中添加服务组 服务组中…

linux安装stable diffusion2.0完整教程-还不会安装sd2.0?一篇文章教会你AI绘画

以下教程出自飞链云AI技术人员&#xff0c;欢迎使用目前国内顶尖的AI绘画工具&#xff0c;微信小程序搜索&#xff1a;【飞链云版图】 注意&#xff1a;请严格按照以下步骤进行&#xff0c;可非常容易进行安装&#xff0c;其他环境不保证丝滑安装&#xff1b; 安装前准备 ub…

分析GC日志

文章目录1.GC日志格式1.1 GC分类1.2 GC日志结构剖析1.3 GC日志分析工具1.4 人生感悟1.GC日志格式 1.1 GC分类 针对HotSpot VM的实现&#xff0c;它里面的Gc按照回收区域又分为两大种类型: 一种是部分收集&#xff08;Partial GC&#xff09;&#xff0c;一种是整堆收集&#…

三.线程的状态

正常线程的五大状态 &#xff1a;新建状态&#xff0c;就绪状态&#xff0c;运行状态&#xff0c;阻塞状态&#xff0c;死亡状态 初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;re…

小白想学习python?怎么学?

首先&#xff0c;学习Python编程技术&#xff0c;自学或者参加培训学习都适用&#xff0c;每个人都有自己的学习方式和方法。 一&#xff1a;明确自己的学习目标。 不管我们学习什么样的知识&#xff0c;都要对自己的学习目标有一个明确的认识。只有这样才能朝着目标持续的前…

26岁学历低的我,是如何从工厂转行Python工程师?

本人坐标长沙&#xff0c;专科工程造价专业。 转行Python工程师薪资&#xff1a;13K。 饮水思泉&#xff0c;在转行的过程中&#xff0c;同学跟老师们对我的帮助非常大&#xff0c;自己找到工作以后&#xff0c;也试着写篇文章&#xff0c;将自己的心得经验全部分享出来&#…

单例模式及其线程安全问题

目录 ​ 1.设计模式 2.饿汉模式 3.懒汉模式 4.线程安全与单例模式 1.设计模式 设计模式是什么? 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的 单例模式的作用就是保证某个…

各类指针的详细介绍

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;《初识C语言》 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、基本数据类型指针1.1 指针的…

漏洞丨cve2017-11882

作者&#xff1a;黑蛋 一、漏洞简介 本次漏洞还是一个office溢出漏洞&#xff0c;漏洞编号cve-2017-11882。该漏洞是office一个组件EQNEDT32.EXE引起的栈溢出&#xff0c;通杀office版本2007-2016。 二、复现环境 系统版本 目标程序 调试工具 辅助工具 win7 sp1 x86 off…

【MySQL基础教程】DDL语句详细介绍

前言 本文为 【MySQL基础教程】DDL语句 相关相关内容进行详尽介绍&#xff0c;下边将对数据库操作&#xff08;包括&#xff1a;查询所有数据库、查询当前数据库、创建数据库、删除数据库、切换数据库等&#xff09;&#xff0c;表操作&#xff08;包括&#xff1a;查询创建、数…

微信小程序分包及案例

文章目录5. 分包6. 独立分包7. 分包预下载8. 案例-自定义tabbar5. 分包 分包指的是把一个完整的小程序项目&#xff0c;按照需求划分为不同的子包&#xff0c;在构建时打包成不同的分包&#xff0c;用户在使用 时按需进行加载。 可以优化小程序首次启动的下载时间在多团队共同…

微信公众号开发—扫描二维码实现登录方案

&#x1f60a; 作者&#xff1a; 一恍过去&#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390&#x1f38a; 社区&#xff1a; Java技术栈交流&#x1f389; 主题&#xff1a; 微信公众号开发—扫描二维码实现登录方案⏱️ 创作时间&#xff1a; 2022…

非零基础自学Golang 第13章 并发与通道 13.1 概述

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.1 概述13.1.1 并行与并发13.1.2 Go并发优势第13章 并发与通道 并发是指在同一段时间内&#xff0c;程序可以执行多个任务。 随着社会需求的发展&#xff0c;光靠硬件的提升是无法满足高并发的需求的&#…

[前端攻坚]:数组去重的几种方法

总结一些日常需要用到的一些api&#xff0c;也是在一些面试中会经常出现的题目&#xff0c;今天分享的是数组去重的几个不同的方法&#xff0c; 同时文章也被收录到我的《JS基础》专栏中&#xff0c;欢迎大家点击收藏加关注。 数组去重的方法 1.set去重 2.map去重 3.for循环in…

Python安装Pycrypto

前言 安装 使用以下命令安装 pip install pycrypto2.6.1报错 如果在安装过程中出现如下错误 则说明系统缺乏相应python开发包&#xff0c;需要进行安装对应的python开发包 解决 在CentOS下&#xff0c;如果是python2.7则使用如下命令安装 yum install python-devel是pyt…

Pytest用例运行及规范

温馨提示 本篇约1600字&#xff0c;看完需3-5分钟&#xff0c;学习学半小时&#xff0c;加油&#xff01; 先看普通函数运行顺序 import pytestdef test_one():print("我是清安")def test_02():print("--02--")def test_a():print("--a--")de…

BP神经网络的最简Python实现

文章目录神经元BP原理及实现测试BP&#xff0c;就是后向传播(back propagation)&#xff0c;说明BP网络要向后传递一个什么东西&#xff0c;这个东西就是误差。 而神经网络&#xff0c;就是由神经元组成的网络&#xff0c;所以在考虑BP之前&#xff0c;还不得不弄清楚神经元是…

endata 电影票房响应数据破解

本文仅供参考学习&#xff0c;如有侵权可联系本人 目标网站 aHR0cHM6Ly93d3cuZW5kYXRhLmNvbS5jbi9Cb3hPZmZpY2UvQk8vWWVhci9pbmRleC5odG1s加密入口分析 在异步请求那里可以看到请求接口&#xff0c;请求参数并未加密只是响应内容进行了加密&#xff0c;暂时也无法判断加密方…

JavaWeb的Servlet学习之Request03

目录 1.Request 1.1Request执行流程 1.2request对象和response对象的原理 1.3 request对象继承体系结构 1.4request功能&#xff1a; 1.3.1获取请求消息数据 1.获取请求行数据 2.获取请求头 3.获取请求体数据 4.其他功能 4.1获取请求参数通用方式&#xff1a;不论get…

开源CA搭建-基于openssl实现数字证书的生成与分发

目录 一、前言 二、openssl介绍 三、openssl的常用用法 &#xff08;一&#xff09;单向加密 &#xff08;二&#xff09;生成随机数 &#xff08;三&#xff09;生成公钥&#xff0c;私钥 1.生成私钥 2.提取公钥 四、搭建CA &#xff08;一&#xff09;创建根CA私钥…