Java EE轻量级框架应用实战:SSM框架(Spring MVC+Spring+MyBatis)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.4 使用接口实现条件查询

2.4.1 使用select元素完成单条件查询

与查询对应的select元素是使用MyBatis框架时最常用的元素。在前面已经实现了对用户表的简单查询,现在需要实现带参数和返回复杂类型的查询,就要先了解<select>的属性。以实现根据用户名模糊查询来获取用户列表信息为例,SQL映射语句见示例19。

img【示例19】 UserMapper.xml

img

这是一个id为getUserListByUserName的映射语句,参数类型为string,返回结果的类型是User。为了使数据库查询的结果和返回类型中的属性能够自动匹配以便开发,对于MySQL数据库和JavaBean都会采用同一套命名规则,即Java命名驼峰规则,这样就不需要再做映射(数据库表的字段名和属性名不一致时需要手动映射)。注意参数的传递使用“#{参数名}”,它告诉MyBatis生成PreparedStatement参数。对于JDBC该参数会被标识为“?”,若采用JDBC来实现,代码的形式如下:

img

由此可以看出,MyBatis框架可以节省大量的代码。如果想完成复杂一些的查询或者让配置文件更简洁些,还需要进一步了解select元素的属性和MyBatis框架配置文件的属性。

(1)id:表示命名空间中唯一的标识符,可以用来引用这条语句。

(2)parameterType:表示查询语句传入参数类型的完全限定名或别名。它支持基础数据类型和复杂数据类型。在上面的示例中使用的是基础数据类型“string”,代表String,这是一个属于内建的类型别名。对于普通的Java类型,都有许多内建的类型别名,并且它们对大小写不敏感。除了内建的类型别名,还可以为自定义的类设置别名。已经讲过了关于别名(typeAliases)在mybatis-config.xml中的设置。在映射文件中可直接使用别名,以减少配置文件的代码。

(3)resultType:表示查询语句返回结果类型的完全限定名或别名。别名的使用方式与parameterType是一样的。

2.4.2 使用select元素完成多条件查询

上述示例是通过一个条件对用户表进行查询操作,但是在实际应用中,数据查询会有多种条件,结果也会有各种类型,如图2.10所示。

img

图2.10 百货中心供应链管理系统的用户列表页面

上图中查询条件包括用户名称(模糊查询)、用户角色,对于多条件查询的实现,常用的方式有以下几种。

1.对象入参

将查询条件封装成对象进行入参,改造UserMapper.java代码,见示例20。

img【示例20】 UserMapper.java

img

改造UserMapper.xml代码,见示例21。

img【示例21】 UserMapper.xml

img

改造测试类UserMapperTest.java代码,见示例22。

img【示例22】 UserMapperTest.java

img

在上述示例中,parameterType使用了复杂数据类型,把条件参数封装成User对象进行入参。

将User对象中userName和userRole两个属性分别进行赋值,在映射的查询语句中设置parameterType为User类型,传入参数分别使用#{userName}和#{userRole}来表示,即#{属性名}(参数对象中的属性名)。

2.Map对象入参

parameterType支持的复杂数据类型除了JavaBean,还包括Map类型,改造示例22,把用户名和用户角色封装成Map对象进行入参,测试类UserMapperTest.java部分代码见示例23。

img【示例23】 UserMapperTest.java

img

改造UserMapper.java,把封装好的userMap作为参数传入接口方法,见示例24。

img【示例24】 UserMapper.java

img

改造UserMapper.xml,parameterType设置为Map,SQL语句中的参数值使用#{uName}和#{uRole}来表示,即#{Map的key},见示例25。

img【示例25】 UserMapper.xml

img

这种做法更加灵活,不管是什么类型的参数,或者多少个参数,都可以把它封装成Map数据结构进行入参,通过Map的key即可获取传入的值。

img

MyBatis框架传入参数类型可以是Java基础数据类型,但是只适用于一个参数的情况,通过#{参数名}即可获取传入的值。若是多参数的情况就需要使用复杂数据类型来支持,包括Java实体类、Map,可通过#{属性名}或#{Map的key}来获取传入的参数值。

3.使用@Param注解

在上述示例中是将两种不同类型数据传入实现查询用户信息的操作,对于此需求,若按照之前封装成User对象的方式进行传参,有点大材小用并不是很合适,还可以用更灵活的方式处理。通过使用定义接口直接进行多参数入参即可,其代码可读性高,还可清晰地看出这个接口方法所需的参数。

修改UserMapper接口,当方法参数有多个时,每个参数前都需增加@Param注解,具体见示例26。

img【示例26】 UserMapper.java

img

使用注解@Param传入多个参数,如@Param("userName")String username,相当于将该参数username重命名为userName,在映射的SQL中需要使用#{注解名称},如#{userName}。

下面继续修改UserMapper.xml,增加id为findUserListByAnnotation的SQL映射,见示例27。

img【示例27】 UserMapper.xml

img

这种做法更加灵活,不用考虑什么类型的参数,或者多少个参数,都可以使用注解实现,当参数太多时通常会选择实体对象入参。这里需要注意的是,在UserMapper.xml文件的<select>中不能写parameterType属性,也不好写传参类型,因此后面配合注解使用接口的操作语句是不需要写parameterType属性的。

测试类UserMapperTest.java部分见示例28。

img【示例28】 UserMapperTest.java

img

在该方法中不需要再封装User对象,直接进行两个参数的入参即可。

2.4.3 实现查询结果的展现

通过上节的学习已经完成了传入多条件的查询操作,但是对于结果列只能展示出用户表(tb_user)中所有字段的值,如用户表中userRole字段记录的是角色id,而不是其对应的角色名称。在实际应用中作为列表页的展示,用户关注的往往是角色名称而不是角色id(见图2.10),那么应该如何解决这类问题?

有两种解决方案,简单介绍并分析如下。

1.使用resultType做自动映射

修改POJO:User.java,增加userRoleName属性,并修改查询用户列表的SQL语句,对用户表(tb_user)和角色表(dsscm_role)进行连表查询,使用resultType做自动映射。

下面通过一个具体案例来演示在此种情况的使用。在cn.dsscm.pojo包中创建持久化类User,并在类中定义id、username和age等属性,以及getter/setter方法和toString()方法,见示例29。

img【示例29】 User.java

img

在cn.dsscm.dao包下创建映射文件UserMapper.xml,并在映射文件中编写映射查询语句,给roleName字段取别名为userRoleName,见示例30。

img【示例30】 UserMapper.xml

img

在测试类中编写测试方法findAllUserTest1(),见示例31。

img【示例31】 UserMapperTest.java

img

使用JUnit4执行上述方法后,控制台的输出结果如下:

img

从控制台的输出可以看出,虽然tb_user表的列名与User对象的属性名完全不一样,但查询出的数据还是被正确地封装到User对象中了。

img

MyBatis框架使用resultType做自动映射时,一定要注意:字段名和POJO的属性名必须一致。若不一致,则需要给字段起别名,保证其别名与属性名一致。

2.通过resultMap来映射自定义结果(推荐使用)

使用resultMap做自定义结果映射时,字段名可以不一致,并且还能指定要显示的列,操作比较灵活,应用也广泛。

下面,通过一个具体案例来演示<resultMap>元素在此种情况的使用,具体步骤如下。

在cn.dsscm.pojo包中创建持久化类User,并在类中定义id、username和age等属性,以及其getter/setter方法和toString()方法,同示例29。

在cn.dsscm.dao包下,创建映射文件UserMapper.xml,并在映射文件中编写映射查询语句,见示例32。

img【示例32】 UserMapper.xml

img

在示例中,<resultMap>的子元素<id>和<result>的property属性表示User类的属性名,column属性表示数据表tb_user的列名。其中userRoleName是实体类属性,roleName是表中列名;<select>元素的resultMap属性表示引用上面定义的resultMap。

在配置文件mybatis-config.xml中引入UserMapper.xml。在测试类中,编写测试方法findAllUserTest2(),见示例33。

img【示例33】 UserMapperTest.java

img

使用JUnit4执行上述方法后,控制台的输出结果如下:

img

从控制台的输出可以看出,虽然tb_user表的列名与User对象的属性名完全不一样,但查询出的数据还是被正确地封装到了User对象中,与修改POJO通过resultType实现的效果相同。

resultMap元素用来描述如何将结果集映射到Java对象,此处使用resultMap元素对列表展示所需的必要字段来进行自由映射,特别是在数据库的字段名和POJO类中的属性名不一致的情况下,如角色名称的字段名column是roleName,而User对象的属性名则为userRoleName,此时就需要做映射。

resultMap元素的属性值和子节点的具体内容如下。

(1)id属性:唯一标识,此id值用于select元素的resultMap属性引用。

(2)type属性:表示该resultMap的映射结果类型。

(3)result子节点:用于标识一些简单属性,其中column属性表示从数据库中查询的字段名;property则表示查询出来的字段对应的值赋给实体对象的哪个属性。

最后在测试类中进行相关字段的输出,展示列表(用户编码、用户名称、性别、年龄、电话、用户角色)。应注意此处用户角色不再是角色id,输出的是角色名称。

3.resultType和resultMap这两个元素的关联和区别

MyBatis框架中在对查询进行select映射时,返回类型既可以用resultType,也可以用resultMap。那么resultType和resultMap到底有何关联和区别?应用场景又是什么呢?下面进行详细讲解。

(1)resultType

resultType直接表示返回类型,包括基础数据类型和复杂数据类型。

(2)resultMap

resultMap是对外部resultMap定义的引用,即对应外部resultMap的id,表示返回结果映射到哪一个resultMap上。它的应用场景一般是,数据库字段信息与对象属性不一致,或者需要做复杂的联合查询以便自由控制映射结果。除此之外,还可以通过<resultMap>元素中的<association>和<collection>处理多表时的关联关系。关于关联关系的内容,将在后续章节详细讲解,这里就不再叙述。

(3)resultType和resultMap的关联

在MyBatis框架进行查询映射时,将查询出来的每个字段值都放在一个对应的Map里面,其中键是字段名,值则是其对应的值。当select元素提供的返回类型属性是resultType时,MyBatis框架会将Map里面的键值对取出赋给resultType所指定的对象对应的属性(调用对应对象里属性的setter方法进行填充)。正因为如此,当使用resultType时,直接在后台就能接收到其相应的对象属性值。由此可看出,MyBatis框架的每个查询映射的返回类型都是resultMap,只是当提供的返回类型属性是resultType时,MyBatis框架会自动把对应的值赋给resultType所指定对象的属性,当提供的返回类型是resultMap时,会因为Map不能很好地表示领域模型,还需要通过进一步的定义把它转化为对应的实体对象。

当返回类型是resultMap时,也是非常有用的。它主要用在进行复杂联合查询上,当然在进行简单查询时使用resultType就足够了。

img

在MyBatis框架的select元素中,resultType和resultMap这两个元素本质上是一样的,都是Map数据结构。但需要明确一点,resultType属性和resultMap属性绝对不能同时存在,只能二者选其一使用。

4.resultMap元素的自动映射级别

在上面的示例中选择部分字段进行resultMap元素映射时,希望没有映射的字段不能在后台进行查询并输出,即使SQL语句中是查询所有字段(select * from …),因为使用resultMap元素也是为了自由灵活地控制映射结果,达到只对关心的属性进行赋值填充的目的。修改测试类(UserMapperTest.java)的输出项,见示例34。

img【示例34】 UserMapperTest.java

img

在该示例代码中,对比之前设置的resultMap元素映射的属性,增加了address和age两个属性值的输出,通过观察结果,发现address和age两个属性的值均可正常输出。

为何age和address两个属性并没有在resultMap元素中做映射关联却能正常输出结果?若更改需求为:没有在resultMap内映射的字段不能获取,那么又该如何实现?

这种情况跟resultMap元素的自动映射级别有关,默认的映射级别为PARTIAL。若要满足需求,则需要设置MyBatis框架对于resultMap元素的自动映射级别(autoMappingBehavior)为NONE,即禁止自动匹配。修改mybatis-config.xml,见示例35。

img【示例35】 设置MyBatis框架对于resultMap元素的自动映射级别

img

增加以上的设置之后,再进行结果的输出,从输出结果中发现address属性值为null,该属性没有进行自动setter赋值,但是age的属性值仍为30,并非为空,这是因为age属性值并非直接取自数据表,而是在getAge()方法中通过birthday属性计算得出的,只要加载了birthday就可以计算出属性值。大家可参考素材中User.java中的getAge()方法进行更多的了解。

img

在MyBatis框架中,使用resultMap元素能够进行自动映射匹配的前提是字段名和属性名要一致,在默认映射级别(PARTIAL)情况下,若一致,即使没有进行属性名和字段名的匹配,也可以在后台获取到未匹配过的属性值;若不一致,且在resultMap元素里没有进行映射,那么就无法在后台获取并输出。

2.4.4 技能训练

上机练习4 实现供应商表的查询

需求说明

(1)实现按条件查询订单表,查询条件如下。

①商品名称(模糊查询)。

②供应商(供应商id)。

③是否付款。

(2)查询结果列显示:订单编码、商品名称、供应商名称、账单金额、是否付款、创建时间。

(3)必须使用resultMap元素进行显示列表字段的自定义映射。

img

(1)修改Bill.java,增加属性providerName。

(2)编写SQL查询语句(连表查询)。

(3)在SQL映射文件中创建resultMap元素自定义映射结果,并在select元素中引用。

思考:该练习的需求是多条件查询,那么作为查询条件应该是多条件入参,可采用封装对象入参,或者直接进行多参数入参(为查询方法定义3个入参)的方式。完成编码之后,运行测试类,查看直接传入多个参数的做法是否会报错?若报错,该如何处理,将在后续内容讲解。