焦作网站建设费用,wordpress修订版本,做网站编辑好还是推广好,中国建设银行手机网站首页本章概要
向SQL语句传参数据输入 Mybatis总体机制概括概念说明单个简单类型参数实体类类型参数零散的简单类型数据Map类型参数 数据输出 输出概述单个简单类型返回实体类对象返回Map类型返回List类型返回主键值实体类属性和数据库字段对应关系 CRUD强化练习mapperXML标签总结 …本章概要
向SQL语句传参数据输入 Mybatis总体机制概括概念说明单个简单类型参数实体类类型参数零散的简单类型数据Map类型参数 数据输出 输出概述单个简单类型返回实体类对象返回Map类型返回List类型返回主键值实体类属性和数据库字段对应关系 CRUD强化练习mapperXML标签总结
2.1 向 SQL 语句传参
2.1.1 mybatis 日志输出配置 mybatis配置文件设计标签和顶层结构如下 configuration配置 properties属性settings设置typeAliases类型别名typeHandlers类型处理器objectFactory对象工厂plugins插件environments环境配置 environment环境变量 transactionManager事务管理器dataSource数据源 databaseIdProvider数据库厂商标识mappers映射器 可以在mybatis的配置文件使用settings标签设置输出运过程SQL日志通过查看日志可以判定 #{} 和 ${} 的输出效果 settings设置项
logImpl指定 MyBatis 所用日志的具体实现未指定时将自动查找。SLF4J | LOG4J3.5.9 起废弃 | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置 日志配置 settings!-- SLF4J 选择slf4j输出 --setting namelogImpl valueSLF4J/
/settings2.1.2 #{}形式 Mybatis 会将 SQL 语句中的 #{} 转换为问号占位符。 2.1.3 ${}形式 ${} 形式传参底层 Mybatis 做的是字符串拼接操作。 通常不会采用 ${} 的方式传值。一个特定的适用场景是通过 Java 程序动态生成数据库表表名部分需要 Java 程序通过参数传入而JDBC对于表名部分是不能使用问号占位符的此时只能使用
结论实际开发中能用 #{} 实现的肯定不用 ${}。
特殊情况 动态的不是值是列名或者关键字需要使用 ${} 拼接
//注解方式传入参数
Select(select * from user where ${column} #{value})
User findByColumn(Param(column) String column, Param(value) String value);2.2 数据输入
2.2.1 Mybatis总体机制概括 2.2.2 概念说明 这里数据输入具体是指上层方法例如Service方法调用Mapper接口时数据传入的形式。 简单类型只包含一个值的数据类型 基本数据类型int、byte、short、double、……基本数据类型的包装类型Integer、Character、Double、……字符串类型String 复杂类型包含多个值的数据类型 实体类类型Employee、Department、……集合类型List、Set、Map、……数组类型int[]、String[]、……复合类型List、实体类中包含集合……
2.2.3 单个简单类型参数 Mapper接口中抽象方法的声明 Employee selectEmployee(Integer empId);SQL语句 select idselectEmployee resultTypecom.atguigu.mybatis.entity.Employeeselect emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id#{empId}
/select单个简单类型参数在 #{} 中可以随意命名但是没有必要。通常还是使用和接口方法参数同名。
2.2.4 实体类类型参数 Mapper 接口中抽象方法的声明 int insertEmployee(Employee employee);SQL 语句 insert idinsertEmployeeinsert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
/insert对应关系 结论 Mybatis会根据#{}中传入的数据加工成getXxx()方法通过反射在实体类对象中调用这个方法从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。 2.2.5 零散的简单类型数据 零散的多个简单类型参数如果没有特殊处理那么Mybatis无法识别自定义名称 Mapper接口中抽象方法的声明 int updateEmployee(Param(empId) Integer empId,Param(empSalary) Double empSalary);SQL语句 update idupdateEmployeeupdate t_emp set emp_salary#{empSalary} where emp_id#{empId}
/update对应关系 2.2.6 Map类型参数 Mapper接口中抽象方法的声明 int updateEmployeeByMap(MapString, Object paramMap);SQL语句 update idupdateEmployeeByMapupdate t_emp set emp_salary#{empSalaryKey} where emp_id#{empIdKey}/updatejunit测试 private SqlSession session;
//junit5会在每一个Test方法前执行BeforeEach方法
BeforeEach
public void init() throws IOException {
session new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(mybatis-config.xml))
.openSession();
}Test
public void testUpdateEmpNameByMap() {EmployeeMapper mapper session.getMapper(EmployeeMapper.class);MapString, Object paramMap new HashMap();paramMap.put(empSalaryKey, 999.99);paramMap.put(empIdKey, 5);int result mapper.updateEmployeeByMap(paramMap);log.info(result result);
}//junit5会在每一个Test方法后执行AfterEach方法
AfterEach
public void clear() {session.commit();session.close();
}对应关系 #{}中写Map中的key 使用场景 有很多零散的参数需要传递但是没有对应的实体类类型可以使用。使用Param注解一个一个传入又太麻烦了。所以都封装到Map中。
2.3 数据输出
2.3.1 输出概述 数据输出总体上有两种形式 增删改操作返回的受影响行数直接使用 int 或 long 类型接收即可查询操作的查询结果
我们需要做的是指定查询的输出数据类型即可 并且插入场景下实现主键数据回显示
2.3.2 单个简单类型 Mapper接口中的抽象方法 int selectEmpCount();SQL语句 select idselectEmpCount resultTypeintselect count(*) from t_emp
/selectMybatis 内部给常用的数据类型设定了很多别名。 以 int 类型为例可以写的名称有int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等。 junit测试 Testpublic void testEmpCount() {EmployeeMapper employeeMapper session.getMapper(EmployeeMapper.class);int count employeeMapper.selectEmpCount();log.info(count count);}细节解释 select标签通过resultType指定查询返回值类型resultType “全限定符 别名 如果是返回集合类型写范型类型即可” 别名问题 https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置意在降低冗余的全限定类名书写。例如 typeAliasestypeAlias aliasAuthor typedomain.blog.Author/typeAlias aliasBlog typedomain.blog.Blog/
/typeAliases当这样配置时Blog 可以用在任何使用 domain.blog.Blog 的地方。 也可以指定一个包名MyBatis 会在包名下面搜索需要的 Java Bean比如 typeAliases package namedomain.blog/ /typeAliases每一个在包 domain.blog 中的 Java Bean在没有注解的情况下会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author若有注解则别名为其注解值。见下面的例子 Alias(author)
public class Author {...
}下面是Mybatis为常见的 Java 类型内建的类型别名。它们都是不区分大小写的注意为了应对原始类型的命名重复采取了特殊的命名风格。 别名映射的类型_bytebyte_char (since 3.5.10)char_character (since 3.5.10)char_longlong_shortshort_intint_integerint_doubledouble_floatfloat_booleanbooleanstringStringbyteBytechar (since 3.5.10)Charactercharacter (since 3.5.10)CharacterlongLongshortShortintIntegerintegerIntegerdoubleDoublefloatFloatbooleanBooleandateDatedecimalBigDecimalbigdecimalBigDecimalbigintegerBigIntegerobjectObjectobject[]Object[]mapMaphashmapHashMaplistListarraylistArrayListcollectionCollection
2.3.3 返回实体类对象 Mapper 接口的抽象方法 Employee selectEmployee(Integer empId);SQL语句 !-- 编写具体的SQL语句使用id属性唯一的标记一条SQL语句 --
!-- resultType属性指定封装查询结果的Java实体类的全类名 --
select idselectEmployee resultTypecom.atguigu.mybatis.entity.Employee!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 --!-- 给每一个字段设置一个别名让别名和Java实体类中属性名一致 --select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id#{maomi}/select通过给数据库表字段加别名让查询结果的每一列都和Java实体类中属性对应起来。
增加全局配置自动识别对应关系 在 Mybatis 全局配置文件中做了下面的配置select语句中可以不给字段设置别名 !-- 在全局范围内对Mybatis进行配置 --
settings!-- 具体配置 --!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 --!-- 将mapUnderscoreToCamelCase属性配置为true表示开启自动映射驼峰式命名规则 --!-- 规则要求数据库表字段命名方式单词_单词 --!-- 规则要求Java实体类属性名命名方式首字母小写的驼峰式命名 --setting namemapUnderscoreToCamelCase valuetrue//settings2.3.4 返回Map类型 适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应没法封装到实体类对象中。能够封装成实体类类型的就不使用Map类型。 Mapper接口的抽象方法 MapString,Object selectEmpNameAndMaxSalary();SQL语句 !-- MapString,Object selectEmpNameAndMaxSalary(); --
!-- 返回工资最高的员工的姓名和他的工资 --
select idselectEmpNameAndMaxSalary resultTypemapSELECTemp_name 员工姓名,emp_salary 员工工资,(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资FROM t_emp WHERE emp_salary(SELECT MAX(emp_salary) FROM t_emp)
/selectjunit测试 Test
public void testQueryEmpNameAndSalary() {EmployeeMapper employeeMapper session.getMapper(EmployeeMapper.class);MapString, Object resultMap employeeMapper.selectEmpNameAndMaxSalary();SetMap.EntryString, Object entrySet resultMap.entrySet();for (Map.EntryString, Object entry : entrySet) {String key entry.getKey();Object value entry.getValue();log.info(key value);}
}2.3.5 返回List类型 查询结果返回多个实体类对象希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理在resultType属性中还是设置实体类类型即可。 Mapper接口中抽象方法 ListEmployee selectAll();SQL语句 !-- ListEmployee selectAll(); --
select idselectAll resultTypecom.atguigu.mybatis.entity.Employeeselect emp_id empId,emp_name empName,emp_salary empSalaryfrom t_emp
/selectjunit测试 Test
public void testSelectAll() {EmployeeMapper employeeMapper session.getMapper(EmployeeMapper.class);ListEmployee employeeList employeeMapper.selectAll();for (Employee employee : employeeList) {log.info(employee employee);}
}2.3.6 返回主键值
自增长类型主键 Mapper接口中的抽象方法 int insertEmployee(Employee employee);SQL语句 !-- int insertEmployee(Employee employee); --
!-- useGeneratedKeys属性字面意思就是“使用生成的主键” --
!-- keyProperty属性可以指定主键在实体类对象中对应的属性名Mybatis会将拿到的主键值存入这个属性 --
insert idinsertEmployee useGeneratedKeystrue keyPropertyempIdinsert into t_emp(emp_name,emp_salary)values(#{empName},#{empSalary})
/insertjunit测试 Test
public void testSaveEmp() {EmployeeMapper employeeMapper session.getMapper(EmployeeMapper.class);Employee employee new Employee();employee.setEmpName(john);employee.setEmpSalary(666.66);employeeMapper.insertEmployee(employee);log.info(employee.getEmpId() employee.getEmpId());
}注意 Mybatis 是将自增主键的值设置到实体类对象中而不是以Mapper接口方法返回值的形式返回。 非自增长类型主键 而对于不支持自增型主键的数据库例如 Oracle或者字符串类型主键则可以使用 selectKey 子元素selectKey 元素将会首先运行id 会被设置然后插入语句会被调用 使用 selectKey 帮助插入UUID作为字符串类型主键示例 insert idinsertUser parameterTypeUserselectKey keyPropertyid resultTypejava.lang.StringorderBEFORESELECT UUID() as id/selectKeyINSERT INTO user (id, username, password) VALUES (#{id},#{username},#{password})
/insert在上例中我们定义了一个 insertUser 的插入语句来将 User 对象插入到 user 表中。我们使用 selectKey 来查询 UUID 并设置到 id 字段中。通过 keyProperty 属性来指定查询到的 UUID 赋值给对象中的 id 属性而 resultType 属性指定了 UUID 的类型为 java.lang.String。
需要注意的是我们将 selectKey 放在了插入语句的前面这是因为 MySQL 在 insert 语句中只支持一个 select 子句而 selectKey 中查询 UUID 的语句就是一个 select 子句因此我们需要将其放在前面。
最后在将 User 对象插入到 user 表中时我们直接使用对象中的 id 属性来插入主键值。使用这种方式我们可以方便地插入 UUID 作为字符串类型主键。当然还有其他插入方式可以使用如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。
2.3.7 实体类属性和数据库字段对应关系
别名对应 将字段的别名设置成和实体类属性一致。 !-- 编写具体的SQL语句使用id属性唯一的标记一条SQL语句 --
!-- resultType属性指定封装查询结果的Java实体类的全类名 --
select idselectEmployee resultTypecom.atguigu.mybatis.entity.Employee!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 --!-- 给每一个字段设置一个别名让别名和Java实体类中属性名一致 --select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id#{maomi}/select关于实体类属性的约定getXxx()方法、setXxx()方法把方法名中的get或set去掉首字母小写。 全局配置自动识别驼峰式命名规则 在Mybatis全局配置文件加入如下配置 !-- 使用settings对Mybatis全局进行设置 --
settings!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 --setting namemapUnderscoreToCamelCase valuetrue//settingsSQL语句中可以不使用别名 !-- Employee selectEmployee(Integer empId); --
select idselectEmployee resultTypecom.atguigu.mybatis.entity.Employeeselect emp_id,emp_name,emp_salary from t_emp where emp_id#{empId}/select使用resultMap 使用resultMap标签定义对应关系再在后面的SQL语句中引用这个对应关系 !-- 专门声明一个resultMap设定column到property之间的对应关系 --
resultMap idselectEmployeeByRMResultMap typecom.atguigu.mybatis.entity.Employee!-- 使用id标签设置主键列和主键属性之间的对应关系 --!-- column属性用于指定字段名property属性用于指定Java实体类属性名 --id columnemp_id propertyempId/!-- 使用result标签设置普通字段和Java实体类属性之间的关系 --result columnemp_name propertyempName/result columnemp_salary propertyempSalary//resultMap!-- Employee selectEmployeeByRM(Integer empId); --
select idselectEmployeeByRM resultMapselectEmployeeByRMResultMapselect emp_id,emp_name,emp_salary from t_emp where emp_id#{empId}/select2.4 CRUD强化练习
准备数据库数据 首先需要准备一张名为 user 的表。该表包含字段 id主键、username、password。创建SQL如下 CREATE TABLE user (id INT(11) NOT NULL AUTO_INCREMENT,username VARCHAR(50) NOT NULL,password VARCHAR(50) NOT NULL,PRIMARY KEY (id)
) ENGINEINNODB AUTO_INCREMENT1 DEFAULT CHARSETutf8;实体类准备 接下来需要定义一个实体类 User来对应 user 表的一行数据。 Data //lombok
public class User {private Integer id;private String username;private String password;Overridepublic String toString() {return User{ id id , username username \ , password password \ };}
}lombok pom 依赖 dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.28/versionscopecompile/scope
/dependencyMapper接口定义 定义一个 Mapper 接口 UserMapper并在其中添加 user 表的增、删、改、查方法。 public interface UserMapper {int insert(User user);int update(User user);int delete(Integer id);User selectById(Integer id);ListUser selectAll();
}MapperXML编写 在 resources/mappers目录下创建一个名为 UserMapper.xml 的 XML 文件包含与 Mapper 接口中相同的五个 SQL 语句并在其中将查询结果映射到 User 实体中。 ?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper
PUBLIC -//mybatis.org//DTD Mapper 3.0//EN
https://mybatis.org/dtd/mybatis-3-mapper.dtd
!-- namespace等于mapper接口类的全限定名,这样实现对应 --
mapper namespacecom.atguigu.mapper.UserMapper!-- 定义一个插入语句并获取主键值 --insert idinsert useGeneratedKeystrue keyPropertyidINSERT INTO user(username, password)VALUES(#{username}, #{password})/insertupdate idupdateUPDATE user SET username#{username}, password#{password}WHERE id#{id}/updatedelete iddeleteDELETE FROM user WHERE id#{id}/delete!-- resultType使用user别名稍后需要配置--select idselectById resultTypeuserSELECT id, username, password FROM user WHERE id#{id}/select!-- resultType返回值类型为集合所以只写范型即可 --select idselectAll resultTypeuserSELECT id, username, password FROM user/select/mapperMyBatis配置文件 位置resources: mybatis-config.xml ?xml version1.0 encodingUTF-8 ?
!DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configurationsettings!-- 开启驼峰式映射--setting namemapUnderscoreToCamelCase valuetrue/!-- 开启logback日志输出--setting namelogImpl valueSLF4J//settingstypeAliases!-- 给实体类起别名 --package namecom.atguigu.entity//typeAliases!-- environments表示配置Mybatis的开发环境可以配置多个环境在众多具体环境中使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --environments defaultdevelopment!-- environment表示配置Mybatis的一个具体的环境 --environment iddevelopment!-- Mybatis的内置的事务管理器 --transactionManager typeJDBC/!-- 配置数据源 --dataSource typePOOLED!-- 建立数据库连接的具体信息 --property namedriver valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/mybatis-example/property nameusername valueroot/property namepassword valueroot//dataSource/environment/environmentsmappers!-- Mapper注册指定Mybatis映射文件的具体位置 --!-- mapper标签配置一个具体的Mapper映射文件 --!-- resource属性指定Mapper映射文件的实际存储位置这里需要使用一个以类路径根目录为基准的相对路径 --!-- 对Maven工程的目录结构来说resources目录下的内容会直接放入类路径所以这里我们可以以resources目录为基准 --mapper resourcemappers/UserMapper.xml//mappers/configuration效果测试
package com.atguigu.test;import com.atguigu.entity.User;
import com.atguigu.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.util.List;/*** projectName: com.atguigu.test*/
public class MyBatisTest {private SqlSession session;// junit会在每一个Test方法前执行BeforeEach方法BeforeEachpublic void init() throws IOException {session new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(mybatis-config.xml)).openSession();}Testpublic void testAll(){createTest();updateTest();selectByIdTest();selectAllTest();deleteTest();}Testpublic void createTest() {User user new User();user.setUsername(admin);user.setPassword(123456);UserMapper userMapper session.getMapper(UserMapper.class);userMapper.insert(user);System.out.println(user);}Testpublic void updateTest() {UserMapper userMapper session.getMapper(UserMapper.class);User user userMapper.selectById(1);user.setUsername(root);user.setPassword(111111);userMapper.update(user);user userMapper.selectById(1);System.out.println(user);}Testpublic void deleteTest() {UserMapper userMapper session.getMapper(UserMapper.class);userMapper.delete(1);User user userMapper.selectById(1);System.out.println(user user);}Testpublic void selectByIdTest() {UserMapper userMapper session.getMapper(UserMapper.class);User user userMapper.selectById(1);System.out.println(user user);}Testpublic void selectAllTest() {UserMapper userMapper session.getMapper(UserMapper.class);ListUser userList userMapper.selectAll();System.out.println(userList userList);}// junit会在每一个Test方法后执行AfterEach方法AfterEachpublic void clear() {session.commit();session.close();}
}2.5 mapperXML标签总结 MyBatis 的真正强大在于它的语句映射这是它的魔力所在。由于它的异常强大映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本让用户能更专注于 SQL 代码。 SQL 映射文件只有很少的几个顶级元素按照应被定义的顺序列出
insert – 映射插入语句。update – 映射更新语句。delete – 映射删除语句。select – 映射查询语句。
**select**标签 MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单
select idselectPerson resultTypehashmap resultMap自定义结构 SELECT * FROM PERSON WHERE ID #{id}
/select这个语句名为 selectPerson接受一个 int或 Integer类型的参数并返回一个 HashMap 类型的对象其中的键是列名值便是结果行中的对应值。
注意参数符号#{id} ${key}
MyBatis 创建一个预处理语句PreparedStatement参数在 JDBC 中这样的一个参数在 SQL 中会由一个“?”来标识并被传递到一个新的预处理语句中就像这样
// 近似的 JDBC 代码非 MyBatis 代码...
String selectPerson SELECT * FROM PERSON WHERE ID?;
PreparedStatement ps conn.prepareStatement(selectPerson);
ps.setInt(1,id);select 元素允许你配置很多属性来配置每条语句的行为细节
属性描述id在命名空间中唯一的标识符可以被用来引用这条语句。resultType期望从这条语句中返回结果的类全限定名或别名。 注意如果返回的是集合那应该设置为集合包含的类型而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性如果你对其理解透彻许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。timeout这个设置是在抛出异常之前驱动程序等待数据库返回请求结果的秒数。默认值为未设置unset依赖数据库驱动。statementType可选 STATEMENTPREPARED 或 CALLABLE。这会让 MyBatis 分别使用 StatementPreparedStatement 或 CallableStatement默认值PREPARED。 **insert****, **update** 和 ****delete**标签 数据变更语句 insertupdate 和 delete 的实现非常接近
insertidinsertAuthorstatementTypePREPAREDkeyPropertykeyColumnuseGeneratedKeystimeout20...
/insert
updateidupdateAuthorstatementTypePREPAREDtimeout20...
/update
deleteiddeleteAuthorstatementTypePREPAREDtimeout20...
/delete属性描述id在命名空间中唯一的标识符可以被用来引用这条语句。timeout这个设置是在抛出异常之前驱动程序等待数据库返回请求结果的秒数。默认值为未设置unset依赖数据库驱动。statementType可选 STATEMENTPREPARED 或 CALLABLE。这会让 MyBatis 分别使用 StatementPreparedStatement 或 CallableStatement默认值PREPARED。useGeneratedKeys仅适用于 insert 和 update这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键比如像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段默认值false。keyProperty仅适用于 insert 和 update指定能够唯一识别对象的属性MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值默认值未设置unset。如果生成列不止一个可以用逗号分隔多个属性名称。keyColumn仅适用于 insert 和 update设置生成键值在表中的列名在某些数据库像 PostgreSQL中当主键列不是表中的第一列的时候是必须设置的。如果生成列不止一个可以用逗号分隔多个属性名称。