MyBatis-Plus (简称 MP,下文就使用简称啦)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 有以下特性:
接下来本文会围绕Spring Boot
整合Mybatis-plus
,从引入、配置、使用等几个方面进行总结汇总,足以覆盖我们日常开发中绝大部分使用场景
使用IDEA创建一个Spring Boot项目,添加如下依赖:
<!-- MySQL连接驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <!-- mp依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency>
配置文件添加数据源:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://127.0.0.1:3306/db_test?&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
启动项目服务如下所示即为成功
首先先在数据库添加一个用户表:tb_user
CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_no` varchar(255) NOT NULL COMMENT '编号', `nickname` varchar(255) DEFAULT NULL COMMENT '昵称', `email` varchar(255) DEFAULT NULL COMMENT '邮箱', `phone` varchar(255) NOT NULL COMMENT '手机号', `gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0:男生 1:女生', `birthday` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '出生日期', `is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志 0:否 1:是', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
在项目服务中添加对应的实体类:User
@Data@TableName(value = "tb_user")public class User { @TableId(type = IdType.AUTO) private Long id; private String userNo; private String nickname; private String email; private String phone; private Integer gender; private Date birthday; private Integer isDelete; private Date createTime; private Date updateTime;}
创建对应的mapper接口:UserDAO
,这里我以DAO结尾,平时习惯使用这个了
public interface UserDAO extends BaseMapper<User> {}
最后启动类添加配置扫描:
@SpringBootApplication@MapperScan(basePackages = "com.shepherd.mybatisplus.demo.dao")public class MybatisPlusDemoApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusDemoApplication.class, args); }}
添加测试类:
@SpringBootTest@RunWith(SpringRunner.class)public class UserServiceTest { @Resource private UserService userService; @Resource private UserDAO userDAO; /** * 添加一条记录 */ @Test public void testAdd() { User user = User.builder() .id(1L) .userNo("001") .nickname("大哥") .email("shepherd@qq.com") .phone("1234556") .birthday(new Date()) .build(); userDAO.insert(user); } /** * 查询所有记录 */ @Test public void testList() { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); List<User> users = userDAO.selectList(queryWrapper); System.out.println(users); }}
执行testAdd
插入方法之后,再执行testList
,控制台输出如下:
2023-11-08 16:50:58.514 INFO 12636 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...2023-11-08 16:50:58.720 INFO 12636 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.2023-11-08 16:50:58.734 DEBUG 12636 --- [ main] c.s.m.demo.dao.UserDAO.selectList : ==> Preparing: SELECT id,user_no,nickname,email,phone,gender,birthday,is_delete,create_time,update_time FROM tb_user2023-11-08 16:50:58.763 DEBUG 12636 --- [ main] c.s.m.demo.dao.UserDAO.selectList : ==> Parameters: 2023-11-08 16:50:58.792 DEBUG 12636 --- [ main] c.s.m.demo.dao.UserDAO.selectList : <== Total: 1[User(id=1, userNo=001, nickname=大哥, email=shepherd@qq.com, phone=1234556, gender=0, birthday=Wed Nov 08 16:26:58 CST 2023, isDelete=0, createTime=Wed Nov 08 16:26:58 CST 2023, updateTime=Wed Nov 08 16:26:58 CST 2023)]
上面的示例就是我们平时开发过程单表的基本CRUD操作,只需要创建好实体类,并创建一个继承自BaseMapper
的接口即可,是不是很简单,不需要我们有过多的编码和配置操作啥的。因为mp会自动做了数据库表名、字段名映射,Java类的驼峰命名和下划线字段之间的转化。
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记
交流探讨qun:Shepherd_126
mp封装了很多注解供我们使用,下面我们就可以来看看常用的注解:
@TableName
表名注解,标识实体类对应的表,实体类的类名(转成小写后)和数据库表名相同时,可以不指定该注解。定义如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})public @interface TableName { /** * 实体对应的表名,实体类的类名(转成小写后)和数据库表名相同时,可以不指定 */ String value() default ""; /** * schema */ String schema() default ""; /** * 是否保持使用全局的 tablePrefix 的值,只生效于 既设置了全局的 tablePrefix 也设置了上面 {@link #value()} 的值 * @since 3.1.1 */ boolean keepGlobalPrefix() default false; /** * 实体映射结果集, * 只生效与 mp 自动注入的 method */ String resultMap() default ""; /** * 是否自动构建 resultMap 并使用, * 只生效与 mp 自动注入的 method, * 如果设置 resultMap 则不会进行 resultMap 的自动构建并注入, * 只适合个别字段 设置了 typeHandler 或 jdbcType 的情况 * * @since 3.1.2 */ boolean autoResultMap() default false; /** * 需要排除的属性名 */ String[] excludeProperty() default {};}
这里对 autoResultMap
这个属性做如下说明强调:
MP 会自动构建一个 resultMap
并注入到 MyBatis 里(一般用不上),请注意以下内容:
因为 MP 底层是 MyBatis,所以 MP 只是帮您注入了常用 CRUD 到 MyBatis 里,注入之前是动态的(根据您的 Entity 字段以及注解变化而变化),但是注入之后是静态的(等于 XML 配置中的内容)。
而对于 typeHandler
属性,MyBatis 只支持写在 2 个地方:
insert
和 update
语句的 #{property}
中的 property
后面(例:#{property,typehandler=xxx.xxx.xxx}
),并且只作用于当前 设置值
除了以上两种直接指定 typeHandler
的形式,MyBatis 有一个全局扫描自定义 typeHandler
包的配置,原理是根据您的 property
类型去找其对应的 typeHandler
并使用。
综上所述,我们对实体类的某个字段自定义了typeHandler
,一定要开启autoResultMap=true
才能生效,如下所示:
@Data@TableName(autoResultMap = true)public class DataSource extends BaseDO implements Serializable { @TableId(type = IdType.AUTO) private Long id; private String name; private String host; private String port; @TableField(typeHandler = JsonStringSetTypeHandler.class) private Set<String> databaseList; private String userName; @TableField(typeHandler = EncryptTypeHandler.class) private String password; private Integer type; private Integer status;}
@TableId
主键注解
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})public @interface TableId { /** * 字段值(驼峰命名方式,该值可无) */ String value() default ""; /** * 主键ID * {@link IdType} */ IdType type() default IdType.NONE;}
名称 | 描述 |
---|---|
AUTO | 数据库自增ID |
NONE | 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | 用户自己设置的ID |
ASSIGN_ID | 当用户传入为空时,自动分配类型为Number或String的主键(雪花算法) |
ASSIGN_UUID | 当用户传入为空时,自动分配类型为String的主键 |
@TableField
字段注解(非主键)
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})public @interface TableField { /** * 数据库字段值 * <p> * 不需要配置该值的情况: * <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 true 时, * (mp下默认是true,mybatis默认是false), 数据库字段值.replace("_","").toUpperCase() == 实体属性名.toUpperCase() </li> * <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 false 时, * 数据库字段值.toUpperCase() == 实体属性名.toUpperCase() </li> */ String value() default ""; /** * 是否为数据库表字段 * <p> * 默认 true 存在,false 不存在 */ boolean exist() default true; /** * 字段 where 实体查询比较条件 * <p> * 默认 {@link SqlCondition#EQUAL} */ String condition() default ""; /** * 字段 update set 部分注入, 该注解优于 el 注解使用 * <p> * 例1:@TableField(.. , update="%s+1") 其中 %s 会填充为字段 * 输出 SQL 为:update 表 set 字段=字段+1 where ... * <p> * 例2:@TableField(.. , update="now()") 使用数据库时间 * 输出 SQL 为:update 表 set 字段=now() where ... */ String update() default ""; /** * 字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略 * <p> * IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty}); * NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) * NOT_EMPTY: insert into table_a(<if test="columnProperty != null and columnProperty!=''">column</if>) values (<if test="columnProperty != null and columnProperty!=''">#{columnProperty}</if>) * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL * * @since 3.1.2 */ FieldStrategy insertStrategy() default FieldStrategy.DEFAULT; /** * 字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略 * <p> * IGNORED: 直接拼接 update table_a set column=#{columnProperty}, 属性为null/空string都会被set进去 * NOT_NULL: update table_a set <if test="columnProperty != null">column=#{columnProperty}</if> * NOT_EMPTY: update table_a set <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL * * @since 3.1.2 */ FieldStrategy updateStrategy() default FieldStrategy.DEFAULT; /** * 字段验证策略之 where: 表示该字段在拼接where条件时的策略 * <p> * IGNORED: 直接拼接 column=#{columnProperty} * NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if> * NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL * * @since 3.1.2 */ FieldStrategy whereStrategy() default FieldStrategy.DEFAULT; /** * 字段自动填充策略 * <p> * 在对应模式下将会忽略 insertStrategy 或 updateStrategy 的配置,等于断言该字段必有值 */ FieldFill fill() default FieldFill.DEFAULT; /** * 是否进行 select 查询 * <p> * 大字段可设置为 false 不加入 select 查询范围 */ boolean select() default true; /** * 是否保持使用全局的 columnFormat 的值 * <p> * 只生效于 既设置了全局的 columnFormat 也设置了上面 {@link #value()} 的值 * 如果是 false , 全局的 columnFormat 不生效 * * @since 3.1.1 */ boolean keepGlobalFormat() default false; /** * {@link ResultMapping#property} and {@link ParameterMapping#property} * * @since 3.4.4 */ String property() default ""; /** * JDBC类型 (该默认值不代表会按照该值生效), * 只生效于 mp 自动注入的 method, * 建议配合 {@link TableName#autoResultMap()} 一起使用 * <p> * {@link ResultMapping#jdbcType} and {@link ParameterMapping#jdbcType} * * @since 3.1.2 */ JdbcType jdbcType() default JdbcType.UNDEFINED; /** * 类型处理器 (该默认值不代表会按照该值生效), * 只生效于 mp 自动注入的 method, * 建议配合 {@link TableName#autoResultMap()} 一起使用 * <p> * {@link ResultMapping#typeHandler} and {@link ParameterMapping#typeHandler} * * @since 3.1.2 */ Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class; /** * 只在使用了 {@link #typeHandler()} 时判断是否辅助追加 javaType * <p> * 一般情况下不推荐使用 * {@link ParameterMapping#javaType} * * @since 3.4.0 @2020-07-23 */ boolean javaType() default false; /** * 指定小数点后保留的位数, * 只生效于 mp 自动注入的 method, * 建议配合 {@link TableName#autoResultMap()} 一起使用 * <p> * {@link ParameterMapping#numericScale} * * @since 3.1.2 */ String numericScale() default "";}
其他注解就不一一叙述,想了解更多直接查看官网文档即可,上面是我们日常开发经常使用的,足够了。
mp封装了一些最基础的CRUD方法,只需要直接继承mp提供的接口,无需编写任何SQL,即可食用。mp提供了两套接口,分别是Mapper CRUD
接口和Service CRUD
接口。并且mp还提供了条件构造器Wrapper
,可以方便地组装SQL语句中的WHERE条件。
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行
remove 删除
list 查询集合
page 分页
首先,新建一个接口,继承IService
public interface UserService extends IService<User> {}
创建这个接口的实现类,并继承ServiceImpl
@Slf4j@Servicepublic class UserServiceImpl extends ServiceImpl<UserDAO, User> implements UserService {}
最后使用userSerive
就可以调用相关方法
mp将常用的CRUD接口封装成了BaseMapper
接口,我们只需要在自己的Mapper中继承它就可以了:
@Mapperpublic interface UserDAO extends BaseMapper<User> {}
mp让我觉得极其方便的一点在于其提供了强大的条件构造器Wrapper
,可以非常方便的构造WHERE条件。条件构造器封装抽象类:AbstractWrapper
,他是QueryWrapper(LambdaQueryWrapper)
和 UpdateWrapper(LambdaUpdateWrapper)
的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件。
allEq
allEq(Map<R, V> params)allEq(Map<R, V> params, boolean null2IsNull)allEq(boolean condition, Map<R, V> params, boolean null2IsNul
全部eq(或个别isNull)
个别参数说明:
params
: key
为数据库字段名,value
为字段值null2IsNull
: 为true
则在map
的value
为null
时调用 isNull 方法,为false
时则忽略value
为null
的
例1: allEq({id:1,name:"老王",age:null})
--->id = 1 and name = '老王' and age is null
例2: allEq({id:1,name:"老王",age:null}, false)
--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
个别参数说明:
filter
: 过滤函数,是否允许字段传入比对条件中params
与 null2IsNull
: 同上
allEq((k,v) -> k.contains("a"), {id:1,name:"老王",age:null})
--->name = '老王' and age is null
allEq((k,v) -> k.contains("a"), {id:1,name:"老王",age:null}, false)
--->name = '老王'
eq
eq(R column, Object val)eq(boolean condition, R column, Object val)
eq("name", "老王")
--->name = '老王'
ne
ne(R column, Object val)ne(boolean condition, R column, Object val)
ne("name", "老王")
--->name <> '老王'
gt
gt(R column, Object val)gt(boolean condition, R column, Object val)
gt("age", 18)
--->age > 18
ge
ge(R column, Object val)ge(boolean condition, R column, Object val)
ge("age", 18)
--->age >= 18
lt
lt(R column, Object val)lt(boolean condition, R column, Object val)
lt("age", 18)
--->age < 18
le
le(R column, Object val)le(boolean condition, R column, Object val)
le("age", 18)
--->age <= 18
between
between(R column, Object val1, Object val2)between(boolean condition, R column, Object val1, Object val2)
between("age", 18, 30)
--->age between 18 and 30
notBetween
notBetween(R column, Object val1, Object val2)notBetween(boolean condition, R column, Object val1, Object val2)
notBetween("age", 18, 30)
--->age not between 18 and 30
like
like(R column, Object val)like(boolean condition, R column, Object val)
like("name", "王")
--->name like '%王%'
notLike
notLike(R column, Object val)notLike(boolean condition, R column, Object val)
notLike("name", "王")
--->name not like '%王%'
likeLeft
likeLeft(R column, Object val)likeLeft(boolean condition, R column, Object val)
likeLeft("name", "王")
--->name like '%王'
likeRight
likeRight(R column, Object val)likeRight(boolean condition, R column, Object val)
likeRight("name", "王")
--->name like '王%'
notLikeLeft
notLikeLeft(R column, Object val)notLikeLeft(boolean condition, R column, Object val)
notLikeLeft("name", "王")
--->name not like '%王'
notLikeRight
notLikeRight(R column, Object val)notLikeRight(boolean condition, R column, Object val)
notLikeRight("name", "王")
--->name not like '王%'
isNull
isNull(R column)isNull(boolean condition, R column)
isNull("name")
--->name is null
isNotNull
isNotNull(R column)isNotNull(boolean condition, R column)
isNotNull("name")
--->name is not null
in
in(R column, Collection<?> value)in(boolean condition, R column, Collection<?> value)
in("age",{1,2,3})
--->age in (1,2,3)
in(R column, Object... values)in(boolean condition, R column, Object... values)
in("age", 1, 2, 3)
--->age in (1,2,3)
notIn
notIn(R column, Collection<?> value)notIn(boolean condition, R column, Collection<?> value)
notIn("age",{1,2,3})
--->age not in (1,2,3)
notIn(R column, Object... values)notIn(boolean condition, R column, Object... values)
notIn("age", 1, 2, 3)
--->age not in (1,2,3)
inSql
inSql(R column, String inValue)inSql(boolean condition, R column, String inValue)
inSql("age", "1,2,3,4,5,6")
--->age in (1,2,3,4,5,6)
inSql("id", "select id from table where id < 3")
--->id in (select id from table where id < 3)
notInSql
notInSql(R column, String inValue)notInSql(boolean condition, R column, String inValue)
notInSql("age", "1,2,3,4,5,6")
--->age not in (1,2,3,4,5,6)
notInSql("id", "select id from table where id < 3")
--->id not in (select id from table where id < 3)
groupBy
groupBy(R... columns)groupBy(boolean condition, R... columns)
groupBy("id", "name")
--->group by id,name
orderByAsc
orderByAsc(R... columns)orderByAsc(boolean condition, R... columns)
orderByAsc("id", "name")
--->order by id ASC,name ASC
orderByDesc
orderByDesc(R... columns)orderByDesc(boolean condition, R... columns)
orderByDesc("id", "name")
--->order by id DESC,name DESC
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
orderBy(true, true, "id", "name")
--->order by id ASC,name ASC
having
having(String sqlHaving, Object... params)having(boolean condition, String sqlHaving, Object... params)
having("sum(age) > 10")
--->having sum(age) > 10
having("sum(age) > {0}", 11)
--->having sum(age) > 11
func
func(Consumer<Children> consumer)func(boolean condition, Consumer<Children> consumer)
func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or
or()or(boolean condition)
拼接 OR
注意事项:
主动调用or
表示紧接着下一个方法不是用and
连接!(不调用or
则默认为使用and
连接)
例: eq("id",1).or().eq("name","老王")
--->id = 1 or name = '老王'
or(Consumer<Param> consumer)or(boolean condition, Consumer<Param> consumer)
or(i -> i.eq("name", "李白").ne("status", "活着"))
--->or (name = '李白' and status <> '活着')
and
and(Consumer<Param> consumer)and(boolean condition, Consumer<Param> consumer)
and(i -> i.eq("name", "李白").ne("status", "活着"))
--->and (name = '李白' and status <> '活着')
nested
nested(Consumer<Param> consumer)nested(boolean condition, Consumer<Param> consumer)
nested(i -> i.eq("name", "李白").ne("status", "活着"))
--->(name = '李白' and status <> '活着')
exists
exists(String existsSql)exists(boolean condition, String existsSql)
exists("select id from table where age = 1")
--->exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)notExists(boolean condition, String notExistsSql)
notExists("select id from table where age = 1")
--->not exists (select id from table where age = 1)
QueryWrapper
说明:
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件 及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取
select
select(String... sqlSelect)select(Predicate<TableFieldInfo> predicate)select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
设置查询字段
说明:
以上方法分为两类.
第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper
内的entity
属性有值! 这两类方法重复调用以最后一次为准
例: select("id", "name", "age")
例: select(i -> i.getProperty().startsWith("test"))
UpdateWrapper
说明:
继承自 AbstractWrapper
,自身的内部属性 entity
也用于生成 where 条件
及 LambdaUpdateWrapper
, 可以通过 new UpdateWrapper().lambda()
方法获取!
set
set(String column, Object val)set(boolean condition, String column, Object val)
set("name", "老李头")
set("name", "")
--->数据库字段值变为空字符串set("name", null)
--->数据库字段值变为null
lambda
LambdaWrapper
在QueryWrapper
中是获取LambdaQueryWrapper
在UpdateWrapper
中是获取LambdaUpdateWrapper
分页
使用分页话需要增加分页插件的配置
@Configurationpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }}
最后来个示例看看上面的条件构造器是怎么用的:
@Override public List<BrandDTO> getList(BrandQuery query) { LambdaQueryWrapper<Brand> queryWrapper = new LambdaQueryWrapper<>(); if (query.getCategoryId() != null) { queryWrapper.eq(Brand::getCategoryId, query.getCategoryId()); } if (StringUtils.isNotBlank(query.getLetter())) { queryWrapper.eq(Brand::getLetter, query.getLetter()); } if (StringUtils.isNotBlank(query.getName())) { queryWrapper.likeRight(Brand::getName, query.getName()); } List<BrandDTO> brandDTOList = brandDAO.selectList(queryWrapper).stream().map(brand -> toBrandDTO(brand)).collect(Collectors.toList()); return brandDTOList; }
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。