当前使用版本(必填,否则不予处理)

3.4.0

该问题是如何引起的?(确定最新版也有问题再提!!!)

重现步骤(如果有就写完整)

当想要调用多次 select 时,是否可以将每次的字段都附加进去,而不是只有最后一次才生效,比如像下面这样

LambdaQueryWrapper<XX> xxQuery = Wrappers.<XX>query().select("DISTINCT aa")    // 或者像 ifnull(aa, '123')
                .lambda().select(XX::getBb, Orders::getCc);
...

最终想要生成的 sql 是

select DISTINCT aa, bb, cc from xx ...    /* 或者 select ifnull(aa, '123'), bb, cc from xx ... */

因为 lambda 只能用属性名,所以只能先用 QueryWrapper 再转换成 lambda 然后再次调用 select,这样会调用多次 select,但实际却只有最后的 select 才有效

报错信息

Comment From: liuanxin

几天过去, 见到两年前的这个 issue, 我这个应该也是不会被处理的

我抽取了这么两个方法出来

import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;
import org.apache.ibatis.reflection.property.PropertyNamer;

import java.util.List;
import java.util.Map;

/**
 * <pre>
 * mp 工具类
 *
 * 比如有表
 * CREATE TABLE IF NOT EXISTS `t_user` (
 *   `id` bigint unsigned NOT NULL AUTO_INCREMENT,
 *   `user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
 *   `password` varchar(64) NOT NULL DEFAULT '' COMMENT '密码',
 *   `nick_name` varchar(16) NOT NULL DEFAULT '' COMMENT '昵称',
 *   `gender` int unsigned NOT NULL DEFAULT '0' COMMENT '性别(0.未知, 1.男, 2.女)',
 *   `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '1.已禁用',
 *   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 *   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 *   `is_deleted` bigint unsigned NOT NULL DEFAULT '0' COMMENT '0 表示未删除, 删除时将当前值置为主键 id 或时间戳',
 *   PRIMARY KEY (`id`),
 *   UNIQUE KEY `user_name` (`user_name`, `is_deleted`)
 * ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户';
 *
 * 其对应的实体是
 * &#047;&#042;&#042; 用户 --> t_user &#042;&#047;
 * &#064;Data
 * &#064;TableName("t_user")
 * public class User {
 *     private Long id;
 *
 *     &#047;&#042;&#042; 用户名 --> user_name &#042;&#047;
 *     private String userName;
 *
 *     &#047;&#042;&#042; 密码 --> password &#042;&#047;
 *     private String password;
 *
 *     &#047;&#042;&#042; 昵称 --> nick_name &#042;&#047;
 *     private String nickName;
 *
 *     &#047;&#042;&#042; 性别(0.未知, 1.男, 2.女) --> gender &#042;&#047;
 *     private Gender gender;
 *
 *     &#047;&#042;&#042; 1.已禁用 --> status &#042;&#047;
 *     private Boolean status;
 *
 *     &#047;&#042;&#042; 创建时间 --> create_time &#042;&#047;
 *     private Date createTime;
 *
 *     &#047;&#042;&#042; 更新时间 --> update_time &#042;&#047;
 *     private Date updateTime;
 *
 *     &#047;&#042;&#042; 0 表示未删除, 删除时将当前值置为主键 id 或时间戳 --> is_deleted &#042;&#047;
 *     &#064;TableLogic(value = "0", delval = "unix_timestamp()")
 *     private Long isDeleted;
 * }
 * </pre>
 */
public class MybatisPlusUtil {

    /**
     * java 字段转换成数据库列名, 如: columnToString(User::getUserName) 返回 user_name
     *
     * @param column lambda 表达式对应的字段, 如 User::getId
     * @param <T> 数据库表对应的实体
     * @return 实体中的属性对应的数据库字段名, 如 id
     */
    public static <T> String fieldToColumn(SFunction<T, ?> column) {
        SerializedLambda lambda = LambdaUtils.resolve(column);
        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
        Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap(lambda.getInstantiatedType());

        String returnColumn;
        if (columnMap != null && columnMap.size() > 0) {
            returnColumn = columnMap.get(LambdaUtils.formatKey(fieldName)).getColumn();
        } else {
            returnColumn = null;
        }

        if (returnColumn == null || returnColumn.trim().length() == 0) {
            // 上面的不成功就按驼峰规则转换: lowerCamel => lower_camel)
            // 如果是大写(lowerCamel => LOWER_CAMEL)则用 UPPER_UNDERSCORE
            return CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.LOWER_UNDERSCORE).convert(fieldName);
        } else {
            return returnColumn;
        }
    }

    /**
     * java 字段转换成数据库列名<br><br>
     *
     * 使用 columnsToString(User::getUserName, User::getPassword, User::getNickName)<br>
     * 返回 [user_name, password, nick_name]
     *
     * @param columns lambda 表达式对应的字段数组, 如 [User::getId, User::getUserName]
     * @param <T> 数据库表对应的实体
     * @return 实体中的属性对应的数据库字段名, 如 [id, user_name]
     */
    @SuppressWarnings("unchecked")
    public static <T> String[] fieldsToColumnArray(SFunction<T, ?>... columns) {
        List<String> returnList = Lists.newArrayList();
        for (SFunction<T, ?> column : columns) {
            returnList.add(fieldToColumn(column));
        }
        return returnList.toArray(new String[0]);
    }
}

3.4.3 里面的 api 重构过, 是这样

import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;
import org.apache.ibatis.reflection.property.PropertyNamer;

import java.util.List;
import java.util.Map;

/**
 * <pre>
 * mp 工具类
 *
 * 比如有表
 * CREATE TABLE IF NOT EXISTS `t_user` (
 *   `id` bigint unsigned NOT NULL AUTO_INCREMENT,
 *   `user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
 *   `password` varchar(64) NOT NULL DEFAULT '' COMMENT '密码',
 *   `nick_name` varchar(16) NOT NULL DEFAULT '' COMMENT '昵称',
 *   `gender` int unsigned NOT NULL DEFAULT '0' COMMENT '性别(0.未知, 1.男, 2.女)',
 *   `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '1.已禁用',
 *   `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
 *   `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
 *   `is_deleted` bigint unsigned NOT NULL DEFAULT '0' COMMENT '0 表示未删除, 删除时将当前值置为主键 id 或时间戳',
 *   PRIMARY KEY (`id`),
 *   UNIQUE KEY `user_name` (`user_name`, `is_deleted`)
 * ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户';
 *
 * 其对应的实体是
 * &#047;&#042;&#042; 用户 --> t_user &#042;&#047;
 * &#064;Data
 * &#064;TableName("t_user")
 * public class User {
 *     private Long id;
 *
 *     &#047;&#042;&#042; 用户名 --> user_name &#042;&#047;
 *     private String userName;
 *
 *     &#047;&#042;&#042; 密码 --> password &#042;&#047;
 *     private String password;
 *
 *     &#047;&#042;&#042; 昵称 --> nick_name &#042;&#047;
 *     private String nickName;
 *
 *     &#047;&#042;&#042; 性别(0.未知, 1.男, 2.女) --> gender &#042;&#047;
 *     private Gender gender;
 *
 *     &#047;&#042;&#042; 1.已禁用 --> status &#042;&#047;
 *     private Boolean status;
 *
 *     &#047;&#042;&#042; 创建时间 --> create_time &#042;&#047;
 *     private Date createTime;
 *
 *     &#047;&#042;&#042; 更新时间 --> update_time &#042;&#047;
 *     private Date updateTime;
 *
 *     &#047;&#042;&#042; 0 表示未删除, 删除时将当前值置为主键 id 或时间戳 --> is_deleted &#042;&#047;
 *     &#064;TableLogic(value = "0", delval = "unix_timestamp()")
 *     private Long isDeleted;
 * }
 * </pre>
 */
public class MybatisPlusUtil {

    /**
     * java 字段转换成数据库列名, 如: columnToString(User::getUserName) 返回 user_name
     *
     * @param column lambda 表达式对应的字段, 如 User::getId
     * @param <T> 数据库表对应的实体
     * @return 实体中的属性对应的数据库字段名, 如 id
     */
    public static <T> String fieldToColumn(SFunction<T, ?> column) {
        LambdaMeta lambda = LambdaUtils.extract(column);
        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
        Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap(lambda.getInstantiatedClass());

        String returnColumn;
        if (columnMap != null && columnMap.size() > 0) {
            returnColumn = columnMap.get(LambdaUtils.formatKey(fieldName)).getColumn();
        } else {
            returnColumn = null;
        }

        if (returnColumn == null || returnColumn.trim().length() == 0) {
            // 上面的不成功就按驼峰规则转换: lowerCamel => lower_camel)
            // 如果是大写(lowerCamel => LOWER_CAMEL)则用 UPPER_UNDERSCORE
            return CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.LOWER_UNDERSCORE).convert(fieldName);
        } else {
            return returnColumn;
        }
    }

    /**
     * java 字段转换成数据库列名<br><br>
     *
     * 使用 columnsToString(User::getUserName, User::getPassword, User::getNickName)<br>
     * 返回 [user_name, password, nick_name]
     *
     * @param columns lambda 表达式对应的字段数组, 如 [User::getId, User::getUserName]
     * @param <T> 数据库表对应的实体
     * @return 实体中的属性对应的数据库字段名, 如 [id, user_name]
     */
    @SuppressWarnings("unchecked")
    public static <T> String[] fieldsToColumnArray(SFunction<T, ?>... columns) {
        List<String> returnList = Lists.newArrayList();
        for (SFunction<T, ?> column : columns) {
            returnList.add(fieldToColumn(column));
        }
        return returnList.toArray(new String[0]);
    }
}

调用多次的问题也就不重要了, 把 lambda 当 string 用, 直接基于 QueryWrapper 就好了

Comment From: philyang7

翻了半天终于翻到和我一样的疑惑了。。。强迫症是真不想把数据库字段硬编码到select里。。。 不过你这个应该不用这么麻烦吧,直接硬编码进去就行了。

like this:

LambdaQueryWrapper xxQuery = Wrappers.query().select("DISTINCT aa","bb","cc") .lambda().eq(XX::getBb, "");

Comment From: liuanxin

翻了半天终于翻到和我一样的疑惑了。。。强迫症是真不想把数据库字段硬编码到select里。。。 不过你这个应该不用这么麻烦吧,直接硬编码进去就行了。

like this:

LambdaQueryWrapper xxQuery = Wrappers.query().select("DISTINCT aa","bb","cc") .lambda().eq(XX::getBb, "");

主要也就是不想要硬编字段名, 在用到字段的时候用 lambda, 其实还是基于 QueryWrapper + string 来整的

QueryWrapper<XXX> xxxQuery = Wrappers.query();
xxxQuery.select(
    "DISTINCT " + MybatisPlusUtil.columnToString(XXX::getX),
    MybatisPlusUtil.columnToString(XXX::getXxx)
    ...
);
xxxQuery.eq(MybatisPlusUtil.columnToString(XXX::getXX), xxxxx);

避免直接写字段名的同时, 也无所谓多次调用 select 了, 只能调一次就只调一次吧