什么是MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射(ORM)。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
在spring的环境下使用mybatis
配置pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.softeem</groupId>
<artifactId>spring-mybatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.version>5.2.0.RELEASE</spring.version>
<mybatis.version>3.5.4</mybatis.version>
<mybatis-spring.version>2.0.4</mybatis-spring.version>
<hikaricp.version>3.4.2</hikaricp.version>
<hsqldb.version>2.5.0</hsqldb.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
</dependencies>
</project>
SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
在这里我们的运行环境是Spring容器,而容器要管理所有参与业务的bean,自然SqlSeesionFactory对象也可以委托给spring容器来创建
AppConfig
package com.softeem;
import com.alibaba.druid.pool.DruidDataSource;
import com.softeem.service.TextService;
import com.softeem.service.UserService;
import com.softeem.service.impl.UserServiceImpl;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
/**
* @Author fyj 2020/9/18 11:11
*/
@ComponentScan
@Configuration
@PropertySource("jdbc.properties")
@MapperScan("com.softeem.dao")
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSource createDataSource(
@Value("${user}") String user,
@Value("${password}") String password,
@Value("${url}") String url,
@Value("${driver}") String driver
){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(user);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager createTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean
SqlSessionFactoryBean createFactoryBean(@Autowired DataSource dataSource) throws IOException {
SqlSessionFactoryBean sf = new SqlSessionFactoryBean();
sf.setDataSource(dataSource);
return sf;
}
}
我们引入了数据源,事物管理器。由于SqlSessionFactory是一个接口,而Spring为了能够和Mybatis整合,故而实现了该接口,实现类的名称为SqlSessionFactoryBean, 我们通过@Bean将其加载至容器中
半ORM和优秀灵活的SQL
Mybatis醉的的特点应该是它只是一个半ORM框架,我们依旧需要编写SQL语句,它简化了我们最终将结果集resultSet映射到对象的这一步操作。Mybatis相比较于JDBC,在编写SQL上进行了一些优化,提供了一些例如可以帮助我们实现动态SQL的标签库
mybatis示例
@Select("select * from user")
public List<User> listUsers();
@Select("select * from user where id = #{value}")
public User getUserById(int id);
使用Mybatis
- 引用相关的pom支持
- 在Configuration(AppConfig.java)中配置SqlSessionFaction到容器,该对象需要传入一个DataSource数据源
- 编写Mapper接口,定义对应得方法
- 添加注解,写入SQL语句
- 在Configuration类中添加注解@MapperScan去扫描mybatis的mapper
- 运行
注解方式
如果是简单的sql语句使用注解方法很容易,但是如果是比较复杂的sql语句那么使用注解的方式就比较困难了
例如
@Select("<script>" +
"select * from user" +
" where 1=1 " +
"<if test='username!=null'>" +
"and username = #{username}" +
"</if>" +
"<if test='password!=null'>" +
"and password = #{password}" +
"</if>" +
"</script>")
public List<User> listUsersByUserNameOrPassword(@Param("username") String username,@Param("password") String password);
这样的可读性太差了,所以非常不推荐在有一些复杂的需求的情况下使用这种方法
配置文件方法
上面比较复杂的sql语句,我们可以采用配置文件的形式来实现
实例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.softeem.mapper.BookMapper">
<resultMap id="BaseResultMap" type="com.softeem.entity.Book">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="cover" jdbcType="VARCHAR" property="cover" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="author" jdbcType="VARCHAR" property="author" />
<result column="date" jdbcType="VARCHAR" property="date" />
<result column="press" jdbcType="VARCHAR" property="press" />
<result column="abs" jdbcType="VARCHAR" property="abs" />
<result column="cid" jdbcType="INTEGER" property="cid" />
</resultMap>
<sql id="Base_Column_List">
id, cover, title, author, `date`, press, `abs`, cid
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from book
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from book
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book (cover, title, author,
`date`, press, `abs`, cid
)
values (#{cover,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{author,jdbcType=VARCHAR},
#{date,jdbcType=VARCHAR}, #{press,jdbcType=VARCHAR}, #{abs,jdbcType=VARCHAR}, #{cid,jdbcType=INTEGER}
)
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="cover != null">
cover,
</if>
<if test="title != null">
title,
</if>
<if test="author != null">
author,
</if>
<if test="date != null">
`date`,
</if>
<if test="press != null">
press,
</if>
<if test="abs != null">
`abs`,
</if>
<if test="cid != null">
cid,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="cover != null">
#{cover,jdbcType=VARCHAR},
</if>
<if test="title != null">
#{title,jdbcType=VARCHAR},
</if>
<if test="author != null">
#{author,jdbcType=VARCHAR},
</if>
<if test="date != null">
#{date,jdbcType=VARCHAR},
</if>
<if test="press != null">
#{press,jdbcType=VARCHAR},
</if>
<if test="abs != null">
#{abs,jdbcType=VARCHAR},
</if>
<if test="cid != null">
#{cid,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.softeem.entity.Book">
update book
<set>
<if test="cover != null">
cover = #{cover,jdbcType=VARCHAR},
</if>
<if test="title != null">
title = #{title,jdbcType=VARCHAR},
</if>
<if test="author != null">
author = #{author,jdbcType=VARCHAR},
</if>
<if test="date != null">
`date` = #{date,jdbcType=VARCHAR},
</if>
<if test="press != null">
press = #{press,jdbcType=VARCHAR},
</if>
<if test="abs != null">
`abs` = #{abs,jdbcType=VARCHAR},
</if>
<if test="cid != null">
cid = #{cid,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.softeem.entity.Book">
update book
set cover = #{cover,jdbcType=VARCHAR},
title = #{title,jdbcType=VARCHAR},
author = #{author,jdbcType=VARCHAR},
`date` = #{date,jdbcType=VARCHAR},
press = #{press,jdbcType=VARCHAR},
`abs` = #{abs,jdbcType=VARCHAR},
cid = #{cid,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.softeem.mapper.BookMapper">
<resultMap id="BaseResultMap" type="com.softeem.entity.Book">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="cover" jdbcType="VARCHAR" property="cover" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="author" jdbcType="VARCHAR" property="author" />
<result column="date" jdbcType="VARCHAR" property="date" />
<result column="press" jdbcType="VARCHAR" property="press" />
<result column="abs" jdbcType="VARCHAR" property="abs" />
<result column="cid" jdbcType="INTEGER" property="cid" />
</resultMap>
<sql id="Base_Column_List">
id, cover, title, author, `date`, press, `abs`, cid
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from book
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from book
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book (cover, title, author,
`date`, press, `abs`, cid
)
values (#{cover,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{author,jdbcType=VARCHAR},
#{date,jdbcType=VARCHAR}, #{press,jdbcType=VARCHAR}, #{abs,jdbcType=VARCHAR}, #{cid,jdbcType=INTEGER}
)
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="cover != null">
cover,
</if>
<if test="title != null">
title,
</if>
<if test="author != null">
author,
</if>
<if test="date != null">
`date`,
</if>
<if test="press != null">
press,
</if>
<if test="abs != null">
`abs`,
</if>
<if test="cid != null">
cid,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="cover != null">
#{cover,jdbcType=VARCHAR},
</if>
<if test="title != null">
#{title,jdbcType=VARCHAR},
</if>
<if test="author != null">
#{author,jdbcType=VARCHAR},
</if>
<if test="date != null">
#{date,jdbcType=VARCHAR},
</if>
<if test="press != null">
#{press,jdbcType=VARCHAR},
</if>
<if test="abs != null">
#{abs,jdbcType=VARCHAR},
</if>
<if test="cid != null">
#{cid,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.softeem.entity.Book">
update book
<set>
<if test="cover != null">
cover = #{cover,jdbcType=VARCHAR},
</if>
<if test="title != null">
title = #{title,jdbcType=VARCHAR},
</if>
<if test="author != null">
author = #{author,jdbcType=VARCHAR},
</if>
<if test="date != null">
`date` = #{date,jdbcType=VARCHAR},
</if>
<if test="press != null">
press = #{press,jdbcType=VARCHAR},
</if>
<if test="abs != null">
`abs` = #{abs,jdbcType=VARCHAR},
</if>
<if test="cid != null">
cid = #{cid,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.softeem.entity.Book">
update book
set cover = #{cover,jdbcType=VARCHAR},
title = #{title,jdbcType=VARCHAR},
author = #{author,jdbcType=VARCHAR},
`date` = #{date,jdbcType=VARCHAR},
press = #{press,jdbcType=VARCHAR},
`abs` = #{abs,jdbcType=VARCHAR},
cid = #{cid,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
准确的说,这个叫映射文件,用来和mapper相对应,mapper接口中定义的方法,在该文件中进行实现。上面的例子基于idea插件的自动生成,同时为我们生成了基本的增删改查语句
那么我们是使用当下大火的注解还是使用传统的配置文件方法呢?
当然是两者都用啊!
- 保留原有的mapperScan注解,Spring容器会继续扫描mybatis的注解
- 对mybatis的核心对象SQLSessionFactory进行配置
@Bean
public SqlSessionFactoryBean createSqlSessionFactory(@Autowired DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/*.xml"));
sqlSessionFactory.setDataSource(dataSource);
return sqlSessionFactory;
}
也就是说加一句代码就可以了
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“classpath:/mapper/*.xml”));
动态SQl
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim、where、set
前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
set user=#{user},pwd=#{pwd} (1,23,45)
<trim prefix="(" suffixOverrides="," suffix=")">
</trim>
<trim prefix="set" suffixOverrides=",">
<if test="user!=null">
user = #{user}
</if>
<if test="pwd!=null">
pwd = #{pwd}
</if>
</trim>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
关于ORM
ORM一般称为对象关系映射,将数据库中的关系转移到JavaBean,在表与表的关系中,大多数情况下都是多对一/一对多关系,多对多,像这种关系我们可以使用外键来建立联系,且永远由多方来维护关系
假如有图书类Book,图书类型类Category,在数据库中图书与图书类型是多对一的关系(一般由多的一方来维护两方的关系);那么在Java里 我们在Book类中加一个Category属性。即:
package com.softeem.entity;
import java.io.Serializable;
/**
* book
* @author
*/
public class Book implements Serializable {
private Integer id;
private String cover;
private Category category;
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
private String title;
private String author;
private String date;
private String press;
private String abs;
private Integer cid;
private static final long serialVersionUID = 1L;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCover() {
return cover;
}
public void setCover(String cover) {
this.cover = cover;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getPress() {
return press;
}
public void setPress(String press) {
this.press = press;
}
public String getAbs() {
return abs;
}
public void setAbs(String abs) {
this.abs = abs;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
}
在mapper下的BookDao.xml中,添加
<association property="category" javaType="com.softeem.entity.Category">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</association>
注意是在图书的内部添加
即我们在查询图书的同时 查询了category对象(图书类型)
当然如果数据库里属性名一样的话会产出冲突,即查询数据只有一条,且相同属性名查询的值会变成一样。所以我们会将相同的属性名取别名来查询,